diff --git a/CMakeLists.txt b/CMakeLists.txt index caee04a7a..1bf22a333 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,13 @@ if (USE_SDL2) # If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses. # This should probably go in a FAQ. set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE) + + # https://github.com/tildearrow/furnace/issues/1237 + # enabling this will result in SDL finding the Direct3D headers, forcing _WIN32_WINNT to an undesirable value (which makes the Wine headers define GetTickCount64) + if (SUPPORT_XP) + set(SDL_RENDER_D3D OFF CACHE BOOL "Enable the Direct3D render driver" FORCE) + endif() + add_subdirectory(extern/SDL EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_DEFINES HAVE_SDL2) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include) @@ -498,6 +505,8 @@ src/engine/platform/sound/sm8521.c src/engine/platform/sound/d65modified.c +src/engine/platform/sound/ted-sound.c + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp @@ -588,6 +597,8 @@ src/engine/platform/k007232.cpp src/engine/platform/ga20.cpp src/engine/platform/sm8521.cpp src/engine/platform/pv1000.cpp +src/engine/platform/k053260.cpp +src/engine/platform/ted.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp @@ -695,6 +706,7 @@ src/gui/subSongs.cpp src/gui/sysConf.cpp src/gui/sysEx.cpp src/gui/sysManager.cpp +src/gui/sysPartNumber.cpp src/gui/sysPicker.cpp src/gui/tutorial.cpp src/gui/util.cpp 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 6ae675deb..f4d4af3c3 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt (warning: 0.5.8!). - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. +- **Void Linux**: [furnace](https://github.com/void-linux/void-packages/tree/master/srcpkgs/furnace) is available in the official repository. --- # developer info diff --git a/demos/a2600/Coconut_Mall.fur b/demos/a2600/Coconut_Mall.fur deleted file mode 100644 index 471fcbec3..000000000 Binary files a/demos/a2600/Coconut_Mall.fur and /dev/null differ diff --git a/demos/arcade/Maximum_Overdrive_NamcoWSG.fur b/demos/arcade/Maximum_Overdrive_NamcoWSG.fur index 0fdbc113d..2f25c28ef 100644 Binary files a/demos/arcade/Maximum_Overdrive_NamcoWSG.fur and b/demos/arcade/Maximum_Overdrive_NamcoWSG.fur differ diff --git a/demos/arcade/Point_of_No_Return_SegaPCM.fur b/demos/arcade/Point_of_No_Return_SegaPCM.fur new file mode 100644 index 000000000..846d3680e Binary files /dev/null and b/demos/arcade/Point_of_No_Return_SegaPCM.fur differ diff --git a/demos/ay8910/vibe_zone.fur b/demos/ay8910/vibe_zone.fur new file mode 100644 index 000000000..ffd97b273 Binary files /dev/null and b/demos/ay8910/vibe_zone.fur differ diff --git a/demos/ay8930/PlayingOnTheStairs.fur b/demos/ay8930/PlayingOnTheStairs.fur index 0391fb4fe..80c5fd82a 100644 Binary files a/demos/ay8930/PlayingOnTheStairs.fur and b/demos/ay8930/PlayingOnTheStairs.fur differ diff --git a/demos/ay8930/joyful_.fur b/demos/ay8930/joyful.fur similarity index 100% rename from demos/ay8930/joyful_.fur rename to demos/ay8930/joyful.fur diff --git a/demos/misc/FiveTwoThreeTwo_MSM5232.fur b/demos/misc/FiveTwoThreeTwo_MSM5232.fur new file mode 100644 index 000000000..4c5efae9c Binary files /dev/null and b/demos/misc/FiveTwoThreeTwo_MSM5232.fur differ diff --git a/demos/a2600/atari breakbeat.fur b/demos/misc/atari_breakbeat_TIA.fur similarity index 100% rename from demos/a2600/atari breakbeat.fur rename to demos/misc/atari_breakbeat_TIA.fur diff --git a/demos/a2600/the_erfngjt.fur b/demos/misc/the_erfngjt_TIA.fur similarity index 100% rename from demos/a2600/the_erfngjt.fur rename to demos/misc/the_erfngjt_TIA.fur diff --git a/demos/msx/21492413.fur b/demos/msx/21492413.fur new file mode 100644 index 000000000..95669441d Binary files /dev/null and b/demos/msx/21492413.fur differ diff --git a/demos/msx/Il_ambreSong.fur b/demos/msx/ranburu_song.fur similarity index 100% rename from demos/msx/Il_ambreSong.fur rename to demos/msx/ranburu_song.fur 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/doc/1-intro/README.md b/doc/1-intro/README.md index 34658ece3..8e9289fc6 100644 --- a/doc/1-intro/README.md +++ b/doc/1-intro/README.md @@ -19,3 +19,5 @@ due to its nature of being feature-packed, it may be technical and somewhat diff it also has a flexible windowing system which you may move around and organize. see [2-interface](../2-interface/README.md) and [3-pattern](../3-pattern/README.md) for more information. + +once familiar with the tracker, look to [9-guides](../9-guides/README.md) for useful techniques. \ 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 6de22e929..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) diff --git a/doc/2-interface/asset-list.md b/doc/2-interface/asset-list.md index 936c4a32f..836d9b694 100644 --- a/doc/2-interface/asset-list.md +++ b/doc/2-interface/asset-list.md @@ -33,6 +33,6 @@ Everything from the instrument list applies here also, with one major difference ![samples window](samples.png) -Everything from the wavetables list applies here also, with the addition of two buttons: +Everything from the wavetables list applies here also, with the addition of two buttons before the Delete button: - **Preview**: Plays the selected sample at its default note. - **Stop preview**: Stops the sample playback. diff --git a/doc/2-interface/instruments-folder.png b/doc/2-interface/instruments-folder.png index a2fbb5ab2..0afe5e0e9 100644 Binary files a/doc/2-interface/instruments-folder.png and b/doc/2-interface/instruments-folder.png differ diff --git a/doc/2-interface/instruments.png b/doc/2-interface/instruments.png index 00102e543..56f994957 100644 Binary files a/doc/2-interface/instruments.png and b/doc/2-interface/instruments.png differ diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md index 822f38897..9a5c2d17b 100644 --- a/doc/2-interface/menu-bar.md +++ b/doc/2-interface/menu-bar.md @@ -1,6 +1,8 @@ # 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. + +items in _italics_ don't appear in basic mode and are only available in advanced mode. # file @@ -38,12 +40,12 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - **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. +- _**add chip...**:_ add a chip to the current song. +- _**configure chip...**:_ set a chip's parameters. - for a list of parameters, see [7-systems](../7-systems/README.md). -- **change chip...**: change a chip to another. +- _**change chip...**:_ change a chip to another. - **Preserve channel positions**: enable this option to make sure Furnace does not auto-arrange/delete channels to compensate for differing channel counts. this can be useful for doing ports, e.g. from Genesis to PC-98. -- **remove chip...**: remove a chip. +- _**remove chip...**_: remove a chip. - **Preserve channel positions**: same thing as above. - **restore backup**: restore a previously saved backup. @@ -99,10 +101,10 @@ the following settings exist: - some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version. - **add pattern change hints**: this option adds a "hint" when a pattern change occurs. only useful if you're a developer. - the format of the "hint" data block that gets written is: `67 66 FE ll ll ll ll 01 oo rr pp pp pp ...` - - ll: length, a 32-bit little-endian number - - oo: order - - rr: initial row (a 0Dxx effect is able to select a different row) - - pp: pattern index (one per channel) + - `ll`: length, a 32-bit little-endian number + - `oo`: order + - `rr`: initial row (a 0Dxx effect is able to select a different row) + - `pp`: pattern index (one per channel) - **direct stream mode**: this option allows DualPCM to work. don't use this for other chips. - may or may not play well with hardware VGM players. @@ -140,7 +142,7 @@ it's not really useful, unless you're a developer and want to use a command stre - **cut**: moves the current selection in the pattern view to clipboard. - **copy**: copies the current selection in the pattern view to clipboard. - **paste**: inserts the clipboard's contents in the cursor position. -- **paste special...**: variants of the paste feature. +- _**paste special...**:_ variants of the paste feature. - **paste mix**: inserts the clipboard's contents in the cursor position, but does not erase the occupied region. - **paste mix (background)**: does the same thing as paste mix, but doesn't alter content which is already there. - **paste with ins (foreground)**: same thing as paste mix, but changes the instrument. @@ -154,8 +156,8 @@ it's not really useful, unless you're a developer and want to use a command stre - 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. @@ -176,7 +178,7 @@ it's not really useful, unless you're a developer and want to use a command stre - **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. @@ -188,7 +190,7 @@ it's not really useful, unless you're a developer and want to use a command stre - **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. @@ -199,46 +201,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. these are detailed in [settings.md]. +- **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. +all these menu items show or hide their associated windows. -- **instrument editor**: shows/hides the Instrument Editor -- **wavetable editor**: shows/hides the Wavetable Editor. -- **sample editor**: shows/hides the Sample Editor. +- [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) -- **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. +- [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/samples.png b/doc/2-interface/samples.png index 2be62a9ba..654ff8787 100644 Binary files a/doc/2-interface/samples.png and b/doc/2-interface/samples.png differ diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md index 06c21f1c9..d6ed79c60 100644 --- a/doc/2-interface/settings.md +++ b/doc/2-interface/settings.md @@ -181,37 +181,19 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **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()** + + +- **Arcade/YM2151 core**\ + **Genesis/YM2612 core**\ + **SN76489 core**\ + **NES core**\ + **FDS core**\ + **SID core**\ + **POKEY core**\ + **OPN/OPNA/OPNB cores**: all of these are covered in the [guide to choosing emulation cores](../9-guides/emulation-cores.md). + +- **PC Speaker strategy**: this is covered in the [PC speaker system doc](../7-systems/pcspkr.md). - **Sample ROMs:** - **OPL4 YRW801 path** diff --git a/doc/2-interface/song-info.md b/doc/2-interface/song-info.md index 7919e7bec..03d698576 100644 --- a/doc/2-interface/song-info.md +++ b/doc/2-interface/song-info.md @@ -1,13 +1,13 @@ # song info - **Name**: The track's title. -- **Author**: List of contributors to a song. If the song is a cover of someone else's track, it's customary to list their name first, followed by `[cv. YourName]`. +- **Author**: List of contributors to a song. If the song is a cover of someone else's work, it's customary to list their name first, followed by `[cv. YourName]`. - **Album**: The associated album name, the name of the game the song is from, or whatever. -- **System**: The game console or computer the track is designed for. This is automatically set when creating a new tune, but it can be changed to anything one wants. The **Auto** button will provide a guess based on the chips in use. +- **System**: The game console or computer the track is designed for. This is automatically set when creating a new tune, but in advanced mode, it can be changed to anything one wants. The **Auto** button will provide a guess based on the chips in use. All of this metadata will be included in a VGM export. This isn't the case for a WAV export, however. -**Tuning (A-4)**: Set tuning based on the note A-4, which should be 440 in most cases. Opening an Amiga MOD will set it to 436 for hardware compatibility. +- _**Tuning (A-4)**:_ Set tuning based on the note A-4, which should be 440 in most cases. Opening an Amiga MOD will set it to 436 for hardware compatibility. Available only in advanced mode. # subsongs @@ -23,6 +23,8 @@ This window allows one to create **subsongs** - multiple individual songs within There are multiple ways to set the tempo of a song. +Items in _italics_ don't appear in basic mode and are only available in advanced mode. + **Tick Rate**: The frequency of ticks per second, thus the rate at which notes and effects are processed. - All values are allowed for all chips, though most chips have hardware limitations that mean they should stay at either 60 (approximately NTSC) or 50 (exactly PAL). - Clicking the Tick Rate button switches to a more traditional **Base Tempo** BPM setting. @@ -30,14 +32,14 @@ There are multiple ways to set the tempo of a song. **Speed**: The number of ticks per row. - Clicking the "Speed" button changes to more complex modes covered in the [grooves] page. -**Virtual Tempo**: Simulates any arbitrary tempo without altering the tick rate. It does this by adding or skipping ticks to approximate the tempo. The two numbers represent a ratio applied to the actual tick rate. Example: +_**Virtual Tempo**:_ Simulates any arbitrary tempo without altering the tick rate. It does this by adding or skipping ticks to approximate the tempo. The two numbers represent a ratio applied to the actual tick rate. Example: - Set tick rate to 150 BPM (60 Hz) and speed to 6. - Set the first virtual tempo number (numerator) to 200. - Set the second virtual tempo number (denominator) to 150. - The track will play at 200 BPM. - The ratio doesn't have to match BPM numbers. Set the numerator to 4 and the denominator to 5, and the virtual BPM becomes 150 × 4/5 = 120. -**Divider**: Changes the effective tick rate. A tick rate of 60Hz and a divisor of 6 will result in ticks lasting a tenth of a second each! +_**Divider**:_ Changes the effective tick rate. A tick rate of 60Hz and a divisor of 6 will result in ticks lasting a tenth of a second each! **Highlight**: Sets the pattern row highlights: - The first value represents the number of rows per beat. @@ -46,4 +48,4 @@ There are multiple ways to set the tempo of a song. **Pattern Length**: The length of each pattern in rows. This affects all patterns in the song, and every pattern must be the same length. (Individual patterns can be cut short by `0Bxx`, `0Dxx`, and `FFxx` commands.) -**Song Length**: How many orders are in the order list. Decreasing it will hide the orders at the bottom. Increasing it will restore those orders; increasing it further will add new orders of all `00` patterns. +_**Song Length**:_ How many orders are in the order list. Decreasing it will hide the orders at the bottom. Increasing it will restore those orders; increasing it further will add new orders of all `00` patterns. diff --git a/doc/2-interface/wavetables.png b/doc/2-interface/wavetables.png index 4ac45f2c8..3de7413c3 100644 Binary files a/doc/2-interface/wavetables.png and b/doc/2-interface/wavetables.png differ diff --git a/doc/3-pattern/README.md b/doc/3-pattern/README.md index d3e0b86f4..d7e1dd7eb 100644 --- a/doc/3-pattern/README.md +++ b/doc/3-pattern/README.md @@ -4,41 +4,85 @@ the pattern view allows you to edit the song. ![pattern view](pattern.png) -a pattern consists of columns ("channels") and rows. +a pattern consists of columns ("channels") and numbered rows. each column has several subcolumns in this order: 1. note 2. instrument 3. volume -4. effect and effect value (several) +4. effects, split into effect type and effect value all columns are represented in hexadecimal, except for the note column. -# managing channels +row highlights show beats and measures, and are configured in the [the Speed window](../2-interface/song-info.md). -you may mute channels, toggle solo mode, collapse channels or even hide them. -clicking on a channel name mutes that channel. -double-clicking or right-clicking it enables solo mode, in where only that channel will be audible. - -clicking the `++` at the top left corner of the pattern view displays additional buttons for channel configuration: - -![channel bar](channelbar.png) - -to rename and/or hide channels, see the Channels window (window > channels). - -![channels](channels.png) - -# cursor and selection +## cursor and selection you may change the cursor position by clicking anywhere on the pattern. -to select, press and hold the left mouse button. then drag the mouse and release the button to finish selection. +to select an area, press and hold the left mouse button. then drag the mouse and release the button to finish selection. -# keyboard layout +right-clicking within the pattern view brings up a pop-up menu with everything in the [edit menu](../2-interface/menu-bar.md) that makes sense for entering data or altering a selected area. -## shortcuts + + +## channel bar + +using the channel bar, you may adjust several aspects of the channel display. + +![channel bar](channelbar.png) + +clicking on a channel name mutes that channel. + +double-clicking or right-clicking it enables solo mode, in which only that channel will be audible. + +clicking the `++` at the top left corner of the pattern view cycles through three channel bar view modes: +- **Compact**: shows only channel names. +- **Expanded**: as shown above. adds buttons: + - **-**: collapse visible columns. changes to **+** when columns are hidden; click to expand them. + - **<**: disables the last effect column and hides it. effects are not deleted... + - **>**: adds an effects column. if one previously existed, its contents will be preserved. +- **Pattern names**: adds a text field with which one can name the current pattern. pattern names are also visible when hovering over a pattern in the order list. + +right-clicking the `++` toggles the visualizer, which is active only during playback. + +to rename and/or hide channels, open [the Channels window](../8-advanced/channels.md) via the window menu. + + +# input + +## note input + +![keyboard](keyboard.png) + +- pressing any of the respective keys will insert a note at the cursor's location, then advance to the next row (or otherwise according to the Edit Step.) +- **note off** turns off the last played note in that channel (key off for FM; note cut otherwise). +- **note release** triggers macro release (and in FM channels it also triggers key off). +- **macro release** does the same as above, but does not trigger key off in FM channels. +- **toggle edit** enables and disables editing. when editing is enabled, the cursor's row will be shaded red. + +## instrument/volume input + +type any hexadecimal number (0-9 and A-F). the cursor will move by the Edit Step when a suitable value is entered. + +## effect input + +works like the instrument/volume input. + +each effect column has two subcolumns: effect and effect value. +if the effect value is not present, it is treated as `00`. + +most effects run until canceled using an effect of the same type with effect value `00`, with some exceptions. + +here's [a list of effect types](effects.md). + + + +# keyboard shortcuts + +these are the default key functions. all keys are configurable in the Keyboard tab of the Settings window. key | action ------------|----------------------------------------------------------------- @@ -68,27 +112,3 @@ Ctrl-F2 | transpose selection (+1 semitone) Ctrl-F3 | transpose selection (-1 octave) Ctrl-F4 | transpose selection (+1 octave) Space | toggle note input (edit) - -## note input - -![keyboard](keyboard.png) - -- pressing any of the respective keys will insert a note at the cursor's location, and then advance it by the Edit Step. -- note off turns off the last played note in that channel (key off on FM; note cut otherwise). -- note release triggers macro release (and in FM channels it also triggers key off). -- macro release does the same as above, but does not trigger key off in FM channels. - -## instrument/volume input - -type any hexadecimal number (0-9 and A-F). the cursor will move by the Edit Step when a suitable value is entered. - -## effect input - -works like the instrument/volume input. - -each effect column has two subcolumns: effect and effect value. -if the effect value is not present, it is treated as `00`. - -most effects run until canceled using an effect of the same type with effect value `00`, with some exceptions. - -for a list of effects [click here](effects.md). diff --git a/doc/3-pattern/channelbar.png b/doc/3-pattern/channelbar.png index 6ef20cb43..dc7193e39 100644 Binary files a/doc/3-pattern/channelbar.png and b/doc/3-pattern/channelbar.png differ diff --git a/doc/3-pattern/channels.png b/doc/3-pattern/channels.png deleted file mode 100644 index ab4c985f2..000000000 Binary files a/doc/3-pattern/channels.png and /dev/null differ diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index df1f85500..91e311473 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -84,7 +84,7 @@ not all chips support these effects. ## other -- `9xxx`: **Set sample position.** jumps current sample to position `xxx \* 0x100`. +- `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. @@ -97,52 +97,52 @@ additionally, [each chip has its own effects](../7-systems/README.md). ## macro table -ID | macro ----|----------------------------- -00 | volume -01 | arpeggio -02 | duty/noise -03 | waveform -04 | pitch -05 | extra 1 -06 | extra 2 -07 | extra 3 -08 | extra A (ALG) -09 | extra B (FM) -0A | extra C (FMS) -0B | extra D (AMS) -0C | panning left -0D | panning right -0E | phase reset -0F | extra 4 -10 | extra 5 -11 | extra 6 -12 | extra 7 -13 | extra 8 -| | **operator 1 macros** -20 | AM -21 | AR -22 | DR -23 | MULT -24 | RR -25 | SL -26 | TL -27 | DT2 -28 | RS -29 | DT -2A | D2R -2B | SSG-EG -2C | DAM -2D | DVB -2E | EGT -2F | KSL -30 | SUS -31 | VIB -32 | WS -33 | KSR -40 | **operator 2 macros** -60 | **operator 3 macros** -80 | **operator 4 macros** + ID | macro +-----|----------------------------- +`00` | volume +`01` | arpeggio +`02` | duty/noise +`03` | waveform +`04` | pitch +`05` | extra 1 +`06` | extra 2 +`07` | extra 3 +`08` | extra A (ALG) +`09` | extra B (FM) +`0A` | extra C (FMS) +`0B` | extra D (AMS) +`0C` | panning left +`0D` | panning right +`0E` | phase reset +`0F` | extra 4 +`10` | extra 5 +`11` | extra 6 +`12` | extra 7 +`13` | extra 8 +| | **operator 1 macros** +`20` | AM +`21` | AR +`22` | DR +`23` | MULT +`24` | RR +`25` | SL +`26` | TL +`27` | DT2 +`28` | RS +`29` | DT +`2A` | D2R +`2B` | SSG-EG +`2C` | DAM +`2D` | DVB +`2E` | EGT +`2F` | KSL +`30` | SUS +`31` | VIB +`32` | WS +`33` | KSR +`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/3-pattern/keyboard.png b/doc/3-pattern/keyboard.png index 7042af1c1..f16f9f697 100644 Binary files a/doc/3-pattern/keyboard.png and b/doc/3-pattern/keyboard.png differ diff --git a/doc/3-pattern/pattern.png b/doc/3-pattern/pattern.png index ca059faa8..0d31fcf1f 100644 Binary files a/doc/3-pattern/pattern.png and b/doc/3-pattern/pattern.png differ diff --git a/doc/4-instrument/FM-ADSRchart.png b/doc/4-instrument/FM-ADSRchart.png new file mode 100644 index 000000000..407239c8d Binary files /dev/null and b/doc/4-instrument/FM-ADSRchart.png differ diff --git a/doc/4-instrument/README.md b/doc/4-instrument/README.md index f33126dd8..7ab7898ec 100644 --- a/doc/4-instrument/README.md +++ b/doc/4-instrument/README.md @@ -1,113 +1,122 @@ -# 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. -- [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. -- [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. +# 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 a shorter bar that controls looping. +- Click to set the start point of a loop; the end point is the last value or release point. It appears as half-height bars. 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. It appears as a full-height bar. 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 entered in the pattern, `@` 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. + + + +# wavetable + +This tab appears for PC Engine, FDS, Namco WSG, and other wavetable-based instruments. + +![wavetable tab](wavetable.png) + +When **Enable synthesizer** is off, the only option is to select a wavetable entry with the text entry box beneath the **Wave 1** preview. + +To use the wavetable synthesizer, refer to the bottom part of [the wavetable documentation](../5-wave/README.md). + + +# sample + +This tab appears for Generic PCM, SNES, Amiga, and other sample-based instruments. + +![sample tab](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. + - to set a note's sample, click the list entry in the "#" column then type the number of the sample. + - to set the pitch at which a sample is played, click the list entry in the "note" column and press the key for the new note. + diff --git a/doc/4-instrument/fm.md b/doc/4-instrument/fm.md index 08542eb32..72c0ba781 100644 --- a/doc/4-instrument/fm.md +++ b/doc/4-instrument/fm.md @@ -1,47 +1,77 @@ # FM synthesis instrument editor -FM editor is divided into 7 tabs: +The FM editor is divided into 7 tabs: - **FM**: for controlling the basic parameters of FM sound source. - **Macros (FM)**: for macros controlling algorithm, feedback and LFO -- **Macros (OP1)**: for macros controlling FM paramets of operator 1 -- **Macros (OP2)**: for macros controlling FM paramets of operator 2 -- **Macros (OP3)**: for macros controlling FM paramets of operator 3 -- **Macros (OP4)**: for macros controlling FM paramets of operator 4 -- **Macros**: for miscellaneous macros controlling volume, argeggio and YM2151 noise generator. +- **Macros (OP1)**: for macros controlling FM parameters of operator 1 +- **Macros (OP2)**: for macros controlling FM parameters of operator 2 +- **Macros (OP3)**: for macros controlling FM parameters of operator 3 +- **Macros (OP4)**: for macros controlling FM parameters of operator 4 +- **Macros**: for miscellaneous macros controlling volume, arpeggio, and YM2151 noise generator. ## FM -FM synthesizers Furnace supports are four-operator, meaning it takes four oscillators to produce a single sound. Each operator is controlled by a dozen sliders: +The FM synthesizers Furnace supports are four-operator, meaning it takes four oscillators to produce a single sound. +These apply to the instrument as a whole: +- **Feedback (FB)**: Determines how many times operator 1 returns its output to itself. (0-7 range) +- **Algorithm (AL)**: Determines how operators are connected to each other. (0-7 range) + - Left-click pops up a small "operators changes with volume?" dialog where each operator can be toggled to scale with volume level. + - Right-click to switch to a preview display of the waveform generated on a new note: + - Left-click restarts the preview. + - Middle-click pauses and unpauses the preview. + - Right-click returns to algorithm view. + +- **LFO Frequency Sensitivity**: Determines the amount of LFO frequency changes. (0-7 range) +- **LFO Amplitude Sensitivity (AM)**: Determines the amount of LFO amplitude changes. (0-3 range) + +These apply to each operator: +- The crossed-arrows button can be dragged to rearrange operators. +- The **OP1**, **OP2**, **OP3**, and **OP4** buttons enable or disable those operators. +- **Amplitude Modulation (AM)**: Makes the operator affected by LFO. +- **Hardware Envelope Generator (SSG-EG)**: Executes the built-in envelope, inherited from AY-3-8910 PSG. Speed of execution is controlled via Decay Rate. YM2610/YM2612 sound source only. - **Attack Rate (AR)**: determines the rising time for the sound. The bigger the value, the faster the attack. (0-31 range) - **Decay Rate (DR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. It's the initial amplitude decay rate. (0-31 range) -- **Secondary Decay Rate (DR2)/Sustain Rate (SR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. This is the long "tail" of the sound that continues as long as the key is depressed. (0-31 range) +- **Sustain Level (SL)**: Determines the point at which the sound ceases to decay and changes to a sound having a constant level. The sustain level is expressed as a fraction of the maximum level. (0-15 range) +- **Secondary Decay Rate (DR2) / Sustain Rate (SR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. This is the long "tail" of the sound that continues as long as the key is depressed. (0-31 range) - **Release Rate (RR)**: Determines the rate at which the sound disappears after KEY-OFF. The higher the value, the shorter the release. (0-15 range) -- **Sustain Level(SL)**: Determines the point at which the sound ceases to decay and changes to a sound having a constant level. The sustain level is expressed as a fraction of the maximum level. (0-15 range) - **Total Level (TL)**: Represents the envelope’s highest amplitude, with 0 being the largest and 127 (decimal) the smallest. A change of one unit is about 0.75 dB. -- **Envelope Scale (KSR)**: A parameter that determines the degree to which the envelope execution speed increases according to the pitch. (0-3 range) + +![FM ADSR chart](FM-ADSRchart.png) + +- **Envelope Scale (KSR)**: Also known as "Key Scale". Determines the degree to which the envelope execution speed increases according to the pitch. (0-3 range) - **Frequency Multiplier (MULT)**: Determines the operator frequency in relation to the pitch. (0-15 range) -- **Fine Detune (DT)**: Shifts the pitch a little (0-7 range) -- **Coarse Detune (DT2)**: Shifts the pitch by tens of cents (0-3 range) WARNING: this parameter affects only YM2151 sound source!!! -- **Hardware Envelope Generator (SSG-EG)**: Executes the built-in envelope, inherited from AY-3-8910 PSG. Speed of execution is controlled via Decay Rate. WARNING: this parameter affects only YM2610/YM2612 sound source!!! -- **Algorithm (AL)**: Determines how operators are connected to each other. (0-7 range) -- **Feedback (FB)**: Determines the amount of signal whick operator 1 returns to itself. (0-7 range) -- **Amplitude Modulation (AM)**: Makes the operator affected by LFO. -- **LFO Frequency Sensitivity**: Determines the amount of LFO frequency changes. (0-7 range) -- **LFO Amplitude Sensitivity (AM)**: Determines the amount of LFO frequency changes. (0-3 range) +- **Fine Detune (DT)**: Shifts the pitch a little. (0-7 range) +- **Coarse Detune (DT2)**: Shifts the pitch by tens of cents. (0-3 range) YM2151 sound source only. + + +## macros + +Macros define the sequence of values passed to the given parameter. Via macro, along with the previously mentioned parameters, the following can be controlled: + +## FM Macros + +- **AM Depth**: amplitude modulation depth. YM2151 sound source only. +- **PM Depth**: pitch modulation depth. YM2151 sound source only. +- **LFO Speed**: LFO frequency. +- **LFO Shape**: LFO shape. Choose between saw, square, triangle, and random. +- **OpMask**: toggles each operator. + +## OP1-OP4 Macros + +All parameters are listed above. ## Macros -Macros define the sequence of values passed to the given parameter. Via macro, aside previously mentioned parameters, the following can be controlled: - -- **LFO Frequency** -- **LFO Waveform**: _WARNING:_ this parameter affects only YM2151 sound source! -- **Amplitude Modulation Depth**: _WARNING:_ this parameter affects only YM2151 sound source! -- **Frequency Modulation Depth**: _WARNING:_ this parameter affects only YM2151 sound source! -- **Arpeggio Macro**: Pitch change sequence in semitones. Two modes are available: - - **Absolute** (default): Executes the pitch with absolute change based on the pitch of the actual note. - - **Fixed**: Executes at the pitch specified in the sequence regardless of the note pitch. +- **Arpeggio**: Pitch change sequence in semitones. - **Noise Frequency**: specifies the noise frequency in noise mode of YM2151's Channel 8 Operator 4 special mode. +- **Panning**: toggles output on left and right channels. +- **Pitch**: fine pitch. + - **Relative**: pitch changes are relative to the current pitch, not the note's base pitch. +- **Phase Reset**: Restarts all operators and resets the waveform to its start. Effectively the same as a `0Cxx` retrigger. -Looping: You can loop the execution of part of a sequence. Left-click anywhere on the Loop line at the bottom of the editor to create a loop. You can move the start and end points of the loop by dragging both ends of the loop. Rigkt-click to remove the loop. + +# links + +[FM instrument tutorial](https://www.youtube.com/watch?v=wS8edjurjDw): A great starting point to learn how create and work with FM sounds. This was made for DefleMask, but all the same principles apply. diff --git a/doc/4-instrument/list.png b/doc/4-instrument/list.png deleted file mode 100644 index e8af84a83..000000000 Binary files a/doc/4-instrument/list.png and /dev/null differ diff --git a/doc/4-instrument/macro-seq.png b/doc/4-instrument/macro-seq.png index 6ea2050b4..e4bd6ee9e 100644 Binary files a/doc/4-instrument/macro-seq.png and b/doc/4-instrument/macro-seq.png differ diff --git a/doc/4-instrument/macro.png b/doc/4-instrument/macro.png deleted file mode 100644 index 4e03bad92..000000000 Binary files a/doc/4-instrument/macro.png and /dev/null differ diff --git a/doc/4-instrument/n163.md b/doc/4-instrument/n163.md index 1ec459143..2e37b0d5f 100644 --- a/doc/4-instrument/n163.md +++ b/doc/4-instrument/n163.md @@ -4,27 +4,23 @@ The Namco 163 instrument editor consists of two tabs: "Namco 163" for control of ## Namco 163 -- **Waveform**: Determines the initial waveform for playing. -- **Offset**: Determines the initial waveform position will be load to RAM. -- **Length**: Determines the initial waveform length will be load to RAM. -- **Load waveform before playback**: Determines the load initial waveform into RAM before playback. -- **Update waveforms into RAM when every waveform changes**: Determines the update every different waveform changes in playback. - +- **Load waveform**: if enabled, a waveform will be loaded when this instrument plays. + - if it isn't then only the offset/length change. +- **Waveform**: determines the waveform that will be loaded. + - only appears when Load waveform is enabled. +- **Per-channel wave offset/length**: when enabled, the offset/length settings are split per channel. +- **Offset**: determines the waveform position in RAM. +- **Length**: determines the waveform length in RAM. ## Macros - **Volume**: volume levels sequence - **Arpeggio**: pitch sequence +- **Wave Pos**: sets waveform seek position in RAM - **Waveform**: sets waveform source for playback immediately or update later - **Panning**: output for left and right channels - **Pitch**: fine pitch -- **Phase Reset**: trigger restart of waveform +- **Wave Length**: sets waveform length \ No newline at end of file +- **Phase Reset**: trigger restart of waveform +--> 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/sample-map.png b/doc/4-instrument/sample-map.png index 1d2787122..fb1f9b51e 100644 Binary files a/doc/4-instrument/sample-map.png and b/doc/4-instrument/sample-map.png differ diff --git a/doc/4-instrument/scc.md b/doc/4-instrument/scc.md index fa9a9408b..29139fe7c 100644 --- a/doc/4-instrument/scc.md +++ b/doc/4-instrument/scc.md @@ -4,5 +4,5 @@ The SCC/Bubble System WSG instrument editor consists of these macros: - **Volume**: volume sequence - **Arpeggio**: pitch sequence -- **Waveform**: spicifies wavetables sequence +- **Waveform**: specifies wavetable sequence - **Pitch**: fine pitch 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/ted.md b/doc/4-instrument/ted.md new file mode 100644 index 000000000..47a5f6d6f --- /dev/null +++ b/doc/4-instrument/ted.md @@ -0,0 +1,11 @@ +# TED instrument editor + +TED instrument editor consists of these macros: + +- **Volume**: volume sequence. **global!** +- **Arpeggio**: pitch sequence +- **Square/Noise**: select whether square, noise or nothing will be output + - noise only available on channel 2 + - if square and noise are enabled, square takes precedence. +- **Pitch**: "fine" pitch +- **Phase Reset**: trigger restart of waveform. **global!** diff --git a/doc/4-instrument/wavetable.png b/doc/4-instrument/wavetable.png new file mode 100644 index 000000000..7c862d953 Binary files /dev/null and b/doc/4-instrument/wavetable.png differ diff --git a/doc/5-wave/README.md b/doc/5-wave/README.md index e03940588..59131d39b 100644 --- a/doc/5-wave/README.md +++ b/doc/5-wave/README.md @@ -1,23 +1,121 @@ +# wavetables + +Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. + +Each chip has its own maximum size, shown in the following table. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. Some hardware doesn't work well with the wavetable synthesizer (described below); these systems are marked in the "synth?" column. + +system | width | height | synth? +--------------------|------:|:-------|:------ +Bubble System | 32 | 16 | +Game Boy | 32 | 16 | +SM8521 | 32 | 16 | +Namco WSG | 32 | 16 | RAM only +WonderSwan | 32 | 16 | +Namco 163 | ≤128 | 16 | +SNES | ≤256 | 16 | +PC Engine | 32 | 32 | +Virtual Boy | 32 | 64 | no +Famicom Disk System | 64 | 64 | +Konami SCC | 32 | 256 | +Seta X1-010 | 128 | 256 | +Amiga | ≤256 | 256 | + + # wavetable editor -Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.6pre4, wavetable editor affects PC Engine, WonderSwan, Namco WSGs, Virtual Boy, Game.com, SCC, FDS, Seta X1-010, Konami Bubble System WSG, SNES, Amiga and channel 3 of Game Boy. +![wavetable editor](wave-editor.png) -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Namco WSG, N163, Game.com, Virtual Boy and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System, SNES, Namco WSG and N163, 32-level height for PCE and 64-level height for Virtual Boy. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. +controls across the top line: +- waveform number. the `-` and `+` buttons step through the list. +- open. +- save. +- **Steps**: view waveform as discrete blocks. +- **Lines**: view waveform as a continuous line. +- **Width**: length of the waveform data. maximum is 256. +- **Height**: depth of the waveform. maximum is 256. +- `<` / `>`: toggle tabs (described below). -Furnace's wavetable editor features multiple ways of creating desired waveform shape: +waveform display: +- the waveform is directly editable with the mouse. +- hovering will display a tooltip with the waveform position and value. -- **Shape** tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders: - - **Duty**: Affects mainly pulse waves, determining its wisth, like on C64/VRC6 - - **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. -- **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. +controls across the bottom line: +- **Dec**: view MML stream as decimal. +- **Hex**: view MML stream as hexadecimal. +- `+` / `±`: toggle MML stream as unsigned/signed. also adjusts waveform display. +- MML stream: waveform data as an editable numeric sequence. -## wavetable synthesizer +## tabs -Furnace contains a mode for wavetable instruments that allows you to modulate or combine 1 or 2 waves to create unique "animated" sounds. Think of it like a VST or a plugin, as it's basically an extension of regular wavetable soundchips that still allow it to run on real hardware. +each tab provides different ways of creating or altering a waveform. + +### Shapes + +![wavetable shape tab](wave-editor-shapes.png) + +this creates a waveform by adding together a few predefined basic wave shapes. +- shape: select shape from sine, triangle, saw, and square. +- **Duty**: only affects pulse waves, determining their width +- **Exponent**: applies an exponent (power) to the waveform (^2, ^3 and so on). +- **XOR Point**: determines the point where the waveform gets negated. +- **Amplitude/Phase**: add together up to 16 instances of the shape. + - **Amplitude**: height of the shape. + - **Phase**: position along the shape. for example, 0.250 starts the shape a quarter of the way along. + +### FM + +![wavetable FM tab](wave-editor-FM.png) + +this creates a waveform using frequency modulation synthesis with up to four operators. + +one can set carrier/modulation levels, frequency multipliers, connections between operators and FM waveforms of these operators. + +### WaveTools + +![wavetable tools tab](wave-editor-tools.png) + +these are useful editing tools to fine-tune the waveform: +- **Scale X**: stretches the waveform to a new length. +- interpolation method: filters the waveform when stretching. choose from none, linear, cosine, and cubic interpolation. +- **Scale Y**: resizes the waveform to a new height. it will clip at the top and bottom. +- **Offset X**: slides the the waveform forward or back. it will wrap around. +- **Offset Y**: slides the waveform up or down. it will clip at the top and bottom. +- **Smooth**. smooths waveform. +- **Amplify**. changes the volume of the waveform. it will clip at the top and bottom. +- **Normalize**: stretches waveform to maximum within the wavetable height. +- **Invert**: flips waveform vertically. +- **Half**: halves the waveform's frequency by stretching its first half to fill the waveform length. +- **Double**: doubles the waveform's frequency by squashing it to half length then repeating it. +- **Convert Signed/Unsigned**. worth trying if an imported wave sounds corrupted. +- **Randomize**: generate a completely random waveform. + + + +# wavetable synthesizer + +Within the "Wavetable" tab of the instrument editor, Furnace allows you to modulate or combine 1 or 2 waves to create unique "animated" sounds. Think of it like a VST or a plugin, as it's basically an extension of regular wavetable soundchips that still allow it to run on real hardware. This is accomplished by selecting a wave or two, a mode, and adjusting the settings as needed until you come up with a sound that you like, without taking up a load of space. This allows you to create unique sound effects or instruments, that, when used well, almost sound like they're Amiga samples. -Unfortunately, on chips like the HuC6280, you cannot use the wavetable synth to animate waveforms and have them sound smooth, as the chip resets the channel's phase when a waveform is changed while the channel is playing. On certain frequencies, this can be avoided, but not on most, unfortunately. +Unfortunately, on some chips like the HuC6280, you cannot use the wavetable synth to animate waveforms and have them sound smooth, as the chip resets the channel's phase when a waveform is changed while the channel is playing. On certain frequencies, this can be avoided, but not on most, unfortunately. + +![instrument wavetable tab](instrument-wavetable.png) + +input waveforms should match the size of the wavetable or unexpected results may occur. + +- **Enable synthesizer**: must be on for the rest of this to work. +- synthesizer type: selects the synthesis algorithm. +- waveform displays. +- **Wave 1**: selects input waveform. +- **Wave 2**: selects second input waveform. only appears when a dual-waveform synthesizer is selected. +- **Pause preview**: toggles live waveform preview. +- **Restart preview**: restarts preview from initial state. +- **Copy to new wavetable**: copies the currently displayed output waveform into the wavetable as a new entry. +- (width×height): size of wavetable. +- **Update Rate**: time in ticks between waveform changes. +- **Speed**: rate of change with each update. +- **Amount**: strength of synthesizer function. +- **Power**: only appears when synthesizer type is "Phase Modulation". +- **Global**: + - if disabled, each note resets the synthesizer to the start. + - if enabled, synthesis continues unbroken from note to note. diff --git a/doc/5-wave/instrument-wavetable.png b/doc/5-wave/instrument-wavetable.png new file mode 100644 index 000000000..d5a8f112b Binary files /dev/null and b/doc/5-wave/instrument-wavetable.png differ diff --git a/doc/5-wave/wave-editor-FM.png b/doc/5-wave/wave-editor-FM.png new file mode 100644 index 000000000..2fd255d31 Binary files /dev/null and b/doc/5-wave/wave-editor-FM.png differ diff --git a/doc/5-wave/wave-editor-shapes.png b/doc/5-wave/wave-editor-shapes.png new file mode 100644 index 000000000..6fbffa028 Binary files /dev/null and b/doc/5-wave/wave-editor-shapes.png differ diff --git a/doc/5-wave/wave-editor-tools.png b/doc/5-wave/wave-editor-tools.png new file mode 100644 index 000000000..64261f90d Binary files /dev/null and b/doc/5-wave/wave-editor-tools.png differ diff --git a/doc/5-wave/wave-editor.png b/doc/5-wave/wave-editor.png new file mode 100644 index 000000000..570a915fb Binary files /dev/null and b/doc/5-wave/wave-editor.png differ diff --git a/doc/6-sample/README.md b/doc/6-sample/README.md index 52e510f47..637884370 100644 --- a/doc/6-sample/README.md +++ b/doc/6-sample/README.md @@ -47,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. diff --git a/doc/7-systems/README.md b/doc/7-systems/README.md index 71285b521..294fdc9e9 100644 --- a/doc/7-systems/README.md +++ b/doc/7-systems/README.md @@ -14,6 +14,7 @@ this is a list of sound chips that Furnace supports, including effects. - [Famicom Disk System](fds.md) - [Game Boy](game-boy.md) - [Konami K007232](k007232.md) +- [Konami K056320](k056320.md) - [Konami SCC](scc.md) - [Konami VRC6](vrc6.md) - [Atari Lynx](lynx.md) diff --git a/doc/7-systems/amiga.md b/doc/7-systems/amiga.md index 8f49bd123..ee197a90a 100644 --- a/doc/7-systems/amiga.md +++ b/doc/7-systems/amiga.md @@ -15,3 +15,9 @@ imported MOD files use this chip, and will set A-4 tuning to 436. - does not work on the last channel. - `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 11fa31b32..0b9d759a4 100644 --- a/doc/7-systems/ay8910.md +++ b/doc/7-systems/ay8910.md @@ -6,6 +6,8 @@ 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.** @@ -44,3 +46,12 @@ the AY-3-8914 variant was used in Intellivision, which is pretty much an AY with - this changes the port's mode to "write". make sure you have connected something to it. - `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 6bbe06859..1a926ebce 100644 --- a/doc/7-systems/ay8930.md +++ b/doc/7-systems/ay8930.md @@ -7,6 +7,8 @@ it is best known for being used in the Covox Sound Master, which didn't sell wel 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.** diff --git a/doc/7-systems/genesis.md b/doc/7-systems/genesis.md index 862c1c242..5215fb101 100644 --- a/doc/7-systems/genesis.md +++ b/doc/7-systems/genesis.md @@ -54,7 +54,7 @@ CSM is beyond the scope of this documentation. for more information, see this [b ## DualPCM -DualPCM splits channel 6 into two individual PCM channels. using the console's Z80 processor, these are mixed together in software and streamed to channel 6 in PCM mode. because this generates a stream of data, exported VGM files will be very large. +[info here.](ym2612.md) ## Sega CD diff --git a/doc/7-systems/k056320.md b/doc/7-systems/k056320.md new file mode 100644 index 000000000..bf6595823 --- /dev/null +++ b/doc/7-systems/k056320.md @@ -0,0 +1,9 @@ +# Konami 056320 + +the 056320 is a sample-based chip that featured in a number of Konami arcade games, notably _Sunset Riders_ and _Teenage Mutant Ninja Turtles: Turtles in Time_. it has four channels of audio and stereo output, and can access up to 2MB of samples in 8-bit PCM or 4-bit ADPCM formats. + + + +# effects + +- `DFxx`: Set sample playback direction. `0` is normal; `1` is reverse. 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 3785cc62f..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.** + +# 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 cb5ff5304..e6645637d 100644 --- a/doc/7-systems/n163.md +++ b/doc/7-systems/n163.md @@ -8,34 +8,33 @@ wavetables are variable in size and may be allocated anywhere in RAM. at least 1 Namco 163 uses time-division multiplexing for its output. this means that only one channel is output per sample (like OPLL and OPN2). therefore, its sound quality gets worse as more channels are activated. -Furnace supports loading waveforms into RAM and waveform playback simultaneously, and channel limit is dynamically changeable with effect commands. +# waveform load position versus waveform position -you must load waveform to RAM first for playback, as its load behavior auto-updates when every waveform changes. +in Furnace, waveform **load** position/length is different from the waveform position/length. -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. +when placing a note, the load pos/len and the pos/len are set to the values specified in the instrument. + +waveforms are loaded in the region set by the **load** pos/len, which you can change using effects `15xx` and `16xx` as described below. + +the region that will play is set by the waveform pos/len, which you can alter using effects `11xx` and `12xx`. + +the waveform pos/len macros only change the pos/len, and not the **load** one. + +if the waveform changes (e.g. ins change, wave macro or wave synth), or the **load** pos/len changes, the wave is written to memory. # 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.** 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. +- `11xx`: **set waveform position in RAM for playback.** +- `12xx`: **set waveform length in RAM for playback.** + - `x` goes from `04` to `FC` in steps of 4. +- `15xx`: **set waveform load position.** +- `16xx`: **set waveform load length.** + - `x` goes from `04` to `FC` in steps of 4. +- `180x`: **set channel limit.** + - range of `x` is `0` to `7`. 1 is added to get results of 1 through 8. +- `20xx`: **load a waveform to RAM.** + - `x` is the waveform. + - the length is determined by the wave's width (it will be snapped to a multiple of 4 if it isn't). + - make sure to use `21xx` first! +- `21xx`: **set position for 20xx.** diff --git a/doc/7-systems/nes.md b/doc/7-systems/nes.md index c3ccb7064..2b81c2a87 100644 --- a/doc/7-systems/nes.md +++ b/doc/7-systems/nes.md @@ -83,45 +83,6 @@ 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 @@ -161,21 +122,25 @@ 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 +"value" is for DefleMask compatability. + +value | tracker | NTSC freq | NTSC pitch | PAL freq | PAL pitch +-----:|:-------:|----------:|:----------:|----------:|:----------: + `00` | `C-3` | 4181.7Hz | C-8 - 2¢ | 4177.4Hz | C-8 - 4¢ + `01` | `D-3` | 4709.9Hz | D-8 + 4¢ | 4696.6Hz | D-8 - 1¢ + `02` | `E-3` | 5264.0Hz | E-8 - 3¢ | 5261.4Hz | E-8 - 4¢ + `03` | `F-3` | 5593.0Hz | F-8 + 2¢ | 5579.2Hz | F-8 - 3¢ + `04` | `G-3` | 6258.0Hz | G-8 - 4¢ | 6023.9Hz | G-8 - 70¢ + `05` | `A-3` | 7046.4Hz | A-8 + 2¢ | 7044.9Hz | A-8 + 1¢ + `06` | `B-3` | 7919.4Hz | B-8 + 4¢ | 7917.2Hz | B-8 + 3¢ + `07` | `C-4` | 8363.4Hz | C-9 - 2¢ | 8397.0Hz | C-9 + 5¢ + `08` | `D-4` | 9419.9Hz | D-9 + 4¢ | 9446.6Hz | D-9 + 9¢ + `09` | `F-4` | 11186.1Hz | F-9 + 2¢ | 11233.8Hz | F-9 + 9¢ + `0A` | `G-4` | 12604.0Hz | G-9 + 8¢ | 12595.5Hz | G-9 + 7¢ + `0B` | `A-4` | 13982.6Hz | A-9 - 12¢ | 14089.9Hz | A-9 + 1¢ + `0C` | `C-5` | 16884.6Hz | C-10 + 15¢ | 16965.4Hz | C-10 + 23¢ + `0D` | `E-5` | 21306.8Hz | E-10 + 17¢ | 21315.5Hz | E-10 + 18¢ + `0E` | `G-5` | 24858.0Hz | G-10 - 16¢ | 25191.0Hz | G-10 + 7¢ + `0F` | `C-6` | 33143.9Hz | C-11 - 18¢ | 33252.1Hz | C-11 - 12¢ + +reference: [NESdev](https://www.nesdev.org/wiki/APU_DMC#Pitch_table) \ No newline at end of file diff --git a/doc/7-systems/sms.md b/doc/7-systems/sms.md index a560ce065..528de4207 100644 --- a/doc/7-systems/sms.md +++ b/doc/7-systems/sms.md @@ -13,3 +13,19 @@ the original iteration of the SN76489 used in the TI-99/4A computer, the SN94624 - `y` controls whether to select noise or thin pulse. - `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/ted.md b/doc/7-systems/ted.md new file mode 100644 index 000000000..e1349a55b --- /dev/null +++ b/doc/7-systems/ted.md @@ -0,0 +1,10 @@ +# MOS Technology TED + +also called 7360/8360, TED stands for Text Editing Device. it's both a video and audio chip of Commodore budget computers, like Plus/4 and 16. + +its audio portion is pretty barren - only 2 channels. one can output square wave and other may be either square or noise. +pitch range is limited as well, akin to that of SN76489, and volume control is global. + +# effects + +none so far. diff --git a/doc/7-systems/tia.md b/doc/7-systems/tia.md index b39d31db4..d4cf06b38 100644 --- a/doc/7-systems/tia.md +++ b/doc/7-systems/tia.md @@ -25,4 +25,271 @@ Furnace isn't complete without this one... - `C`: low square - `D`: low square - `E`: low pure buzzy - - `F`: low reedy \ No newline at end of file + - `F`: low reedy + + + +# tables + +pitch number can be used for absolute notes in arpeggio macros. + +## shape 1 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 2096.0 | C-7 | +2 | 2080.0 | C-7 | -1 +| 1 | 1048.0 | C-6 | +2 | 1040.0 | C-6 | -1 +| 2 | 698.7 | F-5 | 0.0| 693.3 | F-5 | -1 +| 3 | 524.0 | C-5 | +2 | 520.0 | C-5 | -1 +| 4 | 419.2 | G#4 | +16 | 416.0 | G#4 | +3 +| 5 | 349.3 | F-4 | 0.0| 346.7 | F-4 | -13 +| 6 | 299.4 | D-4 | +33 | 297.1 | D-4 | +20 +| 7 | 262.0 | C-4 | +3 | 260.0 | C-4 | -11 +| 8 | 232.9 | A#3 | -2 | 231.1 | A#3 | -15 +| 9 | 209.6 | G#3 | +15 | 208.0 | G#3 | +2 +| 10 | 190.5 | F#3 | +50 | 189.1 | F#3 | +37 +| 11 | 174.7 | F-3 | +1 | 173.3 | F-3 | -13 +| 12 | 161.2 | E-3 | -39 | 160.0 | D#3 | +48 +| 13 | 149.7 | D-3 | +33 | 148.6 | D-3 | +20 +| 14 | 139.7 | C#3 | +13 | 138.7 | C#3 | +1 +| 15 | 131.0 | C-3 | +3 | 130.0 | C-3 | -11 +| 16 | 123.3 | B-2 | -3 | 122.4 | B-2 | -16 +| 17 | 116.4 | A#2 | 0.0| 115.6 | A#2 | -14 +| 18 | 110.3 | A-2 | +5 | 109.5 | A-2 | -8 +| 19 | 104.8 | G#2 | +16 | 104.0 | G#2 | +3 +| 20 | 99.8 | G-2 | +31 | 99.0 | G-2 | +17 +| 21 | 95.3 | G-2 | -49 | 94.5 | F#2 | +36 +| 22 | 91.1 | F#2 | -27 | 90.4 | F#2 | -40 +| 23 | 87.3 | F-2 | 0.0| 86.7 | F-2 | -12 +| 24 | 83.8 | E-2 | +29 | 83.2 | E-2 | +16 +| 25 | 80.6 | E-2 | -39 | 80.0 | D#2 | +48 +| 26 | 77.6 | D#2 | -5 | 77.0 | D#2 | -18 +| 27 | 74.9 | D-2 | +34 | 74.3 | D-2 | +20 +| 28 | 72.3 | D-2 | -27 | 71.7 | D-2 | -41 +| 29 | 69.9 | C#2 | +15 | 69.3 | C#2 | 0 +| 30 | 67.6 | C#2 | -44 | 67.1 | C-2 | +44 +| 31 | 65.5 | C-2 | +3 | 65.0 | C-2 | -11 + +## shapes 2, 3 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 67.6 | C#2 | -44 | 67.1 | C-2 | +44 +| 1 | 33.8 | C#1 | -42 | 33.5 | C-1 | +42 +| 2 | 22.5 | F#0 | -46 | 22.4 | F-0 | +46 +| 3 | 16.9 | C#0 | -44 | 16.8 | C-0 | +44 +| 4 | 13.5 | | | 13.4 +| 5 | 11.3 | | | 11.2 +| 6 | 9.7 | | | 9.6 +| 7 | 8.5 | | | 8.4 +| 8 | 7.5 | | | 7.5 +| 9 | 6.8 | | | 6.7 +| 10 | 6.1 | | | 6.1 +| 11 | 5.6 | | | 5.6 +| 12 | 5.2 | | | 5.2 +| 13 | 4.8 | | | 4.8 +| 14 | 4.5 | | | 4.5 +| 15 | 4.2 | | | 4.2 +| 16 | 4.0 | | | 4.0 +| 17 | 3.8 | | | 3.7 +| 18 | 3.6 | | | 3.5 +| 19 | 3.4 | | | 3.4 +| 20 | 3.2 | | | 3.2 +| 21 | 3.1 | | | 3.0 +| 22 | 3.0 | | | 2.9 +| 23 | 2.8 | | | 2.8 +| 24 | 2.7 | | | 2.7 +| 25 | 2.6 | | | 2.6 +| 26 | 2.5 | | | 2.5 +| 27 | 2.4 | | | 2.4 +| 28 | 2.3 | | | 2.3 +| 29 | 2.3 | | | 2.2 +| 30 | 2.2 | | | 2.2 +| 31 | 2.1 | | | 2.1 + +## shapes 4, 5 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 15720.0 | B-9 | -9 | 15600.0 | B-9 | -23 +| 1 | 7860.0 | B-8 | -9 | 7800.0 | B-8 | -23 +| 2 | 5240.0 | E-8 | -11 | 5200.0 | E-8 | -25 +| 3 | 3930.0 | B-7 | -10 | 3900.0 | B-7 | -23 +| 4 | 3144.0 | G-7 | +4 | 3120.0 | G-7 | -9 +| 5 | 2620.0 | E-7 | -11 | 2600.0 | E-7 | -25 +| 6 | 2245.7 | C#7 | +21 | 2228.6 | C#7 | +8 +| 7 | 1965.0 | B-6 | -9 | 1950.0 | B-6 | -23 +| 8 | 1746.7 | A-6 | -13 | 1733.3 | A-6 | -27 +| 9 | 1572.0 | G-6 | +4 | 1560.0 | G-6 | -9 +| 10 | 1429.1 | F-6 | +39 | 1418.2 | F-6 | +25 +| 11 | 1310.0 | E-6 | -11 | 1300.0 | E-6 | -25 +| 12 | 1209.2 | D-6 | +49 | 1200.0 | D-6 | +36 +| 13 | 1122.9 | C#6 | +22 | 1114.3 | C#6 | +8 +| 14 | 1048.0 | C-6 | +2 | 1040.0 | C-6 | -11 +| 15 | 982.5 | B-5 | -10 | 975.0 | B-5 | -23 +| 16 | 924.7 | A#5 | -15 | 917.6 | A#5 | -28 +| 17 | 873.3 | A-5 | -14 | 866.7 | A-5 | -27 +| 18 | 827.4 | G#5 | -7 | 821.1 | G#5 | -20 +| 19 | 786.0 | G-5 | +4 | 780.0 | G-5 | -9 +| 20 | 748.6 | F#5 | +20 | 742.9 | F#5 | +7 +| 21 | 714.5 | F-5 | +39 | 709.1 | F-5 | +26 +| 22 | 683.5 | F-5 | -38 | 678.3 | E-5 | +48 +| 23 | 655.0 | E-5 | -12 | 650.0 | E-5 | -25 +| 24 | 628.8 | D#5 | +18 | 624.0 | D#5 | +5 +| 25 | 604.6 | D-5 | +49 | 600.0 | D-5 | +36 +| 26 | 582.2 | D-5 | -16 | 577.8 | D-5 | -29 +| 27 | 561.4 | C#5 | +21 | 557.1 | C#5 | +8 +| 28 | 542.1 | C#5 | -40 | 537.9 | C-5 | +47 +| 29 | 524.0 | C-5 | +2 | 520.0 | C-5 | -11 +| 30 | 507.1 | B-4 | +45 | 503.2 | B-4 | +32 +| 31 | 491.3 | B-4 | -9 | 487.5 | B-4 | -23 + +## shapes 6, 7, 9, 10 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 1014.2 | B-5 | +45 | 1006.5 | B-5 | +32 +| 1 | 507.1 | B-4 | +45 | 503.2 | B-4 | +32 +| 2 | 338.1 | E-4 | +43 | 335.5 | E-4 | +30 +| 3 | 253.5 | B-3 | +45 | 251.6 | B-3 | +32 +| 4 | 202.8 | G#3 | -42 | 201.3 | G-3 | +45 +| 5 | 169.0 | E-3 | +43 | 167.7 | E-3 | +30 +| 6 | 144.9 | D-3 | -23 | 143.8 | D-3 | -37 +| 7 | 126.8 | B-2 | +42 | 125.8 | B-2 | +32 +| 8 | 112.7 | A-2 | +42 | 111.8 | A-2 | +28 +| 9 | 101.4 | G#2 | -41 | 100.6 | G-2 | +45 +| 10 | 92.2 | F#2 | -6 | 91.5 | F#2 | -19 +| 11 | 84.5 | E-2 | +43 | 83.9 | E-2 | +31 +| 12 | 78.0 | D#2 | +4 | 77.4 | D#2 | -9 +| 13 | 72.4 | D-2 | -24 | 71.9 | D-2 | -37 +| 14 | 67.6 | C#2 | -44 | 67.1 | C-2 | +44 +| 15 | 63.4 | B-1 | +46 | 62.9 | B-1 | +32 +| 16 | 59.7 | A#1 | +41 | 59.2 | A#1 | +26 +| 17 | 56.3 | A-1 | +39 | 55.9 | A-1 | +27 +| 18 | 53.4 | G#1 | +48 | 53.0 | G#1 | +35 +| 19 | 50.7 | G#1 | -41 | 50.3 | G-1 | +45 +| 20 | 48.3 | G-1 | -25 | 47.9 | G-1 | -39 +| 21 | 46.1 | F#1 | -4 | 45.7 | F#1 | -20 +| 22 | 44.1 | F-1 | +16 | 43.8 | F-1 | +4 +| 23 | 42.3 | E-1 | +44 | 41.9 | E-1 | +28 +| 24 | 40.6 | E-1 | -26 | 40.3 | E-1 | -39 +| 25 | 39.0 | D#1 | +4 | 38.7 | D#1 | -9 +| 26 | 37.6 | D-1 | +41 | 37.3 | D-1 | +27 +| 27 | 36.2 | D-1 | -24 | 35.9 | D-1 | -38 +| 28 | 35.0 | C#1 | +19 | 34.7 | C#1 | +5 +| 29 | 33.8 | C#1 | -42 | 33.5 | C-1 | +42 +| 30 | 32.7 | C-1 | 0.0 | 32.5 | C-1 | -11 +| 31 | 31.7 | B-0 | +44 | 31.5 | B-0 | +33 + +## shapes 8 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 61.5 | B-1 | -6| 61.1 | B-1 | -18 +| 1 | 30.8 | B-0 | -6| 30.5 | B-0 | -22 +| 2 | 20.5 | E-0 | -8| 20.4 | E-0 | -17 +| 3 | 15.4 | | | 15.3 +| 4 | 12.3 | | | 12.2 +| 5 | 10.3 | | | 10.2 +| 6 | 8.8 | | | 8.7 +| 7 | 7.7 | | | 7.6 +| 8 | 6.8 | | | 6.8 +| 9 | 6.2 | | | 6.1 +| 10 | 5.6 | | | 5.6 +| 11 | 5.1 | | | 5.1 +| 12 | 4.7 | | | 4.7 +| 13 | 4.4 | | | 4.4 +| 14 | 4.1 | | | 4.1 +| 15 | 3.8 | | | 3.8 +| 16 | 3.6 | | | 3.6 +| 17 | 3.4 | | | 3.4 +| 18 | 3.2 | | | 3.2 +| 19 | 3.1 | | | 3.1 +| 20 | 2.9 | | | 2.9 +| 21 | 2.8 | | | 2.8 +| 22 | 2.7 | | | 2.7 +| 23 | 2.6 | | | 2.5 +| 24 | 2.5 | | | 2.4 +| 25 | 2.4 | | | 2.3 +| 26 | 2.3 | | | 2.3 +| 27 | 2.2 | | | 2.2 +| 28 | 2.1 | | | 2.1 +| 29 | 2.0 | | | 2.0 +| 30 | 2.0 | | | 2.0 +| 31 | 1.9 | | | 1.9 + +## shapes 12, 13 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 5240.0 | E-8 | -11 | 5200.0 | E-8 | -25 +| 1 | 2620.0 | E-7 | -11 | 2600.0 | E-7 | -25 +| 2 | 1746.6 | A-6 | -14 | 1733.3 | A-6 | -27 +| 3 | 1310.0 | E-6 | -11 | 1300.0 | E-6 | -25 +| 4 | 1048.0 | C-6 | +2 | 1040.0 | C-6 | -11 +| 5 | 873.3 | A-5 | -14 | 866.7 | A-5 | -27 +| 6 | 748.6 | F#5 | +20 | 742.9 | F#5 | +7 +| 7 | 655.0 | E-5 | -12 | 650.0 | E-5 | -25 +| 8 | 582.2 | D-5 | -16 | 577.8 | D-5 | -29 +| 9 | 524.0 | C-5 | +2 | 520.0 | C-5 | -11 +| 10 | 476.4 | A#4 | +39 | 472.7 | A#4 | +23 +| 11 | 436.7 | A-4 | -13 | 433.3 | A-4 | -27 +| 12 | 403.1 | G-4 | +48 | 400.0 | G-4 | +34 +| 13 | 374.3 | F#4 | +20 | 371.4 | F#4 | +6 +| 14 | 349.3 | F-4 | 0.0 | 346.7 | F-4 | -13 +| 15 | 327.5 | E-4 | -11 | 325.0 | E-4 | -25 +| 16 | 308.2 | D#4 | -17 | 305.9 | D#4 | -30 +| 17 | 291.1 | D-4 | -16 | 288.9 | D-4 | -29 +| 18 | 275.8 | C#4 | -9 | 273.7 | C#4 | -22 +| 19 | 262.0 | C-4 | +3 | 260.0 | C-4 | -11 +| 20 | 249.5 | B-3 | +18 | 247.6 | B-3 | +5 +| 21 | 238.2 | A#3 | +37 | 236.4 | A#3 | +24 +| 22 | 227.8 | A#3 | -40 | 226.1 | A-3 | +47 +| 23 | 218.3 | A-3 | -14 | 216.7 | A-3 | -27 +| 24 | 209.6 | G#3 | +15 | 208.0 | G#3 | +2 +| 25 | 201.5 | G-3 | +47 | 200.0 | G-3 | +34 +| 26 | 194.1 | G-3 | -17 | 192.6 | G-3 | -31 +| 27 | 187.1 | F#3 | +19 | 185.7 | F#3 | +6 +| 28 | 180.7 | F#3 | -41 | 179.3 | F-3 | +45 +| 29 | 174.7 | F-3 | +1 | 173.3 | F-3 | -13 +| 30 | 169.0 | E-3 | +43 | 167.7 | E-3 | +30 +| 31 | 163.8 | E-3 | -11 | 162.5 | E-3 | -25 + +## shapes 14, 15 + +| pitch | NTSC | note | cent | PAL | note | cent +|------:|--------:|:----:|-----:|--------:|:----:|-----: +| 0 | 338.1 | E-4 | +43 | 335.5 | E-4 | +30 +| 1 | 169.0 | E-3 | +43 | 167.7 | E-3 | +30 +| 2 | 112.7 | A-2 | +42 | 111.8 | A-2 | +28 +| 3 | 84.5 | E-2 | +43 | 83.9 | E-2 | +31 +| 4 | 67.6 | C#2 | -44 | 67.1 | C-2 | +44 +| 5 | 56.3 | A-1 | +39 | 55.9 | A-1 | +27 +| 6 | 48.3 | G-1 | -25 | 47.9 | G-1 | -39 +| 7 | 42.3 | E-1 | +44 | 41.9 | E-1 | +28 +| 8 | 37.6 | D-1 | +41 | 37.3 | D-1 | +27 +| 9 | 33.8 | C#1 | -42 | 33.5 | C-1 | +42 +| 10 | 30.7 | B-0 | -11 | 30.5 | B-0 | -22 +| 11 | 28.2 | A-0 | +44 | 28.0 | A-0 | +31 +| 12 | 26.0 | G#0 | 0.0 | 25.8 | G#0 | -13 +| 13 | 24.1 | G-0 | -29 | 24.0 | G-0 | -36 +| 14 | 22.5 | F#0 | -46 | 22.4 | F-0 | +46 +| 15 | 21.1 | E-0 | +42 | 21.0 | E-0 | +33 +| 16 | 19.9 | D#0 | +42 | 19.7 | D#0 | +25 +| 17 | 18.8 | D-0 | +40 | 18.6 | D-0 | +20 +| 18 | 17.8 | C#0 | +45 | 17.7 | C#0 | +36 +| 19 | 16.9 | C#0 | -44 | 16.8 | C-0 | +44 +| 20 | 16.1 | C-0 | -30 | 16.0 | C-0 | -40 +| 21 | 15.4 | | | 15.2 +| 22 | 14.7 | | | 14.6 +| 23 | 14.1 | | | 14 +| 24 | 13.5 | | | 13.4 +| 25 | 13.0 | | | 12.9 +| 26 | 12.5 | | | 12.4 +| 27 | 12.1 | | | 12 +| 28 | 11.7 | | | 11.6 +| 29 | 11.3 | | | 11.2 +| 30 | 10.9 | | | 10.8 +| 31 | 10.6 | | | 10.5 + +reference: [Atari 2600 VCS Sound Frequency and Waveform Guide](http://7800.8bitdev.org/index.php/Atari_2600_VCS_Sound_Frequency_and_Waveform_Guide) \ No newline at end of file diff --git a/doc/7-systems/ym2203.md b/doc/7-systems/ym2203.md index 039504442..f79f95d24 100644 --- a/doc/7-systems/ym2203.md +++ b/doc/7-systems/ym2203.md @@ -99,3 +99,9 @@ several variants of this chip were released as well, with more features. - `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 700ec9d52..16f3cc7e1 100644 --- a/doc/7-systems/ym2608.md +++ b/doc/7-systems/ym2608.md @@ -2,7 +2,7 @@ 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. @@ -99,3 +99,9 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built- - `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 9d714acc6..02a630b0e 100644 --- a/doc/7-systems/ym2610.md +++ b/doc/7-systems/ym2610.md @@ -2,7 +2,7 @@ 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 @@ -97,3 +97,9 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different - `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 7428f6fbe..2911c2671 100644 --- a/doc/7-systems/ym2610b.md +++ b/doc/7-systems/ym2610b.md @@ -96,3 +96,9 @@ it is backward compatible with the original chip. - `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 61df55797..ddb91b781 100644 --- a/doc/7-systems/ym2612.md +++ b/doc/7-systems/ym2612.md @@ -1,6 +1,7 @@ # 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 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 index e8e7e73a7..26bdb2581 100644 Binary files a/doc/8-advanced/chanosc-gradient.png and b/doc/8-advanced/chanosc-gradient.png differ diff --git a/doc/8-advanced/chanosc.md b/doc/8-advanced/chanosc.md index f4b79ca75..4e3962ced 100644 --- a/doc/8-advanced/chanosc.md +++ b/doc/8-advanced/chanosc.md @@ -1,6 +1,6 @@ -# 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) @@ -8,8 +8,9 @@ right-clicking within the view will change it to the configuration view shown ab - **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 waveform color. right-clicking on it pops up an option dialog: +- 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. @@ -22,6 +23,7 @@ right-clicking within the view will change it to the configuration view shown ab - `%I`: instrument number (decimal) - `%x`: instrument number (hex) - `%s`: chip name + - `%p`: chip part number - `%S`: chip ID - `%v`: volume (decimal) - `%V`: volume (percentage) diff --git a/doc/8-advanced/chanosc.png b/doc/8-advanced/chanosc.png index 7536dad84..2f90435a8 100644 Binary files a/doc/8-advanced/chanosc.png and b/doc/8-advanced/chanosc.png differ diff --git a/doc/8-advanced/clock.png b/doc/8-advanced/clock.png index c051c5fd1..47ca4fd60 100644 Binary files a/doc/8-advanced/clock.png and b/doc/8-advanced/clock.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/groove.png b/doc/8-advanced/groove.png index ee0877c66..09ac81780 100644 Binary files a/doc/8-advanced/groove.png and b/doc/8-advanced/groove.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/doc/8-advanced/piano.png b/doc/8-advanced/piano.png index 9bf53cf60..ee04dd442 100644 Binary files a/doc/8-advanced/piano.png and b/doc/8-advanced/piano.png differ diff --git a/doc/9-guides/README.md b/doc/9-guides/README.md new file mode 100644 index 000000000..80c96a005 --- /dev/null +++ b/doc/9-guides/README.md @@ -0,0 +1,6 @@ +# guides + +here is a small collection of useful tricks and techniques to really make Furnace sing. + +- [using samples with limited playback rates](limited-samples.md) +- [choosing emulation cores](emulation-cores.md) \ No newline at end of file diff --git a/doc/9-guides/emulation-cores.md b/doc/9-guides/emulation-cores.md new file mode 100644 index 000000000..340f0838b --- /dev/null +++ b/doc/9-guides/emulation-cores.md @@ -0,0 +1,36 @@ +# choosing emulation cores + +Furnace achieves the authentic sound of videogame hardware by emulating sound chips accurately as possible, using **emulator cores**. in some cases there are multiple cores to choose from, each with different strengths and weaknesses. here are the major differences between them all. + +- **Arcade/YM2151 core**: + - **ymfm**: default. much less CPU usage than Nuked-OPM, but less accurate. recommended for users with last-gen or earlier hardware. + - **Nuked-OPM**: much more accurate than ymfm, due to the emulator being based on an image of the die map taken from a real YM2151. very CPU heavy, only recommended for users with recent hardware. + +- **Genesis/YM2612 core**: + - **Nuked-OPN2**: default. a little lighter on the CPU than Nuked-OPM. + - **ymfm**: same as ymfm above. + +- **SN76489 core**: + - **MAME**: default. less accurate than Nuked, but with lower CPU usage. comes from the MAME emulator project. + - **Nuked-PSG Mod**: more accurate, but not by that much. this originally started as an emulator for the YM7101 PSG sound generator, but was modified to emulate the SN7 as the MAME core was deemed unsatisfactory by some. + +- **NES core**: + - **puNES**: default. it comes from a dedicated NES emulator. + - **NSFplay**: higher CPU usage than puNES. + +- **FDS core**: + - **puNES**: default. lower CPU usage and far less accurate. + - **NSFplay**: higher CPU usage and much more accurate. + +- **SID core**: + - **reSID**: default. a high quality emulation core. somewhat CPU heavy. + - **reSIDfp**: improved version of reSID. the most accurate choice. _extremely_ CPU heavy. + - **dSID**: a lightweight open-source core used in DefleMask. not so accurate but it's very CPU light. + +- **POKEY core**: + - **Atari800 (mzpokeysnd)**: does not emulate two-tone mode. + - **ASAP (C++ port)**: default. the sound core used in the ASAP player. most accurate option. + +- **OPN/OPNA/OPNB cores**: + - **ymfm only**: lower CPU usage, less accurate FM. + - **Nuked-OPN2 (FM) + ymfm (SSG/ADPCM)**: default. more accurate FM at the cost of more CPU load. diff --git a/doc/9-guides/limited-samples.md b/doc/9-guides/limited-samples.md new file mode 100644 index 000000000..34977c57f --- /dev/null +++ b/doc/9-guides/limited-samples.md @@ -0,0 +1,24 @@ +# using samples with limited playback rates + +some sample-based chips have a limited number of available sample playback rates. when working with these chips, notes entered in the pattern editor will play back at the closest available rate... which might be perfect, or might be several semitones off. the solution is to prepare samples to work around this. + +for example: using the NES, a `C-4` note in the PCM channel means the associated sample will play back at a rate of 8363Hz. let's say we want to use a slap bass sample recorded at a rate of 22050Hz in which the audible pitch is A-2. let's also say that when we put a `C-4` note in the tracker, we want to hear the bass play at what sounds like C-3, transposed three semitones higher than the recorded pitch. + +here's how to make this example work: + +- load up the sample and open it in the sample editor. +- the Note selector will show "F-6"; add the three semitones mentioned above to make it "G#6". the Hz will change to 26217. +- use the Resample button. in the pop-up dialog, type in `8363`, then click Resample. +- select the instrument from the instrument list, and in the pattern editor, enter a `C-4` in the PCM channel. it should sound like a slap bass playing a C-3 note. + +the NES PCM frequency table shows the sixteen notes can be played. if a `D-4` is entered, the slap bass will be heard at D-3 as desired. what if we want to hear a C#3, though? + +- load up the original sample in a new slot and open it in the sample editor. +- the Note selector will show "F-6"; this time we add four semitones to make it "A-6". the Hz will change to 27776. +- just like before, use the Resample button. in the little pop-up, type in `8363` (yes, the NES's C-4 rate), then click Resample. +- select the instrument from the instrument list and open it in the instrument editor. turn on "Use sample map". +- in the leftmost column, find C#5. click in the next column and enter the number of the second sample. in the next column to the right, click the "C#4" and hit the key for C-4 to change it. +- in the pattern editor, enter a `C#4`; it should sound like a C#3! +- try adding another entry in the sample map so the note D#4 plays the second sample at D-4. + + diff --git a/doc/README.md b/doc/README.md index f561710c1..90e6e905b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -10,6 +10,7 @@ this documentation is a work in progress! expect several sections to be incomple 6. [samples](6-sample/README.md) 7. [list of sound chips](7-systems/README.md) 8. [advanced topics](8-advanced/README.md) +9. [guides](9-guides/README.md) # attribution @@ -24,8 +25,3 @@ writers: - WindowxDeveloper - polluks - Electric Keet - -other: - -- [3-pattern/keyboard.png](3-pattern/keyboard.png) licensed under CC-BY-SA 3.0. - - this is a derivative of [KB United States.svg](https://en.wikipedia.org/wiki/File:KB_United_States.svg) by Denelson83 under the same license. diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 2ccc3e9c0..39815d4a4 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -800,138 +800,138 @@ namespace IGFD // will be called internally // will not been exposed to IGFD API - bool IGFD::FilterManager::prFillFileStyle(std::shared_ptr vFileInfos) const + bool IGFD::FilterManager::prFillFileStyle(FileInfos& vFileInfos) const { - if (vFileInfos.use_count() && !prFilesStyle.empty()) + if (!prFilesStyle.empty()) { for (const auto& _flag : prFilesStyle) { for (const auto& _file : _flag.second) { - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos.fileType == 'd') { if (_file.first.empty()) // for all dirs { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } - else if (_file.first == vFileInfos->fileNameExt) // for dirs who are equal to style criteria + else if (_file.first == vFileInfos.fileNameExt) // for dirs who are equal to style criteria { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos.fileType == 'f') { if (_file.first.empty()) // for all files { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } - else if (_file.first == vFileInfos->fileNameExt) // for files who are equal to style criteria + else if (_file.first == vFileInfos.fileNameExt) // for files who are equal to style criteria { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos.fileType == 'l') { if (_file.first.empty()) // for all links { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } - else if (_file.first == vFileInfos->fileNameExt) // for links who are equal to style criteria + else if (_file.first == vFileInfos.fileNameExt) // for links who are equal to style criteria { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } if (_flag.first & IGFD_FileStyleByExtention) { - if (_file.first == vFileInfos->fileExt) + if (_file.first == vFileInfos.fileExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } // can make sense for some dirs like the hidden by ex ".git" - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos.fileType == 'd') { - if (_file.first == vFileInfos->fileExt) + if (_file.first == vFileInfos.fileExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos.fileType == 'f') { - if (_file.first == vFileInfos->fileExt) + if (_file.first == vFileInfos.fileExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos.fileType == 'l') { - if (_file.first == vFileInfos->fileExt) + if (_file.first == vFileInfos.fileExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } } if (_flag.first & IGFD_FileStyleByFullName) { - if (_file.first == vFileInfos->fileNameExt) + if (_file.first == vFileInfos.fileNameExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos.fileType == 'd') { - if (_file.first == vFileInfos->fileNameExt) + if (_file.first == vFileInfos.fileNameExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos.fileType == 'f') { - if (_file.first == vFileInfos->fileNameExt) + if (_file.first == vFileInfos.fileNameExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos.fileType == 'l') { - if (_file.first == vFileInfos->fileNameExt) + if (_file.first == vFileInfos.fileNameExt) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } } if (_flag.first & IGFD_FileStyleByContainedInFullName) { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + if (vFileInfos.fileNameExt.find(_file.first) != std::string::npos) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos.fileType == 'd') { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + if (vFileInfos.fileNameExt.find(_file.first) != std::string::npos) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos.fileType == 'f') { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + if (vFileInfos.fileNameExt.find(_file.first) != std::string::npos) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos.fileType == 'l') { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + if (vFileInfos.fileNameExt.find(_file.first) != std::string::npos) { - vFileInfos->fileStyle = _file.second; + vFileInfos.fileStyle = _file.second; } } } - if (vFileInfos->fileStyle.use_count()) + if (vFileInfos.fileStyle.use_count()) return true; } } @@ -1142,6 +1142,7 @@ namespace IGFD for (int i=1; i<4; i++) { puSortingDirection[i]=false; } + invalidFile.isValid=false; } void IGFD::FileManager::OpenCurrentPath(const FileDialogInternal& vFileDialogInternal) @@ -1191,29 +1192,24 @@ namespace IGFD puHeaderFileName = tableHeaderDescendingIcon + puHeaderFileName; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - // this code fail in c:\\Users with the link "All users". got a invalid comparator /* // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 // strict ordering for file/directory types beginning in '.' // common on Linux platforms - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') + if (a.fileNameExt[0] == '.' && b.fileNameExt[0] != '.') return false; - if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') + if (a.fileNameExt[0] != '.' && b.fileNameExt[0] == '.') return true; - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') + if (a.fileNameExt[0] == '.' && b.fileNameExt[0] == '.') { - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case + return (stricmp(a.fileNameExt.c_str(), b.fileNameExt.c_str()) < 0); // sort in insensitive case } */ - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case + if (a.fileType != b.fileType) return (a.fileType == 'd'); // directory in first + return (stricmp(a.fileNameExt.c_str(), b.fileNameExt.c_str()) < 0); // sort in insensitive case }); } else @@ -1222,28 +1218,23 @@ namespace IGFD puHeaderFileName = tableHeaderAscendingIcon + puHeaderFileName; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - // this code fail in c:\\Users with the link "All users". got a invalid comparator /* // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 // strict ordering for file/directory types beginning in '.' // common on Linux platforms - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') + if (a.fileNameExt[0] == '.' && b.fileNameExt[0] != '.') return false; - if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') + if (a.fileNameExt[0] != '.' && b.fileNameExt[0] == '.') return true; - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') + if (a.fileNameExt[0] == '.' && b.fileNameExt[0] == '.') { - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case + return (stricmp(a.fileNameExt.c_str(), b.fileNameExt.c_str()) > 0); // sort in insensitive case } */ - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case + return (stricmp(a.fileNameExt.c_str(), b.fileNameExt.c_str()) > 0); // sort in insensitive case }); } } @@ -1259,13 +1250,10 @@ namespace IGFD puHeaderFileType = tableHeaderDescendingIcon + puHeaderFileType; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (a->fileExt < b->fileExt); // else + if (a.fileType != b.fileType) return (a.fileType == 'd'); // directory in first + return (a.fileExt < b.fileExt); // else }); } else @@ -1274,15 +1262,10 @@ namespace IGFD puHeaderFileType = tableHeaderAscendingIcon + puHeaderFileType; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - return (a->fileExt > b->fileExt); // else + if (a.fileType != b.fileType) return (a.fileType != 'd'); // directory in last + return (a.fileExt > b.fileExt); // else }); } } @@ -1298,15 +1281,10 @@ namespace IGFD puHeaderFileSize = tableHeaderDescendingIcon + puHeaderFileSize; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (a->fileSize < b->fileSize); // else + if (a.fileType != b.fileType) return (a.fileType == 'd'); // directory in first + return (a.fileSize < b.fileSize); // else }); } else @@ -1315,15 +1293,10 @@ namespace IGFD puHeaderFileSize = tableHeaderAscendingIcon + puHeaderFileSize; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - return (a->fileSize > b->fileSize); // else + if (a.fileType != b.fileType) return (a.fileType != 'd'); // directory in last + return (a.fileSize > b.fileSize); // else }); } } @@ -1339,15 +1312,10 @@ namespace IGFD puHeaderFileDate = tableHeaderDescendingIcon + puHeaderFileDate; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (a->fileModifDate < b->fileModifDate); // else + if (a.fileType != b.fileType) return (a.fileType == 'd'); // directory in first + return (a.fileModifDate < b.fileModifDate); // else }); } else @@ -1356,15 +1324,10 @@ namespace IGFD puHeaderFileDate = tableHeaderAscendingIcon + puHeaderFileDate; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (a==NULL || b==NULL) - return false; - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - return (a->fileModifDate > b->fileModifDate); // else + if (a.fileType != b.fileType) return (a.fileType != 'd'); // directory in last + return (a.fileModifDate > b.fileModifDate); // else }); } } @@ -1384,15 +1347,12 @@ namespace IGFD puHeaderFileThumbnails = tableHeaderDescendingIcon + puHeaderFileThumbnails; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) - return (a->thumbnailInfo.textureHeight < b->thumbnailInfo.textureHeight); - return (a->thumbnailInfo.textureWidth < b->thumbnailInfo.textureWidth); + if (a.fileType != b.fileType) return (a.fileType == 'd'); // directory in first + if (a.thumbnailInfo.textureWidth == b.thumbnailInfo.textureWidth) + return (a.thumbnailInfo.textureHeight < b.thumbnailInfo.textureHeight); + return (a.thumbnailInfo.textureWidth < b.thumbnailInfo.textureWidth); }); } @@ -1402,15 +1362,12 @@ namespace IGFD puHeaderFileThumbnails = tableHeaderAscendingIcon + puHeaderFileThumbnails; #endif // USE_CUSTOM_SORTING_ICON std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + [](const FileInfos& a, const FileInfos& b) -> bool { - if (!a.use_count() || !b.use_count()) - return false; - - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) - return (a->thumbnailInfo.textureHeight > b->thumbnailInfo.textureHeight); - return (a->thumbnailInfo.textureWidth > b->thumbnailInfo.textureWidth); + if (a.fileType != b.fileType) return (a.fileType != 'd'); // directory in last + if (a.thumbnailInfo.textureWidth == b.thumbnailInfo.textureWidth) + return (a.thumbnailInfo.textureHeight > b.thumbnailInfo.textureHeight); + return (a.thumbnailInfo.textureWidth > b.thumbnailInfo.textureWidth); }); } } @@ -1441,38 +1398,38 @@ namespace IGFD return fileNameExt; } - void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const char& vFileType, void* ent) + void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, char vFileType, void* ent) { - auto infos = std::make_shared(); + FileInfos infos; #ifdef _WIN32 struct dirent* dent=(struct dirent*)ent; #endif - infos->filePath = vPath; - infos->fileNameExt = vFileName; - infos->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos->fileNameExt); - infos->fileType = vFileType; + infos.filePath = vPath; + infos.fileNameExt = vFileName; + infos.fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos.fileNameExt); + infos.fileType = vFileType; - if (infos->fileNameExt.empty() || ((infos->fileNameExt == "." || infos->fileNameExt == "..") && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807 - if (infos->fileNameExt != ".." && (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') // dont show hidden files - if (!vFileDialogInternal.puFilterManager.puDLGFilters.empty() || (vFileDialogInternal.puFilterManager.puDLGFilters.empty() && infos->fileNameExt != ".")) // except "." if in directory mode //-V728 + if (infos.fileNameExt.empty() || ((infos.fileNameExt == "." || infos.fileNameExt == "..") && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807 + if (infos.fileNameExt != ".." && (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos.fileNameExt[0] == '.') // dont show hidden files + if (!vFileDialogInternal.puFilterManager.puDLGFilters.empty() || (vFileDialogInternal.puFilterManager.puDLGFilters.empty() && infos.fileNameExt != ".")) // except "." if in directory mode //-V728 return; - if (infos->fileType == 'f' || - infos->fileType == 'l') // link can have the same extention of a file + if (infos.fileType == 'f' || + infos.fileType == 'l') // link can have the same extention of a file { - size_t lpt = infos->fileNameExt.find_last_of('.'); + size_t lpt = infos.fileNameExt.find_last_of('.'); if (lpt != std::string::npos) { - infos->fileExt = infos->fileNameExt.substr(lpt); + infos.fileExt = infos.fileNameExt.substr(lpt); } - for (char& i: infos->fileExt) { + for (char& i: infos.fileExt) { if (i>='A' && i<='Z') i+='a'-'A'; } - if (!vFileDialogInternal.puFilterManager.IsCoveredByFilters(infos->fileExt)) + if (!vFileDialogInternal.puFilterManager.IsCoveredByFilters(infos.fileExt)) { return; } @@ -1483,16 +1440,16 @@ namespace IGFD SYSTEMTIME localTime; char timebuf[100]; - infos->fileSize=dent->dwin_size; - if (FileTimeToSystemTime(&dent->dwin_mtime,&systemTime)==TRUE) { + 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; + infos.fileModifDate=timebuf; } else { - infos->fileModifDate="???"; + infos.fileModifDate="???"; } #endif @@ -1534,7 +1491,7 @@ namespace IGFD { struct dirent* ent = files[i]; std::string where = path + std::string(PATH_SEP_STR) + std::string(ent->d_name); - char fileType = 0; + char fileType = 'f'; #if defined(HAVE_DIRENT_TYPE) || defined(_WIN32) if (ent->d_type != DT_UNKNOWN) { @@ -1544,7 +1501,7 @@ namespace IGFD fileType = 'f'; break; case DT_DIR: fileType = 'd'; break; - case DT_LNK: + case DT_LNK: { #ifdef _WIN32 fileType = 'f'; #else @@ -1557,7 +1514,8 @@ namespace IGFD } else { - fileType = 'l'; + // why does 'l' make it crash? + fileType = 'f'; } } else @@ -1568,6 +1526,9 @@ namespace IGFD #endif break; } + default: + fileType = 'f'; break; + } } else #endif // HAVE_DIRENT_TYPE @@ -1622,12 +1583,12 @@ namespace IGFD ClearFileLists(); for (auto& drive : drives) { - auto info = std::make_shared(); - info->fileNameExt = drive; - info->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(drive); - info->fileType = 'd'; + FileInfos info; + info.fileNameExt = drive; + info.fileNameExt_optimized = prOptimizeFilenameForSearchOperations(drive); + info.fileType = 'd'; - if (!info->fileNameExt.empty()) + if (!info.fileNameExt.empty()) { prFileList.push_back(info); } @@ -1658,11 +1619,11 @@ namespace IGFD return prFileList.size(); } - std::shared_ptr IGFD::FileManager::GetFullFileAt(size_t vIdx) + const FileInfos& IGFD::FileManager::GetFullFileAt(size_t vIdx) { if (vIdx < prFileList.size()) return prFileList[vIdx]; - return nullptr; + return invalidFile; } bool IGFD::FileManager::IsFilteredListEmpty() @@ -1675,11 +1636,11 @@ namespace IGFD return prFilteredFileList.size(); } - std::shared_ptr IGFD::FileManager::GetFilteredFileAt(size_t vIdx) + const FileInfos& IGFD::FileManager::GetFilteredFileAt(size_t vIdx) { if (vIdx < prFilteredFileList.size()) return prFilteredFileList[vIdx]; - return nullptr; + return invalidFile; } bool IGFD::FileManager::IsFileNameSelected(const std::string& vFileName) @@ -1706,14 +1667,12 @@ namespace IGFD void IGFD::FileManager::ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal) { prFilteredFileList.clear(); - for (const auto& file : prFileList) + for (const FileInfos& file : prFileList) { - if (!file.use_count()) - continue; bool show = true; - if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) // if search tag + if (!file.IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) // if search tag show = false; - if (puDLGDirectoryMode && file->fileType != 'd') // directory mode + if (puDLGDirectoryMode && file.fileType != 'd') // directory mode show = false; if (show) prFilteredFileList.push_back(file); @@ -1750,13 +1709,10 @@ namespace IGFD return ""; } - void IGFD::FileManager::prCompleteFileInfos(const std::shared_ptr& vInfos) + void IGFD::FileManager::prCompleteFileInfos(FileInfos& vInfos) { - if (!vInfos.use_count()) - return; - - if (vInfos->fileNameExt != "." && - vInfos->fileNameExt != "..") + if (vInfos.fileNameExt != "." && + vInfos.fileNameExt != "..") { // _stat struct : //dev_t st_dev; /* ID of device containing file */ @@ -1781,18 +1737,18 @@ namespace IGFD #else std::string fpn; - if (vInfos->fileType == 'f' || vInfos->fileType == 'l' || vInfos->fileType == 'd') // file - fpn = vInfos->filePath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; + if (vInfos.fileType == 'f' || vInfos.fileType == 'l' || vInfos.fileType == 'd') // file + fpn = vInfos.filePath + std::string(1u, PATH_SEP) + vInfos.fileNameExt; struct stat statInfos = {}; char timebuf[100]; int result = stat(fpn.c_str(), &statInfos); if (result!=-1) { - if (vInfos->fileType != 'd') + if (vInfos.fileType != 'd') { - vInfos->fileSize = (size_t)statInfos.st_size; - vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); + vInfos.fileSize = (size_t)statInfos.st_size; + vInfos.formatedFileSize = prFormatFileSize(vInfos.fileSize); } size_t len = 0; @@ -1806,12 +1762,12 @@ namespace IGFD #endif // MSVC if (len) { - vInfos->fileModifDate = std::string(timebuf, len); + vInfos.fileModifDate = std::string(timebuf, len); } } else { - vInfos->fileSize=0; - vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); - vInfos->fileModifDate="???"; + vInfos.fileSize=0; + vInfos.formatedFileSize = prFormatFileSize(vInfos.fileSize); + vInfos.fileModifDate="???"; } #endif } @@ -1994,14 +1950,11 @@ namespace IGFD IGFD::Utils::SetBuffer(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFileName); } - bool IGFD::FileManager::SelectDirectory(const std::shared_ptr& vInfos) + bool IGFD::FileManager::SelectDirectory(const FileInfos& vInfos) { - if (!vInfos.use_count()) - return false; - bool pathClick = false; - if (vInfos->fileNameExt == "..") + if (vInfos.fileNameExt == "..") { pathClick = SetPathOnParentDirectoryIfAny(); } @@ -2011,23 +1964,23 @@ namespace IGFD if (puShowDrives) { - newPath = vInfos->fileNameExt + std::string(1u, PATH_SEP); + newPath = vInfos.fileNameExt + std::string(1u, PATH_SEP); } else { #ifdef __linux__ if (puFsRoot == prCurrentPath) - newPath = prCurrentPath + vInfos->fileNameExt; + newPath = prCurrentPath + vInfos.fileNameExt; else #endif // __linux__ - newPath = prCurrentPath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; + newPath = prCurrentPath + std::string(1u, PATH_SEP) + vInfos.fileNameExt; } if (IGFD::Utils::IsDirectoryExist(newPath)) { if (puShowDrives) { - prCurrentPath = vInfos->fileNameExt; + prCurrentPath = vInfos.fileNameExt; puFsRoot = prCurrentPath; } else @@ -2041,35 +1994,32 @@ namespace IGFD return pathClick; } - void IGFD::FileManager::SelectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos) + void IGFD::FileManager::SelectFileName(const FileDialogInternal& vFileDialogInternal, const FileInfos& vInfos) { - if (!vInfos.use_count()) - return; - if (ImGui::GetIO().KeyCtrl) { if (puDLGcountSelectionMax == 0) // infinite selection { - if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add + if (prSelectedFileNames.find(vInfos.fileNameExt) == prSelectedFileNames.end()) // not found +> add { - prAddFileNameInSelection(vInfos->fileNameExt, true); + prAddFileNameInSelection(vInfos.fileNameExt, true); } else // found +> remove { - prRemoveFileNameInSelection(vInfos->fileNameExt); + prRemoveFileNameInSelection(vInfos.fileNameExt); } } else // selection limited by size { if (prSelectedFileNames.size() < puDLGcountSelectionMax) { - if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add + if (prSelectedFileNames.find(vInfos.fileNameExt) == prSelectedFileNames.end()) // not found +> add { - prAddFileNameInSelection(vInfos->fileNameExt, true); + prAddFileNameInSelection(vInfos.fileNameExt, true); } else // found +> remove { - prRemoveFileNameInSelection(vInfos->fileNameExt); + prRemoveFileNameInSelection(vInfos.fileNameExt); } } } @@ -2081,18 +2031,15 @@ namespace IGFD prSelectedFileNames.clear(); // we will iterate filelist and get the last selection after the start selection bool startMultiSelection = false; - std::string fileNameToSelect = vInfos->fileNameExt; + std::string fileNameToSelect = vInfos.fileNameExt; std::string savedLastSelectedFileName; // for invert selection mode - for (const auto& file : prFileList) + for (const FileInfos& file : prFileList) { - if (!file.use_count()) - continue; - bool canTake = true; - if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) canTake = false; + if (!file.IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) canTake = false; if (canTake) // if not filtered, we will take files who are filtered by the dialog { - if (file->fileNameExt == prLastSelectedFileName) + if (file.fileNameExt == prLastSelectedFileName) { startMultiSelection = true; prAddFileNameInSelection(prLastSelectedFileName, false); @@ -2101,13 +2048,13 @@ namespace IGFD { if (puDLGcountSelectionMax == 0) // infinite selection { - prAddFileNameInSelection(file->fileNameExt, false); + prAddFileNameInSelection(file.fileNameExt, false); } else // selection limited by size { if (prSelectedFileNames.size() < puDLGcountSelectionMax) { - prAddFileNameInSelection(file->fileNameExt, false); + prAddFileNameInSelection(file.fileNameExt, false); } else { @@ -2119,7 +2066,7 @@ namespace IGFD } } - if (file->fileNameExt == fileNameToSelect) + if (file.fileNameExt == fileNameToSelect) { if (!startMultiSelection) // we are before the last Selected FileName, so we must inverse { @@ -2145,7 +2092,7 @@ namespace IGFD { prSelectedFileNames.clear(); IGFD::Utils::ResetBuffer(puFileNameBuffer); - prAddFileNameInSelection(vInfos->fileNameExt, true); + prAddFileNameInSelection(vInfos.fileNameExt, true); } } @@ -3922,11 +3869,8 @@ namespace IGFD } // returns 0 if not break loop, 1 if break loop, 2 if exit dialog - int IGFD::FileDialog::prSelectableItem(int vidx, std::shared_ptr vInfos, bool vSelected, const char* vFmt, ...) + int IGFD::FileDialog::prSelectableItem(int vidx, const FileInfos& vInfos, bool vSelected, const char* vFmt, ...) { - if (!vInfos.use_count()) - return 0; - auto& fdi = prFileDialogInternal.puFileManager; static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | @@ -3957,7 +3901,7 @@ namespace IGFD #endif // USE_EXPLORATION_BY_KEYS if (res) { - if (vInfos->fileType == 'd') + if (vInfos.fileType == 'd') { bool isSelectingDir=false; // nav system, selectebale cause open directory or select directory @@ -4012,27 +3956,27 @@ namespace IGFD return 0; } - void IGFD::FileDialog::prBeginFileColorIconStyle(std::shared_ptr vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) + void IGFD::FileDialog::prBeginFileColorIconStyle(const FileInfos& vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) { vOutStr.clear(); vOutShowColor = false; - if (vFileInfos->fileStyle.use_count()) //-V807 //-V522 + if (vFileInfos.fileStyle.use_count()) //-V807 //-V522 { vOutShowColor = true; - *vOutFont = vFileInfos->fileStyle->font; + *vOutFont = vFileInfos.fileStyle->font; } - if (vOutShowColor && !vFileInfos->fileStyle->icon.empty()) vOutStr = vFileInfos->fileStyle->icon; - else if (vFileInfos->fileType == 'd') vOutStr = dirEntryString; - else if (vFileInfos->fileType == 'l') vOutStr = linkEntryString; - else if (vFileInfos->fileType == 'f') vOutStr = fileEntryString; + if (vOutShowColor && !vFileInfos.fileStyle->icon.empty()) vOutStr = vFileInfos.fileStyle->icon; + else if (vFileInfos.fileType == 'd') vOutStr = dirEntryString; + else if (vFileInfos.fileType == 'l') vOutStr = linkEntryString; + else if (vFileInfos.fileType == 'f') vOutStr = fileEntryString; - vOutStr += " " + vFileInfos->fileNameExt; + vOutStr += " " + vFileInfos.fileNameExt; if (vOutShowColor) - ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos->fileStyle->color); + ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos.fileStyle->color); if (*vOutFont) ImGui::PushFont(*vOutFont); } @@ -4126,13 +4070,13 @@ namespace IGFD { if (i < 0) continue; - auto infos = fdi.GetFilteredFileAt((size_t)i); - if (!infos.use_count()) + const FileInfos& infos = fdi.GetFilteredFileAt((size_t)i); + if (!infos.isValid) continue; prBeginFileColorIconStyle(infos, _showColor, _str, &_font); - bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found + bool selected = fdi.IsFileNameSelected(infos.fileNameExt); // found ImGui::TableNextRow(); @@ -4147,13 +4091,13 @@ namespace IGFD } if (ImGui::TableNextColumn()) // file type { - ImGui::Text("%s", infos->fileExt.c_str()); + ImGui::Text("%s", infos.fileExt.c_str()); } if (ImGui::TableNextColumn()) // file size { - if (infos->fileType != 'd') + if (infos.fileType != 'd') { - ImGui::Text("%s ", infos->formatedFileSize.c_str()); + ImGui::Text("%s ", infos.formatedFileSize.c_str()); } else { @@ -4162,7 +4106,7 @@ namespace IGFD } if (ImGui::TableNextColumn()) // file date + time { - ImGui::Text("%s", infos->fileModifDate.c_str()); + ImGui::Text("%s", infos.fileModifDate.c_str()); } prEndFileColorIconStyle(_showColor, _font); diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 93db26e9d..0850cd680 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -766,7 +766,7 @@ namespace IGFD void ParseFilters(const char* vFilters); // Parse filter syntax, detect and parse filter collection void SetSelectedFilterWithExt(const std::string& vFilter); // Select filter - bool prFillFileStyle(std::shared_ptr vFileInfos) const; // fill with the good style + bool prFillFileStyle(FileInfos& vFileInfos) const; // fill with the good style void SetFileStyle( const IGFD_FileStyleFlags& vFlags, @@ -812,6 +812,7 @@ namespace IGFD #ifdef USE_THUMBNAILS IGFD_Thumbnail_Info thumbnailInfo; // structre for the display for image file tetxure #endif // USE_THUMBNAILS + bool isValid = true; public: bool IsTagFound(const std::string& vTag) const; @@ -824,6 +825,7 @@ namespace IGFD class FileManager { public: // types + FileInfos invalidFile; enum class SortingFieldEnum // sorting for filetering of the file lsit { FIELD_NONE = 0, // no sorting preference, result indetermined haha.. @@ -839,8 +841,8 @@ namespace IGFD private: std::string prCurrentPath; // current path (to be decomposed in prCurrentPathDecomposition std::vector prCurrentPathDecomposition; // part words - std::vector> prFileList; // base container - std::vector> prFilteredFileList; // filtered container (search, sorting, etc..) + std::vector prFileList; // base container + std::vector prFilteredFileList; // filtered container (search, sorting, etc..) std::string prLastSelectedFileName; // for shift multi selection std::set prSelectedFileNames; // the user selection of FilePathNames bool prCreateDirectoryMode = false; // for create directory widget @@ -879,11 +881,11 @@ namespace IGFD static std::string prRoundNumber(double vvalue, int n); // custom rounding number static std::string prFormatFileSize(size_t vByteSize); // format file size field static std::string prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt); // turn all text in lower case for search facilitie - static void prCompleteFileInfos(const std::shared_ptr& FileInfos); // set time and date infos of a file (detail view mode) + static void prCompleteFileInfos(FileInfos& FileInfos); // set time and date infos of a file (detail view mode) 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, void* ent); // add file called by scandir + const std::string& vPath, const std::string& vFileName, char vFileType, void* ent); // add file called by scandir public: FileManager(); @@ -892,9 +894,9 @@ namespace IGFD bool IsFileListEmpty(); bool IsFilteredListEmpty(); size_t GetFullFileListSize(); - std::shared_ptr GetFullFileAt(size_t vIdx); + const FileInfos& GetFullFileAt(size_t vIdx); size_t GetFilteredListSize(); - std::shared_ptr GetFilteredFileAt(size_t vIdx); + const FileInfos& GetFilteredFileAt(size_t vIdx); bool IsFileNameSelected(const std::string& vFileName); std::string GetBack(); void ClearComposer(); @@ -912,9 +914,9 @@ namespace IGFD void SetCurrentPath(const std::string& vCurrentPath); // set the current path static bool IsFileExist(const std::string& vFile); void SetDefaultFileName(const std::string& vFileName); - bool SelectDirectory(const std::shared_ptr& vInfos); // enter directory + bool SelectDirectory(const FileInfos& vInfos); // enter directory void SelectFileName(const FileDialogInternal& vFileDialogInternal, - const std::shared_ptr& vInfos); // select filename + const FileInfos& vInfos); // select filename //depend of dirent.h void SetCurrentDir(const std::string& vPath); // define current directory for scan @@ -1311,7 +1313,7 @@ namespace IGFD // widgets components virtual void prDrawSidePane(float vHeight); // draw side pane virtual int prSelectableItem(int vidx, - std::shared_ptr vInfos, + const FileInfos& vInfos, bool vSelected, const char* vFmt, ...); // draw a custom selectable behavior item virtual bool prDrawFileListView(ImVec2 vSize); // draw file list view (default mode) @@ -1325,7 +1327,7 @@ namespace IGFD // - prDrawThumbnailsListView // - prDrawThumbnailsGridView void prBeginFileColorIconStyle( - std::shared_ptr vFileInfos, + const FileInfos& vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont); // begin style apply of filter with color an icon if any diff --git a/extern/imgui_patched/imgui.h b/extern/imgui_patched/imgui.h index 8674f9970..3ee53ac5c 100644 --- a/extern/imgui_patched/imgui.h +++ b/extern/imgui_patched/imgui.h @@ -1236,6 +1236,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). // tildearrow ImGuiTableFlags_NoBordersInFrozenArea = 1 << 28, // Disable vertical borders in frozen area. + ImGuiTableFlags_NoScrollWithMouse = 1 << 29, // Disable user scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. // [Internal] Combinations and masks ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame, diff --git a/extern/imgui_patched/imgui_tables.cpp b/extern/imgui_patched/imgui_tables.cpp index 8850094de..4aae7a1e6 100644 --- a/extern/imgui_patched/imgui_tables.cpp +++ b/extern/imgui_patched/imgui_tables.cpp @@ -401,6 +401,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Create scrolling region (without border and zero window padding) ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + if (flags & ImGuiTableFlags_NoScrollWithMouse) { + child_flags |= ImGuiWindowFlags_NoScrollWithMouse; + } BeginChildEx(name, instance_id, outer_rect.GetSize(), false, child_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; 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/papers/clipboard-format.md b/papers/clipboard-format.md new file mode 100644 index 000000000..d12af1cc2 --- /dev/null +++ b/papers/clipboard-format.md @@ -0,0 +1,98 @@ +# clipboard format + +when copying pattern data from Furnace, it's stored in the clipboard as plain text. + +``` +org.tildearrow.furnace - Pattern Data (144) +``` + +this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6pre7 is `162`. + +the second line is a number between 0 and 18 (decimal) which indicates which column the clip starts from. +- `0`: note. +- `1`: instrument. +- `2`: volume. +- `3`: effect 1 type. +- `4`: effect 1 value. effect type is always included in the clip, even if skipped over. +- `5`: effect 2 type. +- `6`: effect 2 value. effect type is always included in the clip, even if skipped over. +- `7`: effect 3 type... +- ...and so on. + +examples of the starting column: + +``` +org.tildearrow.furnace - Pattern Data (144) +0 +D-6007F08080706|...........| +...............|...........| +...............|A#500..080F| +...............|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +1 +007F08080706|...........| +............|...........| +............|A#500..080F| +............|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +2 +7F08080706|...........| +..........|...........| +..........|A#500..080F| +..........|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +3 +08080706|...........| +........|...........| +........|A#500..080F| +........|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +4 +08080706|...........| +........|...........| +........|A#500..080F| +........|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +5 +0706|...........| +....|...........| +....|A#500..080F| +....|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +6 +0706|...........| +....|...........| +....|A#500..080F| +....|...........| +``` + +``` +org.tildearrow.furnace - Pattern Data (144) +0 +...........| +...........| +A#500..080F| +...........| +``` + +each line following the column number is verbatim from the pattern view with channels separated by `|`. each line also ends in `|`. + +notes use the default settings for note display (no German notation), including note off `OFF`, note release `===`, and macro release `REL`. diff --git a/papers/format.md b/papers/format.md index 78d62d489..46bb4c1fa 100644 --- a/papers/format.md +++ b/papers/format.md @@ -34,110 +34,15 @@ 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 -- 156: Furnace dev156 -- 155: Furnace dev155 -- 154: Furnace dev154 -- 153: Furnace dev153 -- 152: Furnace dev152 -- 151: Furnace dev151 -- 150: Furnace dev150 -- 149: Furnace dev149 -- 148: Furnace dev148 -- 147: Furnace dev147 - 146: Furnace Pro (joke version) -- 145: Furnace dev145 -- 144: Furnace dev144 - 143: Furnace 0.6pre4 -- 142: Furnace dev142 - 141: Furnace Tournament Edition (for intro tune contest) -- 140: Furnace dev140 -- 139: Furnace dev139 -- 138: Furnace dev138 -- 137: Furnace dev137 -- 136: Furnace dev136 -- 135: Furnace dev135 -- 134: Furnace dev134 - 133: Furnace 0.6pre3 - 132: Furnace 0.6pre2 -- 131: Furnace dev131 -- 130: Furnace dev130 -- 129: Furnace dev129 -- 128: Furnace dev128 -- 127: Furnace dev127 -- 126: Furnace dev126 -- 125: Furnace dev125 -- 124: Furnace dev124 -- 123: Furnace dev123 -- 122: Furnace dev122 -- 121: Furnace dev121 -- 120: Furnace dev120 -- 119: Furnace dev119 -- 118: Furnace dev118 -- 117: Furnace dev117 - 116: Furnace 0.6pre1.5 -- 115: Furnace dev115 -- 114: Furnace dev114 -- 113: Furnace dev113 -- 112: Furnace dev112 -- 111: Furnace dev111 -- 110: Furnace dev110 -- 109: Furnace dev109 -- 108: Furnace dev108 -- 107: Furnace dev107 -- 106: Furnace dev106 -- 105: Furnace dev105 -- 104: Furnace dev104 -- 103: Furnace dev103 -- 102: Furnace 0.6pre1 (dev102) -- 101: Furnace 0.6pre1 (dev101) - 100: Furnace 0.6pre1 -- 99: Furnace dev99 -- 98: Furnace dev98 -- 97: Furnace dev97 -- 96: Furnace dev96 -- 95: Furnace dev95 -- 94: Furnace dev94 -- 93: Furnace dev93 -- 92: Furnace dev92 -- 91: Furnace dev91 -- 90: Furnace dev90 -- 89: Furnace dev89 -- 88: Furnace dev88 -- 87: Furnace dev87 -- 86: Furnace dev86 -- 85: Furnace dev85 -- 84: Furnace dev84 -- 83: Furnace dev83 -- 82: Furnace dev82 -- 81: Furnace dev81 -- 80: Furnace dev80 -- 79: Furnace dev79 -- 78: Furnace dev78 -- 77: Furnace dev77 -- 76: Furnace dev76 - 75: Furnace dev75/April Fools' 0.6pre0 -- 74: Furnace dev74 -- 73: Furnace dev73 -- 72: Furnace dev72 -- 71: Furnace dev71 -- 70: Furnace dev70 -- 69: Furnace dev69 -- 68: Furnace dev68 -- 67: Furnace dev67 -- 66: Furnace dev66 -- 65: Furnace dev65 -- 64: Furnace dev64 -- 63: Furnace dev63 -- 62: Furnace dev62 -- 61: Furnace dev61 -- 60: Furnace dev60 -- 59: Furnace dev59 -- 58: Furnace dev58 -- 57: Furnace dev57 - 54: Furnace 0.5.8 - 53: Furnace 0.5.7 @@ -176,6 +81,8 @@ the format versions are: - 13: Furnace 0.2.1 - 12: Furnace 0.2 +versions that do not appear in this list are `dev???` ones. + # header the header is 32 bytes long. @@ -311,6 +218,7 @@ size | description | - 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels | - 0xcb: Casio PV-1000 - 3 channels | - 0xcc: K053260 - 4 channels + | - 0xcd: TED - 2 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfc: Pong - 1 channel diff --git a/papers/newIns.md b/papers/newIns.md index 84369a6c7..786a553c3 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -117,6 +117,8 @@ the following instrument types are available: - 47: Pokémon Mini/QuadTone - 48: SM8521 - 49: PV-1000 +- 50: K053260 +- 52: TED the following feature codes are recognized: @@ -468,6 +470,12 @@ size | description 1 | wave pos 1 | wave len 1 | wave mode + | **extra info** (>=164) + 1 | enable per channel wave pos/len + 8 | per channel wave pos + | - only read if enabled. + 8 | per channel wave len + | - only read if enabled. ``` # FDS/Virtual Boy data (FD) diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 6d90a49f1..ef8bf3a0e 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -237,56 +237,60 @@ bool DivConfig::loadFromBase64(const char* buf) { } bool DivConfig::getBool(String key, bool fallback) const { - try { - String val=conf.at(key); - if (val=="true") { + auto val=conf.find(key); + if (val!=conf.cend()) { + if (val->second=="true") { return true; - } else if (val=="false") { + } else if (val->second=="false") { return false; } - } catch (std::out_of_range& e) { } return fallback; } int DivConfig::getInt(String key, int fallback) const { - try { - String val=conf.at(key); - int ret=std::stoi(val); - return ret; - } catch (std::out_of_range& e) { - } catch (std::invalid_argument& e) { + auto val=conf.find(key); + if (val!=conf.cend()) { + try { + int ret=std::stoi(val->second); + return ret; + } catch (std::out_of_range& e) { + } catch (std::invalid_argument& e) { + } } return fallback; } float DivConfig::getFloat(String key, float fallback) const { - try { - String val=conf.at(key); - float ret=std::stof(val); - return ret; - } catch (std::out_of_range& e) { - } catch (std::invalid_argument& e) { + auto val=conf.find(key); + if (val!=conf.cend()) { + try { + float ret=std::stof(val->second); + return ret; + } catch (std::out_of_range& e) { + } catch (std::invalid_argument& e) { + } } return fallback; } double DivConfig::getDouble(String key, double fallback) const { - try { - String val=conf.at(key); - double ret=std::stod(val); - return ret; - } catch (std::out_of_range& e) { - } catch (std::invalid_argument& e) { + auto val=conf.find(key); + if (val!=conf.cend()) { + try { + double ret=std::stod(val->second); + return ret; + } catch (std::out_of_range& e) { + } catch (std::invalid_argument& e) { + } } return fallback; } String DivConfig::getString(String key, String fallback) const { - try { - String val=conf.at(key); - return val; - } catch (std::out_of_range& e) { + auto val=conf.find(key); + if (val!=conf.cend()) { + return val->second; } return fallback; } @@ -294,37 +298,34 @@ String DivConfig::getString(String key, String fallback) const { std::vector DivConfig::getIntList(String key, std::initializer_list fallback) const { String next; std::vector ret; - try { - String val=conf.at(key); - - for (char i: val) { - if (i==',') { + auto val=conf.find(key); + if (val!=conf.cend()) { + try { + for (char i: val->second) { + if (i==',') { + int num=std::stoi(next); + ret.push_back(num); + next=""; + } else { + next+=i; + } + } + if (!next.empty()) { int num=std::stoi(next); ret.push_back(num); - next=""; - } else { - next+=i; } - } - if (!next.empty()) { - int num=std::stoi(next); - ret.push_back(num); - } - return ret; - } catch (std::out_of_range& e) { - } catch (std::invalid_argument& e) { + return ret; + } catch (std::out_of_range& e) { + } catch (std::invalid_argument& e) { + } } return fallback; } bool DivConfig::has(String key) const { - try { - String test=conf.at(key); - } catch (std::out_of_range& e) { - return false; - } - return true; + auto val=conf.find(key); + return (val!=conf.cend()); } void DivConfig::set(String key, bool value) { diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index acb7af66e..acd0cc77b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -173,16 +173,16 @@ enum DivDispatchCmds { DIV_CMD_N163_WAVE_POSITION, DIV_CMD_N163_WAVE_LENGTH, - DIV_CMD_N163_WAVE_MODE, - DIV_CMD_N163_WAVE_LOAD, + DIV_CMD_N163_WAVE_UNUSED1, + DIV_CMD_N163_WAVE_UNUSED2, DIV_CMD_N163_WAVE_LOADPOS, DIV_CMD_N163_WAVE_LOADLEN, - DIV_CMD_N163_WAVE_LOADMODE, + DIV_CMD_N163_WAVE_UNUSED3, DIV_CMD_N163_CHANNEL_LIMIT, DIV_CMD_N163_GLOBAL_WAVE_LOAD, DIV_CMD_N163_GLOBAL_WAVE_LOADPOS, - DIV_CMD_N163_GLOBAL_WAVE_LOADLEN, - DIV_CMD_N163_GLOBAL_WAVE_LOADMODE, + DIV_CMD_N163_UNUSED4, + DIV_CMD_N163_UNUSED5, DIV_CMD_SU_SWEEP_PERIOD_LOW, // (which, val) DIV_CMD_SU_SWEEP_PERIOD_HIGH, // (which, val) @@ -295,6 +295,8 @@ struct DivRegWrite { */ 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 6a532548b..2e8532603 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -78,6 +78,8 @@ #include "platform/ga20.h" #include "platform/sm8521.h" #include "platform/pv1000.h" +#include "platform/k053260.h" +#include "platform/ted.h" #include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -503,6 +505,12 @@ 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_TED: + dispatch=new DivPlatformTED; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index c7b5ac9e0..d27b6a57e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -927,12 +927,13 @@ void DivEngine::runExportThread() { } } - float* outBuf[2]; + float* outBuf[DIV_MAX_OUTPUTS]; + memset(outBuf,0,sizeof(void*)*DIV_MAX_OUTPUTS); outBuf[0]=new float[EXPORT_BUFSIZE]; outBuf[1]=new float[EXPORT_BUFSIZE]; short* sysBuf[DIV_MAX_CHIPS]; for (int i=0; igetOutputCount()]; } // take control of audio output @@ -2598,6 +2599,8 @@ void DivEngine::stepOne(int row) { } stepPlay=2; ticks=1; + prevOrder=curOrder; + prevRow=curRow; BUSY_END; } @@ -3848,6 +3851,23 @@ void DivEngine::delSample(int index) { song.sampleLen=song.sample.size(); removeAsset(song.sampleDir,index); checkAssetDir(song.sampleDir,song.sample.size()); + + // compensate + for (DivInstrument* i: song.ins) { + if (i->amiga.initSample==index) { + i->amiga.initSample=-1; + } else if (i->amiga.initSample>index) { + i->amiga.initSample--; + } + for (int j=0; j<120; j++) { + if (i->amiga.noteMap[j].map==index) { + i->amiga.noteMap[j].map=-1; + } else if (i->amiga.noteMap[j].map>index) { + i->amiga.noteMap[j].map--; + } + } + } + renderSamples(); } saveLock.unlock(); @@ -4039,6 +4059,27 @@ void DivEngine::exchangeIns(int one, int two) { } } +void DivEngine::exchangeWave(int one, int two) { + // TODO +} + +void DivEngine::exchangeSample(int one, int two) { + for (DivInstrument* i: song.ins) { + if (i->amiga.initSample==one) { + i->amiga.initSample=two; + } else if (i->amiga.initSample==two) { + i->amiga.initSample=one; + } + for (int j=0; j<120; j++) { + if (i->amiga.noteMap[j].map==one) { + i->amiga.noteMap[j].map=two; + } else if (i->amiga.noteMap[j].map==two) { + i->amiga.noteMap[j].map=one; + } + } + } +} + bool DivEngine::moveInsUp(int which) { if (which<1 || which>=(int)song.ins.size()) return false; BUSY_BEGIN; @@ -4061,6 +4102,7 @@ bool DivEngine::moveWaveUp(int which) { song.wave[which]=song.wave[which-1]; song.wave[which-1]=prev; moveAsset(song.waveDir,which,which-1); + exchangeWave(which,which-1); saveLock.unlock(); BUSY_END; return true; @@ -4077,6 +4119,7 @@ bool DivEngine::moveSampleUp(int which) { song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; moveAsset(song.sampleDir,which,which-1); + exchangeSample(which,which-1); saveLock.unlock(); renderSamples(); BUSY_END; @@ -4104,6 +4147,7 @@ bool DivEngine::moveWaveDown(int which) { saveLock.lock(); song.wave[which]=song.wave[which+1]; song.wave[which+1]=prev; + exchangeWave(which,which+1); moveAsset(song.waveDir,which,which+1); saveLock.unlock(); BUSY_END; @@ -4120,6 +4164,7 @@ bool DivEngine::moveSampleDown(int which) { saveLock.lock(); song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; + exchangeSample(which,which+1); moveAsset(song.sampleDir,which,which+1); saveLock.unlock(); renderSamples(); @@ -4320,9 +4365,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; 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..9fa7477f2 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -134,7 +134,24 @@ bool DivInstrumentN163::operator==(const DivInstrumentN163& other) { _C(wave) && _C(wavePos) && _C(waveLen) && - _C(waveMode) + _C(waveMode) && + _C(perChanPos) && + _C(wavePosCh[0]) && + _C(wavePosCh[1]) && + _C(wavePosCh[2]) && + _C(wavePosCh[3]) && + _C(wavePosCh[4]) && + _C(wavePosCh[5]) && + _C(wavePosCh[6]) && + _C(wavePosCh[7]) && + _C(waveLenCh[0]) && + _C(waveLenCh[1]) && + _C(waveLenCh[2]) && + _C(waveLenCh[3]) && + _C(waveLenCh[4]) && + _C(waveLenCh[5]) && + _C(waveLenCh[6]) && + _C(waveLenCh[7]) ); } @@ -519,6 +536,17 @@ void DivInstrument::writeFeatureN1(SafeWriter* w) { w->writeC(n163.waveLen); w->writeC(n163.waveMode); + w->writeC(n163.perChanPos); + + if (n163.perChanPos) { + for (int i=0; i<8; i++) { + w->writeC(n163.wavePosCh[i]); + } + for (int i=0; i<8; i++) { + w->writeC(n163.waveLenCh[i]); + } + } + FEATURE_END; } @@ -929,6 +957,12 @@ 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_TED: + break; case DIV_INS_MAX: break; @@ -2276,6 +2310,18 @@ void DivInstrument::readFeatureN1(SafeReader& reader, short version) { n163.waveLen=(unsigned char)reader.readC(); n163.waveMode=(unsigned char)reader.readC(); + if (version>=164) { + n163.perChanPos=reader.readC(); + if (n163.perChanPos) { + for (int i=0; i<8; i++) { + n163.wavePosCh[i]=(unsigned char)reader.readC(); + } + for (int i=0; i<8; i++) { + n163.waveLenCh[i]=(unsigned char)reader.readC(); + } + } + } + READ_FEAT_END; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index c2e4a7313..ef79a97a3 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -80,6 +80,9 @@ enum DivInstrumentType: unsigned short { DIV_INS_POKEMINI=47, DIV_INS_SM8521=48, DIV_INS_PV1000=49, + DIV_INS_K053260=50, + // DIV_INS_YMF292=51, + DIV_INS_TED=52, DIV_INS_MAX, DIV_INS_NULL }; @@ -445,6 +448,9 @@ struct DivInstrumentX1_010 { struct DivInstrumentN163 { int wave, wavePos, waveLen; unsigned char waveMode; + bool perChanPos; + int wavePosCh[8]; + int waveLenCh[8]; bool operator==(const DivInstrumentN163& other); bool operator!=(const DivInstrumentN163& other) { @@ -455,7 +461,13 @@ struct DivInstrumentN163 { wave(-1), wavePos(0), waveLen(32), - waveMode(3) {} + waveMode(3), + perChanPos(false) { + for (int i=0; i<8; i++) { + wavePosCh[i]=(i&3)<<5; + waveLenCh[i]=32; + } + } }; struct DivInstrumentFDS { diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 48128bc66..c97624c54 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -809,6 +809,8 @@ void DivPlatformAmiga::setFlags(const DivConfig& flags) { } else { chipClock=COLOR_NTSC; } + CHECK_CUSTOM_CLOCK; + rate=chipClock/AMIGA_DIVIDER; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate; 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 5c1521aba..6ca85ffe0 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -23,7 +23,7 @@ #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 diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 5f67b956a..0da59fc83 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -21,7 +21,7 @@ #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" @@ -62,9 +62,10 @@ class DivPlatformC64: 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; unsigned char filtControl, filtRes, vol; unsigned char writeOscBuf; diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index f388e860c..4f8745bda 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(); } @@ -361,26 +360,6 @@ void DivPlatformES5506::tick(bool sysTick) { } } } - if (chan[i].pcm.isNoteMap) { - // note map macros - if (chan[i].std.wave.had) { - if (chan[i].std.wave.val>=0 && chan[i].std.wave.val<120) { - if (chan[i].pcm.next!=chan[i].std.wave.val) { - chan[i].pcm.next=chan[i].std.wave.val; - chan[i].pcmChanged.index=1; - } - } - } - } else if (!chan[i].pcm.isNoteMap) { - if (chan[i].std.wave.had) { - if (chan[i].std.wave.val>=0 && chan[i].std.wave.valsong.sampleLen) { - if (chan[i].pcm.next!=chan[i].std.wave.val) { - chan[i].pcm.next=chan[i].std.wave.val; - chan[i].pcmChanged.index=1; - } - } - } - } // update registers if (chan[i].volChanged.changed) { // calculate volume (16 bit) @@ -433,7 +412,7 @@ void DivPlatformES5506::tick(bool sysTick) { off=(double)center/8363.0; } if (ins->amiga.useNoteMap) { - chan[i].pcm.note=next; + //chan[i].pcm.note=next; } // get loop mode DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOP_MAX; @@ -749,13 +728,13 @@ int DivPlatformES5506::dispatch(DivCommand c) { if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) || ((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSamplesong.sampleLen))) { int sample=ins->amiga.getSample(c.value); - c.value=ins->amiga.getFreq(c.value); if (sample>=0 && samplesong.sampleLen) { sampleValid=true; chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff; chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff; - chan[c.chan].pcm.note=c.value; chan[c.chan].pcm.next=ins->amiga.useNoteMap?c.value:sample; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].pcm.note=c.value; chan[c.chan].filter=ins->es5506.filter; chan[c.chan].envelope=ins->es5506.envelope; } @@ -871,20 +850,6 @@ int DivPlatformES5506::dispatch(DivCommand c) { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; - // sample commands - case DIV_CMD_WAVE: - if (!chan[c.chan].useWave) { - if (chan[c.chan].active) { - DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506); - if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) || - ((!ins->amiga.useNoteMap) && (c.value>=0 && c.valuesong.sampleLen))) { - chan[c.chan].pcm.next=c.value; - chan[c.chan].pcmChanged.index=1; - } - } - } - // reserved for useWave - break; // Filter commands case DIV_CMD_ES5506_FILTER_MODE: if (!chan[c.chan].active) { @@ -1095,8 +1060,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); @@ -1256,6 +1219,7 @@ int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, const DivCo dumpWrites=false; skipRegisterWrites=false; volScale=0; + curPage=0; for (int i=0; i<32; i++) { isMuted[i]=false; 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/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 a475b7da6..cec83e6df 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -22,8 +22,8 @@ #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 @@ -255,7 +255,7 @@ void DivPlatformGB::tick(bool sysTick) { chan[i].sweepChanged=true; break; case DivInstrumentGB::DIV_GB_HWCMD_WAIT: - chan[i].hwSeqDelay=data+1; + chan[i].hwSeqDelay=(data+1)*parent->tickMult; leave=true; break; case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL: 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 fcd969726..20e87d618 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -284,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; ireset(); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index e0a3e4a6c..d4ac27603 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -452,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; @@ -462,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 @@ -630,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..e4564134e 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) @@ -56,10 +56,10 @@ void DivPlatformMSM5232::acquire(short** buf, size_t len) { for (int i=0; i<8; i++) { int o=( - ((regPool[12+(i>>4)]&1)?((msm->vo16[i]*partVolume[3+(i&4)])>>8):0)+ - ((regPool[12+(i>>4)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+ - ((regPool[12+(i>>4)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+ - ((regPool[12+(i>>4)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0) + ((regPool[12+(i>>2)]&1)?((msm->vo16[i]*partVolume[3+(i&4)])>>8):0)+ + ((regPool[12+(i>>2)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+ + ((regPool[12+(i>>2)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+ + ((regPool[12+(i>>2)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0) )<<2; oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(o,-32768,32767); } 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..e5de16456 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -24,17 +24,13 @@ #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; } void DivPlatformMSM6258::acquire(short** buf, size_t len) { - short* outs[2]={ - &msmOut, - NULL - }; for (size_t h=0; hsound_stream_update(outs,1); + msm->sound_stream_update(&msmOut,1); msmDividerCount=msmDivider; } msmClockCount=msmClock; @@ -390,6 +386,9 @@ int DivPlatformMSM6258::init(DivEngine* p, int channels, int sugRate, const DivC oscBuf[i]=new DivDispatchOscBuffer; } msm=new okim6258_device(4000000); + msm->set_start_div(okim6258_device::FOSC_DIV_BY_1024); + msm->set_type(okim6258_device::TYPE_4BITS); + msm->set_outbits(okim6258_device::OUTPUT_12BITS); msm->device_start(); setFlags(flags); reset(); diff --git a/src/engine/platform/msm6258.h b/src/engine/platform/msm6258.h index 0c19d9763..6be120c2b 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,11 +42,11 @@ 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; unsigned char sampleBank, msmPan, msmDivider, rateSel, msmClock, clockSel; signed char msmDividerCount, msmClockCount; 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 1063349df..e24459ba9 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -22,9 +22,8 @@ #include "../../ta-log.h" #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) \ @@ -35,7 +34,7 @@ rWriteMask(0x78-(c<<3)+(a&7),v,m) \ } -#define CHIP_FREQBASE (15*32768) +#define CHIP_FREQBASE (15*524288) const char* regCheatSheetN163[]={ "FreqL7", "40", @@ -147,7 +146,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) { } else { // load from custom DivWavetable* wt=parent->getWave(wave); - for (int i=0; ilen; i++) { unsigned char addr=(pos+i); // address (nibble each) if (addr>=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area break; @@ -156,7 +155,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) { if (wt->max<1 || wt->len<1) { rWriteMask(addr>>1,0,mask); } else { - int data=wt->data[i*wt->len/len]*15/wt->max; + int data=wt->data[i]*15/wt->max; if (data<0) data=0; if (data>15) data=15; rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask); @@ -167,7 +166,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) { void DivPlatformN163::updateWaveCh(int ch) { if (ch<=chanMax) { - logV("updateWave with pos %d and len %d",chan[ch].wavePos,chan[ch].waveLen); + //logV("updateWave with pos %d and len %d",chan[ch].wavePos,chan[ch].waveLen); updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen); if (chan[ch].active && !isMuted[ch]) { chan[ch].volumeChanged=true; @@ -198,11 +197,8 @@ void DivPlatformN163::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].std.duty.had) { - if (chan[i].wavePos!=chan[i].std.duty.val) { - chan[i].wavePos=chan[i].std.duty.val; - if (chan[i].waveMode&0x2) { - chan[i].waveUpdated=true; - } + if (chan[i].curWavePos!=chan[i].std.duty.val) { + chan[i].curWavePos=chan[i].std.duty.val; chan[i].waveChanged=true; } } @@ -210,7 +206,7 @@ void DivPlatformN163::tick(bool sysTick) { 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) { + if (chan[i].waveMode) { chan[i].waveUpdated=true; } } @@ -225,60 +221,11 @@ void DivPlatformN163::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].std.ex1.had) { - if (chan[i].waveLen!=(chan[i].std.ex1.val&0xfc)) { - chan[i].waveLen=chan[i].std.ex1.val&0xfc; - chan[i].ws.setWidth(chan[i].waveLen); - if (chan[i].waveMode&0x2) { - chan[i].waveUpdated=true; - } + if (chan[i].curWaveLen!=(chan[i].std.ex1.val&0xfc)) { + chan[i].curWaveLen=chan[i].std.ex1.val&0xfc; chan[i].freqChanged=true; } } - if (chan[i].std.ex2.had) { - if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2.val&0x2)) { // update when every waveform changed - chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2.val&0x2); - if (chan[i].waveMode&0x2) { - chan[i].waveUpdated=true; - chan[i].waveChanged=true; - } - } - if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2.val&0x1)) { // update waveform now - chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2.val&0x1); - if (chan[i].waveMode&0x1) { // rising edge - chan[i].waveUpdated=true; - chan[i].waveChanged=true; - } - } - } - if (chan[i].std.ex3.had) { - if (chan[i].loadWave!=chan[i].std.ex3.val) { - chan[i].loadWave=chan[i].std.ex3.val; - if (chan[i].loadMode&0x2) { - updateWave(i,chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); - } - } - } - if (chan[i].std.alg.had) { - if (chan[i].loadPos!=chan[i].std.alg.val) { - chan[i].loadPos=chan[i].std.alg.val; - } - } - if (chan[i].std.fb.had) { - if (chan[i].loadLen!=(chan[i].std.fb.val&0xfc)) { - chan[i].loadLen=chan[i].std.fb.val&0xfc; - } - } - if (chan[i].std.fms.had) { - if ((chan[i].loadMode&0x2)!=(chan[i].std.fms.val&0x2)) { // load when every waveform changes - chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms.val&0x2); - } - if ((chan[i].loadMode&0x1)!=(chan[i].std.fms.val&0x1)) { // load now - chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms.val&0x1); - if (chan[i].loadMode&0x1) { // rising edge - updateWave(i,chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); - } - } - } if (chan[i].volumeChanged) { if (chan[i].active && !isMuted[i]) { chWriteMask(i,0x7,chan[i].resVol&0xf,0xf); @@ -288,7 +235,7 @@ void DivPlatformN163::tick(bool sysTick) { chan[i].volumeChanged=false; } if (chan[i].waveChanged) { - chWrite(i,0x6,chan[i].wavePos); + chWrite(i,0x6,chan[i].curWavePos); if (chan[i].active) { chan[i].freqChanged=true; } @@ -309,23 +256,22 @@ void DivPlatformN163::tick(bool sysTick) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { // TODO: what is this mess? chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); - chan[i].freq=(((chan[i].freq*chan[i].waveLen)*(chanMax+1))/16); + if (lenCompensate) { + chan[i].freq=(((chan[i].freq*chan[i].curWaveLen)*(chanMax+1))/256); + } else { + chan[i].freq*=(chanMax+1); + chan[i].freq>>=3; + } if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - if (chan[i].waveMode&0x2) { - updateWaveCh(i); - } - } } if (chan[i].keyOff && !isMuted[i]) { chWriteMask(i,0x7,0,0xf); } chWrite(i,0x0,chan[i].freq&0xff); chWrite(i,0x2,chan[i].freq>>8); - chWrite(i,0x4,((256-chan[i].waveLen)&0xfc)|((chan[i].freq>>16)&3)); + chWrite(i,0x4,((256-chan[i].curWaveLen)&0xfc)|((chan[i].freq>>16)&3)); if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; chan[i].freqChanged=false; @@ -338,14 +284,21 @@ int DivPlatformN163::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163); if (chan[c.chan].insChanged) { - chan[c.chan].wave=ins->n163.wave; - chan[c.chan].wavePos=ins->n163.wavePos; - chan[c.chan].waveLen=ins->n163.waveLen; + if (ins->n163.wave>=0) { + chan[c.chan].wave=ins->n163.wave; + } + chan[c.chan].wavePos=ins->n163.perChanPos?ins->n163.wavePosCh[c.chan&7]:ins->n163.wavePos; + chan[c.chan].waveLen=ins->n163.perChanPos?ins->n163.waveLenCh[c.chan&7]:ins->n163.waveLen; chan[c.chan].waveMode=ins->n163.waveMode; - chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,false); + chan[c.chan].curWavePos=chan[c.chan].wavePos; + chan[c.chan].curWaveLen=chan[c.chan].waveLen; + chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,true); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + } chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].waveChanged=true; - if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) { + if (chan[c.chan].waveMode) { chan[c.chan].waveUpdated=true; } } @@ -369,7 +322,7 @@ int DivPlatformN163::dispatch(DivCommand c) { chan[c.chan].active=false; chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; - //chan[c.chan].macroInit(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].active=false; @@ -411,13 +364,13 @@ int DivPlatformN163::dispatch(DivCommand c) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value; + chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:16); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value; + chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:16); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -432,68 +385,40 @@ int DivPlatformN163::dispatch(DivCommand c) { } case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - if (chan[c.chan].waveMode&0x2) { + if (chan[c.chan].waveMode) { chan[c.chan].waveUpdated=true; } chan[c.chan].keyOn=true; break; case DIV_CMD_N163_WAVE_POSITION: - chan[c.chan].wavePos=c.value; - if (chan[c.chan].waveMode&0x2) { - chan[c.chan].waveUpdated=true; - } + chan[c.chan].curWavePos=c.value; chan[c.chan].waveChanged=true; break; case DIV_CMD_N163_WAVE_LENGTH: - chan[c.chan].waveLen=c.value&0xfc; - if (chan[c.chan].waveMode&0x2) { - chan[c.chan].waveUpdated=true; - } + chan[c.chan].curWaveLen=c.value&0xfc; chan[c.chan].freqChanged=true; break; - case DIV_CMD_N163_WAVE_MODE: - chan[c.chan].waveMode=c.value&0x3; - if (chan[c.chan].waveMode&0x3) { // update now - chan[c.chan].waveUpdated=true; - chan[c.chan].waveChanged=true; - } - break; - case DIV_CMD_N163_WAVE_LOAD: - chan[c.chan].loadWave=c.value; - if (chan[c.chan].loadMode&0x2) { // load when every waveform changes - updateWave(c.chan,chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); - } - break; case DIV_CMD_N163_WAVE_LOADPOS: - chan[c.chan].loadPos=c.value; + chan[c.chan].wavePos=c.value; + if (chan[c.chan].waveMode) { + chan[c.chan].waveUpdated=true; + } break; case DIV_CMD_N163_WAVE_LOADLEN: - chan[c.chan].loadLen=c.value&0xfc; - break; - case DIV_CMD_N163_WAVE_LOADMODE: - chan[c.chan].loadMode=c.value&0x3; - if (chan[c.chan].loadMode&0x1) { // load now - updateWave(c.chan,chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + chan[c.chan].waveLen=c.value&0xfc; + if (chan[c.chan].waveMode) { + chan[c.chan].waveUpdated=true; } break; case DIV_CMD_N163_GLOBAL_WAVE_LOAD: loadWave=c.value; - if (loadMode&0x2) { // load when every waveform changes - updateWave(c.chan,loadWave,loadPos,loadLen); + if (loadWave>=0 && loadWavesong.waveLen) { + updateWave(-1,loadWave,loadPos,-1); } break; case DIV_CMD_N163_GLOBAL_WAVE_LOADPOS: loadPos=c.value; break; - case DIV_CMD_N163_GLOBAL_WAVE_LOADLEN: - loadLen=c.value&0xfc; - break; - case DIV_CMD_N163_GLOBAL_WAVE_LOADMODE: - loadMode=c.value&0x3; - if (loadMode&0x3) { // load now - updateWave(c.chan,loadWave,loadPos,loadLen); - } - break; case DIV_CMD_N163_CHANNEL_LIMIT: if (chanMax!=(c.value&0x7)) { chanMax=c.value&0x7; @@ -547,9 +472,6 @@ void DivPlatformN163::forceIns() { chan[i].freqChanged=true; chan[i].volumeChanged=true; chan[i].waveChanged=true; - if (chan[i].waveMode&0x2) { - chan[i].waveUpdated=true; - } } } } @@ -557,7 +479,7 @@ void DivPlatformN163::forceIns() { void DivPlatformN163::notifyWaveChange(int wave) { for (int i=0; i<8; i++) { if (chan[i].wave==wave) { - if (chan[i].waveMode&0x2) { + if (chan[i].waveMode) { chan[i].ws.changeWave1(wave); chan[i].waveUpdated=true; } @@ -619,8 +541,6 @@ void DivPlatformN163::reset() { chanMax=initChanMax; loadWave=-1; loadPos=0; - loadLen=0; - loadMode=0; rWrite(0x7f,initChanMax<<4); } @@ -655,6 +575,8 @@ void DivPlatformN163::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/(initChanMax+1); } + lenCompensate=flags.getBool("lenCompensate",false); + // needed to make sure changing channel count won't trigger glitches reset(); } diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 49c0ff05e..c5ec64b7e 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" @@ -29,9 +29,8 @@ class DivPlatformN163: public DivDispatch { struct Channel: public SharedChannel { signed char resVol; short wave, wavePos, waveLen; - unsigned char waveMode; - short loadWave, loadPos, loadLen; - unsigned char loadMode; + short curWavePos, curWaveLen; + bool waveMode; bool volumeChanged; bool waveChanged, waveUpdated; DivWaveSynth ws; @@ -41,11 +40,9 @@ class DivPlatformN163: public DivDispatch { wave(-1), wavePos(0), waveLen(0), + curWavePos(0), + curWaveLen(0), waveMode(0), - loadWave(-1), - loadPos(0), - loadLen(0), - loadMode(0), volumeChanged(false), waveChanged(false), waveUpdated(false) {} @@ -54,17 +51,17 @@ 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; - unsigned char loadMode; - bool multiplex; + short loadWave, loadPos; + bool multiplex, lenCompensate; n163_core n163; unsigned char regPool[128]; 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..b0012c224 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 @@ -230,13 +230,16 @@ void DivPlatformOPLL::tick(bool sysTick) { if (i>=6 && properDrums) { drumState&=~(0x10>>(i-6)); immWrite(0x0e,0x20|drumState); + logV("properDrums %d",i); } else if (i>=6 && drums) { drumState&=~(0x10>>(chan[i].note%12)); immWrite(0x0e,0x20|drumState); + logV("drums %d",i); } else { if (i<9) { immWrite(0x20+i,(chan[i].freqH)|(chan[i].state.alg?0x20:0)); } + logV("normal %d",i); } //chan[i].keyOn=false; chan[i].keyOff=false; @@ -253,7 +256,7 @@ void DivPlatformOPLL::tick(bool sysTick) { for (int i=0; i<11; i++) { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE); - if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq; + if (chan[i].fixedFreq>0 && properDrums) chan[i].freq=chan[i].fixedFreq; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; int freqt=toFreq(chan[i].freq); @@ -771,11 +774,17 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (c.value) { properDrums=true; immWrite(0x0e,0x20); + drumState=0; } else { properDrums=false; immWrite(0x0e,0x00); drumState=0; } + chan[6].freqChanged=true; + chan[7].freqChanged=true; + chan[8].freqChanged=true; + chan[9].freqChanged=true; + chan[10].freqChanged=true; break; case DIV_CMD_MACRO_OFF: chan[c.chan].std.mask(c.value,true); 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/pet.cpp b/src/engine/platform/pet.cpp index 4d085581e..e7f135759 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -283,6 +283,7 @@ void DivPlatformPET::reset() { memset(regPool,0,16); chan[0]=Channel(); chan[0].std.setEngine(parent); + rWrite(10,chan[0].wave); } int DivPlatformPET::getOutputCount() { 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.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 20032a1be..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) { 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_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..a6c3e26f9 100644 --- a/src/engine/platform/sound/oki/okim6258.cpp +++ b/src/engine/platform/sound/oki/okim6258.cpp @@ -135,9 +135,9 @@ void okim6258_device::device_reset() // sound_stream_update - handle a stream update //------------------------------------------------- -void okim6258_device::sound_stream_update(short** outputs, int len) +void okim6258_device::sound_stream_update(short* output, int len) { - auto &buffer = outputs[0]; + short* buffer = output; if (m_status & STATUS_PLAYING) { diff --git a/src/engine/platform/sound/oki/okim6258.h b/src/engine/platform/sound/oki/okim6258.h index 88a429d31..b3dc65efc 100644 --- a/src/engine/platform/sound/oki/okim6258.h +++ b/src/engine/platform/sound/oki/okim6258.h @@ -43,7 +43,7 @@ public: void device_clock_changed(); // sound stream updates - void sound_stream_update(short** outputs, int len); + void sound_stream_update(short* output, int len); private: int16_t clock_adpcm(uint8_t nibble); diff --git a/src/engine/platform/sound/ted-sound.c b/src/engine/platform/sound/ted-sound.c new file mode 100644 index 000000000..c03662997 --- /dev/null +++ b/src/engine/platform/sound/ted-sound.c @@ -0,0 +1,231 @@ +/* + * ted-sound.c + * + * Written by + * Andreas Boose + * Tibor Biczo + * Marco van den Heuvel + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#include +#include +#include + +#include "ted-sound.h" + +/* ------------------------------------------------------------------------- */ + +/* FIXME: Find proper volume multiplier. */ +const int16_t volume_tab[16] = { + 0x0000, 0x0800, 0x1000, 0x1800, 0x2000, 0x2800, 0x3000, 0x3800, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff +}; + +int ted_sound_machine_calculate_samples(struct plus4_sound_s* snd, int16_t *pbuf, int nr, int scc) +{ + int i; + int j; + int16_t volume; + + if (snd->digital) { + for (i = 0; i < nr; i++) { + pbuf[i] = (snd->volume * (snd->voice0_output_enabled + snd->voice1_output_enabled)); + } + } else { + for (i = 0; i < nr; i++) { + snd->sample_position_remainder += snd->sample_length_remainder; + if (snd->sample_position_remainder >= snd->speed) { + snd->sample_position_remainder -= snd->speed; + snd->sample_position_integer++; + } + snd->sample_position_integer += snd->sample_length_integer; + if (snd->sample_position_integer >= 8) { + /* Advance state engine */ + uint32_t ticks = snd->sample_position_integer >> 3; + if (snd->voice0_accu <= ticks) { + uint32_t delay = ticks - snd->voice0_accu; + snd->voice0_sign ^= 1; + snd->voice0_accu = 1023 - snd->voice0_reload; + if (snd->voice0_accu == 0) { + snd->voice0_accu = 1024; + } + if (delay >= snd->voice0_accu) { + snd->voice0_sign = ((delay / snd->voice0_accu) + & 1) ? snd->voice0_sign ^ 1 + : snd->voice0_sign; + snd->voice0_accu = snd->voice0_accu - (delay % snd->voice0_accu); + } else { + snd->voice0_accu -= delay; + } + } else { + snd->voice0_accu -= ticks; + } + + if (snd->voice1_accu <= ticks) { + uint32_t delay = ticks - snd->voice1_accu; + snd->voice1_sign ^= 1; + snd->noise_shift_register + = (snd->noise_shift_register << 1) + + ( 1 ^ ((snd->noise_shift_register >> 7) & 1) ^ + ((snd->noise_shift_register >> 5) & 1) ^ + ((snd->noise_shift_register >> 4) & 1) ^ + ((snd->noise_shift_register >> 1) & 1)); + snd->voice1_accu = 1023 - snd->voice1_reload; + if (snd->voice1_accu == 0) { + snd->voice1_accu = 1024; + } + if (delay >= snd->voice1_accu) { + snd->voice1_sign = ((delay / snd->voice1_accu) + & 1) ? snd->voice1_sign ^ 1 + : snd->voice1_sign; + for (j = 0; j < (int)(delay / snd->voice1_accu); + j++) { + snd->noise_shift_register + = (snd->noise_shift_register << 1) + + ( 1 ^ ((snd->noise_shift_register >> 7) & 1) ^ + ((snd->noise_shift_register >> 5) & 1) ^ + ((snd->noise_shift_register >> 4) & 1) ^ + ((snd->noise_shift_register >> 1) & 1)); + } + snd->voice1_accu = snd->voice1_accu - (delay % snd->voice1_accu); + } else { + snd->voice1_accu -= delay; + } + } else { + snd->voice1_accu -= ticks; + } + } + snd->sample_position_integer = snd->sample_position_integer & 7; + + volume = 0; + + if (snd->voice0_output_enabled && snd->voice0_sign) { + volume += snd->volume; + } + if (snd->voice1_output_enabled && !snd->noise && snd->voice1_sign) { + volume += snd->volume; + } + if (snd->voice1_output_enabled && snd->noise && (!(snd->noise_shift_register & 1))) { + volume += snd->volume; + } + + pbuf[i] = volume; + } + } + return nr; +} + +int ted_sound_machine_init(struct plus4_sound_s* snd, int speed, int cycles_per_sec) +{ + uint8_t val; + memset(snd,0,sizeof(struct plus4_sound_s)); + + snd->speed = speed; + snd->sample_length_integer = cycles_per_sec / speed; + snd->sample_length_remainder = cycles_per_sec % speed; + snd->sample_position_integer = 0; + snd->sample_position_remainder = 0; + snd->noise_shift_register = 0; + + snd->voice0_reload = (snd->plus4_sound_data[0] | (snd->plus4_sound_data[4] << 8)); + snd->voice1_reload = (snd->plus4_sound_data[1] | (snd->plus4_sound_data[2] << 8)); + val = snd->plus4_sound_data[3]; + snd->volume = volume_tab[val & 0x0f]; + snd->voice0_output_enabled = (val & 0x10) ? 1 : 0; + snd->voice1_output_enabled = (val & 0x60) ? 1 : 0; + snd->noise = ((val & 0x60) == 0x40) ? 1 : 0; + snd->digital = val & 0x80; + if (snd->digital) { + snd->voice0_sign = 1; + snd->voice0_accu = 0; + snd->voice1_sign = 1; + snd->voice1_accu = 0; + snd->noise_shift_register = 0; + } + + return 1; +} + +void ted_sound_machine_store(struct plus4_sound_s* snd, uint16_t addr, uint8_t val) +{ + switch (addr) { + case 0x0e: + snd->plus4_sound_data[0] = val; + snd->voice0_reload = (snd->plus4_sound_data[0] | (snd->plus4_sound_data[4] << 8)); + break; + case 0x0f: + snd->plus4_sound_data[1] = val; + snd->voice1_reload = (snd->plus4_sound_data[1] | (snd->plus4_sound_data[2] << 8)); + break; + case 0x10: + snd->plus4_sound_data[2] = val & 3; + snd->voice1_reload = (snd->plus4_sound_data[1] | (snd->plus4_sound_data[2] << 8)); + break; + case 0x11: + snd->volume = volume_tab[val & 0x0f]; + snd->voice0_output_enabled = (val & 0x10) ? 1 : 0; + snd->voice1_output_enabled = (val & 0x60) ? 1 : 0; + snd->noise = ((val & 0x60) == 0x40) ? 1 : 0; + snd->digital = val & 0x80; + if (snd->digital) { + snd->voice0_sign = 1; + snd->voice0_accu = 0; + snd->voice1_sign = 1; + snd->voice1_accu = 0; + snd->noise_shift_register = 0; + } + snd->plus4_sound_data[3] = val; + break; + case 0x12: + snd->plus4_sound_data[4] = val & 3; + snd->voice0_reload = (snd->plus4_sound_data[0] | (snd->plus4_sound_data[4] << 8)); + break; + } +} + +uint8_t ted_sound_machine_read(struct plus4_sound_s* snd, uint16_t addr) +{ + switch (addr) { + case 0x0e: + return snd->plus4_sound_data[0]; + case 0x0f: + return snd->plus4_sound_data[1]; + case 0x10: + return snd->plus4_sound_data[2] | 0xc0; + case 0x11: + return snd->plus4_sound_data[3]; + case 0x12: + return snd->plus4_sound_data[4]; + } + + return 0; +} + +void ted_sound_reset(struct plus4_sound_s* snd) +{ + uint16_t i; + + snd->noise_shift_register = 0; + for (i = 0x0e; i <= 0x12; i++) { + ted_sound_machine_store(snd,i,0); + } +} diff --git a/src/engine/platform/sound/ted-sound.h b/src/engine/platform/sound/ted-sound.h new file mode 100644 index 000000000..d2c6de1c4 --- /dev/null +++ b/src/engine/platform/sound/ted-sound.h @@ -0,0 +1,81 @@ +/* + * ted-sound.h + * + * Written by + * Andreas Boose + * Marco van den Heuvel + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#ifndef VICE_TEDSOUND_H +#define VICE_TEDSOUND_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct plus4_sound_s { + /* Voice 0 collect number of cycles elapsed */ + uint32_t voice0_accu; + /* Voice 0 toggle sign and reload accu if accu reached 0 */ + uint32_t voice0_reload; + /* Voice 0 sign of the square wave */ + int16_t voice0_sign; + uint8_t voice0_output_enabled; + + /* Voice 1 collect number of cycles elapsed */ + uint32_t voice1_accu; + /* Voice 1 toggle sign and reload accu if accu reached 0 */ + uint32_t voice1_reload; + /* Voice 1 sign of the square wave */ + int16_t voice1_sign; + uint8_t voice1_output_enabled; + + /* Volume multiplier */ + int16_t volume; + /* 8 cycles units per sample */ + uint32_t speed; + uint32_t sample_position_integer; + uint32_t sample_position_remainder; + uint32_t sample_length_integer; + uint32_t sample_length_remainder; + /* Digital output? */ + uint8_t digital; + /* Noise generator active? */ + uint8_t noise; + uint8_t noise_shift_register; + + /* Registers */ + uint8_t plus4_sound_data[5]; +}; + +int ted_sound_machine_init(struct plus4_sound_s* snd, int speed, int cycles_per_sec); +int ted_sound_machine_calculate_samples(struct plus4_sound_s* snd, int16_t *pbuf, int nr, int sound_chip_channels); +void ted_sound_machine_store(struct plus4_sound_s* snd, uint16_t addr, uint8_t val); +uint8_t ted_sound_machine_read(struct plus4_sound_s* snd, uint16_t addr); +void ted_sound_reset(struct plus4_sound_s* snd); + +#ifdef __cplusplus +}; +#endif +#endif diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 728d7a91a..3c591cdaf 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 @@ -626,7 +626,7 @@ void DivPlatformSoundUnit::quit() { delete oscBuf[i]; } delete su; - delete sampleMem; + delete[] sampleMem; } DivPlatformSoundUnit::~DivPlatformSoundUnit() { 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 f2fc6d439..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 diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index cff6cc627..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); 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/ted.cpp b/src/engine/platform/ted.cpp new file mode 100644 index 000000000..be5a379e7 --- /dev/null +++ b/src/engine/platform/ted.cpp @@ -0,0 +1,354 @@ +/** + * 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 "ted.h" +#include "../engine.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_DIVIDER 8 + +const char* regCheatSheetTED[]={ + "Freq0L", "0e", + "Freq1L", "0f", + "Freq1H", "10", + "Control", "11", + "Freq0H", "12", + NULL +}; + +const char** DivPlatformTED::getRegisterSheet() { + return regCheatSheetTED; +} + +void DivPlatformTED::acquire(short** buf, size_t len) { + for (size_t h=0; hdata[oscBuf[0]->needle++]=(ted.voice0_output_enabled && ted.voice0_sign)?(ted.volume<<1):0; + oscBuf[1]->data[oscBuf[1]->needle++]=(ted.voice1_output_enabled && ((ted.noise && (!(ted.noise_shift_register&1))) || (!ted.noise && ted.voice1_sign)))?(ted.volume<<1):0; + } +} + +void DivPlatformTED::tick(bool sysTick) { + bool resetPhase=false; + + for (int _i=0; _i<2; _i++) { + int i=chanOrder[_i]; + + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol,MIN(8,chan[i].std.vol.val),8); + updateCtrl=true; + vol=chan[i].outVol; + } + if (chan[i].std.duty.had) { + chan[i].noise=chan[i].std.duty.val&2; + chan[i].square=chan[i].std.duty.val&1; + chan[i].freqChanged=true; + updateCtrl=true; + } + 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].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=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)-1; + if (i==1 && chan[i].noise && !chan[i].square) chan[i].freq>>=4; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>1023) chan[i].freq=1023; + + if (i==1) { + rWrite(0x0f,(1022-chan[i].freq)&0xff); + rWrite(0x10,((1022-chan[i].freq)>>8)&0xff); + } else { + rWrite(0x0e,(1022-chan[i].freq)&0xff); + rWrite(0x12,((1022-chan[i].freq)>>8)&0xff); + } + + if (chan[i].keyOn) { + updateCtrl=true; + } + if (chan[i].keyOff) { + updateCtrl=true; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + resetPhase=true; + updateCtrl=true; + } + } + + if (resetPhase) { + rWrite(0x11,0x80); + } + + if (updateCtrl) { + updateCtrl=false; + rWrite(0x11,(vol&15)|((chan[0].active && chan[0].square && !isMuted[0])?0x10:0)|((chan[1].active && chan[1].square && !isMuted[1])?0x20:0)|((chan[1].active && chan[1].noise && !isMuted[1])?0x40:0)); + } +} + +int DivPlatformTED::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_TED); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + 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; + } + chan[c.chan].insChanged=false; + if (keyPriority) { + if (chanOrder[0]==c.chan) { + chanOrder[0]=chanOrder[1]; + chanOrder[1]=c.chan; + } + } + break; + } + case DIV_CMD_NOTE_OFF: + 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; + chan[c.chan].insChanged=true; + } + 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; + } + vol=chan[c.chan].outVol; + updateCtrl=true; + } + 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_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>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_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(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_TED)); + } + 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_GET_VOLMAX: + return 8; + 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 DivPlatformTED::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + updateCtrl=true; +} + +void DivPlatformTED::forceIns() { + for (int i=0; i<2; i++) { + chan[i].freqChanged=true; + } + updateCtrl=true; +} + +void* DivPlatformTED::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformTED::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformTED::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformTED::getRegisterPool() { + return regPool; +} + +int DivPlatformTED::getRegisterPoolSize() { + return 5; +} + +void DivPlatformTED::reset() { + writes.clear(); + memset(regPool,0,8); + for (int i=0; i<2; i++) { + chan[i]=DivPlatformTED::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + ted_sound_machine_init(&ted,1,8); + updateCtrl=true; + vol=15; + + chanOrder[0]=0; + chanOrder[1]=1; +} + +int DivPlatformTED::getOutputCount() { + return 1; +} + +bool DivPlatformTED::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformTED::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformTED::setFlags(const DivConfig& flags) { + if (flags.getInt("clockSel",0)) { + chipClock=COLOR_PAL*2.0/5.0; + } else { + chipClock=COLOR_NTSC/2.0; + } + CHECK_CUSTOM_CLOCK; + rate=chipClock/8; + for (int i=0; i<2; i++) { + oscBuf[i]->rate=rate; + } + keyPriority=flags.getBool("keyPriority",true); +} + +void DivPlatformTED::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformTED::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformTED::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<2; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 2; +} + +void DivPlatformTED::quit() { + for (int i=0; i<2; i++) { + delete oscBuf[i]; + } +} + +DivPlatformTED::~DivPlatformTED() { +} diff --git a/src/engine/platform/ted.h b/src/engine/platform/ted.h new file mode 100644 index 000000000..25c41e3d8 --- /dev/null +++ b/src/engine/platform/ted.h @@ -0,0 +1,78 @@ +/** + * 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. + */ + +#ifndef _TED_H +#define _TED_H + +#include "../dispatch.h" +#include "../fixedQueue.h" +#include "sound/ted-sound.h" + +class DivPlatformTED: public DivDispatch { + struct Channel: public SharedChannel { + bool noise, square; + Channel(): + SharedChannel(8), + noise(false), + square(true) {} + }; + Channel chan[2]; + DivDispatchOscBuffer* oscBuf[2]; + bool isMuted[2]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + FixedQueue writes; + + struct plus4_sound_s ted; + unsigned char vol; + bool updateCtrl, keyPriority; + + unsigned char chanOrder[2]; + unsigned char regPool[8]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short** buf, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + bool keyOffAffectsArp(int ch); + void setFlags(const DivConfig& flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformTED(); +}; + +#endif 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/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 9f8a2f10b..aabce5c6a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1130,8 +1130,10 @@ void DivEngine::nextRow() { } } - prevOrder=curOrder; - prevRow=curRow; + if (!stepPlay) { + prevOrder=curOrder; + prevRow=curRow; + } for (int i=0; i0) ret+=" + "; - if (ds.system[i]==DIV_SYSTEM_N163) { - ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME); - } else { - ret+=getSystemName(ds.system[i]); - } + ret+=getSystemName(ds.system[i]); } return ret; @@ -242,11 +236,6 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta const char* DivEngine::getSystemName(DivSystem sys) { if (sysDefs[sys]==NULL) return "Unknown"; - if (sys==DIV_SYSTEM_N163) { - String c1=getConfString("c163Name",DIV_C163_DEFAULT_NAME); - strncpy(c163NameCS,c1.c_str(),1023); - return c163NameCS; - } return sysDefs[sys]->name; } @@ -998,27 +987,24 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_N163]=new DivSysDef( - "Namco 163/C163/129/160/106/whatever", NULL, 0x8c, 0, 8, false, true, 0, false, 0, + "Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false, 0, "an expansion chip for the Famicom, with full wavetable.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163}, {}, + { + {0x18, {DIV_CMD_N163_CHANNEL_LIMIT, "18xx: Change channel limits (0 to 7, x + 1)"}}, + {0x20, {DIV_CMD_N163_GLOBAL_WAVE_LOAD, "20xx: Load a waveform into memory"}}, + {0x21, {DIV_CMD_N163_GLOBAL_WAVE_LOADPOS, "21xx: Set position for wave load"}} + }, { {0x10, {DIV_CMD_WAVE, "10xx: Select waveform"}}, - {0x11, {DIV_CMD_N163_WAVE_POSITION, "11xx: Set waveform position in RAM (single nibble unit)"}}, - {0x12, {DIV_CMD_N163_WAVE_LENGTH, "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)"}}, - {0x13, {DIV_CMD_N163_WAVE_MODE, "130x: Change waveform update mode (0: off; bit 0: update now; bit 1: update when every waveform changes)"}}, - {0x14, {DIV_CMD_N163_WAVE_LOAD, "14xx: Select waveform for load to RAM"}}, - {0x15, {DIV_CMD_N163_WAVE_LOADPOS, "15xx: Set waveform position for load to RAM (single nibble unit)"}}, - {0x16, {DIV_CMD_N163_WAVE_LOADLEN, "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)"}}, - {0x17, {DIV_CMD_N163_WAVE_LOADMODE, "170x: Change waveform load mode (0: off; bit 0: load now; bit 1: load when every waveform changes)"}}, - {0x18, {DIV_CMD_N163_CHANNEL_LIMIT, "180x: Change channel limits (0 to 7, x + 1)"}}, - {0x20, {DIV_CMD_N163_GLOBAL_WAVE_LOAD, "20xx: (Global) Select waveform for load to RAM"}}, - {0x21, {DIV_CMD_N163_GLOBAL_WAVE_LOADPOS, "21xx: (Global) Set waveform position for load to RAM (single nibble unit)"}}, - {0x22, {DIV_CMD_N163_GLOBAL_WAVE_LOADLEN, "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)"}}, - {0x23, {DIV_CMD_N163_GLOBAL_WAVE_LOADMODE, "230x: (Global) Change waveform load mode (0: off; bit 0: load now; bit 1: load when every waveform changes)"}}, + {0x11, {DIV_CMD_N163_WAVE_POSITION, "11xx: Set waveform position in RAM"}}, + {0x12, {DIV_CMD_N163_WAVE_LENGTH, "12xx: Set waveform length in RAM (04 to FC in steps of 4)"}}, + {0x15, {DIV_CMD_N163_WAVE_LOADPOS, "15xx: Set waveform load position"}}, + {0x16, {DIV_CMD_N163_WAVE_LOADLEN, "16xx: Set waveform load length (04 to FC in steps of 4)"}}, } ); @@ -1556,7 +1542,6 @@ void DivEngine::registerSystems() { ); EffectHandlerMap es5506PreEffectHandlerMap={ - {0x10, {DIV_CMD_WAVE, "10xx: Change waveform (00 to FF)",effectVal}}, {0x11, {DIV_CMD_ES5506_FILTER_MODE, "11xx: Set filter mode (00 to 03)",effectValAnd<3>}}, {0x14, {DIV_CMD_ES5506_FILTER_K1, "14xx: Set filter coefficient K1 low byte (00 to FF)",effectValShift<0>,constVal<0x00ff>}}, {0x15, {DIV_CMD_ES5506_FILTER_K1, "15xx: Set filter coefficient K1 high byte (00 to FF)",effectValShift<8>,constVal<0xff00>}}, @@ -1864,6 +1849,29 @@ void DivEngine::registerSystems() { } ); + sysDefs[DIV_SYSTEM_K053260]=new DivSysDef( + "Konami K053260", NULL, 0xcc, 0, 4, false, true, 0x161, false, 1U<writeC(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; } @@ -1029,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; @@ -1201,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}; @@ -1725,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; @@ -2086,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); diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 5dadfcf67..37a40c527 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -38,6 +38,7 @@ const char* aboutLine[]={ "akumanatt", "cam900", "djtuBIG-MaliceX", + "Eknous-P", "laoo", "MooingLemur", "OPNA2608", @@ -114,6 +115,7 @@ const char* aboutLine[]={ "potatoTeto", "psxdominator", "Raijin", + "railzen7", "SnugglyBun", "SuperJet Spade", "SwapXFO", @@ -182,6 +184,8 @@ const char* aboutLine[]={ "Stella by Stella Team", "QSound emulator by superctr and Valley Bell", "VICE VIC-20 sound core by Rami Rasanen and viznut", + "VICE TED sound core by Andreas Boose, Tibor Biczo", + "and Marco van den Heuvel", "VERA sound core by Frank van den Hoef", "mzpokeysnd POKEY emulator by Michael Borisov", "ASAP POKEY emulator by Piotr Fusik", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 809f214b7..803a5665b 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -308,6 +308,7 @@ void FurnaceGUI::drawChanOsc() { "- %I: instrument number (decimal)\n" "- %x: instrument number (hex)\n" "- %s: chip name\n" + "- %p: chip part number\n" "- %S: chip ID\n" "- %v: volume (decimal)\n" "- %V: volume (percentage)\n" @@ -510,6 +511,10 @@ void FurnaceGUI::drawChanOsc() { text+=e->getSystemName(e->sysOfChan[ch]); break; } + case 'p': { + text+=FurnaceGUI::getSystemPartNumber(e->sysOfChan[ch], e->song.systemFlags[e->dispatchOfChan[ch]]); + break; + } case 'S': { text+=fmt::sprintf("%d",e->dispatchOfChan[ch]); break; diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 09da1b257..15f9e53ff 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -271,9 +271,15 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (cursor.y>=e->curSubSong->patLen) { if (settings.wrapVertical!=0 && !select) { cursor.y=0; - if (settings.wrapVertical==2) { - if ((!e->isPlaying() || !followPattern) && curOrder<(e->curSubSong->ordersLen-1)) { - setOrder(curOrder+1); + if (settings.wrapVertical>1) { + if (!e->isPlaying() || !followPattern) { + if (curOrder<(e->curSubSong->ordersLen-1)) { + setOrder(curOrder+1); + } else if (settings.wrapVertical==3) { + setOrder(0); + } else { + cursor.y=e->curSubSong->patLen-1; + } } else { cursor.y=e->curSubSong->patLen-1; } @@ -289,9 +295,15 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (cursor.y<0) { if (settings.wrapVertical!=0 && !select) { cursor.y=e->curSubSong->patLen-1; - if (settings.wrapVertical==2) { - if ((!e->isPlaying() || !followPattern) && curOrder>0) { - setOrder(curOrder-1); + if (settings.wrapVertical>1) { + if (!e->isPlaying() || !followPattern) { + if (curOrder>0) { + setOrder(curOrder-1); + } else if (settings.wrapVertical==3) { + setOrder(e->curSubSong->ordersLen-1); + } else { + cursor.y=0; + } } else { cursor.y=0; } diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index b9c1e11a7..289584492 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -77,7 +77,6 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) { if (i>=0 && isong.insLen) { DivInstrument* ins=e->song.ins[i]; insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]; - if (ins->type==DIV_INS_N163) insType=settings.c163Name.c_str(); switch (ins->type) { case DIV_INS_FM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); @@ -279,6 +278,14 @@ 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; + case DIV_INS_TED: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TED]); + 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 60806bde1..bd6bdb81f 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 \ @@ -461,8 +462,6 @@ void putDispatchChip(void* data, int type) { ImGui::Text("- chanMax: %d",ch->chanMax); ImGui::Text("- loadWave: %d",ch->loadWave); ImGui::Text("- loadPos: %d",ch->loadPos); - ImGui::Text("- loadLen: %d",ch->loadLen); - ImGui::Text("- loadMode: %d",ch->loadMode); COMMON_CHIP_DEBUG_BOOL; ImGui::TextColored(ch->multiplex?colorOn:colorOff,">> Multiplex"); break; @@ -544,6 +543,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; @@ -868,10 +874,6 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("- wavepos: %d",ch->wavePos); ImGui::Text("- wavelen: %d",ch->waveLen); ImGui::Text("- wavemode: %d",ch->waveMode); - ImGui::Text("- loadwave: %d",ch->loadWave); - ImGui::Text("- loadpos: %d",ch->loadPos); - ImGui::Text("- loadlen: %d",ch->loadLen); - ImGui::Text("- loadmode: %d",ch->loadMode); ImGui::Text("- resVol: %.2x",ch->resVol); COMMON_CHAN_DEBUG_BOOL; ImGui::TextColored(ch->volumeChanged?colorOn:colorOff,">> VolumeChanged"); @@ -1082,6 +1084,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/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 f40d7f27d..a2c3c7422 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -98,6 +98,10 @@ const char* FurnaceGUI::noteName(short note, short octave) { if (seek<0 || seek>=180) { return "???"; } + if (settings.flatNotes) { + if (settings.germanNotation) return noteNamesGF[seek]; + return noteNamesF[seek]; + } if (settings.germanNotation) return noteNamesG[seek]; return noteNames[seek]; } @@ -606,10 +610,11 @@ void FurnaceGUI::autoDetectSystem() { std::map sysCountMap; std::map sysConfMap; for (int i=0; isong.systemLen; i++) { - try { - sysCountMap.at(e->song.system[i])++; - } catch (std::exception& ex) { + auto it=sysCountMap.find(e->song.system[i]); + if (it==sysCountMap.cend()) { sysCountMap[e->song.system[i]]=1; + } else { + it->second++; } sysConfMap[e->song.system[i]]=e->song.systemFlags[i]; } @@ -627,10 +632,11 @@ void FurnaceGUI::autoDetectSystem() { defCountMap.clear(); defConfMap.clear(); for (FurnaceGUISysDefChip& k: j.orig) { - try { - defCountMap.at(k.sys)++; - } catch (std::exception& ex) { + auto it=defCountMap.find(k.sys); + if (it==defCountMap.cend()) { defCountMap[k.sys]=1; + } else { + it->second++; } DivConfig dc; dc.loadFromMemory(k.flags); @@ -643,27 +649,37 @@ void FurnaceGUI::autoDetectSystem() { logV("- %s: %d",e->getSystemName(k.first),k.second); }*/ for (std::pair k: defCountMap) { - try { - if (sysCountMap.at(k.first)!=k.second) { - isMatch=false; - break; - } - DivConfig& sysDC=sysConfMap.at(k.first); - for (std::pair l: defConfMap.at(k.first).configMap()) { - if (!sysDC.has(l.first)) { - isMatch=false; - break; - } - if (sysDC.getString(l.first,"")!=l.second) { - isMatch=false; - break; - } - } - if (!isMatch) break; - } catch (std::exception& ex) { + auto countI=sysCountMap.find(k.first); + if (countI==sysCountMap.cend()) { + isMatch=false; + break; + } else if (countI->second!=k.second) { isMatch=false; break; } + + auto confI=sysConfMap.find(k.first); + if (confI==sysConfMap.cend()) { + isMatch=false; + break; + } + DivConfig& sysDC=confI->second; + auto defConfI=defConfMap.find(k.first); + if (defConfI==defConfMap.cend()) { + isMatch=false; + break; + } + for (std::pair l: defConfI->second.configMap()) { + if (!sysDC.has(l.first)) { + isMatch=false; + break; + } + if (sysDC.getString(l.first,"")!=l.second) { + isMatch=false; + break; + } + } + if (!isMatch) break; } if (isMatch) { logV("match found!"); @@ -682,11 +698,7 @@ void FurnaceGUI::autoDetectSystem() { if (k.second>1) { e->song.systemName+=fmt::sprintf("%d×",k.second); } - if (k.first==DIV_SYSTEM_N163) { - e->song.systemName+=settings.c163Name; - } else { - e->song.systemName+=e->getSystemName(k.first); - } + e->song.systemName+=e->getSystemName(k.first); isFirst=false; } } @@ -1097,8 +1109,9 @@ void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { } void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { - try { - int key=noteKeys.at(scancode); + auto it=noteKeys.find(scancode); + if (it!=noteKeys.cend()) { + int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) if (num>119) num=119; // B-9 @@ -1110,7 +1123,6 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { e->synchronized([this,num]() { e->autoNoteOff(-1,num); }); - } catch (std::out_of_range& e) { } } @@ -1310,8 +1322,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { break; } } else { - try { - int num=valueKeys.at(ev.key.keysym.sym); + auto it=valueKeys.find(ev.key.keysym.sym); + if (it!=valueKeys.cend()) { + int num=it->second; switch (latchTarget) { case 1: // instrument changeLatch(latchIns); @@ -1326,7 +1339,6 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { changeLatch(latchEffectVal); break; } - } catch (std::out_of_range& e) { } } return; @@ -1339,8 +1351,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { alterSampleMap(true,-1); return; } - try { - int key=noteKeys.at(ev.key.keysym.scancode); + auto it=noteKeys.find(ev.key.keysym.scancode); + if (it!=noteKeys.cend()) { + int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) @@ -1348,7 +1361,6 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { alterSampleMap(true,num); return; - } catch (std::out_of_range& e) { } } else { // TODO: map? @@ -1356,34 +1368,35 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { alterSampleMap(false,-1); return; } - try { - int num=valueKeys.at(ev.key.keysym.sym); + auto it=valueKeys.find(ev.key.keysym.sym); + if (it!=valueKeys.cend()) { + int num=it->second; if (num<10) { alterSampleMap(false,num); return; } - } catch (std::out_of_range& e) { } } } // PER-WINDOW KEYS switch (curWindow) { - case GUI_WINDOW_PATTERN: - try { - int action=actionMapPat.at(mapped); + case GUI_WINDOW_PATTERN: { + auto actionI=actionMapPat.find(mapped); + if (actionI!=actionMapPat.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } // pattern input otherwise if (mapped&(FURKMOD_ALT|FURKMOD_CTRL|FURKMOD_META|FURKMOD_SHIFT)) break; if (!ev.key.repeat) { if (cursor.xFine==0) { // note - try { - int key=noteKeys.at(ev.key.keysym.scancode); + auto it=noteKeys.find(ev.key.keysym.scancode); + if (it!=noteKeys.cend()) { + int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) @@ -1392,33 +1405,36 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (edit) { noteInput(num,key); } - } catch (std::out_of_range& e) { } } else if (edit) { // value - try { - int num=valueKeys.at(ev.key.keysym.sym); + auto it=valueKeys.find(ev.key.keysym.sym); + if (it!=valueKeys.cend()) { + int num=it->second; valueInput(num); - } catch (std::out_of_range& e) { } } } break; - case GUI_WINDOW_ORDERS: - try { - int action=actionMapOrders.at(mapped); + } + case GUI_WINDOW_ORDERS: { + auto actionI=actionMapOrders.find(mapped); + if (actionI!=actionMapOrders.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } // order input otherwise if (mapped&(FURKMOD_ALT|FURKMOD_CTRL|FURKMOD_META|FURKMOD_SHIFT)) break; if (orderEditMode!=0) { - try { - int num=valueKeys.at(ev.key.keysym.sym); + auto it=valueKeys.find(ev.key.keysym.sym); + if (it!=valueKeys.cend()) { + int num=it->second; if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { + prepareUndo(GUI_UNDO_CHANGE_ORDER); e->lockSave([this,num]() { + if (!curNibble && !settings.pushNibble) e->curOrders->ord[orderCursor][curOrder]=0; e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); MARK_MODIFIED; @@ -1436,63 +1452,68 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } } e->walkSong(loopOrder,loopRow,loopEnd); + makeUndo(GUI_UNDO_CHANGE_ORDER); } - } catch (std::out_of_range& e) { } } break; - case GUI_WINDOW_SAMPLE_EDIT: - try { - int action=actionMapSample.at(mapped); + } + case GUI_WINDOW_SAMPLE_EDIT: { + auto actionI=actionMapSample.find(mapped); + if (actionI!=actionMapSample.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } break; - case GUI_WINDOW_INS_LIST: - try { - int action=actionMapInsList.at(mapped); + } + case GUI_WINDOW_INS_LIST: { + auto actionI=actionMapInsList.find(mapped); + if (actionI!=actionMapInsList.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } break; - case GUI_WINDOW_WAVE_LIST: - try { - int action=actionMapWaveList.at(mapped); + } + case GUI_WINDOW_WAVE_LIST: { + auto actionI=actionMapWaveList.find(mapped); + if (actionI!=actionMapWaveList.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } break; - case GUI_WINDOW_SAMPLE_LIST: - try { - int action=actionMapSampleList.at(mapped); + } + case GUI_WINDOW_SAMPLE_LIST: { + auto actionI=actionMapSampleList.find(mapped); + if (actionI!=actionMapSampleList.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } break; + } default: break; } // GLOBAL KEYS - try { - int action=actionMapGlobal.at(mapped); + auto actionI=actionMapGlobal.find(mapped); + if (actionI!=actionMapGlobal.cend()) { + int action=actionI->second; if (action>0) { doAction(action); return; } - } catch (std::out_of_range& e) { } } @@ -2419,7 +2440,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { if (y>waveDragMax) y=waveDragMax; if (ynotifyWaveChange(curWave); + notifyWaveChange=true; MARK_MODIFIED; } } @@ -2978,9 +2999,10 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { if (settings.notePreviewBehavior==0) return 1; switch (curWindow) { case GUI_WINDOW_SAMPLE_EDIT: - case GUI_WINDOW_SAMPLE_LIST: - try { - int key=noteKeys.at(ev->key.keysym.scancode); + case GUI_WINDOW_SAMPLE_LIST: { + auto it=noteKeys.find(ev->key.keysym.scancode); + if (it!=noteKeys.cend()) { + int key=it->second; int num=12*curOctave+key; if (key!=100 && key!=101 && key!=102) { int pStart=-1; @@ -3001,13 +3023,14 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { samplePreviewKey=ev->key.keysym.scancode; samplePreviewNote=num; } - } catch (std::out_of_range& e) { } break; + } case GUI_WINDOW_WAVE_LIST: - case GUI_WINDOW_WAVE_EDIT: - try { - int key=noteKeys.at(ev->key.keysym.scancode); + case GUI_WINDOW_WAVE_EDIT: { + auto it=noteKeys.find(ev->key.keysym.scancode); + if (it!=noteKeys.cend()) { + int key=it->second; int num=12*curOctave+key; if (key!=100 && key!=101 && key!=102) { e->previewWave(curWave,num); @@ -3015,9 +3038,9 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { wavePreviewKey=ev->key.keysym.scancode; wavePreviewNote=num; } - } catch (std::out_of_range& e) { } break; + } case GUI_WINDOW_ORDERS: // ignore here break; case GUI_WINDOW_PATTERN: @@ -3027,9 +3050,10 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { if (edit && cursor.xFine!=0) break; } // fall-through - default: - try { - int key=noteKeys.at(ev->key.keysym.scancode); + default: { + auto it=noteKeys.find(ev->key.keysym.scancode); + if (it!=noteKeys.cend()) { + int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) @@ -3038,9 +3062,9 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { if (key!=100 && key!=101 && key!=102) { previewNote(cursor.xCoarse,num); } - } catch (std::out_of_range& e) { } break; + } } } } else if (ev->type==SDL_KEYUP) { @@ -3733,6 +3757,11 @@ bool FurnaceGUI::loop() { midiLock.unlock(); } + if (notifyWaveChange) { + notifyWaveChange=false; + e->notifyWaveChange(curWave); + } + eventTimeEnd=SDL_GetPerformanceCounter(); if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { @@ -4845,6 +4874,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: @@ -5712,10 +5742,26 @@ 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(); @@ -6720,6 +6766,7 @@ FurnaceGUI::FurnaceGUI(): preserveChanPos(false), wantScrollList(false), noteInputPoly(true), + notifyWaveChange(false), displayPendingIns(false), pendingInsSingle(false), displayPendingRawSample(false), @@ -6752,6 +6799,7 @@ FurnaceGUI::FurnaceGUI(): pendingRawSampleUnsigned(false), pendingRawSampleBigEndian(false), pendingRawSampleSwapNibbles(false), + pendingRawSampleReplace(false), globalWinFlags(0), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), @@ -7017,7 +7065,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 83c5a380a..b4a8a3e5b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -239,6 +239,9 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_POKEMINI, GUI_COLOR_INSTR_SM8521, GUI_COLOR_INSTR_PV1000, + GUI_COLOR_INSTR_K053260, + GUI_COLOR_INSTR_SCSP, + GUI_COLOR_INSTR_TED, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, @@ -1310,7 +1313,7 @@ class FurnaceGUI { 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; @@ -1334,7 +1337,7 @@ class FurnaceGUI { String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; - bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles; + bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles, pendingRawSampleReplace; ImGuiWindowFlags globalWinFlags; @@ -1418,6 +1421,7 @@ class FurnaceGUI { int chipNames; int overflowHighlight; int partyTime; + int flatNotes; int germanNotation; int stepOnDelete; int scrollStep; @@ -1520,13 +1524,13 @@ class FurnaceGUI { int pullDeleteRow; int newSongBehavior; int memUsageUnit; + int cursorFollowsWheel; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; String audioDevice; String midiInDevice; String midiOutDevice; - String c163Name; String renderBackend; String renderDriver; String initialSysName; @@ -1674,13 +1678,13 @@ class FurnaceGUI { pullDeleteRow(1), newSongBehavior(0), memUsageUnit(1), + cursorFollowsWheel(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), audioDevice(""), midiInDevice(""), midiOutDevice(""), - c163Name(""), renderBackend(""), renderDriver(""), initialSysName("Sega Genesis/Mega Drive"), @@ -1913,7 +1917,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; @@ -2285,6 +2290,7 @@ class FurnaceGUI { bool quitRender(); const char* getSystemName(DivSystem which); + const char* getSystemPartNumber(DivSystem sys, DivConfig& flags); public: void editStr(String* which); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 25364d2d9..7e7aecc18 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -62,6 +62,42 @@ const char* noteNamesG[180]={ "C-9", "C#9", "D-9", "D#9", "E-9", "F-9", "F#9", "G-9", "G#9", "A-9", "A#9", "H-9" }; +const char* noteNamesF[180]={ + "c_5", "dd5", "d_5", "ed5", "e_5", "f_5", "gd5", "g_5", "ad5", "a_5", "bd5", "b_5", + "c_4", "dd4", "d_4", "ed4", "e_4", "f_4", "gd4", "g_4", "ad4", "a_4", "bd4", "b_4", + "c_3", "dd3", "d_3", "ed3", "e_3", "f_3", "gd3", "g_3", "ad3", "a_3", "bd3", "b_3", + "c_2", "dd2", "d_2", "ed2", "e_2", "f_2", "gd2", "g_2", "ad2", "a_2", "bd2", "b_2", + "c_1", "dd1", "d_1", "ed1", "e_1", "f_1", "gd1", "g_1", "ad1", "a_1", "bd1", "b_1", + "C-0", "Db0", "D-0", "Eb0", "E-0", "F-0", "Gb0", "G-0", "Ab0", "A-0", "Bb0", "B-0", + "C-1", "Db1", "D-1", "Eb1", "E-1", "F-1", "Gb1", "G-1", "Ab1", "A-1", "Bb1", "B-1", + "C-2", "Db2", "D-2", "Eb2", "E-2", "F-2", "Gb2", "G-2", "Ab2", "A-2", "Bb2", "B-2", + "C-3", "Db3", "D-3", "Eb3", "E-3", "F-3", "Gb3", "G-3", "Ab3", "A-3", "Bb3", "B-3", + "C-4", "Db4", "D-4", "Eb4", "E-4", "F-4", "Gb4", "G-4", "Ab4", "A-4", "Bb4", "B-4", + "C-5", "Db5", "D-5", "Eb5", "E-5", "F-5", "Gb5", "G-5", "Ab5", "A-5", "Bb5", "B-5", + "C-6", "Db6", "D-6", "Eb6", "E-6", "F-6", "Gb6", "G-6", "Ab6", "A-6", "Bb6", "B-6", + "C-7", "Db7", "D-7", "Eb7", "E-7", "F-7", "Gb7", "G-7", "Ab7", "A-7", "Bb7", "B-7", + "C-8", "Db8", "D-8", "Eb8", "E-8", "F-8", "Gb8", "G-8", "Ab8", "A-8", "Bb8", "B-8", + "C-9", "Db9", "D-9", "Eb9", "E-9", "F-9", "Gb9", "G-9", "Ab9", "A-9", "Bb9", "B-9" +}; + +const char* noteNamesGF[180]={ + "c_5", "dd5", "d_5", "ed5", "e_5", "f_5", "gd5", "g_5", "ad5", "a_5", "b_5", "h_5", + "c_4", "dd4", "d_4", "ed4", "e_4", "f_4", "gd4", "g_4", "ad4", "a_4", "b_4", "h_4", + "c_3", "dd3", "d_3", "ed3", "e_3", "f_3", "gd3", "g_3", "ad3", "a_3", "b_3", "h_3", + "c_2", "dd2", "d_2", "ed2", "e_2", "f_2", "gd2", "g_2", "ad2", "a_2", "b_2", "h_2", + "c_1", "dd1", "d_1", "ed1", "e_1", "f_1", "gd1", "g_1", "ad1", "a_1", "b_1", "h_1", + "C-0", "Db0", "D-0", "Eb0", "E-0", "F-0", "Gb0", "G-0", "Ab0", "A-0", "B-0", "H-0", + "C-1", "Db1", "D-1", "Eb1", "E-1", "F-1", "Gb1", "G-1", "Ab1", "A-1", "B-1", "H-1", + "C-2", "Db2", "D-2", "Eb2", "E-2", "F-2", "Gb2", "G-2", "Ab2", "A-2", "B-2", "H-2", + "C-3", "Db3", "D-3", "Eb3", "E-3", "F-3", "Gb3", "G-3", "Ab3", "A-3", "B-3", "H-3", + "C-4", "Db4", "D-4", "Eb4", "E-4", "F-4", "Gb4", "G-4", "Ab4", "A-4", "B-4", "H-4", + "C-5", "Db5", "D-5", "Eb5", "E-5", "F-5", "Gb5", "G-5", "Ab5", "A-5", "B-5", "H-5", + "C-6", "Db6", "D-6", "Eb6", "E-6", "F-6", "Gb6", "G-6", "Ab6", "A-6", "B-6", "H-6", + "C-7", "Db7", "D-7", "Eb7", "E-7", "F-7", "Gb7", "G-7", "Ab7", "A-7", "B-7", "H-7", + "C-8", "Db8", "D-8", "Eb8", "E-8", "F-8", "Gb8", "G-8", "Ab8", "A-8", "B-8", "H-8", + "C-9", "Db9", "D-9", "Eb9", "E-9", "F-9", "Gb9", "G-9", "Ab9", "A-9", "B-9", "H-9" +}; + const char* pitchLabel[11]={ "1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x" }; @@ -131,6 +167,9 @@ const char* insTypes[DIV_INS_MAX+1]={ "Pokémon Mini/QuadTone", "SM8521", "PV-1000", + "K053260", + "SCSP", + "TED", NULL }; @@ -851,6 +890,9 @@ 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_SCSP,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_TED,"",ImVec4(0.7f,0.6f,1.0f,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 +1076,8 @@ const int availableSystems[]={ DIV_SYSTEM_GA20, DIV_SYSTEM_SM8521, DIV_SYSTEM_PV1000, + DIV_SYSTEM_K053260, + DIV_SYSTEM_TED, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, 0 // don't remove this last one! @@ -1084,6 +1128,7 @@ const int chipsSquare[]={ DIV_SYSTEM_MSM5232, DIV_SYSTEM_T6W28, DIV_SYSTEM_PV1000, + DIV_SYSTEM_TED, 0 // don't remove this last one! }; @@ -1142,6 +1187,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/guiConst.h b/src/gui/guiConst.h index 00b350495..494ba58dc 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -38,6 +38,8 @@ struct FurnaceGUIColorDef { extern const int opOrder[4]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; +extern const char* noteNamesF[180]; +extern const char* noteNamesGF[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; extern const char* sampleLoopModes[]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 183392612..341f38a44 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -267,6 +267,10 @@ const char* msm5232ControlBits[7]={ "16'", "8'", "4'", "2'", "sustain", NULL }; +const char* tedControlBits[3]={ + "square", "noise", NULL +}; + const char* x1_010EnvBits[8]={ "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; @@ -2305,7 +2309,7 @@ void FurnaceGUI::drawInsEdit() { ins->type=(DivInstrumentType)insType; } */ - if (ImGui::BeginCombo("##Type",insType==DIV_INS_N163?settings.c163Name.c_str():insTypes[insType])) { + if (ImGui::BeginCombo("##Type",insTypes[insType])) { std::vector insTypeList; if (settings.displayAllInsTypes) { for (int i=0; insTypes[i]; i++) { @@ -2315,7 +2319,7 @@ void FurnaceGUI::drawInsEdit() { insTypeList=e->getPossibleInsTypes(); } for (DivInstrumentType i: insTypeList) { - if (ImGui::Selectable(i==DIV_INS_N163?settings.c163Name.c_str():insTypes[i],insType==i)) { + if (ImGui::Selectable(insTypes[i],insType==i)) { ins->type=i; // reset macro zoom @@ -4409,7 +4413,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; @@ -4664,30 +4669,79 @@ void FurnaceGUI::drawInsEdit() { sampleMapFocused=false; } } - if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem(settings.c163Name.c_str())) { - if (ImGui::InputInt("Waveform##WAVE",&ins->n163.wave,1,10)) { PARAMETER - if (ins->n163.wave<0) ins->n163.wave=0; - if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; - } - if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER - if (ins->n163.wavePos<0) ins->n163.wavePos=0; - if (ins->n163.wavePos>255) ins->n163.wavePos=255; - } - if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER - if (ins->n163.waveLen<0) ins->n163.waveLen=0; - if (ins->n163.waveLen>252) ins->n163.waveLen=252; - ins->n163.waveLen&=0xfc; - } - + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { bool preLoad=ins->n163.waveMode&0x1; - if (ImGui::Checkbox("Load waveform before playback",&preLoad)) { PARAMETER + if (ImGui::Checkbox("Load waveform",&preLoad)) { PARAMETER ins->n163.waveMode=(ins->n163.waveMode&~0x1)|(preLoad?0x1:0); } - bool waveMode=ins->n163.waveMode&0x2; - if (ImGui::Checkbox("Update waveforms into RAM when every waveform changes",&waveMode)) { PARAMETER - ins->n163.waveMode=(ins->n163.waveMode&~0x2)|(waveMode?0x2:0); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a waveform will be loaded into RAM.\nwhen disabled, only the offset and length change."); } + if (preLoad) { + if (ImGui::InputInt("Waveform##WAVE",&ins->n163.wave,1,10)) { PARAMETER + if (ins->n163.wave<0) ins->n163.wave=0; + if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; + } + } + + ImGui::Separator(); + + P(ImGui::Checkbox("Per-channel wave offset/length",&ins->n163.perChanPos)); + + if (ins->n163.perChanPos) { + if (ImGui::BeginTable("N1PerChPos",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Ch"); + ImGui::TableNextColumn(); + ImGui::Text("Offset"); + ImGui::TableNextColumn(); + ImGui::Text("Length"); + + for (int i=0; i<8; i++) { + ImGui::PushID(64+i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(dpiScale,ImGui::GetFrameHeightWithSpacing())); + ImGui::SameLine(); + ImGui::Text("%d",i+1); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##pcOff",&ins->n163.wavePosCh[i],1,16)) { PARAMETER + if (ins->n163.wavePosCh[i]<0) ins->n163.wavePosCh[i]=0; + if (ins->n163.wavePosCh[i]>255) ins->n163.wavePosCh[i]=255; + } + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##pcLen",&ins->n163.waveLenCh[i],4,16)) { PARAMETER + if (ins->n163.waveLenCh[i]<0) ins->n163.waveLenCh[i]=0; + if (ins->n163.waveLenCh[i]>252) ins->n163.waveLenCh[i]=252; + ins->n163.waveLenCh[i]&=0xfc; + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + } else { + if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER + if (ins->n163.wavePos<0) ins->n163.wavePos=0; + if (ins->n163.wavePos>255) ins->n163.wavePos=255; + } + if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER + if (ins->n163.waveLen<0) ins->n163.waveLen=0; + if (ins->n163.waveLen>252) ins->n163.waveLen=252; + ins->n163.waveLen&=0xfc; + } + } ImGui::EndTabItem(); } if (ins->type==DIV_INS_FDS) if (ImGui::BeginTabItem("FDS")) { @@ -5316,7 +5370,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) { @@ -5338,7 +5393,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_MSM6258) { volMax=0; } - if (ins->type==DIV_INS_MSM6295) { + if (ins->type==DIV_INS_MSM6295 || ins->type==DIV_INS_TED) { volMax=8; } if (ins->type==DIV_INS_ADPCMA) { @@ -5405,7 +5460,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) { @@ -5424,6 +5479,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="On/Off"; dutyMax=1; } + if (ins->type==DIV_INS_TED) { + dutyLabel="Square/Noise"; + dutyMax=2; + } if (ins->type==DIV_INS_SWAN) { dutyLabel="Noise"; dutyMax=ins->amiga.useSample?0:8; @@ -5440,10 +5499,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=63; } - /*if (ins->type==DIV_INS_N163) { - dutyLabel="Waveform pos."; + if (ins->type==DIV_INS_N163) { + dutyLabel="Wave Pos"; dutyMax=255; - }*/ + } if (ins->type==DIV_INS_VRC6) { dutyLabel="Duty"; dutyMax=ins->amiga.useSample?0:7; @@ -5504,8 +5563,11 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_MSM6295) waveMax=0; if (ins->type==DIV_INS_SEGAPCM) waveMax=0; if (ins->type==DIV_INS_K007232) waveMax=0; + if (ins->type==DIV_INS_ES5506) 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_TED) waveMax=0; if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7; if (ins->type==DIV_INS_PET) { waveMax=8; @@ -5544,7 +5606,6 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_N163) { ex1Max=252; - ex2Max=2; } if (ins->type==DIV_INS_FDS) { ex1Max=63; @@ -5623,6 +5684,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; @@ -5647,6 +5713,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); } else if (ins->type==DIV_INS_POKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,pokeyCtlBits)); + } else if (ins->type==DIV_INS_TED) { + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,80,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,tedControlBits)); } else if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,msm5232ControlBits)); } else if (ins->type==DIV_INS_ES5506) { @@ -5713,7 +5781,9 @@ 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 || + ins->type==DIV_INS_TED) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { @@ -5723,8 +5793,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,saaEnvBits)); } else if (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) { macroList.push_back(FurnaceGUIMacroDesc("Envelope Mode",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,x1_010EnvBits)); - /*} else if (ins->type==DIV_INS_N163) { - macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));*/ + } else if (ins->type==DIV_INS_N163) { + macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SU) { @@ -5746,8 +5816,6 @@ void FurnaceGUI::drawInsEdit() { if (ex2Max>0) { if (ins->type==DIV_INS_C64) { macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER])); - /*} else if (ins->type==DIV_INS_N163) { - macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));*/ } else if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SU) { @@ -5777,12 +5845,6 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Noise AND Mask",&ins->std.fbMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); macroList.push_back(FurnaceGUIMacroDesc("Noise OR Mask",&ins->std.fmsMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } - if (ins->type==DIV_INS_N163) { - /*macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Wave",&ins->std.ex3Macro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Pos",&ins->std.algMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Len",&ins->std.fbMacro,0,252,160,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Trigger",&ins->std.fmsMacro,0,2,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));*/ - } if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Position",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); } diff --git a/src/gui/mixer.cpp b/src/gui/mixer.cpp index 95dba1254..ff1641ba0 100644 --- a/src/gui/mixer.cpp +++ b/src/gui/mixer.cpp @@ -337,9 +337,11 @@ void FurnaceGUI::drawMixer() { if (selectedSubPort>=0) { portDragActive=true; ImGui::InhibitInertialScroll(); - try { - subPortPos=portPos.at((selectedPortSet<<4)|selectedSubPort); - } catch (std::out_of_range& e) { + + auto subPortI=portPos.find((selectedPortSet<<4)|selectedSubPort); + if (subPortI!=portPos.cend()) { + subPortPos=subPortI->second; + } else { portDragActive=false; } } @@ -353,9 +355,10 @@ void FurnaceGUI::drawMixer() { if (selectedSubPort>=0) { portDragActive=true; ImGui::InhibitInertialScroll(); - try { - subPortPos=portPos.at((selectedPortSet<<4)|selectedSubPort); - } catch (std::out_of_range& e) { + auto subPortI=portPos.find((selectedPortSet<<4)|selectedSubPort); + if (subPortI!=portPos.cend()) { + subPortPos=subPortI->second; + } else { portDragActive=false; } } @@ -365,9 +368,10 @@ void FurnaceGUI::drawMixer() { if (selectedSubPort>=0) { portDragActive=true; ImGui::InhibitInertialScroll(); - try { - subPortPos=portPos.at((selectedPortSet<<4)|selectedSubPort); - } catch (std::out_of_range& e) { + auto subPortI=portPos.find((selectedPortSet<<4)|selectedSubPort); + if (subPortI!=portPos.cend()) { + subPortPos=subPortI->second; + } else { portDragActive=false; } } @@ -380,9 +384,10 @@ void FurnaceGUI::drawMixer() { if (selectedSubPort>=0) { portDragActive=true; ImGui::InhibitInertialScroll(); - try { - subPortPos=portPos.at((selectedPortSet<<4)|selectedSubPort); - } catch (std::out_of_range& e) { + auto subPortI=portPos.find((selectedPortSet<<4)|selectedSubPort); + if (subPortI!=portPos.cend()) { + subPortPos=subPortI->second; + } else { portDragActive=false; } } @@ -415,22 +420,24 @@ void FurnaceGUI::drawMixer() { // draw connections for (unsigned int i: e->song.patchbay) { if ((i>>20)==selectedPortSet) continue; - try { - ImVec2 portSrc=portPos.at(i>>16); - ImVec2 portDest=portPos.at(0x10000|(i&0xffff)); + auto portSrcI=portPos.find(i>>16); + auto portDestI=portPos.find(0x10000|(i&0xffff)); + if (portSrcI!=portPos.cend() && portDestI!=portPos.cend()) { + ImVec2 portSrc=portSrcI->second; + ImVec2 portDest=portDestI->second; dl->AddLine(portSrc,portDest,ImGui::GetColorU32(uiColors[GUI_COLOR_PATCHBAY_CONNECTION_BG]),2.0f*dpiScale); - } catch (std::out_of_range& e) { } } // foreground for (unsigned int i: e->song.patchbay) { if ((i>>20)!=selectedPortSet) continue; - try { - ImVec2 portSrc=portPos.at(i>>16); - ImVec2 portDest=portPos.at(0x10000|(i&0xffff)); + auto portSrcI=portPos.find(i>>16); + auto portDestI=portPos.find(0x10000|(i&0xffff)); + if (portSrcI!=portPos.cend() && portDestI!=portPos.cend()) { + ImVec2 portSrc=portSrcI->second; + ImVec2 portDest=portDestI->second; dl->AddLine(portSrc,portDest,ImGui::GetColorU32(uiColors[GUI_COLOR_PATCHBAY_CONNECTION]),2.0f*dpiScale); - } catch (std::out_of_range& e) { } } } diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index fd7eace75..98c44357c 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -113,7 +113,35 @@ 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::PushTextWrapPos(MIN(scrW*dpiScale,400.0f*dpiScale)); + ImGui::Text("%s (x%d): ",sysDef->name,chipCounts[chip]); + ImGui::Text("%s",sysDef->description); + ImGui::PopTextWrapPos(); + if (chipIndex+1getRow(); if (e->isPlaying() && followPattern && (!e->isStepping() || pendingStepUpdate)) updateScroll(curRow); - pendingStepUpdate=false; + if (--pendingStepUpdate<0) pendingStepUpdate=0; if (nextScroll>-0.5f) { ImGui::SetScrollY(nextScroll); nextScroll=-1.0f; @@ -952,9 +952,16 @@ void FurnaceGUI::drawPattern() { demandScrollX=false; } + // cursor follows wheel + if (settings.cursorFollowsWheel && (!e->isPlaying() || !followPattern) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) { + if (wheelX!=0 || wheelY!=0) { + moveCursor(wheelX,wheelY,false); + } + } + // overflow changes order // TODO: this is very unreliable and sometimes it can warp you out of the song - if (settings.scrollChangesOrder && !e->isPlaying() && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) { + if (settings.scrollChangesOrder && (!e->isPlaying() || !followPattern) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && !settings.cursorFollowsWheel) { if (wheelY!=0) { if (wheelY>0) { if (ImGui::GetScrollY()<=0) { @@ -963,6 +970,10 @@ void FurnaceGUI::drawPattern() { setOrder(curOrder-1); ImGui::SetScrollY(ImGui::GetScrollMaxY()); updateScroll(e->curSubSong->patLen); + } else if (settings.scrollChangesOrder==2) { + setOrder(e->curSubSong->ordersLen-1); + ImGui::SetScrollY(ImGui::GetScrollMaxY()); + updateScroll(e->curSubSong->patLen); } haveHitBounds=false; } else { @@ -978,6 +989,10 @@ void FurnaceGUI::drawPattern() { setOrder(curOrder+1); ImGui::SetScrollY(0); updateScroll(0); + } else if (settings.scrollChangesOrder==2) { + setOrder(0); + ImGui::SetScrollY(0); + updateScroll(0); } haveHitBounds=false; } else { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 12b77cebe..05f5af87a 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -278,6 +278,12 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "Commodore VIC-20", { CH(DIV_SYSTEM_VIC20, 1.0f, 0, "clockSel=1") + }, + "tickRate=50" + ); + ENTRY( + "Commodore Plus/4", { + CH(DIV_SYSTEM_TED, 1.0f, 0, "") } ); ENTRY( @@ -1262,6 +1268,42 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_K007232, 1.0f, 0, "") // "" } ); + ENTRY( + "Konami Rollergames", { + CH(DIV_SYSTEM_OPL2, 1.0f, 0, ""), // 3.58MHz + CH(DIV_SYSTEM_K053260, 1.0f, 0, ""), // "" + } + ); + ENTRY( + "Konami Rollergames (drums mode)", { + CH(DIV_SYSTEM_OPL2_DRUMS, 1.0f, 0, ""), // 3.58MHz + CH(DIV_SYSTEM_K053260, 1.0f, 0, ""), // "" + } + ); + ENTRY( + "Konami Golfing Greats", { + CH(DIV_SYSTEM_K053260, 1.0f, 0, ""), // 3.58MHz + } + ); + ENTRY( + "Konami Lightning Fighters", { + CH(DIV_SYSTEM_YM2151, 1.0f, 0, ""), // 3.58MHz + CH(DIV_SYSTEM_K053260, 1.0f, 0, ""), // "" + } + ); + ENTRY( + "Konami Over Drive", { + CH(DIV_SYSTEM_YM2151, 1.0f, 0, ""), // 3.58MHz + CH(DIV_SYSTEM_K053260, 1.0f, 0, ""), // "" + CH(DIV_SYSTEM_K053260, 1.0f, 0, ""), // "" + } + ); + ENTRY( + "Konami Asterix", { + CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"), // 4MHz + CH(DIV_SYSTEM_K053260, 1.0f, 0, "clockSel=1"), // "" + } + ); ENTRY( "Konami Hexion", { CH(DIV_SYSTEM_SCC, 1.0f, 0, "clockSel=2"), // 1.5MHz (3MHz input) @@ -2397,6 +2439,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_PV1000, 1.0f, 0, "") } ); + ENTRY( + "MOS Technology TED", { + CH(DIV_SYSTEM_TED, 1.0f, 0, "clockSel=1") + } + ); CATEGORY_END; CATEGORY_BEGIN("Sample","chips/systems which use PCM or ADPCM samples for sound synthesis."); @@ -2466,6 +2513,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_ES5506, 1.0f, 0, "channels=31") } ); + ENTRY( + "Konami K053260", { + CH(DIV_SYSTEM_K053260, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("Wavetable","chips which use user-specified waveforms to generate sound."); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 4246922c5..99ccbd22c 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -267,6 +267,16 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_WARN(warnLength,"SegaPCM: maximum sample length is 65280"); } break; + case DIV_SYSTEM_K053260: + if (sample->loop) { + if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { + SAMPLE_WARN(warnLoopPos,"K053260: loop point ignored (may only loop entire sample)"); + } + } + if (sample->samples>65535) { + SAMPLE_WARN(warnLength,"K053260: maximum sample length is 65535"); + } + break; default: break; } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b0e280e90..57f1fa32a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -572,11 +572,6 @@ void FurnaceGUI::drawSettings() { settings.effectDeletionAltersValue=effectDeletionAltersValueB; } - bool scrollChangesOrderB=settings.scrollChangesOrder; - if (ImGui::Checkbox("Change order when scrolling outside of pattern bounds",&scrollChangesOrderB)) { - settings.scrollChangesOrder=scrollChangesOrderB; - } - bool stepOnInsertB=settings.stepOnInsert; if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) { settings.stepOnInsert=stepOnInsertB; @@ -592,6 +587,11 @@ void FurnaceGUI::drawSettings() { settings.cursorMoveNoScroll=cursorMoveNoScrollB; } + bool cursorFollowsWheelB=settings.cursorFollowsWheel; + if (ImGui::Checkbox("Move cursor with scroll wheel",&cursorFollowsWheelB)) { + settings.cursorFollowsWheel=cursorFollowsWheelB; + } + bool doubleClickColumnB=settings.doubleClickColumn; if (ImGui::Checkbox("Double click selects entire column",&doubleClickColumnB)) { settings.doubleClickColumn=doubleClickColumnB; @@ -758,6 +758,21 @@ void FurnaceGUI::drawSettings() { if (ImGui::RadioButton("Yes, and move to next/prev pattern##wrapV2",settings.wrapVertical==2)) { settings.wrapVertical=2; } + if (ImGui::RadioButton("Yes, and move to next/prev pattern (wrap around)##wrapV2",settings.wrapVertical==3)) { + settings.wrapVertical=3; + } + + ImGui::Text("Change order when scrolling outside of pattern bounds:"); + if (ImGui::RadioButton("No##pscroll0",settings.scrollChangesOrder==0)) { + settings.scrollChangesOrder=0; + } + if (ImGui::RadioButton("Yes##pscroll1",settings.scrollChangesOrder==1)) { + settings.scrollChangesOrder=1; + } + if (ImGui::RadioButton("Yes, and wrap around song##pscroll2",settings.scrollChangesOrder==2)) { + settings.scrollChangesOrder=2; + } + ImGui::Text("Cursor movement keys behavior:"); if (ImGui::RadioButton("Move by one##cmk0",settings.scrollStep==0)) { @@ -1638,12 +1653,6 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); - ImGui::Text("Namco 163 chip name"); - ImGui::SameLine(); - ImGui::InputTextWithHint("##C163Name",DIV_C163_DEFAULT_NAME,&settings.c163Name); - - ImGui::Separator(); - ImGui::Text("Channel colors:"); if (ImGui::RadioButton("Single##CHC0",settings.channelColors==0)) { settings.channelColors=0; @@ -1774,6 +1783,11 @@ void FurnaceGUI::drawSettings() { settings.viewPrevPattern=viewPrevPatternB; } + bool flatNotesB=settings.flatNotes; + if (ImGui::Checkbox("Use flats instead of sharps",&flatNotesB)) { + settings.flatNotes=flatNotesB; + } + bool germanNotationB=settings.germanNotation; if (ImGui::Checkbox("Use German notation",&germanNotationB)) { settings.germanNotation=germanNotationB; @@ -2067,11 +2081,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL,"FM (OPL)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_FDS,"FDS"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_VBOY,"Virtual Boy"); - // special case - String c163Label=fmt::sprintf("%s##CC_GUI_COLOR_INSTR_N163",settings.c163Name); - if (ImGui::ColorEdit4(c163Label.c_str(),(float*)&uiColors[GUI_COLOR_INSTR_N163])) { - applyUISettings(false); - } + UI_COLOR_CONFIG(GUI_COLOR_INSTR_N163,"Namco 163"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SCC,"Konami SCC"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPZ,"FM (OPZ)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEY,"POKEY"); @@ -2102,6 +2112,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEMINI,"Pokémon Mini"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SM8521,"SM8521"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_PV1000,"PV-1000"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_K053260,"K053260"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -2639,6 +2650,11 @@ void FurnaceGUI::drawSettings() { settingsOpen=false; syncSettings(); } + ImGui::SameLine(); + if (ImGui::Button("Apply##SettingsApply")) { + settingsOpen=true; + willCommit=true; + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SETTINGS; ImGui::End(); @@ -2661,7 +2677,6 @@ void FurnaceGUI::syncSettings() { settings.audioChans=e->getConfInt("audioChans",2); settings.midiInDevice=e->getConfString("midiInDevice",""); settings.midiOutDevice=e->getConfString("midiOutDevice",""); - settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME); settings.renderDriver=e->getConfString("renderDriver",""); settings.sdlAudioDriver=e->getConfString("sdlAudioDriver",""); settings.audioQuality=e->getConfInt("audioQuality",0); @@ -2695,6 +2710,7 @@ void FurnaceGUI::syncSettings() { settings.chipNames=e->getConfInt("chipNames",0); settings.overflowHighlight=e->getConfInt("overflowHighlight",0); settings.partyTime=e->getConfInt("partyTime",0); + settings.flatNotes=e->getConfInt("flatNotes",0); settings.germanNotation=e->getConfInt("germanNotation",0); settings.stepOnDelete=e->getConfInt("stepOnDelete",0); settings.scrollStep=e->getConfInt("scrollStep",0); @@ -2804,6 +2820,7 @@ void FurnaceGUI::syncSettings() { settings.pullDeleteRow=e->getConfInt("pullDeleteRow",1); settings.newSongBehavior=e->getConfInt("newSongBehavior",0); settings.memUsageUnit=e->getConfInt("memUsageUnit",1); + settings.cursorFollowsWheel=e->getConfInt("cursorFollowsWheel",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2829,13 +2846,14 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.soloAction,0,2); clampSetting(settings.pullDeleteBehavior,0,1); clampSetting(settings.wrapHorizontal,0,2); - clampSetting(settings.wrapVertical,0,2); + clampSetting(settings.wrapVertical,0,3); clampSetting(settings.macroView,0,1); clampSetting(settings.fmNames,0,2); clampSetting(settings.allowEditDocking,0,1); clampSetting(settings.chipNames,0,1); clampSetting(settings.overflowHighlight,0,1); clampSetting(settings.partyTime,0,1); + clampSetting(settings.flatNotes,0,1); clampSetting(settings.germanNotation,0,1); clampSetting(settings.stepOnDelete,0,1); clampSetting(settings.scrollStep,0,1); @@ -2873,7 +2891,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.insEditColorize,0,1); clampSetting(settings.metroVol,0,200); clampSetting(settings.pushNibble,0,1); - clampSetting(settings.scrollChangesOrder,0,1); + clampSetting(settings.scrollChangesOrder,0,2); clampSetting(settings.oplStandardWaveNames,0,1); clampSetting(settings.cursorMoveNoScroll,0,1); clampSetting(settings.lowLatency,0,1); @@ -2931,6 +2949,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.pullDeleteRow,0,1); clampSetting(settings.newSongBehavior,0,1); clampSetting(settings.memUsageUnit,0,1); + clampSetting(settings.cursorFollowsWheel,0,1); if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; @@ -3010,7 +3029,6 @@ void FurnaceGUI::commitSettings() { e->setConf("audioDevice",settings.audioDevice); e->setConf("midiInDevice",settings.midiInDevice); e->setConf("midiOutDevice",settings.midiOutDevice); - e->setConf("c163Name",settings.c163Name); e->setConf("renderDriver",settings.renderDriver); e->setConf("sdlAudioDriver",settings.sdlAudioDriver); e->setConf("audioQuality",settings.audioQuality); @@ -3045,6 +3063,7 @@ void FurnaceGUI::commitSettings() { e->setConf("chipNames",settings.chipNames); e->setConf("overflowHighlight",settings.overflowHighlight); e->setConf("partyTime",settings.partyTime); + e->setConf("flatNotes",settings.flatNotes); e->setConf("germanNotation",settings.germanNotation); e->setConf("stepOnDelete",settings.stepOnDelete); e->setConf("scrollStep",settings.scrollStep); @@ -3155,6 +3174,7 @@ void FurnaceGUI::commitSettings() { e->setConf("pullDeleteRow",settings.pullDeleteRow); e->setConf("newSongBehavior",settings.newSongBehavior); e->setConf("memUsageUnit",settings.memUsageUnit); + e->setConf("cursorFollowsWheel",settings.cursorFollowsWheel); // colors for (int i=0; ilockSave([&]() { flags.set("clockSel",clockSel); flags.set("channels",channels-1); flags.set("multiplex",multiplex); + flags.set("lenCompensate",lenCompensate); }); } break; @@ -1882,6 +1887,60 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; }*/ + case DIV_SYSTEM_K053260: { + int clockSel=flags.getInt("clockSel",0); + + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("3.58MHz (NTSC)",clockSel==0)) { + clockSel=0; + altered=true; + } + if (ImGui::RadioButton("4MHz",clockSel==1)) { + clockSel=1; + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("clockSel",clockSel); + }); + } + break; + } + case DIV_SYSTEM_TED: { + int clockSel=flags.getInt("clockSel",0); + bool keyPriority=flags.getBool("keyPriority",true); + + ImGui::Text("Clock rate:"); + + if (ImGui::RadioButton("NTSC (1.79MHz)",clockSel==0)) { + clockSel=0; + altered=true; + } + if (ImGui::RadioButton("PAL (1.77MHz)",clockSel==1)) { + clockSel=1; + altered=true; + } + + ImGui::Text("Global parameter priority:"); + + if (ImGui::RadioButton("Left to right",!keyPriority)) { + keyPriority=false; + altered=true; + } + if (ImGui::RadioButton("Last used channel",keyPriority)) { + keyPriority=true; + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("clockSel",clockSel); + flags.set("keyPriority",keyPriority); + }); + } + break; + } case DIV_SYSTEM_SWAN: case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_PET: diff --git a/src/gui/sysPartNumber.cpp b/src/gui/sysPartNumber.cpp new file mode 100644 index 000000000..94dc822b8 --- /dev/null +++ b/src/gui/sysPartNumber.cpp @@ -0,0 +1,272 @@ +/** + * 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 "gui.h" + +const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { + switch (sys) { + case DIV_SYSTEM_YMU759: + return "YMU759"; + break; + case DIV_SYSTEM_SMS:{ + int chipType=flags.getInt("chipType",0); + if (chipType==4) { + return "SN76489A"; + } else if (chipType==5) { + return "SN76496"; + } else if (chipType==6) { + return "8496"; + } else if (chipType==7) { + return "PSSJ";//not part number + } else if (chipType==8) { + return "SN94624"; + } else if (chipType==9) { + return "SN76494"; + } else { + return "SN76489"; + } + break; + } + case DIV_SYSTEM_PCE:{ + int chipType=flags.getInt("chipType",0); + if (chipType==1) { + return "HuC6280A"; + } else { + return "HuC6280"; + } + } + case DIV_SYSTEM_NES: + return "2A03"; + break; + case DIV_SYSTEM_C64_6581: + return "MOS 6581"; + break; + case DIV_SYSTEM_C64_8580: + return "MOS 8580"; + break; + case DIV_SYSTEM_Y8950: + case DIV_SYSTEM_Y8950_DRUMS: + return "Y8950"; + break; + case DIV_SYSTEM_AY8910:{ + int chipType=flags.getInt("chipType",0); + if (chipType==1) { + return "YM2149(F)"; + } else if (chipType==2) { + return "5B"; + } else if (chipType==3) { + return "AY-3-8914"; + } else { + return "AY-3-8910"; + } + break; + } + case DIV_SYSTEM_YM2151: + return "YM2151"; + break; + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_CSM: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: + case DIV_SYSTEM_YM2612_EXT:{ + int chipType=0; + if (flags.has("chipType")) { + chipType=flags.getInt("chipType",0); + } else { + chipType=flags.getBool("ladderEffect",0)?1:0; + } + if (chipType==0) { + return "YM3438"; + } else if (chipType==2) { + return "YMF276"; + } else { + return "YM2612"; + } + break; + } + case DIV_SYSTEM_TIA: + return "TIA"; + break; + case DIV_SYSTEM_SAA1099: + return "SAA1099"; + break; + case DIV_SYSTEM_AY8930: + return "AY8930"; + break; + case DIV_SYSTEM_VIC20: + return "VIC"; + break; + case DIV_SYSTEM_PET: + return "PET"; + break; + case DIV_SYSTEM_VRC6: + return "VRC6"; + break; + case DIV_SYSTEM_FDS: + return "FDS"; + break; + case DIV_SYSTEM_MMC5: + return "MMC5"; + break; + case DIV_SYSTEM_N163: + return "N163"; + break; + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: + case DIV_SYSTEM_YM2203_CSM: + return "YM2203"; + break; + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_CSM: + case DIV_SYSTEM_YM2608_EXT: + return "YM2608"; + break; + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS:{ + int patchSet=flags.getInt("patchSet",0); + if (patchSet==1) { + return "YMF281"; + } else if (patchSet==2) { + return "YM2423"; + } else if (patchSet==3) { + return "VRC7"; + } else { + return "YM2413"; + } + break; + } + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + return "YM3812"; + break; + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: + return "YMF262"; + break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + return "YMF278"; + break; + case DIV_SYSTEM_MULTIPCM: + return "YMW258-F"; + break; + case DIV_SYSTEM_RF5C68:{ + int chipType=flags.getInt("chipType",0); + if (chipType==1) { + return "RF5C164"; + } else { + return "RF5C68"; + } + break; + } + case DIV_SYSTEM_OPZ: + return "YM2414"; + break; + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: + return "SegaPCM"; + break; + case DIV_SYSTEM_VRC7: + return "VRC7"; + break; + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_CSM: + case DIV_SYSTEM_YM2610B_EXT: + return "YM2610B"; + break; + case DIV_SYSTEM_SFX_BEEPER: + case DIV_SYSTEM_SFX_BEEPER_QUADTONE: + return "ZXS Beeper"; + break; + case DIV_SYSTEM_SCC: + return "SCC"; + break; + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_CSM: + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: + return "YM2610"; + break; + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + return "YM2413"; + break; + case DIV_SYSTEM_QSOUND: + return "QSound"; + break; + case DIV_SYSTEM_X1_010: + return "X1-010"; + break; + case DIV_SYSTEM_BUBSYS_WSG: + return "Konami WSG"; + break; + case DIV_SYSTEM_ES5506: + return "ES5506"; + break; + case DIV_SYSTEM_SCC_PLUS: + return "SCC+"; + break; + case DIV_SYSTEM_SOUND_UNIT: + return "TSU"; + break; + case DIV_SYSTEM_MSM6295: + return "MSM6295"; + break; + case DIV_SYSTEM_MSM6258: + return "MSM6258"; + break; + case DIV_SYSTEM_YMZ280B: + return "YMZ280B"; + break; + case DIV_SYSTEM_NAMCO_15XX: + return "C15"; + break; + case DIV_SYSTEM_NAMCO_CUS30: + return "C30"; + break; + case DIV_SYSTEM_MSM5232: + return "MSM5232"; + break; + case DIV_SYSTEM_K007232: + return "K007232"; + break; + case DIV_SYSTEM_GA20: + return "GA20"; + break; + case DIV_SYSTEM_PCM_DAC: + return "DAC"; + break; + case DIV_SYSTEM_SM8521: + return "SM8521"; + break; + case DIV_SYSTEM_PV1000: + return "PV-1000"; + break; + case DIV_SYSTEM_K053260: + return "K053260"; + break; + case DIV_SYSTEM_TED: + return "TED"; + break; + default: + return FurnaceGUI::getSystemName(sys); + break; + } +} diff --git a/src/main.cpp b/src/main.cpp index b5484ea36..bf2ac269b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -191,6 +191,7 @@ TAParamResult pVersion(String) { printf("- MAME SegaPCM core by Hiromitsu Shioya and Olivier Galibert (BSD 3-clause)\n"); printf("- QSound core by superctr (BSD 3-clause)\n"); printf("- VICE VIC-20 by Rami Rasanen and viznut (GPLv2)\n"); + printf("- VICE TED by Andreas Boose, Tibor Biczo and Marco van den Heuvel (GPLv2)\n"); printf("- VERA core by Frank van den Hoef (BSD 2-clause)\n"); printf("- SAASound by Dave Hooper and Simon Owen (BSD 3-clause)\n"); printf("- SameBoy by Lior Halphon (MIT)\n");