diff --git a/.gitmodules b/.gitmodules index c78fee42a..498a32410 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,3 +15,6 @@ [submodule "extern/portaudio"] path = extern/portaudio url = https://github.com/PortAudio/portaudio.git +[submodule "extern/adpcm-xq"] + path = extern/adpcm-xq + url = https://github.com/dbry/adpcm-xq.git diff --git a/CMakeLists.txt b/CMakeLists.txt index decea3fef..44e751627 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -485,6 +485,8 @@ extern/adpcm/yma_codec.c extern/adpcm/ymb_codec.c extern/adpcm/ymz_codec.c +extern/adpcm-xq/adpcm-lib.c + extern/opn/ym3438.c extern/Nuked-PSG/ympsg.c extern/opm/opm.c @@ -514,6 +516,8 @@ src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c +src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp +src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp src/engine/platform/sound/nes_nsfplay/nes_apu.cpp src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp src/engine/platform/sound/nes_nsfplay/nes_fds.cpp @@ -609,6 +613,8 @@ src/engine/platform/sound/c140_c219.c src/engine/platform/sound/dave/dave.cpp +src/engine/platform/sound/nds.cpp + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp @@ -620,6 +626,7 @@ src/engine/fileOps/ftm.cpp src/engine/fileOps/fur.cpp src/engine/fileOps/mod.cpp src/engine/fileOps/s3m.cpp +src/engine/fileOps/text.cpp src/engine/blip_buf.c src/engine/brrUtils.c @@ -717,6 +724,9 @@ src/engine/platform/c140.cpp src/engine/platform/esfm.cpp src/engine/platform/powernoise.cpp src/engine/platform/dave.cpp +src/engine/platform/gbadma.cpp +src/engine/platform/gbaminmod.cpp +src/engine/platform/nds.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp @@ -789,6 +799,7 @@ src/gui/channels.cpp src/gui/chanOsc.cpp src/gui/clock.cpp src/gui/compatFlags.cpp +src/gui/csPlayer.cpp src/gui/cursor.cpp src/gui/dataList.cpp src/gui/debugWindow.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70d9831fa..7fd671d5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,7 +54,8 @@ the coding style is described here: - in float/double operations, always use decimal and `f` if single-precision. - e.g. `1.0f` or `1.0` instead of `1`. - prefer `NULL` over `nullptr` or any other proprietary null. -- don't use `auto` unless needed. +- only use `auto` if needed. +- avoid using `goto` unless absolutely required. - use `String` for `std::string` (this is typedef'd in ta-utils.h). - prefer using operator for String (std::string) comparisons (a==""). - if you have to work with C strings, only use safe C string operations: @@ -97,6 +98,7 @@ just put your demo song in `demos/`! be noted there are some guidelines: - the following systems are not acceptable: - YMU759/MA-2: exists only for compatibility. - Pong: it is a joke system. +- the song shall be in Furnace file format. # Finishing diff --git a/README.md b/README.md index 776b3bcf5..0460a4d98 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **Flatpak**: yes! Furnace is now available on [Flathub](https://flathub.org/apps/org.tildearrow.furnace) thanks to ColinKinloch. - **Arch Linux**: [furnace](https://archlinux.org/packages/extra/x86_64/furnace/) is in the official repositories. +- **Chimera Linux**: [furnace](https://pkgs.chimera-linux.org/package/current/contrib/x86_64/furnace) is in the contrib repository. - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. @@ -272,7 +273,7 @@ Available options: | `WITH_DEMOS` | `ON` | Install demo songs on `make install` | | `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` | | `WITH_WAVETABLES` | `ON` | Install wavetables on `make install` | -| `SHOW_OPEN_ASSETS_MENU_ENTRY` | `ON` | `Show option to open built-in assets directory (on supported platforms)` | +| `SHOW_OPEN_ASSETS_MENU_ENTRY` | `OFF` | Show option to open built-in assets directory (on supported platforms) | (\*) `ON` if system-installed JACK detected, otherwise `OFF` diff --git a/demos/arcade/Grape_Jelly_Alpha5232.fur b/demos/arcade/Grape_Jelly_Alpha5232.fur new file mode 100644 index 000000000..bee9debd2 Binary files /dev/null and b/demos/arcade/Grape_Jelly_Alpha5232.fur differ diff --git a/demos/ay8910/cardboardmywater.fur b/demos/ay8910/cardboardmywater.fur index 9f67d1ca6..e65b5bc3f 100644 Binary files a/demos/ay8910/cardboardmywater.fur and b/demos/ay8910/cardboardmywater.fur differ diff --git a/demos/genesis/SparkmanMD.fur b/demos/genesis/SparkmanMD.fur new file mode 100644 index 000000000..3e16aa8e2 Binary files /dev/null and b/demos/genesis/SparkmanMD.fur differ diff --git a/demos/genesis/Swaggin_Dragon.dmf b/demos/genesis/Swaggin_Dragon.dmf deleted file mode 100644 index 20c358049..000000000 Binary files a/demos/genesis/Swaggin_Dragon.dmf and /dev/null differ diff --git a/demos/genesis/darkstar.dmf b/demos/genesis/darkstar.dmf deleted file mode 100644 index eb269ba59..000000000 Binary files a/demos/genesis/darkstar.dmf and /dev/null differ diff --git a/demos/genesis/darkstar.fur b/demos/genesis/darkstar.fur new file mode 100644 index 000000000..a87f97f29 Binary files /dev/null and b/demos/genesis/darkstar.fur differ diff --git a/demos/genesis/the_serenity_of_lonliness.fur b/demos/genesis/the_serenity_of_lonliness.fur index dc733370f..d97f6b811 100644 Binary files a/demos/genesis/the_serenity_of_lonliness.fur and b/demos/genesis/the_serenity_of_lonliness.fur differ diff --git a/demos/misc/Galactic_Melody_PowerNoise.fur b/demos/misc/Galactic_Melody_PowerNoise.fur index 91bfea9b0..cf7e2bbc6 100644 Binary files a/demos/misc/Galactic_Melody_PowerNoise.fur and b/demos/misc/Galactic_Melody_PowerNoise.fur differ diff --git a/demos/misc/deepmist_dave.fur b/demos/misc/deepmist_dave.fur new file mode 100644 index 000000000..1373294e6 Binary files /dev/null and b/demos/misc/deepmist_dave.fur differ diff --git a/demos/misc/morepain_TIA.fur b/demos/misc/morepain_TIA.fur new file mode 100644 index 000000000..fbb23cdf7 Binary files /dev/null and b/demos/misc/morepain_TIA.fur differ diff --git a/demos/misc/walkontheroof_T6W28.fur b/demos/misc/walkontheroof_T6W28.fur new file mode 100644 index 000000000..a4c6e08ba Binary files /dev/null and b/demos/misc/walkontheroof_T6W28.fur differ diff --git a/demos/multichip/Namco_C30_C219_Loop.fur b/demos/multichip/Namco_C30_C219_Loop.fur new file mode 100644 index 000000000..e89e5cf02 Binary files /dev/null and b/demos/multichip/Namco_C30_C219_Loop.fur differ diff --git a/demos/specs2/KeygenTypeBeat.fur b/demos/specs2/KeygenTypeBeat.fur new file mode 100644 index 000000000..eb9f98a94 Binary files /dev/null and b/demos/specs2/KeygenTypeBeat.fur differ diff --git a/demos/specs2/rastaline_dub.fur b/demos/specs2/rastaline_dub.fur new file mode 100644 index 000000000..6de407b4f Binary files /dev/null and b/demos/specs2/rastaline_dub.fur differ diff --git a/doc/2-interface/export.md b/doc/2-interface/export.md index c2e520b5d..11b627d8a 100644 --- a/doc/2-interface/export.md +++ b/doc/2-interface/export.md @@ -76,10 +76,9 @@ click on **Begin Export** to... you know. ## export command stream -this option exports a text or binary file which contains a dump of the internal command stream produced when playing the song. +this option exports a binary file which contains a dump of the internal command stream produced when playing the song. it's not really useful, unless you're a developer and want to use a command stream dump for some reason (e.g. writing a hardware sound driver). -- **export (binary)**: exports in Furnace's own command stream format (FCS). see `export-tech.md` in `papers/` for details. -- **export (text)**: exports the command stream as a text file. only useful for analysis, really. +- **export**: exports in Furnace's own command stream format (FCS). see `export-tech.md` in `papers/` for details. diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index e8f02c8cf..b416ebb64 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -34,6 +34,11 @@ however, effects are continuous, which means you only need to type it once and t - `E2xy`: **Note slide down.** `x` is the speed, while `y` is how many semitones to slide down. - --- - `EAxx`: **Toggle legato.** while on, new notes instantly change the pitch of the currently playing sound instead of starting it over. +- `E6xy`: **Quick legato (compatibility).** transposes note by `y` semitones after `x` ticks. + - if `x` is between 0 and 7, it transposes up. + - if `x` is between 8 and F, it transposes down. +- `E8xy`: **Quick legato up**. transposes note up by `y` semitones after `x` ticks. +- `E9xy`: **Quick legato down**. transposes note down by `y` semitones after `x` ticks. - `00xy`: **Arpeggio.** this effect produces a rapid cycle between the current note, the note plus `x` semitones and the note plus `y` semitones. - `E0xx`: **Set arpeggio speed.** this sets the number of ticks between arpeggio values. default is 1. - --- @@ -70,6 +75,9 @@ not all chips support these effects. - `xxx` may be from `000` to `3FF`. - `F0xx`: **Set BPM.** changes tick rate according to beats per minute. range is `01` to `FF`. - --- +- `FDxx`: **Set virtual tempo numerator.** sets the virtual tempo's numerator to the effect value. +- `FExx`: **Set virtual tempo denominator.** sets the virtual tempo's denominator to the effect value. + - --- - `0Bxx`: **Jump to order.** `x` is the order to play after the current row. - this marks the end of a loop with order `x` as the loop start. - `0Dxx`: **Jump to next pattern.** skips the current row and remainder of current order. `x` is the row at which to start playing the next pattern. @@ -80,8 +88,10 @@ not all chips support these effects. - `0Cxx`: **Retrigger.** repeats current note every `xx` ticks. - this effect is not continuous; it must be entered on every row. -- `ECxx`: **Note cut.** ends current note after `xx` ticks. for FM instruments, it's equivalent to a "key off". +- `ECxx`: **Note cut.** triggers note off after `xx` ticks. this triggers key off in FM/hardware envelope chips, or cuts note otherwise. - `EDxx`: **Note delay.** delays note by `x` ticks. +- `FCxx`: **Note release.** releases current note after `xx` ticks. this releases macros and triggers key off in FM/hardware envelope chips. +- `E7xx`: **Macro release.** releases macros after `xx` ticks. this does not trigger key off. ## other diff --git a/doc/8-advanced/command-line.md b/doc/8-advanced/command-line.md index cc5d4f0df..3a84575e1 100644 --- a/doc/8-advanced/command-line.md +++ b/doc/8-advanced/command-line.md @@ -83,7 +83,6 @@ the following parameters may be used: - `-cmdout path`: output command stream dump to `path`. - you must provide a file, otherwise Furnace will quit. -- `-binary`: set command stream output format to binary. ## COMMAND LINE INTERFACE diff --git a/doc/8-advanced/memory-composition.md b/doc/8-advanced/memory-composition.md new file mode 100644 index 000000000..d01fb766c --- /dev/null +++ b/doc/8-advanced/memory-composition.md @@ -0,0 +1,5 @@ +# memory composition + +this window displays the memory usage of chips that support memory (e.g. for samples). + +![memory composition](memcompo.png) diff --git a/doc/8-advanced/stats.md b/doc/8-advanced/stats.md index 01bc78775..e65f47b77 100644 --- a/doc/8-advanced/stats.md +++ b/doc/8-advanced/stats.md @@ -1,5 +1,5 @@ # statistics -the Statistics dialog shows running stats such as overall audio processing load and per-chip sample memory. +the Statistics window shows audio load (CPU used by emulation/playback). -![statistics dialog](stats.png) +![statistics window](stats.png) diff --git a/extern/ESFMu/README.md b/extern/ESFMu/README.md index 5e04ef6fb..723f17967 100644 --- a/extern/ESFMu/README.md +++ b/extern/ESFMu/README.md @@ -1,3 +1,7 @@ +# MODIFIED + +this is a modified version of ESFMu which adds a "fast" mode that uses OPL3 feedback calculation rather than patent workaround calculation (which is slow but accurate). + # ESFMu An emulator for the ESS "ESFM" enhanced OPL3 clone, based on Nuke.YKT's **Nuked OPL3** and reverse-engineering efforts from the community. diff --git a/extern/ESFMu/esfm.c b/extern/ESFMu/esfm.c index b7373d58a..dc8cccfc4 100644 --- a/extern/ESFMu/esfm.c +++ b/extern/ESFMu/esfm.c @@ -1776,6 +1776,14 @@ ESFM_process_feedback(esfm_chip *chip) phase_acc = (uint32_t)(slot->in.phase_acc - phase_offset * 28); eg_output = slot->in.eg_output; + if (chip->fast_mode) { + phase_feedback = (slot->in.fb_out0 + slot->in.fb_out1) >> 2; + slot->in.fb_out1 = slot->in.fb_out0; + phase = phase_feedback >> mod_in_shift; + phase += phase_acc >> 9; + wave_out = slot->in.fb_out0 = ESFM_envelope_wavegen(waveform, phase, eg_output); + phase_acc += phase_offset; + } else { // ASM optimizaions! #if defined(__GNUC__) && defined(__x86_64__) asm ( @@ -1974,6 +1982,7 @@ ESFM_process_feedback(esfm_chip *chip) phase_acc += phase_offset; } #endif + } // TODO: Figure out - is this how the ESFM chip does it, like the // patent literally says? (it's really hacky...) diff --git a/extern/ESFMu/esfm.h b/extern/ESFMu/esfm.h index f33567aeb..73100ce57 100644 --- a/extern/ESFMu/esfm.h +++ b/extern/ESFMu/esfm.h @@ -58,7 +58,7 @@ typedef struct _esfm_channel esfm_channel; typedef struct _esfm_chip esfm_chip; -void ESFM_init (esfm_chip *chip); +void ESFM_init (esfm_chip *chip, uint8_t fast); void ESFM_write_reg (esfm_chip *chip, uint16_t address, uint8_t data); void ESFM_write_reg_buffered (esfm_chip *chip, uint16_t address, uint8_t data); void ESFM_write_reg_buffered_fast (esfm_chip *chip, uint16_t address, uint8_t data); @@ -148,6 +148,8 @@ typedef struct _esfm_slot_internal uint16 eg_delay_counter; uint16 eg_delay_counter_compare; + uint16 fb_out0; + uint16 fb_out1; } esfm_slot_internal; struct _esfm_slot @@ -270,6 +272,8 @@ struct _esfm_chip size_t write_buf_start; size_t write_buf_end; uint64_t write_buf_timestamp; + + flag fast_mode; }; #ifdef __cplusplus diff --git a/extern/ESFMu/esfm_registers.c b/extern/ESFMu/esfm_registers.c index d87a5944f..3c944e86d 100644 --- a/extern/ESFMu/esfm_registers.c +++ b/extern/ESFMu/esfm_registers.c @@ -948,7 +948,7 @@ ESFM_set_mode (esfm_chip *chip, bool native_mode) /* ------------------------------------------------------------------------- */ void -ESFM_init (esfm_chip *chip) +ESFM_init (esfm_chip *chip, uint8_t fast) { esfm_slot *slot; esfm_channel *channel; @@ -999,5 +999,6 @@ ESFM_init (esfm_chip *chip) } chip->lfsr = 1; + chip->fast_mode = fast; } diff --git a/extern/YMF276-LLE/fmopn2.c b/extern/YMF276-LLE/fmopn2.c index 2a11366c1..546e8e4e3 100644 --- a/extern/YMF276-LLE/fmopn2.c +++ b/extern/YMF276-LLE/fmopn2.c @@ -2066,6 +2066,8 @@ void FMOPN2_YMF276Accumulator1(fmopn2_t *chip) int acc_l = 0; int acc_r = 0; + chip->osc_out = 0; + for (i = 0; i < 14; i++) accm += ((chip->ch_accm[i][1] >> 5) & 1) << i; if (chip->alg_output && !test_dac) @@ -2106,12 +2108,12 @@ void FMOPN2_YMF276Accumulator1(fmopn2_t *chip) chip->fsm_op1_sel_l3[0] = chip->fsm_op1_sel; if (sel_dac) - out |= chip->mode_dac_data[1] << 6; + chip->osc_out |= chip->mode_dac_data[1] << 6; if (sel_fm) - out |= accm; + chip->osc_out |= accm; - if (out & 0x2000) - out |= 0x1c000; + if (chip->osc_out & 0x2000) + chip->osc_out |= 0x1c000; for (i = 0; i < 2; i++) pan |= (((chip->chan_pan[i][1] >> 5) & 1) ^ 1) << i; @@ -2130,8 +2132,8 @@ void FMOPN2_YMF276Accumulator1(fmopn2_t *chip) acc_r = chip->ch_accm_r[1]; } - chip->ch_accm_l[0] = acc_l + ((pan & 2) != 0 ? out : 0); - chip->ch_accm_r[0] = acc_r + ((pan & 1) != 0 ? out : 0); + chip->ch_accm_l[0] = acc_l + ((pan & 2) != 0 ? chip->osc_out : 0); + chip->ch_accm_r[0] = acc_r + ((pan & 1) != 0 ? chip->osc_out : 0); } void FMOPN2_YMF276Accumulator2(fmopn2_t *chip) diff --git a/extern/YMF276-LLE/fmopn2.h b/extern/YMF276-LLE/fmopn2.h index f1ba12839..044859637 100644 --- a/extern/YMF276-LLE/fmopn2.h +++ b/extern/YMF276-LLE/fmopn2.h @@ -72,6 +72,9 @@ typedef struct { int out_l; int out_r; + // added by LTVA for Furnace tracker per-channel oscilloscopes! + int osc_out; + // io int write_addr_trig; int write_addr_trig_sync; diff --git a/extern/adpcm-xq b/extern/adpcm-xq new file mode 160000 index 000000000..6220fed76 --- /dev/null +++ b/extern/adpcm-xq @@ -0,0 +1 @@ +Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf diff --git a/papers/format.md b/papers/format.md index 1a1bca87d..f979ee2f7 100644 --- a/papers/format.md +++ b/papers/format.md @@ -239,9 +239,12 @@ size | description | - 0xd2: Ensoniq ES5503 (hard pan) - 32 channels (UNAVAILABLE) | - 0xd4: PowerNoise - 4 channels | - 0xd5: Dave - 6 channels - | - 0xd6: NDS - 16 channels (UNAVAILABLE) + | - 0xd6: NDS - 16 channels + | - 0xd7: Game Boy Advance (direct) - 2 channels + | - 0xd8: Game Boy Advance (MinMod) - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels + | - 0xf1: 5E01 - 5 channels | - 0xfc: Pong - 1 channel | - 0xfd: Dummy System - 8 channels | - 0xfe: reserved for development @@ -562,9 +565,13 @@ size | description | - 4: QSound ADPCM | - 5: ADPCM-A | - 6: ADPCM-B + | - 7: K05 ADPCM | - 8: 8-bit PCM | - 9: BRR (SNES) | - 10: VOX + | - 11: 8-bit μ-law PCM + | - 12: C219 PCM + | - 13: IMA ADPCM | - 16: 16-bit PCM 1 | loop direction (>=123) or reserved | - 0: forward diff --git a/papers/newIns.md b/papers/newIns.md index d301d27c2..e5eee2d14 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -124,6 +124,8 @@ the following instrument types are available: - 55: ESFM - 56: PowerNoise (noise) - 57: PowerNoise (slope) +- 58: Dave +- 59: NDS the following feature codes are recognized: @@ -359,6 +361,7 @@ size | description 1 | sound length | - 64 is infinity 1 | flags + | - bit 2: double wave width for GBA (>=196) | - bit 1: always init envelope | - bit 0: software envelope (zombie mode) 1 | hardware sequence length diff --git a/res/icons.sfd b/res/icons.sfd index 558f8c051..5cb5e3326 100644 --- a/res/icons.sfd +++ b/res/icons.sfd @@ -21,7 +21,7 @@ OS2Version: 0 OS2_WeightWidthSlopeOnly: 0 OS2_UseTypoMetrics: 0 CreationTime: 1691897631 -ModificationTime: 1706927377 +ModificationTime: 1710664281 PfmFamily: 81 TTFWeight: 400 TTFWidth: 5 @@ -53,7 +53,7 @@ FitToEm: 0 WinInfo: 57540 21 12 BeginPrivate: 0 EndPrivate -BeginChars: 65536 98 +BeginChars: 65536 102 StartChar: space Encoding: 32 32 0 @@ -6833,7 +6833,7 @@ EndChar StartChar: uniE15B Encoding: 57691 57691 97 Width: 1792 -Flags: HWO +Flags: HW LayerCount: 2 Fore SplineSet @@ -6882,5 +6882,429 @@ SplineSet 1282.36914062 744.515625 1209.40039062 868.03125 1136.43164062 991.546875 c 1 EndSplineSet EndChar + +StartChar: uniE15C +Encoding: 57692 57692 98 +Width: 1792 +Flags: HW +LayerCount: 2 +Fore +SplineSet +543.03125 -247.515625 m 1 + 469.75 -24.15625 396.46875 199.203125 323.1875 422.5625 c 1 + 360.635742188 422.5625 398.083007812 422.5625 435.53125 422.5625 c 1 + 471.051757812 312.171875 506.573242188 201.78125 542.09375 91.390625 c 1 + 563.526367188 13.6298828125 584.958007812 -64.1298828125 606.390625 -141.890625 c 1 + 607.666992188 -141.890625 608.942382812 -141.890625 610.21875 -141.890625 c 1 + 631.989257812 -64.1298828125 653.760742188 13.6298828125 675.53125 91.390625 c 1 + 711.051757812 201.78125 746.573242188 312.171875 782.09375 422.5625 c 1 + 818.578125 422.5625 855.0625 422.5625 891.546875 422.5625 c 1 + 817.301757812 199.203125 743.057617188 -24.15625 668.8125 -247.515625 c 1 + 626.885742188 -247.515625 584.958007812 -247.515625 543.03125 -247.515625 c 1 +1028.5 -247.515625 m 1 + 1028.5 -24.15625 1028.5 199.203125 1028.5 422.5625 c 1 + 1171.546875 422.5625 1314.59375 422.5625 1457.640625 422.5625 c 1 + 1457.640625 390.557617188 1457.640625 358.551757812 1457.640625 326.546875 c 1 + 1350.73925781 326.546875 1243.83886719 326.546875 1136.9375 326.546875 c 1 + 1136.9375 264.463867188 1136.9375 202.379882812 1136.9375 140.296875 c 1 + 1233.91699219 140.296875 1330.89550781 140.296875 1427.875 140.296875 c 1 + 1427.875 108.317382812 1427.875 76.3388671875 1427.875 44.359375 c 1 + 1330.89550781 44.359375 1233.91699219 44.359375 1136.9375 44.359375 c 1 + 1136.9375 -20.9267578125 1136.9375 -86.2138671875 1136.9375 -151.5 c 1 + 1243.83886719 -151.5 1350.73925781 -151.5 1457.640625 -151.5 c 1 + 1457.640625 -183.504882812 1457.640625 -215.510742188 1457.640625 -247.515625 c 1 + 1314.59375 -247.515625 1171.546875 -247.515625 1028.5 -247.515625 c 1 +310.0625 1288.578125 m 1 + 389.125 1288.578125 468.1875 1288.578125 547.25 1288.578125 c 1 + 590.0625 1288.578125 628.96875 1281.546875 663.8125 1267.484375 c 128 + 698.65625 1253.34375 728.5 1232.40625 753.1875 1204.59375 c 128 + 777.71875 1176.703125 796.78125 1141.859375 810.21875 1099.90625 c 128 + 823.65625 1058.03125 830.375 1009.203125 830.375 953.578125 c 256 + 830.375 897.875 823.65625 849.046875 810.21875 807.171875 c 128 + 796.78125 765.21875 777.71875 730.375 753.1875 702.484375 c 128 + 728.5 674.671875 698.65625 653.734375 663.8125 639.59375 c 128 + 628.96875 625.53125 590.0625 618.5 547.25 618.5 c 1 + 468.1875 618.5 389.125 618.5 310.0625 618.5 c 1 + 310.0625 841.859375 310.0625 1065.21875 310.0625 1288.578125 c 1 +547.25 714.515625 m 1 + 597.09375 714.515625 637.40625 730.21875 668.1875 761.546875 c 128 + 698.8125 792.875 714.28125 838.96875 714.28125 899.75 c 1 + 714.28125 935.609375 714.28125 971.46875 714.28125 1007.328125 c 1 + 714.28125 1068.109375 698.8125 1114.203125 668.1875 1145.53125 c 128 + 637.40625 1176.9375 597.09375 1192.5625 547.25 1192.5625 c 1 + 504.333007812 1192.5625 461.416992188 1192.5625 418.5 1192.5625 c 1 + 418.5 1033.21386719 418.5 873.864257812 418.5 714.515625 c 1 + 461.416992188 714.515625 504.333007812 714.515625 547.25 714.515625 c 1 +1368.65625 618.5 m 1 + 1348.5 678.96875 1328.34375 739.4375 1308.1875 799.90625 c 1 + 1224.64550781 799.90625 1141.10449219 799.90625 1057.5625 799.90625 c 1 + 1038.03125 739.4375 1018.5 678.96875 998.96875 618.5 c 1 + 962.198242188 618.5 925.426757812 618.5 888.65625 618.5 c 1 + 964.801757812 841.859375 1040.94824219 1065.21875 1117.09375 1288.578125 c 1 + 1162.5625 1288.578125 1208.03125 1288.578125 1253.5 1288.578125 c 1 + 1329.64550781 1065.21875 1405.79199219 841.859375 1481.9375 618.5 c 1 + 1444.17675781 618.5 1406.41699219 618.5 1368.65625 618.5 c 1 +1185.21875 1188.734375 m 1 + 1183.65625 1188.734375 1182.09375 1188.734375 1180.53125 1188.734375 c 1 + 1148.1875 1090.16699219 1115.84375 991.598632812 1083.5 893.03125 c 1 + 1149.4375 893.03125 1215.375 893.03125 1281.3125 893.03125 c 1 + 1249.28125 991.598632812 1217.25 1090.16699219 1185.21875 1188.734375 c 1 +EndSplineSet +EndChar + +StartChar: uniE15D +Encoding: 57693 57693 99 +Width: 1792 +Flags: HW +LayerCount: 2 +Fore +SplineSet +218.8515625 936.797851562 m 1 + 317.614257812 936.797851562 416.377929688 936.797851562 515.140625 936.797851562 c 1 + 568.8515625 936.797851562 617.2890625 928.008789062 661.0390625 910.430664062 c 128 + 704.59375 892.754882812 741.703125 866.583007812 772.5625 831.817382812 c 128 + 803.421875 796.954101562 827.25 753.399414062 844.046875 700.958007812 c 128 + 860.84375 648.614257812 869.2421875 587.579101562 869.2421875 518.047851562 c 256 + 869.2421875 448.418945312 860.84375 387.383789062 844.046875 335.040039062 c 128 + 827.25 282.598632812 803.421875 239.043945312 772.5625 204.180664062 c 128 + 741.703125 169.415039062 704.59375 143.243164062 661.0390625 125.567382812 c 128 + 617.2890625 107.989257812 568.8515625 99.2001953125 515.140625 99.2001953125 c 1 + 416.377929688 99.2001953125 317.614257812 99.2001953125 218.8515625 99.2001953125 c 1 + 218.8515625 378.399414062 218.8515625 657.598632812 218.8515625 936.797851562 c 1 +515.140625 219.219726562 m 1 + 577.640625 219.219726562 628.03125 238.848632812 666.3125 278.008789062 c 128 + 704.7890625 317.168945312 723.9296875 374.786132812 723.9296875 450.762695312 c 1 + 723.9296875 495.586914062 723.9296875 540.411132812 723.9296875 585.235351562 c 1 + 723.9296875 661.211914062 704.7890625 718.829101562 666.3125 757.989257812 c 128 + 628.03125 797.247070312 577.640625 816.778320312 515.140625 816.778320312 c 1 + 461.559570312 816.778320312 407.979492188 816.778320312 354.3984375 816.778320312 c 1 + 354.3984375 617.591796875 354.3984375 418.405273438 354.3984375 219.219726562 c 1 + 407.979492188 219.219726562 461.559570312 219.219726562 515.140625 219.219726562 c 1 +1269.6328125 84.8447265625 m 0 + 1199.125 84.8447265625 1139.359375 97.6376953125 1090.140625 123.223632812 c 128 + 1040.921875 148.809570312 998.734375 183.184570312 963.578125 226.446289062 c 1 + 994.762695312 255.645507812 1025.94824219 284.844726562 1057.1328125 314.043945312 c 1 + 1086.8203125 278.008789062 1119.4375 250.762695312 1154.984375 232.403320312 c 128 + 1190.53125 214.043945312 1231.15625 204.766601562 1276.859375 204.766601562 c 0 + 1330.375 204.766601562 1370.8046875 216.778320312 1397.953125 240.801757812 c 128 + 1425.1015625 264.825195312 1438.7734375 297.247070312 1438.7734375 337.969726562 c 0 + 1438.7734375 370.782226562 1429.203125 396.758789062 1410.0625 415.997070312 c 128 + 1390.7265625 435.235351562 1356.7421875 449.590820312 1307.9140625 459.161132812 c 1 + 1283.109375 463.587890625 1258.3046875 468.014648438 1233.5 472.442382812 c 1 + 1152.0546875 487.579101562 1090.7265625 514.434570312 1049.90625 552.813476562 c 128 + 1009.0859375 591.192382812 988.7734375 644.024414062 988.7734375 711.211914062 c 0 + 988.7734375 748.028320312 995.8046875 781.426757812 1009.8671875 811.407226562 c 128 + 1023.734375 841.387695312 1043.4609375 866.583007812 1068.65625 886.993164062 c 128 + 1093.8515625 907.403320312 1124.3203125 923.223632812 1160.453125 934.356445312 c 128 + 1196.390625 945.586914062 1236.8203125 951.153320312 1281.546875 951.153320312 c 0 + 1344.828125 951.153320312 1399.515625 940.215820312 1446 918.243164062 c 128 + 1492.2890625 896.172851562 1531.9375 864.434570312 1564.75 822.833007812 c 1 + 1533.17480469 794.837890625 1501.59863281 766.842773438 1470.0234375 738.848632812 c 1 + 1448.34375 766.778320312 1421.9765625 789.239257812 1390.7265625 806.036132812 c 128 + 1359.671875 822.833007812 1320.8046875 831.231445312 1274.3203125 831.231445312 c 0 + 1226.46875 831.231445312 1189.1640625 821.563476562 1162.796875 802.422851562 c 128 + 1136.4296875 783.184570312 1123.1484375 755.157226562 1123.1484375 718.438476562 c 0 + 1123.1484375 683.184570312 1133.890625 657.012695312 1155.5703125 639.825195312 c 128 + 1177.25 622.637695312 1210.84375 609.551757812 1256.3515625 600.762695312 c 1 + 1281.15625 595.586914062 1305.9609375 590.411132812 1330.765625 585.235351562 c 1 + 1414.75 569.219726562 1476.2734375 541.973632812 1514.9453125 503.594726562 c 128 + 1553.8125 465.215820312 1573.1484375 412.383789062 1573.1484375 345.196289062 c 0 + 1573.1484375 306.036132812 1566.3125 270.391601562 1552.8359375 238.360351562 c 128 + 1539.1640625 206.426757812 1519.4375 178.985351562 1493.4609375 156.231445312 c 128 + 1467.2890625 133.379882812 1435.6484375 115.801757812 1397.953125 103.399414062 c 128 + 1360.453125 90.9970703125 1317.484375 84.8447265625 1269.6328125 84.8447265625 c 0 +EndSplineSet +EndChar + +StartChar: uniE15E +Encoding: 57694 57694 100 +Width: 1792 +Flags: HW +LayerCount: 2 +Fore +SplineSet +11.3125 422.5625 m 1 + 84.90625 422.5625 158.5 422.5625 232.09375 422.5625 c 1 + 271.78125 422.5625 307.171875 416.15625 338.1875 403.34375 c 128 + 369.28125 390.53125 395.453125 370.53125 416.9375 343.34375 c 128 + 438.34375 316.15625 454.671875 281.46875 465.921875 239.203125 c 128 + 477.09375 196.9375 482.71875 146.390625 482.71875 87.5625 c 256 + 482.71875 28.65625 477.09375 -21.890625 465.921875 -64.15625 c 128 + 454.671875 -106.421875 438.34375 -141.109375 416.9375 -168.296875 c 128 + 395.453125 -195.484375 369.28125 -215.484375 338.1875 -228.296875 c 128 + 307.171875 -241.109375 271.78125 -247.515625 232.09375 -247.515625 c 1 + 158.5 -247.515625 84.90625 -247.515625 11.3125 -247.515625 c 1 + 11.3125 -24.15625 11.3125 199.203125 11.3125 422.5625 c 1 +232.09375 -158.21875 m 1 + 276.3125 -158.21875 310.84375 -144 335.84375 -115.484375 c 128 + 360.765625 -87.046875 373.265625 -45.25 373.265625 9.75 c 1 + 373.265625 61.5986328125 373.265625 113.448242188 373.265625 165.296875 c 1 + 373.265625 220.296875 360.765625 262.09375 335.84375 290.53125 c 128 + 310.84375 319.046875 276.3125 333.265625 232.09375 333.265625 c 1 + 192.432617188 333.265625 152.770507812 333.265625 113.109375 333.265625 c 1 + 113.109375 169.4375 113.109375 5.609375 113.109375 -158.21875 c 1 + 152.770507812 -158.21875 192.432617188 -158.21875 232.09375 -158.21875 c 1 +1031.15625 165.296875 m 1 + 1033.39550781 203.370117188 1035.63574219 241.442382812 1037.875 279.515625 c 1 + 1034.046875 279.515625 1030.21875 279.515625 1026.390625 279.515625 c 1 + 1011.02636719 242.067382812 995.661132812 204.620117188 980.296875 167.171875 c 1 + 936.78125 76.3125 893.265625 -14.546875 849.75 -105.40625 c 1 + 806.546875 -14.546875 763.34375 76.3125 720.140625 167.171875 c 1 + 704.776367188 203.995117188 689.411132812 240.817382812 674.046875 277.640625 c 1 + 670.21875 277.640625 666.390625 277.640625 662.5625 277.640625 c 1 + 664.801757812 240.192382812 667.041992188 202.745117188 669.28125 165.296875 c 1 + 669.28125 27.6923828125 669.28125 -109.911132812 669.28125 -247.515625 c 1 + 636.625 -247.515625 603.96875 -247.515625 571.3125 -247.515625 c 1 + 571.3125 -24.15625 571.3125 199.203125 571.3125 422.5625 c 1 + 611.3125 422.5625 651.3125 422.5625 691.3125 422.5625 c 1 + 727.484375 344.489257812 763.65625 266.416992188 799.828125 188.34375 c 1 + 815.504882812 144.176757812 831.182617188 100.010742188 846.859375 55.84375 c 1 + 849.723632812 55.84375 852.588867188 55.84375 855.453125 55.84375 c 1 + 871.15625 100.010742188 886.859375 144.176757812 902.5625 188.34375 c 1 + 938.395507812 266.416992188 974.229492188 344.489257812 1010.0625 422.5625 c 1 + 1049.75 422.5625 1089.4375 422.5625 1129.125 422.5625 c 1 + 1129.125 199.203125 1129.125 -24.15625 1129.125 -247.515625 c 1 + 1096.46875 -247.515625 1063.8125 -247.515625 1031.15625 -247.515625 c 1 + 1031.15625 -109.911132812 1031.15625 27.6923828125 1031.15625 165.296875 c 1 +1666.703125 -247.515625 m 1 + 1649.41113281 -185.745117188 1632.12011719 -123.973632812 1614.828125 -62.203125 c 1 + 1539.64550781 -62.203125 1464.46386719 -62.203125 1389.28125 -62.203125 c 1 + 1371.98925781 -123.973632812 1354.69824219 -185.745117188 1337.40625 -247.515625 c 1 + 1304.125 -247.515625 1270.84375 -247.515625 1237.5625 -247.515625 c 1 + 1304.4375 -24.15625 1371.3125 199.203125 1438.1875 422.5625 c 1 + 1481.390625 422.5625 1524.59375 422.5625 1567.796875 422.5625 c 1 + 1635.01074219 199.203125 1702.22363281 -24.15625 1769.4375 -247.515625 c 1 + 1735.19238281 -247.515625 1700.94824219 -247.515625 1666.703125 -247.515625 c 1 +1528.421875 232.484375 m 1 + 1521.390625 265.114257812 1514.359375 297.745117188 1507.328125 330.375 c 1 + 1503.16113281 330.375 1498.99511719 330.375 1494.828125 330.375 c 1 + 1487.796875 297.745117188 1480.765625 265.114257812 1473.734375 232.484375 c 1 + 1453.890625 162.09375 1434.046875 91.703125 1414.203125 21.3125 c 1 + 1473.08300781 21.3125 1531.96386719 21.3125 1590.84375 21.3125 c 1 + 1570.03613281 91.703125 1549.22949219 162.09375 1528.421875 232.484375 c 1 +456.3125 717.40625 m 1 + 452.458007812 717.40625 448.604492188 717.40625 444.75 717.40625 c 1 + 438.421875 685.375 422.09375 658.96875 395.84375 638.1875 c 128 + 369.59375 617.40625 332.484375 607.015625 284.4375 607.015625 c 0 + 249.90625 607.015625 218.03125 613.34375 188.96875 626.15625 c 128 + 159.828125 638.96875 134.359375 659.28125 112.640625 687.171875 c 128 + 90.84375 714.984375 73.890625 750.84375 61.78125 794.671875 c 128 + 49.59375 838.5 43.5 891.15625 43.5 952.5625 c 256 + 43.5 1014.046875 49.4375 1066.625 61.234375 1110.53125 c 128 + 73.109375 1154.359375 90.375 1190.375 113.109375 1218.5 c 128 + 135.84375 1246.625 163.1875 1267.328125 195.21875 1280.453125 c 128 + 227.171875 1293.578125 263.34375 1300.0625 303.65625 1300.0625 c 0 + 363.1875 1300.0625 411.546875 1287.328125 448.65625 1261.703125 c 128 + 485.765625 1236.078125 514.515625 1199.59375 535.0625 1152.25 c 1 + 507.223632812 1135.921875 479.385742188 1119.59375 451.546875 1103.265625 c 1 + 439.984375 1135.921875 422.5625 1161.859375 399.203125 1181.078125 c 128 + 375.84375 1200.296875 344.28125 1209.828125 304.59375 1209.828125 c 0 + 256.625 1209.828125 219.359375 1194.515625 192.796875 1163.8125 c 128 + 166.234375 1133.03125 152.953125 1090.21875 152.953125 1035.140625 c 1 + 152.953125 980.739257812 152.953125 926.338867188 152.953125 871.9375 c 1 + 152.953125 816.9375 166.234375 774.046875 192.796875 743.265625 c 128 + 219.359375 712.5625 256.625 697.25 304.59375 697.25 c 0 + 324.4375 697.25 343.03125 700.0625 360.296875 705.84375 c 128 + 377.5625 711.625 392.796875 719.90625 405.921875 730.84375 c 128 + 419.046875 741.703125 429.28125 755.296875 436.625 771.625 c 128 + 443.96875 787.953125 447.640625 806.3125 447.640625 826.78125 c 1 + 447.640625 846.3125 447.640625 865.84375 447.640625 885.375 c 1 + 406.364257812 885.375 365.088867188 885.375 323.8125 885.375 c 1 + 323.8125 914.489257812 323.8125 943.604492188 323.8125 972.71875 c 1 + 398.057617188 972.71875 472.301757812 972.71875 546.546875 972.71875 c 1 + 546.546875 854.645507812 546.546875 736.573242188 546.546875 618.5 c 1 + 516.46875 618.5 486.390625 618.5 456.3125 618.5 c 1 + 456.3125 651.46875 456.3125 684.4375 456.3125 717.40625 c 1 +710.375 1288.578125 m 1 + 800.948242188 1288.578125 891.520507812 1288.578125 982.09375 1288.578125 c 1 + 1033.890625 1288.578125 1073.265625 1273.890625 1100.140625 1244.4375 c 128 + 1127.015625 1214.984375 1140.453125 1172.09375 1140.453125 1115.765625 c 0 + 1140.453125 1072.875 1131.15625 1039.28125 1112.640625 1014.984375 c 128 + 1094.046875 990.6875 1067.484375 976.9375 1032.953125 973.734375 c 1 + 1032.953125 969.879882812 1032.953125 966.026367188 1032.953125 962.171875 c 1 + 1052.171875 962.171875 1069.59375 958.5 1085.296875 951.15625 c 128 + 1100.921875 943.8125 1114.515625 933.34375 1126.078125 919.90625 c 128 + 1137.5625 906.46875 1146.546875 890.53125 1152.953125 871.9375 c 128 + 1159.359375 853.34375 1162.5625 833.1875 1162.5625 811.46875 c 0 + 1162.5625 782.640625 1158.890625 756.390625 1151.546875 732.71875 c 128 + 1144.125 709.046875 1133.734375 688.734375 1120.296875 671.78125 c 128 + 1106.859375 654.828125 1090.6875 641.703125 1071.859375 632.40625 c 128 + 1052.953125 623.109375 1032.015625 618.5 1008.96875 618.5 c 1 + 909.4375 618.5 809.90625 618.5 710.375 618.5 c 1 + 710.375 841.859375 710.375 1065.21875 710.375 1288.578125 c 1 +812.171875 707.796875 m 1 + 867.848632812 707.796875 923.526367188 707.796875 979.203125 707.796875 c 1 + 1003.5 707.796875 1022.09375 714.359375 1034.90625 727.484375 c 128 + 1047.640625 740.609375 1054.046875 761.546875 1054.046875 790.375 c 1 + 1054.046875 805.088867188 1054.046875 819.801757812 1054.046875 834.515625 c 1 + 1054.046875 863.265625 1047.640625 884.28125 1034.90625 897.40625 c 128 + 1022.09375 910.53125 1003.5 917.09375 979.203125 917.09375 c 1 + 923.526367188 917.09375 867.848632812 917.09375 812.171875 917.09375 c 1 + 812.171875 847.328125 812.171875 777.5625 812.171875 707.796875 c 1 +812.171875 1004.4375 m 1 + 862.40625 1004.4375 912.640625 1004.4375 962.875 1004.4375 c 1 + 985.296875 1004.4375 1002.5625 1010.375 1014.671875 1022.171875 c 128 + 1026.859375 1034.046875 1032.953125 1053.34375 1032.953125 1080.296875 c 1 + 1032.953125 1094.671875 1032.953125 1109.046875 1032.953125 1123.421875 c 1 + 1032.953125 1150.375 1026.859375 1169.671875 1014.671875 1181.546875 c 128 + 1002.5625 1193.34375 985.296875 1199.28125 962.875 1199.28125 c 1 + 912.640625 1199.28125 862.40625 1199.28125 812.171875 1199.28125 c 1 + 812.171875 1134.33300781 812.171875 1069.38574219 812.171875 1004.4375 c 1 +1645.765625 618.5 m 1 + 1628.47363281 680.270507812 1611.18261719 742.041992188 1593.890625 803.8125 c 1 + 1518.70800781 803.8125 1443.52636719 803.8125 1368.34375 803.8125 c 1 + 1351.05175781 742.041992188 1333.76074219 680.270507812 1316.46875 618.5 c 1 + 1283.1875 618.5 1249.90625 618.5 1216.625 618.5 c 1 + 1283.5 841.859375 1350.375 1065.21875 1417.25 1288.578125 c 1 + 1460.453125 1288.578125 1503.65625 1288.578125 1546.859375 1288.578125 c 1 + 1614.07324219 1065.21875 1681.28613281 841.859375 1748.5 618.5 c 1 + 1714.25488281 618.5 1680.01074219 618.5 1645.765625 618.5 c 1 +1507.484375 1098.5 m 1 + 1500.453125 1131.12988281 1493.421875 1163.76074219 1486.390625 1196.390625 c 1 + 1482.22363281 1196.390625 1478.05761719 1196.390625 1473.890625 1196.390625 c 1 + 1466.859375 1163.76074219 1459.828125 1131.12988281 1452.796875 1098.5 c 1 + 1432.953125 1028.109375 1413.109375 957.71875 1393.265625 887.328125 c 1 + 1452.14550781 887.328125 1511.02636719 887.328125 1569.90625 887.328125 c 1 + 1549.09863281 957.71875 1528.29199219 1028.109375 1507.484375 1098.5 c 1 +EndSplineSet +EndChar + +StartChar: uniE15F +Encoding: 57695 57695 101 +Width: 1792 +Flags: HW +LayerCount: 2 +Fore +SplineSet +711.3125 165.296875 m 1 + 713.551757812 203.370117188 715.791992188 241.442382812 718.03125 279.515625 c 1 + 714.203125 279.515625 710.375 279.515625 706.546875 279.515625 c 1 + 691.182617188 242.067382812 675.817382812 204.620117188 660.453125 167.171875 c 1 + 616.9375 76.3125 573.421875 -14.546875 529.90625 -105.40625 c 1 + 486.703125 -14.546875 443.5 76.3125 400.296875 167.171875 c 1 + 384.932617188 203.995117188 369.567382812 240.817382812 354.203125 277.640625 c 1 + 350.375 277.640625 346.546875 277.640625 342.71875 277.640625 c 1 + 344.958007812 240.192382812 347.198242188 202.745117188 349.4375 165.296875 c 1 + 349.4375 27.6923828125 349.4375 -109.911132812 349.4375 -247.515625 c 1 + 316.78125 -247.515625 284.125 -247.515625 251.46875 -247.515625 c 1 + 251.46875 -24.15625 251.46875 199.203125 251.46875 422.5625 c 1 + 291.46875 422.5625 331.46875 422.5625 371.46875 422.5625 c 1 + 407.640625 344.489257812 443.8125 266.416992188 479.984375 188.34375 c 1 + 495.661132812 144.176757812 511.338867188 100.010742188 527.015625 55.84375 c 1 + 529.879882812 55.84375 532.745117188 55.84375 535.609375 55.84375 c 1 + 551.3125 100.010742188 567.015625 144.176757812 582.71875 188.34375 c 1 + 618.551757812 266.416992188 654.385742188 344.489257812 690.21875 422.5625 c 1 + 729.90625 422.5625 769.59375 422.5625 809.28125 422.5625 c 1 + 809.28125 199.203125 809.28125 -24.15625 809.28125 -247.515625 c 1 + 776.625 -247.515625 743.96875 -247.515625 711.3125 -247.515625 c 1 + 711.3125 -109.911132812 711.3125 27.6923828125 711.3125 165.296875 c 1 +1431.3125 165.296875 m 1 + 1433.55175781 203.370117188 1435.79199219 241.442382812 1438.03125 279.515625 c 1 + 1434.203125 279.515625 1430.375 279.515625 1426.546875 279.515625 c 1 + 1411.18261719 242.067382812 1395.81738281 204.620117188 1380.453125 167.171875 c 1 + 1336.9375 76.3125 1293.421875 -14.546875 1249.90625 -105.40625 c 1 + 1206.703125 -14.546875 1163.5 76.3125 1120.296875 167.171875 c 1 + 1104.93261719 203.995117188 1089.56738281 240.817382812 1074.203125 277.640625 c 1 + 1070.375 277.640625 1066.546875 277.640625 1062.71875 277.640625 c 1 + 1064.95800781 240.192382812 1067.19824219 202.745117188 1069.4375 165.296875 c 1 + 1069.4375 27.6923828125 1069.4375 -109.911132812 1069.4375 -247.515625 c 1 + 1036.78125 -247.515625 1004.125 -247.515625 971.46875 -247.515625 c 1 + 971.46875 -24.15625 971.46875 199.203125 971.46875 422.5625 c 1 + 1011.46875 422.5625 1051.46875 422.5625 1091.46875 422.5625 c 1 + 1127.640625 344.489257812 1163.8125 266.416992188 1199.984375 188.34375 c 1 + 1215.66113281 144.176757812 1231.33886719 100.010742188 1247.015625 55.84375 c 1 + 1249.87988281 55.84375 1252.74511719 55.84375 1255.609375 55.84375 c 1 + 1271.3125 100.010742188 1287.015625 144.176757812 1302.71875 188.34375 c 1 + 1338.55175781 266.416992188 1374.38574219 344.489257812 1410.21875 422.5625 c 1 + 1449.90625 422.5625 1489.59375 422.5625 1529.28125 422.5625 c 1 + 1529.28125 199.203125 1529.28125 -24.15625 1529.28125 -247.515625 c 1 + 1496.625 -247.515625 1463.96875 -247.515625 1431.3125 -247.515625 c 1 + 1431.3125 -109.911132812 1431.3125 27.6923828125 1431.3125 165.296875 c 1 +456.3125 717.40625 m 1 + 452.458007812 717.40625 448.604492188 717.40625 444.75 717.40625 c 1 + 438.421875 685.375 422.09375 658.96875 395.84375 638.1875 c 128 + 369.59375 617.40625 332.484375 607.015625 284.4375 607.015625 c 0 + 249.90625 607.015625 218.03125 613.34375 188.96875 626.15625 c 128 + 159.828125 638.96875 134.359375 659.28125 112.640625 687.171875 c 128 + 90.84375 714.984375 73.890625 750.84375 61.78125 794.671875 c 128 + 49.59375 838.5 43.5 891.15625 43.5 952.5625 c 256 + 43.5 1014.046875 49.4375 1066.625 61.234375 1110.53125 c 128 + 73.109375 1154.359375 90.375 1190.375 113.109375 1218.5 c 128 + 135.84375 1246.625 163.1875 1267.328125 195.21875 1280.453125 c 128 + 227.171875 1293.578125 263.34375 1300.0625 303.65625 1300.0625 c 0 + 363.1875 1300.0625 411.546875 1287.328125 448.65625 1261.703125 c 128 + 485.765625 1236.078125 514.515625 1199.59375 535.0625 1152.25 c 1 + 507.223632812 1135.921875 479.385742188 1119.59375 451.546875 1103.265625 c 1 + 439.984375 1135.921875 422.5625 1161.859375 399.203125 1181.078125 c 128 + 375.84375 1200.296875 344.28125 1209.828125 304.59375 1209.828125 c 0 + 256.625 1209.828125 219.359375 1194.515625 192.796875 1163.8125 c 128 + 166.234375 1133.03125 152.953125 1090.21875 152.953125 1035.140625 c 1 + 152.953125 980.739257812 152.953125 926.338867188 152.953125 871.9375 c 1 + 152.953125 816.9375 166.234375 774.046875 192.796875 743.265625 c 128 + 219.359375 712.5625 256.625 697.25 304.59375 697.25 c 0 + 324.4375 697.25 343.03125 700.0625 360.296875 705.84375 c 128 + 377.5625 711.625 392.796875 719.90625 405.921875 730.84375 c 128 + 419.046875 741.703125 429.28125 755.296875 436.625 771.625 c 128 + 443.96875 787.953125 447.640625 806.3125 447.640625 826.78125 c 1 + 447.640625 846.3125 447.640625 865.84375 447.640625 885.375 c 1 + 406.364257812 885.375 365.088867188 885.375 323.8125 885.375 c 1 + 323.8125 914.489257812 323.8125 943.604492188 323.8125 972.71875 c 1 + 398.057617188 972.71875 472.301757812 972.71875 546.546875 972.71875 c 1 + 546.546875 854.645507812 546.546875 736.573242188 546.546875 618.5 c 1 + 516.46875 618.5 486.390625 618.5 456.3125 618.5 c 1 + 456.3125 651.46875 456.3125 684.4375 456.3125 717.40625 c 1 +710.375 1288.578125 m 1 + 800.948242188 1288.578125 891.520507812 1288.578125 982.09375 1288.578125 c 1 + 1033.890625 1288.578125 1073.265625 1273.890625 1100.140625 1244.4375 c 128 + 1127.015625 1214.984375 1140.453125 1172.09375 1140.453125 1115.765625 c 0 + 1140.453125 1072.875 1131.15625 1039.28125 1112.640625 1014.984375 c 128 + 1094.046875 990.6875 1067.484375 976.9375 1032.953125 973.734375 c 1 + 1032.953125 969.879882812 1032.953125 966.026367188 1032.953125 962.171875 c 1 + 1052.171875 962.171875 1069.59375 958.5 1085.296875 951.15625 c 128 + 1100.921875 943.8125 1114.515625 933.34375 1126.078125 919.90625 c 128 + 1137.5625 906.46875 1146.546875 890.53125 1152.953125 871.9375 c 128 + 1159.359375 853.34375 1162.5625 833.1875 1162.5625 811.46875 c 0 + 1162.5625 782.640625 1158.890625 756.390625 1151.546875 732.71875 c 128 + 1144.125 709.046875 1133.734375 688.734375 1120.296875 671.78125 c 128 + 1106.859375 654.828125 1090.6875 641.703125 1071.859375 632.40625 c 128 + 1052.953125 623.109375 1032.015625 618.5 1008.96875 618.5 c 1 + 909.4375 618.5 809.90625 618.5 710.375 618.5 c 1 + 710.375 841.859375 710.375 1065.21875 710.375 1288.578125 c 1 +812.171875 707.796875 m 1 + 867.848632812 707.796875 923.526367188 707.796875 979.203125 707.796875 c 1 + 1003.5 707.796875 1022.09375 714.359375 1034.90625 727.484375 c 128 + 1047.640625 740.609375 1054.046875 761.546875 1054.046875 790.375 c 1 + 1054.046875 805.088867188 1054.046875 819.801757812 1054.046875 834.515625 c 1 + 1054.046875 863.265625 1047.640625 884.28125 1034.90625 897.40625 c 128 + 1022.09375 910.53125 1003.5 917.09375 979.203125 917.09375 c 1 + 923.526367188 917.09375 867.848632812 917.09375 812.171875 917.09375 c 1 + 812.171875 847.328125 812.171875 777.5625 812.171875 707.796875 c 1 +812.171875 1004.4375 m 1 + 862.40625 1004.4375 912.640625 1004.4375 962.875 1004.4375 c 1 + 985.296875 1004.4375 1002.5625 1010.375 1014.671875 1022.171875 c 128 + 1026.859375 1034.046875 1032.953125 1053.34375 1032.953125 1080.296875 c 1 + 1032.953125 1094.671875 1032.953125 1109.046875 1032.953125 1123.421875 c 1 + 1032.953125 1150.375 1026.859375 1169.671875 1014.671875 1181.546875 c 128 + 1002.5625 1193.34375 985.296875 1199.28125 962.875 1199.28125 c 1 + 912.640625 1199.28125 862.40625 1199.28125 812.171875 1199.28125 c 1 + 812.171875 1134.33300781 812.171875 1069.38574219 812.171875 1004.4375 c 1 +1645.765625 618.5 m 1 + 1628.47363281 680.270507812 1611.18261719 742.041992188 1593.890625 803.8125 c 1 + 1518.70800781 803.8125 1443.52636719 803.8125 1368.34375 803.8125 c 1 + 1351.05175781 742.041992188 1333.76074219 680.270507812 1316.46875 618.5 c 1 + 1283.1875 618.5 1249.90625 618.5 1216.625 618.5 c 1 + 1283.5 841.859375 1350.375 1065.21875 1417.25 1288.578125 c 1 + 1460.453125 1288.578125 1503.65625 1288.578125 1546.859375 1288.578125 c 1 + 1614.07324219 1065.21875 1681.28613281 841.859375 1748.5 618.5 c 1 + 1714.25488281 618.5 1680.01074219 618.5 1645.765625 618.5 c 1 +1507.484375 1098.5 m 1 + 1500.453125 1131.12988281 1493.421875 1163.76074219 1486.390625 1196.390625 c 1 + 1482.22363281 1196.390625 1478.05761719 1196.390625 1473.890625 1196.390625 c 1 + 1466.859375 1163.76074219 1459.828125 1131.12988281 1452.796875 1098.5 c 1 + 1432.953125 1028.109375 1413.109375 957.71875 1393.265625 887.328125 c 1 + 1452.14550781 887.328125 1511.02636719 887.328125 1569.90625 887.328125 c 1 + 1549.09863281 957.71875 1528.29199219 1028.109375 1507.484375 1098.5 c 1 +EndSplineSet +EndChar EndChars EndSplineFont diff --git a/res/icons.ttf b/res/icons.ttf index 07f6313f2..f10b37a05 100644 Binary files a/res/icons.ttf and b/res/icons.ttf differ diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index 6036364c5..d8c77030f 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -35,8 +35,30 @@ bool DivCSChannelState::doCall(unsigned int addr) { return true; } +unsigned char* DivCSPlayer::getData() { + return b; +} + +size_t DivCSPlayer::getDataLen() { + return bLen; +} + +DivCSChannelState* DivCSPlayer::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivCSPlayer::getFastDelays() { + return fastDelays; +} + +unsigned char* DivCSPlayer::getFastCmds() { + return fastCmds; +} + void DivCSPlayer::cleanup() { delete b; + b=NULL; + bLen=0; } bool DivCSPlayer::tick() { @@ -55,8 +77,15 @@ bool DivCSPlayer::tick() { chan[i].readPos=0; break; } + + chan[i].trace[chan[i].tracePos++]=chan[i].readPos; + if (chan[i].tracePos>=DIV_MAX_CSTRACE) { + chan[i].tracePos=0; + } + unsigned char next=stream.readC(); unsigned char command=0; + bool mustTell=true; if (next<0xb3) { // note e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,(int)next-60)); @@ -66,6 +95,7 @@ bool DivCSPlayer::tick() { command=fastCmds[next&15]; } else if (next>=0xe0 && next<=0xef) { // preset delay chan[i].waitTicks=fastDelays[next&15]; + chan[i].lastWaitLen=chan[i].waitTicks; } else switch (next) { case 0xb4: // note on null e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); @@ -121,9 +151,11 @@ bool DivCSPlayer::tick() { break; } chan[i].readPos=chan[i].callStack[--chan[i].callStackPos]; + mustTell=false; break; case 0xfa: chan[i].readPos=stream.readI(); + mustTell=false; break; case 0xfb: logE("TODO: RATE"); @@ -131,16 +163,20 @@ bool DivCSPlayer::tick() { break; case 0xfc: chan[i].waitTicks=(unsigned short)stream.readS(); + chan[i].lastWaitLen=chan[i].waitTicks; break; case 0xfd: chan[i].waitTicks=(unsigned char)stream.readC(); + chan[i].lastWaitLen=chan[i].waitTicks; break; case 0xfe: chan[i].waitTicks=1; + chan[i].lastWaitLen=chan[i].waitTicks; break; case 0xff: - chan[i].readPos=0; - logI("%d: stop",i); + chan[i].readPos=chan[i].startPos; + mustTell=false; + logI("%d: stop go back to %x",i,chan[i].readPos); break; default: logE("%d: illegal instruction $%.2x! $%.x",i,next,chan[i].readPos); @@ -315,7 +351,7 @@ bool DivCSPlayer::tick() { } } - chan[i].readPos=stream.tell(); + if (mustTell) chan[i].readPos=stream.tell(); } if (sendVolume || chan[i].volSpeed!=0) { @@ -383,7 +419,8 @@ bool DivCSPlayer::init() { stream.readI(); continue; } - chan[i].readPos=stream.readI(); + chan[i].startPos=stream.readI(); + chan[i].readPos=chan[i].startPos; } stream.read(fastDelays,16); @@ -427,3 +464,17 @@ bool DivEngine::playStream(unsigned char* f, size_t length) { BUSY_END; return true; } + +DivCSPlayer* DivEngine::getStreamPlayer() { + return cmdStreamInt; +} + +bool DivEngine::killStream() { + if (!cmdStreamInt) return false; + BUSY_BEGIN; + cmdStreamInt->cleanup(); + delete cmdStreamInt; + cmdStreamInt=NULL; + BUSY_END; + return true; +} diff --git a/src/engine/cmdStream.h b/src/engine/cmdStream.h index cd0a786b7..4704b41b9 100644 --- a/src/engine/cmdStream.h +++ b/src/engine/cmdStream.h @@ -23,11 +23,15 @@ #include "defines.h" #include "safeReader.h" +#define DIV_MAX_CSTRACE 64 + class DivEngine; struct DivCSChannelState { + unsigned int startPos; unsigned int readPos; int waitTicks; + int lastWaitLen; int note, pitch; int volume, volMax, volSpeed; @@ -38,11 +42,7 @@ struct DivCSChannelState { unsigned int callStack[8]; unsigned char callStackPos; - struct TraceEntry { - unsigned int addr; - unsigned char length; - unsigned char data[11]; - } trace[32]; + unsigned int trace[DIV_MAX_CSTRACE]; unsigned char tracePos; bool doCall(unsigned int addr); @@ -50,6 +50,7 @@ struct DivCSChannelState { DivCSChannelState(): readPos(0), waitTicks(0), + lastWaitLen(0), note(-1), pitch(0), volume(0x7f00), @@ -63,12 +64,18 @@ struct DivCSChannelState { arp(0), arpStage(0), arpTicks(0), - callStackPos(0) {} + callStackPos(0), + tracePos(0) { + for (int i=0; i255) { \ - chanStream[x]->writeC(0xfc); \ - chanStream[x]->writeS(tick-lastTick[x]); \ - } else if (tick-lastTick[x]>1) { \ - delayPopularity[tick-lastTick[x]]++; \ - chanStream[x]->writeC(0xfd); \ - chanStream[x]->writeC(tick-lastTick[x]); \ - } else if (tick-lastTick[x]>0) { \ - chanStream[x]->writeC(0xfe); \ - } \ - lastTick[x]=tick; \ - } \ - } else { \ - if (!wroteTickGlobal) { \ - wroteTickGlobal=true; \ - w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ + if (!wroteTick[x]) { \ + wroteTick[x]=true; \ + if (tick-lastTick[x]>255) { \ + chanStream[x]->writeC(0xfc); \ + chanStream[x]->writeS(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>1) { \ + delayPopularity[tick-lastTick[x]]++; \ + chanStream[x]->writeC(0xfd); \ + chanStream[x]->writeC(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>0) { \ + chanStream[x]->writeC(0xfe); \ } \ + lastTick[x]=tick; \ } void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { @@ -205,7 +198,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { } } -SafeWriter* DivEngine::saveCommand(bool binary) { +SafeWriter* DivEngine::saveCommand() { stop(); repeatPattern=false; shallStop=false; @@ -243,49 +236,23 @@ SafeWriter* DivEngine::saveCommand(bool binary) { w->init(); // write header - if (binary) { - w->write("FCS",4); - w->writeI(chans); - // offsets - for (int i=0; iinit(); - w->writeI(0); - } - // preset delays and speed dial - for (int i=0; i<32; i++) { - w->writeC(0); - } - } else { - w->writeText("# Furnace Command Stream\n\n"); - - w->writeText("[Information]\n"); - w->writeText(fmt::sprintf("name: %s\n",song.name)); - w->writeText(fmt::sprintf("author: %s\n",song.author)); - w->writeText(fmt::sprintf("category: %s\n",song.category)); - w->writeText(fmt::sprintf("system: %s\n",song.systemName)); - - w->writeText("\n"); - - w->writeText("[SubSongInformation]\n"); - w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); - w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); - - w->writeText("\n"); - - w->writeText("[SysDefinition]\n"); - // TODO - - w->writeText("\n"); + w->write("FCS",4); + w->writeI(chans); + // offsets + for (int i=0; iinit(); + w->writeI(0); + } + // preset delays and speed dial + for (int i=0; i<32; i++) { + w->writeC(0); } // play the song ourselves bool done=false; playSub(false); - if (!binary) { - w->writeText("[Stream]\n"); - } int tick=0; bool oldCmdStreamEnabled=cmdStreamEnabled; cmdStreamEnabled=true; @@ -296,19 +263,15 @@ SafeWriter* DivEngine::saveCommand(bool binary) { while (!done) { if (nextTick(false,true) || !playing) { done=true; + break; } // get command stream - bool wroteTickGlobal=false; memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); if (curDivider!=divider) { curDivider=divider; WRITE_TICK(0); - if (binary) { - chanStream[0]->writeC(0xfb); - chanStream[0]->writeI((int)(curDivider*65536)); - } else { - w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); - } + chanStream[0]->writeC(0xfb); + chanStream[0]->writeI((int)(curDivider*65536)); } for (DivCommand& i: cmdStream) { switch (i.cmd) { @@ -327,206 +290,198 @@ SafeWriter* DivEngine::saveCommand(bool binary) { break; default: WRITE_TICK(i.chan); - if (binary) { - cmdPopularity[i.cmd]++; - writePackedCommandValues(chanStream[i.chan],i); - } else { - w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); - } + cmdPopularity[i.cmd]++; + writePackedCommandValues(chanStream[i.chan],i); break; } } cmdStream.clear(); tick++; } + memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); + for (int i=0; iwriteC(0xff); - // optimize stream - SafeWriter* oldStream=chanStream[i]; - SafeReader* reader=oldStream->toReader(); - chanStream[i]=new SafeWriter; - chanStream[i]->init(); + sortedCmdPopularity[sortPos]=cmdPopularity[sortCand]; + sortedCmd[sortPos]=sortCand; + cmdPopularity[sortCand]=0; + sortPos++; + } - while (1) { - try { - unsigned char next=reader->readC(); - switch (next) { - case 0xb8: // instrument - case 0xc0: // pre porta - case 0xc3: // vibrato range - case 0xc4: // vibrato shape - case 0xc5: // pitch - case 0xc7: // volume - case 0xca: // legato - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xbe: // panning - case 0xc2: // vibrato - case 0xc6: // arpeggio - case 0xc8: // vol slide - case 0xc9: // porta - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xf0: { // full command (pre) - unsigned char cmd=reader->readC(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedCmd[j]==cmd) { - chanStream[i]->writeC(0xd0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(0xf7); // full command - chanStream[i]->writeC(cmd); - } - - unsigned char cmdLen=reader->readC(); - logD("cmdLen: %d",cmdLen); - for (unsigned char j=0; jreadC(); - chanStream[i]->writeC(next); - } - break; - } - case 0xfb: // tick rate - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xfc: { // 16-bit wait - unsigned short delay=reader->readS(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedDelay[j]==delay) { - chanStream[i]->writeC(0xe0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(next); - chanStream[i]->writeS(delay); - } - break; - } - case 0xfd: { // 8-bit wait - unsigned char delay=reader->readC(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedDelay[j]==delay) { - chanStream[i]->writeC(0xe0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(next); - chanStream[i]->writeC(delay); - } - break; - } - default: - chanStream[i]->writeC(next); - break; - } - } catch (EndOfFileException& e) { - break; + sortCand=-1; + sortPos=0; + while (sortPos<16) { + sortCand=-1; + for (int i=0; i<256; i++) { + if (delayPopularity[i]) { + if (sortCand==-1) { + sortCand=i; + } else if (delayPopularity[sortCand]finish(); - delete oldStream; + sortedDelayPopularity[sortPos]=delayPopularity[sortCand]; + sortedDelay[sortPos]=sortCand; + delayPopularity[sortCand]=0; + sortPos++; + } + + for (int i=0; iwriteC(0xff); + // optimize stream + SafeWriter* oldStream=chanStream[i]; + SafeReader* reader=oldStream->toReader(); + chanStream[i]=new SafeWriter; + chanStream[i]->init(); + + while (1) { + try { + unsigned char next=reader->readC(); + switch (next) { + case 0xb8: // instrument + case 0xc0: // pre porta + case 0xc3: // vibrato range + case 0xc4: // vibrato shape + case 0xc5: // pitch + case 0xc7: // volume + case 0xca: // legato + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xbe: // panning + case 0xc2: // vibrato + case 0xc6: // arpeggio + case 0xc8: // vol slide + case 0xc9: // porta + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xf0: { // full command (pre) + unsigned char cmd=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedCmd[j]==cmd) { + chanStream[i]->writeC(0xd0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(0xf7); // full command + chanStream[i]->writeC(cmd); + } + + unsigned char cmdLen=reader->readC(); + logD("cmdLen: %d",cmdLen); + for (unsigned char j=0; jreadC(); + chanStream[i]->writeC(next); + } + break; + } + case 0xfb: // tick rate + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xfc: { // 16-bit wait + unsigned short delay=reader->readS(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeS(delay); + } + break; + } + case 0xfd: { // 8-bit wait + unsigned char delay=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeC(delay); + } + break; + } + default: + chanStream[i]->writeC(next); + break; + } + } catch (EndOfFileException& e) { + break; + } } - for (int i=0; itell(); - logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); - w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); - chanStream[i]->finish(); - delete chanStream[i]; - } + oldStream->finish(); + delete oldStream; + } - w->seek(8,SEEK_SET); - for (int i=0; iwriteI(chanStreamOff[i]); - } + for (int i=0; itell(); + logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); + w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); + chanStream[i]->finish(); + delete chanStream[i]; + } - logD("delay popularity:"); - for (int i=0; i<16; i++) { - w->writeC(sortedDelay[i]); - if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); - } + w->seek(8,SEEK_SET); + for (int i=0; iwriteI(chanStreamOff[i]); + } - logD("command popularity:"); - for (int i=0; i<16; i++) { - w->writeC(sortedCmd[i]); - if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); - } - } else { - if (!playing) { - w->writeText(">> END\n"); - } else { - w->writeText(">> LOOP 0\n"); - } + logD("delay popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedDelay[i]); + if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); + } + + logD("command popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedCmd[i]); + if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); } remainingLoops=-1; diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 7808806ab..12238cfe2 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -251,13 +251,15 @@ enum DivDispatchCmds { DIV_CMD_POWERNOISE_COUNTER_LOAD, // (which, val) DIV_CMD_POWERNOISE_IO_WRITE, // (port, value) - + DIV_CMD_DAVE_HIGH_PASS, DIV_CMD_DAVE_RING_MOD, DIV_CMD_DAVE_SWAP_COUNTERS, DIV_CMD_DAVE_LOW_PASS, DIV_CMD_DAVE_CLOCK_DIV, + DIV_CMD_MINMOD_ECHO, + DIV_CMD_MAX }; @@ -441,6 +443,8 @@ enum DivMemoryEntryType { DIV_MEMORY_WAVE_RAM, DIV_MEMORY_WAVE_STATIC, DIV_MEMORY_ECHO, + DIV_MEMORY_N163_LOAD, + DIV_MEMORY_N163_PLAY, DIV_MEMORY_BANK0, DIV_MEMORY_BANK1, DIV_MEMORY_BANK2, @@ -456,6 +460,25 @@ struct DivMemoryEntry { String name; int asset; size_t begin, end; + DivMemoryEntry(DivMemoryEntryType t, String n, int a, size_t b, size_t e): + type(t), + name(n), + asset(a), + begin(b), + end(e) {} + DivMemoryEntry(): + type(DIV_MEMORY_FREE), + name(""), + asset(-1), + begin(0), + end(0) {} +}; + +enum DivMemoryWaveView: unsigned char { + DIV_MEMORY_WAVE_NONE=0, + DIV_MEMORY_WAVE_4BIT, // Namco 163 + DIV_MEMORY_WAVE_6BIT, // Virtual Boy + DIV_MEMORY_WAVE_8BIT_SIGNED, // SCC }; struct DivMemoryComposition { @@ -463,6 +486,14 @@ struct DivMemoryComposition { String name; size_t capacity; size_t used; + const unsigned char* memory; + DivMemoryWaveView waveformView; + DivMemoryComposition(): + name(""), + capacity(0), + used(0), + memory(NULL), + waveformView(DIV_MEMORY_WAVE_NONE) {} }; class DivEngine; diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8118c4a54..0a88e5407 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -81,10 +81,13 @@ #include "platform/k053260.h" #include "platform/ted.h" #include "platform/c140.h" +#include "platform/gbadma.h" +#include "platform/gbaminmod.h" #include "platform/pcmdac.h" #include "platform/esfm.h" #include "platform/powernoise.h" #include "platform/dave.h" +#include "platform/nds.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -307,6 +310,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do } else { ((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1); } + ((DivPlatformNES*)dispatch)->set5E01(false); break; case DIV_SYSTEM_C64_6581: dispatch=new DivPlatformC64; @@ -644,11 +648,22 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformC140; ((DivPlatformC140*)dispatch)->set219(true); break; + case DIV_SYSTEM_GBA_DMA: + dispatch=new DivPlatformGBADMA; + break; + case DIV_SYSTEM_GBA_MINMOD: + dispatch=new DivPlatformGBAMinMod; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; case DIV_SYSTEM_ESFM: dispatch=new DivPlatformESFM; + if (isRender) { + ((DivPlatformESFM*)dispatch)->setFast(eng->getConfInt("esfmCoreRender",0)); + } else { + ((DivPlatformESFM*)dispatch)->setFast(eng->getConfInt("esfmCore",0)); + } break; case DIV_SYSTEM_POWERNOISE: dispatch=new DivPlatformPowerNoise; @@ -656,6 +671,18 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_DAVE: dispatch=new DivPlatformDave; break; + case DIV_SYSTEM_NDS: + dispatch=new DivPlatformNDS; + break; + case DIV_SYSTEM_5E01: + dispatch=new DivPlatformNES; + if (isRender) { + ((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCoreRender",0)==1); + } else { + ((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1); + } + ((DivPlatformNES*)dispatch)->set5E01(true); + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ee254c2c8..05387fd8f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -105,6 +105,14 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul return "E4xx: Set vibrato range"; case 0xe5: return "E5xx: Set pitch (80: center)"; + case 0xe6: + return "E6xy: Quick legato (x: time (0-7 up; 8-F down); y: semitones)"; + case 0xe7: + return "E7xx: Macro release"; + case 0xe8: + return "E8xy: Quick legato up (x: time; y: semitones)"; + case 0xe9: + return "E9xy: Quick legato down (x: time; y: semitones)"; case 0xea: return "EAxx: Legato"; case 0xeb: @@ -137,6 +145,12 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul return "F9xx: Single tick volume slide down"; case 0xfa: return "FAxx: Fast volume slide (0y: down; x0: up)"; + case 0xfc: + return "FCxx: Note release"; + case 0xfd: + return "FDxx: Set virtual tempo numerator"; + case 0xfe: + return "FExx: Set virtual tempo denominator"; case 0xff: return "FFxx: Stop song"; default: @@ -974,7 +988,10 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_GA20 || i->type==DIV_INS_K053260 || i->type==DIV_INS_C140 || - i->type==DIV_INS_C219) { + i->type==DIV_INS_C219 || + i->type==DIV_INS_NDS || + i->type==DIV_INS_GBA_DMA || + i->type==DIV_INS_GBA_MINMOD) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } @@ -1679,7 +1696,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { runMidiTime(cycles); } if (oldOrder!=curOrder) break; - if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break; + if (ticks-((tempoAccum+virtualTempoN)/MAX(1,virtualTempoD))<1 && curRow>=goalRow) break; } for (int i=0; isetSkipRegisterWrites(false); if (goal>0 || goalRow>0) { @@ -1687,6 +1704,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { } for (int i=0; ispeeds; + virtualTempoN=curSubSong->virtualTempoN; + virtualTempoD=curSubSong->virtualTempoD; firstTick=false; shallStop=false; shallStopSched=false; @@ -2363,6 +2383,21 @@ float DivEngine::getCurHz() { return divider; } +short DivEngine::getVirtualTempoN() { + return virtualTempoN; +} + +short DivEngine::getVirtualTempoD() { + return virtualTempoD; +} + +void DivEngine::virtualTempoChanged() { + BUSY_BEGIN; + virtualTempoN=curSubSong->virtualTempoN; + virtualTempoD=curSubSong->virtualTempoD; + BUSY_END; +} + int DivEngine::getTotalSeconds() { return totalSeconds; } @@ -4013,11 +4048,13 @@ bool DivEngine::init() { return true; } -bool DivEngine::quit() { +bool DivEngine::quit(bool saveConfig) { deinitAudioBackend(); quitDispatch(); - logI("saving config."); - saveConf(); + if (saveConfig) { + logI("saving config."); + saveConf(); + } active=false; for (int i=0; i delayed; int note, oldNote, lastIns, pitch, portaSpeed, portaNote; - int volume, volSpeed, cut, rowDelay, volMax; + int volume, volSpeed, cut, legatoDelay, legatoTarget, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; - unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta; + unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta, cutType; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff, releasing; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp; bool wentThroughNote, goneThroughNote; @@ -123,6 +123,8 @@ struct DivChannelState { volume(0x7f00), volSpeed(0), cut(-1), + legatoDelay(-1), + legatoTarget(0), rowDelay(0), volMax(0), delayOrder(0), @@ -147,6 +149,7 @@ struct DivChannelState { panRR(0), lastVibrato(0), lastPorta(0), + cutType(0), doNote(false), legato(false), portaStop(false), @@ -441,6 +444,7 @@ class DivEngine { int curMidiTimePiece, curMidiTimeCode; unsigned char extValue, pendingMetroTick; DivGroovePattern speeds; + short virtualTempoN, virtualTempoD; short tempoAccum; DivStatusView view; DivHaltPositions haltOn; @@ -542,10 +546,10 @@ class DivEngine { void testFunction(); bool loadDMF(unsigned char* file, size_t len); - bool loadFur(unsigned char* file, size_t len); + bool loadFur(unsigned char* file, size_t len, int variantID=0); bool loadMod(unsigned char* file, size_t len); bool loadS3M(unsigned char* file, size_t len); - bool loadFTM(unsigned char* file, size_t len); + bool loadFTM(unsigned char* file, size_t len, bool dnft, bool dnftSig, bool eft); bool loadFC(unsigned char* file, size_t len); void loadDMP(SafeReader& reader, std::vector& ret, String& stripPath); @@ -630,9 +634,13 @@ class DivEngine { void createNew(const char* description, String sysName, bool inBase64=true); void createNewFromDefaults(); // load a file. - bool load(unsigned char* f, size_t length); + bool load(unsigned char* f, size_t length, const char* nameHint=NULL); // play a binary command stream. bool playStream(unsigned char* f, size_t length); + // get the playing stream. + DivCSPlayer* getStreamPlayer(); + // destroy command stream player. + bool killStream(); // save as .dmf. SafeWriter* saveDMF(unsigned char version); // save as .fur. @@ -651,7 +659,7 @@ class DivEngine { // dump to ZSM. SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true, bool optimize=true); // dump command stream. - SafeWriter* saveCommand(bool binary=false); + SafeWriter* saveCommand(); // export to text SafeWriter* saveText(bool separatePatterns=true); // export to an audio file @@ -886,6 +894,13 @@ class DivEngine { // get current Hz float getCurHz(); + // get virtual tempo + short getVirtualTempoN(); + short getVirtualTempoD(); + + // tell engine about virtual tempo changes + void virtualTempoChanged(); + // get time int getTotalTicks(); // 1/1000000th of a second int getTotalSeconds(); @@ -1248,7 +1263,7 @@ class DivEngine { void everythingOK(); // terminate the engine. - bool quit(); + bool quit(bool saveConfig=true); unsigned char* yrw801ROM; unsigned char* tg100ROM; @@ -1329,6 +1344,8 @@ class DivEngine { curMidiTimeCode(0), extValue(0), pendingMetroTick(0), + virtualTempoN(150), + virtualTempoD(150), tempoAccum(0), view(DIV_STATUS_NOTHING), haltOn(DIV_HALT_NONE), diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index f55e10da8..d226a54e8 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -19,6 +19,69 @@ #include "fileOpsCommon.h" +// known version numbers: +// - 27: v1.1.7 +// - current format version +// - adds sample start/end points +// - 26: v1.1.3 +// - changes height of FDS wave to 6-bit (it was 4-bit before) +// - 25: v1.1 +// - adds pattern names (in a rather odd way) +// - v1.1.4 fixes these but breaks old songs (yeah) +// - introduces SMS+OPLL system +// - 24: v0.12/0.13/1.0 +// - changes pattern length from char to int, probably to allow for size 256 +// - 23: ??? +// - what happened here? +// - 20: v11.1 (?) +// - E5xx effect range is now ±1 semitone +// - 19: v11 +// - introduces Arcade system +// - changes to the FM instrument format due to YMU759 being dropped +// - 18: v10 +// - radically changes STD instrument for Game Boy +// - 17: v9 +// - changes C64 volIsCutoff flag from int to char for unknown reasons +// - 16: v8 (?) +// - introduces C64 system +// - 15: v7 (?) +// - 14: v6 (?) +// - introduces NES system +// - changes macro and wave values from char to int +// - 13: v5.1 +// - introduces PC Engine system in later version (how?) +// - stores highlight in file +// - 12: v5 (?) +// - introduces Game Boy system +// - introduces wavetables +// - 11: ??? +// - introduces Sega Master System +// - custom Hz support +// - instrument type (FM/STD) present +// - prior to this version the instrument type depended on the system +// - 10: ??? +// - introduces multiple effect columns +// - 9: v3.9 +// - introduces Genesis system +// - introduces system number +// - patterns now stored in current known format +// - 8: ??? +// - only used in the Medivo YMU cover +// - 7: ??? +// - only present in a later version of First.dmf +// - pattern format changes: empty field is 0xFF instead of 0x80 +// - instrument now stored in pattern +// - 5: BETA 3 +// - adds arpeggio tick +// - 4: BETA 2 +// - possibly adds instrument number (stored in channel)? +// - cannot confirm as I don't have any version 4 modules +// - 3: BETA 1 +// - possibly the first version that could save +// - basic format, no system number, 16 instruments, one speed, YMU759-only +// - patterns were stored in a different format (chars instead of shorts) and no instrument +// - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth a lot + static double samplePitches[11]={ 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, 1, @@ -1033,11 +1096,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.systemFlags[0].set("dpcmMode",false); } - // C64 no key priority, reset time and multiply relative + // C64 no key priority, reset time, multiply relative and macro race if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { ds.systemFlags[0].set("keyPriority",false); ds.systemFlags[0].set("initResetTime",1); ds.systemFlags[0].set("multiplyRel",true); + ds.systemFlags[0].set("macroRace",true); } // OPM broken pitch @@ -1562,355 +1626,3 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { saveLock.unlock(); return w; } - -static const char* trueFalse[2]={ - "no", "yes" -}; - -static const char* gbEnvDir[2]={ - "down", "up" -}; - -static const char* notes[12]={ - "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" -}; - -static const char* notesNegative[12]={ - "c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_" -}; - -static const char* sampleLoopModes[4]={ - "forward", "backward", "ping-pong", "invalid" -}; - -void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) { - if ((m.open&6)==0 && m.len<1) return; - if (!wroteMacroHeader) { - w->writeText("- macros:\n"); - wroteMacroHeader=true; - } - w->writeText(fmt::sprintf(" - %s:",name)); - int len=m.len; - switch (m.open&6) { - case 2: - len=16; - w->writeText(" [ADSR]"); - break; - case 4: - len=16; - w->writeText(" [LFO]"); - break; - } - if (m.mode) { - w->writeText(fmt::sprintf(" [MODE %d]",m.mode)); - } - if (m.delay>0) { - w->writeText(fmt::sprintf(" [DELAY %d]",m.delay)); - } - if (m.speed>1) { - w->writeText(fmt::sprintf(" [SPEED %d]",m.speed)); - } - for (int i=0; iwriteText(" |"); - } - if (i==m.rel) { - w->writeText(" /"); - } - w->writeText(fmt::sprintf(" %d",m.val[i])); - } - w->writeText("\n"); -} - -SafeWriter* DivEngine::saveText(bool separatePatterns) { - saveLock.lock(); - - SafeWriter* w=new SafeWriter; - w->init(); - - w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION)); - w->writeText(fmt::sprintf("- name: %s\n",song.name)); - w->writeText(fmt::sprintf("- author: %s\n",song.author)); - w->writeText(fmt::sprintf("- album: %s\n",song.category)); - w->writeText(fmt::sprintf("- system: %s\n",song.systemName)); - w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning)); - - w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen)); - w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen)); - w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen)); - - w->writeText("# Sound Chips\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("- %s\n",getSystemName(song.system[i]))); - w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i])); - w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i])); - w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i])); - w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i])); - - String sysFlags=song.systemFlags[i].toString(); - - if (!sysFlags.empty()) { - w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags)); - } - } - - if (!song.notes.empty()) { - w->writeText("\n# Song Comments\n\n"); - w->writeText(song.notes); - } - - w->writeText("\n# Instruments\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name)); - - w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); - - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { - w->writeText("- FM parameters:\n"); - w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); - w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); - w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms)); - w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); - w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); - w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); - w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); - w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); - w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); - w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); - w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); - w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); - - for (int j=0; jfm.ops; j++) { - DivInstrumentFM::Operator& op=ins->fm.op[j]; - - w->writeText(fmt::sprintf(" - operator %d:\n",j)); - w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0])); - w->writeText(fmt::sprintf(" - AM: %d\n",op.am)); - w->writeText(fmt::sprintf(" - AR: %d\n",op.ar)); - w->writeText(fmt::sprintf(" - DR: %d\n",op.dr)); - w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult)); - w->writeText(fmt::sprintf(" - RR: %d\n",op.rr)); - w->writeText(fmt::sprintf(" - SL: %d\n",op.sl)); - w->writeText(fmt::sprintf(" - TL: %d\n",op.tl)); - w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2)); - w->writeText(fmt::sprintf(" - RS: %d\n",op.rs)); - w->writeText(fmt::sprintf(" - DT: %d\n",op.dt)); - w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r)); - w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv)); - w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam)); - w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb)); - w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt)); - w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl)); - w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus)); - w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib)); - w->writeText(fmt::sprintf(" - WS: %d\n",op.ws)); - w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr)); - w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs)); - } - } - - if (ins->type==DIV_INS_ESFM) { - w->writeText("- ESFM parameters:\n"); - w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); - - for (int j=0; jfm.ops; j++) { - DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; - - w->writeText(fmt::sprintf(" - operator %d:\n",j)); - w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay)); - w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl)); - w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn)); - w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0])); - w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0])); - w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct)); - w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt)); - w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0])); - } - } - - if (ins->type==DIV_INS_GB) { - w->writeText("- Game Boy parameters:\n"); - w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); - w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); - w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); - w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); - w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); - w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0])); - if (ins->gb.hwSeqLen>0) { - w->writeText(" - hardware sequence:\n"); - for (int j=0; jgb.hwSeqLen; j++) { - w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data)); - } - } - } - - bool header=false; - writeTextMacro(w,ins->std.volMacro,"vol",header); - writeTextMacro(w,ins->std.arpMacro,"arp",header); - writeTextMacro(w,ins->std.dutyMacro,"duty",header); - writeTextMacro(w,ins->std.waveMacro,"wave",header); - writeTextMacro(w,ins->std.pitchMacro,"pitch",header); - writeTextMacro(w,ins->std.panLMacro,"panL",header); - writeTextMacro(w,ins->std.panRMacro,"panR",header); - writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header); - writeTextMacro(w,ins->std.ex1Macro,"ex1",header); - writeTextMacro(w,ins->std.ex2Macro,"ex2",header); - writeTextMacro(w,ins->std.ex3Macro,"ex3",header); - writeTextMacro(w,ins->std.ex4Macro,"ex4",header); - writeTextMacro(w,ins->std.ex5Macro,"ex5",header); - writeTextMacro(w,ins->std.ex6Macro,"ex6",header); - writeTextMacro(w,ins->std.ex7Macro,"ex7",header); - writeTextMacro(w,ins->std.ex8Macro,"ex8",header); - writeTextMacro(w,ins->std.algMacro,"alg",header); - writeTextMacro(w,ins->std.fbMacro,"fb",header); - writeTextMacro(w,ins->std.fmsMacro,"fms",header); - writeTextMacro(w,ins->std.amsMacro,"ams",header); - - // TODO: the rest - w->writeText("\n"); - } - - w->writeText("\n# Wavetables\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1)); - for (int j=0; j<=wave->len; j++) { - w->writeText(fmt::sprintf(" %d",wave->data[j])); - } - w->writeText("\n"); - } - - w->writeText("\n# Samples\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name)); - - w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth)); - w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen())); - w->writeText(fmt::sprintf("- samples: %d\n",sample->samples)); - w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate)); - w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate)); - w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0])); - if (sample->loop) { - w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart)); - w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd)); - w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3])); - } - w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0])); - w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0])); - - // TODO' render matrix - unsigned char* buf=(unsigned char*)sample->getCurBuf(); - unsigned int bufLen=sample->getCurBufLen(); - w->writeText("\n```"); - for (unsigned int i=0; iwriteText(fmt::sprintf("\n%.8X:",i)); - w->writeText(fmt::sprintf(" %.2X",buf[i])); - } - w->writeText("\n```\n\n"); - } - - w->writeText("\n# Subsongs\n\n"); - - for (size_t i=0; iwriteText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name)); - - w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz)); - w->writeText(fmt::sprintf("- speeds:")); - for (int j=0; jspeeds.len; j++) { - w->writeText(fmt::sprintf(" %d",s->speeds.val[j])); - } - w->writeText("\n"); - w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD)); - w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase)); - w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen)); - w->writeText(fmt::sprintf("\norders:\n```\n")); - - for (int j=0; jordersLen; j++) { - w->writeText(fmt::sprintf("%.2X |",j)); - for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); - } - w->writeText("\n"); - } - w->writeText("```\n\n## Patterns\n\n"); - - if (separatePatterns) { - w->writeText("TODO: separate patterns\n\n"); - } else { - for (int j=0; jordersLen; j++) { - w->writeText(fmt::sprintf("----- ORDER %.2X\n",j)); - - for (int k=0; kpatLen; k++) { - w->writeText(fmt::sprintf("%.2X ",k)); - - for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); - - int note=p->data[k][0]; - int octave=p->data[k][1]; - - if (note==0 && octave==0) { - w->writeText("|... "); - } else if (note==100) { - w->writeText("|OFF "); - } else if (note==101) { - w->writeText("|=== "); - } else if (note==102) { - w->writeText("|REL "); - } else if ((octave>9 && octave<250) || note>12) { - w->writeText("|??? "); - } else { - if (octave>=128) octave-=256; - if (note>11) { - note-=12; - octave++; - } - w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave)); - } - - if (p->data[k][2]==-1) { - w->writeText(".. "); - } else { - w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff)); - } - - if (p->data[k][3]==-1) { - w->writeText(".."); - } else { - w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff)); - } - - for (int m=0; mpat[l].effectCols; m++) { - if (p->data[k][4+(m<<1)]==-1) { - w->writeText(" .."); - } else { - w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff)); - } - if (p->data[k][5+(m<<1)]==-1) { - w->writeText(".."); - } else { - w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff)); - } - } - } - - w->writeText("\n"); - } - } - } - - } - - saveLock.unlock(); - return w; -} diff --git a/src/engine/fileOps/fileOpsCommon.cpp b/src/engine/fileOps/fileOpsCommon.cpp index 56046b078..52e1d31be 100644 --- a/src/engine/fileOps/fileOpsCommon.cpp +++ b/src/engine/fileOps/fileOpsCommon.cpp @@ -19,10 +19,10 @@ #include "fileOpsCommon.h" -bool DivEngine::load(unsigned char* f, size_t slen) { +bool DivEngine::load(unsigned char* f, size_t slen, const char* nameHint) { unsigned char* file; size_t len; - if (slen<18) { + if (slen<21) { logE("too small!"); lastError="file is too small"; delete[] f; @@ -31,6 +31,21 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (!systemsRegistered) registerSystems(); + // step 0: get extension of file + String extS; + if (nameHint!=NULL) { + const char* ext=strrchr(nameHint,'.'); + if (ext!=NULL) { + for (; *ext; ext++) { + char i=*ext; + if (i>='A' && i<='Z') { + i+='a'-'A'; + } + extS+=i; + } + } + } + // step 1: try loading as a zlib-compressed file logD("trying zlib..."); try { @@ -128,9 +143,13 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (memcmp(file,DIV_DMF_MAGIC,16)==0) { return loadDMF(file,len); } else if (memcmp(file,DIV_FTM_MAGIC,18)==0) { - return loadFTM(file,len); + return loadFTM(file,len,(extS==".dnm"),false,(extS==".eft")); + } else if (memcmp(file,DIV_DNM_MAGIC,21)==0) { + return loadFTM(file,len,true,true,false); } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); + } else if (memcmp(file,DIV_FUR_MAGIC_DS0,16)==0) { + return loadFur(file,len,DIV_FUR_VARIANT_B); } else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) { return loadFC(file,len); } diff --git a/src/engine/fileOps/fileOpsCommon.h b/src/engine/fileOps/fileOpsCommon.h index baceadaf3..1ba6eff8a 100644 --- a/src/engine/fileOps/fileOpsCommon.h +++ b/src/engine/fileOps/fileOpsCommon.h @@ -49,6 +49,14 @@ struct NotZlibException { #define DIV_DMF_MAGIC ".DelekDefleMask." #define DIV_FUR_MAGIC "-Furnace module-" #define DIV_FTM_MAGIC "FamiTracker Module" +#define DIV_DNM_MAGIC "Dn-FamiTracker Module" #define DIV_FC13_MAGIC "SMOD" #define DIV_FC14_MAGIC "FC14" #define DIV_S3M_MAGIC "SCRM" + +#define DIV_FUR_MAGIC_DS0 "Furnace-B module" + +enum DivFurVariants: int { + DIV_FUR_VARIANT_VANILLA=0, + DIV_FUR_VARIANT_B=1, +}; diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index 152c7c6d3..e687e73f0 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -17,14 +17,174 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +// most of this code written by LTVA +// ported to Furnace by tildearrow + +// portions apparently taken from FamiTracker source, which is under GPLv2+ + +// TODO: +// - audit for CVEs +// - format code? + #include "fileOpsCommon.h" #define CHECK_BLOCK_VERSION(x) \ if (blockVersion>x) { \ - logW("incompatible block version %d for %s!",blockVersion,blockName); \ + logW("incompatible block version %d for %s!", blockVersion, blockName); \ } +enum FTEffects { + FT_EF_NONE = 0, + FT_EF_SPEED, // Speed + FT_EF_JUMP, // Jump + FT_EF_SKIP, // Skip + FT_EF_HALT, // Halt + FT_EF_VOLUME, // Volume + FT_EF_PORTAMENTO, // Porta on + FT_EF_PORTAOFF, // Porta off unused + FT_EF_SWEEPUP, // Sweep up + FT_EF_SWEEPDOWN, // Sweep down + FT_EF_ARPEGGIO, // Arpeggio + FT_EF_VIBRATO, // Vibrato + FT_EF_TREMOLO, // Tremolo + FT_EF_PITCH, // Pitch + FT_EF_DELAY, // Note delay + FT_EF_DAC, // DAC setting + FT_EF_PORTA_UP, // Portamento up + FT_EF_PORTA_DOWN, // Portamento down + FT_EF_DUTY_CYCLE, // Duty cycle + FT_EF_SAMPLE_OFFSET, // Sample offset + FT_EF_SLIDE_UP, // Slide up + FT_EF_SLIDE_DOWN, // Slide down + FT_EF_VOLUME_SLIDE, // Volume slide + FT_EF_NOTE_CUT, // Note cut + FT_EF_RETRIGGER, // DPCM retrigger + FT_EF_DELAYED_VOLUME, // // // Delayed channel volume + FT_EF_FDS_MOD_DEPTH, // FDS modulation depth + FT_EF_FDS_MOD_SPEED_HI, // FDS modulation speed hi + FT_EF_FDS_MOD_SPEED_LO, // FDS modulation speed lo + FT_EF_DPCM_PITCH, // DPCM Pitch + FT_EF_SUNSOFT_ENV_TYPE, // Sunsoft envelope type + FT_EF_SUNSOFT_ENV_HI, // Sunsoft envelope high + FT_EF_SUNSOFT_ENV_LO, // Sunsoft envelope low + FT_EF_SUNSOFT_NOISE, // // // 050B Sunsoft noise period + FT_EF_VRC7_PORT, // // // 050B VRC7 custom patch port + FT_EF_VRC7_WRITE, // // // 050B VRC7 custom patch write + FT_EF_NOTE_RELEASE, // // // Delayed release + FT_EF_GROOVE, // // // Groove + FT_EF_TRANSPOSE, // // // Delayed transpose + FT_EF_N163_WAVE_BUFFER, // // // N163 wave buffer + FT_EF_FDS_VOLUME, // // // FDS volume envelope + FT_EF_FDS_MOD_BIAS, // // // FDS auto-FM bias + FT_EF_PHASE_RESET, // Reset waveform phase without retriggering note (VRC6-only so far) + FT_EF_HARMONIC, // Multiply the note pitch by an integer + FT_EF_TARGET_VOLUME_SLIDE, // // !! Target volume slide + + FT_EF_COUNT +}; + const int ftEffectMap[]={ + -1, // none + 0x0f, + 0x0b, + 0x0d, + 0xff, + -1, // volume? not supported in Furnace yet + 0x03, + 0x03, // unused? + 0x13, 0x14, 0x00, 0x04, 0x07, 0xe5, 0xed, 0x11, + 0x01, // porta up + 0x02, // porta down + 0x12, + 0x90, // sample offset - not supported yet + 0xe1, // Slide up + 0xe2, // Slide down + 0x0a, 0xec, 0x0c, + -1, // delayed volume - not supported yet + 0x11, // FDS modulation depth + 0x12, // FDS modulation speed hi + 0x13, // FDS modulation speed lo + 0x20, // DPCM pitch + 0x22, // Sunsoft envelope type + 0x24, // Sunsoft envelope high + 0x23, // Sunsoft envelope low + 0x21, // 050B Sunsoft noise period + -1, // VRC7 "custom patch port" - not supported? + -1, // VRC7 "custom patch write" + 0xfc, // delayed release + 0x09, // select groove + 0xe6, // delayed note transpose + 0x11, // Namco 163 wave RAM offset + -1, // FDS vol env - not supported + -1, // FDS auto FM - not supported yet + -1, // phase reset - not supported + -1, // harmonic - not supported + -1, // target volume slide - not supported +}; + +enum EFTEffects { + EFT_EF_NONE = 0, + EFT_EF_SPEED, // Speed + EFT_EF_JUMP, // Jump + EFT_EF_SKIP, // Skip + EFT_EF_HALT, // Halt + EFT_EF_VOLUME, // Volume + EFT_EF_PORTAMENTO, // Porta on + EFT_EF_PORTAOFF, // Porta off unused + EFT_EF_SWEEPUP, // Sweep up + EFT_EF_SWEEPDOWN, // Sweep down + EFT_EF_ARPEGGIO, // Arpeggio + EFT_EF_VIBRATO, // Vibrato + EFT_EF_TREMOLO, // Tremolo + EFT_EF_PITCH, // Pitch + EFT_EF_DELAY, // Note delay + EFT_EF_DAC, // DAC setting + EFT_EF_PORTA_UP, // Portamento up + EFT_EF_PORTA_DOWN, // Portamento down + EFT_EF_DUTY_CYCLE, // Duty cycle + EFT_EF_SAMPLE_OFFSET, // Sample offset + EFT_EF_SLIDE_UP, // Slide up + EFT_EF_SLIDE_DOWN, // Slide down + EFT_EF_VOLUME_SLIDE, // Volume slide + EFT_EF_NOTE_CUT, // Note cut + EFT_EF_RETRIGGER, // DPCM retrigger + EFT_EF_DELAYED_VOLUME, // // // Delayed channel volume + EFT_EF_FDS_MOD_DEPTH, // FDS modulation depth + EFT_EF_FDS_MOD_SPEED_HI, // FDS modulation speed hi + EFT_EF_FDS_MOD_SPEED_LO, // FDS modulation speed lo + EFT_EF_DPCM_PITCH, // DPCM Pitch + EFT_EF_SUNSOFT_ENV_TYPE, // Sunsoft envelope type + EFT_EF_SUNSOFT_ENV_HI, // Sunsoft envelope high + EFT_EF_SUNSOFT_ENV_LO, // Sunsoft envelope low + EFT_EF_SUNSOFT_NOISE, // // // 050B Sunsoft noise period + EFT_EF_AY8930_PULSE_WIDTH, // // // AY8930 pulse width + EFT_EF_AY8930_AND_MASK, // // // AY8930 noise AND mask + EFT_EF_AY8930_OR_MASK, // // // AY8930 noise OR mask + EFT_EF_AY8930_VOL, // // // AY8930 extra volume bit + EFT_EF_VRC7_PORT, // // // 050B VRC7 custom patch port + EFT_EF_VRC7_WRITE, // // // 050B VRC7 custom patch write + EFT_EF_NOTE_RELEASE, // // // Delayed release + EFT_EF_GROOVE, // // // Groove + EFT_EF_TRANSPOSE, // // // Delayed transpose + EFT_EF_N163_WAVE_BUFFER, // // // N163 wave buffer + EFT_EF_FDS_VOLUME, // // // FDS volume envelope + EFT_EF_FDS_MOD_BIAS, // // // FDS auto-FM bias + EFT_EF_PHASE_RESET, // Reset waveform phase without retriggering note (VRC6-only so far) + EFT_EF_HARMONIC, // Multiply the note pitch by an integer + EFT_EF_PWM, // // // Pulse width modulation effect + EFT_EF_VOLUME_OFFSET, // // // Relative volume change + EFT_EF_SAA_NOISE_MODE, // // // SAA1099 noise mode + EFT_EF_SID_FILTER_RESONANCE, // // // SID filter resonance + EFT_EF_SID_FILTER_CUTOFF_HI, // // // SID filter cutoff hi + EFT_EF_SID_FILTER_CUTOFF_LO, // // // SID filter cutoff lo + EFT_EF_SID_FILTER_MODE, // // // SID filter mode + EFT_EF_SID_ENVELOPE, // // // SID envelope parameters + EFT_EF_SID_RING, // // // SID ringmod + + EFT_EF_COUNT +}; + +const int eftEffectMap[] = { -1, // none 0x0f, 0x0b, @@ -45,288 +205,704 @@ const int ftEffectMap[]={ 0x02, // porta down 0x12, 0x90, // sample offset - not supported yet - 0xe1, - 0xe2, + 0xe1, // Slide up + 0xe2, // Slide down 0x0a, 0xec, 0x0c, - -1, // delayed volume - not supported yet - 0x11, // FDS - 0x12, - 0x13, - 0x20, // DPCM pitch - 0x22, // 5B - 0x24, - 0x23, - 0x21, - -1, // VRC7 "custom patch port" - not supported? - -1, // VRC7 "custom patch write" - -1, // release - not supported yet - 0x09, // select groove - -1, // transpose - not supported - 0x10, // Namco 163 - -1, // FDS vol env - not supported - -1, // FDS auto FM - not supported yet - -1, // phase reset - not supported - -1, // harmonic - not supported + -1, // delayed volume - not supported yet + 0x11, // FDS modulation depth + 0x12, // FDS modulation speed hi + 0x13, // FDS modulation speed lo + 0x20, // DPCM pitch + 0x22, // Sunsoft envelope type + 0x24, // Sunsoft envelope high + 0x23, // Sunsoft envelope low + 0x21, // 050B Sunsoft noise period + 0x12, // // // AY8930 pulse width + 0x27, // // // AY8930 noise AND mask + 0x28, // // // AY8930 noise OR mask + 0x100, // // // AY8930 extra volume bit + -1, // VRC7 "custom patch port" - not supported? + -1, // VRC7 "custom patch write" + 0xfc, // delayed release + 0x09, // select groove + 0xe6, // delayed note transpose + 0x11, // Namco 163 wave RAM offset + -1, // FDS vol env - not supported + -1, // FDS auto FM - not supported yet + -1, // phase reset - not supported + -1, // harmonic - not supported + -1, // // // Pulse width modulation effect + -1, // // // Relative volume change + -1, // // // SAA1099 noise mode + 0x13, // // // SID filter resonance + 0x40, // // // SID filter cutoff hi + 0x40, // // // SID filter cutoff lo + 0x14, // // // SID filter mode + -1, // // // SID envelope parameters + -1, // // // SID ringmod }; -constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int); +const int eff_conversion_050[][2] = { + {FT_EF_SUNSOFT_NOISE, FT_EF_NOTE_RELEASE}, + {FT_EF_VRC7_PORT, FT_EF_GROOVE}, + {FT_EF_VRC7_WRITE, FT_EF_TRANSPOSE}, + {FT_EF_NOTE_RELEASE, FT_EF_N163_WAVE_BUFFER}, + {FT_EF_GROOVE, FT_EF_FDS_VOLUME}, + {FT_EF_TRANSPOSE, FT_EF_FDS_MOD_BIAS}, + {FT_EF_N163_WAVE_BUFFER, FT_EF_SUNSOFT_NOISE}, + {FT_EF_FDS_VOLUME, FT_EF_VRC7_PORT}, + {FT_EF_FDS_MOD_BIAS, FT_EF_VRC7_WRITE}, + {0xFF, 0xFF}, // end mark +}; -bool DivEngine::loadFTM(unsigned char* file, size_t len) { - SafeReader reader=SafeReader(file,len); - warnings=""; +constexpr int ftEffectMapSize = sizeof(ftEffectMap) / sizeof(int); + +int convertMacros2A03[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY}; +int convertMacrosVRC6[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY}; +int convertMacrosN163[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_WAVE}; +int convertMacros5B[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY}; + +int convertMacrosSID[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_WAVE}; + +int convert_vrc6_duties[4] = {1, 3, 7, 3}; + +void copyMacro(DivInstrument* ins, DivInstrumentMacro* from, int macro_type, int setting) { + DivInstrumentMacro* to = NULL; + + switch (ins->type) { + case DIV_INS_NES: { + if (convertMacros2A03[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacros2A03[macro_type]); + break; + } + case DIV_INS_VRC6: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacros2A03[macro_type]); + break; + } + case DIV_INS_N163: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacrosN163[macro_type]); + break; + } + case DIV_INS_AY: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacros5B[macro_type]); + break; + } + case DIV_INS_C64: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacrosSID[macro_type]); + break; + } + default: + break; + } + + if (to == NULL) + return; + + for (int i = 0; i < 256; i++) { + to->val[i] = from->val[i]; + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_ARP) { + if (setting == 0) // absolute + { + if (to->val[i] > 0x60) { + int temp = to->val[i]; + to->val[i] = -1 * (0xff - temp + 1); // 2s complement integer my beloved + } + } + + if (setting == 1) // fixed + { + to->val[i] |= (1 << 30); // 30th bit in Furnace arp macro marks fixed mode + } + } + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_PITCH) { + if (setting == 0 || setting == 1) // relative/absolute + { + int temp = to->val[i]; + + if (temp < 0x80) { + to->val[i] = -1 * temp; + } else { + to->val[i] = (0x100 - temp); + } + } + } + + if ((DivMacroType)convertMacrosN163[macro_type] == DIV_MACRO_WAVE && ins->type == DIV_INS_N163) { + // pfffff + } + + if ((DivMacroType)convertMacrosN163[macro_type] == DIV_MACRO_PITCH && (ins->type == DIV_INS_N163 || ins->type == DIV_INS_C64)) { + to->val[i] *= -1; // wtf is going on!!! + } + } + + to->len = from->len; + to->delay = from->delay; + to->lenMemory = from->lenMemory; + to->mode = from->mode; + to->rel = from->rel; + to->speed = from->speed; + to->loop = from->loop; + to->open = from->open; + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_ARP) { + if (setting == 1) // fixed + { + if (to->loop == 255) // no loop + { + to->len++; + to->val[to->len - 1] = 0; // return to orig pitch (relative mode, 0 offset) + } + } + } + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_PITCH) { + if (setting == 0) // relative + { + to->mode = 1; // setting relative mode + } + } + + if (ins->type == DIV_INS_AY && macro_type == 4) // S5B noise/mode macro combines noise freq and tone/env/noise settings, so we need to separate them into two macros + { + DivInstrumentMacro* wave = &ins->std.waveMacro; + to = &ins->std.dutyMacro; + + wave->len = to->len; + wave->delay = to->delay; + wave->lenMemory = to->lenMemory; + wave->mode = to->mode; + wave->rel = to->rel; + wave->speed = to->speed; + wave->loop = to->loop; + wave->open = to->open; + + for (int i = 0; i < to->len; i++) { + // ? ? ? ? + + logI("%02X", to->val[i]); + wave->val[i] = 0; + + int temp = 0; + + if (to->val[i] & 0b10000000) // noise + { + temp |= 2; + } + if (to->val[i] & 0b01000000) // tone + { + temp |= 1; + } + if (to->val[i] & 0b00100000) // envelope + { + temp |= 4; + } + + wave->val[i] = temp; + + // #define S5B_ENVL 0b10000000 + // #define S5B_TONE 0b01000000 + // #define S5B_NOIS 0b00100000 + + to->val[i] = to->val[i] & 31; + } + } +} + +bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_sig, bool eft) { + SafeReader reader = SafeReader(file, len); + warnings = ""; try { DivSong ds; String blockName; - unsigned char expansions=0; - unsigned int tchans=0; - unsigned int n163Chans=0; + unsigned int expansions = 0; + unsigned int tchans = 0; + unsigned int n163Chans = 0; bool hasSequence[256][8]; unsigned char sequenceIndex[256][8]; - unsigned int hilightA=4; - unsigned int hilightB=16; - double customHz=60; - - memset(hasSequence,0,256*8*sizeof(bool)); - memset(sequenceIndex,0,256*8); + unsigned char macro_types[256][8]; + std::vector> macros; + unsigned char map_channels[DIV_MAX_CHANS]; + unsigned int hilightA = 4; + unsigned int hilightB = 16; + double customHz = 60.0; - if (!reader.seek(18,SEEK_SET)) { + unsigned char fds_chan = 0xff; + unsigned char vrc6_saw_chan = 0xff; + unsigned char n163_chans[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + unsigned char vrc7_chans[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + // these two seem to go unused, but why? + unsigned char vrc6_chans[2] = {0xff, 0xff}; + unsigned char mmc5_chans[2] = {0xff, 0xff}; + + int total_chans = 0; + + memset(hasSequence, 0, 256 * 8 * sizeof(bool)); + memset(sequenceIndex, 0, 256 * 8); + memset(macro_types, 0, 256 * 8); + memset(map_channels, 0xfe, DIV_MAX_CHANS * sizeof(unsigned char)); + + for (int i = 0; i < 256; i++) { + std::vector mac; + + for (int j = 0; j < 8; j++) { + mac.push_back(DivInstrumentMacro(DIV_MACRO_VOL)); + } + + macros.push_back(mac); + } + + if (!reader.seek((dnft && dnft_sig) ? 21 : 18, SEEK_SET)) { logE("premature end of file!"); - lastError="incomplete file"; + lastError = "incomplete file"; delete[] file; return false; } - ds.version=(unsigned short)reader.readI(); - logI("module version %d (0x%.4x)",ds.version,ds.version); + ds.version = (unsigned short)reader.readI(); + logI("module version %d (0x%.4x)", ds.version, ds.version); - if (ds.version>0x0450) { - logE("incompatible version %x!",ds.version); - lastError="incompatible version"; + if ((ds.version > 0x0450 && !eft) || (eft && ds.version > 0x0460)) { + logE("incompatible version %x!", ds.version); + lastError = "incompatible version"; delete[] file; return false; } - for (DivSubSong* i: ds.subsong) { + for (DivSubSong* i : ds.subsong) { i->clearData(); delete i; } ds.subsong.clear(); - ds.linearPitch=0; + ds.linearPitch = 0; + + unsigned int pal = 0; while (true) { - blockName=reader.readString(3); - if (blockName=="END") { + blockName = reader.readString(3); + if (blockName == "END") { // end of module logD("end of data"); break; } // not the end - reader.seek(-3,SEEK_CUR); - blockName=reader.readString(16); - unsigned int blockVersion=(unsigned int)reader.readI(); - unsigned int blockSize=(unsigned int)reader.readI(); - size_t blockStart=reader.tell(); - - logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize); - if (blockName=="PARAMS") { + reader.seek(-3, SEEK_CUR); + blockName = reader.readString(16); + unsigned int blockVersion = (unsigned int)reader.readI(); + unsigned int blockSize = (unsigned int)reader.readI(); + size_t blockStart = reader.tell(); + + logD("reading block %s (version %d, %d bytes, position %x)", blockName, blockVersion, blockSize, reader.tell()); + if (blockName == "PARAMS") { // versions 7-9 don't change anything? CHECK_BLOCK_VERSION(9); - unsigned int oldSpeedTempo=0; - if (blockVersion<=1) { - oldSpeedTempo=reader.readI(); + unsigned int oldSpeedTempo = 0; + if (blockVersion <= 1) { + oldSpeedTempo = reader.readI(); } - if (blockVersion>=2) { - expansions=reader.readC(); + if (blockVersion >= 2) { + if (eft) { + if (blockVersion < 7) { + expansions = reader.readC(); + } else { + expansions = reader.readI(); + } + } else { + expansions = reader.readC(); + } } - tchans=reader.readI(); - unsigned int pal=reader.readI(); - if (blockVersion>=7) { - // advanced Hz control - int controlType=reader.readI(); - switch (controlType) { - case 1: - customHz=1000000.0/(double)reader.readI(); - break; - default: - reader.readI(); - break; + + tchans = reader.readI(); + + if (tchans == 5) { + expansions = 0; // This is strange. Sometimes expansion chip is set to 0xFF in files + } + + pal = reader.readI(); + if (!eft) { + if (blockVersion >= 7) { + // advanced Hz control + int controlType = reader.readI(); + switch (controlType) { + case 1: + customHz = 1000000.0 / (double)reader.readI(); + break; + default: + reader.readI(); + break; + } + } else { + customHz = reader.readI(); } } else { - customHz=reader.readI(); - } - unsigned int newVibrato=0; - bool sweepReset=false; - unsigned int speedSplitPoint=0; - if (blockVersion>=3) { - newVibrato=reader.readI(); - } - if (blockVersion>=9) { - sweepReset=reader.readI(); - } - if (blockVersion>=4 && blockVersion<7) { - hilightA=reader.readI(); - hilightB=reader.readI(); - } - if (expansions&8) if (blockVersion>=5) { // N163 channels - n163Chans=reader.readI(); - } - if (blockVersion>=6) { - speedSplitPoint=reader.readI(); + customHz = reader.readI(); } - if (blockVersion>=8) { - int fineTuneCents=reader.readC()*100; - fineTuneCents+=reader.readC(); - - ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0); + unsigned int newVibrato = 0; + bool sweepReset = false; + unsigned int speedSplitPoint = 0; + if (blockVersion >= 3) { + newVibrato = reader.readI(); + } + if (blockVersion >= 9) { + sweepReset = reader.readI(); + } + if (eft) { + if (blockVersion >= 4 && blockVersion <= 7) { + hilightA = reader.readI(); + hilightB = reader.readI(); + } + } else { + if (blockVersion >= 4 && blockVersion < 7) { + hilightA = reader.readI(); + hilightB = reader.readI(); + } } - logV("old speed/tempo: %d",oldSpeedTempo); - logV("expansions: %x",expansions); - logV("channels: %d",tchans); - logV("PAL: %d",pal); - logV("custom Hz: %f",customHz); - logV("new vibrato: %d",newVibrato); - logV("N163 channels: %d",n163Chans); - logV("highlight 1: %d",hilightA); - logV("highlight 2: %d",hilightB); - logV("split point: %d",speedSplitPoint); - logV("sweep reset: %d",sweepReset); + if ((expansions & 16) && blockVersion >= 5) { // N163 channels + n163Chans = reader.readI(); + } + if (blockVersion >= 6) { + speedSplitPoint = reader.readI(); + } + + if (blockVersion == 8) { + int fineTuneCents = reader.readC() * 100; + fineTuneCents += reader.readC(); + + ds.tuning = 440.0 * pow(2.0, (double)fineTuneCents / 1200.0); + + logV("detune: %d", fineTuneCents); + } + + logV("old speed/tempo: %d", oldSpeedTempo); + logV("expansions: %x", expansions); + logV("channels: %d", tchans); + logV("PAL: %d", pal); + logV("custom Hz: %f", customHz); + logV("new vibrato: %d", newVibrato); + logV("N163 channels: %d", n163Chans); + logV("highlight 1: %d", hilightA); + logV("highlight 2: %d", hilightB); + logV("split point: %d", speedSplitPoint); + logV("sweep reset: %d", sweepReset); + + //addWarning("FamiTracker import is experimental."); // initialize channels - int systemID=0; - ds.system[systemID++]=DIV_SYSTEM_NES; - if (expansions&1) { - ds.system[systemID++]=DIV_SYSTEM_VRC6; - } - if (expansions&2) { - ds.system[systemID++]=DIV_SYSTEM_VRC7; - } - if (expansions&4) { - ds.system[systemID++]=DIV_SYSTEM_FDS; - } - if (expansions&8) { - ds.system[systemID++]=DIV_SYSTEM_MMC5; - } - if (expansions&16) { - ds.system[systemID]=DIV_SYSTEM_N163; - ds.systemFlags[systemID++].set("channels",(int)n163Chans); - } - if (expansions&32) { - ds.system[systemID]=DIV_SYSTEM_AY8910; - ds.systemFlags[systemID++].set("chipType",2); // Sunsoft 5B - } - ds.systemLen=systemID; + int systemID = 0; - unsigned int calcChans=0; - for (int i=0; iDIV_MAX_CHANS) { - tchans=DIV_MAX_CHANS; - logW("too many channels!"); - } - } else if (blockName=="INFO") { - CHECK_BLOCK_VERSION(1); - ds.name=reader.readString(32); - ds.author=reader.readString(32); - ds.category=reader.readString(32); - ds.systemName="NES"; - } else if (blockName=="HEADER") { - CHECK_BLOCK_VERSION(4); - unsigned char totalSongs=reader.readC(); - logV("%d songs:",totalSongs+1); - ds.subsong.reserve(totalSongs); - for (int i=0; i<=totalSongs; i++) { - String subSongName=reader.readString(); - ds.subsong.push_back(new DivSubSong); - ds.subsong[i]->name=subSongName; - ds.subsong[i]->hilightA=hilightA; - ds.subsong[i]->hilightB=hilightB; - if (customHz!=0) { - ds.subsong[i]->hz=customHz; + + if (expansions & 1) { + ds.system[systemID++] = DIV_SYSTEM_VRC6; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + + if (ch < 2) { + vrc6_chans[ch] = map_ch; + logV("%d",vrc6_chans[ch]); + } + + curr_chan++; + map_ch++; } - logV("- %s",subSongName); + + vrc6_saw_chan = map_ch - 1; } - for (unsigned int i=0; ipat[i].effectCols=effectCols+1; - logV("- song %d has %d effect columns",j,effectCols); + if (expansions & 8) { + ds.system[systemID++] = DIV_SYSTEM_MMC5; + + for (int ch = 0; ch < (eft ? 3 : 2); ch++) { + map_channels[curr_chan] = map_ch; + + if (ch < 2) { + mmc5_chans[ch] = map_ch; + logV("%d",mmc5_chans[ch]); + } + + curr_chan++; + map_ch++; + } + + if (!eft) { + map_channels[curr_chan] = map_ch; // do not populate and skip MMC5 PCM channel! + map_ch++; } } + if (expansions & 16) { + ds.system[systemID] = DIV_SYSTEM_N163; + ds.systemFlags[systemID].set("channels", (int)n163Chans - 1); + systemID++; - if (blockVersion>=4) { - for (int i=0; i<=totalSongs; i++) { - ds.subsong[i]->hilightA=(unsigned char)reader.readC(); - ds.subsong[i]->hilightB=(unsigned char)reader.readC(); + for (int ch = 0; ch < (int)n163Chans; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + n163_chans[ch] = map_ch; + map_ch++; + } + + for (int ch = 0; ch < (8 - (int)n163Chans); ch++) { + map_channels[curr_chan] = map_ch; // do not populate and skip the missing N163 channels! + map_ch++; } } - } else if (blockName=="INSTRUMENTS") { - CHECK_BLOCK_VERSION(6); + if (expansions & 4) { + ds.system[systemID++] = DIV_SYSTEM_FDS; - reader.seek(blockSize,SEEK_CUR); + map_channels[curr_chan] = map_ch; + fds_chan = map_ch; + curr_chan++; + map_ch++; + } + if (expansions & 2) { + ds.system[systemID++] = DIV_SYSTEM_VRC7; - /* - ds.insLen=reader.readI(); - if (ds.insLen<0 || ds.insLen>256) { - logE("too many instruments/out of range!"); - lastError="too many instruments/out of range"; - delete[] file; - return false; + for (int ch = 0; ch < 6; ch++) { + map_channels[curr_chan] = map_ch; + vrc7_chans[ch] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 32) { + ds.system[systemID] = DIV_SYSTEM_AY8910; + ds.systemFlags[systemID++].set("chipType", 2); // Sunsoft 5B + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 64) { + ds.system[systemID++] = DIV_SYSTEM_AY8930; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 128) { + ds.system[systemID++] = DIV_SYSTEM_SAA1099; + + for (int ch = 0; ch < 6; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 256) { + ds.system[systemID++] = DIV_SYSTEM_5E01; + + for (int ch = 0; ch < 5; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 512) { + ds.system[systemID++] = DIV_SYSTEM_C64_6581; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 1024) { + ds.system[systemID++] = DIV_SYSTEM_C64_8580; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 2048) { + ds.system[systemID++] = DIV_SYSTEM_POKEY; + + for (int ch = 0; ch < 4; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + ds.systemLen = systemID; + + for (int i = 0; i < curr_chan; i++) { + logV("map ch: fami ch %d mapped to furnace ch %d", i, map_channels[i]); } - for (int i=0; i=ds.ins.size()) { - logE("instrument index %d is out of range!",insIndex); - lastError="instrument index out of range"; + calcChans += getChannelCount(ds.system[i]); + total_chans += getChannelCount(ds.system[i]); + + if (ds.system[i] == DIV_SYSTEM_N163) { + calcChans -= getChannelCount(ds.system[i]); + calcChans += (int)n163Chans; + } + } + if (calcChans != tchans) { + if (!eft || (eft && (expansions & 8) == 0)) // ignore since I have no idea how to tell apart E-FT versions which do or do not have PCM chan. Yes, this may lead to all the righer channels to be shifted but at least you still get note data! + { + logE("channel counts do not match! %d != %d", tchans, calcChans); + lastError = "channel counts do not match"; delete[] file; return false; } + } + if (tchans > DIV_MAX_CHANS) { + tchans = DIV_MAX_CHANS; + logW("too many channels!"); + } + if (blockVersion == 9 && blockSize - (reader.tell() - blockStart) == 2) // weird + { + reader.seek(2, SEEK_CUR); + } + if (eft) { + // reader.seek(8,SEEK_CUR); + } - DivInstrument* ins=ds.ins[insIndex]; - unsigned char insType=reader.readC(); + } else if (blockName == "INFO") { + CHECK_BLOCK_VERSION(1); + ds.name = reader.readString(32); + ds.author = reader.readString(32); + ds.copyright = reader.readString(32); + ds.systemName = "NES"; + } else if (blockName == "HEADER") { + CHECK_BLOCK_VERSION(4); + unsigned char totalSongs=0; + if (blockVersion>=2) totalSongs=reader.readC(); + logV("%d songs:", totalSongs + 1); + ds.subsong.reserve(totalSongs); + for (int i = 0; i <= totalSongs; i++) { + String subSongName; + if (blockVersion>=3) subSongName=reader.readString(); + ds.subsong.push_back(new DivSubSong); + ds.subsong[i]->name = subSongName; + ds.subsong[i]->hilightA = hilightA; + ds.subsong[i]->hilightB = hilightB; + if (customHz != 0) { + ds.subsong[i]->hz = customHz; + ds.subsong[i]->virtualTempoD = (short)(2.5 * customHz); + } + logV("- %s", subSongName); + } + for (unsigned int i = 0; i < tchans; i++) { + // TODO: obey channel ID + unsigned char chID = reader.readC(); + logV("for channel ID %d", chID); + for (int j = 0; j <= totalSongs; j++) { + unsigned char effectCols = reader.readC(); + + if (map_channels[i] == 0xfe) { + ds.subsong[j]->pat[i].effectCols = 1; + logV("- song %d has %d effect columns", j, effectCols); + } else { + ds.subsong[j]->pat[map_channels[i]].effectCols = effectCols + 1; + logV("- song %d has %d effect columns", j, effectCols); + } + } + } + + if (blockVersion >= 4) { + for (int i = 0; i <= totalSongs; i++) { + ds.subsong[i]->hilightA = (unsigned char)reader.readC(); + ds.subsong[i]->hilightB = (unsigned char)reader.readC(); + } + } + } else if (blockName == "INSTRUMENTS") { + CHECK_BLOCK_VERSION(9); + + // reader.seek(blockSize,SEEK_CUR); + + ds.insLen = reader.readI(); + if (ds.insLen < 0 || ds.insLen > 256) { + logE("too many instruments/out of range!"); + lastError = "too many instruments/out of range"; + delete[] file; + return false; + } + + for (int i = 0; i < 128; i++) { + DivInstrument* ins = new DivInstrument; + ds.ins.push_back(ins); + ds.ins[i]->type = DIV_INS_FM; + } + + logV("instruments:"); + for (int i = 0; i < ds.insLen; i++) { + unsigned int insIndex = reader.readI(); + if (insIndex >= ds.ins.size()) { + // logE("instrument index %d is out of range!",insIndex); + // lastError="instrument index out of range"; + // delete[] file; + // return false; + } + + DivInstrument* ins = ds.ins[insIndex]; + unsigned char insType = reader.readC(); switch (insType) { case 1: - ins->type=DIV_INS_NES; + ins->type = DIV_INS_NES; break; - case 2: // TODO: tell VRC6 and VRC6 saw instruments apart - ins->type=DIV_INS_VRC6; + case 2: + ins->type = DIV_INS_VRC6; break; - case 3: - ins->type=DIV_INS_OPLL; + case 3: // VRC7 + ins->type = DIV_INS_OPLL; break; case 4: - ins->type=DIV_INS_FDS; + ins->type = DIV_INS_FDS; break; case 5: - ins->type=DIV_INS_N163; + ins->type = DIV_INS_N163; break; - case 6: // 5B? - ins->type=DIV_INS_AY; + case 6: // 5B + ins->type = DIV_INS_AY; + break; + case 7: // 6581 SID + ins->type = DIV_INS_C64; break; default: { - logE("%d: invalid instrument type %d",insIndex,insType); - lastError="invalid instrument type"; + logE("%d: invalid instrument type %d", insIndex, insType); + lastError = "invalid instrument type"; delete[] file; return false; } @@ -335,253 +911,1503 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { // instrument data switch (ins->type) { case DIV_INS_NES: { - unsigned int totalSeqs=reader.readI(); - if (totalSeqs>5) { - logE("%d: too many sequences!",insIndex); - lastError="too many sequences"; + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; delete[] file; return false; } - for (unsigned int j=0; j=2)?96:72; - for (int j=0; jamiga.noteMap[j].map=(short)((unsigned char)reader.readC())-1; - ins->amiga.noteMap[j].freq=(unsigned char)reader.readC(); - if (blockVersion>=6) { - reader.readC(); // DMC value + int dpcmNotes = (blockVersion >= 2) ? 96 : 72; + + if (blockVersion >= 7) { + unsigned int notes = reader.readI(); + dpcmNotes = notes; + } + + // should dpcmNotes be 96 always? + for (int j = 0; j < dpcmNotes; j++) { + int note = j; + if (blockVersion >= 7) { + note = reader.readC(); } + ins->amiga.noteMap[note].map = (short)((unsigned char)reader.readC()) - 1; + unsigned char freq = reader.readC(); + ins->amiga.noteMap[note].dpcmFreq = (freq & 15); // 0-15 = 0-15 unlooped, 128-143 = 0-15 looped + ins->amiga.noteMap[note].freq = (freq & 0x80) ? 1 : 0; // loop + if (blockVersion >= 6) { + ins->amiga.noteMap[note].dpcmDelta = (unsigned char)reader.readC(); // DMC value + } + } + + ins->amiga.useSample = true; + ins->amiga.useNoteMap = true; + + bool empty_note_map = true; + + for (int j = 0; j < dpcmNotes; j++) { + if (ins->amiga.noteMap[j].map != -1) { + empty_note_map = false; + } + } + + if (empty_note_map) { + ins->amiga.useSample = false; + ins->amiga.useNoteMap = false; + ins->amiga.initSample = -1; } break; } case DIV_INS_VRC6: { - unsigned int totalSeqs=reader.readI(); - if (totalSeqs>4) { - logE("%d: too many sequences!",insIndex); - lastError="too many sequences"; + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; delete[] file; return false; } - for (unsigned int j=0; jfm.opllPreset=(unsigned int)reader.readI(); - // TODO + ins->fm.opllPreset = (unsigned int)reader.readI(); + + unsigned char custom_patch[8] = {0}; + + for (int i = 0; i < 8; i++) { + custom_patch[i] = reader.readC(); + } + + for (int i = 0; i < 2; i++) { + ins->fm.op[i].am = custom_patch[i] >> 7; + ins->fm.op[i].vib = (custom_patch[i] >> 6) & 1; + ins->fm.op[i].ssgEnv = ((custom_patch[i] >> 5) & 1) << 3; + ins->fm.op[i].ksr = (custom_patch[i] >> 4) & 1; + + ins->fm.op[i].mult = custom_patch[i] & 15; + + ins->fm.op[i].ksl = custom_patch[i + 2] >> 6; + + ins->fm.op[i].ar = custom_patch[i + 4] >> 4; + ins->fm.op[i].dr = custom_patch[i + 4] & 15; + + ins->fm.op[i].sl = custom_patch[i + 6] >> 4; + ins->fm.op[i].rr = custom_patch[i + 6] & 15; + } + + ins->fm.fms = (custom_patch[3] >> 4) & 1; + ins->fm.ams = (custom_patch[3] >> 3) & 1; + ins->fm.fb = custom_patch[3] & 7; + + ins->fm.op[0].tl = custom_patch[2] & 0x3f; + ins->fm.op[1].tl = 0; break; } case DIV_INS_FDS: { - DivWavetable* wave=new DivWavetable; - wave->len=64; - wave->max=64; - for (int j=0; j<64; j++) { - wave->data[j]=reader.readC(); + DivWavetable* wave = new DivWavetable; + wave->len = 64; + wave->max = 63; + for (int j = 0; j < 64; j++) { + wave->data[j] = reader.readC(); } - ins->std.waveMacro.len=1; - ins->std.waveMacro.val[0]=ds.wave.size(); - for (int j=0; j<32; j++) { - ins->fds.modTable[j]=reader.readC()-3; + ins->std.waveMacro.len = 1; + ins->std.waveMacro.val[0] = ds.wave.size(); + for (int j = 0; j < 32; j++) { + ins->fds.modTable[j] = reader.readC() - 3; } - ins->fds.modSpeed=reader.readI(); - ins->fds.modDepth=reader.readI(); + ins->fds.modSpeed = reader.readI(); + ins->fds.modDepth = reader.readI(); reader.readI(); // this is delay. currently ignored. TODO. ds.wave.push_back(wave); + ds.waveLen++; - ins->std.volMacro.len=reader.readC(); - ins->std.volMacro.loop=reader.readI(); - ins->std.volMacro.rel=reader.readI(); - reader.readI(); // arp mode does not apply here - for (int j=0; jstd.volMacro.len; j++) { - ins->std.volMacro.val[j]=reader.readC(); - } + unsigned int a = reader.readI(); + unsigned int b = reader.readI(); - ins->std.arpMacro.len=reader.readC(); - ins->std.arpMacro.loop=reader.readI(); - ins->std.arpMacro.rel=reader.readI(); - // TODO: get rid - ins->std.arpMacro.mode=reader.readI(); - for (int j=0; jstd.arpMacro.len; j++) { - ins->std.arpMacro.val[j]=reader.readC(); - } + reader.seek(-8, SEEK_CUR); - ins->std.pitchMacro.len=reader.readC(); - ins->std.pitchMacro.loop=reader.readI(); - ins->std.pitchMacro.rel=reader.readI(); - reader.readI(); // arp mode does not apply here - for (int j=0; jstd.pitchMacro.len; j++) { - ins->std.pitchMacro.val[j]=reader.readC(); + if (a < 256 && (b & 0xFF) != 0x00) { + // don't look at me like this. I don't know why this should be like this either! + } else { + ins->std.volMacro.len = reader.readC(); + ins->std.volMacro.loop = reader.readI(); + ins->std.volMacro.rel = reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j = 0; j < ins->std.volMacro.len; j++) { + ins->std.volMacro.val[j] = reader.readC(); + + if (blockVersion <= 3) { + ins->std.volMacro.val[j] *= 2; + } + } + + ins->std.arpMacro.len = reader.readC(); + ins->std.arpMacro.loop = reader.readI(); + ins->std.arpMacro.rel = reader.readI(); + unsigned int mode = reader.readI(); + for (int j = 0; j < ins->std.arpMacro.len; j++) { + ins->std.arpMacro.val[j] = reader.readC(); + + if (mode == 1) // fixed arp + { + ins->std.arpMacro.val[j] |= (1 << 30); + } + } + + if (blockVersion >= 3) { + ins->std.pitchMacro.len = reader.readC(); + ins->std.pitchMacro.loop = reader.readI(); + ins->std.pitchMacro.rel = reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j = 0; j < ins->std.pitchMacro.len; j++) { + ins->std.pitchMacro.val[j] = reader.readC(); + + int temp_val = ins->std.pitchMacro.val[j]; + int temp = temp_val; + + if (temp_val < 0x80) { + ins->std.pitchMacro.val[j] = temp; + } else { + ins->std.pitchMacro.val[j] = -1 * (0x100 - temp); + } + } + + if (mode == 0) // relative + { + ins->std.pitchMacro.mode = 1; // setting relative mode + } + } } break; } case DIV_INS_N163: { - // TODO! + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j = 0; j < totalSeqs; j++) { + hasSequence[insIndex][j] = reader.readC(); + sequenceIndex[insIndex][j] = reader.readC(); + } + + unsigned int wave_size = reader.readI(); + unsigned int wave_pos = reader.readI(); + ins->n163.waveLen = wave_size; + ins->n163.wavePos = wave_pos; + + if (blockVersion >= 8) { + unsigned int autopos = reader.readI(); + (void)autopos; + } + + unsigned int wave_count = reader.readI(); + size_t waveOff = ds.wave.size(); + + for (unsigned int ii = 0; ii < wave_count; ii++) { + DivWavetable* wave = new DivWavetable(); + wave->len = wave_size; + wave->max = 15; + + for (unsigned int jj = 0; jj < wave_size; jj++) { + unsigned char val = reader.readC(); + wave->data[jj] = val; + } + + if (ds.wave.size()<256) { + ds.wave.push_back(wave); + } else { + delete wave; + } + } + + // offset wave macro + if (ins->std.waveMacro.len == 0) // empty wave macro + { + ins->std.waveMacro.len = 1; + ins->std.waveMacro.val[0] = waveOff; + } else { + for (int p=0; pstd.waveMacro.len; p++) { + ins->std.waveMacro.val[p] += waveOff; + } + } + break; } - // TODO: 5B! + + case DIV_INS_AY: { + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError ="too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j = 0; j < totalSeqs; j++) { + hasSequence[insIndex][j] = reader.readC(); + sequenceIndex[insIndex][j] = reader.readC(); + } + + break; + } + + case DIV_INS_C64: { + unsigned int instVersion = reader.readI(); + logV("C64 inst version %d", instVersion); + + int seek_amount = 94 - 4; + + if (instVersion <= 255) { + unsigned char ad = reader.readC(); + unsigned char sr = reader.readC(); + ins->c64.a = ad >> 4; + ins->c64.d = ad & 15; + ins->c64.s = sr >> 4; + ins->c64.r = sr & 15; + + ins->c64.pulseOn = true; + ins->c64.sawOn = false; + + seek_amount -= 2; + + if (instVersion >= 2) { + unsigned int pwm_start = reader.readI(); + unsigned int pwm_end = reader.readI(); + + unsigned char pwm_speed = reader.readC(); // for LFO mode * 40 / 69 * (diff / (2048 - 311)) (and divide by two for triangle mode) + unsigned char pwm_mode = reader.readC(); // for ADSR mode * 11 / 69 + + if (pwm_mode != 0) { + ins->c64.dutyIsAbs = true; + + logV("pwm mode %d", pwm_mode); + + // modes: 1 = saw lfo, 2 = tri lfo, 3 = oneshot ADSR, 4 = set val? + + ins->std.dutyMacro.len = 18; + + if (pwm_mode != 4) { + ins->std.dutyMacro.val[0] = pwm_start; + ins->std.dutyMacro.val[1] = pwm_end; + } else { + ins->std.dutyMacro.len = 1; + ins->std.dutyMacro.val[0] = pwm_end; // sequence mode, set last value + } + + if (pwm_mode == 1 || pwm_mode == 2) { + ins->std.dutyMacro.open = 4 | 1; // LFO + ins->std.dutyMacro.mode = 2; // LFO + + ins->std.dutyMacro.val[11] = (int)((uint64_t)pwm_speed * (uint64_t)40 * (uint64_t)abs((int)(pwm_start - pwm_end)) / (2048 - 311) / 69); // LFO speed + ins->std.dutyMacro.val[12] = 1; // sawtooth LFO wave + + if (pwm_mode == 2) { + ins->std.dutyMacro.val[11] = (int)((uint64_t)pwm_speed * (uint64_t)40 * (uint64_t)abs((int)(pwm_start - pwm_end)) / (2048 - 311) / 69 / 2); // LFO speed multiplied by 2 + ins->std.dutyMacro.val[12] = 0; // triangle LFO wave + } + } + + if (pwm_mode == 3) { + ins->std.dutyMacro.open = 2 | 1; // ADSR + ins->std.dutyMacro.mode = 1; // LFO + ins->std.dutyMacro.val[2] = (int)((uint64_t)pwm_speed * (uint64_t)11 * (uint64_t)abs((int)(pwm_start - pwm_end)) / (2048 - 311) / 69); // ADSR attack rate + } + } + + seek_amount -= 10; + } + + if (instVersion >= 3) { + unsigned int filter_start = reader.readI(); + unsigned int filter_end = reader.readI(); + + unsigned char filter_speed = reader.readC(); + unsigned char filter_mode = reader.readC(); + + if (filter_mode == 0) { + seek_amount -= 10; + } else { + ins->c64.filterIsAbs = true; + + // modes: 1 = saw lfo, 2 = tri lfo, 3 = oneshot ADSR, 4 = set val? + + ins->std.algMacro.len = 18; + + if (filter_mode != 4) { + ins->std.algMacro.val[0] = filter_start; + ins->std.algMacro.val[1] = filter_end; + } else { + ins->std.algMacro.len = 1; + ins->std.algMacro.val[0] = filter_end; // sequence mode, set last value + } + + if (filter_mode == 1 || filter_mode == 2) { + ins->std.algMacro.open = 4 | 1; // LFO + ins->std.algMacro.mode = 2; // LFO + + ins->std.algMacro.val[11] = (int)((uint64_t)filter_speed * (uint64_t)40 * (uint64_t)abs((int)(filter_start - filter_end)) / (2048 - 311) / 69); // LFO speed + ins->std.algMacro.val[12] = 1; // sawtooth LFO wave + + if (filter_mode == 2) { + ins->std.algMacro.val[11] = (int)((uint64_t)filter_speed * (uint64_t)40 * (uint64_t)abs((int)(filter_start - filter_end)) / (2048 - 311) / 69 / 2); // LFO speed multiplied by 2 + ins->std.algMacro.val[12] = 0; // triangle LFO wave + } + } + + if (filter_mode == 3) { + ins->std.algMacro.open = 2 | 1; // ADSR + ins->std.algMacro.mode = 1; // LFO + ins->std.algMacro.val[2] = (int)((uint64_t)filter_speed * (uint64_t)11 * (uint64_t)abs((int)(filter_start - filter_end)) / (2048 - 311) / 69); // ADSR attack rate + } + } + } + + if (instVersion >= 4) { + unsigned int totalSeqs = reader.readI(); + seek_amount -= 4; + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j = 0; j < totalSeqs; j++) { + hasSequence[insIndex][j] = reader.readC(); + sequenceIndex[insIndex][j] = reader.readC(); + + seek_amount -= 2; + } + } + + if (instVersion == 2) { + reader.seek(seek_amount, SEEK_CUR); // what the fuck + } + + /*for(int tti = 0; tti < 20; tti++) + { + char aaaa = reader.readC(); + logV("\'%c\'", aaaa); + }*/ + } else { + reader.seek(-4, SEEK_CUR); + } + + break; + } + default: { - logE("%d: what's going on here?",insIndex); - lastError="invalid instrument type"; + logE("%d: what's going on here?", insIndex); + lastError = "invalid instrument type"; delete[] file; return false; } } // name - ins->name=reader.readString((unsigned int)reader.readI()); - logV("- %d: %s",insIndex,ins->name); + ins->name = reader.readString((unsigned int)reader.readI()); + logV("- %d: %s", insIndex, ins->name); } - */ - } else if (blockName=="SEQUENCES") { + + ds.insLen = 128; + + } else if (blockName == "SEQUENCES") { CHECK_BLOCK_VERSION(6); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="FRAMES") { + + if (blockVersion < 2) { + lastError = "sequences block version is too old"; + delete[] file; + return false; + } + + unsigned int seq_count = reader.readI(); + + if (blockVersion == 2) { + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + unsigned int type = reader.readI(); + unsigned char size = reader.readC(); + macros[index][type].len = size; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + reader.readC(); // reserved? + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins=ds.ins[k]; + if (sequenceIndex[k][type] == index && ins->type == DIV_INS_NES && hasSequence[k][type]) { + copyMacro(ins, ¯os[index][type], type, 0); + } + } + } + } else { + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + if (blockVersion == 4) { + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + } + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) { + copyMacro(ins, ¯os[index][type], Types[i], setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + if (blockVersion == 5) // Version 5 saved the release points incorrectly, this is fixed in ver 6 + { + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 5; j++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][j] == i && ins->type == DIV_INS_NES && hasSequence[k][j]) { + macros[k][j].rel = release; + macro_types[k][j] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][j]][j], j, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)j, true), ¯os[sequenceIndex[k][j]][j], sizeof(DivInstrumentMacro)); + } + } + } + } + } + + if (blockVersion >= 6) // Read release points correctly stored + { + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + // macros[index][type].rel = release; + // macro_types[index][type] = setting; + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) { + macros[sequenceIndex[k][Types[i]]][Types[i]].rel = release; + macro_types[k][Types[i]] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[k][Types[i]]][Types[i]], sizeof(DivInstrumentMacro)); + } + } + } + } + + delete[] Indices; + delete[] Types; + } + } else if (blockName == "GROOVES") { + CHECK_BLOCK_VERSION(6); + // reader.seek(blockSize,SEEK_CUR); + + unsigned char num_grooves = reader.readC(); + int max_groove = 0; + + for (int i = 0; i < 0xff; i++) { + ds.grooves.push_back(DivGroovePattern()); + } + + for (int gr = 0; gr < num_grooves; gr++) { + unsigned char index = reader.readC(); + unsigned char size = reader.readC(); + + if (index > max_groove) + max_groove = index + 1; + + DivGroovePattern gp; + gp.len = size; + + for (int sz = 0; sz < size; sz++) { + unsigned char value = reader.readC(); + gp.val[sz] = value; + } + + ds.grooves[index] = gp; + } + + ds.grooves.resize(max_groove == 0 ? 1 : max_groove); + + unsigned char subsongs = reader.readC(); + + for (int sub = 0; sub < subsongs; sub++) { + unsigned char used = reader.readC(); + + if (used) { + ds.subsong[sub]->speeds = ds.grooves[0]; + } + } + + if ((reader.tell() - blockStart) != blockSize) { + logE("block %s size does not match! block size %d curr pos %d", blockName, blockSize, reader.tell() - blockStart); + } + + } else if (blockName == "FRAMES") { CHECK_BLOCK_VERSION(3); - for (size_t i=0; iordersLen=reader.readI(); - if (blockVersion>=3) { - s->speeds.val[0]=reader.readI(); + s->ordersLen = reader.readI(); + if (blockVersion >= 3) { + s->speeds.val[0] = reader.readI(); } - if (blockVersion>=2) { - s->virtualTempoN=reader.readI(); - s->patLen=reader.readI(); - } - int why=tchans; - if (blockVersion==1) { - why=reader.readI(); - } - logV("reading %d and %d orders",tchans,s->ordersLen); + if (blockVersion >= 2) { + int tempo = reader.readI(); - for (int j=0; jordersLen; j++) { - for (int k=0; korders.ord[k][j]=o; + logV("tempo %d", tempo); + + if (tempo == 0) { + s->virtualTempoN = s->virtualTempoD; + } else { + s->virtualTempoN = tempo; + } + + s->patLen = reader.readI(); + } + int why = tchans; + if (blockVersion == 1) { + why = reader.readI(); + } + logV("reading %d and %d orders", tchans, s->ordersLen); + + for (int j = 0; j < s->ordersLen; j++) { + for (int k = 0; k < why; k++) { + unsigned char o = reader.readC(); + // logV("%.2x",o); + s->orders.ord[map_channels[k]][j] = o; } } } - } else if (blockName=="PATTERNS") { + } else if (blockName == "PATTERNS") { CHECK_BLOCK_VERSION(6); - size_t blockEnd=reader.tell()+blockSize; + size_t blockEnd = reader.tell() + blockSize; - if (blockVersion==1) { - int patLenOld=reader.readI(); - for (DivSubSong* i: ds.subsong) { - i->patLen=patLenOld; + if (blockVersion == 1) { + int patLenOld = reader.readI(); + for (DivSubSong* i : ds.subsong) { + i->patLen = patLenOld; } } // so it appears .ftm doesn't keep track of how many patterns are stored in the file.... - while (reader.tell()=2) subs=reader.readI(); - int ch=reader.readI(); - int patNum=reader.readI(); - int numRows=reader.readI(); + while (reader.tell() < blockEnd) { + logV("reading pattern at %x...",reader.tell()); + int subs = 0; + if (blockVersion >= 2) + subs = reader.readI(); + int ch = reader.readI(); + int patNum = reader.readI(); + int numRows = reader.readI(); - DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true); - for (int i=0; i=2 && blockVersion<6) { // row index - row=reader.readI(); + logV("ch: %d",ch); + logV("subs: %d. map_channels[ch]: %d",subs,map_channels[ch]); + logV("patNum: %d",patNum); + logV("rows: %d",numRows); + + DivPattern* pat = ds.subsong[subs]->pat[map_channels[ch]].getPattern(patNum, true); + for (int i = 0; i < numRows; i++) { + unsigned int row = 0; + if (ds.version==0x200 || blockVersion >= 6) { // row index + row = (unsigned char)reader.readC(); } else { - row=reader.readC(); + row = reader.readI(); } - unsigned char nextNote=reader.readC(); - unsigned char nextOctave=reader.readC(); - if (nextNote==0x0d) { - pat->data[row][0]=100; - } else if (nextNote==0x0e) { - pat->data[row][0]=101; - } else if (nextNote==0x01) { - pat->data[row][0]=12; - pat->data[row][1]=nextOctave-1; - } else if (nextNote==0) { - pat->data[row][0]=0; - } else if (nextNote<0x0d) { - pat->data[row][0]=nextNote-1; - pat->data[row][1]=nextOctave; + unsigned char nextNote = reader.readC(); + unsigned char nextOctave = reader.readC(); + + if (blockVersion < 5 && map_channels[ch] == fds_chan) // FDS transpose + { + nextOctave += 2; + nextOctave -= 2; } - - unsigned char nextIns=reader.readC(); - if (nextIns<0x40) { - pat->data[row][2]=nextIns; - } else { - pat->data[row][2]=-1; + if (blockVersion >= 5 && map_channels[ch] == fds_chan) // FDS transpose + { + nextOctave -= 2; } - unsigned char nextVol=reader.readC(); - if (nextVol<0x10) { - pat->data[row][3]=nextVol; - } else { - pat->data[row][3]=-1; + if (map_channels[ch] != 0xff) { + if (nextNote == 0x0d) { + pat->data[row][0] = 101; + } else if (nextNote == 0x0e) { + pat->data[row][0] = 100; + } else if (nextNote == 0x01) { + pat->data[row][0] = 12; + pat->data[row][1] = nextOctave - 1; + } else if (nextNote == 0) { + pat->data[row][0] = 0; + } else if (nextNote < 0x0d) { + pat->data[row][0] = nextNote - 1; + pat->data[row][1] = nextOctave; + } } - int effectCols=ds.subsong[subs]->pat[ch].effectCols; - if (blockVersion>=6) effectCols=4; - - for (int j=0; jdata[row][4+(j*2)]=-1; - pat->data[row][5+(j*2)]=-1; + unsigned char nextIns = reader.readC(); + if (map_channels[ch] != 0xff) { + if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) { + pat->data[row][2] = nextIns; } else { - if (nextEffectdata[row][4+(j*2)]=ftEffectMap[nextEffect]; - } else { - pat->data[row][4+(j*2)]=-1; + pat->data[row][2] = -1; + } + } + + unsigned char nextVol = reader.readC(); + if (map_channels[ch] != 0xff) { + if (nextVol < 0x10) { + pat->data[row][3] = nextVol; + if (map_channels[ch] == vrc6_saw_chan) // scale volume + { + pat->data[row][3] = (pat->data[row][3] * 42) / 15; + } + + if (map_channels[ch] == fds_chan) { + pat->data[row][3] = (pat->data[row][3] * 31) / 15; + } + } else { + pat->data[row][3] = -1; + } + } + + int effectCols = ds.subsong[subs]->pat[map_channels[ch]].effectCols; + if (blockVersion >= 6) + effectCols = 4; + + if (ds.version == 0x200) { + effectCols = 1; + } + + logV("effectCols: %d",effectCols); + + unsigned char nextEffectVal = 0; + unsigned char nextEffect = 0; + + //logV("row %d effects are read at %x",row,reader.tell()); + + for (int j = 0; j < effectCols; j++) { + nextEffect = reader.readC(); + + if (nextEffect>0 && ((nextEffect < FT_EF_COUNT && !eft) || (nextEffect < EFT_EF_COUNT && eft))) { + nextEffectVal = reader.readC(); + + if (blockVersion < 3) { + if (nextEffect == FT_EF_PORTAOFF) { + nextEffect = FT_EF_PORTAMENTO; + nextEffectVal = 0; + } else if (nextEffect == FT_EF_PORTAMENTO) { + if (nextEffect < 0xFF) + nextEffectVal++; + } + } + } else if (blockVersion < 6) { + nextEffectVal = reader.readC(); + } + + // Specific for version 2.0 + if (ds.version == 0x0200 && j == 0) { + if (nextEffect == FT_EF_SPEED && nextEffectVal < 20) + nextEffectVal++; + + if (pat->data[row][3] == 0) + pat->data[row][3] = 0xf; + else { + pat->data[row][3]--; + pat->data[row][3] &= 0x0F; + } + + if (pat->data[row][0] == 0) + pat->data[row][2] = -1; + } + + if (blockVersion == 3) { + // Fix for VRC7 portamento + bool is_vrc7 = false; + + for (int vrr = 0; vrr < 6; vrr++) { + if (map_channels[ch] == vrc7_chans[vrr]) { + is_vrc7 = true; + } + } + + if (is_vrc7) { + switch (nextEffect) { + case FT_EF_PORTA_DOWN: + nextEffect = FT_EF_PORTA_UP; + break; + case FT_EF_PORTA_UP: + nextEffect = FT_EF_PORTA_DOWN; + break; + default: + break; + } + } + // FDS pitch effect fix + else if (map_channels[ch] == fds_chan) { + switch (nextEffect) { + case FT_EF_PITCH: + if (nextEffectVal != 0x80) + nextEffectVal = (0x100 - nextEffectVal) & 0xFF; + break; + default: + break; + } + } + } + + for (int v = 0; v < 8; v++) { + if (map_channels[ch] == n163_chans[v]) { + if (nextEffect == FT_EF_SAMPLE_OFFSET) { + nextEffect = FT_EF_N163_WAVE_BUFFER; + } + } + } + + if (ds.version < 0x0450 || dnft) { + unsigned char idx = 0; + + while (eff_conversion_050[idx][0] != 0xFF) // until the end of the array + { + if (nextEffect == eff_conversion_050[idx][0]) // remap the effects (0CC vs FT effects type order) bruh idk why but it should be done to correctly read some modules + { + nextEffect = eff_conversion_050[idx][1]; + + break; + } + + idx++; + } + + } + + // logW("next effect %d val %d", nextEffect, nextEffectVal); + + if (map_channels[ch] != 0xff) { + if (nextEffect == 0 && nextEffectVal == 0) { + pat->data[row][4 + (j * 2)] = -1; + pat->data[row][5 + (j * 2)] = -1; + } else { + if (nextEffect < ftEffectMapSize) { + if (eft) { + pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect]; + pat->data[row][5 + (j * 2)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal; + + if (pat->data[row][4 + (j * 2)] == 0x100) { + pat->data[row][3] += pat->data[row][5 + (j * 2)] ? 0x10 : 0; // extra volume bit for AY8930 + pat->data[row][4 + (j * 2)] = -1; + pat->data[row][5 + (j * 2)] = -1; + } + + if (eftEffectMap[nextEffect] == 0x0f && nextEffectVal > 0x1f) { + pat->data[row][4 + (j * 2)] = 0xfd; // BPM speed change! + } + + if ((eftEffectMap[nextEffect] == 0xe1 || eftEffectMap[nextEffect] == 0xe2) && (nextEffectVal & 0xf0) == 0) { + pat->data[row][5 + (j * 2)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed! + } + } else { + pat->data[row][4 + (j * 2)] = ftEffectMap[nextEffect]; + pat->data[row][5 + (j * 2)] = ftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal; + + if (ftEffectMap[nextEffect] == 0x0f && nextEffectVal > 0x1f) { + pat->data[row][4 + (j * 2)] = 0xfd; // BPM speed change! + } + + if ((ftEffectMap[nextEffect] == 0xe1 || ftEffectMap[nextEffect] == 0xe2) && (nextEffectVal & 0xf0) == 0) { + pat->data[row][5 + (j * 2)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed! + } + } + for (int v = 0; v < 8; v++) { + if (map_channels[ch] == n163_chans[v]) { + if (pat->data[row][4 + (j * 2)] == 0x12) { + pat->data[row][4 + (j * 2)] = 0x10; // TODO: map wave + } + } + } + } else { + pat->data[row][4 + (j * 2)] = -1; + pat->data[row][5 + (j * 2)] = -1; + } } - pat->data[row][5+(j*2)]=nextEffectVal; } } } } - } else if (blockName=="DPCM SAMPLES") { + } else if (blockName == "DPCM SAMPLES") { CHECK_BLOCK_VERSION(1); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="SEQUENCES_VRC6") { - // where are the 5B and FDS sequences? + // reader.seek(blockSize,SEEK_CUR); + unsigned char num_samples = reader.readC(); + + for (int i = 0; i < 256; i++) { + DivSample* s = new DivSample(); + ds.sample.push_back(s); + } + + ds.sampleLen = ds.sample.size(); + + unsigned int true_size = 0; + unsigned char index = 0; + + for (unsigned char i = 0; i < num_samples; i++) { + index = reader.readC(); + + DivSample* sample = ds.sample[index]; + + sample->rate = 33144; + sample->centerRate = 33144; + sample->depth = DIV_SAMPLE_DEPTH_1BIT_DPCM; + + sample->name = reader.readString((unsigned int)reader.readI()); + + unsigned int sample_len = reader.readI(); + + true_size = sample_len + ((1 - (int)sample_len) & 0x0f); + sample->lengthDPCM = true_size; + sample->samples = true_size * 8; + + sample->dataDPCM = new unsigned char[true_size]; + + memset(sample->dataDPCM, 0xAA, true_size); + + reader.read(sample->dataDPCM, sample_len); + } + + int last_non_empty_sample = 0xff; + + for (int i = 255; i > 0; i--) { + DivSample* s = ds.sample[i]; + + if (s->dataDPCM) { + last_non_empty_sample = i; + break; + } + } + + for (int i = 255; i > last_non_empty_sample; i--) { + ds.sample.erase(ds.sample.begin() + i); + } + + ds.sampleLen = ds.sample.size(); + + } else if (blockName == "SEQUENCES_VRC6") { CHECK_BLOCK_VERSION(6); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="SEQUENCES_N163") { + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + if (blockVersion == 4) { + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + } + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) { + copyMacro(ins, ¯os[index][type], type, setting); + + if (type == 0 && setting == 1) { + ins->type = DIV_INS_VRC6_SAW; + } + } + } + } + + if (blockVersion == 5) // Version 5 saved the release points incorrectly, this is fixed in ver 6 + { + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 5; j++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][j] == i && ins->type == DIV_INS_VRC6 && hasSequence[k][j]) { + macros[k][j].rel = release; + macro_types[k][j] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][j]][j], j, setting); + + if (j == 0 && setting == 1) { + ins->type = DIV_INS_VRC6_SAW; + } + } + } + } + } + } + + if (blockVersion >= 6) // Read release points correctly stored + { + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + // macros[index][type].rel = release; + // macro_types[index][type] = setting; + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) { + macros[sequenceIndex[k][Types[i]]][Types[i]].rel = release; + macro_types[k][Types[i]] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting); + + if (Types[i] == 0 && setting == 1) { + ins->type = DIV_INS_VRC6_SAW; + } + } + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "SEQUENCES_N163" || blockName == "SEQUENCES_N106") { CHECK_BLOCK_VERSION(1); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="COMMENTS") { + // reader.seek(blockSize,SEEK_CUR); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_N163 && hasSequence[k][Types[i]]) { + copyMacro(ins, ¯os[index][type], type, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + delete[] Indices; + delete[] Types; + + } else if (blockName == "SEQUENCES_S5B") { CHECK_BLOCK_VERSION(1); - reader.seek(blockSize,SEEK_CUR); + // reader.seek(blockSize,SEEK_CUR); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_AY && hasSequence[k][type]) { + copyMacro(ins, ¯os[index][type], type, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "SEQUENCES_SID") { + CHECK_BLOCK_VERSION(4); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_C64 && hasSequence[k][type]) { + copyMacro(ins, ¯os[index][type], type, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "JSON") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); + } else if (blockName == "PARAMS_EMU") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); + } else if (blockName == "DETUNETABLES") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); + } else if (blockName == "COMMENTS") { + CHECK_BLOCK_VERSION(1); + // reader.seek(blockSize,SEEK_CUR); + unsigned int display_comment = reader.readI(); + (void)display_comment; + + char ch = 1; + + do { + ch = reader.readC(); + String sss = String() + ch; + ds.subsong[0]->notes += sss; + } while (ch != 0); + + // ds.subsong[0]->notes = reader.readS(); + } else if (blockName == "PARAMS_EXTRA") { + CHECK_BLOCK_VERSION(3); + // reader.seek(blockSize,SEEK_CUR); + unsigned int linear_pitch = reader.readI(); + + ds.linearPitch = linear_pitch == 0 ? 0 : 2; + + if (blockVersion >= 2) { + int fineTuneCents = reader.readC() * 100; + fineTuneCents += reader.readC(); + + ds.tuning = 440.0 * pow(2.0, (double)fineTuneCents / 1200.0); + } + if (blockVersion >= 3) { + unsigned char flats = reader.readC(); + (void)flats; + } + } else if (blockName == "TUNING") { + CHECK_BLOCK_VERSION(1); + // reader.seek(blockSize,SEEK_CUR); + if (blockVersion == 1) { + int fineTuneCents = reader.readC() * 100; + fineTuneCents += reader.readC(); + + ds.tuning = 440.0 * pow(2.0, (double)fineTuneCents / 1200.0); + } + } else if (blockName == "BOOKMARKS") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); } else { - logE("block %s is unknown!",blockName); - lastError="unknown block "+blockName; + logE("block %s is unknown!", blockName); + lastError = "unknown block " + blockName; delete[] file; return false; } - if ((reader.tell()-blockStart)!=blockSize) { - logE("block %s is incomplete!",blockName); - lastError="incomplete block "+blockName; + if ((reader.tell() - blockStart) != blockSize) { + logE("block %s is incomplete! reader.tell()-blockStart %d blockSize %d", blockName, (reader.tell() - blockStart), blockSize); + lastError = "incomplete block " + blockName; delete[] file; return false; } } - addWarning("FamiTracker import is experimental!"); + ds.insLen = ds.ins.size(); + + if (ds.insLen > 0) { + for (int tries = 0; tries < 5; tries++) // de-duplicating instruments + { + for (int i = 0; i < 128; i++) { + if (ds.ins.empty()) break; + int index = i >= (int)ds.insLen ? ((int)ds.insLen - 1) : i; + if (index < 0) + index = 0; + + DivInstrument* ins = ds.ins[index]; + + if (ins->type == DIV_INS_FM) { + delete ds.ins[index]; + ds.ins.erase(ds.ins.begin() + index); + ds.insLen = ds.ins.size(); + for (int ii = 0; ii < total_chans; ii++) { + for (size_t j = 0; j < ds.subsong.size(); j++) { + for (int k = 0; k < DIV_MAX_PATTERNS; k++) { + if (ds.subsong[j]->pat[ii].data[k] == NULL) + continue; + for (int l = 0; l < ds.subsong[j]->patLen; l++) { + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] > index) { + ds.subsong[j]->pat[ii].data[k]->data[l][2]--; + } + } + } + } + } + } + } + } + } + + // famitracker is not fucking strict with what instrument types can be used on any channel. This leads to e.g. 2A03 instuments being used on VRC6 channels. + // Furnace is way more strict, so NES instrument in VRC6 channel just does not play. To fix this, we are creating copies of instruments, changing their type to please Furnace system. + // I kinda did the same in klystrack import, tbh. + + // actually, this is wrong. Furnace is very lax regarding instrument usage. you can put an OPM instrument in OPL channel and yeah, it'll sound weird but it'll work. + + /* + int ins_vrc6_conv[256][2]; + int ins_vrc6_saw_conv[256][2]; + int ins_nes_conv[256][2]; // vrc6 (or whatever) -> nes + + for (int i = 0; i < 256; i++) { + ins_vrc6_conv[i][0] = -1; + ins_vrc6_conv[i][1] = -1; + + ins_vrc6_saw_conv[i][0] = -1; + ins_vrc6_saw_conv[i][1] = -1; + + ins_nes_conv[i][0] = -1; + ins_nes_conv[i][1] = -1; + } + + int init_inst_num = ds.ins.size(); + + for (int i = 0; i < init_inst_num; i++) { + for (int ii = 0; ii < total_chans; ii++) { + for (size_t j = 0; j < ds.subsong.size(); j++) { + for (int k = 0; k < DIV_MAX_PATTERNS; k++) { + if (ds.subsong[j]->pat[ii].data[k] == NULL) + continue; + for (int l = 0; l < ds.subsong[j]->patLen; l++) { + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == i) // instrument + { + DivInstrument* ins = ds.ins[i]; + bool go_to_end = false; + + if (ins->type != DIV_INS_VRC6 && (ii == vrc6_chans[0] || ii == vrc6_chans[1])) // we encountered non-VRC6 instrument on VRC6 channel + { + DivInstrument* insnew = new DivInstrument; + ds.ins.push_back(insnew); + + copyInstrument(ds.ins[ds.ins.size() - 1], ins); + + ds.ins[ds.ins.size() - 1]->name += " [VRC6 copy]"; + ds.ins[ds.ins.size() - 1]->amiga.useSample = false; + ds.ins[ds.ins.size() - 1]->amiga.useNoteMap = false; + + ds.ins[ds.ins.size() - 1]->type = DIV_INS_VRC6; + + if (ins->std.get_macro(DIV_MACRO_DUTY, false)->len > 0) { + for (int mm = 0; mm < ins->std.get_macro(DIV_MACRO_DUTY, false)->len; mm++) { + if (ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] < 4) { + int vall = ins->std.get_macro(DIV_MACRO_DUTY, false)->val[mm]; + ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = convert_vrc6_duties[vall]; + } + } + } + + ins_vrc6_conv[i][0] = i; + ins_vrc6_conv[i][1] = ds.ins.size() - 1; + + go_to_end = true; + } + + if (go_to_end) { + goto end1; + } + } + } + } + } + } + + end1:; + } + + for (int i = 0; i < init_inst_num; i++) { + for (int ii = 0; ii < total_chans; ii++) { + for (size_t j = 0; j < ds.subsong.size(); j++) { + for (int k = 0; k < DIV_MAX_PATTERNS; k++) { + if (ds.subsong[j]->pat[ii].data[k] == NULL) + continue; + for (int l = 0; l < ds.subsong[j]->patLen; l++) { + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == i) // instrument + { + DivInstrument* ins = ds.ins[i]; + bool go_to_end = false; + + if (ins->type != DIV_INS_VRC6_SAW && ii == vrc6_saw_chan) // we encountered non-VRC6-saw instrument on VRC6 saw channel + { + DivInstrument* insnew = new DivInstrument; + ds.ins.push_back(insnew); + + copyInstrument(ds.ins[ds.ins.size() - 1], ins); + + ds.ins[ds.ins.size() - 1]->name += " [VRC6 saw copy]"; + ds.ins[ds.ins.size() - 1]->amiga.useSample = false; + ds.ins[ds.ins.size() - 1]->amiga.useNoteMap = false; + + ds.ins[ds.ins.size() - 1]->type = DIV_INS_VRC6_SAW; + + if (ins->std.get_macro(DIV_MACRO_VOL, false)->len > 0) { + for (int mm = 0; mm < ins->std.get_macro(DIV_MACRO_VOL, false)->len; mm++) { + if (ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_VOL, false)->val[mm] < 16) { + int vall = ins->std.get_macro(DIV_MACRO_VOL, false)->val[mm]; + ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_VOL, false)->val[mm] = vall * 42 / 15; + } + } + } + + ins_vrc6_saw_conv[i][0] = i; + ins_vrc6_saw_conv[i][1] = ds.ins.size() - 1; + + go_to_end = true; + } + + if (go_to_end) { + goto end2; + } + } + } + } + } + } + + end2:; + } + + for (int i = 0; i < init_inst_num; i++) { + for (int ii = 0; ii < total_chans; ii++) { + for (size_t j = 0; j < ds.subsong.size(); j++) { + for (int k = 0; k < DIV_MAX_PATTERNS; k++) { + if (ds.subsong[j]->pat[ii].data[k] == NULL) + continue; + for (int l = 0; l < ds.subsong[j]->patLen; l++) { + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == i) // instrument + { + DivInstrument* ins = ds.ins[i]; + bool go_to_end = false; + + if (ins->type != DIV_INS_NES && (ii == mmc5_chans[0] || ii == mmc5_chans[1] || ii < 5)) // we encountered VRC6 (or whatever?) instrument on NES/MMC5 channel + { + DivInstrument* insnew = new DivInstrument; + ds.ins.push_back(insnew); + + copyInstrument(ds.ins[ds.ins.size() - 1], ins); + + ds.ins[ds.ins.size() - 1]->name += " [NES copy]"; + + ds.ins[ds.ins.size() - 1]->type = DIV_INS_NES; + + if (ins->type == DIV_INS_VRC6) { + if (insnew->std.get_macro(DIV_MACRO_DUTY, false)->len > 0) // convert duties for NES + { + for (int mm = 0; mm < insnew->std.get_macro(DIV_MACRO_DUTY, false)->len; mm++) { + switch (insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm]) { + case 0: + case 1: { + insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 0; + break; + } + case 2: + case 3: + case 4: + case 5: { + insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 1; + break; + } + case 6: + case 7: { + insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 2; + break; + } + default: + break; + } + } + } + } + + ins_nes_conv[i][0] = i; + ins_nes_conv[i][1] = ds.ins.size() - 1; + + go_to_end = true; + } + + if (go_to_end) { + goto end3; + } + } + } + } + } + } + + end3:; + } + + for (int i = 0; i < 256; i++) { + if (ins_vrc6_conv[i][0] != -1 || ins_vrc6_saw_conv[i][0] != -1 || ins_nes_conv[i][0] != -1) { + for (int ii = 0; ii < total_chans; ii++) { + for (size_t j = 0; j < ds.subsong.size(); j++) { + for (int k = 0; k < DIV_MAX_PATTERNS; k++) { + if (ds.subsong[j]->pat[ii].data[k] == NULL) + continue; + for (int l = 0; l < ds.subsong[j]->patLen; l++) { + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_vrc6_conv[i][0] && (ii == vrc6_chans[0] || ii == vrc6_chans[1])) // change ins index + { + ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_vrc6_conv[i][1]; + } + + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_vrc6_saw_conv[i][0] && ii == vrc6_saw_chan) { + ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_vrc6_saw_conv[i][1]; + } + + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_nes_conv[i][0] && (ii == mmc5_chans[0] || ii == mmc5_chans[1] || ii < 5)) { + ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_nes_conv[i][1]; + } + } + } + } + } + } + } + */ + + ds.delayBehavior=0; ds.version=DIV_VERSION_FTM; + ds.insLen = ds.ins.size(); + ds.sampleLen = ds.sample.size(); + ds.waveLen = ds.wave.size(); if (active) quitDispatch(); BUSY_BEGIN_SOFT; @@ -601,11 +2427,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { } } catch (EndOfFileException& e) { logE("premature end of file!"); - lastError="incomplete file"; + lastError = "incomplete file"; delete[] file; return false; } delete[] file; return true; } - diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 82054dd6c..0cd32fa76 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -691,7 +691,7 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS } } -bool DivEngine::loadFur(unsigned char* file, size_t len) { +bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { unsigned int insPtr[256]; unsigned int wavePtr[256]; unsigned int samplePtr[256]; @@ -720,6 +720,11 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.version=reader.readS(); logI("module version %d (0x%.2x)",ds.version,ds.version); + if (variantID!=DIV_FUR_VARIANT_VANILLA) { + logW("Furnace variant detected: %d",variantID); + addWarning("this module was created with a downstream version of Furnace. certain features may not be compatible."); + } + if (ds.version>DIV_ENGINE_VERSION) { logW("this module was created with a more recent version of Furnace!"); addWarning("this module was created with a more recent version of Furnace!"); @@ -2052,6 +2057,27 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // OPLL fixedAll compat + if (ds.version<194) { + for (int i=0; iwriteText("- macros:\n"); + wroteMacroHeader=true; + } + w->writeText(fmt::sprintf(" - %s:",name)); + int len=m.len; + switch (m.open&6) { + case 2: + len=16; + w->writeText(" [ADSR]"); + break; + case 4: + len=16; + w->writeText(" [LFO]"); + break; + } + if (m.mode) { + w->writeText(fmt::sprintf(" [MODE %d]",m.mode)); + } + if (m.delay>0) { + w->writeText(fmt::sprintf(" [DELAY %d]",m.delay)); + } + if (m.speed>1) { + w->writeText(fmt::sprintf(" [SPEED %d]",m.speed)); + } + for (int i=0; iwriteText(" |"); + } + if (i==m.rel) { + w->writeText(" /"); + } + w->writeText(fmt::sprintf(" %d",m.val[i])); + } + w->writeText("\n"); +} + +SafeWriter* DivEngine::saveText(bool separatePatterns) { + saveLock.lock(); + + SafeWriter* w=new SafeWriter; + w->init(); + + w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION)); + w->writeText(fmt::sprintf("- name: %s\n",song.name)); + w->writeText(fmt::sprintf("- author: %s\n",song.author)); + w->writeText(fmt::sprintf("- album: %s\n",song.category)); + w->writeText(fmt::sprintf("- system: %s\n",song.systemName)); + w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning)); + + w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen)); + w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen)); + w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen)); + + w->writeText("# Sound Chips\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("- %s\n",getSystemName(song.system[i]))); + w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i])); + w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i])); + w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i])); + w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i])); + + String sysFlags=song.systemFlags[i].toString(); + + if (!sysFlags.empty()) { + w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags)); + } + } + + if (!song.notes.empty()) { + w->writeText("\n# Song Comments\n\n"); + w->writeText(song.notes); + } + + w->writeText("\n# Instruments\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name)); + + w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { + w->writeText("- FM parameters:\n"); + w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); + w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); + w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms)); + w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); + w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); + w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); + w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); + w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); + w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); + w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); + w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); + w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); + + for (int j=0; jfm.ops; j++) { + DivInstrumentFM::Operator& op=ins->fm.op[j]; + + w->writeText(fmt::sprintf(" - operator %d:\n",j)); + w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0])); + w->writeText(fmt::sprintf(" - AM: %d\n",op.am)); + w->writeText(fmt::sprintf(" - AR: %d\n",op.ar)); + w->writeText(fmt::sprintf(" - DR: %d\n",op.dr)); + w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult)); + w->writeText(fmt::sprintf(" - RR: %d\n",op.rr)); + w->writeText(fmt::sprintf(" - SL: %d\n",op.sl)); + w->writeText(fmt::sprintf(" - TL: %d\n",op.tl)); + w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2)); + w->writeText(fmt::sprintf(" - RS: %d\n",op.rs)); + w->writeText(fmt::sprintf(" - DT: %d\n",op.dt)); + w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r)); + w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv)); + w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam)); + w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb)); + w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt)); + w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl)); + w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus)); + w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib)); + w->writeText(fmt::sprintf(" - WS: %d\n",op.ws)); + w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr)); + w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs)); + } + } + + if (ins->type==DIV_INS_ESFM) { + w->writeText("- ESFM parameters:\n"); + w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); + + for (int j=0; jfm.ops; j++) { + DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; + + w->writeText(fmt::sprintf(" - operator %d:\n",j)); + w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay)); + w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl)); + w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn)); + w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0])); + w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0])); + w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct)); + w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt)); + w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0])); + } + } + + if (ins->type==DIV_INS_GB) { + w->writeText("- Game Boy parameters:\n"); + w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); + w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); + w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); + w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); + w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); + w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0])); + if (ins->gb.hwSeqLen>0) { + w->writeText(" - hardware sequence:\n"); + for (int j=0; jgb.hwSeqLen; j++) { + w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data)); + } + } + } + + bool header=false; + writeTextMacro(w,ins->std.volMacro,"vol",header); + writeTextMacro(w,ins->std.arpMacro,"arp",header); + writeTextMacro(w,ins->std.dutyMacro,"duty",header); + writeTextMacro(w,ins->std.waveMacro,"wave",header); + writeTextMacro(w,ins->std.pitchMacro,"pitch",header); + writeTextMacro(w,ins->std.panLMacro,"panL",header); + writeTextMacro(w,ins->std.panRMacro,"panR",header); + writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header); + writeTextMacro(w,ins->std.ex1Macro,"ex1",header); + writeTextMacro(w,ins->std.ex2Macro,"ex2",header); + writeTextMacro(w,ins->std.ex3Macro,"ex3",header); + writeTextMacro(w,ins->std.ex4Macro,"ex4",header); + writeTextMacro(w,ins->std.ex5Macro,"ex5",header); + writeTextMacro(w,ins->std.ex6Macro,"ex6",header); + writeTextMacro(w,ins->std.ex7Macro,"ex7",header); + writeTextMacro(w,ins->std.ex8Macro,"ex8",header); + writeTextMacro(w,ins->std.algMacro,"alg",header); + writeTextMacro(w,ins->std.fbMacro,"fb",header); + writeTextMacro(w,ins->std.fmsMacro,"fms",header); + writeTextMacro(w,ins->std.amsMacro,"ams",header); + + // TODO: the rest + w->writeText("\n"); + } + + w->writeText("\n# Wavetables\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1)); + for (int j=0; j<=wave->len; j++) { + w->writeText(fmt::sprintf(" %d",wave->data[j])); + } + w->writeText("\n"); + } + + w->writeText("\n# Samples\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name)); + + w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth)); + w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen())); + w->writeText(fmt::sprintf("- samples: %d\n",sample->samples)); + w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate)); + w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate)); + w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0])); + if (sample->loop) { + w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart)); + w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd)); + w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3])); + } + w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0])); + w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0])); + + // TODO' render matrix + unsigned char* buf=(unsigned char*)sample->getCurBuf(); + unsigned int bufLen=sample->getCurBufLen(); + w->writeText("\n```"); + for (unsigned int i=0; iwriteText(fmt::sprintf("\n%.8X:",i)); + w->writeText(fmt::sprintf(" %.2X",buf[i])); + } + w->writeText("\n```\n\n"); + } + + w->writeText("\n# Subsongs\n\n"); + + for (size_t i=0; iwriteText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name)); + + w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz)); + w->writeText(fmt::sprintf("- speeds:")); + for (int j=0; jspeeds.len; j++) { + w->writeText(fmt::sprintf(" %d",s->speeds.val[j])); + } + w->writeText("\n"); + w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD)); + w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase)); + w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen)); + w->writeText(fmt::sprintf("\norders:\n```\n")); + + for (int j=0; jordersLen; j++) { + w->writeText(fmt::sprintf("%.2X |",j)); + for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); + } + w->writeText("\n"); + } + w->writeText("```\n\n## Patterns\n\n"); + + if (separatePatterns) { + w->writeText("TODO: separate patterns\n\n"); + } else { + for (int j=0; jordersLen; j++) { + w->writeText(fmt::sprintf("----- ORDER %.2X\n",j)); + + for (int k=0; kpatLen; k++) { + w->writeText(fmt::sprintf("%.2X ",k)); + + for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); + + int note=p->data[k][0]; + int octave=p->data[k][1]; + + if (note==0 && octave==0) { + w->writeText("|... "); + } else if (note==100) { + w->writeText("|OFF "); + } else if (note==101) { + w->writeText("|=== "); + } else if (note==102) { + w->writeText("|REL "); + } else if ((octave>9 && octave<250) || note>12) { + w->writeText("|??? "); + } else { + if (octave>=128) octave-=256; + if (note>11) { + note-=12; + octave++; + } + w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave)); + } + + if (p->data[k][2]==-1) { + w->writeText(".. "); + } else { + w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff)); + } + + if (p->data[k][3]==-1) { + w->writeText(".."); + } else { + w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff)); + } + + for (int m=0; mpat[l].effectCols; m++) { + if (p->data[k][4+(m<<1)]==-1) { + w->writeText(" .."); + } else { + w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff)); + } + if (p->data[k][5+(m<<1)]==-1) { + w->writeText(".."); + } else { + w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff)); + } + } + } + + w->writeText("\n"); + } + } + } + + } + + saveLock.unlock(); + return w; +} diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 37bd99f57..6225765aa 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -83,7 +83,8 @@ bool DivInstrumentGB::operator==(const DivInstrumentGB& other) { _C(soundLen) && _C(hwSeqLen) && _C(softEnv) && - _C(alwaysInit) + _C(alwaysInit) && + _C(doubleWave) ); } @@ -259,6 +260,40 @@ bool DivInstrumentPowerNoise::operator==(const DivInstrumentPowerNoise& other) { #undef _C +#define CONSIDER(x,t) \ + case t: \ + return &x; \ + break; + +DivInstrumentMacro* DivInstrumentSTD::macroByType(DivMacroType type) { + switch (type) { + CONSIDER(volMacro,DIV_MACRO_VOL) + CONSIDER(arpMacro,DIV_MACRO_ARP) + CONSIDER(dutyMacro,DIV_MACRO_DUTY) + CONSIDER(waveMacro,DIV_MACRO_WAVE) + CONSIDER(pitchMacro,DIV_MACRO_PITCH) + CONSIDER(ex1Macro,DIV_MACRO_EX1) + CONSIDER(ex2Macro,DIV_MACRO_EX2) + CONSIDER(ex3Macro,DIV_MACRO_EX3) + CONSIDER(algMacro,DIV_MACRO_ALG) + CONSIDER(fbMacro,DIV_MACRO_FB) + CONSIDER(fmsMacro,DIV_MACRO_FMS) + CONSIDER(amsMacro,DIV_MACRO_AMS) + CONSIDER(panLMacro,DIV_MACRO_PAN_LEFT) + CONSIDER(panRMacro,DIV_MACRO_PAN_RIGHT) + CONSIDER(phaseResetMacro,DIV_MACRO_PHASE_RESET) + CONSIDER(ex4Macro,DIV_MACRO_EX4) + CONSIDER(ex5Macro,DIV_MACRO_EX5) + CONSIDER(ex6Macro,DIV_MACRO_EX6) + CONSIDER(ex7Macro,DIV_MACRO_EX7) + CONSIDER(ex8Macro,DIV_MACRO_EX8) + } + + return NULL; +} + +#undef CONSIDER + #define FEATURE_BEGIN(x) \ w->write(x,2); \ size_t featStartSeek=w->tell(); \ @@ -450,6 +485,7 @@ void DivInstrument::writeFeatureGB(SafeWriter* w) { w->writeC(gb.soundLen); w->writeC( + (gb.doubleWave?4:0)| (gb.alwaysInit?2:0)| (gb.softEnv?1:0) ); @@ -1064,6 +1100,18 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo break; case DIV_INS_DAVE: break; + case DIV_INS_NDS: + featureSM=true; + if (amiga.useSample) featureSL=true; + break; + case DIV_INS_GBA_DMA: + featureSM=true; + featureSL=true; + break; + case DIV_INS_GBA_MINMOD: + featureSM=true; + featureSL=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: @@ -1587,6 +1635,7 @@ void DivInstrument::readFeatureGB(SafeReader& reader, short version) { gb.soundLen=reader.readC(); next=reader.readC(); + if (version>=196) gb.doubleWave=next&4; gb.alwaysInit=next&2; gb.softEnv=next&1; @@ -2952,6 +3001,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version, DivS type=0; } else if (memcmp(magic,"INS2",4)==0) { type=1; + } else if (memcmp(magic,"IN2B",4)==0) { // DIV_FUR_VARIANT_B + type=1; } else if (memcmp(magic,"FINS",4)==0) { type=2; } else { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 714adeea5..21de82121 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -89,6 +89,9 @@ enum DivInstrumentType: unsigned short { DIV_INS_POWERNOISE=56, DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_DAVE=58, + DIV_INS_NDS=59, + DIV_INS_GBA_DMA=60, + DIV_INS_GBA_MINMOD=61, DIV_INS_MAX, DIV_INS_NULL }; @@ -329,6 +332,9 @@ struct DivInstrumentSTD { damMacro(DIV_MACRO_OP_DAM), dvbMacro(DIV_MACRO_OP_DVB), egtMacro(DIV_MACRO_OP_EGT), kslMacro(DIV_MACRO_OP_KSL), susMacro(DIV_MACRO_OP_SUS), vibMacro(DIV_MACRO_OP_VIB), wsMacro(DIV_MACRO_OP_WS), ksrMacro(DIV_MACRO_OP_KSR) {} } opMacros[4]; + + DivInstrumentMacro* macroByType(DivMacroType type); + DivInstrumentSTD(): volMacro(DIV_MACRO_VOL,true), arpMacro(DIV_MACRO_ARP), @@ -378,7 +384,7 @@ struct DivInstrumentSTD { struct DivInstrumentGB { unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; - bool softEnv, alwaysInit; + bool softEnv, alwaysInit, doubleWave; enum HWSeqCommands: unsigned char { DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_SWEEP, @@ -406,7 +412,8 @@ struct DivInstrumentGB { soundLen(64), hwSeqLen(0), softEnv(false), - alwaysInit(false) { + alwaysInit(false), + doubleWave(false) { memset(hwSeq,0,256*sizeof(HWSeqCommandGB)); } }; diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 9555019bd..905fcd787 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -922,11 +922,22 @@ bool DivPlatformAmiga::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformAmiga::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformAmiga::renderSamples(int sysID) { memset(sampleMem,0,2097152); memset(sampleOff,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Chip Memory"; + + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM,"Wave RAM",-1,0,1024)); + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"End of Sample",-1,1024,1026)); + // first 1024 bytes reserved for wavetable // the next 2 bytes are reserved for end of sample size_t memPos=1026; @@ -947,6 +958,7 @@ void DivPlatformAmiga::renderSamples(int sysID) { if (actualLength>0) { sampleOff[i]=memPos; memcpy(&sampleMem[memPos],s->data8,actualLength); + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength)); memPos+=actualLength; } // align memPos to short @@ -954,6 +966,9 @@ void DivPlatformAmiga::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleMemLen=memPos; + + memCompo.capacity=1<device_start(); diff --git a/src/engine/platform/c140.cpp b/src/engine/platform/c140.cpp index 14903506b..bde3e1fe7 100644 --- a/src/engine/platform/c140.cpp +++ b/src/engine/platform/c140.cpp @@ -595,11 +595,19 @@ bool DivPlatformC140::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformC140::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformC140::renderSamples(int sysID) { memset(sampleMem,0,is219?524288:16777216); memset(sampleOff,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -658,6 +666,7 @@ void DivPlatformC140::renderSamples(int sysID) { } sampleOff[i]=memPos>>1; sampleLoaded[i]=true; + memCompo.entries.push_back(DivMemoryEntry((DivMemoryEntryType)(DIV_MEMORY_BANK0+((memPos>>17)&3)),"Sample",i,memPos,memPos+length)); memPos+=length; } else { // C140 (16-bit) unsigned int length=s->length16+4; @@ -704,10 +713,14 @@ void DivPlatformC140::renderSamples(int sysID) { } sampleOff[i]=memPos>>1; sampleLoaded[i]=true; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+length)); memPos+=length; } } sampleMemLen=memPos+256; + + memCompo.used=sampleMemLen; + memCompo.capacity=getSampleMemCapacity(0); } void DivPlatformC140::set219(bool is_219) { diff --git a/src/engine/platform/c140.h b/src/engine/platform/c140.h index ecd8bc239..fb385c5eb 100644 --- a/src/engine/platform/c140.h +++ b/src/engine/platform/c140.h @@ -74,6 +74,7 @@ class DivPlatformC140: public DivDispatch { FixedQueue writes; struct c140_t c140; struct c219_t c219; + DivMemoryComposition memCompo; unsigned char regPool[512]; char bankLabel[4][4]; friend void putDispatchChip(void*,int); @@ -108,6 +109,7 @@ class DivPlatformC140: public DivDispatch { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int getClockRangeMin(); int getClockRangeMax(); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 9130cdccf..fed93e4dc 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -198,7 +198,7 @@ void DivPlatformC64::tick(bool sysTick) { } chan[i].freqChanged=true; } - if (chan[i].std.alg.had) { // new cutoff macro + if (chan[i].std.alg.had && (_i==2 || macroRace)) { // new cutoff macro DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); if (ins->c64.filterIsAbs) { filtCut=MIN(2047,chan[i].std.alg.val); @@ -732,6 +732,7 @@ void DivPlatformC64::setFlags(const DivConfig& flags) { keyPriority=flags.getBool("keyPriority",true); no1EUpdate=flags.getBool("no1EUpdate",false); multiplyRel=flags.getBool("multiplyRel",false); + macroRace=flags.getBool("macroRace",false); testAD=((flags.getInt("testAttack",0)&15)<<4)|(flags.getInt("testDecay",0)&15); testSR=((flags.getInt("testSustain",0)&15)<<4)|(flags.getInt("testRelease",0)&15); initResetTime=flags.getInt("initResetTime",2); diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index c24ee3159..eb4426ca0 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -77,7 +77,7 @@ class DivPlatformC64: public DivDispatch { unsigned char sidCore; int filtCut, resetTime, initResetTime; - bool keyPriority, sidIs6581, needInitTables, no1EUpdate, multiplyRel; + bool keyPriority, sidIs6581, needInitTables, no1EUpdate, multiplyRel, macroRace; unsigned char chanOrder[3]; unsigned char testAD, testSR; diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 5a7c01c08..2299752a6 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -1203,11 +1203,19 @@ bool DivPlatformES5506::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformES5506::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformES5506::renderSamples(int sysID) { memset(sampleMem,0,getSampleMemCapacity()); memset(sampleOffES5506,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample Memory"; + size_t memPos=128; // add silent at begin and end of each bank for reverse playback for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -1236,9 +1244,13 @@ void DivPlatformES5506::renderSamples(int sysID) { } sampleOffES5506[i]=memPos; sampleLoaded[i]=true; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+length)); memPos+=length; } sampleMemLen=memPos+256; + + memCompo.used=sampleMemLen; + memCompo.capacity=16777216; } int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/es5506.h b/src/engine/platform/es5506.h index b51ee9f2c..04ed8beae 100644 --- a/src/engine/platform/es5506.h +++ b/src/engine/platform/es5506.h @@ -278,6 +278,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { unsigned char initChanMax, chanMax; es5506_core es5506; + DivMemoryComposition memCompo; unsigned char regPool[4*16*128]; // 7 bit page x 16 registers per page x 32 bit per registers friend void putDispatchChip(void*,int); @@ -315,6 +316,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { 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 const DivMemoryComposition* getMemCompo(int index) override; virtual void renderSamples(int sysID) override; virtual const char** getRegisterSheet() override; virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; diff --git a/src/engine/platform/esfm.cpp b/src/engine/platform/esfm.cpp index 090254a00..3adab20f5 100644 --- a/src/engine/platform/esfm.cpp +++ b/src/engine/platform/esfm.cpp @@ -983,7 +983,7 @@ int DivPlatformESFM::getRegisterPoolSize() { void DivPlatformESFM::reset() { while (!writes.empty()) writes.pop(); - ESFM_init(&chip); + ESFM_init(&chip,isFast?1:0); // set chip to native mode ESFM_write_reg(&chip, 0x105, 0x80); // ensure NTS bit in register 0x408 is reset, for smooth envelope rate scaling @@ -1053,6 +1053,10 @@ void DivPlatformESFM::setFlags(const DivConfig& flags) { rate=chipClock/288.0; } +void DivPlatformESFM::setFast(bool fast) { + isFast=fast; +} + int DivPlatformESFM::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { parent=p; dumpWrites=false; diff --git a/src/engine/platform/esfm.h b/src/engine/platform/esfm.h index 58fafbfab..8321369bf 100644 --- a/src/engine/platform/esfm.h +++ b/src/engine/platform/esfm.h @@ -120,6 +120,7 @@ class DivPlatformESFM: public DivDispatch { }; FixedQueue writes; esfm_chip chip; + bool isFast; unsigned char regPool[ESFM_REG_POOL_SIZE]; short oldWrites[ESFM_REG_POOL_SIZE]; @@ -201,6 +202,7 @@ class DivPlatformESFM: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); void setFlags(const DivConfig& flags); + void setFast(bool fast); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); ~DivPlatformESFM(); diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 2098f140b..859c586bb 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -441,11 +441,19 @@ bool DivPlatformGA20::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformGA20::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformGA20::renderSamples(int sysID) { memset(sampleMem,0x00,getSampleMemCapacity()); memset(sampleOffGA20,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -458,6 +466,7 @@ void DivPlatformGA20::renderSamples(int sysID) { int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-1,length); if (actualLength>0) { sampleOffGA20[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+1)); for (int j=0; jdata8[j]))^0x80; @@ -473,10 +482,13 @@ void DivPlatformGA20::renderSamples(int sysID) { } else { sampleLoaded[i]=true; } - // allign to 16 byte + // align to 16 byte memPos=(memPos+0xf)&~0xf; } sampleMemLen=memPos; + + memCompo.used=sampleMemLen; + memCompo.capacity=1048576; } int DivPlatformGA20::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/ga20.h b/src/engine/platform/ga20.h index 995a7cc15..51617849f 100644 --- a/src/engine/platform/ga20.h +++ b/src/engine/platform/ga20.h @@ -66,6 +66,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf { unsigned char* sampleMem; size_t sampleMemLen; iremga20_device ga20; + DivMemoryComposition memCompo; unsigned char regPool[32]; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -97,6 +98,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf { 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 const DivMemoryComposition* getMemCompo(int index) override; virtual void renderSamples(int chipID) override; virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; virtual void quit() override; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 3ae02dcf9..e942951ff 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -81,17 +81,41 @@ void DivPlatformGB::acquire(short** buf, size_t len) { } void DivPlatformGB::updateWave() { - rWrite(0x1a,0); - for (int i=0; i<16; i++) { - int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; - int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31]; - if (invertWave) { - nibble1^=15; - nibble2^=15; + if (doubleWave) { + rWrite(0x1a,0x40); // select 1 -> write to bank 0 + for (int i=0; i<16; i++) { + int nibble1=ws.output[((i<<1)+antiClickWavePos)&63]; + int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&63]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } + rWrite(0x30+i,(nibble1<<4)|nibble2); } - rWrite(0x30+i,(nibble1<<4)|nibble2); + rWrite(0x1a,0); // select 0 -> write to bank 1 + for (int i=0; i<16; i++) { + int nibble1=ws.output[((32+(i<<1))+antiClickWavePos)&63]; + int nibble2=ws.output[((33+(i<<1))+antiClickWavePos)&63]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } + rWrite(0x30+i,(nibble1<<4)|nibble2); + } + antiClickWavePos&=63; + } else { + rWrite(0x1a,model==GB_MODEL_AGB_NATIVE?0x40:0); + for (int i=0; i<16; i++) { + int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; + int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } + rWrite(0x30+i,(nibble1<<4)|nibble2); + } + antiClickWavePos&=31; } - antiClickWavePos&=31; } static unsigned char chanMuteMask[4]={ @@ -112,6 +136,13 @@ static unsigned char gbVolMap[16]={ 0x20, 0x20, 0x20, 0x20 }; +static unsigned char gbVolMapEx[16]={ + 0x00, 0x00, 0x00, 0x00, + 0x60, 0x60, 0x60, 0x60, + 0x40, 0x40, 0x40, 0x40, + 0xa0, 0xa0, 0x20, 0x20 +}; + static unsigned char noiseTable[256]={ 0, 0xf7, 0xf6, 0xf5, 0xf4, @@ -156,7 +187,7 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].outVol<0) chan[i].outVol=0; if (i==2) { - rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[chan[i].outVol]); chan[i].soundLen=64; } else { chan[i].envLen=0; @@ -188,7 +219,7 @@ void DivPlatformGB::tick(bool sysTick) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); } else if (!chan[i].softEnv) { if (parent->song.waveDutyIsVol) { - rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); + rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[(chan[i].std.duty.val&3)<<2]); } } } @@ -301,8 +332,8 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].keyOn) { if (i==2) { // wave rWrite(16+i*5,0x00); - rWrite(16+i*5,0x80); - rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + rWrite(16+i*5,doubleWave?0xa0:0x80); + rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[chan[i].outVol]); } else { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); @@ -379,11 +410,16 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].softEnv=ins->gb.softEnv; chan[c.chan].macroInit(ins); if (c.chan==2) { + doubleWave=(model==GB_MODEL_AGB_NATIVE) && ins->gb.doubleWave; if (chan[c.chan].wave<0) { chan[c.chan].wave=0; ws.changeWave1(chan[c.chan].wave); } - ws.init(ins,32,15,chan[c.chan].insChanged); + ws.init(ins,doubleWave?64:32,15,chan[c.chan].insChanged); + if (doubleWave!=lastDoubleWave) { + ws.changeWave1(chan[c.chan].wave); + lastDoubleWave=doubleWave; + } } if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) { if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { @@ -447,7 +483,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].vol=c.value; chan[c.chan].outVol=c.value; if (c.chan==2) { - rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]); + rWrite(16+c.chan*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[chan[c.chan].outVol]); } if (!chan[c.chan].softEnv) { chan[c.chan].envVol=chan[c.chan].vol; @@ -619,6 +655,8 @@ void DivPlatformGB::reset() { antiClickPeriodCount=0; antiClickWavePos=0; + doubleWave=false; + lastDoubleWave=false; } int DivPlatformGB::getPortaFloor(int ch) { @@ -630,7 +668,7 @@ int DivPlatformGB::getOutputCount() { } bool DivPlatformGB::getDCOffRequired() { - return (model==GB_MODEL_AGB); + return (model==GB_MODEL_AGB_NATIVE); } void DivPlatformGB::notifyInsChange(int ins) { @@ -676,7 +714,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { model=GB_MODEL_CGB_E; break; case 3: - model=GB_MODEL_AGB; + model=GB_MODEL_AGB_NATIVE; break; } invertWave=flags.getBool("invertWave",true); diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 6fe094020..fa0484dff 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -59,6 +59,8 @@ class DivPlatformGB: public DivDispatch { bool antiClickEnabled; bool invertWave; bool enoughAlready; + bool doubleWave; + bool lastDoubleWave; unsigned char lastPan; DivWaveSynth ws; struct QueuedWrite { diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp new file mode 100644 index 000000000..ec0dab3b3 --- /dev/null +++ b/src/engine/platform/gbadma.cpp @@ -0,0 +1,529 @@ +/** + * 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. + */ + +#define _USE_MATH_DEFINES +#include "gbadma.h" +#include "../engine.h" +#include "../filter.h" +#include + +#define CHIP_DIVIDER 16 + +void DivPlatformGBADMA::acquire(short** buf, size_t len) { + // HLE for now + int outL[2]={0,0}; + int outR[2]={0,0}; + for (size_t h=0; h=0 && chan[i].samplesong.sampleLen))) { + chan[i].audSub+=(1<=chan[i].freq) { + int posInc=chan[i].audSub/chan[i].freq; + chan[i].audSub-=chan[i].freq*posInc; + chan[i].audPos+=posInc; + chan[i].dmaCount+=posInc; + if (chan[i].dmaCount>=16 && chan[i].audPos>=(int)chan[i].audLen) { + chan[i].audPos%=chan[i].audLen; + } + chan[i].dmaCount&=15; + } + } else if (sampleLoaded[chan[i].sample]) { + DivSample* s=parent->getSample(chan[i].sample); + if (s->samples>0) { + if (chan[i].audPos>=0) { + unsigned int pos=(sampleOff[chan[i].sample]+chan[i].audPos)&0x01ffffff; + chan[i].audDat=sampleMem[pos]; + } + newSamp=true; + if (chan[i].audSub>=chan[i].freq) { + int posInc=chan[i].audSub/chan[i].freq; + chan[i].audSub-=chan[i].freq*posInc; + chan[i].audPos+=posInc; + chan[i].dmaCount+=posInc; + if (s->isLoopable()) { + if (chan[i].dmaCount>=16 && chan[i].audPos>=s->loopEnd) { + int loopStart=s->loopStart&~3; + int loopPos=chan[i].audPos-loopStart; + chan[i].audPos=(loopPos%(s->loopEnd-s->loopStart))+loopStart; + } + } else if (chan[i].audPos>=(int)s->samples) { + chan[i].sample=-1; + } + chan[i].dmaCount&=15; + } + } else { + chan[i].sample=-1; + chan[i].audSub=0; + chan[i].audPos=0; + } + } + } + if (!isMuted[i] && newSamp) { + int out=chan[i].audDat*(chan[i].vol*chan[i].envVol/2)<<1; + outL[i]=(chan[i].pan&2)?out:0; + outR[i]=(chan[i].pan&1)?out:0; + } + oscBuf[i]->data[oscBuf[i]->needle++]=(short)((outL[i]+outR[i])<<5); + } + int l=outL[0]+outL[1]; + int r=outR[0]+outR[1]; + l=(l>>(10-outDepth))<<(16-outDepth); + r=(r>>(10-outDepth))<<(16-outDepth); + if (l<-32768) l=-32768; + if (l>32767) l=32767; + if (r<-32768) r=-32768; + if (r>32767) r=32767; + buf[0][h]=(short)l; + buf[1][h]=(short)r; + } +} + +void DivPlatformGBADMA::tick(bool sysTick) { + for (int i=0; i<2; i++) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].envVol=chan[i].std.vol.val; + if (ins->type==DIV_INS_AMIGA) chan[i].envVol/=32; + else if (chan[i].envVol>2) chan[i].envVol=2; + } + 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].useWave && chan[i].std.wave.had) { + 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].keyOff) chan[i].keyOn=true; + } + } + if (chan[i].useWave && chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + 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 (ins->type==DIV_INS_AMIGA) { + if (chan[0].std.panL.had) { + chan[0].pan=(chan[0].pan&~2)|(chan[0].std.panL.val>0?2:0); + } + if (chan[0].std.panR.had) { + chan[0].pan=(chan[0].pan&~1)|(chan[0].std.panR.val>0?1:0); + } + } else { + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + } + } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + chan[i].audPos=0; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + double off=1.0; + if (!chan[i].useWave && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].sample); + off=(s->centerRate>=1)?(8363.0/(double)s->centerRate):1.0; + } + chan[i].freq=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); + + // emulate prescaler rounding + if (chan[i].freq<65536) { + if (chan[i].freq<1) chan[i].freq=1; + } else if (chan[i].freq<65536*64) { + chan[i].freq=chan[i].freq&~63; + } else if (chan[i].freq<65536*256) { + chan[i].freq=chan[i].freq&~255; + } else { + chan[i].freq=chan[i].freq&~1024; + if (chan[i].freq>65536*1024) chan[i].freq=65536*1024; + } + if (chan[i].keyOn) { + if (!chan[i].std.vol.had) { + chan[i].envVol=2; + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } +} + +int DivPlatformGBADMA::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].audLen=ins->amiga.waveLen+1; + wtMemCompo.entries[c.chan].end=wtMemCompo.entries[c.chan].begin+chan[c.chan].audLen; + if (chan[c.chan].insChanged) { + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.setWidth(chan[c.chan].audLen); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + } + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } + chan[c.chan].useWave=false; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } + if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + if (chan[c.chan].setPos) { + chan[c.chan].setPos=false; + } else { + chan[c.chan].audPos=0; + } + chan[c.chan].audSub=0; + chan[c.chan].audDat=0; + chan[c.chan].dmaCount=0; + 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].envVol=2; + } + if (chan[c.chan].useWave) { + chan[c.chan].ws.init(ins,chan[c.chan].audLen,255,chan[c.chan].insChanged); + } + chan[c.chan].insChanged=false; + 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; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=MIN(c.value,2); + if (!chan[c.chan].std.vol.has) { + chan[c.chan].envVol=2; + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PANNING: + chan[c.chan].pan=0; + chan[c.chan].pan|=(c.value>0)?2:0; + chan[c.chan].pan|=(c.value2>0)?1:0; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) break; + chan[c.chan].wave=c.value; + chan[c.chan].keyOn=true; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + break; + case DIV_CMD_NOTE_PORTA: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].sample=ins->amiga.getSample(c.value2); + 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_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_AMIGA)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + if (chan[c.chan].useWave) break; + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + break; + case DIV_CMD_GET_VOLMAX: + return 2; + 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_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformGBADMA::updateWave(int ch) { + int addr=ch*256; + for (unsigned int i=0; i=512) break; + wtMem[addr+i]=(signed char)(chan[ch].ws.output[i]-128); + } +} + +void DivPlatformGBADMA::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformGBADMA::forceIns() { + for (int i=0; i<2; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + chan[i].audPos=0; + chan[i].sample=-1; + } +} + +void* DivPlatformGBADMA::getChanState(int ch) { + return &chan; +} + +DivDispatchOscBuffer* DivPlatformGBADMA::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +void DivPlatformGBADMA::reset() { + for (int i=0; i<2; i++) { + chan[i]=DivPlatformGBADMA::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255); + chan[i].audDat=0; + } +} + +int DivPlatformGBADMA::getOutputCount() { + return 2; +} + +DivMacroInt* DivPlatformGBADMA::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformGBADMA::getPan(int ch) { + return ((chan[ch].pan&2)<<7)|(chan[ch].pan&1); +} + +DivSamplePos DivPlatformGBADMA::getSamplePos(int ch) { + if (ch>=2 || !chan[ch].active || + chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) { + return DivSamplePos(); + } + return DivSamplePos( + chan[ch].sample, + chan[ch].audPos, + chipClock/chan[ch].freq + ); +} + +void DivPlatformGBADMA::notifyInsChange(int ins) { + for (int i=0; i<2; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformGBADMA::notifyWaveChange(int wave) { + for (int i=0; i<2; i++) { + if (chan[i].useWave && chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + updateWave(i); + } + } +} + +void DivPlatformGBADMA::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +const void* DivPlatformGBADMA::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformGBADMA::getSampleMemCapacity(int index) { + return index == 0 ? 33554432 : 0; +} + +size_t DivPlatformGBADMA::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformGBADMA::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +const DivMemoryComposition* DivPlatformGBADMA::getMemCompo(int index) { + switch (index) { + case 0: return &romMemCompo; + case 1: return &wtMemCompo; + } + return NULL; +} + +void DivPlatformGBADMA::renderSamples(int sysID) { + size_t maxPos=getSampleMemCapacity(); + memset(sampleMem,0,maxPos); + romMemCompo.entries.clear(); + romMemCompo.capacity=maxPos; + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + int length=s->length8; + int actualLength=MIN((int)(maxPos-memPos),length); + if (actualLength>0) { + sampleOff[i]=memPos; + memcpy(&sampleMem[memPos],s->data8,actualLength); + memPos+=actualLength; + } + if (actualLength>outDepth; + for (int i=0; i<2; i++) { + oscBuf[i]->rate=rate; + } +} + +int DivPlatformGBADMA::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + wtMemCompo=DivMemoryComposition(); + wtMemCompo.name="Wavetable RAM"; + wtMemCompo.used=256*2; + wtMemCompo.capacity=256*2; + wtMemCompo.memory=(unsigned char*)wtMem; + wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED; + for (int i=0; i<2; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256)); + } + sampleMem=new signed char[getSampleMemCapacity()]; + sampleMemLen=0; + romMemCompo=DivMemoryComposition(); + romMemCompo.name="Sample ROM"; + setFlags(flags); + reset(); + return 2; +} + +void DivPlatformGBADMA::quit() { + delete[] sampleMem; + for (int i=0; i<2; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/gbadma.h b/src/engine/platform/gbadma.h new file mode 100644 index 000000000..8ea38e545 --- /dev/null +++ b/src/engine/platform/gbadma.h @@ -0,0 +1,101 @@ +/** + * 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 _GBA_DMA_H +#define _GBA_DMA_H + +#include "../dispatch.h" +#include "../waveSynth.h" + +class DivPlatformGBADMA: public DivDispatch { + struct Channel: public SharedChannel { + unsigned int audLoc; + unsigned short audLen; + int audDat; + int audPos; + int audSub; + int dmaCount; + int sample, wave; + int pan; + bool useWave, setPos; + int envVol; + DivWaveSynth ws; + Channel(): + SharedChannel(2), + audLoc(0), + audLen(0), + audDat(0), + audPos(0), + audSub(0), + dmaCount(0), + sample(-1), + wave(-1), + pan(3), + useWave(false), + setPos(false), + envVol(2) {} + }; + Channel chan[2]; + DivDispatchOscBuffer* oscBuf[2]; + bool isMuted[2]; + unsigned int sampleOff[256]; + bool sampleLoaded[256]; + int outDepth; + + signed char* sampleMem; + size_t sampleMemLen; + // maximum wavetable length is currently hardcoded to 256 + signed char wtMem[256*2]; + DivMemoryComposition romMemCompo; + DivMemoryComposition wtMemCompo; + + 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); + DivDispatchOscBuffer* getOscBuffer(int chan); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); + DivSamplePos getSamplePos(int ch); + void setFlags(const DivConfig& flags); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); + void renderSamples(int chipID); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + + private: + void updateWave(int ch); +}; + +#endif diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp new file mode 100644 index 000000000..30d9da7c0 --- /dev/null +++ b/src/engine/platform/gbaminmod.cpp @@ -0,0 +1,782 @@ +/** + * 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 "gbaminmod.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +#define CHIP_FREQBASE 16777216 + +#define rWrite(a,v) {regPool[a]=v;} + +const char* regCheatSheetMinMod[]={ + "CHx_Counter", "x0", + "CHx_Address", "x2", + "CHx_LastLeft", "x4", + "CHx_LastRight", "x6", + "CHx_Freq", "x8", + "CHx_LoopEnd", "xA", + "CHx_LoopStart", "xC", + "CHx_VolumeLeft", "xE", + "CHx_VolumeRight", "xF", + NULL +}; + +const char** DivPlatformGBAMinMod::getRegisterSheet() { + return regCheatSheetMinMod; +} + +void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { + size_t sampPos=mixBufReadPos&3; + bool newSamp=false; + // cache channel registers that might change + struct { + uint64_t address; + unsigned int freq, loopEnd, loopStart; + short volL, volR; + } chState[16]; + for (int i=0; i=sampCycles) { + // the driver generates 4 samples at a time and can be start-offset + sampPos=mixBufReadPos&3; + if (sampPos==mixBufOffset) { + for (size_t j=mixBufOffset; j<4; j++) { + mixOut[0][j]=0; + mixOut[1][j]=0; + } + for (int i=0; i>32; + chState[i].address+=((uint64_t)chState[i].freq)<<8; + unsigned int newAddr=chState[i].address>>32; + if (newAddr!=lastAddr) { + if (newAddr>=chState[i].loopEnd) { + newAddr=newAddr-chState[i].loopEnd+chState[i].loopStart; + chState[i].address=(chState[i].address&0xffffffff)|((uint64_t)newAddr<<32); + } + int newSamp=0; + switch (newAddr>>24) { + case 2: // wavetable + newAddr&=0x0003ffff; + if (newAddr0x800) { + newSamp=mixBuf[(newAddr-0x800)/1024][newAddr&1023]; + } + break; + case 8: // sample + case 9: + case 10: + case 11: + case 12: + newSamp=sampleMem[newAddr&0x01ffffff]; + break; + } + chanOut[i][0]=newSamp*chState[i].volL; + chanOut[i][1]=newSamp*chState[i].volR; + } + int outL=chanOut[i][0]; + int outR=chanOut[i][1]; + int outA=(chan[i].invertL==chan[i].invertR)?outL+outR:outL-outR; + mixOut[0][j]+=(unsigned char)(outL>>15); + mixOut[1][j]+=(unsigned char)(outR>>15); + oscOut[i][j]=volScale>0?outA*64/volScale:0; + } + } + for (size_t j=mixBufOffset; j<4; j++) { + mixBuf[mixBufPage][mixBufWritePos]=mixOut[0][j]; + mixBuf[mixBufPage+1][mixBufWritePos]=mixOut[1][j]; + mixBufWritePos++; + } + mixBufOffset=0; + } + newSamp=true; + mixBufReadPos++; + sampTimer-=sampCycles; + } + if (newSamp) { + // assuming max PCM FIFO volume + sampL=((short)mixOut[0][sampPos]<<8)&(0xff80<<(9-dacDepth)); + sampR=((short)mixOut[1][sampPos]<<8)&(0xff80<<(9-dacDepth)); + newSamp=false; + } + buf[0][h]=sampL; + buf[1][h]=sampR; + for (int i=0; idata[oscBuf[i]->needle++]=oscOut[i][sampPos]; + } + for (int i=chanMax; i<16; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=0; + } + while (updTimer>=updCycles) { + // flip buffer + // logV("ut=%d,pg=%d,w=%d,r=%d,sc=%d,st=%d",updTimer,mixBufPage,mixBufWritePos,mixBufReadPos,sampCycles,sampTimer); + mixMemCompo.entries[mixBufPage].end=mixMemCompo.entries[mixBufPage].begin+mixBufWritePos; + mixMemCompo.entries[mixBufPage+1].end=mixMemCompo.entries[mixBufPage+1].begin+mixBufWritePos; + mixBufPage=(mixBufPage+2)%(mixBufs*2); + memset(mixBuf[mixBufPage],0,sizeof(mixBuf[mixBufPage])); + memset(mixBuf[mixBufPage+1],0,sizeof(mixBuf[mixBufPage+1])); + // emulate buffer loss prevention and buffer copying + sampsRendered+=mixBufReadPos; + mixBufOffset=mixBufWritePos-mixBufReadPos; + for (size_t j=0; jgetCurHz(); + float updCyclesNew=(hz>=1)?(16777216.f/hz):1; + // the maximum buffer size in the default multi-rate config is 1024 samples + // (so 15 left/right buffers + mixer code fit the entire 32k of internal RAM) + // if the driver determines that the current tick rate is too low, it will + // internally double the rate until the resulting buffer size fits + while (true) { + updCycles=floorf(updCyclesNew); + // emulate prescaler rounding + if (updCycles>=65536*256) { + updCycles&=~1024; + } else if (updCycles>=65536*64) { + updCycles&=~256; + } else if (updCycles>=65536) { + updCycles&=~64; + } + unsigned int bufSize=(updCycles/sampCycles+3)&~3; + if (bufSize<1024 || updCyclesNew<1) { + break; + } + updCyclesNew/=2; + } + } + updTimer+=1<>16)&0xffff; + chReg[2]=(chState[i].address>>32)&0xffff; + chReg[3]=(chState[i].address>>48)&0xffff; + chReg[4]=(chanOut[i][0]>>7)&0xff00; + chReg[5]=0; + chReg[6]=(chanOut[i][1]>>7)&0xff00; + chReg[7]=0; + chReg[10]=chState[i].loopEnd&0xffff; + chReg[11]=(chState[i].loopEnd>>16)&0xffff; + chReg[12]=chState[i].loopStart&0xffff; + chReg[13]=(chState[i].loopStart>>16)&0xffff; + } +} + +void DivPlatformGBAMinMod::tick(bool sysTick) { + // collect stats for display in chip config + // logV("rendered=%d,updTot=%d",sampsRendered,updCyclesTotal); + if (sysTick && updCyclesTotal>0) { + // assuming new sample, L!=R and lowest ROM access wait in all channels + // this gives 39.5 cycles/sample, rounded up to 40 for loops + maxCPU=(float)sampsRendered*chanMax*40/(float)updCyclesTotal; + } + sampsRendered=0; + updCyclesTotal=0; + + for (int i=0; icalcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].useWave && chan[i].std.wave.had) { + 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].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) { + chan[i].chPanL=(255*(chan[i].std.panL.val&255))/chan[i].macroPanMul; + chan[i].volChangedL=true; + } + + if (chan[i].std.panR.had) { + chan[i].chPanR=(255*(chan[i].std.panR.val&255))/chan[i].macroPanMul; + chan[i].volChangedR=true; + } + if (chan[i].std.phaseReset.had) { + if ((chan[i].std.phaseReset.val==1) && chan[i].active) { + chan[i].audPos=0; + chan[i].setPos=true; + } + } + if (chan[i].std.ex1.had) { + if (chan[i].invertL!=(bool)(chan[i].std.ex1.val&16)) { + chan[i].invertL=chan[i].std.ex1.val&2; + chan[i].volChangedL=true; + } + if (chan[i].invertR!=(bool)(chan[i].std.ex1.val&8)) { + chan[i].invertR=chan[i].std.ex1.val&1; + chan[i].volChangedR=true; + } + } + if (chan[i].setPos) { + // force keyon + chan[i].keyOn=true; + chan[i].setPos=false; + } else { + chan[i].audPos=0; + } + if (chan[i].useWave && chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivSample* s=parent->getSample(chan[i].sample); + double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + chan[i].freq=(int)(off*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)); + if (chan[i].keyOn) { + unsigned int start, end, loop; + if ((chan[i].echo&0xf)!=0) { + // make sure echo channels' frequency can't be faster than the sample rate + if (chan[i].freq>CHIP_FREQBASE) { + chan[i].freq=CHIP_FREQBASE; + } + // this is only to match the HLE implementation + // the actual engine will handle mid-frame echo switch differently + start=loop=0x08000000; + end=0x08000001; + } else if (chan[i].useWave) { + start=(i*256)|0x02000000; + end=start+chan[i].wtLen; + loop=start; + } else { + size_t maxPos=getSampleMemCapacity(); + start=sampleOff[chan[i].sample]; + if (s->isLoopable()) { + end=MIN(start+MAX(s->length8,1),maxPos); + loop=start+s->loopStart; + } else { + end=MIN(start+s->length8+16,maxPos); + loop=MIN(start+s->length8,maxPos); + } + if (chan[i].audPos>0) { + start=start+MIN(chan[i].audPos,end); + } + start|=0x08000000; + end|=0x08000000; + loop|=0x08000000; + } + rWrite(2+i*16,start&0xffff); + rWrite(3+i*16,start>>16); + rWrite(10+i*16,end&0xffff); + rWrite(11+i*16,end>>16); + rWrite(12+i*16,loop&0xffff); + rWrite(13+i*16,loop>>16); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + } + chan[i].volChangedL=true; + chan[i].volChangedR=true; + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].volChangedL=true; + chan[i].volChangedR=true; + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite(8+i*16,chan[i].freq&0xffff); + rWrite(9+i*16,chan[i].freq>>16); + chan[i].freqChanged=false; + } + } + // don't scale echo channels + if (chan[i].volChangedL) { + int out=chan[i].outVol*chan[i].chPanL; + if ((chan[i].echo&0xf)==0) out=(out*volScale)>>16; + else out=out>>1; + if (chan[i].invertL) out=-out; + rWrite(14+i*16,(isMuted[i] || !chan[i].active)?0:out); + chan[i].volChangedL=false; + } + if (chan[i].volChangedR) { + int out=chan[i].outVol*chan[i].chPanR; + if ((chan[i].echo&0xf)==0) out=(out*volScale)>>16; + else out=out>>1; + if (chan[i].invertR) out=-out; + rWrite(15+i*16,(isMuted[i] || !chan[i].active)?0:out); + chan[i].volChangedR=false; + } + } +} + +int DivPlatformGBAMinMod::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:255; + chan[c.chan].macroPanMul=ins->type==DIV_INS_AMIGA?127:255; + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].wtLen=ins->amiga.waveLen+1; + if (c.chanamiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } + chan[c.chan].useWave=false; + } + if (chan[c.chan].useWave || 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].baseFreq=round(NOTE_FREQUENCY(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; + } + 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: + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + chan[c.chan].volChangedL=true; + chan[c.chan].volChangedR=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_SNES_INVERT: + chan[c.chan].invertL=(c.value>>4); + chan[c.chan].invertR=c.value&15; + chan[c.chan].volChangedL=true; + chan[c.chan].volChangedR=true; + break; + case DIV_CMD_PANNING: + chan[c.chan].chPanL=c.value; + chan[c.chan].chPanR=c.value2; + chan[c.chan].volChangedL=true; + chan[c.chan].volChangedR=true; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) break; + chan[c.chan].wave=c.value; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(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_LEGATO: { + chan[c.chan].baseFreq=NOTE_FREQUENCY(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_FREQUENCY(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_MINMOD_ECHO: + chan[c.chan].echo=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 255; + 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_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformGBAMinMod::updateWave(int ch) { + int addr=ch*256; + for (unsigned int i=0; i=chanMax || + chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen || + !chan[ch].active || (chan[ch].echo&0xf)!=0 + ) { + return DivSamplePos(); + } + return DivSamplePos( + chan[ch].sample, + (((int)regPool[ch*16+2]|((int)regPool[ch*16+3]<<16))&0x01ffffff)-sampleOff[chan[ch].sample], + (int64_t)chan[ch].freq*chipClock/CHIP_FREQBASE + ); +} + +DivDispatchOscBuffer* DivPlatformGBAMinMod::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +void DivPlatformGBAMinMod::reset() { + resetMixer(); + memset(regPool,0,sizeof(regPool)); + memset(wtMem,0,sizeof(wtMem)); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformGBAMinMod::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255); + } +} + +void DivPlatformGBAMinMod::resetMixer() { + sampTimer=sampCycles; + updTimer=0; + updCycles=0; + mixBufReadPos=0; + mixBufWritePos=0; + mixBufPage=0; + mixBufOffset=0; + sampsRendered=0; + sampL=0; + sampR=0; + memset(mixBuf,0,sizeof(mixBuf)); + memset(chanOut,0,sizeof(chanOut)); +} + +int DivPlatformGBAMinMod::getOutputCount() { + return 2; +} + +void DivPlatformGBAMinMod::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformGBAMinMod::notifyWaveChange(int wave) { + for (int i=0; i<16; i++) { + if (chan[i].useWave && chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + updateWave(i); + } + } +} + +void DivPlatformGBAMinMod::notifyInsDeletion(void* ins) { + for (int i=0; i<16; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformGBAMinMod::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformGBAMinMod::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +unsigned char* DivPlatformGBAMinMod::getRegisterPool() { + return (unsigned char*)regPool; +} + +int DivPlatformGBAMinMod::getRegisterPoolSize() { + return 256; +} + +int DivPlatformGBAMinMod::getRegisterPoolDepth() { + return 16; +} + +const void* DivPlatformGBAMinMod::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformGBAMinMod::getSampleMemCapacity(int index) { + return index == 0 ? 33554432 : 0; +} + +size_t DivPlatformGBAMinMod::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformGBAMinMod::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +const DivMemoryComposition* DivPlatformGBAMinMod::getMemCompo(int index) { + switch (index) { + case 0: return &romMemCompo; + case 1: return &wtMemCompo; + case 2: return &mixMemCompo; + } + return NULL; +} + +void DivPlatformGBAMinMod::renderSamples(int sysID) { + size_t maxPos=getSampleMemCapacity(); + memset(sampleMem,0,maxPos); + romMemCompo.entries.clear(); + romMemCompo.capacity=maxPos; + + // dummy zero-length samples are at pos 0 as the engine still outputs them + size_t memPos=1; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + int length=s->length8; + int actualLength=MIN((int)(maxPos-memPos),length); + if (actualLength>0) { + sampleOff[i]=memPos; + memcpy(&sampleMem[memPos],s->data8,actualLength); + memPos+=actualLength; + romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); + // if it's one-shot, add 16 silent samples for looping area + // this should be enough for most cases even though the + // frequency register can make position jump by up to 256 samples + if (!s->isLoopable()) { + int oneShotLen=MIN((int)maxPos-memPos,16); + memset(&sampleMem[memPos],0,oneShotLen); + memPos+=oneShotLen; + } + } + if (actualLength>dacDepth; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + sampCycles=16777216/flags.getInt("sampRate",21845); + chipClock=16777216/sampCycles; + resetMixer(); + wtMemCompo.used=256*chanMax; + mixMemCompo.used=2048*mixBufs; + wtMemCompo.entries.clear(); + mixMemCompo.entries.clear(); + for (int i=0; i { + unsigned char echo; + unsigned int audPos, wtLen; + int sample, wave; + bool useWave, setPos, volChangedL, volChangedR, invertL, invertR; + int chPanL, chPanR; + int macroVolMul; + int macroPanMul; + DivWaveSynth ws; + Channel(): + SharedChannel(255), + echo(0), + audPos(0), + wtLen(1), + sample(-1), + wave(-1), + useWave(false), + setPos(false), + volChangedL(false), + volChangedR(false), + invertL(false), + invertR(false), + chPanL(255), + chPanR(255), + macroVolMul(256), + macroPanMul(127) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + unsigned int sampleOff[256]; + bool sampleLoaded[256]; + int volScale; + unsigned char chanMax; + + // emulator part + unsigned int mixBufs; + unsigned int dacDepth; + unsigned int sampCycles; + unsigned int sampTimer; + unsigned int updCycles; + unsigned int updTimer; + unsigned int updCyclesTotal; + unsigned int sampsRendered; + signed char mixBuf[15*2][1024]; + unsigned char mixOut[2][4]; + short oscOut[16][4]; + int chanOut[16][2]; + size_t mixBufPage; + size_t mixBufReadPos; + size_t mixBufWritePos; + size_t mixBufOffset; + short sampL, sampR; + + signed char* sampleMem; + size_t sampleMemLen; + // maximum wavetable length is currently hardcoded to 256 + unsigned short regPool[16*16]; + signed char wtMem[256*16]; + DivMemoryComposition romMemCompo; + DivMemoryComposition mixMemCompo; + DivMemoryComposition wtMemCompo; + + 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); + unsigned short getPan(int chan); + DivSamplePos getSamplePos(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + int getRegisterPoolDepth(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); + void renderSamples(int chipID); + void setFlags(const DivConfig& flags); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + + float maxCPU; + private: + void updateWave(int ch); + // emulator part + void resetMixer(); +}; + +#endif diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index f59cf1975..fc82709ac 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -143,7 +143,7 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { if (!writes.empty()) { QueuedWrite& w=writes.front(); if (w.addrOrVal) { - //logV("%.3x = %.2x",w.addr,w.val); + //logV("%.3x=%.2x",w.addr,w.val); OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; writes.pop_front(); @@ -289,8 +289,279 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) { } } +const unsigned char chanMap276[6]={ + 1, 5, 3, 0, 4, 2 +}; + +// thanks LTVA +void DivPlatformGenesis::acquire276OscSub() { + if (fm_276.fsm_cnt2[1]==0 && llePrevCycle!=0) { + lleCycle=0; + } + + llePrevCycle=fm_276.fsm_cnt2[1]; + + if (fm_276.flags==fmopn2_flags_ym3438) { + lleOscData[lleCycle/(24*2)]+=fm_276.out_l+fm_276.out_r; + + lleCycle++; + + if (lleCycle==(144*2)) { + lleCycle=0; + + for (int i=0; i<6; i++) { + if ((softPCM && ((chanMap276[i]!=5) || !chan[5].dacMode)) || (!softPCM)) { + oscBuf[chanMap276[i]]->data[oscBuf[chanMap276[i]]->needle++]=lleOscData[i]; + } + lleOscData[i]=0; + } + + if (softPCM && chan[5].dacMode) { + oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; + oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; + } else { + oscBuf[6]->data[oscBuf[6]->needle++]=0; + } + } + } else { + for (int i=0; i<6; i++) { + if (lleCycle==((7+i)*12)) { + oscBuf[i]->data[oscBuf[i]->needle]=(fm_276.osc_out>>1)*8; + } + } + + lleCycle++; + + if (lleCycle==(144*2)) { + lleCycle=0; + + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle]>>=1; + } + + if (softPCM && chan[5].dacMode) { + oscBuf[5]->data[oscBuf[5]->needle]=chan[5].dacOutput<<6; + oscBuf[6]->data[oscBuf[6]->needle]=chan[6].dacOutput<<6; + } else { + oscBuf[6]->data[oscBuf[6]->needle]=0; + } + + for (int i=0; i<7; i++) { + oscBuf[i]->needle++; + } + } + } +} + +// thanks LTVA void DivPlatformGenesis::acquire_nuked276(short** buf, size_t len) { - // TODO + for (size_t h=0; h>8)<<1),w.val); + was_reg_write=true; + + fm_276.input.address=w.addr<0x100?0:2; + fm_276.input.data=w.addr&0xff; + fm_276.input.wr=1; + FMOPN2_Clock(&fm_276,0); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + fm_276.input.wr=0; + FMOPN2_Clock(&fm_276,1); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + if (chipType==2) { + if (!o_bco && fm_276.o_bco) { + dacShifter=(dacShifter<<1)|fm_276.o_so; + + if (o_lro!=fm_276.o_lro) { + if (o_lro) + sample_l=dacShifter; + else + sample_r=dacShifter; + } + + o_lro=fm_276.o_lro; + } + o_bco=fm_276.o_bco; + } + + for (int c=0; c<17; c++) { + FMOPN2_Clock(&fm_276,0); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + FMOPN2_Clock(&fm_276,1); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + if (chipType==2) { + if (!o_bco && fm_276.o_bco) { + dacShifter=(dacShifter<<1)|fm_276.o_so; + + if (o_lro!=fm_276.o_lro) { + if (o_lro) + sample_l=dacShifter; + else + sample_r=dacShifter; + } + + o_lro=fm_276.o_lro; + } + o_bco=fm_276.o_bco; + } + } + + fm_276.input.address=w.addr<0x100?1:3; + fm_276.input.data=w.val; + fm_276.input.wr=1; + FMOPN2_Clock(&fm_276,0); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + fm_276.input.wr=0; + + acquire276OscSub(); + + FMOPN2_Clock(&fm_276,1); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + if (chipType==2) { + if (!o_bco && fm_276.o_bco) { + dacShifter=(dacShifter<<1)|fm_276.o_so; + + if (o_lro!=fm_276.o_lro) { + if (o_lro) + sample_l=dacShifter; + else + sample_r=dacShifter; + } + + o_lro=fm_276.o_lro; + } + o_bco=fm_276.o_bco; + } + + for (int c=0; c<83; c++) { + FMOPN2_Clock(&fm_276,0); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + FMOPN2_Clock(&fm_276,1); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + if (chipType==2) { + if (!o_bco && fm_276.o_bco) { + dacShifter=(dacShifter<<1)|fm_276.o_so; + + if (o_lro!=fm_276.o_lro) { + if (o_lro) { + sample_l=dacShifter; + } else { + sample_r=dacShifter; + } + } + + o_lro=fm_276.o_lro; + } + o_bco=fm_276.o_bco; + } + } + + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + + if (dacWrite>=0) { + if (!canWriteDAC) { + canWriteDAC=true; + } else { + urgentWrite(0x2a,dacWrite); + dacWrite=-1; + canWriteDAC=writes.empty(); + } + } + } else { + w.addrOrVal=true; + } + } else { + canWriteDAC=true; + if (dacWrite>=0) { + urgentWrite(0x2a,dacWrite); + dacWrite=-1; + } + flushFirst=false; + } + + for (int j=0; j<(was_reg_write?(144-83-19):144); j++) { + FMOPN2_Clock(&fm_276,0); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + FMOPN2_Clock(&fm_276,1); + sum_l+=fm_276.out_l; + sum_r+=fm_276.out_r; + + acquire276OscSub(); + + if (chipType==2) { + if (!o_bco && fm_276.o_bco) { + dacShifter=(dacShifter<<1)|fm_276.o_so; + + if (o_lro!=fm_276.o_lro) { + if (o_lro) + sample_l=dacShifter; + else + sample_r=dacShifter; + } + + o_lro=fm_276.o_lro; + } + o_bco=fm_276.o_bco; + } + } + + if (chipType==2) { + buf[0][h]=sample_l; + buf[1][h]=sample_r; + } else { + buf[0][h]=(sum_l*3)>>2; + buf[1][h]=(sum_r*3)>>2; + } + } } void DivPlatformGenesis::acquire(short** buf, size_t len) { @@ -1333,7 +1604,48 @@ void DivPlatformGenesis::reset() { writes.clear(); memset(regPool,0,512); if (useYMFM==2) { + dacShifter=0; + o_bco=0; + o_lro=0; + + lleCycle=0; + llePrevCycle=0; + + for (int i=0; i<6; i++) { + lleOscData[i]=0; + } + memset(&fm_276,0,sizeof(fmopn2_t)); + + fm_276.input.cs=1; + fm_276.input.rd=0; + fm_276.input.wr=0; + fm_276.input.address=0; + fm_276.input.data=0; + + if (chipType==2) { + fm_276.flags=0; + } else { + fm_276.flags=fmopn2_flags_ym3438; + } + + fm_276.input.ic=0; + for (int i=0; i<288; i++) { + FMOPN2_Clock(&fm_276,0); + FMOPN2_Clock(&fm_276,1); + } + + fm_276.input.ic=1; + for (int i=0; i<288*2; i++) { + FMOPN2_Clock(&fm_276,0); + FMOPN2_Clock(&fm_276,1); + } + + fm_276.input.ic=0; + for (int i=0; i<288*2; i++) { + FMOPN2_Clock(&fm_276,0); + FMOPN2_Clock(&fm_276,1); + } } else if (useYMFM==1) { fm_ymfm->reset(); } @@ -1474,6 +1786,8 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) { fm_ymfm=new ymfm::ym3438(iface); } rate=chipClock/144; + } else if (useYMFM==2) { + rate=chipClock/144; } else { rate=chipClock/36; } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index a24cabb52..76b3e57ef 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -22,7 +22,9 @@ #include "fmshared_OPN.h" #include "sound/ymfm/ymfm_opn.h" +extern "C" { #include "../../../extern/YMF276-LLE/fmopn2.h" +} class DivYM2612Interface: public ymfm::ymfm_interface { int setA, setB; @@ -89,6 +91,11 @@ class DivPlatformGenesis: public DivPlatformOPN { unsigned char useYMFM; unsigned char chipType; short dacWrite; + + int lleCycle; + int llePrevCycle; + int lleOscData[6]; + int dacShifter, o_lro, o_bco; unsigned char dacVolTable[128]; @@ -97,6 +104,7 @@ class DivPlatformGenesis: public DivPlatformOPN { inline void processDAC(int iRate); inline void commitState(int ch, DivInstrument* ins); + void acquire276OscSub(); void acquire_nuked(short** buf, size_t len); void acquire_nuked276(short** buf, size_t len); void acquire_ymfm(short** buf, size_t len); diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index 3f3ebc6d9..5bd3dc47c 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -528,11 +528,19 @@ bool DivPlatformK007232::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformK007232::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformK007232::renderSamples(int sysID) { memset(sampleMem,0xc0,getSampleMemCapacity()); memset(sampleOffK007232,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -551,6 +559,7 @@ void DivPlatformK007232::renderSamples(int sysID) { memPos=(memPos+0x1ffff)&0xfe0000; } sampleOffK007232[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+1)); for (int j=0; jdata8[j])^0x80; @@ -568,6 +577,9 @@ void DivPlatformK007232::renderSamples(int sysID) { } } sampleMemLen=memPos; + + memCompo.used=sampleMemLen; + memCompo.capacity=16777216; } int DivPlatformK007232::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/k007232.h b/src/engine/platform/k007232.h index 379d41106..1bc54fb48 100644 --- a/src/engine/platform/k007232.h +++ b/src/engine/platform/k007232.h @@ -74,6 +74,7 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf { unsigned char* sampleMem; size_t sampleMemLen; k007232_core k007232; + DivMemoryComposition memCompo; unsigned char regPool[20]; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -105,6 +106,7 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); diff --git a/src/engine/platform/k053260.cpp b/src/engine/platform/k053260.cpp index 68704c9ee..690071fc5 100644 --- a/src/engine/platform/k053260.cpp +++ b/src/engine/platform/k053260.cpp @@ -81,7 +81,7 @@ void DivPlatformK053260::acquire(short** buf, size_t len) { 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; + oscBuf[i]->data[oscBuf[i]->needle++]=(k053260.voice_out(i,0)+k053260.voice_out(i,1))>>1; } } } @@ -466,11 +466,19 @@ bool DivPlatformK053260::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformK053260::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformK053260::renderSamples(int sysID) { memset(sampleMem,0,getSampleMemCapacity()); memset(sampleOffK053260,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=1; // for avoid silence for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -486,6 +494,7 @@ void DivPlatformK053260::renderSamples(int sysID) { actualLength=MIN((int)(getSampleMemCapacity()-memPos-1),length); if (actualLength>0) { sampleOffK053260[i]=memPos-1; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+1)); for (int j=0; jdataK[j]; } @@ -496,6 +505,7 @@ void DivPlatformK053260::renderSamples(int sysID) { actualLength=MIN((int)(getSampleMemCapacity()-memPos-1),length); if (actualLength>0) { sampleOffK053260[i]=memPos-1; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+1)); for (int j=0; jdata8[j]; } @@ -509,6 +519,9 @@ void DivPlatformK053260::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleMemLen=memPos; + + memCompo.capacity=2097152; + memCompo.used=sampleMemLen; } int DivPlatformK053260::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/k053260.h b/src/engine/platform/k053260.h index 4f1afea2d..8001d7eeb 100644 --- a/src/engine/platform/k053260.h +++ b/src/engine/platform/k053260.h @@ -52,6 +52,7 @@ class DivPlatformK053260: public DivDispatch, public k053260_intf { unsigned char* sampleMem; size_t sampleMemLen; k053260_core k053260; + DivMemoryComposition memCompo; unsigned char regPool[64]; void updatePanning(unsigned char mask); @@ -84,6 +85,7 @@ class DivPlatformK053260: public DivDispatch, public k053260_intf { 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 const DivMemoryComposition* getMemCompo(int index) override; virtual void renderSamples(int chipID) override; virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; virtual void quit() override; diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 5d9100f8c..1ea439beb 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -34,7 +34,6 @@ #define WRITE_ATTEN(ch,v) rWrite((0x40+ch),(v)) #define WRITE_STEREO(v) rWrite(0x50,(v)) -#define CHIP_DIVIDER 64 #define CHIP_FREQBASE 16000000 static int32_t clamp(int32_t v, int32_t lo, int32_t hi) @@ -42,6 +41,41 @@ static int32_t clamp(int32_t v, int32_t lo, int32_t hi) return vhi?hi:v); } +const int DUTY_DIVIDERS[]={ + 1, 2, 4, 3, 6, 7, 7, 4, 8, 15, 6, 14, 15, 12, 14, 5, + 10, 21, 31, 28, 31, 30, 12, 31, 21, 8, 30, 31, 28, 31, 31, 6, + 12, 63, 14, 62, 9, 28, 62, 15, 14, 42, 8, 21, 62, 63, 15, 60, + 63, 20, 42, 63, 28, 12, 63, 62, 62, 63, 21, 24, 15, 62, 60, 7, + 16, 63, 30, 254, 217, 84, 186, 217, 12, 254, 28, 15, 254, 51, 255, 84, + 217, 120, 210, 63, 60, 255, 255, 254, 254, 217, 63, 252, 17, 42, 124, 85, + 30, 254, 24, 217, 210, 21, 255, 252, 28, 217, 10, 186, 63, 124, 254, 255, + 186, 255, 255, 56, 255, 70, 36, 30, 255, 60, 254, 85, 124, 85, 21, 254, + 22,1533,2047, 868,1953,2046, 420, 651,1533,2044,2046,2047,1016,1785, 105, 126, + 595, 630, 204,1533,1302,2047,2047,2044,2044, 119, 372,1778,1953, 120, 682,1905, + 595, 868, 510,2047,2044, 762, 279, 682, 210,1953, 595,1524,1533, 210, 248, 635, + 60,2047,2047, 62,1905,2044,2046, 42,2047, 510, 252,1905,1778,2047,1533,1016, + 1953,1860,1778, 219, 48,1905,2047,1778, 682,2047, 465,1020,1785, 126,2044, 434, + 2044, 63,2047,2046,2047, 280, 30,1953, 210, 682, 868, 89,2046,2047,2047,1524, + 1302,1785,2047,1860,2047,2046,1016, 372,2047, 84, 630, 70, 252,2047,1533, 682, + 1905,2046, 372,1905, 90,1533, 217, 168, 340,2047,2047,1302,1533, 28, 186,2047, + 24,3255, 126,1190, 45,4092, 178, 315, 28, 438, 124, 315,3570, 255,1023,2044, + 819,1016, 930,3937,1260,1302, 511,4094,3810, 819, 195,2604,1023,4094, 84,1365, + 18,3810, 56,1023, 42,3937, 819,4092, 124,4095, 30,4094, 85,1524,3906, 63, + 3906,1023,3255, 120,4095,3570,1020,3937,2667,3556,4094, 195,2044,4095,4095, 762, + 28,1302, 84,4095,3906,1023, 255, 292, 16,1085, 42,3066, 315,3556,4094,4095, + 3066,4095,3937, 372, 511,4094, 620,1302, 273, 504,1190, 819,4092,4095,1023, 210, + 124,1023, 126,3570,3937, 252, 438,4095, 30,4094, 120,4095,4094, 255, 63,1020, + 4095,1364, 558,2667, 420,4095, 105,1270,1190,3255, 93,1016, 91, 372,4092,3937, + 3255, 44,3066,1365,1736, 510, 91,4094,1302, 511,3937, 420, 105,3906,4092, 819, + 252, 585, 255, 210,3937,1016,3570,1023, 455,3066,2044,1365,4094, 126,2667,4092, + 3810, 93, 63,1364,1365,3906, 120, 28,1023,2044, 238,4095,3556, 255,4095, 372, + 511,1190,1260,1023,3066, 255, 819, 408,2044, 255,1302,4094, 455,2604,4094, 585, + 438, 511, 455, 292, 455, 434,1364, 102,1085, 204,3570, 255, 240,4095, 93,4094, + 3937,4094, 84, 63,4094, 315, 819, 140,1260,3937,4095,3066,2667, 680,4094, 511, + 4095,2044, 178,1023,1020, 819, 21,3810,4094, 20,1023,3556,3937, 210,2040, 273, + 252,2667,4095,3906, 63, 124, 930, 455, 510,4094,4092, 511,3570,4095, 30, 744 +}; + const char* regCheatSheetLynx[]={ "AUDIO0_VOLCNTRL", "20", "AUDIO0_FEEDBACK", "21", @@ -134,6 +168,7 @@ void DivPlatformLynx::tick(bool sysTick) { chan[i].handleArp(); } else if (chan[i].std.arp.had) { if (!chan[i].inPorta) { + double CHIP_DIVIDER=tuned?DUTY_DIVIDERS[chan[i].duty.val&0x1ff]*8:64; chan[i].actualNote=parent->calcArp(chan[i].note,chan[i].std.arp.val); chan[i].baseFreq=NOTE_PERIODIC(chan[i].actualNote); if (chan[i].pcm) chan[i].sampleBaseFreq=NOTE_FREQUENCY(chan[i].actualNote); @@ -165,6 +200,11 @@ void DivPlatformLynx::tick(bool sysTick) { chan[i].freqChanged=true; } + if (chan[i].std.ex1.had) { + WRITE_LFSR(i, chan[i].std.ex1.val&0xff); + WRITE_OTHER(i, (chan[i].std.ex1.val&0xf00)>>4); + } + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { if (chan[i].pcm && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { @@ -194,28 +234,35 @@ void DivPlatformLynx::tick(bool sysTick) { WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4)); chan[i].lfsr=-1; } - chan[i].fd=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].std.duty.had) { chan[i].duty=chan[i].std.duty.val; if (!chan[i].pcm) { WRITE_FEEDBACK(i, chan[i].duty.feedback); } } + double divider=tuned?DUTY_DIVIDERS[chan[i].duty.val&0x1ff]*8:64; + chan[i].fd=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,divider); WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); WRITE_BACKUP( i, chan[i].fd.backup ); } chan[i].freqChanged=false; } else if (chan[i].std.duty.had) { - chan[i].duty = chan[i].std.duty.val; + chan[i].duty=chan[i].std.duty.val; if (!chan[i].pcm) { + if (tuned) { + double divider=DUTY_DIVIDERS[chan[i].duty.val&0x1ff]*8; + chan[i].fd=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,divider); + } WRITE_FEEDBACK(i, chan[i].duty.feedback); WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); + if (tuned) WRITE_BACKUP( i, chan[i].fd.backup ); } } } } int DivPlatformLynx::dispatch(DivCommand c) { + double CHIP_DIVIDER=tuned?DUTY_DIVIDERS[chan[c.chan].duty.val&0x1ff]*8:64; switch (c.cmd) { case DIV_CMD_NOTE_ON: { bool prevPCM=chan[c.chan].pcm; @@ -455,6 +502,16 @@ bool DivPlatformLynx::getLegacyAlwaysSetVolume() { // return 12; //} +void DivPlatformLynx::setFlags(const DivConfig& flags) { + tuned=flags.getBool("tuned",false); + chipClock=16000000; + CHECK_CUSTOM_CLOCK; + rate=chipClock/128; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } +} + void DivPlatformLynx::notifyInsDeletion(void* ins) { for (int i=0; i<4; i++) { chan[i].std.notifyInsDeletion((DivInstrument*)ins); @@ -478,14 +535,7 @@ int DivPlatformLynx::init(DivEngine* p, int channels, int sugRate, const DivConf isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - - chipClock = 16000000; - CHECK_CUSTOM_CLOCK; - rate = chipClock/128; - - for (int i=0; i<4; i++) { - oscBuf[i]->rate=rate; - } + setFlags(flags); reset(); return 4; @@ -533,6 +583,7 @@ DivPlatformLynx::MikeyDuty::MikeyDuty(int duty) { //1: f1 //0: f0 + val=duty; //f7 moved to bit 7 and int moved to bit 5 int_feedback7=((duty&0x40)<<1)|((duty&0x200)>>4); //f11 and f10 moved to bits 7 & 6 diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index 7d705261d..c796126fc 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -35,6 +35,7 @@ class DivPlatformLynx: public DivDispatch { struct MikeyDuty { unsigned char int_feedback7; unsigned char feedback; + int val; MikeyDuty(int duty); }; @@ -64,6 +65,7 @@ class DivPlatformLynx: public DivDispatch { Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; + bool tuned; std::unique_ptr mikey; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -86,6 +88,7 @@ class DivPlatformLynx: public DivDispatch { bool keyOffAffectsPorta(int ch); bool getLegacyAlwaysSetVolume(); //int getPortaFloor(int ch); + void setFlags(const DivConfig& flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index a8c30b3e1..916234f0e 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -370,6 +370,11 @@ bool DivPlatformMSM6295::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformMSM6295::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformMSM6295::renderSamples(int sysID) { unsigned int sampleOffVOX[256]; @@ -381,6 +386,11 @@ void DivPlatformMSM6295::renderSamples(int sysID) { bankedPhrase[i].phrase=0; } + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"Phrase Book",-1,0,128*8)); + // sample data size_t memPos=128*8; if (isBanked) { @@ -419,6 +429,7 @@ void DivPlatformMSM6295::renderSamples(int sysID) { bankedPhrase[i].bank=bankInd; bankedPhrase[i].phrase=phraseInd; bankedPhrase[i].length=paddedLen; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; phraseInd++; } @@ -460,6 +471,7 @@ void DivPlatformMSM6295::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleOffVOX[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } adpcmMemLen=memPos+256; @@ -476,6 +488,9 @@ void DivPlatformMSM6295::renderSamples(int sysID) { adpcmMem[5+i*8]=(endPos)&0xff; } } + + memCompo.capacity=getSampleMemCapacity(0); + memCompo.used=adpcmMemLen; } void DivPlatformMSM6295::setFlags(const DivConfig& flags) { diff --git a/src/engine/platform/msm6295.h b/src/engine/platform/msm6295.h index 51e052a81..5664adb7d 100644 --- a/src/engine/platform/msm6295.h +++ b/src/engine/platform/msm6295.h @@ -69,6 +69,8 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { phrase(0), length(0) {} } bankedPhrase[256]; + + DivMemoryComposition memCompo; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -99,6 +101,7 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { virtual size_t getSampleMemCapacity(int index) override; virtual size_t getSampleMemUsage(int index) override; virtual bool isSampleLoaded(int index, int sample) override; + virtual const DivMemoryComposition* getMemCompo(int index) override; virtual void renderSamples(int chipID) override; virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 5e2ccd9c0..7455134a7 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -279,6 +279,19 @@ void DivPlatformN163::tick(bool sysTick) { chan[i].freqChanged=false; } } + + // update memory composition positions + for (int i=0; i<=chanMax; i++) { + memCompo.entries[i].begin=chan[i].wavePos>>1; + memCompo.entries[i].end=(chan[i].wavePos+chan[i].waveLen)>>1; + memCompo.entries[i+8].begin=chan[i].curWavePos>>1; + memCompo.entries[i+8].end=(chan[i].curWavePos+chan[i].curWaveLen)>>1; + } + + // update register pool + for (int i=0; i<128; i++) { + regPool[i]=n163.reg(i); + } } int DivPlatformN163::dispatch(DivCommand c) { @@ -363,7 +376,9 @@ int DivPlatformN163::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + double destFreqD=NOTE_FREQUENCY(c.value2); + if (destFreqD>2000000000.0) destFreqD=2000000000.0; + int destFreq=destFreqD; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:16); @@ -387,6 +402,7 @@ int DivPlatformN163::dispatch(DivCommand c) { } case DIV_CMD_WAVE: chan[c.chan].wave=c.value; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); if (chan[c.chan].waveMode) { chan[c.chan].waveUpdated=true; } @@ -476,6 +492,7 @@ void DivPlatformN163::forceIns() { chan[i].waveChanged=true; } } + memCompo.entries[16].begin=120-chanMax*8; } void DivPlatformN163::notifyWaveChange(int wave) { @@ -516,9 +533,6 @@ DivDispatchOscBuffer* DivPlatformN163::getOscBuffer(int ch) { } unsigned char* DivPlatformN163::getRegisterPool() { - for (int i=0; i<128; i++) { - regPool[i]=n163.reg(i); - } return regPool; } @@ -544,6 +558,8 @@ void DivPlatformN163::reset() { loadWave=-1; loadPos=0; rWrite(0x7f,initChanMax<<4); + + memCompo.entries[16].begin=120-chanMax*8; } void DivPlatformN163::poke(unsigned int addr, unsigned short val) { @@ -554,6 +570,11 @@ void DivPlatformN163::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } +const DivMemoryComposition* DivPlatformN163::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformN163::setFlags(const DivConfig& flags) { switch (flags.getInt("clockSel",0)) { case 1: // PAL @@ -591,6 +612,20 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, const DivConf isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } + + memCompo.used=0; + memCompo.capacity=128; + memCompo.memory=regPool; + memCompo.waveformView=DIV_MEMORY_WAVE_4BIT; + + for (int i=0; i<8; i++) { + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_N163_LOAD,fmt::sprintf("Channel %d (load)",i),-1,0,0)); + } + for (int i=0; i<8; i++) { + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_N163_PLAY,fmt::sprintf("Channel %d (play)",i),-1,0,0)); + } + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"Registers",-1,127,128)); + setFlags(flags); reset(); diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 648a7af24..2c7c404fc 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -65,6 +65,7 @@ class DivPlatformN163: public DivDispatch { n163_core n163; unsigned char regPool[128]; + DivMemoryComposition memCompo; void updateWave(int ch, int wave, int pos, int len); void updateWaveCh(int ch); friend void putDispatchChip(void*,int); @@ -82,6 +83,7 @@ class DivPlatformN163: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + const DivMemoryComposition* getMemCompo(int index); void setFlags(const DivConfig& flags); void notifyWaveChange(int wave); void notifyInsChange(int ins); diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp new file mode 100644 index 000000000..86135f0ae --- /dev/null +++ b/src/engine/platform/nds.cpp @@ -0,0 +1,593 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 "nds.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +#define CHIP_DIVIDER 32 +#define CLOCK_DIVIDER 512 // for match to output rate + +#define rRead8(a) (nds.read8(a)) +#define rWrite8(a,v) {if(!skipRegisterWrites) {nds.write8((a),(v)); regPool[(a)]=(v); if(dumpWrites) addWrite((a),(v)); }} +#define rWrite16(a,v) { \ + if(!skipRegisterWrites) { \ + nds.write16((a)>>1,(v)); \ + regPool[(a)+0]=(v)&0xff; \ + regPool[(a)+1]=((v)>>8)&0xff; \ + if(dumpWrites) addWrite((a)+0,(v)&0xff); \ + if(dumpWrites) addWrite((a)+1,((v)>>8)&0xff); \ + } \ +} + +#define rWrite32(a,v) { \ + if(!skipRegisterWrites) { \ + nds.write32((a)>>2,(v)); \ + regPool[(a)+0]=(v)&0xff; \ + regPool[(a)+1]=((v)>>8)&0xff; \ + regPool[(a)+2]=((v)>>16)&0xff; \ + regPool[(a)+3]=((v)>>24)&0xff; \ + if(dumpWrites) addWrite((a)+0,(v)&0xff); \ + if(dumpWrites) addWrite((a)+1,((v)>>8)&0xff); \ + if(dumpWrites) addWrite((a)+2,((v)>>16)&0xff); \ + if(dumpWrites) addWrite((a)+3,((v)>>24)&0xff); \ + } \ +} + +const char* regCheatSheetNDS[]={ + "CHx_Control", "000+x*10", + "CHx_Start", "004+x*10", + "CHx_Freq", "008+x*10", + "CHx_LoopStart", "00A+x*10", + "CHx_Length", "00C+x*10", + "Control", "100", + "Bias", "104", + "CAPx_Control", "108+x*1", + "CAPx_Dest", "110+x*8", + "CAPx_Length", "114+x*8", + NULL +}; + +const char** DivPlatformNDS::getRegisterSheet() { + return regCheatSheetNDS; +} + +void DivPlatformNDS::acquire(short** buf, size_t len) { + for (size_t i=0; i32767) 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<16; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(nds.chan_lout(i)+nds.chan_rout(i))>>1; + } + } +} + +u8 DivPlatformNDS::read_byte(u32 addr) { + if (addrcalcArp(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 ((i>=8) && (i<14)) { + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; + if ((!chan[i].pcm)) { // pulse + rWrite8(0x03+i*16,(rRead8(0x03+i*16)&0xe8)|(chan[i].duty&7)); + } + } + } + if (chan[i].std.panL.had) { // panning + chan[i].panning=0x40+chan[i].std.panL.val; + rWrite8(0x02+i*16,chan[i].panning); + } + if (chan[i].std.phaseReset.had) { + if ((chan[i].std.phaseReset.val==1) && chan[i].active) { + chan[i].audPos=0; + chan[i].setPos=true; + if ((rRead8(0x03+i*16)&0x80)==0) + chan[i].busy=true; + } + } + if (chan[i].setPos) { + // force keyon + chan[i].keyOn=true; + chan[i].setPos=false; + } else { + chan[i].audPos=0; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + unsigned char ctrl=0; + if (chan[i].pcm || i<8) { + DivSample* s=parent->getSample(chan[i].sample); + switch (s->depth) { + case DIV_SAMPLE_DEPTH_IMA_ADPCM: ctrl=0x40; break; + case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x00; break; + case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x20; break; + default: break; + } + double off=(s->centerRate>=1)?(8363.0/(double)s->centerRate):1.0; + chan[i].freq=0x10000-(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<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + if ((!chan[i].keyOn) && ((rRead8(0x03+i*16)&0x80)==0)) + chan[i].busy=false; + ctrl|=(chan[i].busy?0x80:0)|((s->isLoopable())?0x08:0x10); + if (chan[i].keyOn) { + unsigned int start=0; + int loopStart=0; + int loopEnd=0; + int end=0; + if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + start=sampleOff[chan[i].sample]; + end=s->getCurBufLen()/4; + } + if (chan[i].audPos>0) { + switch (s->depth) { + case DIV_SAMPLE_DEPTH_IMA_ADPCM: start+=chan[i].audPos/2; end-=(chan[i].audPos/8); break; + case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; end-=(chan[i].audPos/4); break; + case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; end-=(chan[i].audPos/2); break; + default: break; + } + } + if (s->isLoopable()) { + if (chan[i].audPos>0) { + switch (s->depth) { + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + loopStart=(s->loopStart-chan[i].audPos)/8; + loopEnd=(s->loopEnd-s->loopStart)/8; + if (chan[i].audPos>(unsigned int)s->loopStart) { + loopStart=0; + loopEnd-=(chan[i].audPos-s->loopStart)/8; + } + break; + case DIV_SAMPLE_DEPTH_8BIT: + loopStart=(s->loopStart-chan[i].audPos)/4; + loopEnd=(s->loopEnd-s->loopStart)/4; + if (chan[i].audPos>(unsigned int)s->loopStart) { + loopStart=0; + loopEnd-=(chan[i].audPos-s->loopStart)/4; + } + break; + case DIV_SAMPLE_DEPTH_16BIT: + loopStart=(s->loopStart-chan[i].audPos)/2; + loopEnd=(s->loopEnd-s->loopStart)/2; + if (chan[i].audPos>(unsigned int)s->loopStart) { + loopStart=0; + loopEnd-=(chan[i].audPos-s->loopStart)/2; + } + break; + default: break; + } + } else { + switch (s->depth) { + case DIV_SAMPLE_DEPTH_IMA_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break; + case DIV_SAMPLE_DEPTH_8BIT: loopStart=s->loopStart/4; loopEnd=(s->loopEnd-s->loopStart)/4; break; + case DIV_SAMPLE_DEPTH_16BIT: loopStart=s->loopStart/2; loopEnd=(s->loopEnd-s->loopStart)/2; break; + default: break; + } + } + loopEnd=CLAMP(loopEnd,0,0x3fffff); + loopStart=CLAMP(loopStart,0,0xffff); + rWrite16(0x0a+i*16,loopStart); + rWrite32(0x0c+i*16,loopEnd); + } else { + end=CLAMP(end,0,0x3fffff); + rWrite16(0x0a+i*16,0); + rWrite32(0x0c+i*16,end&0x3fffff); + } + rWrite8(0x03+i*16,ctrl&~0x80); // force keyoff first + rWrite32(0x04+i*16,start&0x7fffffc); + } + } else { + chan[i].freq=0x10000-(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,8)); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + ctrl=(chan[i].active?0xe8:0)|(chan[i].duty&7); + if (chan[i].keyOff || chan[i].keyOn) { + rWrite8(0x03+i*16,ctrl&~0x80); // force keyoff first + } + } + chan[i].keyOn=false; + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite16(0x08+i*16,chan[i].freq&0xffff); + chan[i].freqChanged=false; + } + rWrite8(0x03+i*16,ctrl); + } + } +} + +int DivPlatformNDS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_NDS); + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample || (c.chan<8)) { + chan[c.chan].pcm=true; + } + if (chan[c.chan].pcm || (c.chan<8)) { + 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); + chan[c.chan].sampleNote=c.value; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + } + if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + } else { + chan[c.chan].macroVolMul=127; + } + 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].busy=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].busy=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_ADPCMA_GLOBAL_VOLUME: { + if (globalVolume!=(c.value&0x7f)) { + globalVolume=c.value&0x7f; + rWrite32(0x100,0x8000|globalVolume); + } + 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; + writeOutVol(c.chan); + } + } + 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,127),127); + rWrite8(0x02+c.chan*16,chan[c.chan].panning); + 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+chan[c.chan].sampleNoteDelta); + 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: + if ((c.chan>=8) && (c.chan<14) && (!chan[c.chan].pcm)) { // pulse + chan[c.chan].duty=c.value; + rWrite8(0x03+c.chan*16,(rRead8(0x03+c.chan*16)&0xe8)|(chan[c.chan].duty&7)); + } + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((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_NDS)); + } + 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: + if (chan[c.chan].pcm || (c.chan<8)) { + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + } + 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_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformNDS::writeOutVol(int ch) { + unsigned char val=isMuted[ch]?0:chan[ch].outVol; + rWrite8(0x00+ch*16,val); +} + +void DivPlatformNDS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformNDS::forceIns() { + for (int i=0; i<16; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + chan[i].sample=-1; + + rWrite8(0x02+i*16,chan[i].panning); + writeOutVol(i); + } +} + +void* DivPlatformNDS::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformNDS::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformNDS::getPan(int ch) { + return parent->convertPanLinearToSplit(chan[ch].panning,8,127); +} + +DivDispatchOscBuffer* DivPlatformNDS::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +void DivPlatformNDS::reset() { + memset(regPool,0,288); + nds.reset(); + globalVolume=0x7f; + rWrite32(0x100,0x8000|globalVolume); // enable keyon + rWrite32(0x104,0x200); // initialize bias + for (int i=0; i<16; i++) { + chan[i]=DivPlatformNDS::Channel(); + chan[i].std.setEngine(parent); + rWrite32(0x00+i*16,isMuted[i]?0x400000:0x40007f); + } +} + +int DivPlatformNDS::getOutputCount() { + return 2; +} + +void DivPlatformNDS::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformNDS::notifyWaveChange(int wave) { + // TODO when wavetables are added + // TODO they probably won't be added unless the samples reside in RAM +} + +void DivPlatformNDS::notifyInsDeletion(void* ins) { + for (int i=0; i<16; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformNDS::poke(unsigned int addr, unsigned short val) { + rWrite8(addr,val); +} + +void DivPlatformNDS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite8(i.addr,i.val); +} + +unsigned char* DivPlatformNDS::getRegisterPool() { + return regPool; +} + +int DivPlatformNDS::getRegisterPoolSize() { + return 288; +} + +float DivPlatformNDS::getPostAmp() { + return 1.0f; +} + +const void* DivPlatformNDS::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformNDS::getSampleMemCapacity(int index) { + return index == 0 ? (isDSi?16777216:4194304) : 0; +} + +size_t DivPlatformNDS::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformNDS::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +const DivMemoryComposition* DivPlatformNDS::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + +void DivPlatformNDS::renderSamples(int sysID) { + memset(sampleMem,0,16777216); + memset(sampleOff,0,256*sizeof(unsigned int)); + memset(sampleLoaded,0,256*sizeof(bool)); + + memCompo=DivMemoryComposition(); + memCompo.name="Main Memory"; + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + + int length=MIN(16777212,s->getCurBufLen()); + unsigned char* src=(unsigned char*)s->getCurBuf(); + int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length); + if (actualLength>0) { + memcpy(&sampleMem[memPos],src,actualLength); + sampleOff[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength)); + memPos+=actualLength; + } + if (actualLengthrate=rate; + } +} + +int DivPlatformNDS::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + + for (int i=0; i<16; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + sampleMem=new unsigned char[16777216]; + sampleMemLen=0; + nds.reset(); + setFlags(flags); + reset(); + + return 16; +} + +void DivPlatformNDS::quit() { + delete[] sampleMem; + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/nds.h b/src/engine/platform/nds.h new file mode 100644 index 000000000..ec4a84796 --- /dev/null +++ b/src/engine/platform/nds.h @@ -0,0 +1,104 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 _NDS_H +#define _NDS_H + +#include "../dispatch.h" +#include "sound/nds.hpp" + +using namespace nds_sound_emu; + +class DivPlatformNDS: public DivDispatch, public nds_sound_intf { + struct Channel: public SharedChannel { + unsigned int audPos; + int sample, wave; + int panning, duty; + bool setPos, pcm, busy; + int macroVolMul; + Channel(): + SharedChannel(127), + audPos(0), + sample(-1), + wave(-1), + panning(64), + duty(0), + setPos(false), + pcm(false), + busy(false), + macroVolMul(64) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + bool isDSi; + int globalVolume; + unsigned int sampleOff[256]; + bool sampleLoaded[256]; + + unsigned char* sampleMem; + size_t sampleMemLen; + nds_sound_t nds; + DivMemoryComposition memCompo; + unsigned char regPool[288]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + + public: + virtual u8 read_byte(u32 addr) override; + virtual void write_byte(u32 addr, u8 data) 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 unsigned short getPan(int chan) 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 float getPostAmp() 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 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 const DivMemoryComposition* getMemCompo(int index) override; + virtual void renderSamples(int chipID) override; + virtual void setFlags(const DivConfig& flags) override; + virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; + virtual void quit() override; + DivPlatformNDS(): + DivDispatch(), + nds_sound_intf(), + nds(*this) {} + private: + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 526944a3d..6ccfb5c11 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -64,8 +64,13 @@ const char** DivPlatformNES::getRegisterSheet() { void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { if (useNP) { - nes1_NP->Write(addr,data); - nes2_NP->Write(addr,data); + if (isE) { + e1_NP->Write(addr,data); + e2_NP->Write(addr,data); + } else { + nes1_NP->Write(addr,data); + nes2_NP->Write(addr,data); + } } else { apu_wr_reg(nes,addr,data); } @@ -151,9 +156,40 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) { } } +void DivPlatformNES::acquire_NSFPlayE(short** buf, size_t len) { + int out1[2]; + int out2[2]; + for (size_t i=0; iTick(8); + e2_NP->TickFrameSequence(8); + e2_NP->Tick(8); + e1_NP->Render(out1); + e2_NP->Render(out2); + + int sample=(out1[0]+out1[1]+out2[0]+out2[1])<<1; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + buf[0][i]=sample; + if (++writeOscBuf>=4) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=e1_NP->out[0]<<11; + oscBuf[1]->data[oscBuf[1]->needle++]=e1_NP->out[1]<<11; + oscBuf[2]->data[oscBuf[2]->needle++]=e2_NP->out[0]<<11; + oscBuf[3]->data[oscBuf[3]->needle++]=e2_NP->out[1]<<11; + oscBuf[4]->data[oscBuf[4]->needle++]=e2_NP->out[2]<<8; + } + } +} + void DivPlatformNES::acquire(short** buf, size_t len) { if (useNP) { - acquire_NSFPlay(buf,len); + if (isE) { + acquire_NSFPlayE(buf,len); + } else { + acquire_NSFPlay(buf,len); + } } else { acquire_puNES(buf,len); } @@ -242,6 +278,8 @@ void DivPlatformNES::tick(bool sysTick) { } if (i!=2) { rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6)); + } else if (isE) { + rWrite(0x4000+9,chan[i].duty); } if (i==3) { // noise chan[i].freqChanged=true; @@ -279,7 +317,9 @@ void DivPlatformNES::tick(bool sysTick) { } } ntPos+=chan[i].pitch2; - if (parent->song.properNoiseLayout) { + if (isE) { + chan[i].freq=31-(ntPos&31); + } else if (parent->song.properNoiseLayout) { chan[i].freq=15-(ntPos&15); } else { if (ntPos<0) ntPos=0; @@ -678,8 +718,13 @@ int DivPlatformNES::dispatch(DivCommand c) { void DivPlatformNES::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (useNP) { - nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); - nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + if (isE) { + e1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); + e2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + } else { + nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); + nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + } } else { nes->muted[ch]=mute; } @@ -743,10 +788,17 @@ void DivPlatformNES::reset() { linearCount=255; if (useNP) { - nes1_NP->Reset(); - nes2_NP->Reset(); - nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); - nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + if (isE) { + e1_NP->Reset(); + e2_NP->Reset(); + e1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); + e2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + } else { + nes1_NP->Reset(); + nes2_NP->Reset(); + nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); + nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + } } else { apu_turn_on(nes,apuType); nes->apu.cpu_cycles=0; @@ -779,11 +831,19 @@ void DivPlatformNES::setFlags(const DivConfig& flags) { apuType=0; } if (useNP) { - nes1_NP->SetClock(rate); - nes1_NP->SetRate(rate); - nes2_NP->SetClock(rate); - nes2_NP->SetRate(rate); - nes2_NP->SetPal(apuType==1); + if (isE) { + e1_NP->SetClock(rate); + e1_NP->SetRate(rate); + e2_NP->SetClock(rate); + e2_NP->SetRate(rate); + e2_NP->SetPal(apuType==1); + } else { + nes1_NP->SetClock(rate); + nes1_NP->SetRate(rate); + nes2_NP->SetClock(rate); + nes2_NP->SetRate(rate); + nes2_NP->SetPal(apuType==1); + } } else { nes->apu.type=apuType; } @@ -817,6 +877,12 @@ void DivPlatformNES::setNSFPlay(bool use) { useNP=use; } +void DivPlatformNES::set5E01(bool use) { + isE=use; + // for now + if (isE) useNP=true; +} + unsigned char DivPlatformNES::readDMC(unsigned short addr) { return dpcmMem[(addr&0x3fff)|((dpcmBank&15)<<14)]; } @@ -839,10 +905,18 @@ bool DivPlatformNES::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformNES::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformNES::renderSamples(int sysID) { - memset(dpcmMem,0,getSampleMemCapacity(0));\ + memset(dpcmMem,0,getSampleMemCapacity(0)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="DPCM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -871,9 +945,13 @@ void DivPlatformNES::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleOffDPCM[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } dpcmMemLen=memPos; + + memCompo.capacity=262144; + memCompo.used=dpcmMemLen; } int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { @@ -881,14 +959,25 @@ int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, const DivConfi dumpWrites=false; skipRegisterWrites=false; if (useNP) { - nes1_NP=new xgm::NES_APU; - nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1); - nes2_NP=new xgm::NES_DMC; - nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1); - nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) { - data=readDMC(addr); - }); - nes2_NP->SetAPU(nes1_NP); + if (isE) { + e1_NP=new xgm::I5E01_APU; + e1_NP->SetOption(xgm::I5E01_APU::OPT_NONLINEAR_MIXER,1); + e2_NP=new xgm::I5E01_DMC; + e2_NP->SetOption(xgm::I5E01_DMC::OPT_NONLINEAR_MIXER,1); + e2_NP->SetMemory([this](unsigned short addr, unsigned int& data) { + data=readDMC(addr); + }); + e2_NP->SetAPU(e1_NP); + } else { + nes1_NP=new xgm::NES_APU; + nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1); + nes2_NP=new xgm::NES_DMC; + nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1); + nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) { + data=readDMC(addr); + }); + nes2_NP->SetAPU(nes1_NP); + } } else { nes=new struct NESAPU; nes->readDMC=_readDMC; @@ -917,8 +1006,13 @@ void DivPlatformNES::quit() { delete oscBuf[i]; } if (useNP) { - delete nes1_NP; - delete nes2_NP; + if (isE) { + delete e1_NP; + delete e2_NP; + } else { + delete nes1_NP; + delete nes2_NP; + } } else { delete nes; } diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index 43377fee2..470ab7719 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include "sound/nes_nsfplay/nes_apu.h" +#include "sound/nes_nsfplay/5e01_apu.h" class DivPlatformNES: public DivDispatch { struct Channel: public SharedChannel { @@ -62,11 +63,15 @@ class DivPlatformNES: public DivDispatch { bool useNP; bool goingToLoop; bool countMode; + bool isE; struct NESAPU* nes; xgm::NES_APU* nes1_NP; xgm::NES_DMC* nes2_NP; + xgm::I5E01_APU* e1_NP; + xgm::I5E01_DMC* e2_NP; unsigned char regPool[128]; unsigned int sampleOffDPCM[256]; + DivMemoryComposition memCompo; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -75,6 +80,7 @@ class DivPlatformNES: public DivDispatch { unsigned char calcDPCMRate(int inRate); void acquire_puNES(short** buf, size_t len); void acquire_NSFPlay(short** buf, size_t len); + void acquire_NSFPlayE(short** buf, size_t len); public: void acquire(short** buf, size_t len); @@ -92,6 +98,7 @@ class DivPlatformNES: public DivDispatch { float getPostAmp(); unsigned char readDMC(unsigned short addr); void setNSFPlay(bool use); + void set5E01(bool use); void setFlags(const DivConfig& flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); @@ -101,6 +108,7 @@ class DivPlatformNES: public DivDispatch { size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d62161827..679a65462 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -844,7 +844,7 @@ void DivPlatformOPL::tick(bool sysTick) { unsigned short baseAddr=slotMap[slot]; DivInstrumentFM::Operator& op=chan[i].state.op[(ops==4)?orderedOpsL[j]:j]; - if (isMuted[i]) { + if (isMuted[i] && i<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(i,j) || i>melodicChans) { @@ -893,7 +893,7 @@ void DivPlatformOPL::tick(bool sysTick) { } if (chan[i].std.alg.had || chan[i].std.fb.had || (oplType==3 && chan[i].std.panL.had)) { - if (isMuted[i]) { + if (isMuted[i] && i<=melodicChans) { rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); if (ops==4) { rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); @@ -964,7 +964,7 @@ void DivPlatformOPL::tick(bool sysTick) { op.ksl=m.ksl.val; } if (m.tl.had || m.ksl.had) { - if (isMuted[i]) { + if (isMuted[i] && i<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(i,j) || i>melodicChans) { @@ -1128,7 +1128,7 @@ void DivPlatformOPL::tick(bool sysTick) { } else { if (chan[i].keyOn) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH); - drumState|=(1<<(totalChans-i-1)); + if (!isMuted[i]) drumState|=(1<<(totalChans-i-1)); updateDrums=true; chan[i].keyOn=false; } else if (chan[i].freqChanged) { @@ -1233,7 +1233,7 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { unsigned short baseAddr=slotMap[slot]; DivInstrumentFM::Operator& op=chan[ch].state.op[(ops==4)?orderedOpsL[i]:i]; - if (isMuted[ch]) { + if (isMuted[ch] && ch<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(ch,i) || ch>melodicChans) { @@ -1248,7 +1248,7 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { return; } - if (isMuted[ch]) { + if (isMuted[ch] && ch<=melodicChans) { rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)); if (ops==4) { rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1)); @@ -1289,11 +1289,7 @@ void DivPlatformOPL::commitState(int ch, DivInstrument* ins) { DivInstrumentFM::Operator& op=chan[ch].state.op[0]; chan[ch].fourOp=false; - if (isMuted[ch]) { - rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); - } else { - rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[ch].outVol&0x3f,63))|(op.ksl<<6)); - } + rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[ch].outVol&0x3f,63))|(op.ksl<<6)); rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult); rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr); @@ -1302,13 +1298,8 @@ void DivPlatformOPL::commitState(int ch, DivInstrument* ins) { rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); } - if (isMuted[ch]) { - oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1; - rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)); - } else { - oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1; - rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&15)<<4)); - } + oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1; + rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&15)<<4)); } } else { int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2; @@ -1330,7 +1321,7 @@ void DivPlatformOPL::commitState(int ch, DivInstrument* ins) { unsigned short baseAddr=slotMap[slot]; DivInstrumentFM::Operator& op=chan[ch].state.op[(ops==4)?orderedOpsL[i]:i]; - if (isMuted[ch]) { + if (isMuted[ch] && ch<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(ch,i) || ch>melodicChans) { @@ -1348,7 +1339,7 @@ void DivPlatformOPL::commitState(int ch, DivInstrument* ins) { } } - if (isMuted[ch]) { + if (isMuted[ch] && ch<=melodicChans) { oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1; rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)); if (ops==4) { @@ -1530,7 +1521,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { unsigned short baseAddr=slotMap[slot]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i]; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(c.chan,i) || c.chan>melodicChans) { @@ -1565,7 +1556,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].pan|=(c.value>0)|((c.value2>0)<<1); } int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); if (ops==4) { rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); @@ -1592,7 +1583,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { } int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); if (ops==4) { rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); @@ -1678,7 +1669,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (c.chan==adpcmChan) break; chan[c.chan].state.fb=c.value&7; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); if (ops==4) { rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); @@ -1712,7 +1703,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { unsigned char slot=slots[c.value][c.chan]; if (slot==255) break; unsigned short baseAddr=slotMap[slot]; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(c.chan,c.value) || c.chan>melodicChans) { @@ -1942,7 +1933,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { unsigned short baseAddr=slotMap[slot]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i]; op.ksl=c.value2&3; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(c.chan,i) || c.chan>melodicChans) { @@ -1959,7 +1950,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { unsigned char slot=slots[c.value][c.chan]; if (slot==255) break; unsigned short baseAddr=slotMap[slot]; - if (isMuted[c.chan]) { + if (isMuted[c.chan] && c.chan<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(c.chan,c.value) || c.chan>melodicChans) { @@ -2042,7 +2033,7 @@ void DivPlatformOPL::forceIns() { unsigned short baseAddr=slotMap[slot]; DivInstrumentFM::Operator& op=chan[i].state.op[(ops==4)?orderedOpsL[j]:j]; - if (isMuted[i]) { + if (isMuted[i] && c.chan<=melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { if (KVSL(i,j) || i>melodicChans) { @@ -2060,7 +2051,7 @@ void DivPlatformOPL::forceIns() { } } - if (isMuted[i]) { + if (isMuted[i] && c.chan<=melodicChans) { rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); if (ops==4) { rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); @@ -2539,12 +2530,21 @@ bool DivPlatformOPL::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformOPL::getMemCompo(int index) { + if (adpcmChan<0) return NULL; + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformOPL::renderSamples(int sysID) { if (adpcmChan<0) return; memset(adpcmBMem,0,getSampleMemCapacity(0)); memset(sampleOffB,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample Memory"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -2569,9 +2569,13 @@ void DivPlatformOPL::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleOffB[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } adpcmBMemLen=memPos+256; + + memCompo.used=adpcmBMemLen; + memCompo.capacity=getSampleMemCapacity(0); } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 7beb3532d..5f2fd7d28 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -126,6 +126,8 @@ class DivPlatformOPL: public DivDispatch { fmopl2_t fm_lle2; fmopl3_t fm_lle3; + DivMemoryComposition memCompo; + int octave(int freq); int toFreq(int freq); double NOTE_ADPCMB(int note); @@ -174,6 +176,7 @@ class DivPlatformOPL: public DivDispatch { size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 262f55d78..d1195b9e4 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -1099,7 +1099,7 @@ void DivPlatformOPLL::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/2; } noTopHatFreq=flags.getBool("noTopHatFreq",false); - fixedAll=flags.getBool("fixedAll",false); + fixedAll=flags.getBool("fixedAll",true); } int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 152afb932..cdd8fc6b2 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -735,11 +735,19 @@ const char* DivPlatformQSound::getSampleMemName(int index) { return index == 0 ? "PCM" : index == 1 ? "ADPCM" : NULL; } +const DivMemoryComposition* DivPlatformQSound::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformQSound::renderSamples(int sysID) { memset(sampleMem,0,getSampleMemCapacity()); memset(sampleLoaded,0,256*sizeof(bool)); memset(sampleLoadedBS,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -771,6 +779,7 @@ void DivPlatformQSound::renderSamples(int sysID) { sampleLoaded[i]=true; } offPCM[i]=memPos^0x8000; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,memPos,memPos+length)); memPos+=length+16; } sampleMemLen=memPos+256; @@ -808,9 +817,13 @@ void DivPlatformQSound::renderSamples(int sysID) { sampleLoadedBS[i]=true; } offBS[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE_ALT1,"ADPCM",i,memPos,memPos+length)); memPos+=length+16; } sampleMemLenBS=memPos+256; + + memCompo.used=sampleMemLenBS; + memCompo.capacity=getSampleMemCapacity(0); } int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index 929411402..3d817606a 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -58,6 +58,8 @@ class DivPlatformQSound: public DivDispatch { unsigned int offPCM[256]; unsigned int offBS[256]; + DivMemoryComposition memCompo; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -89,6 +91,7 @@ class DivPlatformQSound: public DivDispatch { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 148df4c28..e8887f156 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -415,11 +415,19 @@ bool DivPlatformRF5C68::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformRF5C68::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformRF5C68::renderSamples(int sysID) { memset(sampleMem,0,getSampleMemCapacity()); memset(sampleOffRFC,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample Memory"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -432,6 +440,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) { int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-32,length); if (actualLength>0) { sampleOffRFC[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+32)); for (int j=0; jdata8[j]; @@ -451,6 +460,9 @@ void DivPlatformRF5C68::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleMemLen=memPos; + + memCompo.used=sampleMemLen; + memCompo.capacity=getSampleMemCapacity(0); } int DivPlatformRF5C68::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/rf5c68.h b/src/engine/platform/rf5c68.h index 3ae081017..e87d1eb7c 100644 --- a/src/engine/platform/rf5c68.h +++ b/src/engine/platform/rf5c68.h @@ -50,6 +50,7 @@ class DivPlatformRF5C68: public DivDispatch { unsigned char* sampleMem; size_t sampleMemLen; rf5c68_device rf5c68; + DivMemoryComposition memCompo; unsigned char regPool[144]; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -80,6 +81,7 @@ class DivPlatformRF5C68: public DivDispatch { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 19ec2bbb7..3f8598c43 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -479,6 +479,11 @@ void DivPlatformSegaPCM::reset() { } } +const DivMemoryComposition* DivPlatformSegaPCM::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformSegaPCM::renderSamples(int sysID) { size_t memPos=0; @@ -486,6 +491,9 @@ void DivPlatformSegaPCM::renderSamples(int sysID) { memset(sampleLoaded,0,256*sizeof(bool)); memset(sampleOffSegaPCM,0,256*sizeof(unsigned int)); memset(sampleEndSegaPCM,0,256); + + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; for (int i=0; isong.sampleLen; i++) { DivSample* sample=parent->getSample(i); @@ -501,6 +509,7 @@ void DivPlatformSegaPCM::renderSamples(int sysID) { sampleLoaded[i]=true; if (memPos>=2097152) break; sampleOffSegaPCM[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+alignedSize)); for (unsigned int j=0; j=sample->samples) { sampleMem[memPos++]=0; @@ -514,6 +523,9 @@ void DivPlatformSegaPCM::renderSamples(int sysID) { if (memPos>=2097152) break; } sampleMemLen=memPos; + + memCompo.used=sampleMemLen; + memCompo.capacity=getSampleMemCapacity(0); } void DivPlatformSegaPCM::setFlags(const DivConfig& flags) { diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index f99da7317..1c117c266 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -80,6 +80,8 @@ class DivPlatformSegaPCM: public DivDispatch { unsigned int sampleOffSegaPCM[256]; unsigned char sampleEndSegaPCM[256]; bool sampleLoaded[256]; + + DivMemoryComposition memCompo; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -110,6 +112,7 @@ class DivPlatformSegaPCM: public DivDispatch { size_t getSampleMemCapacity(int index=0); size_t getSampleMemUsage(int index=0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); ~DivPlatformSegaPCM(); diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index ef1068c3f..b97895659 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -827,6 +827,13 @@ void DivPlatformSNES::initEcho() { rWrite(0x7d,0); rWrite(0x6d,0xff); } + + for (DivMemoryEntry& i: memCompo.entries) { + if (i.type==DIV_MEMORY_ECHO) { + i.begin=(65536-echoDelay*2048); + } + } + memCompo.used=sampleMemLen+echoDelay*2048; } void DivPlatformSNES::reset() { @@ -953,6 +960,10 @@ void DivPlatformSNES::renderSamples(int sysID) { memCompo=DivMemoryComposition(); memCompo.name="SPC/DSP Memory"; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"State",-1,0,sampleTableBase)); + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"Sample Directory",-1,sampleTableBase,sampleTableBase+8*4)); + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM,"Wave RAM",-1,sampleTableBase+8*4,sampleTableBase+8*4+8*9*16)); + // skip past sample table and wavetable buffer size_t memPos=sampleTableBase+8*4+8*9*16; for (int i=0; isong.sampleLen; i++) { @@ -971,6 +982,7 @@ void DivPlatformSNES::renderSamples(int sysID) { if (s->loop) { copyOfSampleMem[memPos+actualLength-9]|=3; } + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength)); memPos+=actualLength; } if (actualLengthapu.is_active[GB_WAVE]) { uint8_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + uint8_t base = (!gb->apu.wave_channel.double_length && gb->apu.wave_channel.bank_select) ? 32 : 0; cycles_left -= gb->apu.wave_channel.sample_countdown + 1; gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; gb->apu.wave_channel.current_sample_index++; - gb->apu.wave_channel.current_sample_index &= 0x1F; + gb->apu.wave_channel.current_sample_index &= gb->apu.wave_channel.double_length ? 0x3F : 0x1F; gb->apu.wave_channel.current_sample = - gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - cycles - cycles_left); + gb->apu.wave_channel.wave_form[base + gb->apu.wave_channel.current_sample_index]; + int8_t sample = gb->apu.wave_channel.force_3 ? + (gb->apu.wave_channel.current_sample * 3) >> 2 : + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift; + update_sample(gb, GB_WAVE, sample, cycles - cycles_left); gb->apu.wave_channel.wave_form_just_read = true; } if (cycles_left) { @@ -738,8 +740,10 @@ void GB_apu_init(GB_gameboy_t *gb) memset(&gb->apu, 0, sizeof(gb->apu)); /* Restore the wave form */ for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) { - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 32] = gb->io_registers[reg] >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 33] = gb->io_registers[reg] & 0xF; } gb->apu.lf_div = 1; /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, @@ -903,6 +907,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) } break; case GB_MODEL_AGB: + case GB_MODEL_AGB_NATIVE: /* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple pattern like the other revisions. */ /* For the most part, AGS seems to do: @@ -1160,14 +1165,24 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.is_active[GB_WAVE] = false; update_sample(gb, GB_WAVE, 0, 0); } + if (gb->model==GB_MODEL_AGB_NATIVE) { + gb->apu.wave_channel.bank_select = value & 0x40; + gb->apu.wave_channel.double_length = value & 0x20; + } break; case GB_IO_NR31: gb->apu.wave_channel.pulse_length = (0x100 - value); break; case GB_IO_NR32: gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; + if (gb->model==GB_MODEL_AGB_NATIVE) { + gb->apu.wave_channel.force_3 = value & 0x80; + } if (gb->apu.is_active[GB_WAVE]) { - update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); + int8_t sample = gb->apu.wave_channel.force_3 ? + (gb->apu.wave_channel.current_sample * 3) >> 2 : + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift; + update_sample(gb, GB_WAVE, sample, 0); } break; case GB_IO_NR33: @@ -1209,9 +1224,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (!gb->apu.is_active[GB_WAVE]) { gb->apu.is_active[GB_WAVE] = true; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - 0); + int8_t sample = gb->apu.wave_channel.force_3 ? + (gb->apu.wave_channel.current_sample * 3) >> 2 : + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift; + update_sample(gb, GB_WAVE, sample, 0); } gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; gb->apu.wave_channel.current_sample_index = 0; @@ -1411,8 +1427,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) default: if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; + uint8_t base = 0; + if (gb->model == GB_MODEL_AGB_NATIVE && + (!gb->apu.global_enable || !gb->apu.wave_channel.bank_select)) { + base = 32; + } + gb->apu.wave_channel.wave_form[base + (reg - GB_IO_WAV_START) * 2] = value >> 4; + gb->apu.wave_channel.wave_form[base + (reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; } } gb->io_registers[reg] = value; diff --git a/src/engine/platform/sound/gb/apu.h b/src/engine/platform/sound/gb/apu.h index 3c07b46fe..6ba168f71 100644 --- a/src/engine/platform/sound/gb/apu.h +++ b/src/engine/platform/sound/gb/apu.h @@ -93,12 +93,15 @@ typedef struct uint8_t shift; // NR32 uint16_t sample_length; // NR33, NR34, in APU ticks bool length_enabled; // NR34 + bool double_length; // NR30 + bool bank_select; // NR30 + bool force_3; // NR32 uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint8_t current_sample_index; uint8_t current_sample; // Current sample before shifting. - int8_t wave_form[32]; + int8_t wave_form[64]; bool wave_form_just_read; } wave_channel; diff --git a/src/engine/platform/sound/gb/gb.h b/src/engine/platform/sound/gb/gb.h index ca3650852..7adb27599 100644 --- a/src/engine/platform/sound/gb/gb.h +++ b/src/engine/platform/sound/gb/gb.h @@ -114,6 +114,7 @@ typedef enum { // GB_MODEL_CGB_D = 0x204, GB_MODEL_CGB_E = 0x205, GB_MODEL_AGB = 0x206, + GB_MODEL_AGB_NATIVE = 0x226, } GB_model_t; enum { diff --git a/src/engine/platform/sound/nds.cpp b/src/engine/platform/sound/nds.cpp new file mode 100644 index 000000000..67c45a884 --- /dev/null +++ b/src/engine/platform/sound/nds.cpp @@ -0,0 +1,634 @@ +/* + +============================================================================ + +NDS sound emulator +by cam900 + +This file is licensed under zlib license. + +============================================================================ + +zlib License + +(C) 2024-present cam900 and contributors + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +============================================================================ +TODO: +- needs to further verifications from real hardware + +Tech info: https://problemkaputt.de/gbatek.htm + +*/ + +#include "nds.hpp" + +namespace nds_sound_emu +{ + void nds_sound_t::reset() + { + for (channel_t &elem : m_channel) + elem.reset(); + for (capture_t &elem : m_capture) + elem.reset(); + + m_control = 0; + m_bias = 0; + m_loutput = 0; + m_routput = 0; + } + + void nds_sound_t::tick(s32 cycle) + { + m_loutput = m_routput = (m_bias & 0x3ff); + if (!enable()) + return; + + // mix outputs + s32 lmix = 0, rmix = 0; + for (u8 i = 0; i < 16; i++) + { + channel_t &channel = m_channel[i]; + channel.update(cycle); + // bypass mixer + if (((i == 1) && (mix_ch1())) || ((i == 3) && (mix_ch3()))) + continue; + + lmix += channel.loutput(); + rmix += channel.routput(); + } + + // send mixer output to capture + m_capture[0].update(lmix, cycle); + m_capture[1].update(rmix, cycle); + + // select left/right output source + switch (lout_from()) + { + case 0: // left mixer + break; + case 1: // channel 1 + lmix = m_channel[1].loutput(); + break; + case 2: // channel 3 + lmix = m_channel[3].loutput(); + break; + case 3: // channel 1 + 3 + lmix = m_channel[1].loutput() + m_channel[3].loutput(); + break; + } + + switch (rout_from()) + { + case 0: // right mixer + break; + case 1: // channel 1 + rmix = m_channel[1].routput(); + break; + case 2: // channel 3 + rmix = m_channel[3].routput(); + break; + case 3: // channel 1 + 3 + rmix = m_channel[1].routput() + m_channel[3].routput(); + break; + } + + // adjust master volume + lmix = (lmix * mvol()) >> 13; + rmix = (rmix * mvol()) >> 13; + + // add bias and clip output + m_loutput = clamp((lmix + (m_bias & 0x3ff)), 0, 0x3ff); + m_routput = clamp((rmix + (m_bias & 0x3ff)), 0, 0x3ff); + } + + u8 nds_sound_t::read8(u32 addr) + { + return bitfield(read32(addr >> 2), bitfield(addr, 0, 2) << 3, 8); + } + + u16 nds_sound_t::read16(u32 addr) + { + return bitfield(read32(addr >> 1), bitfield(addr, 0) << 4, 16); + } + + u32 nds_sound_t::read32(u32 addr) + { + addr <<= 2; // word address + + switch (addr & 0x100) + { + case 0x000: + if ((addr & 0xc) == 0) + return m_channel[bitfield(addr, 4, 4)].control(); + break; + case 0x100: + switch (addr & 0xff) + { + case 0x00: + return m_control; + case 0x04: + return m_bias; + case 0x08: + return m_capture[0].control() | (m_capture[1].control() << 8); + case 0x10: + case 0x18: + return m_capture[bitfield(addr, 3)].dstaddr(); + default: + break; + } + break; + } + return 0; + } + + void nds_sound_t::write8(u32 addr, u8 data) + { + const u8 bit = bitfield(addr, 0, 2); + const u32 in = u32(data) << (bit << 3); + const u32 in_mask = 0xff << (bit << 3); + write32(addr >> 2, in, in_mask); + } + + void nds_sound_t::write16(u32 addr, u16 data, u16 mask) + { + const u8 bit = bitfield(addr, 0); + const u32 in = u32(data) << (bit << 4); + const u32 in_mask = u32(mask) << (bit << 4); + write32(addr >> 1, in, in_mask); + } + + void nds_sound_t::write32(u32 addr, u32 data, u32 mask) + { + addr <<= 2; // word address + + switch (addr & 0x100) + { + case 0x000: + m_channel[bitfield(addr, 4, 4)].write(bitfield(addr, 2, 2), data, mask); + break; + case 0x100: + switch (addr & 0xff) + { + case 0x00: + m_control = (m_control & ~mask) | (data & mask); + break; + case 0x04: + mask &= 0x3ff; + m_bias = (m_bias & ~mask) | (data & mask); + break; + case 0x08: + if (bitfield(mask, 0, 8)) + m_capture[0].control_w(data & 0xff); + if (bitfield(mask, 8, 8)) + m_capture[1].control_w((data >> 8) & 0xff); + break; + case 0x10: + case 0x14: + case 0x18: + case 0x1c: + m_capture[bitfield(addr, 3)].addrlen_w(bitfield(addr, 2), data, mask); + break; + default: + break; + } + break; + } + } + + // channels + void nds_sound_t::channel_t::reset() + { + m_control = 0; + m_sourceaddr = 0; + m_freq = 0; + m_loopstart = 0; + m_length = 0; + + m_playing = false; + m_adpcm_out = 0; + m_adpcm_index = 0; + m_prev_adpcm_out = 0; + m_prev_adpcm_index = 0; + m_cur_addr = 0; + m_cur_state = 0; + m_cur_bitaddr = 0; + m_delay = 0; + m_sample = 0; + m_lfsr = 0x7fff; + m_lfsr_out = 0x7fff; + m_counter = 0x10000; + m_output = 0; + m_loutput = 0; + m_routput = 0; + } + + void nds_sound_t::channel_t::write(u32 offset, u32 data, u32 mask) + { + const u32 old = m_control; + switch (offset & 3) + { + case 0: // Control/Status + m_control = (m_control & ~mask) | (data & mask); + if (bitfield(old ^ m_control, 31)) + { + if (busy()) + keyon(); + else if (!busy()) + keyoff(); + } + // reset hold flag + if (!m_playing && !hold()) + { + m_sample = m_lfsr_out = 0; + m_output = m_loutput = m_routput = 0; + } + break; + case 1: // Source address + mask &= 0x7ffffff; + m_sourceaddr = (m_sourceaddr & ~mask) | (data & mask); + break; + case 2: // Frequency, Loopstart + if (bitfield(mask, 0, 16)) + m_freq = (m_freq & bitfield(~mask, 0, 16)) | (bitfield(data & mask, 0, 16)); + if (bitfield(mask, 16, 16)) + m_loopstart = (m_loopstart & bitfield(~mask, 16, 16)) | (bitfield(data & mask, 16, 16)); + break; + case 3: // Length + mask &= 0x3fffff; + m_length = (m_length & ~mask) | (data & mask); + break; + } + } + + void nds_sound_t::channel_t::keyon() + { + if (!m_playing) + { + m_playing = true; + m_delay = format() == 2 ? 11 : 3; // 3 (11 for ADPCM) delay for playing sample + m_cur_bitaddr = m_cur_addr = 0; + m_cur_state = (format() == 2) ? STATE_ADPCM_LOAD : ((m_loopstart == 0) ? STATE_POST_LOOP : STATE_PRE_LOOP); + m_counter = 0x10000; + m_sample = 0; + m_lfsr_out = 0x7fff; + m_lfsr = 0x7fff; + } + } + + + void nds_sound_t::channel_t::keyoff() + { + if (m_playing) + { + if (busy()) + m_control &= ~(1 << 31); + if (!hold()) + { + m_sample = m_lfsr_out = 0; + m_output = m_loutput = m_routput = 0; + } + + m_playing = false; + } + } + + void nds_sound_t::channel_t::update(s32 cycle) + { + if (m_playing) + { + // get output + fetch(); + m_counter -= cycle; + while (m_counter <= m_freq) + { + // advance + advance(); + m_counter += 0x10000 - m_freq; + } + m_output = (m_sample * volume()) >> (7 + voldiv()); + m_loutput = (m_output * lvol()) >> 7; + m_routput = (m_output * rvol()) >> 7; + } + } + + void nds_sound_t::channel_t::fetch() + { + if (m_playing) + { + // fetch samples + switch (format()) + { + case 0: // PCM8 + m_sample = s16(m_host.m_intf.read_byte(addr()) << 8); + break; + case 1: // PCM16 + m_sample = m_host.m_intf.read_word(addr()); + break; + case 2: // ADPCM + m_sample = m_cur_state == STATE_ADPCM_LOAD ? 0 : m_adpcm_out; + break; + case 3: // PSG or Noise + m_sample = 0; + if (m_psg) // psg + m_sample = (duty() == 7) ? -0x8000 : ((m_cur_bitaddr < s32(u32(7) - duty())) ? -0x8000 : 0x7fff); + else if (m_noise) // noise + m_sample = m_lfsr_out; + break; + } + } + + // apply delay + if (format() != 3 && m_delay > 0) + m_sample = 0; + } + + void nds_sound_t::channel_t::advance() + { + if (m_playing) + { + // advance bit address + switch (format()) + { + case 0: // PCM8 + m_cur_bitaddr += 8; + break; + case 1: // PCM16 + m_cur_bitaddr += 16; + break; + case 2: // ADPCM + if (m_cur_state == STATE_ADPCM_LOAD) // load ADPCM data + { + if (m_cur_bitaddr == 0) + m_prev_adpcm_out = m_adpcm_out = m_host.m_intf.read_word(addr()); + if (m_cur_bitaddr == 16) + m_prev_adpcm_index = m_adpcm_index = clamp(m_host.m_intf.read_byte(addr()) & 0x7f, 0, 88); + } + else // decode ADPCM + { + const u8 input = bitfield(m_host.m_intf.read_byte(addr()), m_cur_bitaddr & 4, 4); + s32 diff = ((bitfield(input, 0, 3) * 2 + 1) * m_host.adpcm_diff_table[m_adpcm_index] / 8); + if (bitfield(input, 3)) diff = -diff; + m_adpcm_out = clamp(m_adpcm_out + diff, -0x8000, 0x7fff); + m_adpcm_index = clamp(m_adpcm_index + m_host.adpcm_index_table[bitfield(input, 0, 3)], 0, 88); + } + m_cur_bitaddr += 4; + break; + case 3: // PSG or Noise + if (m_psg) // psg + m_cur_bitaddr = (m_cur_bitaddr + 1) & 7; + else if (m_noise) // noise + { + if (bitfield(m_lfsr, 1)) + { + m_lfsr = (m_lfsr >> 1) ^ 0x6000; + m_lfsr_out = -0x8000; + } + else + { + m_lfsr >>= 1; + m_lfsr_out = 0x7fff; + } + } + break; + } + + // address update + if (format() != 3) + { + // adjust delay + m_delay--; + + // update address, loop + while (m_cur_bitaddr >= 32) + { + // already loaded? + if (format() == 2 && m_cur_state == STATE_ADPCM_LOAD) + { + m_cur_state = m_loopstart == 0 ? STATE_POST_LOOP : STATE_PRE_LOOP; + } + m_cur_addr++; + if (m_cur_state == STATE_PRE_LOOP && m_cur_addr >= m_loopstart) + { + m_cur_state = STATE_POST_LOOP; + m_cur_addr = 0; + if (format() == 2) + { + m_prev_adpcm_out = m_adpcm_out; + m_prev_adpcm_index = m_adpcm_index; + } + } + else if (m_cur_state == STATE_POST_LOOP && m_cur_addr >= m_length) + { + switch (repeat()) + { + case 0: // manual; not correct? + case 2: // one-shot + case 3: // prohibited + keyoff(); + break; + case 1: // loop infinitely + if (format() == 2) + { + if (m_loopstart == 0) // reload ADPCM + { + m_cur_state = STATE_ADPCM_LOAD; + } + else // restore + { + m_adpcm_out = m_prev_adpcm_out; + m_adpcm_index = m_prev_adpcm_index; + } + } + m_cur_addr = 0; + break; + } + } + m_cur_bitaddr -= 32; + } + } + } + } + + // capture + void nds_sound_t::capture_t::reset() + { + m_control = 0; + m_dstaddr = 0; + m_length = 0; + + m_counter = 0x10000; + m_cur_addr = 0; + m_cur_waddr = 0; + m_cur_bitaddr = 0; + m_enable = false; + } + + void nds_sound_t::capture_t::control_w(u8 data) + { + const u8 old = m_control; + m_control = data; + if (bitfield(old ^ m_control, 7)) + { + if (busy()) + capture_on(); + else if (!busy()) + capture_off(); + } + } + + void nds_sound_t::capture_t::addrlen_w(u32 offset, u32 data, u32 mask) + { + switch (offset & 1) + { + case 0: // Destination Address + mask &= 0x7ffffff; + m_dstaddr = (m_dstaddr & ~mask) | (data & mask); + break; + case 1: // Buffer Length + mask &= 0xffff; + m_length = (m_length & ~mask) | (data & mask); + break; + } + } + + void nds_sound_t::capture_t::update(s32 mix, s32 cycle) + { + if (m_enable) + { + s32 inval = 0; + // get inputs + // TODO: hardware bugs aren't emulated, add mode behavior not verified + if (addmode()) + inval = get_source() ? m_input.output() + m_output.output() : mix; + else + inval = get_source() ? m_input.output() : mix; + + // clip output + inval = clamp(inval, -0x8000, 0x7fff); + + // update counter + m_counter -= cycle; + while (m_counter <= m_output.freq()) + { + // write to memory; TODO: verify write behavior + if (format()) // 8 bit output + { + m_fifo[m_fifo_head & 7].write_byte(m_cur_bitaddr & 0x18, (inval >> 8) & 0xff); + m_cur_bitaddr += 8; + } + else + { + m_fifo[m_fifo_head & 7].write_word(m_cur_bitaddr & 0x10, inval & 0xffff); + m_cur_bitaddr += 16; + } + + // update address + while (m_cur_bitaddr >= 32) + { + // clear FIFO empty flag + m_fifo_empty = false; + + // advance FIFO head position + m_fifo_head = (m_fifo_head + 1) & 7; + if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask())) + m_fifo_full = true; + + // update loop + if (++m_cur_addr >= m_length) + { + if (repeat()) + m_cur_addr = 0; + else + capture_off(); + } + + if (m_fifo_full) + { + // execute FIFO + fifo_write(); + + // check repeat + if (m_cur_waddr >= m_length && repeat()) + m_cur_waddr = 0; + } + + m_cur_bitaddr -= 32; + } + m_counter += 0x10000 - m_output.freq(); + } + } + } + + bool nds_sound_t::capture_t::fifo_write() + { + if (m_fifo_empty) + return true; + + // clear FIFO full flag + m_fifo_full = false; + + // write FIFO data to memory + m_host.m_intf.write_dword(waddr(), m_fifo[m_fifo_tail].data()); + m_cur_waddr++; + + // advance FIFO tail position + m_fifo_tail = (m_fifo_tail + 1) & 7; + if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask())) + m_fifo_empty = true; + + return m_fifo_empty; + } + + void nds_sound_t::capture_t::capture_on() + { + if (!m_enable) + { + m_enable = true; + + // reset address + m_cur_bitaddr = 0; + m_cur_addr = m_cur_waddr = 0; + m_counter = 0x10000; + + // reset FIFO + m_fifo_head = m_fifo_tail = 0; + m_fifo_empty = true; + m_fifo_full = false; + } + } + + void nds_sound_t::capture_t::capture_off() + { + if (m_enable) + { + // flush FIFO + while (m_cur_waddr < m_length) + { + if (fifo_write()) + break; + } + + m_enable = false; + if (busy()) + m_control &= ~(1 << 7); + } + } + +}; // namespace nds_sound_emu \ No newline at end of file diff --git a/src/engine/platform/sound/nds.hpp b/src/engine/platform/sound/nds.hpp new file mode 100644 index 000000000..8f67f436a --- /dev/null +++ b/src/engine/platform/sound/nds.hpp @@ -0,0 +1,415 @@ +/* + +============================================================================ + +NDS sound emulator +by cam900 + +This file is licensed under zlib license. + +============================================================================ + +zlib License + +(C) 2024-present cam900 and contributors + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +============================================================================ +TODO: +- needs to further verifications from real hardware + +Tech info: https://problemkaputt.de/gbatek.htm + +*/ + +#ifndef NDS_SOUND_EMU_H +#define NDS_SOUND_EMU_H + +namespace nds_sound_emu +{ + using u8 = unsigned char; + using u16 = unsigned short; + using u32 = unsigned int; + using u64 = unsigned long long; + using s8 = signed char; + using s16 = signed short; + using s32 = signed int; + using s64 = signed long long; + + template + static const inline T bitfield(const T in, const u8 pos) + { + return (in >> pos) & 1; + } // bitfield + + template + static const inline T bitfield(const T in, const u8 pos, const u8 len) + { + return (in >> pos) & ((1 << len) - 1); + } // bitfield + + template + static const inline T clamp(const T in, const T min, const T max) + { + return (in < min) ? min : ((in > max) ? max : in); + } // clamp + + class nds_sound_intf + { + public: + nds_sound_intf() + { + } + + virtual u8 read_byte(u32 addr) { return 0; } + inline u16 read_word(u32 addr) { return read_byte(addr) | (u16(read_byte(addr + 1)) << 8); } + inline u32 read_dword(u32 addr) { return read_word(addr) | (u16(read_word(addr + 2)) << 16); } + + virtual void write_byte(u32 addr, u8 data) {} + inline void write_word(u32 addr, u16 data) + { + write_byte(addr, data & 0xff); + write_byte(addr + 1, data >> 8); + } + inline void write_dword(u32 addr, u32 data) + { + write_word(addr, data & 0xffff); + write_word(addr + 2, data >> 16); + } + }; + + class nds_sound_t + { + public: + nds_sound_t(nds_sound_intf &intf) + : m_intf(intf) + , m_channel{ + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, false, false), channel_t(*this, false, false), + channel_t(*this, true, false), channel_t(*this, true, false), + channel_t(*this, true, false), channel_t(*this, true, false), + channel_t(*this, true, false), channel_t(*this, true, false), + channel_t(*this, false, true), channel_t(*this, false, true) + } + , m_capture{ + capture_t(*this, m_channel[0], m_channel[1]), + capture_t(*this, m_channel[2], m_channel[3]) + } + , m_control(0) + , m_bias(0) + , m_loutput(0) + , m_routput(0) + { + } + + void reset(); + void tick(s32 cycle); + + // host accesses + u32 read32(u32 addr); + void write32(u32 addr, u32 data, u32 mask = ~0); + + u16 read16(u32 addr); + void write16(u32 addr, u16 data, u16 mask = ~0); + + u8 read8(u32 addr); + void write8(u32 addr, u8 data); + + s32 loutput() { return m_loutput; } + s32 routput() { return m_routput; } + + // for debug + s32 chan_lout(u8 ch) { return m_channel[ch].loutput(); } + s32 chan_rout(u8 ch) { return m_channel[ch].routput(); } + + private: + // ADPCM tables + s8 adpcm_index_table[8] = + { + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + u16 adpcm_diff_table[89] = + { + 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x0010, + 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001c, 0x001f, 0x0022, 0x0025, + 0x0029, 0x002d, 0x0032, 0x0037, 0x003c, 0x0042, 0x0049, 0x0050, 0x0058, + 0x0061, 0x006b, 0x0076, 0x0082, 0x008f, 0x009d, 0x00ad, 0x00be, 0x00d1, + 0x00e6, 0x00fd, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01c1, 0x01ee, + 0x0220, 0x0256, 0x0292, 0x02d4, 0x031c, 0x036c, 0x03c3, 0x0424, 0x048e, + 0x0502, 0x0583, 0x0610, 0x06ab, 0x0756, 0x0812, 0x08e0, 0x09c3, 0x0abd, + 0x0bd0, 0x0cff, 0x0e4c, 0x0fba, 0x114c, 0x1307, 0x14ee, 0x1706, 0x1954, + 0x1bdc, 0x1ea5, 0x21b6, 0x2515, 0x28ca, 0x2cdf, 0x315b, 0x364b, 0x3bb9, + 0x41b2, 0x4844, 0x4f7e, 0x5771, 0x602f, 0x69ce, 0x7462, 0x7fff + }; + + // structs + enum + { + STATE_ADPCM_LOAD = 0, + STATE_PRE_LOOP, + STATE_POST_LOOP + }; + + class channel_t + { + public: + channel_t(nds_sound_t &host, bool psg, bool noise) + : m_host(host) + + , m_psg(psg) + , m_noise(noise) + + , m_control(0) + , m_sourceaddr(0) + , m_freq(0) + , m_loopstart(0) + , m_length(0) + , m_playing(false) + , m_adpcm_out(0) + , m_adpcm_index(0) + , m_prev_adpcm_out(0) + , m_prev_adpcm_index(0) + , m_cur_addr(0) + , m_cur_state(0) + , m_cur_bitaddr(0) + , m_delay(0) + , m_sample(0) + , m_lfsr(0x7fff) + , m_lfsr_out(0x7fff) + , m_counter(0x10000) + , m_output(0) + , m_loutput(0) + , m_routput(0) + { + } + + void reset(); + void write(u32 offset, u32 data, u32 mask = ~0); + + void update(s32 cycle); + + // getters + // control word + u32 control() const { return m_control; } + u32 freq() const { return m_freq; } + + // outputs + s32 output() const { return m_output; } + s32 loutput() const { return m_loutput; } + s32 routput() const { return m_routput; } + + private: + // inline constants + const u8 m_voldiv_shift[4] = {0, 1, 2, 4}; + + // control bits + s32 volume() const { return bitfield(m_control, 0, 7); } // global volume + u32 voldiv() const { return m_voldiv_shift[bitfield(m_control, 8, 2)]; } // volume shift + bool hold() const { return bitfield(m_control, 15); } // hold bit + u32 pan() const { return bitfield(m_control, 16, 7); } // panning (0...127, 0 = left, 127 = right, 64 = half) + u32 duty() const { return bitfield(m_control, 24, 3); } // PSG duty + u32 repeat() const { return bitfield(m_control, 27, 2); } // Repeat mode (Manual, Loop infinitely, One-shot) + u32 format() const { return bitfield(m_control, 29, 2); } // Sound Format (PCM8, PCM16, ADPCM, PSG/Noise when exists) + bool busy() const { return bitfield(m_control, 31); } // Busy flag + + // calculated values + s32 lvol() const { return (pan() == 0x7f) ? 0 : 128 - pan(); } // calculated left volume + s32 rvol() const { return (pan() == 0x7f) ? 128 : pan(); } // calculated right volume + + // calculated address + u32 addr() const { return (m_sourceaddr & ~3) + (m_cur_bitaddr >> 3) + (m_cur_state == STATE_POST_LOOP ? ((m_loopstart + m_cur_addr) << 2) : (m_cur_addr << 2)); } + + void keyon(); + void keyoff(); + void fetch(); + void advance(); + + // interfaces + nds_sound_t &m_host; // host device + + // configuration + bool m_psg = false; // PSG Enable + bool m_noise = false; // Noise Enable + + // registers + u32 m_control = 0; // Control + u32 m_sourceaddr = 0; // Source Address + u16 m_freq = 0; // Frequency + u16 m_loopstart = 0; // Loop Start + u32 m_length = 0; // Length + + // internal states + bool m_playing = false; // playing flag + s32 m_adpcm_out = 0; // current ADPCM sample value + s32 m_adpcm_index = 0; // current ADPCM step + s32 m_prev_adpcm_out = 0; // previous ADPCM sample value + s32 m_prev_adpcm_index = 0; // previous ADPCM step + u32 m_cur_addr = 0; // current address + s32 m_cur_state = 0; // current state + s32 m_cur_bitaddr = 0; // bit address + s32 m_delay = 0; // delay + s16 m_sample = 0; // current sample + u32 m_lfsr = 0x7fff; // noise LFSR + s16 m_lfsr_out = 0x7fff; // LFSR output + s32 m_counter = 0x10000; // clock counter + s32 m_output = 0; // current output + s32 m_loutput = 0; // current left output + s32 m_routput = 0; // current right output + }; + + class capture_t + { + public: + capture_t(nds_sound_t &host, channel_t &input, channel_t &output) + : m_host(host) + , m_input(input) + , m_output(output) + + , m_control(0) + , m_dstaddr(0) + , m_length(0) + + , m_counter(0x10000) + , m_cur_addr(0) + , m_cur_waddr(0) + , m_cur_bitaddr(0) + , m_enable(0) + + , m_fifo{ + fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t(), + fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t() + } + , m_fifo_head(0) + , m_fifo_tail(0) + , m_fifo_empty(true) + , m_fifo_full(false) + { + } + + void reset(); + void update(s32 mix, s32 cycle); + + void control_w(u8 data); + void addrlen_w(u32 offset, u32 data, u32 mask = ~0); + + // getters + u32 control() const { return m_control; } + u32 dstaddr() const { return m_dstaddr; } + + private: + // inline constants + // control bits + bool addmode() const { return bitfield(m_control, 0); } // Add mode (add channel 1/3 output with channel 0/2) + bool get_source() const { return bitfield(m_control, 1); } // Select source (left or right mixer, channel 0/2) + bool repeat() const { return bitfield(m_control, 2); } // repeat flag + bool format() const { return bitfield(m_control, 3); } // store format (PCM16, PCM8) + bool busy() const { return bitfield(m_control, 7); } // busy flag + + // FIFO offset mask + u32 fifo_mask() const { return format() ? 7 : 3; } + + // calculated address + u32 waddr() const { return (m_dstaddr & ~3) + (m_cur_waddr << 2); } + + void capture_on(); + void capture_off(); + bool fifo_write(); + + // interfaces + nds_sound_t &m_host; // host device + channel_t &m_input; // Input channel + channel_t &m_output; // Output channel + + // registers + u8 m_control = 0; // Control + u32 m_dstaddr = 0; // Destination Address + u32 m_length = 0; // Buffer Length + + // internal states + u32 m_counter = 0x10000; // clock counter + u32 m_cur_addr = 0; // current address + u32 m_cur_waddr = 0; // current write address + s32 m_cur_bitaddr = 0; // bit address + bool m_enable = false; // capture enable + + // FIFO + class fifo_data_t + { + public: + fifo_data_t() + : m_data(0) + { + } + + void reset() + { + m_data = 0; + } + + // accessors + void write_byte(const u8 bit, const u8 data) + { + u32 input = u32(data) << bit; + u32 mask = (0xff << bit); + m_data = (m_data & ~mask) | (input & mask); + } + + void write_word(const u8 bit, const u16 data) + { + u32 input = u32(data) << bit; + u32 mask = (0xffff << bit); + m_data = (m_data & ~mask) | (input & mask); + } + + // getters + u32 data() const { return m_data; } + + private: + u32 m_data = 0; + }; + + fifo_data_t m_fifo[8]; // FIFO (8 word, for 16 sample delay) + u32 m_fifo_head = 0; // FIFO head + u32 m_fifo_tail = 0; // FIFO tail + bool m_fifo_empty = true; // FIFO empty flag + bool m_fifo_full = false; // FIFO full flag + }; + + nds_sound_intf &m_intf; // memory interface + + channel_t m_channel[16]; // 16 channels + capture_t m_capture[2]; // 2 capture channels + + inline u8 mvol() const { return bitfield(m_control, 0, 7); } // master volume + inline u8 lout_from() const { return bitfield(m_control, 8, 2); } // left output source (mixer, channel 1, channel 3, channel 1+3) + inline u8 rout_from() const { return bitfield(m_control, 10, 2); } // right output source (mixer, channel 1, channel 3, channel 1+3) + inline bool mix_ch1() const { return bitfield(m_control, 12); } // mix/bypass channel 1 + inline bool mix_ch3() const { return bitfield(m_control, 13); } // mix/bypass channel 3 + inline bool enable() const { return bitfield(m_control, 15); } // global enable + + u32 m_control = 0; // global control + u32 m_bias = 0; // output bias + s32 m_loutput = 0; // left output + s32 m_routput = 0; // right output + }; +}; // namespace nds_sound_emu + +#endif // NDS_SOUND_EMU_H \ No newline at end of file diff --git a/src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp b/src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp new file mode 100644 index 000000000..01cca33f0 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp @@ -0,0 +1,401 @@ +// +// I5E01 2A03 +// +#include +#include "5e01_apu.h" +#include "common.h" + +namespace xgm +{ + void I5E01_APU::sweep_sqr(int i) + { + int shifted = freq[i] >> sweep_amount[i]; + if (i == 0 && sweep_mode[i]) shifted += 1; + sfreq[i] = freq[i] + (sweep_mode[i] ? -shifted : shifted); + //DEBUG_OUT("shifted[%d] = %d (%d >> %d)\n",i,shifted,freq[i],sweep_amount[i]); + } + + void I5E01_APU::FrameSequence(int s) + { + //DEBUG_OUT("FrameSequence(%d)\n",s); + + if (s > 3) return; // no operation in step 4 + + // 240hz clock + for (int i = 0; i < 2; ++i) + { + bool divider = false; + if (envelope_write[i]) + { + envelope_write[i] = false; + envelope_counter[i] = 15; + envelope_div[i] = 0; + } + else + { + ++envelope_div[i]; + if (envelope_div[i] > envelope_div_period[i]) + { + divider = true; + envelope_div[i] = 0; + } + } + if (divider) + { + if (envelope_loop[i] && envelope_counter[i] == 0) + envelope_counter[i] = 15; + else if (envelope_counter[i] > 0) + --envelope_counter[i]; + } + } + + // 120hz clock + if ((s & 1) == 0) + for (int i = 0; i < 2; ++i) + { + if (!envelope_loop[i] && (length_counter[i] > 0)) + --length_counter[i]; + + if (sweep_enable[i]) + { + //DEBUG_OUT("Clock sweep: %d\n", i); + + --sweep_div[i]; + if (sweep_div[i] <= 0) + { + sweep_sqr(i); // calculate new sweep target + + //DEBUG_OUT("sweep_div[%d] (0/%d)\n",i,sweep_div_period[i]); + //DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d\n",i,freq[i],i,sfreq[i]); + + if (freq[i] >= 8 && sfreq[i] < 0x800 && sweep_amount[i] > 0) // update frequency if appropriate + { + freq[i] = sfreq[i] < 0 ? 0 : sfreq[i]; + } + sweep_div[i] = sweep_div_period[i] + 1; + + //DEBUG_OUT("freq[%d]=%d\n",i,freq[i]); + } + + if (sweep_write[i]) + { + sweep_div[i] = sweep_div_period[i] + 1; + sweep_write[i] = false; + } + } + } + + } + + int I5E01_APU::calc_sqr(int i, unsigned int clocks) + { + static const short sqrtbl[4][16] = { + {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0} + }; + + scounter[i] -= clocks; + while (scounter[i] < 0) + { + sphase[i] = (sphase[i] + 1) & 15; + scounter[i] += freq[i] + 1; + } + + int ret = 0; + if (length_counter[i] > 0 && + freq[i] >= 8 && + sfreq[i] < 0x800 + ) + { + int v = envelope_disable[i] ? volume[i] : envelope_counter[i]; + ret = sqrtbl[duty[i]][sphase[i]] ? v : 0; + } + + return ret; + } + + bool I5E01_APU::Read(unsigned int adr, unsigned int& val, unsigned int id) + { + if (0x4100 <= adr && adr < 0x4108) + { + val |= reg[adr & 0x7]; + return true; + } + else if (adr == 0x4115) + { + val |= (length_counter[1] ? 2 : 0) | (length_counter[0] ? 1 : 0); + return true; + } + else + return false; + } + + void I5E01_APU::Tick(unsigned int clocks) + { + out[0] = calc_sqr(0, clocks); + out[1] = calc_sqr(1, clocks); + } + + // 生成される波形の振幅は0-8191 + unsigned int I5E01_APU::Render(int b[2]) + { + out[0] = (mask & 1) ? 0 : out[0]; + out[1] = (mask & 2) ? 0 : out[1]; + + int m[2]; + + if (option[OPT_NONLINEAR_MIXER]) + { + int voltage = square_table[out[0] + out[1]]; + m[0] = out[0] << 6; + m[1] = out[1] << 6; + int ref = m[0] + m[1]; + if (ref > 0) + { + m[0] = (m[0] * voltage) / ref; + m[1] = (m[1] * voltage) / ref; + } + else + { + m[0] = voltage; + m[1] = voltage; + } + } + else + { + m[0] = (out[0] * square_linear) / 15; + m[1] = (out[1] * square_linear) / 15; + } + + b[0] = m[0] * sm[0][0]; + b[0] += m[1] * sm[0][1]; + b[0] >>= 7; + + b[1] = m[0] * sm[1][0]; + b[1] += m[1] * sm[1][1]; + b[1] >>= 7; + + return 2; + } + + I5E01_APU::I5E01_APU() + { + SetClock(DEFAULT_CLOCK); + SetRate(DEFAULT_RATE); + option[OPT_UNMUTE_ON_RESET] = true; + option[OPT_PHASE_REFRESH] = true; + option[OPT_NONLINEAR_MIXER] = true; + option[OPT_DUTY_SWAP] = false; + option[OPT_NEGATE_SWEEP_INIT] = false; + + square_table[0] = 0; + for (int i = 1;i < 32;i++) + square_table[i] = (int)((8192.0 * 95.88) / (8128.0 / i + 100)); + + square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear + + for (int c = 0;c < 2;++c) + for (int t = 0;t < 2;++t) + sm[c][t] = 128; + } + + I5E01_APU::~I5E01_APU() + { + } + + void I5E01_APU::Reset() + { + int i; + gclock = 0; + mask = 0; + + for (int i = 0; i < 2; ++i) + { + scounter[i] = 0; + sphase[i] = 0; + duty[i] = 0; + volume[i] = 0; + freq[i] = 0; + sfreq[i] = 0; + sweep_enable[i] = 0; + sweep_mode[i] = 0; + sweep_write[i] = 0; + sweep_div_period[i] = 0; + sweep_div[i] = 1; + sweep_amount[i] = 0; + envelope_disable[i] = 0; + envelope_loop[i] = 0; + envelope_write[i] = 0; + envelope_div_period[i] = 0; + envelope_div[0] = 0; + envelope_counter[i] = 0; + length_counter[i] = 0; + enable[i] = 0; + } + + for (i = 0x4100; i < 0x4108; i++) + Write(i, 0); + + Write(0x4115, 0); + if (option[OPT_UNMUTE_ON_RESET]) + Write(0x4115, 0x0f); + if (option[OPT_NEGATE_SWEEP_INIT]) + { + Write(0x4101, 0x08); + Write(0x4105, 0x08); + } + + for (i = 0; i < 2; i++) + out[i] = 0; + + SetRate(rate); + } + + void I5E01_APU::SetOption(int id, int val) + { + if (id < OPT_END) option[id] = val; + } + + void I5E01_APU::SetClock(double c) + { + clock = c; + } + + void I5E01_APU::SetRate(double r) + { + rate = r ? r : DEFAULT_RATE; + } + + void I5E01_APU::SetStereoMix(int trk, short mixl, short mixr) + { + if (trk < 0) return; + if (trk > 1) return; + sm[0][trk] = mixl; + sm[1][trk] = mixr; + } + + double I5E01_APU::GetFrequencyPulse1() const // // !! + { + if (!(length_counter[0] > 0 && + freq[0] >= 8 && + sfreq[0] < 0x800)) + return 0.0; + return clock / 16 / (freq[0] + 1); + } + + double I5E01_APU::GetFrequencyPulse2() const // // !! + { + if (!(length_counter[1] > 0 && + freq[1] >= 8 && + sfreq[1] < 0x800)) + return 0.0; + return clock / 16 / (freq[1] + 1); + } + + bool I5E01_APU::Write(unsigned int adr, unsigned int val, unsigned int id) + { + int ch; + + // hack + adr+=0x100; + + static const unsigned char length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + if (0x4100 <= adr && adr < 0x4108) + { + //DEBUG_OUT("$%04X = %02X\n",adr,val); + + adr &= 0xf; + ch = adr >> 2; + switch (adr) + { + case 0x0: + case 0x4: + volume[ch] = val & 15; + envelope_disable[ch] = (val >> 4) & 1; + envelope_loop[ch] = (val >> 5) & 1; + envelope_div_period[ch] = (val & 15); + duty[ch] = (val >> 6) & 3; + if (option[OPT_DUTY_SWAP]) + { + if (duty[ch] == 1) duty[ch] = 2; + else if (duty[ch] == 2) duty[ch] = 1; + } + break; + + case 0x1: + case 0x5: + sweep_enable[ch] = (val >> 7) & 1; + sweep_div_period[ch] = (((val >> 4) & 7)); + sweep_mode[ch] = (val >> 3) & 1; + sweep_amount[ch] = val & 7; + sweep_write[ch] = true; + sweep_sqr(ch); + break; + + case 0x2: + case 0x6: + freq[ch] = val | (freq[ch] & 0x700); + sweep_sqr(ch); + break; + + case 0x3: + case 0x7: + freq[ch] = (freq[ch] & 0xFF) | ((val & 0x7) << 8); + if (option[OPT_PHASE_REFRESH]) + sphase[ch] = 0; + envelope_write[ch] = true; + if (enable[ch]) + { + length_counter[ch] = length_table[(val >> 3) & 0x1f]; + } + sweep_sqr(ch); + break; + + default: + return false; + } + reg[adr] = val; + return true; + } + else if (adr == 0x4115) + { + enable[0] = (val & 1) ? true : false; + enable[1] = (val & 2) ? true : false; + + if (!enable[0]) + length_counter[0] = 0; + if (!enable[1]) + length_counter[1] = 0; + + reg[adr - 0x4100] = val; + return true; + } + + // 4017 is handled in nes_dmc.cpp + //else if (adr == 0x4017) + //{ + //} + + return false; + } +} // namespace xgm; diff --git a/src/engine/platform/sound/nes_nsfplay/5e01_apu.h b/src/engine/platform/sound/nes_nsfplay/5e01_apu.h new file mode 100644 index 000000000..3e42a3d11 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/5e01_apu.h @@ -0,0 +1,89 @@ +#ifndef _I5E01_APU_H_ +#define _I5E01_APU_H_ +#include "5e01_dmc.h" + +namespace xgm +{ + /** Upper half of APU **/ + class I5E01_APU + { + public: + enum + { + OPT_UNMUTE_ON_RESET=0, + OPT_PHASE_REFRESH, + OPT_NONLINEAR_MIXER, + OPT_DUTY_SWAP, + OPT_NEGATE_SWEEP_INIT, + OPT_END }; + + enum + { SQR0_MASK = 1, SQR1_MASK = 2, }; + + public: + int option[OPT_END]; // 各種オプション + int mask; + int sm[2][2]; + + unsigned int gclock; + unsigned char reg[0x20]; + double rate, clock; + + int square_table[32]; // nonlinear mixer + int square_linear; // linear mix approximation + + int scounter[2]; // frequency divider + int sphase[2]; // phase counter + + int duty[2]; + int volume[2]; + int freq[2]; + int sfreq[2]; + + bool sweep_enable[2]; + bool sweep_mode[2]; + bool sweep_write[2]; + int sweep_div_period[2]; + int sweep_div[2]; + int sweep_amount[2]; + + bool envelope_disable[2]; + bool envelope_loop[2]; + bool envelope_write[2]; + int envelope_div_period[2]; + int envelope_div[2]; + int envelope_counter[2]; + + int length_counter[2]; + + bool enable[2]; + + void sweep_sqr (int ch); // calculates target sweep frequency + int calc_sqr (int ch, unsigned int clocks); + + public: + int out[2]; + I5E01_APU (); + ~I5E01_APU (); + + // // !! fetch frequency directly instead of through GetTrackInfo() + double GetFrequencyPulse1() const; + double GetFrequencyPulse2() const; + + void FrameSequence(int s); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + void SetRate (double rate); + void SetClock (double clock); + void SetOption (int id, int b); + void SetMask(int m){ mask = m; } + void SetStereoMix (int trk, short mixl, short mixr); + }; + +} // namespace + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp b/src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp new file mode 100644 index 000000000..2d961a6e7 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp @@ -0,0 +1,732 @@ +#include "5e01_dmc.h" +#include "5e01_apu.h" + +#include "common.h" +#include +#include + +namespace xgm +{ + const unsigned int I5E01_DMC::wavlen_table[2][32] = { + { // NTSC + 4,5,6,7,9,12,15,19,23,29,37,46,58,72,91,114,142,178,222,278,348,435,544,681,851,1064,1331,1664,2081,2602,3253,4068 + }, + { // PAL + 3,4,6,7,9,12,15,18,23,29,36,45,56,70,88,110,137,171,213,266,332,414,517,644,804,1003,1251,1560,1946,2428,3028,3778 + } }; + + const unsigned int I5E01_DMC::freq_table[2][16] = { + { // NTSC + 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 + }, + { // PAL + 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 + } }; + + const unsigned int BITREVERSE[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, + }; + + I5E01_DMC::I5E01_DMC() : GETA_BITS(20) + { + SetClock(DEFAULT_CLOCK); + SetRate(DEFAULT_RATE); + SetPal(false); + option[OPT_ENABLE_4011] = 1; + option[OPT_ENABLE_PNOISE] = 1; + option[OPT_UNMUTE_ON_RESET] = 1; + option[OPT_DPCM_ANTI_CLICK] = 0; + option[OPT_NONLINEAR_MIXER] = 1; + option[OPT_RANDOMIZE_NOISE] = 1; + option[OPT_RANDOMIZE_TRI] = 1; + option[OPT_TRI_MUTE] = 1; + option[OPT_DPCM_REVERSE] = 0; + tnd_table[0][0][0][0] = 0; + tnd_table[1][0][0][0] = 0; + + apu = NULL; + frame_sequence_count = 0; + frame_sequence_length = 7458; + frame_sequence_steps = 4; + + for (int c = 0;c < 2;++c) + for (int t = 0;t < 3;++t) + sm[c][t] = 128; + } + + + I5E01_DMC::~I5E01_DMC() + { + } + + void I5E01_DMC::SetStereoMix(int trk, short mixl, short mixr) + { + if (trk < 0) return; + if (trk > 2) return; + sm[0][trk] = mixl; + sm[1][trk] = mixr; + } + + void I5E01_DMC::FrameSequence(int s) + { + //DEBUG_OUT("FrameSequence: %d\n",s); + + if (s > 3) return; // no operation in step 4 + + if (apu) + { + apu->FrameSequence(s); + } + + if (s == 0 && (frame_sequence_steps == 4)) + { + if (frame_irq_enable) frame_irq = true; + } + + // 240hz clock + { + // triangle linear counter + if (linear_counter_halt) + { + linear_counter = linear_counter_reload; + } + else + { + if (linear_counter > 0) --linear_counter; + } + if (!linear_counter_control) + { + linear_counter_halt = false; + } + + // noise envelope + bool divider = false; + if (envelope_write) + { + envelope_write = false; + envelope_counter = 15; + envelope_div = 0; + } + else + { + ++envelope_div; + if (envelope_div > envelope_div_period) + { + divider = true; + envelope_div = 0; + } + } + if (divider) + { + if (envelope_loop && envelope_counter == 0) + envelope_counter = 15; + else if (envelope_counter > 0) + --envelope_counter; + } + } + + // 120hz clock + if ((s & 1) == 0) + { + // triangle length counter + if (!linear_counter_control && (length_counter[0] > 0)) + --length_counter[0]; + + // noise length counter + if (!envelope_loop && (length_counter[1] > 0)) + --length_counter[1]; + } + + } + + // 三角波チャンネルの計算 戻り値は0-15 + unsigned int I5E01_DMC::calc_tri(unsigned int clocks) + { + static unsigned int wavtbl[4][32] = + { + { + 15,14,13,12,11,10, 9, 8, + 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13,14,15, + }, + { + 11,11,11,11,10,10,10,10, + 9, 9, 9, 9, 8, 8, 8, 8, + 7, 7, 7, 7, 6, 6, 6, 6, + 5, 5, 5, 5, 4, 4, 4, 4, + }, + { + 11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + }, + { + 8, 9,10,12,13,14,14,15, + 15,15,14,14,13,12,10, 9, + 8, 6, 5, 3, 2, 1, 1, 0, + 0, 0, 1, 1, 2, 3, 5, 6, + }, + }; + + if (linear_counter > 0 && length_counter[0] > 0 + && (!option[OPT_TRI_MUTE] || tri_freq > 0)) + { + counter[0] -= clocks; + while (counter[0] < 0) + { + tphase = (tphase + 1) & 31; + counter[0] += (tri_freq + 1); + } + } + + unsigned int ret = wavtbl[tduty][tphase]; + return ret; + } + + // ノイズチャンネルの計算 戻り値は0-127 + // 低サンプリングレートで合成するとエイリアスノイズが激しいので + // ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート + // 変換を行っている。 + unsigned int I5E01_DMC::calc_noise(unsigned int clocks) + { + unsigned int env = envelope_disable ? noise_volume : envelope_counter; + if (length_counter[1] < 1) env = 0; + + unsigned int last = (noise & 0x4000) ? 0 : env; + if (clocks < 1) return last; + + // simple anti-aliasing (noise requires it, even when oversampling is off) + unsigned int count = 0; + unsigned int accum = counter[1] * last; // samples pending from previous calc + unsigned int accum_clocks = counter[1]; +#ifdef _DEBUG + int start_clocks = counter[1]; +#endif + if (counter[1] < 0) // only happens on startup when using the randomize noise option + { + accum = 0; + accum_clocks = 0; + } + + counter[1] -= clocks; + assert(nfreq > 0); // prevent infinite loop + while (counter[1] < 0) + { + // tick the noise generator + unsigned int feedback = (noise & 1) ^ ((noise & noise_tap) ? 1 : 0); + noise = (noise >> 1) | (feedback << 14); + + last = (noise & 0x4000) ? 0 : env; + accum += (last * nfreq); + counter[1] += nfreq; + ++count; + accum_clocks += nfreq; + } + + if (count < 1) // no change over interval, don't anti-alias + { + return last; + } + + accum -= (last * counter[1]); // remove these samples which belong in the next calc + accum_clocks -= counter[1]; +#ifdef _DEBUG + if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal +#endif + + unsigned int average = accum / accum_clocks; + assert(average <= 15); // above this would indicate overflow + return average; + } + + // Tick the DMC for the number of clocks, and return output counter; + unsigned int I5E01_DMC::calc_dmc(unsigned int clocks) + { + counter[2] -= clocks; + assert(dfreq > 0); // prevent infinite loop + while (counter[2] < 0) + { + counter[2] += dfreq; + + if (data > 0x100) // data = 0x100 when shift register is empty + { + if (!empty) + { + if ((data & 1) && (damp < 63)) + damp++; + else if (!(data & 1) && (0 < damp)) + damp--; + } + data >>= 1; + } + + if (data <= 0x100) // shift register is empty + { + if (dlength > 0) + { + memory(daddress, data); + // (checking for the 3-cycle case would require sub-instruction emulation) + data &= 0xFF; // read 8 bits + if (option[OPT_DPCM_REVERSE]) data = BITREVERSE[data]; + data |= 0x10000; // use an extra bit to signal end of data + empty = false; + daddress = ((daddress + 1) & 0xFFFF) | 0x8000; + --dlength; + if (dlength == 0) + { + if (mode & 1) // looped DPCM = auto-reload + { + daddress = ((adr_reg << 6) | 0xC000); + dlength = (len_reg << 4) + 1; + } + else if (mode & 2) // IRQ and not looped + { + irq = true; + } + } + } + else + { + data = 0x10000; // DMC will do nothing + empty = true; + } + } + } + + return (damp << 1) + dac_lsb; + } + + void I5E01_DMC::TickFrameSequence(unsigned int clocks) + { + frame_sequence_count += clocks; + while (frame_sequence_count > frame_sequence_length) + { + FrameSequence(frame_sequence_step); + frame_sequence_count -= frame_sequence_length; + ++frame_sequence_step; + if (frame_sequence_step >= frame_sequence_steps) + frame_sequence_step = 0; + } + } + + void I5E01_DMC::Tick(unsigned int clocks) + { + out[0] = calc_tri(clocks); + out[1] = calc_noise(clocks); + out[2] = calc_dmc(clocks); + } + + unsigned int I5E01_DMC::Render(int b[2]) + { + out[0] = (mask & 1) ? 0 : out[0]; + out[1] = (mask & 2) ? 0 : out[1]; + out[2] = (mask & 4) ? 0 : out[2]; + + int m[3]; + m[0] = tnd_table[0][out[0]][0][0]; + m[1] = tnd_table[0][0][out[1]][0]; + m[2] = tnd_table[0][0][0][out[2]]; + + if (option[OPT_NONLINEAR_MIXER]) + { + int ref = m[0] + m[1] + m[2]; + int voltage = tnd_table[1][out[0]][out[1]][out[2]]; + if (ref) + { + for (int i = 0; i < 3; ++i) + m[i] = (m[i] * voltage) / ref; + } + else + { + for (int i = 0; i < 3; ++i) + m[i] = voltage; + } + } + + // anti-click nullifies any 4011 write but preserves nonlinearity + if (option[OPT_DPCM_ANTI_CLICK]) + { + if (dmc_pop) // $4011 will cause pop this frame + { + // adjust offset to counteract pop + dmc_pop_offset += dmc_pop_follow - m[2]; + dmc_pop = false; + + // prevent overflow, keep headspace at edges + const int OFFSET_MAX = (1 << 30) - (4 << 16); + if (dmc_pop_offset > OFFSET_MAX) dmc_pop_offset = OFFSET_MAX; + if (dmc_pop_offset < -OFFSET_MAX) dmc_pop_offset = -OFFSET_MAX; + } + dmc_pop_follow = m[2]; // remember previous position + + m[2] += dmc_pop_offset; // apply offset + + // TODO implement this in a better way + // roll off offset (not ideal, but prevents overflow) + if (dmc_pop_offset > 0) --dmc_pop_offset; + else if (dmc_pop_offset < 0) ++dmc_pop_offset; + } + + b[0] = m[0] * sm[0][0]; + b[0] += m[1] * sm[0][1]; + b[0] += m[2] * sm[0][2]; + b[0] >>= 7; + + b[1] = m[0] * sm[1][0]; + b[1] += m[1] * sm[1][1]; + b[1] += m[2] * sm[1][2]; + b[1] >>= 7; + + return 2; + } + + void I5E01_DMC::SetClock(double c) + { + clock = c; + } + + void I5E01_DMC::SetRate(double r) + { + rate = (unsigned int)(r ? r : DEFAULT_RATE); + } + + void I5E01_DMC::SetPal(bool is_pal) + { + pal = (is_pal ? 1 : 0); + // set CPU cycles in frame_sequence + frame_sequence_length = is_pal ? 8314 : 7458; + } + + void I5E01_DMC::SetAPU(I5E01_APU* apu_) + { + apu = apu_; + } + + // Initializing TRI, NOISE, DPCM mixing table + void I5E01_DMC::InitializeTNDTable(double wt, double wn, double wd) { + + // volume adjusted by 0.95 based on empirical measurements + const double MASTER = 8192.0 * 0.95; + // truthfully, the nonlinear curve does not appear to match well + // with my tests. Do more testing of the APU/DMC DAC later. + // this value keeps the triangle consistent with measured levels, + // but not necessarily the rest of this APU channel, + // because of the lack of a good DAC model, currently. + + { // Linear Mixer + for (int t = 0; t < 16; t++) { + for (int n = 0; n < 16; n++) { + for (int d = 0; d < 128; d++) { + tnd_table[0][t][n][d] = (unsigned int)(MASTER * (3.0 * t + 2.0 * n + d) / 208.0); + } + } + } + } + { // Non-Linear Mixer + tnd_table[1][0][0][0] = 0; + for (int t = 0; t < 16; t++) { + for (int n = 0; n < 16; n++) { + for (int d = 0; d < 128; d++) { + if (t != 0 || n != 0 || d != 0) + tnd_table[1][t][n][d] = (unsigned int)((MASTER * 159.79) / (100.0 + 1.0 / ((double)t / wt + (double)n / wn + (double)d / wd))); + } + } + } + } + + } + + void I5E01_DMC::Reset() + { + int i; + mask = 0; + + InitializeTNDTable(8227, 12241, 22638); + + counter[0] = 0; + counter[1] = 0; + counter[2] = 0; + tphase = 0; + tduty = 0; + nfreq = wavlen_table[0][0]; + dfreq = freq_table[0][0]; + tri_freq = 0; + linear_counter = 0; + linear_counter_reload = 0; + linear_counter_halt = 0; + linear_counter_control = 0; + noise_volume = 0; + noise = 0; + noise_tap = 0; + envelope_loop = 0; + envelope_disable = 0; + envelope_write = 0; + envelope_div_period = 0; + envelope_div = 0; + envelope_counter = 0; + enable[0] = 0; + enable[1] = 0; + length_counter[0] = 0; + length_counter[1] = 0; + frame_irq = false; + frame_irq_enable = false; + frame_sequence_count = 0; + frame_sequence_steps = 4; + frame_sequence_step = 0; + + for (i = 0; i < 0x0F; i++) + Write(0x4108 + i, 0); + Write(0x4117, 0x40); + + irq = false; + Write(0x4115, 0x00); + if (option[OPT_UNMUTE_ON_RESET]) + Write(0x4115, 0x0f); + + out[0] = out[1] = out[2] = 0; + damp = 0; + dmc_pop = false; + dmc_pop_offset = 0; + dmc_pop_follow = 0; + dac_lsb = 0; + data = 0x100; + empty = true; + adr_reg = 0; + dlength = 0; + len_reg = 0; + daddress = 0; + noise = 1; + noise_tap = (1 << 1); + + SetRate(rate); + } + + void I5E01_DMC::SetMemory(std::function r) + { + memory = r; + } + + void I5E01_DMC::SetOption(int id, int val) + { + if (id < OPT_END) + { + option[id] = val; + if (id == OPT_NONLINEAR_MIXER) + InitializeTNDTable(8227, 12241, 22638); + } + } + + bool I5E01_DMC::Write(unsigned int adr, unsigned int val, unsigned int id) + { + static const unsigned char length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + // hack + adr+=0x100; + + if (adr == 0x4115) + { + enable[0] = (val & 4) ? true : false; + enable[1] = (val & 8) ? true : false; + + if (!enable[0]) + { + length_counter[0] = 0; + } + if (!enable[1]) + { + length_counter[1] = 0; + } + + if ((val & 16) && dlength == 0) + { + daddress = (0xC000 | (adr_reg << 6)); + dlength = (len_reg << 4) + 1; + } + else if (!(val & 16)) + { + dlength = 0; + } + + irq = false; + + reg[adr - 0x4108] = val; + return true; + } + + if (adr == 0x4117) + { + //DEBUG_OUT("4017 = %02X\n", val); + frame_irq_enable = ((val & 0x40) != 0x40); + if (frame_irq_enable) frame_irq = false; + + frame_sequence_count = 0; + if (val & 0x80) + { + frame_sequence_steps = 5; + frame_sequence_step = 0; + FrameSequence(frame_sequence_step); + ++frame_sequence_step; + } + else + { + frame_sequence_steps = 4; + frame_sequence_step = 1; + } + } + + if (adr < 0x4108 || 0x4113 < adr) + return false; + + reg[adr - 0x4108] = val & 0xff; + + //DEBUG_OUT("$%04X %02X\n", adr, val); + + switch (adr) + { + + // tri + + case 0x4108: + linear_counter_control = (val >> 7) & 1; + linear_counter_reload = val & 0x7F; + break; + + case 0x4109: + tduty = val & 3; + break; + + case 0x410a: + tri_freq = val | (tri_freq & 0x700); + break; + + case 0x410b: + tri_freq = (tri_freq & 0xff) | ((val & 0x7) << 8); + linear_counter_halt = true; + if (enable[0]) + { + length_counter[0] = length_table[(val >> 3) & 0x1f]; + } + break; + + // noise + + case 0x410c: + noise_volume = val & 15; + envelope_div_period = val & 15; + envelope_disable = (val >> 4) & 1; + envelope_loop = (val >> 5) & 1; + break; + + case 0x410d: + break; + + case 0x410e: + if (option[OPT_ENABLE_PNOISE]) + noise_tap = (val & 0x80) ? (1 << 6) : (1 << 1); + else + noise_tap = (1 << 1); + nfreq = wavlen_table[pal][val & 31]; + break; + + case 0x410f: + if (enable[1]) + { + length_counter[1] = length_table[(val >> 3) & 0xf]; + } + envelope_write = true; + break; + + // dmc + + case 0x4110: + mode = (val >> 6) & 3; + if (!(mode & 2)) + { + irq = false; + } + dfreq = freq_table[pal][val & 15]; + break; + + case 0x4111: + if (option[OPT_ENABLE_4011]) + { + damp = (val >> 1) & 0x3f; + dac_lsb = val & 1; + dmc_pop = true; + } + break; + + case 0x4112: + adr_reg = val & 0xff; + // ここでdaddressは更新されない + break; + + case 0x4113: + len_reg = val & 0xff; + // ここでlengthは更新されない + break; + + default: + return false; + } + + return true; + } + + bool I5E01_DMC::Read(unsigned int adr, unsigned int& val, unsigned int id) + { + if (adr == 0x4115) + { + val |= (irq ? 0x80 : 0) + | (frame_irq ? 0x40 : 0) + | ((dlength > 0) ? 0x10 : 0) + | (length_counter[1] ? 0x08 : 0) + | (length_counter[0] ? 0x04 : 0) + ; + + frame_irq = false; + return true; + } + else if (0x4108 <= adr && adr <= 0x4114) + { + val |= reg[adr - 0x4108]; + return true; + } + else + return false; + } +} // namespace diff --git a/src/engine/platform/sound/nes_nsfplay/5e01_dmc.h b/src/engine/platform/sound/nes_nsfplay/5e01_dmc.h new file mode 100644 index 000000000..2fb032900 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/5e01_dmc.h @@ -0,0 +1,127 @@ +#ifndef _I5E01_DMC_H_ +#define _I5E01_DMC_H_ + +#include + +namespace xgm +{ + class I5E01_APU; // forward declaration + + /** Bottom Half of APU **/ + class I5E01_DMC + { + public: + enum + { + OPT_ENABLE_4011 = 0, + OPT_ENABLE_PNOISE, + OPT_UNMUTE_ON_RESET, + OPT_DPCM_ANTI_CLICK, + OPT_NONLINEAR_MIXER, + OPT_RANDOMIZE_NOISE, + OPT_TRI_MUTE, + OPT_RANDOMIZE_TRI, + OPT_DPCM_REVERSE, + OPT_END + }; + protected: + const int GETA_BITS; + + // DPCM. + static const unsigned int freq_table[2][16]; + + // Noise. + static const unsigned int wavlen_table[2][32]; + + unsigned int tnd_table[2][16][16][128]; + + int option[OPT_END]; + int mask; + int sm[2][3]; + unsigned char reg[0x10]; + unsigned int len_reg; + unsigned int adr_reg; + std::function memory; + unsigned int daddress; + unsigned int dlength; + unsigned int data; + bool empty; + short damp; + int dac_lsb; + bool dmc_pop; + int dmc_pop_offset; + int dmc_pop_follow; + double clock; + unsigned int rate; + int pal; + int mode; + bool irq; + + int counter[3]; // frequency dividers + int tphase; // triangle phase + int tduty; // wave shape + unsigned int nfreq; // noise frequency + unsigned int dfreq; // DPCM frequency + + unsigned int tri_freq; + int linear_counter; + int linear_counter_reload; + bool linear_counter_halt; + bool linear_counter_control; + + int noise_volume; + unsigned int noise, noise_tap; + + // noise envelope + bool envelope_loop; + bool envelope_disable; + bool envelope_write; + int envelope_div_period; + int envelope_div; + int envelope_counter; + + bool enable[2]; // tri/noise enable + int length_counter[2]; // 0=tri, 1=noise + + + // frame sequencer + I5E01_APU* apu; // apu is clocked by DMC's frame sequencer + int frame_sequence_count; // current cycle count + int frame_sequence_length; // CPU cycles per FrameSequence + int frame_sequence_step; // current step of frame sequence + int frame_sequence_steps; // 4/5 steps per frame + bool frame_irq; + bool frame_irq_enable; + + inline unsigned int calc_tri(unsigned int clocks); + inline unsigned int calc_dmc(unsigned int clocks); + inline unsigned int calc_noise(unsigned int clocks); + + public: + unsigned int out[3]; + I5E01_DMC(); + ~I5E01_DMC(); + + void InitializeTNDTable(double wt, double wn, double wd); + void SetPal(bool is_pal); + void SetAPU(I5E01_APU* apu_); + void SetMemory(std::function r); + void FrameSequence(int s); + int GetDamp() { return (damp << 1) | dac_lsb; } + void TickFrameSequence(unsigned int clocks); + + void Reset(); + void Tick(unsigned int clocks); + unsigned int Render(int b[2]); + bool Write(unsigned int adr, unsigned int val, unsigned int id = 0); + bool Read(unsigned int adr, unsigned int& val, unsigned int id = 0); + void SetRate(double rate); + void SetClock(double rate); + void SetOption(int, int); + void SetMask(int m) { mask = m; } + void SetStereoMix(int trk, short mixl, short mixr); + }; + +} + +#endif diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 8a80ee7c2..9425f8787 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -657,11 +657,19 @@ bool DivPlatformSoundUnit::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformSoundUnit::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformSoundUnit::renderSamples(int sysID) { memset(sampleMem,0,sampleMemSize?65536:8192); memset(sampleOffSU,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample RAM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -684,12 +692,20 @@ void DivPlatformSoundUnit::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleOffSU[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } sampleMemLen=memPos; sysIDCache=sysID; memcpy(su->pcm,sampleMem,sampleMemSize?65536:8192); + + memCompo.used=sampleMemLen; + memCompo.capacity=sampleMemSize?65536:8192; + + if (initIlSize&64) { + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_ECHO,"Echo Buffer",-1,memCompo.capacity-((1+(initIlSize&63))<<7),memCompo.capacity)); + } } int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index f39a59cc7..0456f42f7 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -99,6 +99,7 @@ class DivPlatformSoundUnit: public DivDispatch { unsigned char* sampleMem; size_t sampleMemLen; unsigned char regPool[128]; + DivMemoryComposition memCompo; double NOTE_SU(int ch, int note); void writeControl(int ch); void writeControlUpper(int ch); @@ -129,6 +130,7 @@ class DivPlatformSoundUnit: public DivDispatch { size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 9ac523f6f..104203420 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -995,11 +995,19 @@ bool DivPlatformX1_010::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformX1_010::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformX1_010::renderSamples(int sysID) { memset(sampleMem,0,16777216); memset(sampleOffX1,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -1030,9 +1038,13 @@ void DivPlatformX1_010::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleOffX1[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } sampleMemLen=memPos+256; + + memCompo.used=sampleMemLen; + memCompo.capacity=getSampleMemCapacity(0); } int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index f4a815455..413aba3e8 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -119,6 +119,8 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf { unsigned int sampleOffX1[256]; bool sampleLoaded[256]; + DivMemoryComposition memCompo; + unsigned char regPool[0x2000]; double NoteX1_010(int ch, int note); void updateWave(int ch); @@ -151,6 +153,7 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 0a6168cd2..378ce4a43 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -1605,11 +1605,19 @@ bool DivPlatformYM2608::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformYM2608::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformYM2608::renderSamples(int sysID) { memset(adpcmBMem,0,getSampleMemCapacity(0)); memset(sampleOffB,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="ADPCM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -1634,9 +1642,13 @@ void DivPlatformYM2608::renderSamples(int sysID) { sampleLoaded[i]=true; } sampleOffB[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } adpcmBMemLen=memPos+256; + + memCompo.used=adpcmBMemLen; + memCompo.capacity=getSampleMemCapacity(0); } void DivPlatformYM2608::setFlags(const DivConfig& flags) { diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 2b51da1d6..2e95ebcae 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -64,6 +64,8 @@ class DivPlatformYM2608: public DivPlatformOPN { bool extMode, noExtMacros; unsigned char prescale, nukedMult; + + DivMemoryComposition memCompo; double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); @@ -100,6 +102,7 @@ class DivPlatformYM2608: public DivPlatformOPN { size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); void setFlags(const DivConfig& flags); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 0c96e5447..63dd3ecc6 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -73,6 +73,9 @@ class DivPlatformYM2610Base: public DivPlatformOPN { unsigned char writeADPCMAOff, writeADPCMAOn; int globalADPCMAVolume; + DivMemoryComposition memCompoA; + DivMemoryComposition memCompoB; + double NOTE_OPNB(int ch, int note) { if (ch>=adpcmBChanOffs) { // ADPCM return NOTE_ADPCMB(note); @@ -144,6 +147,12 @@ class DivPlatformYM2610Base: public DivPlatformOPN { if (sample<0 || sample>255) return false; return sampleLoaded[index][sample]; } + + const DivMemoryComposition* getMemCompo(int index) { + if (index==0) return &memCompoA; + if (index==1) return &memCompoB; + return NULL; + } void renderSamples(int sysID) { memset(adpcmAMem,0,getSampleMemCapacity(0)); @@ -151,6 +160,12 @@ class DivPlatformYM2610Base: public DivPlatformOPN { memset(sampleOffB,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*2*sizeof(bool)); + memCompoA=DivMemoryComposition(); + memCompoA.name="ADPCM-A"; + + memCompoB=DivMemoryComposition(); + memCompoB.name="ADPCM-B"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -175,10 +190,14 @@ class DivPlatformYM2610Base: public DivPlatformOPN { sampleLoaded[0][i]=true; } sampleOffA[i]=memPos; + memCompoA.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } adpcmAMemLen=memPos+256; + memCompoA.used=adpcmAMemLen; + memCompoA.capacity=getSampleMemCapacity(0); + memset(adpcmBMem,0,getSampleMemCapacity(1)); memPos=0; @@ -205,9 +224,13 @@ class DivPlatformYM2610Base: public DivPlatformOPN { sampleLoaded[1][i]=true; } sampleOffB[i]=memPos; + memCompoB.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); memPos+=paddedLen; } adpcmBMemLen=memPos+256; + + memCompoB.used=adpcmBMemLen; + memCompoB.capacity=getSampleMemCapacity(1); } void setFlags(const DivConfig& flags) { diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index 878ed0f0d..b95e8a9d3 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -442,11 +442,19 @@ bool DivPlatformYMZ280B::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformYMZ280B::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + void DivPlatformYMZ280B::renderSamples(int sysID) { memset(sampleMem,0,getSampleMemCapacity()); memset(sampleOff,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); + memCompo=DivMemoryComposition(); + memCompo.name="Sample ROM"; + size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; @@ -471,6 +479,7 @@ void DivPlatformYMZ280B::renderSamples(int sysID) { } #endif sampleOff[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+length)); memPos+=length; } if (actualLength0) speeds.val[0]=effectVal; } break; + case 0xfd: // virtual tempo num + if (effectVal>0) virtualTempoN=effectVal; + break; + case 0xfe: // virtual tempo den + if (effectVal>0) virtualTempoD=effectVal; + break; case 0x0b: // change order if (changeOrd==-1 || song.jumpTreatment==0) { changeOrd=effectVal; @@ -950,6 +958,47 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch)); break; + case 0xe6: // Delayed legato + // why does this have to follow FamiTracker verbatim + // couldn't you do better? + if ((effectVal&15)!=0) { + chan[i].legatoDelay=(((effectVal&0xf0)>>4)&7)+1; + if (effectVal&128) { + chan[i].legatoTarget=-(effectVal&15); + } else { + chan[i].legatoTarget=(effectVal&15); + } + } else { + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + break; + case 0xe7: // delayed macro release + // "Bruh" + if (effectVal>0 && (song.delayBehavior==2 || effectVal>4)+1; + chan[i].legatoTarget=(effectVal&15); + } else { + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + break; + case 0xe9: // delayed legato down + if ((effectVal&15)!=0) { + chan[i].legatoDelay=((effectVal&0xf0)>>4)+1; + chan[i].legatoTarget=-(effectVal&15); + } else { + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + break; case 0xea: // legato mode chan[i].legato=effectVal; break; @@ -959,6 +1008,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xec: // delayed note cut if (effectVal>0 && (song.delayBehavior==2 || effectValvirtualTempoNvirtualTempoD)?curSubSong->virtualTempoD:curSubSong->virtualTempoN; - while (tempoAccum>=curSubSong->virtualTempoD) { - tempoAccum-=curSubSong->virtualTempoD; + tempoAccum+=(skipping && virtualTempoN=virtualTempoD) { + tempoAccum-=virtualTempoD; if (--ticks<=0) { ret=endOfSong; if (shallStopSched) { @@ -1515,6 +1575,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { break; } } + if (chan[i].legatoDelay>0) { + if (--chan[i].legatoDelay<1) { + chan[i].note+=chan[i].legatoTarget; + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + } if (!song.noSlidesOnFirstTick || !firstTick) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { @@ -1530,30 +1599,36 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (chan[i].cut>0) { if (--chan[i].cut<1) { - chan[i].oldNote=chan[i].note; - //chan[i].note=-1; - if (chan[i].inPorta && song.noteOffResetsSlides) { - chan[i].keyOff=true; - chan[i].keyOn=false; - if (chan[i].stopOnOff) { - chan[i].portaNote=-1; - chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); - chan[i].stopOnOff=false; + if (chan[i].cutType==2) { + dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i)); + chan[i].releasing=true; + } else { + chan[i].oldNote=chan[i].note; + //chan[i].note=-1; + if (chan[i].inPorta && song.noteOffResetsSlides) { + chan[i].keyOff=true; + chan[i].keyOn=false; + if (chan[i].stopOnOff) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); + chan[i].stopOnOff=false; + } + if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); + } + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + chan[i].scheduledSlideReset=true; } - if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { - chan[i].portaNote=-1; - chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); - /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { - chan[i+1].portaNote=-1; - chan[i+1].portaSpeed=-1; - }*/ + if (chan[i].cutType==1) { + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i)); + } else { + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); } - dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); - chan[i].scheduledSlideReset=true; + chan[i].releasing=true; } - dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); } } if (chan[i].resetArp) { @@ -1595,9 +1670,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (subticks==tickMult && cmdStreamInt) { if (!cmdStreamInt->tick()) { - cmdStreamInt->cleanup(); - delete cmdStreamInt; - cmdStreamInt=NULL; + // !!! } } @@ -1671,7 +1744,7 @@ void DivEngine::runMidiClock(int totalCycles) { if (hl<=0.0) hl=4.0; double timeBase=curSubSong->timeBase+1; double speedSum=0; - double vD=curSubSong->virtualTempoD; + double vD=virtualTempoD; for (int i=0; ivirtualTempoN/vD; + double bpm=((24.0*divider)/(timeBase*hl*speedSum))*(double)virtualTempoN/vD; if (bpm<1.0) bpm=1.0; int increment=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 0e447b2dc..c2388c460 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -227,3 +227,9 @@ void SafeWriter::finish() { buf=NULL; operative=false; } + +void SafeWriter::disown() { + if (!operative) return; + buf=NULL; + operative=false; +} diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 640e78c47..666b0f1e2 100644 --- a/src/engine/safeWriter.h +++ b/src/engine/safeWriter.h @@ -62,6 +62,7 @@ class SafeWriter { void init(); SafeReader* toReader(); void finish(); + void disown(); SafeWriter(): operative(false), diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index bb8a21d3f..17340f551 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -35,6 +35,7 @@ extern "C" { #include "../../extern/adpcm/ymb_codec.h" #include "../../extern/adpcm/ymz_codec.h" } +#include "../../extern/adpcm-xq/adpcm-lib.h" #include "brrUtils.h" DivSampleHistory::~DivSampleHistory() { @@ -279,6 +280,9 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { case DIV_SAMPLE_DEPTH_C219: off=offset; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + off=(offset+1)/2; + break; case DIV_SAMPLE_DEPTH_16BIT: off=offset*2; break; @@ -338,6 +342,10 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { off=offset; len=length; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + off=(offset+1)/2; + len=(length+1)/2; + break; case DIV_SAMPLE_DEPTH_16BIT: off=offset*2; len=length*2; @@ -396,6 +404,9 @@ int DivSample::getEndPosition(DivSampleDepth depth) { case DIV_SAMPLE_DEPTH_C219: off=lengthC219; break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + off=lengthIMA; + break; case DIV_SAMPLE_DEPTH_16BIT: off=length16; break; @@ -587,6 +598,12 @@ bool DivSample::initInternal(DivSampleDepth d, int count) { dataC219=new unsigned char[(count+4095)&(~0xfff)]; memset(dataC219,0,(count+4095)&(~0xfff)); break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM + if (dataIMA!=NULL) delete[] dataIMA; + lengthIMA=4+((count+1)/2); + dataIMA=new unsigned char[lengthIMA]; + memset(dataIMA,0,lengthIMA); + break; case DIV_SAMPLE_DEPTH_16BIT: // 16-bit if (data16!=NULL) delete[] data16; length16=count*2; @@ -1271,6 +1288,9 @@ void DivSample::render(unsigned int formatMask) { if (dataC219[i]&0x80) data16[i]=-data16[i]; } break; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM + if (adpcm_decode_block(data16,dataIMA,lengthIMA,1)==0) logE("oh crap!"); + break; default: return; } @@ -1442,6 +1462,23 @@ void DivSample::render(unsigned int formatMask) { dataC219[i]=x|(negate?0x80:0); } } + if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_IMA_ADPCM)) { // IMA ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_IMA_ADPCM,samples)) return; + int delta[2]; + delta[0]=0; + delta[1]=0; + + void* codec=adpcm_create_context(1,4,NOISE_SHAPING_OFF,delta); + if (codec==NULL) { + logE("oh no IMA encoder could not be created!"); + } else { + size_t whyPointer=0; + adpcm_encode_block(codec,dataIMA,&whyPointer,data16,samples); + if (whyPointer!=lengthIMA) logW("IMA length mismatch! %d -> %d!=%d",(int)samples,(int)whyPointer,(int)lengthIMA); + + adpcm_free_context(codec); + } + } } void* DivSample::getCurBuf() { @@ -1470,6 +1507,8 @@ void* DivSample::getCurBuf() { return dataMuLaw; case DIV_SAMPLE_DEPTH_C219: return dataC219; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + return dataIMA; case DIV_SAMPLE_DEPTH_16BIT: return data16; default: @@ -1504,6 +1543,8 @@ unsigned int DivSample::getCurBufLen() { return lengthMuLaw; case DIV_SAMPLE_DEPTH_C219: return lengthC219; + case DIV_SAMPLE_DEPTH_IMA_ADPCM: + return lengthIMA; case DIV_SAMPLE_DEPTH_16BIT: return length16; default: @@ -1616,4 +1657,5 @@ DivSample::~DivSample() { if (dataVOX) delete[] dataVOX; if (dataMuLaw) delete[] dataMuLaw; if (dataC219) delete[] dataC219; + if (dataIMA) delete[] dataIMA; } diff --git a/src/engine/sample.h b/src/engine/sample.h index 63d219259..cd4175e9d 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -46,6 +46,7 @@ enum DivSampleDepth: unsigned char { DIV_SAMPLE_DEPTH_VOX=10, DIV_SAMPLE_DEPTH_MULAW=11, DIV_SAMPLE_DEPTH_C219=12, + DIV_SAMPLE_DEPTH_IMA_ADPCM=13, DIV_SAMPLE_DEPTH_16BIT=16, DIV_SAMPLE_DEPTH_MAX // boundary for sample depth }; @@ -114,6 +115,7 @@ struct DivSample { // - 10: VOX ADPCM // - 11: 8-bit µ-law PCM // - 12: C219 "µ-law" PCM + // - 13: IMA ADPCM // - 16: 16-bit PCM DivSampleDepth depth; bool loop, brrEmphasis, dither; @@ -139,8 +141,9 @@ struct DivSample { unsigned char* dataVOX; // 10 unsigned char* dataMuLaw; // 11 unsigned char* dataC219; // 12 + unsigned char* dataIMA; // 13 - unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219; + unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA; unsigned int samples; @@ -349,6 +352,7 @@ struct DivSample { dataVOX(NULL), dataMuLaw(NULL), dataC219(NULL), + dataIMA(NULL), length8(0), length16(0), length1(0), @@ -362,6 +366,7 @@ struct DivSample { lengthVOX(0), lengthMuLaw(0), lengthC219(0), + lengthIMA(0), samples(0) { for (int i=0; i,effectVal}}, + {0x14, {DIV_CMD_NES_SWEEP, "14xy: Sweep down (x: time; y: shift)",constVal<1>,effectVal}}, + {0x15, {DIV_CMD_NES_ENV_MODE, "15xx: Set envelope mode (0: envelope, 1: length, 2: looping, 3: constant)"}}, + {0x16, {DIV_CMD_NES_LENGTH, "16xx: Set length counter (refer to manual for a list of values)"}}, + {0x17, {DIV_CMD_NES_COUNT_MODE, "17xx: Set frame counter mode (0: 4-step, 1: 5-step)"}}, + {0x18, {DIV_CMD_SAMPLE_MODE, "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"}}, + {0x19, {DIV_CMD_NES_LINEAR_LENGTH, "19xx: Set triangle linear counter (0 to 7F; 80 and higher halt)"}}, + {0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set DPCM frequency (0 to F)"}} + } + ); sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef( "Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0, 0, 0, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 2d85601f8..a8a1fab74 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -2424,7 +2424,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p while (!done) { if (loopPos==-1) { if (loopOrder==curOrder && loopRow==curRow) { - if ((ticks-((tempoAccum+curSubSong->virtualTempoN)/curSubSong->virtualTempoD))<=0) { + if ((ticks-((tempoAccum+virtualTempoN)/virtualTempoD))<=0) { writeLoop=true; } } diff --git a/src/gui/about.cpp b/src/gui/about.cpp index a20be2cc7..ce3c0b729 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -70,7 +70,6 @@ const char* aboutLine[]={ "Abstract 64", "Aburtos", "ActualNK358", - "airconmanws", "akumanatt", "AmigaX", "AquaDoesStuff", @@ -99,6 +98,7 @@ const char* aboutLine[]={ "dumbut", "Eknous", "Electric Keet", + "EntropyAuthor", "EpicTyphlosion", "FΛDE", "Forte", @@ -152,6 +152,7 @@ const char* aboutLine[]={ "RevvoBolt", "Rockyfan75000", "scooblee", + "sheffield^2", "sillygoose", "smaybius", "SnugglyBun", @@ -176,7 +177,6 @@ const char* aboutLine[]={ "tom_atom", "traumatized", "Tytanium654", - "Uhrwerk Klockwerx", "Ultraprogramer", "UserSniper", "Weeppiko", @@ -214,9 +214,10 @@ const char* aboutLine[]={ "FFTW by Matteo Frigo and Steven G. Johnson", "backward-cpp by Google", "adpcm by superctr", + "adpcm-xq by David Bryant", "Nuked-OPL3/OPLL/OPM/OPN2/PSG by nukeykt", "YM3812-LLE, YMF262-LLE and YMF276-LLE by nukeykt", - "ESFMu by Kagamiin~", + "ESFMu (modified version) by Kagamiin~", "ymfm by Aaron Giles", "MAME SN76496 by Nicola Salmoria", "MAME AY-3-8910 by Couriersud", @@ -253,6 +254,8 @@ const char* aboutLine[]={ "D65010G031 emulator (modified version) by cam900", "Namco C140/C219 emulator (modified version) by cam900", "PowerNoise emulator by scratchminer", + "ep128emu by Istvan Varga", + "NDS sound emulator by cam900", "", "greetings to:", "NEOART Costa Rica", diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index eccc63244..81e96a1cb 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -57,7 +57,7 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("delay arpeggio by one tick on every new note."); } - ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); + ImGui::Checkbox("Disable DAC when sample ends",&e->song.brokenDACMode); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); } diff --git a/src/gui/csPlayer.cpp b/src/gui/csPlayer.cpp new file mode 100644 index 000000000..bc43fe99f --- /dev/null +++ b/src/gui/csPlayer.cpp @@ -0,0 +1,371 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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" +#include +#include "imgui.h" +#include "guiConst.h" + +// TODO: memory safety +String disasmCmd(unsigned char* buf, size_t bufLen, unsigned int addr) { + if (addr>=bufLen) return "???"; + + if (buf[addr]<0xb4) { + return fmt::sprintf("note %s",noteNames[buf[addr]]); + } else switch (buf[addr]) { + case 0xb4: + return "note null"; + break; + case 0xb5: + return "off"; + break; + case 0xb6: + return "offrel"; + break; + case 0xb7: + return "mrel"; + break; + case 0xb8: + return fmt::sprintf("ins $%.2x",(int)buf[addr+1]); + break; + case 0xbe: + return fmt::sprintf("pan $%x, $%x",(int)buf[addr+1],(int)buf[addr+2]); + break; + case 0xc0: + return fmt::sprintf("preporta $%.2x",(int)buf[addr+1]); + break; + case 0xc2: + return fmt::sprintf("vib %d, %d",(int)buf[addr+1],(int)buf[addr+2]); + break; + case 0xc3: + return fmt::sprintf("vibrange %d",(int)buf[addr+1]); + break; + case 0xc4: + return fmt::sprintf("vibshape %d",(int)buf[addr+1]); + break; + case 0xc5: + return fmt::sprintf("pitch $%.2x",(int)buf[addr+1]); + break; + case 0xc6: + return fmt::sprintf("arp %d, %d",(int)buf[addr+1],(int)buf[addr+2]); + break; + case 0xc7: + return fmt::sprintf("vol $%.2x",(int)buf[addr+1]); + break; + case 0xc8: + return fmt::sprintf("volslide %d",(int)((short)(buf[addr+1]|(buf[addr+2]<<8)))); + break; + case 0xc9: + return fmt::sprintf("porta %d, %d",(int)buf[addr+1],(int)buf[addr+2]); + break; + case 0xca: + return fmt::sprintf("legato %d",(int)buf[addr+1]); + break; + case 0xe0: case 0xe1: case 0xe2: case 0xe3: + case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0xec: case 0xed: case 0xee: case 0xef: + return fmt::sprintf("qwait (%d)",(int)(buf[addr]-0xe0)); + break; + case 0xfc: + return fmt::sprintf("waits %d",(int)(buf[addr+1]|(buf[addr+2]<<8))); + break; + case 0xfd: + return fmt::sprintf("waitc %d",(int)buf[addr+1]); + break; + case 0xfe: + return "wait 1"; + break; + case 0xff: + return "stop"; + break; + default: + return "ill"; + break; + } + return "TODO"; +} + +void FurnaceGUI::drawCSPlayer() { + if (nextWindow==GUI_WINDOW_CS_PLAYER) { + csPlayerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!csPlayerOpen) return; + if (ImGui::Begin("Command Stream Player",&csPlayerOpen,globalWinFlags)) { + if (ImGui::Button("Load")) { + openFileDialog(GUI_FILE_CMDSTREAM_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Kill")) { + if (!e->killStream()) { + showError("Kikai wa mou shindeiru!"); + } + } + ImGui::SameLine(); + if (ImGui::Button("Burn Current Song")) { + SafeWriter* w=e->saveCommand(); + if (w!=NULL) { + if (!e->playStream(w->getFinalBuf(),w->size())) { + showError(e->getLastError()); + w->finish(); + delete w; + } else { + w->disown(); + delete w; + } + } + } + + DivCSPlayer* cs=e->getStreamPlayer(); + if (cs) { + if (ImGui::BeginTabBar("CSOptions")) { + int chans=e->getTotalChannelCount(); + if (ImGui::BeginTabItem("Status")) { + if (ImGui::BeginTable("CSStat",12,ImGuiTableFlags_SizingFixedSame|ImGuiTableFlags_ScrollX|ImGuiTableFlags_Borders)) { + ImGui::TableSetupScrollFreeze(1,1); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("channel"); + ImGui::TableNextColumn(); + ImGui::Text("start"); + ImGui::TableNextColumn(); + ImGui::Text("PC"); + ImGui::TableNextColumn(); + ImGui::Text("wait"); + ImGui::TableNextColumn(); + ImGui::Text("SP"); + ImGui::TableNextColumn(); + ImGui::Text("note"); + ImGui::TableNextColumn(); + ImGui::Text("pitch"); + ImGui::TableNextColumn(); + ImGui::Text("vol"); + ImGui::TableNextColumn(); + ImGui::Text("vols"); + ImGui::TableNextColumn(); + ImGui::Text("vib"); + ImGui::TableNextColumn(); + ImGui::Text("porta"); + ImGui::TableNextColumn(); + ImGui::Text("arp"); + + for (int i=0; igetChanState(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",i); + ImGui::TableNextColumn(); + ImGui::Text("$%.4x",state->startPos); + ImGui::TableNextColumn(); + ImGui::Text("$%.4x",state->readPos); + ImGui::TableNextColumn(); + ImGui::Text("%d/%d",state->waitTicks,state->lastWaitLen); + ImGui::TableNextColumn(); + ImGui::Text("%d",state->callStackPos); + ImGui::TableNextColumn(); + ImGui::Text("%d",state->note); + ImGui::TableNextColumn(); + ImGui::Text("%d",state->pitch); + ImGui::TableNextColumn(); + ImGui::Text("$%.4X",state->volume); + ImGui::TableNextColumn(); + ImGui::Text("%+d",state->volSpeed); + ImGui::TableNextColumn(); + ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos); + ImGui::TableNextColumn(); + ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed); + ImGui::TableNextColumn(); + ImGui::Text("$%.2X",state->arp); + } + + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Trace")) { + ImGui::PushFont(patFont); + if (ImGui::BeginTable("CSTrace",chans,ImGuiTableFlags_SizingFixedSame|ImGuiTableFlags_Borders|ImGuiTableFlags_ScrollX)) { + char tempID[32]; + for (int i=0; igetChanState(i); + ImGui::TableNextColumn(); + ImGui::Text("%d: $%.4x",i,state->readPos); + } + + ImGui::TableNextRow(); + unsigned char* buf=cs->getData(); + size_t bufSize=cs->getDataLen(); + for (int i=0; igetChanState(i); + ImGui::TableNextColumn(); + int maxItems=(ImGui::GetContentRegionAvail().y/MAX(ImGui::GetTextLineHeightWithSpacing(),1.0f)); + if (maxItems>=DIV_MAX_CSTRACE) maxItems=DIV_MAX_CSTRACE-1; + + int tracePos=state->tracePos; + + for (int j=(tracePos-maxItems)&(DIV_MAX_CSTRACE-1); j!=tracePos; j=(j+1)&(DIV_MAX_CSTRACE-1)) { + if (state->trace[j]==0) { + ImGui::TextUnformatted("..."); + } else { + String dis=disasmCmd(buf,bufSize,state->trace[j]); + ImGui::Text("%.4x: %s",state->trace[j],dis.c_str()); + } + } + } + + ImGui::EndTable(); + } + ImGui::PopFont(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Disassemble")) { + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Hex")) { + ImGui::PushFont(patFont); + if (ImGui::BeginTable("CSHexPos",chans,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + for (int i=0; igetChanState(i); + ImGui::TableNextColumn(); + ImGui::Text("$%.4x",state->readPos); + } + ImGui::EndTable(); + } + + float oneCharSize=ImGui::CalcTextSize("A").x; + float threeCharSize=ImGui::CalcTextSize("AA").x; + float charViewSize=ImGui::CalcTextSize("0123456789ABCDEF").x; + float fiveCharSize=ImGui::CalcTextSize("AAAAA").x; + + if (ImGui::BeginTable("CSHexView",19,ImGuiTableFlags_ScrollY)) { + char charView[17]; + ImGui::TableSetupScrollFreeze(1,1); + ImGui::TableSetupColumn("addr",ImGuiTableColumnFlags_WidthFixed,fiveCharSize); + ImGui::TableSetupColumn("d0",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d1",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d2",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d3",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d4",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d5",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d6",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d7",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d8",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d9",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d10",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d11",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d12",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d13",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d14",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d15",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("spacer",ImGuiTableColumnFlags_WidthFixed,oneCharSize); + ImGui::TableSetupColumn("char",ImGuiTableColumnFlags_WidthFixed,charViewSize); + + // header + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + for (int i=0; i<16; i++) { + ImGui::TableNextColumn(); + ImGui::Text("%X",i); + } + + // content + unsigned char* buf=cs->getData(); + size_t bufSize=cs->getDataLen(); + csClipper.Begin((bufSize+15)>>4,ImGui::GetTextLineHeightWithSpacing()); + while (csClipper.Step()) { + //std::vector highlightsUnsorted; + std::vector highlights; + int nextHighlight=-1; + int highlightPos=0; + + for (int i=0; igetChanState(i); + if ((int)state->readPos>=(csClipper.DisplayStart<<4) && (int)state->readPos<=(csClipper.DisplayEnd<<4)) { + highlights.push_back(state->readPos); + } + } + if (!highlights.empty()) nextHighlight=highlights[0]; + + + for (int i=csClipper.DisplayStart; i=(int)bufSize) continue; + if (pos==nextHighlight) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(ImGuiCol_HeaderActive)); + highlightPos++; + if (highlightPos>=(int)highlights.size()) { + nextHighlight=-1; + } else { + nextHighlight=highlights[highlightPos]; + } + } + ImGui::Text("%.2X",buf[pos]); + } + + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + for (int j=0; j<16; j++) { + int pos=(i<<4)|j; + if (pos>=(int)bufSize) { + charView[j]=' '; + } else if (buf[pos]>=0x20 && buf[pos]<=0x7e) { + charView[j]=buf[pos]; + } else { + charView[j]='.'; + } + } + charView[16]=0; + + ImGui::TextUnformatted(charView); + } + } + csClipper.End(); + + ImGui::EndTable(); + } + ImGui::PopFont(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CS_PLAYER; + ImGui::End(); +} diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 98a2d9dc7..ed5057915 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -77,7 +77,7 @@ void FurnaceGUI::drawDebug() { ImGui::SameLine(); if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); - if (ImGui::Button("Play Command Stream")) openFileDialog(GUI_FILE_CMDSTREAM_OPEN); + if (ImGui::Button("Play Command Stream")) nextWindow=GUI_WINDOW_CS_PLAYER; if (ImGui::Button("Panic")) e->syncReset(); ImGui::SameLine(); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index c706410b3..b86a50de3 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -316,6 +316,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_MEMORY: nextWindow=GUI_WINDOW_MEMORY; break; + case GUI_ACTION_WINDOW_CS_PLAYER: + nextWindow=GUI_WINDOW_CS_PLAYER; + break; case GUI_ACTION_COLLAPSE_WINDOW: collapseWindow=true; @@ -418,6 +421,9 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_MEMORY: memoryOpen=false; break; + case GUI_WINDOW_CS_PLAYER: + csPlayerOpen=false; + break; default: break; } @@ -1008,7 +1014,8 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_GA20 || i==DIV_INS_K053260 || i==DIV_INS_C140 || - i==DIV_INS_C219) { + i==DIV_INS_C219 || + i==DIV_INS_NDS) { makeInsTypeList.push_back(i); } } @@ -1534,7 +1541,10 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_GA20 || i==DIV_INS_K053260 || i==DIV_INS_C140 || - i==DIV_INS_C219) { + i==DIV_INS_C219 || + i==DIV_INS_NDS || + i==DIV_INS_GBA_DMA || + i==DIV_INS_GBA_MINMOD) { makeInsTypeList.push_back(i); } } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 3a2b031f6..f4f373817 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -587,6 +587,10 @@ void FurnaceGUI::drawMobileControls() { if (ImGui::Button("Meter")) { volMeterOpen=!volMeterOpen; } + ImGui::SameLine(); + if (ImGui::Button("Memory")) { + memoryOpen=!memoryOpen; + } ImGui::Separator(); diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 16dd7eeda..5bbf84a78 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -242,16 +242,38 @@ void FurnaceGUI::drawExportCommand(bool onWindow) { ); if (onWindow) { ImGui::Separator(); - if (ImGui::Button("Cancel",ImVec2(133.3f*dpiScale,0))) ImGui::CloseCurrentPopup(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); ImGui::SameLine(); } - if (ImGui::Button("Export (binary)",ImVec2(133.3f*dpiScale,0))) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); ImGui::CloseCurrentPopup(); } - ImGui::SameLine(); - if (ImGui::Button("Export (text)",ImVec2(133.3f*dpiScale,0))) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); +} + +void FurnaceGUI::drawExportDMF(bool onWindow) { + exitDisabledTimer=1; + + ImGui::Text( + "export in DefleMask module format.\n" + "only do it if you really, really need to, or are downgrading an existing .dmf." + ); + + ImGui::Text("format version:"); + ImGui::RadioButton("1.1.3 and higher",&dmfExportVersion,0); + ImGui::RadioButton("1.0/legacy (0.12)",&dmfExportVersion,1); + + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { + if (dmfExportVersion==1) { + openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); + } else { + openFileDialog(GUI_FILE_SAVE_DMF); + } ImGui::CloseCurrentPopup(); } } @@ -295,6 +317,10 @@ void FurnaceGUI::drawExport() { drawExportCommand(true); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("DMF")) { + drawExportDMF(true); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } } else switch (curExportType) { @@ -316,6 +342,9 @@ void FurnaceGUI::drawExport() { case GUI_EXPORT_CMD_STREAM: drawExportCommand(true); break; + case GUI_EXPORT_DMF: + drawExportDMF(true); + break; default: ImGui::Text("congratulations! you've unlocked a secret panel."); if (ImGui::Button("Toggle hidden systems")) { diff --git a/src/gui/fmPreview.cpp b/src/gui/fmPreview.cpp index 525d30339..4e11137b9 100644 --- a/src/gui/fmPreview.cpp +++ b/src/gui/fmPreview.cpp @@ -77,7 +77,13 @@ void FurnaceGUI::renderFMPreviewOPN(const DivInstrumentFM& params, int pos) { OPN_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4)); OPN_WRITE(0xa4,mult0?0x1c:0x14); // frequency OPN_WRITE(0xa0,0); - OPN_WRITE(0x28,0xf0); // key on + OPN_WRITE( + 0x28, + (params.op[0].enable?0x10:0)| + (params.op[2].enable?0x20:0)| + (params.op[1].enable?0x40:0)| + (params.op[3].enable?0x80:0) + ); // key on } // render @@ -138,7 +144,13 @@ void FurnaceGUI::renderFMPreviewOPM(const DivInstrumentFM& params, int pos) { OPM_WRITE(0x38,((params.fms&7)<<4)|(params.ams&3)); OPM_WRITE(0x28,mult0?0x39:0x29); // frequency OPM_WRITE(0x30,0xe6); - OPM_WRITE(0x08,0x78); // key on + OPM_WRITE( + 0x08, + (params.op[0].enable?0x08:0)| + (params.op[2].enable?0x10:0)| + (params.op[1].enable?0x20:0)| + (params.op[3].enable?0x40:0) + ); // key on } // render @@ -350,7 +362,7 @@ void FurnaceGUI::renderFMPreviewESFM(const DivInstrumentFM& params, const DivIns bool mult0=false; if (pos==0) { - ESFM_init((esfm_chip*)fmPreviewESFM); + ESFM_init((esfm_chip*)fmPreviewESFM,0); // set native mode ESFM_WRITE(0x105, 0x80); diff --git a/src/gui/font_furicon.cpp b/src/gui/font_furicon.cpp index f4c5fb888..af67295d8 100644 --- a/src/gui/font_furicon.cpp +++ b/src/gui/font_furicon.cpp @@ -1,374 +1,392 @@ #include "fonts.h" -// File: 'icons.ttf' (32364 bytes) +// File: 'icons.ttf' (34100 bytes) // Exported using binary_to_compressed_c.cpp -const unsigned int furIcons_compressed_size = 17542; -const unsigned int furIcons_compressed_data[17544/4] = +const unsigned int furIcons_compressed_size = 18414; +const unsigned int furIcons_compressed_data[18416/4] = { - 0x0000bc57, 0x00000000, 0x6c7e0000, 0x00000400, 0x00010037, 0x000e0000, 0x00030080, 0x54464660, 0xbd36a34d, 0x7e000051, 0x28158250, 0x4544471c, - 0x00150046, 0x200f8214, 0x2b0f8334, 0x322f534f, 0x8f52fa8c, 0x68010000, 0x562c0f82, 0x70616d63, 0x7ace15d2, 0x94020000, 0x142c0382, 0x20747663, - 0x6f043b00, 0xa8040000, 0x04261f82, 0x70736167, 0x5982ffff, 0x2c7e0022, 0x08380f82, 0x66796c67, 0x4aa9652e, 0x78050000, 0xdc720000, 0x64616568, - 0x759d8025, 0xec201b82, 0x36210382, 0x23108268, 0x05068209, 0x24205f82, 0x24280f82, 0x78746d68, 0x3519b32f, 0xc0200f82, 0xd2280f82, 0x61636f6c, - 0xea7f5862, 0xac205f82, 0xcc270f82, 0x7078616d, 0x8203af00, 0x4801213b, 0x202c0f82, 0x656d616e, 0xdd521d80, 0x54780000, 0xe3303382, 0x74736f70, - 0x12a0e944, 0x387a0000, 0xf4030000, 0x012deb84, 0xafdd0000, 0x0f5ff7a9, 0x0b00f53c, 0x25368207, 0xfde00000, 0x08839fff, 0x55e3e12b, 0xfe80fc91, - 0x05000768, 0x220f82e0, 0x82020008, 0x82028305, 0x00062233, 0x290982ff, 0x80fc0007, 0x00070000, 0x0c820100, 0x0420028b, 0x65251184, 0x0800bb03, + 0x0000bc57, 0x00000000, 0x34850000, 0x00000400, 0x00010037, 0x000e0000, 0x00030080, 0x54464660, 0xc26fa34d, 0x85000099, 0x2c158218, 0x4544471c, + 0x00150046, 0x84000014, 0x2b0f83fc, 0x322f534f, 0x9352fa8c, 0x68010000, 0x562c0f82, 0x70616d63, 0x5acfe3d2, 0x9c020000, 0x1c2c0382, 0x20747663, + 0x6f043b00, 0xb8040000, 0x04261f82, 0x70736167, 0x5982ffff, 0xf4840022, 0x08380f82, 0x66796c67, 0x2dda8f80, 0x90050000, 0x60790000, 0x64616568, + 0xbda2b925, 0xec201b82, 0x36210382, 0x23108268, 0x05068209, 0x24205f82, 0x24280f82, 0x78746d68, 0x761ab830, 0xc0200f82, 0xda270f82, 0x61636f6c, + 0x82f7d6d7, 0xbc04210a, 0xd4270482, 0x7078616d, 0x8203b600, 0x4801213b, 0x202c0f82, 0x656d616e, 0x20c6e37f, 0xf07e0000, 0xe62c3382, 0x74736f70, + 0x9231507a, 0xd8800000, 0x1c209382, 0x012deb84, 0x6cdf0000, 0x0f5f7a0e, 0x0b00f53c, 0x25368207, 0xfde00000, 0x08839fff, 0x5a1ce22b, 0xfe80fcd9, + 0x05000768, 0x220f82e0, 0x82020008, 0x82028305, 0x00062233, 0x290982ff, 0x80fc0007, 0x00070000, 0x0c820100, 0x0420028b, 0x69251184, 0x0b00bb03, 0x21008300, 0x23830002, 0x40222583, 0x0b822e00, 0x072b0d82, 0x00900100, 0x04000005, 0x82e6048c, 0x85fa2011, 0x5c032b07, 0xcf015900, 0x00020000, - 0x15820905, 0x01210285, 0x84078510, 0x75462905, 0x40005472, 0x5be12000, 0x06208385, 0x04856a84, 0x3b208b84, 0x07202683, 0x03830482, 0x0280fc25, - 0x82910009, 0x0052220c, 0x2c798220, 0x003500a5, 0x0029004b, 0x006d0191, 0x349b8278, 0x00470060, 0x00170152, 0x004a000a, 0x0032018f, 0x004f001b, - 0x2631822e, 0x011801ca, 0x82a90036, 0x0028251b, 0x004d0133, 0x23829182, 0x22000338, 0x7a017c01, 0x39003900, 0xce012701, 0x4c001300, 0x22007301, - 0x0d824700, 0x85000b24, 0x0f82b500, 0x2e005b26, 0x5c004b01, 0x2a200184, 0x53200582, 0x4b320382, 0x53005e00, 0x5c00c800, 0x2e015601, 0x1e006b00, - 0x01844600, 0x85007d21, 0x00462201, 0x214b8245, 0x018b00a0, 0x63006327, 0xaa009f00, 0x21cd8401, 0xc1820300, 0x1c200383, 0x0123e785, 0x8403000e, - 0x001c2409, 0x82f20004, 0x00082e1b, 0x00020008, 0xe0200000, 0xff5be1f4, 0x260984ff, 0xff00e1f0, 0x82e3ffff, 0x8400200b, 0x0006222b, 0x260b820e, - 0x00050004, 0x82070006, 0x8209202f, 0x000b24d1, 0x820d000c, 0x000f2617, 0x00110010, 0x32858212, 0x00150014, 0x00170016, 0x00190018, 0x001b001a, - 0x821d001c, 0x1f4a08af, 0x21002000, 0x23002200, 0x25002400, 0x27002600, 0x29002800, 0x2b002a00, 0x2d002c00, 0x2f002e00, 0x31003000, 0x33003200, - 0x35003400, 0x37003600, 0x39003800, 0x3b003a00, 0x3d003c00, 0x3f003e00, 0x41004000, 0x43004200, 0xeb824400, 0x00463c08, 0x00480047, 0x004a0049, - 0x004c004b, 0x004e004d, 0x0050004f, 0x00520051, 0x00540053, 0x00560055, 0x00580057, 0x005a0059, 0x005c005b, 0x005e005d, 0x0060005f, 0x00620061, - 0x82640063, 0x850620cc, 0x22cd82d0, 0x82020100, 0x00022105, 0x0123008d, 0x8d030000, 0xd10d4011, 0x6f043b22, 0x2c21d482, 0x08018500, 0x84003ce3, - 0x6404c600, 0xc4052405, 0x5807d406, 0xce08ea07, 0x76093409, 0xfe0ab00a, 0xee0b880b, 0xe60c2e0c, 0xd80d6a0d, 0xca0e640e, 0x5a10a40f, 0x7e11bc10, - 0x9212c611, 0x8c130c13, 0x9814d813, 0x38164815, 0x80171217, 0xae1b1e1b, 0xc01c341c, 0x081fea1d, 0x8020bc1f, 0x8a213421, 0x8622fc21, 0x2a27c823, - 0xb028ca27, 0xc6295229, 0xbe2a0a2a, 0xf42ba42b, 0x442e162e, 0x8e2e682e, 0x0c30f02f, 0xd2306a30, 0x56311e31, 0xde31ac31, 0xba32ec31, 0xc6336433, - 0xbc344034, 0xb6353a35, 0x1a36de35, 0x90364236, 0x1c37e436, 0x9e378c37, 0xc637b237, 0xee37da37, 0x14380238, 0x7c384a38, 0x2239e638, 0x02006e39, - 0x00003b00, 0xaa041502, 0x07000300, 0x01b12e00, 0xb23c2f00, 0xed000407, 0x0506b132, 0x03b23cdc, 0x220a8202, 0x8303b100, 0x83052016, 0x07b22716, - 0x3cfc0106, 0x178301b2, 0x21113338, 0x11212511, 0xda013b21, 0x640161fe, 0xaa049cfe, 0x043b56fb, 0x52820034, 0xff80fc25, 0x828003f5, 0x82012009, - 0x05002e59, 0xfd212521, 0xf99a0533, 0x0b0007b3, 0x0815820b, 0x09020325, 0xf7040f00, 0x1b00fd03, 0x2f002500, 0x30010000, 0x15163221, 0x07010e14, - 0x16150706, 0x1e171617, 0x82141501, 0x010e2f0b, 0x21372123, 0x013d3632, 0x21232634, 0x09853335, 0x012b4d08, 0xbd010a02, 0x311f8e7d, 0x1e1e2020, - 0x1f202525, 0x2214132b, 0xfe385f23, 0x1801a217, 0x3f48483f, 0x3dfce8fe, 0xfc3d4242, 0x778dfd03, 0x0d324f38, 0x0208030c, 0x1c1c0d0d, 0x363c3f58, - 0x2c272735, 0x313e428b, 0x3e88423e, 0x3e3a2e39, 0x30088f83, 0x63009100, 0xa9036f06, 0x17000f00, 0x00002b00, 0x30273025, 0x30073021, 0x37123623, - 0x17121633, 0x06233001, 0x26333007, 0x00113001, 0x30333027, 0x2f038513, 0x11070206, 0x4cea0230, 0x8a49c7fe, 0xab2fbf2f, 0x34080382, 0x3c068dfe, - 0x023cf73d, 0x17f7fe84, 0xcb04cc9b, 0x1dce3794, 0x8ce3e363, 0x8c8c2e02, 0x028cd2fd, 0xb9b9b9c9, 0x4a01f0fd, 0xfe28d401, 0x6175018b, 0xfe329afe, - 0x218282b3, 0x04820300, 0x0007ab3a, 0x58036103, 0xba038c03, 0x26370000, 0x30332702, 0x1d17021d, 0x031d1704, 0x14910290, 0x478211a3, 0x719126f1, - 0x1e268695, 0x3e331701, 0x73413701, 0x220d8606, 0x8937033d, 0x8a042002, 0xa317850b, 0xf1298211, 0xa1719126, 0x06332e86, 0x26230702, 0x012e2702, - 0x010e2327, 0x2d0d8207, 0x27262205, 0x1e37013e, 0x35323301, 0x18832634, 0x26272629, 0x33363435, 0x82171632, 0x012e2b21, 0x15062223, 0x17161714, - 0x0285011e, 0x06141523, 0x223b8225, 0x82272223, 0x26272628, 0x37363734, 0x823c823e, 0x84072021, 0x011d352c, 0x32331614, 0x3d023e37, 0x33352301, - 0x188e2311, 0x0168185e, 0x043400cc, 0x040c040e, 0x390e030e, 0x390f7b0e, 0x030f030e, 0x040e030d, 0x012761cc, 0x19621864, 0x833a0e7a, 0x820c205c, - 0x3c0f3b5c, 0x4bf0020f, 0x2f0b276e, 0x314d1d0c, 0x09342a77, 0x2b5a0a24, 0x4865702a, 0x13822463, 0x401a3c08, 0x16373731, 0x24093116, 0x15443009, - 0x02741316, 0x4f090c43, 0x2b2c3448, 0x12132021, 0x22221212, 0x375a3d5f, 0x11531e38, 0x50483c46, 0x1a1e4850, 0x7c16271a, 0x70b75adf, 0xcd70be01, - 0x4d0010b8, 0x1a2d4701, 0x67191968, 0x10014419, 0xf0fe4444, 0x220a8244, 0x101a6819, 0x47015b01, 0x084d4741, 0x42fe70af, 0x13014570, 0x19671a45, - 0x451a6719, 0x0b45edfe, 0x2a0b3638, 0x6d2c290b, 0x020d2f2c, 0x2c150207, 0x615c512d, 0x290a3431, 0x2d24250b, 0x15162a34, 0x0208020c, 0x17171f0b, - 0x6865293f, 0x133e306e, 0x42292a13, 0x4242b842, 0x27282a2a, 0x31314726, 0xa4525c39, 0x09085c52, 0x3b1e3121, 0x009efe57, 0x52000700, 0xae0602ff, - 0x1a001a05, 0x38002400, 0x53004900, 0x80007800, 0x22050000, 0x37343526, 0x3337023e, 0x07060706, 0x3307010e, 0x3233013e, 0x06141516, 0x013d3227, - 0x1d222334, 0x26031401, 0x16332702, 0x17161712, 0x36373633, 0x272a8212, 0x23210702, 0x17322111, 0x07272783, 0x012e2313, 0x48372327, 0x15270779, - 0x27262201, 0x8234012e, 0x3637222a, 0x21238233, 0x58450617, 0x3698080b, 0x17011e37, 0x1105010e, 0x11011501, 0xa2013501, 0x0f106d6b, 0x8a314f3a, - 0x1e2a2a3b, 0x0c09251d, 0x51314711, 0x6d6a6e5f, 0x211f6c6d, 0x136b2183, 0x0e0e1450, 0x140e0e0c, 0x2169144e, 0x8f012083, 0x56090165, 0x3a392b2c, - 0x56087180, 0x249b7817, 0x9b242626, 0x5d3aad02, 0x23232122, 0x2e2e2221, 0x3333563b, 0x12490d1f, 0x4c44353e, 0x4236444c, 0x0e380f12, 0xe4fe6920, - 0x72fe2801, 0x84fdd8fe, 0x3c3d3c7f, 0x2d22606f, 0x28292829, 0x312f3256, 0x776a676a, 0x77197755, 0x56080282, 0x01701803, 0xfe466fbf, 0x4f4e46e7, - 0x01464e4f, 0xfe6f4619, 0x9e027041, 0x4d603433, 0xe7fe1160, 0x5536c913, 0x2a3e2a26, 0x8efede26, 0x842a2a27, 0x2a2a84b6, 0x25251414, 0x3929074a, - 0xa1525f32, 0x3b365e53, 0x4a092108, 0x34feb24e, 0xfe821001, 0xfecd0192, 0x820082ef, 0x00043f00, 0x06ab0020, 0x006103e0, 0x0015000b, 0x0074004a, - 0x11303700, 0x16322130, 0x012b0614, 0xb7490119, 0x46012008, 0x162205ce, 0x12821617, 0x2806d046, 0x3435012e, 0x3233023e, 0x08d14617, 0x1506072c, - 0x011f1614, 0x1415011e, 0xcd46020e, 0x022e2105, 0x16212485, 0x0cc64617, 0x3e373623, 0x06c84601, 0x0121d108, 0x66665d20, 0x2aadb45d, 0xad292f2e, - 0x7655dd02, 0x0d320c2a, 0x362b2b23, 0x3b2e4141, 0x620a270a, 0x563d2162, 0x37384c36, 0x0d320d28, 0x39384b1a, 0x37342020, 0x205d643c, 0xdd025a3f, - 0x4c5e0a04, 0x294a663c, 0x426c4d2a, 0x59237d5a, 0x503d5113, 0x20506161, 0x16161d1c, 0x5ff38a19, 0x6d9e02b7, 0xf5fe6db9, 0x282b6a01, 0xfd2b292e, - 0x0b343db6, 0x162b0c2f, 0x27313a17, 0x07020c2e, 0x515c1202, 0x1b31482c, 0x0b321a1b, 0x29210b2d, 0x2a2c1817, 0x130c0b29, 0x4d2f505d, 0x31711d37, - 0x82572d40, 0x58815454, 0x3447502e, 0x5d663b2f, 0x09665d6e, 0x2f0f1008, 0xfe5d3420, 0x0005009b, 0x06ab002e, 0x006103d2, 0x001f0012, 0x0134002a, - 0x25000046, 0x28076842, 0x1e32013e, 0x020e1402, 0x05834227, 0x1d06222a, 0x05161401, 0x17322111, 0x2c0f5e41, 0x27012e05, 0x17071d23, 0x1d170f1d, - 0x8502870e, 0x3305820b, 0x11231101, 0x17121633, 0x3317011e, 0x3d27053d, 0x0e3d270f, 0x0b880287, 0x3311042d, 0x02262311, 0x603c3301, 0x82232223, - 0x7a5f3a02, 0x23234460, 0x483c6144, 0x4f904f4f, 0x01df014f, 0x2b2c5609, 0x9aa35657, 0x26008225, 0x0863029a, 0x440c0922, 0x62280732, 0x22852175, - 0x0d082308, 0x76231088, 0x42ac8521, 0x282d074c, 0xb6845428, 0x5a275484, 0x52a2525d, 0x3304845d, 0x339e024f, 0xfe67c134, 0x256901f1, 0x26293d2a, - 0x155315b2, 0xae4d7f44, 0xfe02284d, 0x439e0270, 0x4043f4fe, 0x01268088, 0x4362fd90, 0x5f430c01, 0x00a53a05, 0x035b0654, 0x002f00b8, 0x00540048, - 0x2500005e, 0x010e2330, 0x022e2223, 0x078a4435, 0x20083e43, 0x06e34906, 0x82373621, 0x013d3501, 0x11213523, 0x32210123, 0x06141516, 0x07010e07, - 0x17021e15, 0x07300b82, 0x2123020e, 0x37323337, 0x34013d36, 0x012b2627, 0x080a5d4d, 0x0e04e541, 0x804a6074, 0x3035345c, 0x53434330, 0x122b9c71, - 0x6518124a, 0x7979634d, 0x24242863, 0x10101b1b, 0x772f01ac, 0x72010601, 0x141b7769, 0x1a1a3415, 0x1311353d, 0x4f391011, 0x8768fe2f, 0x821e35e9, - 0x356a0800, 0x3733d2e9, 0xe2d23337, 0x6d38503d, 0xa2686aa2, 0x1c1c3738, 0x2b0b5963, 0x7f493b0b, 0x7f758875, 0x14130b0a, 0x41271e1d, 0x0341fe75, - 0x2f627646, 0x14151542, 0x16010703, 0x3525242f, 0x412d2c32, 0x1c1b7525, 0x1b342933, 0x3033711c, 0x00333027, 0x00050000, 0x06ab0035, 0x006103cb, - 0x0046001f, 0x00600053, 0x25000066, 0x1b442622, 0x37362c13, 0x0617011e, 0x22210607, 0x8227012e, 0x37362321, 0x0646013e, 0x2a148205, 0x17163207, - 0x3233033e, 0x4315021e, 0xa8081058, 0x36352135, 0x11333712, 0x15231533, 0x23113325, 0x4e01010e, 0x49269683, 0x76584169, 0x48115b21, 0x59594d3b, - 0x124c3d4d, 0x210e3a0f, 0xfc013c3d, 0x113c5738, 0x23232c10, 0x39902955, 0x22212d2d, 0x020d1617, 0x1c0b0104, 0x2a203025, 0x211d3548, 0x3b35573e, - 0x42754141, 0xfea10242, 0x2eb52dc9, 0xfe5b5b8e, 0x2307dbbe, 0xa9b1ac8e, 0x2e588154, 0x30324b4d, 0x665d6a39, 0x313f6a5d, 0x49092309, 0x44242929, - 0x493d3030, 0x53363680, 0x2627291c, 0x2d2d2928, 0x15010136, 0x1c0f1b23, 0x332f4d37, 0x58213c53, 0x3d0d3d3e, 0x0804843e, 0x5d834d2f, 0x4a2a014a, - 0x83553afe, 0x3b6301d8, 0x000800ed, 0x0608ff4b, 0x001505b5, 0x0016000c, 0x0028001c, 0x0068005c, 0x009b0072, 0x11301700, 0x12354430, 0x83110121, - 0x113323ec, 0x01871521, 0x4c06a645, 0xb44c2d75, 0x13232406, 0x85031333, 0x010f2a0b, 0x013e2533, 0x010e2337, 0x82028207, 0x27028213, 0x17011e23, - 0x33112311, 0x2005af4c, 0x821e8233, 0x11332902, 0x0901b023, 0x582c2c56, 0x30075644, 0xf466ca01, 0xfe850186, 0xfe0301e1, 0xfa1f01fd, 0x0cdd4be7, - 0x4b092521, 0x0c2207dd, 0xdd4b0c2e, 0x38300811, 0xe1092209, 0x64082309, 0xf1ca81c9, 0x0d030e04, 0x3b040e03, 0x02d002b0, 0x080b0104, 0x5716071f, - 0x15571516, 0x0b081f08, 0x62010501, 0x12481278, 0x09200b82, 0x54080382, 0x77124712, 0x9f02f862, 0x67c23334, 0x6901f1fe, 0x293e2926, 0x02bcfd25, - 0x5abbfd9f, 0xc45a9f02, 0x035acf58, 0x0b353957, 0x2c2a0b2b, 0x0c2f2c6e, 0x14020802, 0x5d512c2d, 0x0a343160, 0x25240a29, 0x162a342d, 0x08020b16, - 0x17200a03, 0x64293f17, 0x7c1f0b69, 0x8202821f, 0x01613340, 0x104111e0, 0xd3114110, 0x144c1390, 0x2d134b13, 0x02822db6, 0x13491322, 0xfe290b82, - 0x279f0263, 0x5816279d, 0x82028216, 0x61fd2c08, 0x04000000, 0xab002900, 0x4703d706, 0x3a220553, 0x53474600, 0x1140471a, 0x14011d26, 0x32331617, - 0x20070443, 0x0adc4125, 0x01292108, 0x66655e20, 0x29adb35d, 0xad292e2e, 0x95840f03, 0x41694926, 0x223b3b57, 0x110f3d0f, 0x2c4e3b48, 0x4e350082, - 0x0e134c3c, 0x3c220f3a, 0x012d013d, 0x01bffeae, 0x01ddfe23, 0x10054741, 0x54a9b134, 0x272d5981, 0x22084b26, 0x35393008, 0x5d665d35, 0xc6423535, - 0x020b2a07, 0x60ba609e, 0x000060c4, 0x825f5103, 0x6d01082e, 0x930502ff, 0x44001a05, 0x6d005500, 0x9e299182, 0xd500ab00, 0x0000e300, 0x05584725, - 0x0622232c, 0x27012e07, 0x013e3736, 0x1b443637, 0x0e072205, 0x098f5202, 0x82070621, 0x22232a01, 0x3727022e, 0x17161716, 0x09d6481e, 0x44013521, - 0x37370664, 0x3233023e, 0x27061016, 0x3d023e32, 0x022e3401, 0x07010e22, 0x4f011d06, 0x258205a1, 0x34350225, 0x48353736, 0x322005f2, 0x634a4282, - 0x1e152405, 0x82141501, 0x0ce1476b, 0xee471320, 0x8325200b, 0x2307211e, 0x02829982, 0x27262223, 0x232d8206, 0x2e272223, 0x37204f82, 0x3320b083, - 0x07204c85, 0x7382c988, 0x02165508, 0x3a3c3e4b, 0x17433031, 0x0f0c300c, 0x21331413, 0x502f2b20, 0x0a0a213a, 0x1d1a2f24, 0x13131a19, 0x1f1f2216, - 0x2f362c2d, 0x0f2a3647, 0x0e0e0c50, 0x1c2e1112, 0x3f45413e, 0x3e980247, 0x0f0e3c5c, 0x5c3c0e0f, 0x76767d3e, 0x1e31237d, 0x311e0e0e, 0x071f3145, - 0xb3080082, 0xfd31100f, 0x3d593bf8, 0x343c4a1f, 0x53371e3e, 0x38292968, 0x343f0f0f, 0x10104a3d, 0x3c3a5a3c, 0x42794242, 0x3b373d42, 0x3a3a6f3b, - 0x16165203, 0x90295645, 0x21225b38, 0x05010d2d, 0x0e0e0a01, 0x2a203026, 0x1d352424, 0x1f1f1110, 0x2b383656, 0x10113c2c, 0x42413bec, 0x41413b3a, - 0x07293599, 0x262c312e, 0x160a2a0a, 0x0a1d1415, 0x412d1709, 0x191a2028, 0x04061928, 0x150d0d06, 0x2c253914, 0x0e191a48, 0x2c21140e, 0x11143e17, - 0x0d0c0c11, 0x3407343a, 0x6afe5c36, 0x4040582f, 0x40415253, 0xfeb72f59, 0x1c5cb6b8, 0x752c4933, 0x1c34482c, 0x2424341c, 0x2e080982, 0x1a1a2325, - 0x1d06031c, 0x432a4734, 0x13071151, 0x40263a4e, 0x0c0d192d, 0x2620202d, 0x07134e3a, 0x2a435111, 0x1d342423, 0x13323857, 0x82373732, 0x01382704, - 0x102e303a, 0x0484302e, 0x491f2a08, 0x556b4041, 0x284e291b, 0x01365a28, 0x12121401, 0x0f0e0f1b, 0x32304c36, 0x1e1d2b2a, 0x44131222, 0x3f933030, - 0x3e3c0e3c, 0x2104823e, 0x0082003f, 0x78000437, 0x8806b700, 0x0f005603, 0x37002700, 0x00004000, 0x30113001, 0x2a038423, 0x30213035, 0x30133015, - 0x8a333035, 0x201b830f, 0x22178233, 0x82273021, 0x0c815427, 0x07010e3c, 0x01012e33, 0x01c86cac, 0x5b5b53fc, 0x5c5c2401, 0xfb3c2b02, 0x99266f3a, - 0x03828826, 0xd7fe4308, 0x10411005, 0x024010c6, 0x02c2fdf5, 0xfd60603e, 0xee0158c2, 0x12fe5858, 0x70b5b558, 0x7070be01, 0x027042fe, 0x31c5313a, - 0x0000c531, 0x00080005, 0x03f806ab, 0x00340061, 0x0046003e, 0x00580050, 0x9f4b3700, 0x23272a32, 0x33132307, 0x01171216, 0x90a18623, 0xfd420811, - 0x0d2a7655, 0x2b230d31, 0x4140372b, 0x280a3a2e, 0x2262620a, 0x4c36563c, 0x0d273838, 0x4b1a0d32, 0x1f203a37, 0x653c3634, 0x5a3e215d, 0xfa3dd002, - 0x89e46e3b, 0xfe269826, 0x401104d7, 0x4010c510, 0x13912803, 0x824bac20, 0xb50b242c, 0x419e02b5, 0xfd210a00, 0x20108ef7, 0x34008200, 0x00600003, - 0x03a006ab, 0x00150061, 0x004b002b, 0x03302500, 0x0eaa4630, 0x07020624, 0xb1413521, 0x48052013, 0x012d1d87, 0x1270db3c, 0x2b0a1247, 0x2b0b040b, - 0x2509820b, 0x2595256d, 0xc7414b01, 0x84012605, 0x4a269683, 0x14614868, 0x9e02b728, 0x2737dd37, 0x0282279b, 0xc3830882, 0x2007d641, 0x1741480b, - 0x042acb82, 0xb7004700, 0x5603b906, 0xcd820b00, 0x29002122, 0x46183f46, 0x01280a1b, 0x23112311, 0x48152135, 0x28074c4d, 0x292e2e29, 0x011402ad, - 0x070246ad, 0x6c7b0125, 0x46fc01c8, 0xc1200f08, 0x2106ea45, 0x79423e02, 0x207f8205, 0x08934f06, 0x27001e34, 0x4e003d00, 0x7e005800, 0x22050000, - 0x34352627, 0x01843637, 0x0e333025, 0x4f060702, 0x17200594, 0x2408954f, 0x14011d22, 0x06944f01, 0x200a1256, 0x2d964f06, 0x4f010e21, 0x033f1297, - 0x37366b7a, 0x1d1d0f10, 0x8a312827, 0x123a553b, 0x110c0a12, 0x30513147, 0x6d6a6e2f, 0x4f09fed9, 0x04260692, 0x050c0513, 0x944f0512, 0x4d142212, - 0x17944f14, 0x0e3a0e22, 0x350e954f, 0x7f4242fd, 0x363d3d3c, 0x232f3038, 0x2b50522d, 0x312f332a, 0x904f3535, 0x691a2213, 0x4f02821a, 0x2e221192, - 0x924f2eb6, 0x20082215, 0x0d934f08, 0x0106003c, 0x0508ff17, 0x001505e9, 0x0017000b, 0x004b002d, 0x005f0055, 0x11300500, 0x03823330, 0x15302122, - 0x0b870784, 0x3305c650, 0x37363435, 0x1632013e, 0x14011e17, 0x010e0706, 0x37363227, 0x3d2b0182, 0x26273401, 0x23012e27, 0x82070622, 0x07bf4601, - 0x4f110521, 0xcc2412d1, 0x6311016c, 0x22080382, 0x6a4210fd, 0x29292626, 0x836a2626, 0x2a26256b, 0x6b25262a, 0x17402641, 0x0c0c0d16, 0x4017160d, - 0x873f2826, 0x013f220b, 0x0c8348f4, 0x9f02f825, 0x8460c1fd, 0x57032b04, 0x822b2c2d, 0x2b825455, 0x09832d2c, 0x0883a920, 0x191b612d, 0x2e252519, - 0x25252e69, 0x8c1b1919, 0x02562f0c, 0x6eb86e9f, 0x6a01f5fe, 0x282e282c, 0xf082002c, 0x000a0036, 0x03f606ab, 0x00120061, 0x003a0030, 0x004a0044, - 0x2e222500, 0x4f064050, 0xe6af0749, 0x1133112f, 0x2c011521, 0x294c6a42, 0x426a4c29, 0x9b078541, 0x665d21e0, 0x08086449, 0x6d130222, 0x2dac1001, - 0x54548257, 0x2d2d5882, 0x82a88258, 0x1b602d57, 0x2425191a, 0x242e6a2e, 0x1b1a1925, 0x55200c8c, 0x280e6d50, 0xfd9e02c1, 0x000060c2, 0x2ccb8204, - 0x03b606ab, 0x00130061, 0x002d0022, 0x06d34366, 0x30153023, 0x26038821, 0x30013011, 0x83173233, 0x070124e8, 0x52012b06, 0x272405ba, 0x11012b26, - 0x51064d58, 0x2724077e, 0x012e2726, 0x35220282, 0x80513734, 0x1506230d, 0x80511714, 0x07240806, 0x010e0706, 0xfea3014a, 0xfe1301ca, 0xedc301ed, - 0x4a343441, 0x344a2929, 0xeded4134, 0x2e2e5c4b, 0xdc02804b, 0x2705de45, 0x372b2a24, 0x17174140, 0x2405df45, 0x563d1111, 0x07e04535, 0x384a2b08, - 0x1a1a3f3a, 0x5d653c36, 0x201f1011, 0x9e02b759, 0xfe60ba60, 0x159e02dc, 0xa87d5415, 0x1515547d, 0x6c5b5e60, 0xfe2f2f5b, 0xd6456b22, 0x1717210a, - 0x21075a51, 0x5b512424, 0x2f290809, 0x15142a2c, 0x5c130c0b, 0x26272f51, 0x001d1c1b, 0x8f000400, 0x71066300, 0x1500a903, 0x39002f00, 0x00004300, - 0x30013025, 0x06ec5033, 0x2806535a, 0x01070206, 0x15163221, 0x0bd44914, 0x2322df84, 0xa2523721, 0x09064f08, 0xfea2012e, 0x59168ded, 0x0b360f16, - 0x0b380f05, 0x89370982, 0x012fb92f, 0x687301c2, 0x34291b77, 0x1f1e1919, 0x10241b1a, 0x4e1d1c11, 0x3c2106f5, 0x07f34e3c, 0x46036334, 0x45ecfe45, - 0x3729c536, 0x014525c8, 0xfd8c4514, 0xe44e8cd2, 0x142a3605, 0x0b010703, 0x4918170b, 0x2d2c3235, 0x75252120, 0x34293337, 0x09e34e37, 0xff320134, - 0x05ce0508, 0x00260014, 0x00780033, 0x009b008a, 0xc44e0500, 0x4a01202e, 0x2e3241d1, 0x11232701, 0x13331123, 0x3317011e, 0x23113311, 0x04823521, - 0x07020e26, 0x37362726, 0x30081082, 0x38700215, 0x10103d57, 0x5523222c, 0x2d39902a, 0x1721222d, 0x05010c17, 0x251c0a01, 0x472b1f31, 0x3d221e35, - 0x423a3557, 0x41417542, 0x3b3f4402, 0x08884a39, 0x4a341321, 0x0b220688, 0x884a230a, 0x233c0807, 0x2c2c1f1f, 0x37472f36, 0x0d4f0f29, 0x12110e0e, - 0x413e1d2d, 0xfd474045, 0x0e3408b2, 0xdb796803, 0x0308340e, 0x43017967, 0x341708bc, 0x4002123e, 0xa3a55655, 0x304425f8, 0x81493c30, 0x080d284f, - 0x1b241447, 0x4d361d0f, 0x3c53332f, 0x3d3e5922, 0x3f3f3c0d, 0x3e3d0d3c, 0x29353d01, 0x2b312e07, 0x0a2a0a26, 0x1e141416, 0x2d170909, 0x1a202840, - 0x0619271a, 0x0e0c0605, 0x25381514, 0x1a1a482c, 0x21150e0e, 0x153f172b, 0x06594a10, 0x36333508, 0x104a035c, 0x00fe1b63, 0x8dfe9e02, 0x0210631b, - 0x5a62fd01, 0x3818f501, 0x3a021343, 0xbcfd5d5c, 0x0003005a, 0x06ab001b, 0x006103e5, 0x005c0039, 0x2500007f, 0x430d4749, 0x26220568, 0x69432627, - 0x0aea5410, 0x07060726, 0x2221010e, 0x23051d47, 0x3233013e, 0x200fd051, 0x055b5636, 0x012722a2, 0x2a775410, 0x490c320d, 0x91430570, 0x31312c06, - 0x573c1111, 0x38374c36, 0x49330c27, 0x23080b73, 0x1f1f1011, 0x8429025a, 0x24131395, 0x57426825, 0x3d0f2276, 0x3a48120f, 0x4e58584e, 0x0f124c3c, - 0x79220f3a, 0x8b491e9e, 0x06b4430b, 0x512e2e23, 0x0bb5432c, 0x2d171723, 0x0611552a, 0x1b26272d, 0xa9b11d1c, 0x2c404154, 0x4e4d2e2c, 0x0051053b, - 0x9a52200a, 0x0000281a, 0x004f0005, 0x55b106ab, 0x0021092b, 0x342b5540, 0x35210122, 0x28080282, 0x02061521, 0x54012107, 0x2222603d, 0x22222323, - 0x44607960, 0x60442424, 0x4e4e483c, 0x014f4f90, 0x570901de, 0x57572c2b, 0x058957a3, 0xfe60032e, 0xfe420141, 0x36a601cd, 0x4c0136d6, 0x082bd954, - 0x5bbcfd2a, 0x5b59ea01, 0x52bafe52, 0x2e000700, 0xd20608ff, 0x1c001505, 0x47003300, 0x5d005300, 0x91007300, 0x06370000, 0x30153007, 0x3205034c, - 0x36333011, 0x30373637, 0x16070633, 0x2e231712, 0x45110101, 0xf1450ae5, 0x2726230c, 0x29493330, 0x82332006, 0x301122cf, 0x187d5701, 0x47012e21, - 0x36082df3, 0x6d1a42f7, 0x1848046d, 0x417f5a5a, 0x16ba32ae, 0x01922783, 0xfead01a5, 0xfe2301c0, 0x014001dd, 0x7c737422, 0x031b6d1b, 0x781b6c1b, - 0xfb2da418, 0x5e20014a, 0x505e6565, 0x1720075d, 0x08291448, 0x1e4b4123, 0xfe9f02d0, 0x6d1e5cac, 0x4bce4d6d, 0x3d21e8fe, 0x02e2fee1, 0x60bb609f, - 0x080160c4, 0xc732cbcc, 0x27028232, 0x4ee2fe29, 0x6203f6fe, 0x210df847, 0x3748b5fd, 0x0000212d, 0x358f8362, 0xffca0005, 0x05360608, 0x000f0015, - 0x00330017, 0x00920066, 0x054e0500, 0x07062a11, 0x05263330, 0x2327012e, 0x05494e30, 0x13303326, 0x3317011e, 0x2106e749, 0x63593023, 0x27262110, - 0x5a0a6259, 0x615905ad, 0x0226230d, 0x42823327, 0x53171621, 0x9c53078e, 0x36372706, 0x02063337, 0x6c822307, 0x2327262f, 0x010e0706, 0x3df80207, - 0x266e3afb, 0x07704e98, 0xc6313034, 0x0e190230, 0x68020834, 0x3408db79, 0x7967030e, 0x7c445bfc, 0x050e4805, 0x1e3a2e35, 0x2262621e, 0x4c36563d, - 0x03273837, 0x384a1a49, 0x4d201f3a, 0x5e0807eb, 0x6a1a9001, 0x2e0d6e1a, 0x03171809, 0x0b0a2408, 0x0e790f36, 0x23090a36, 0x23310309, 0x6e1c6b23, - 0x390d7a1b, 0x02191910, 0x3b101919, 0xb6b6f80d, 0x70bf0170, 0x7041fe70, 0x94943b02, 0x641b3494, 0x02fffd0f, 0x0f8dfe9f, 0x01021b64, 0x570361fd, - 0x2f0c343d, 0x16162b0b, 0x2e27313a, 0x5905060b, 0x1a350694, 0x4102321b, 0x17172822, 0x0a2a2a2c, 0x515c130d, 0x1e364d2f, 0x334b830b, 0x6f2ddb3b, - 0x29981e70, 0x3c3cdc2b, 0x94252bdc, 0xa2a1de25, 0x32235f83, 0x82663fea, 0xea3f3700, 0x01040032, 0x050eff18, 0x000f05e8, 0x003a001b, 0x00570043, - 0x7d412500, 0x3021221a, 0x05fc5423, 0x3e230724, 0x0a823701, 0x27089843, 0x1e07010e, 0x33110101, 0x2007c543, 0x06764111, 0x27823320, 0x0806b543, - 0x320ccd36, 0x7867030c, 0x0c310ddb, 0x03796803, 0x6d1b7f3f, 0x6b1b021b, 0x9124781b, 0x238b2324, 0x19661980, 0x1a671a02, 0x248c2478, 0xd9fb9325, - 0x0111016c, 0x269a2601, 0x08078043, 0x9a277723, 0x5f178127, 0x02fffd18, 0x188efe9e, 0x0002175f, 0xb22d62fd, 0x2db22c2c, 0x3639e539, 0xa72936db, - 0x3e028229, 0x3936da36, 0x022903e6, 0x60c2fd9e, 0x01440801, 0xc732430f, 0x32c73131, 0x43f2fe43, 0x8200f6fe, 0x01062200, 0x20f38236, 0x2ff382ca, - 0x001e0014, 0x0032002a, 0x00520046, 0x23300500, 0x22055744, 0x82061415, 0x5d1720cb, 0x27220f19, 0x30650723, 0x070b5008, 0x4f030221, 0x02220e3f, - 0x704e2103, 0x6da33a0b, 0x685a2101, 0x61184142, 0x5a167918, 0x2aac8516, 0xac292f2e, 0xfa3db603, 0x0a66423b, 0x10401034, 0xfd4010c5, 0x716e6e79, - 0x0b114811, 0x0b040a2b, 0x09820b2c, 0x6f706e24, 0x9c4e6801, 0xf2200809, 0x5d6c9e02, 0x2f145f48, 0xb52d2fbc, 0x292b5b2d, 0xd52b282e, 0xb6b696fe, - 0x6fbf0170, 0x7041fe6f, 0x2506fd50, 0x4f015901, 0x554f4f01, 0xb1fe240b, 0x5402b1fe, 0x003007ae, 0x00a90002, 0x03570663, 0x001d00a9, 0x25000033, - 0x2005d341, 0x058d5106, 0x210eb641, 0x0d500516, 0x08194905, 0x33304008, 0x03301530, 0x9027a076, 0x64640413, 0x35c21896, 0xa017bc33, 0x02207f20, - 0x96208120, 0x8a33be17, 0x0aeb2d01, 0x164f411c, 0x41955003, 0x4163ccce, 0xa7a720ed, 0x53330127, 0x2426014f, 0x8234d034, 0xfe243402, 0xd7d74fdb, - 0x1e720271, 0x02175446, 0xfd47a148, 0x1000712b, 0x8101e71e, 0x28010634, 0xd80502ff, 0x0f001a05, 0x43002500, 0x74005600, 0xd3537c00, 0x335b4f11, - 0xa14e0120, 0x07a6512b, 0x6d670228, 0x01fb01c7, 0x704f4173, 0x6b822107, 0x2205824f, 0x4f27416b, 0x27250979, 0x16164027, 0x2c00820d, 0xfd401616, - 0x4c6b41d0, 0x6b4c2929, 0x21078741, 0x1c874126, 0x28264134, 0x0c17163f, 0x170c0d0d, 0xed023f16, 0xfc01c86c, 0xcf514c01, 0xb7fd3605, 0x822c2b2d, - 0x2c815554, 0x2c2d2d2c, 0x82a9812c, 0x612d2b2c, 0x06c04e1a, 0x4e242521, 0x242105c0, 0x06cd4e25, 0x01031a24, 0x2e83572d, 0x2d2d5823, 0x202c8258, - 0x4f2b8a57, 0xf94e09c2, 0x011a2205, 0x083352e8, 0x33000736, 0xc206fdfe, 0x0c000905, 0x3c001600, 0x86006100, 0xa2009c00, 0x6119b95a, 0xf4510f8b, - 0x5a012014, 0x0b210683, 0x1a7f5a01, 0x112c24a5, 0x36321614, 0x11331135, 0x020e0714, 0x2005934d, 0x063e5b11, 0x57090124, 0x284a2b2c, 0xad022a08, - 0x21215e3a, 0x21212323, 0x05b2612f, 0x390c1d52, 0x200e390e, 0x01c30269, 0x080b0105, 0x8282081f, 0x0b081f07, 0x62010401, 0x8b5a1378, 0x20072905, - 0x12471208, 0xc0fb6277, 0x0c202282, 0x82261d82, 0x081e0882, 0x2283020c, 0x12491222, 0x08253382, 0x12072008, 0x33228248, 0x8e386401, 0x0c0c6538, - 0x577f5835, 0x020c0c36, 0xf8f46636, 0x080fc15a, 0x2b27b120, 0x83b6842a, 0x14132b2a, 0x084a2525, 0x32390820, 0x53a1535e, 0x093c365e, 0x4f4a0821, - 0x925aa801, 0xf0fe2305, 0x905a1001, 0xff042117, 0x320823a1, 0x4efe9f02, 0x514d4d51, 0x5efeb201, 0x41323244, 0x32411f1f, 0xa2014432, 0x9f0261fd, - 0x005abbfd, 0x01040000, 0x0502ff4d, 0x001a05b3, 0x00510017, 0x529d008b, 0xbb50059f, 0x09d54a09, 0x4d14aa50, 0xfe482341, 0x297a4d0e, 0xf14e0120, - 0x85012110, 0x38095a46, 0x76544b01, 0x0c320c2a, 0x362c2a24, 0x17174240, 0x0a280a3b, 0x10313161, 0x06d94811, 0x0c330c23, 0x0653621a, 0x5d653b29, - 0x1f201010, 0x848bfd5a, 0x230d2332, 0x32862b2b, 0x620a272e, 0x11113131, 0x4c36563c, 0x0d283738, 0x3c213289, 0x2b328264, 0x015b1f1f, 0x0d310ce2, - 0xdb796802, 0x03230682, 0x46f27967, 0x0b320692, 0x2e0c343d, 0x16162b0c, 0x18273139, 0x07020b17, 0x5e4d1202, 0x2822250d, 0x2b2c1717, 0x210b5e4d, - 0x33b16203, 0x487e0121, 0x002f0f3c, 0x00020000, 0x065400cf, 0x00b80331, 0x4e4a0034, 0x262010d9, 0x5c05666a, 0x5e4a0880, 0x07405209, 0x01020e22, - 0x080d3543, 0x022e2787, 0x02021135, 0x0f34946a, 0x352d0f3f, 0x52504436, 0x2525493a, 0x2a3d3d7b, 0x5e446b4c, 0x5b314646, 0x465d2104, 0x21282847, - 0x7e4b4420, 0x704e2974, 0xc552f901, 0x11108551, 0x3bae764b, 0x55214b3b, 0x3a0f404d, 0x1b1c360f, 0x3a313d48, 0x1706070e, 0x3865393a, 0x21213e59, - 0x03513e21, 0x1c1d322a, 0x1a1a3438, 0x7318100d, 0x44613a65, 0xfd540325, 0x6c6c6bfc, 0xfe04026b, 0x44435c11, 0x16152b57, 0x015c8658, 0x10db82ef, - 0x3d07732d, 0x08ff0a39, 0x1505f606, 0x18000e00, 0x49002b00, 0x5d005300, 0x00006300, 0x5c333001, 0x2b200950, 0x23067e5a, 0x11012b26, 0x5c2ce24c, - 0x113a146b, 0x15211133, 0x41ed7602, 0x29252568, 0x68252529, 0x4beded41, 0x804b5c5c, 0x985b49fe, 0xa7013c3a, 0x7e2a292b, 0x292a7ea7, 0x5b5f602b, - 0xfe5f5b6b, 0x2df70221, 0x54558257, 0x822d5782, 0x82a92007, 0x1b612906, 0x24251919, 0x242f692f, 0x8807bf4c, 0x0e825c0c, 0x02c0fd2e, 0x61c2fd9f, - 0x03000500, 0xfd06ab00, 0x200aa756, 0x34a75659, 0xbd4c0520, 0x56082024, 0xae6b11c0, 0x56a4200d, 0x962006c0, 0x2105744c, 0x744c8307, 0x67612007, - 0xd4560f00, 0x13a73a2b, 0x4b13134c, 0x01effe12, 0x124a1211, 0xfe134a13, 0x279e0263, 0x5916279c, 0x82028216, 0x62fd2208, 0x31008200, 0x00220003, - 0x03de06ab, 0x001b0061, 0x006c0033, 0x41531300, 0x15e7561c, 0x2114114c, 0x5259012e, 0x0fbc5c10, 0x4d53d720, 0x0cdb2805, 0x68030c32, 0x56400179, - 0x4c200abc, 0x212fb85c, 0x214b2a02, 0x0218240a, 0x4b62fd01, 0x0f6e09a1, 0x27b55c08, 0x00002508, 0xff7c0107, 0x05840502, 0x0037001a, 0x0068005b, - 0x009c0075, 0x00d500a9, 0x21300100, 0x3307010e, 0x023e3736, 0x2e078866, 0x23060701, 0x012e2722, 0x37272627, 0x821e1716, 0x05157917, 0x07062222, - 0x3e231482, 0x66213701, 0xbf663a5e, 0x0d246b07, 0x33011e22, 0x2117246b, 0x71823521, 0x24052960, 0x0e222326, 0x82838302, 0x3202233c, 0x01821716, - 0x8206c979, 0x213708c2, 0xd7fe2603, 0x06020c03, 0x200d0d0b, 0x4b2b1c2b, 0x10101f36, 0x392c2d3d, 0x3423232d, 0x4f101314, 0x230d0e0c, 0x413b1c2b, - 0x342b3b41, 0x0f3c0f12, 0x01041104, 0x6669017c, 0x3b37245d, 0x3717fe3b, 0x11103c58, 0x5523232c, 0x2e389029, 0x1622212d, 0x6b010c17, 0x1e21097d, - 0x067d6b22, 0x41415908, 0x39fe5003, 0x33248f23, 0x323a1b1b, 0x141f291c, 0x103f1006, 0x3812120b, 0x1b50664c, 0x190f0e1c, 0x201e1d2d, 0x011a691a, - 0x274e0152, 0x1014289e, 0x1c0c1710, 0x32314e36, 0x113d2a2a, 0x210a0a10, 0x3e181516, 0x19111014, 0x0b3a410d, 0x1421413a, 0x3e020902, 0x57fd3ef9, - 0x21374e66, 0xe66bd101, 0x27262d0b, 0x2d2e2828, 0x14010135, 0x1d101b23, 0x0812e66b, 0x7f20683f, 0x2a2a2e20, 0x38310c2e, 0x16271c11, 0x20061906, - 0x1b2e1d1d, 0x241b1a1e, 0x45282b25, 0x1c1c1c3e, 0x00175a17, 0x01060000, 0x0502ff7a, 0x001a0586, 0x00360028, 0x0092006c, 0x00ca009f, 0x0c406800, - 0x22263f68, 0x42062125, 0x3e241883, 0x031e3701, 0x6906345f, 0x13220738, 0xa75e2521, 0x196b6d14, 0x36352122, 0x42068861, 0x52201e45, 0x35054568, - 0x22225a39, 0x04030d2c, 0x260e0e0a, 0x242b1f30, 0x111e3424, 0x44681e11, 0x3d450805, 0x3bec1010, 0x3a3b4141, 0x34034242, 0x0c05d6fe, 0x0c0d0b07, - 0x2b1d2a21, 0x101f364a, 0x2c2d3e0f, 0x23222d39, 0x10141335, 0x0d0d340e, 0x1c2b221b, 0x3c40403c, 0x0e12342b, 0x12070f3c, 0x10fd7c01, 0x103c5838, - 0x2c238211, 0x38902955, 0x22212e2d, 0x030c1716, 0x07c76d03, 0x2205cd5e, 0x42754141, 0x6b250549, 0x1c1b336b, 0x0548423b, 0x42400f21, 0x1c2e0748, - 0x190f0e1b, 0x201d1e2c, 0x011a691b, 0x3068b352, 0x49f2222d, 0x147542a4, 0x0a2a0a24, 0x76422114, 0x0174230b, 0x3b42b901, 0x605f212d, 0x38243a42, - 0x39000600, 0xc70608ff, 0x0f001505, 0x28001c00, 0x57003200, 0x00007a00, 0x07d15805, 0x30133034, 0x17121633, 0x30272603, 0x30070623, 0x2e333007, - 0x536e0101, 0x0f995315, 0x53132575, 0x16251473, 0x17011e17, 0x05884433, 0x0423113c, 0x06240a1e, 0x0a2406e2, 0x2182c964, 0x0af12187, 0x0a0b0c0b, - 0x290bb13c, 0x306e9bfc, 0x0230080d, 0x215d3bad, 0x21242421, 0x3b2e2f21, 0x1f343355, 0x3f114a0d, 0x4c4c4434, 0x12433544, 0x1f0e380e, 0x01c4026a, - 0x090c0105, 0x82820421, 0x0c092203, 0x623e0c82, 0x09373678, 0x18080521, 0x15520517, 0x23f86277, 0x7f18187f, 0x709f0223, 0x017041fe, 0x008231e0, - 0x8c24d324, 0x4e53a501, 0x2907211d, 0x3f124d53, 0xfe095216, 0x081001f0, 0x4b141651, 0x0263fe12, 0x1975769f, 0x42420e5d, 0xfd2fb10b, 0x07000061, - 0x2c086741, 0x00200016, 0x0035002a, 0x0064003f, 0x08077f87, 0x15070623, 0x061c6c32, 0xa5710e20, 0x3d362305, 0xa3713401, 0x7301200c, 0x785613ff, - 0x1e9b7605, 0x08227541, 0x01980222, 0x37504e10, 0x18171d34, 0x16090a22, 0xfe223928, 0x25a766d5, 0xa7252626, 0x25252197, 0x3bfd9721, 0x3f507d41, - 0x5459a701, 0x0c054841, 0x1c280b0b, 0x472b211c, 0x275a1c33, 0x272c2c2b, 0x2b282457, 0x1d012329, 0x34518541, 0x01050000, 0x0502ff27, 0x001a05d9, - 0x0034002a, 0x0071003c, 0x7314827d, 0x3e240959, 0x16323302, 0x6a197176, 0x7b5410e7, 0x085e650e, 0x5d0d5d65, 0x0c5b0d7c, 0xf402210a, 0x261f8676, - 0xfb3c7302, 0x6be56f3a, 0x402607ed, 0x4010c611, 0xea76b6fd, 0x135d6b12, 0x215e6426, 0x2e015a3f, 0x2009d354, 0x0796768c, 0x762d5921, 0x652e0896, - 0x10100808, 0x5d341f2f, 0xb6b69bfe, 0x345b9e02, 0x544e200b, 0x2f220a51, 0xf376020b, 0x0982540e, 0x2f515c25, 0x6f1d374d, 0x003d09fd, 0xce010200, - 0x320516ff, 0x21000e04, 0x00003b00, 0x22233005, 0x30352627, 0x27012e35, 0x42088226, 0x322f05b0, 0x1e171617, 0x02141501, 0x27331507, 0x74013e32, - 0x2e220598, 0x436f2302, 0x1e280808, 0x8e700402, 0x55272855, 0x1b2f2f88, 0x39383e19, 0x50c45050, 0x3e383950, 0xf09aa0bb, 0x1244603a, 0x44121313, - 0x5f3b3a60, 0x5f310985, 0x492b2cea, 0x414d0b4c, 0x735c5b41, 0x4142c27f, 0x34008222, 0x7fc24241, 0x1cfcfee1, 0x4c28f267, 0x9e453737, 0x4c373745, - 0x290a8a28, 0x13000300, 0xed06b700, 0x07825603, 0x51003d22, 0x20056167, 0x0f5f6103, 0x2206944b, 0x58033003, 0x30230753, 0x6c113023, 0x11230f2b, - 0x82213023, 0x30352201, 0x08058401, 0x15302123, 0x21070206, 0x72cfe330, 0x0d175d17, 0x70175b17, 0x03228a22, 0x01050109, 0x081e080c, 0x1f088282, - 0x05974407, 0x12491236, 0x08082007, 0x12081f08, 0x62771248, 0x41fe9a02, 0xcefe4101, 0x32067662, 0x010301b7, 0x31c8329b, 0x4432c831, 0xfe45eefe, - 0x4b9d01fd, 0x7d622187, 0x00002809, 0x004c0004, 0x82b406ab, 0x001426e3, 0x0028001e, 0x5de5835f, 0xa6421ddb, 0x01112107, 0x2032994a, 0x06e45db9, - 0x5d601921, 0x2e2408e4, 0x1302ac2a, 0x27075668, 0xd7fe5803, 0x07030c03, 0x2123244a, 0x244a0e13, 0x5db72006, 0x28270eec, 0xd52b292e, 0x680296fe, - 0x402a065a, 0x14279e28, 0x0c171010, 0xc549351d, 0x1011210f, 0x200ec549, 0x30008200, 0xff730104, 0x058d0502, 0x004c001a, 0x00b4007d, 0x171772e0, - 0xe24a1720, 0x06072106, 0x15220184, 0xf24a011e, 0x220d8405, 0x57222306, 0x27210511, 0x72178237, 0x324b0c22, 0x2627210a, 0x4b06596b, 0x01230636, - 0x86323637, 0x23548555, 0x2107010e, 0x20336841, 0x49648a01, 0x02211d51, 0x11926750, 0x28286208, 0x11101d1d, 0x12120a0a, 0x1d1a1718, 0x0b131333, - 0x2011110b, 0x362c2c1f, 0x1b24232f, 0x0f15141c, 0x111c0d4f, 0x3d1d2d12, 0x47404542, 0x39fe8003, 0x33238f24, 0x1e1d1b1c, 0x0f2a1b32, 0x050a0b0f, - 0x0b0f4010, 0x26371311, 0x1b516626, 0x190e0e1c, 0x1e1d1616, 0x1b691a20, 0xa7fd5201, 0x0b03d6fe, 0x08934903, 0x0f1e3735, 0x2d2c3e10, 0x23222d39, - 0x0f141434, 0x0e0d0d4e, 0x491b2c22, 0x0f210692, 0x05074c3b, 0xfe500224, 0xb34b2438, 0x1e2a2107, 0x2008b34b, 0x205e854b, 0x08b34b18, 0x08103572, - 0x170b0c27, 0x291f2116, 0x14191a20, 0x060d0c14, 0x151a0604, 0x251c1d14, 0x1a24242c, 0x0a0e0e19, 0x1511110a, 0x143e1716, 0x093d7222, 0x614b7520, - 0x1c1c2609, 0x140e0e11, 0x07644b13, 0x4b0e0d21, 0x29230565, 0x4b1f1f44, 0x05210566, 0x2b224c46, 0xbb4b6220, 0x4f042028, 0x56280627, 0x2e000f00, - 0x8f033700, 0x6f135b5e, 0x0526243f, 0x33270226, 0x4618011d, 0x45182235, 0x38c9158a, 0x26a65fa6, 0x194a4618, 0x46181520, 0x0122153d, 0x45183d37, - 0x14941477, 0x11b34518, 0x38a61191, 0x5fcc26a6, 0x18333521, 0x28103f46, 0xb2663a01, 0x0601c901, 0x05f6706b, 0x70272821, 0x012312f6, 0x185f18eb, - 0x2d4f0346, 0x040e0301, 0x040e040c, 0x7c0e380e, 0x0782390e, 0x52044618, 0x19640128, 0x0f7a1862, 0x6b820f39, 0x0e040b3a, 0x0f3d0f03, 0xbbfdfc02, - 0x59594502, 0x4242b0fd, 0x3c3d3c7f, 0x302f3837, 0x21077a71, 0x7a713430, 0x104a200a, 0x5503da45, 0x0000002f, 0x00470005, 0x03b906ab, 0x001e0061, - 0x280b822f, 0x00700058, 0x010e0100, 0x0ad76c07, 0x2005c67a, 0x6c148233, 0x3e7b06d9, 0xa4052024, 0x10013925, 0x6c103d0f, 0x4010046c, 0x1e791e10, - 0x289f287e, 0x832bad2b, 0x63028822, 0x2718d27a, 0x0f070606, 0x52023110, 0x2208209e, 0x4612f001, 0x9e02cf12, 0x5115acfe, 0x24912514, 0x412fbc2f, - 0x3441fefe, 0x2ef0fed1, 0x53404059, 0x7a414052, 0x1b350679, 0x762b4934, 0x1b34482c, 0x2424341b, 0x252b762c, 0x1b1a1b23, 0x2224a35c, 0x4e060000, - 0x2f3a0893, 0x63004200, 0x98008d00, 0x0000a000, 0x30213005, 0x36373035, 0x30353637, 0xdb493435, 0x37362a0a, 0x1e32013e, 0x15161701, 0x05814a14, - 0x010e0725, 0x56052107, 0x3521052a, 0x211d8234, 0x937c3637, 0x4d3d8306, 0x26210555, 0x20018227, 0x752f8522, 0x162305dd, 0x18011617, 0x4e085142, - 0x122022f7, 0x240cf279, 0x39fe4503, 0x091153d6, 0x2c055a55, 0x664c1c1c, 0x0f0e3750, 0x17160c0d, 0x0613531d, 0x3f490126, 0x0e3c2e2e, 0x23080082, - 0x3f2e2e3c, 0x7c76767c, 0x10181922, 0x0606070f, 0x18100f07, 0x19184519, 0x0706100f, 0x0f100607, 0x89fe1819, 0x26200e4f, 0x3afb3d74, 0x4f72726e, - 0xf22c0c0f, 0x2a2ebf68, 0x310c2f29, 0x261d1138, 0x2a06c349, 0x1e1b1717, 0x2b252435, 0x49222328, 0x672207c3, 0x2e7c1817, 0x18173307, 0xb6b8feb7, - 0x1a0e0e5c, 0x2d24231a, 0x24242c75, 0xa1821a1a, 0x1b1a0e29, 0x752c2423, 0x8323242d, 0x7703210e, 0x231f2d4f, 0x4f014f01, 0x080a2f4f, 0x00000020, - 0xfe0b0106, 0x05f50568, 0x001f000f, 0x00410039, 0x0055004b, 0x0100006f, 0x26222330, 0xfd4e013d, 0x4e062014, 0xf6671afd, 0x77012007, 0x05201287, - 0x0d654118, 0x23112e08, 0x33133311, 0x11333736, 0x5fe50223, 0x5b383539, 0x1211201f, 0x3625262a, 0x35368235, 0x7c292626, 0x27a1676b, 0x0c0d2d40, - 0x402d0d0c, 0x2d098227, 0x0c0d0d0c, 0xed02402d, 0xfb01c76d, 0xff793bfb, 0x15042b0c, 0x09230905, 0x18186219, 0x08821962, 0x80670539, 0x595a06b4, - 0x68fe677b, 0x0732313a, 0x3d2b2b34, 0x81554c3e, 0x82172b2c, 0x2c2b3d00, 0xad965581, 0x1aa24513, 0x2d252533, 0x24252e6a, 0x331b1b33, 0x6a2e2524, - 0x3325252d, 0x2108ec67, 0xe2762401, 0x2e292d09, 0x123e2b28, 0xb32c1249, 0x2cb32d2d, 0xfd390882, 0xfe9e02ff, 0xfdaaabab, 0x00000062, 0x00850002, - 0x037b0654, 0x003400b8, 0x357f644d, 0x08131a41, 0x1133133d, 0x69b70123, 0x3e103594, 0x35362c10, 0x39515144, 0x7a252649, 0x4c2a3e3d, 0x465f436c, - 0x035b3145, 0x48465d21, 0x21202728, 0x757e4a44, 0x03714e29, 0x2c0b06fa, 0x2381130b, 0x82138124, 0x82062708, 0xe007e1a1, 0x8c64829a, 0x8f022a2d, - 0x22175a17, 0xec4040ec, 0x38088222, 0x46037ffd, 0xaa0156fe, 0x0000bafc, 0x00b50003, 0x034b0663, 0x000b00a9, 0x19ad7b15, 0x76010921, 0xb63c11d5, - 0x7f746801, 0xd8e1747f, 0x34393934, 0xfe5a03d8, 0x58178ced, 0x0f370a17, 0x0a380f05, 0x88340982, 0x632eba2e, 0xe6894603, 0x01b2fe89, 0x3a3236c5, - 0x31fd3632, 0x2205a176, 0x7637c825, 0x00200aa1, 0x1b500082, 0x45612007, 0x7e2006bf, 0x4e46bd45, 0x210834ce, 0x0f3d1015, 0x10036d6d, 0x781f1040, - 0xa0287f1e, 0x2bac2b27, 0x02872283, 0x3b5c3f63, 0x0f0e0e0f, 0x43445c3b, 0x1f312805, 0x311f0d0d, 0x441f3145, 0x3122053b, 0x50501a03, 0x3edf4532, - 0x50ef0121, 0x053a2c71, 0x02ff5b01, 0x1a05a505, 0x3e003400, 0x81004800, 0x0000a200, 0x27262205, 0x9f793736, 0x103f5407, 0x2e070225, 0x54222301, - 0x4b180f3f, 0x45891465, 0x76290169, 0xb1570d59, 0x17162a0e, 0x5002010e, 0x252a7655, 0x14c77f26, 0x1e1a0b23, 0x0dc87f09, 0x442d0121, 0xb3350545, - 0x2e2e2aac, 0x2cfeac2a, 0x262b7654, 0x2b2a2425, 0x18424037, 0x11406916, 0x69190d21, 0x74760e40, 0x25210805, 0x58416924, 0x2e2d2275, 0x4e3b4811, - 0x3d4e5858, 0x4d0a134b, 0x3dfd7922, 0x2b232334, 0x31391616, 0x0e8c5427, 0x1b160a23, 0x128d5408, 0x07bf4b18, 0x282e2925, 0x8918012b, 0x2650693d, - 0xa9b12a08, 0x2d404055, 0x4b4d2d2c, 0x39301919, 0x5d665d6a, 0x06313e69, 0x0052492f, 0x00040000, 0x06b7002e, 0x005603d2, 0x0027000f, 0xe7411836, - 0x15d55e15, 0x2d611320, 0x6c622518, 0x70fc01c8, 0x2b09ad55, 0x6940ed83, 0x28282525, 0x40692525, 0x2005f660, 0xe8411881, 0x70022009, 0x022705bb, - 0x2a2a2a9e, 0x827da87d, 0x5e602705, 0x5e5b6c5b, 0x9e8222fe, 0x0106002d, 0x0502ff4b, 0x001a05b5, 0x821e0015, 0x004930ab, 0x00d8016e, 0x35300500, - 0x35302130, 0x74371236, 0x152e053d, 0x15302330, 0x33302530, 0x23301130, 0x145e010e, 0x183e2007, 0x2008d344, 0x073d4801, 0x22012e23, 0x07197e06, - 0x01021e24, 0x006a2622, 0x06816d08, 0x1d060722, 0x0e814618, 0x1133352b, 0x15010f23, 0x0f150e0f, 0x8205840d, 0x270b9108, 0x27012e05, 0x0b3f3537, - 0x0a200284, 0x0b880584, 0x17930620, 0x1133073c, 0xa1021533, 0xb52dc9fe, 0x5c5c8e2d, 0x07dbbffe, 0xaf028e23, 0x0e3c5c3e, 0x44181d0f, 0x072009bd, - 0x0f260082, 0x3145310f, 0x09820f10, 0x311f0739, 0x9683eefd, 0x41684927, 0x213b3b58, 0x11103c0f, 0x2c4d3b48, 0x182c2d2d, 0x220ad849, 0x1808bc2f, - 0xeb2e3b4c, 0x2c0b222e, 0x206ed20b, 0x2453d202, 0xa3a60101, 0xa54a18f2, 0xe2fe290e, 0x4140582f, 0x58835152, 0x07aa4518, 0x2c25243b, 0x24252b75, - 0x1c1c1a1a, 0x25241a1a, 0x252c752b, 0x031c3324, 0x55a9b106, 0x61481880, 0x3e342c0f, 0x09230931, 0x0b292949, 0xd4f5015a, 0x2254d0b1, 0xd00a280a, - 0x41508953, 0x5f8655b6, 0x5abcfd23, 0x08008200, 0x5c000235, 0xa4065f02, 0x03007105, 0x00001b00, 0x21152113, 0x17372701, 0x37153335, 0x15330717, - 0x27071723, 0x07352315, 0x35233727, 0xf948065c, 0x8cc204b8, 0x823c8c2a, 0xc6c62103, 0x02240887, 0x220246a5, 0x1a84118c, 0x01000026, 0x25ff5c00, - 0xd6225b82, 0x59821400, 0x2c323808, 0x35053e01, 0x032e3411, 0x5c23022c, 0x0134019d, 0xadc7f605, 0x3c2e5b7f, 0xfeccaa6f, 0xfee5fef8, 0xa502ada9, - 0x7e725633, 0x47667a83, 0x154ff90e, 0x91907d5b, 0x8d3e6886, 0x22012447, 0x832e012c, 0x823e2047, 0x06332747, 0xccfe9da4, 0x4889fbfe, 0x01080125, - 0x9657011b, 0x32ef8248, 0xff2a0005, 0x05d60638, 0x00040094, 0x00440040, 0x8208014b, 0x21302b9b, 0x22042107, 0x0e07030e, 0xeb570704, 0x34352e06, - 0x17323336, 0x3e32041e, 0x043e3703, 0x240c8337, 0x15161716, 0x391e8314, 0x2103032e, 0x15012115, 0x23250523, 0x3e342535, 0x16323302, 0x1e323617, - 0x26821703, 0x83061421, 0x32362105, 0x17250583, 0x3e37013e, 0x20478203, 0x83408436, 0x011e2228, 0x840c8631, 0x32332317, 0x2983021e, 0x15215b85, - 0x29618514, 0x012e2726, 0x2e353435, 0x6e832702, 0x23010e2e, 0x010e2722, 0x27220607, 0x030e0706, 0x06830c86, 0x0e820220, 0x2683b084, 0x2e210386, - 0x08ba8202, 0x0e34273b, 0x010e3501, 0x26272627, 0x34351617, 0x34370627, 0x22233637, 0xe4015c26, 0x0462fe46, 0x506154e0, 0x070f2f4f, 0x5b403c15, - 0x934b522c, 0x08354da7, 0x0f1b151d, 0x504f2f0f, 0x081b9a61, 0xe401ddd4, 0x10fe62fe, 0x01000196, 0x7afc9600, 0x1a240e04, 0x0e0b1b13, 0x080c171f, - 0x1b110202, 0x06021115, 0x2e380407, 0x0e101007, 0x0810020c, 0x130c0603, 0x06090a0c, 0x1339530b, 0x282c040d, 0x1e180c11, 0x1d0c2f41, 0x0e040d15, - 0x2013241a, 0x0c1e2714, 0x090a1114, 0x492f0909, 0x13012c21, 0x180f121a, 0x0f061004, 0x0f142521, 0x1c192a10, 0x12241017, 0x0e0d2109, 0x3b1a132e, - 0x07071813, 0x09380d06, 0x141b0e29, 0x340d111a, 0x0807122f, 0x0a111910, 0x1010160b, 0x0f171c10, 0x1a0c2218, 0x16110822, 0x1601030d, 0x03270617, - 0x0801021a, 0x01030110, 0x12070802, 0x6eb9021a, 0x52422fbb, 0x1d09173c, 0x184b424a, 0x4d4ea72d, 0x1d150f0d, 0x523c1716, 0x18972f42, 0x58019008, - 0xfa25016e, 0xa0faf0f0, 0x1a291d0d, 0x0e071b15, 0x0e111d11, 0x0b3a2617, 0x070d0c02, 0x10370505, 0x09100b09, 0x670c0804, 0x2741040d, 0x60373b1f, - 0x11070e18, 0x2617121d, 0x100f1509, 0x04435201, 0x05120438, 0x11083401, 0x2d0c1017, 0x04030b12, 0x0b423e08, 0x111d2f07, 0x10450b24, 0x331c1411, - 0x090e1d0f, 0x0c4d0a5d, 0x410c1818, 0x1617022d, 0x05081721, 0x141b0c11, 0x0e052226, 0x17031922, 0x0c0c0207, 0x02050203, 0x02020411, 0x82010402, - 0x07022403, 0x4300171a, 0x250807b3, 0x000b0049, 0x0100000f, 0x23153315, 0x23352315, 0x01353335, 0x05211521, 0x64b4b498, 0x28fbb4b4, 0xb8f94806, - 0x0b834905, 0xfdb46438, 0x0500465c, 0x3eff5300, 0xa7058c06, 0x28000600, 0x30002c00, 0x3d823700, 0x0b331129, 0x27113301, 0x83270723, 0x84032001, - 0x020b2605, 0x37021b35, 0x20018217, 0x29058413, 0x33213317, 0x35022b15, 0x2d853733, 0xa1042e08, 0xa0cecea0, 0x334ac936, 0x3f2f303f, 0x19373a3a, - 0x37361031, 0x36372727, 0x37193110, 0x2f3f3a3a, 0x4a333f30, 0x8af201c9, 0x7373bc8a, 0x082c8388, 0x01080247, 0xfe2201b0, 0x3a50fede, 0x72a93ed6, - 0xfcfee3cd, 0xdf6a8b54, 0x01d4fe92, 0x89eb0104, 0x0401eb01, 0xdfb7affe, 0xfe548b6a, 0x72cde3fc, 0x61d63ea9, 0x50fe3a61, 0x2201defe, 0x0000b001, - 0xff5c0003, 0x05a40637, 0x22bb82e0, 0x833b000d, 0x092326b7, 0x03112301, 0x22068411, 0x85012f01, 0x200585bf, 0x220f840f, 0x821f020f, 0x013f23c4, - 0xc485011f, 0x03280586, 0x1401c834, 0x98c81401, 0x023d0685, 0x4e7238d8, 0x60484b60, 0x27545a5a, 0x4554184b, 0x364b394b, 0x2539155a, 0x5a153925, - 0x310c8236, 0x4b185445, 0x5a5a5427, 0x604b4860, 0x5d01724e, 0x9484fcfe, 0x5d020422, 0xd183b982, 0xfefc3008, 0x04381bc8, 0x59512d42, 0x2a372166, - 0x70844858, 0x3b4b645a, 0x6c438f52, 0x528f4a65, 0x5a644b3b, 0x58488470, 0x6621372a, 0x422d5159, 0x82003804, 0x00023b00, 0x0622ff4b, 0x00d005b5, - 0x002a0009, 0x2d150500, 0x35211501, 0x0135010d, 0xad8b1713, 0xd98d3f20, 0x03070324, 0x7e836d01, 0x01260422, 0x26087a82, 0x6e4ed3fa, 0x32621f6d, - 0x7d75756d, 0x667d615e, 0x94494994, 0x5e617d66, 0x6d75757d, 0x6d1f6232, 0xa03e4e6e, 0x83a0cece, 0x03330803, 0xcd7efe54, 0x54b073ec, 0xb2cc426e, - 0x318459a1, 0x99353699, 0xa1598431, 0x6e42ccb2, 0x0190b054, 0x7efecd09, 0x00020000, 0x066aff5e, 0x007b05a2, 0x421f000b, 0x03300e5b, 0x0b270327, - 0x37112702, 0x1337021b, 0x15211337, 0x21066942, 0x7487c7fe, 0x02218687, 0x077742a7, 0xfef0fc33, 0xf7fe629d, 0x01befeb2, 0x8467fe65, 0xfe840705, - 0x2f098267, 0xf7feb2be, 0x749dfe62, 0x53000300, 0xaf063eff, 0x2c069342, 0x1300002f, 0x010d3521, 0x15032135, 0x05074121, 0x22058042, 0x8235021b, - 0x09a04279, 0x85030721, 0x42cb2026, 0x55220754, 0x8c424503, 0x363b2106, 0x210b8c42, 0x8c423b36, 0x42962006, 0x0428074b, 0xcecea003, 0x61fcfea0, - 0x21268642, 0x2f839bfe, 0x00820020, 0xc8000428, 0x38065eff, 0xb582a605, 0x1b000f2b, 0x00001f00, 0x15213525, 0x0b7f4303, 0x112e0b8b, 0x6c041123, - 0xb4b4cc01, 0xb4b464b4, 0x0684c0fc, 0x5a310227, 0x046464a0, 0x0595439c, 0x855cfe21, 0x0e022507, 0x4806b8f9, 0x00216282, 0x058f4701, 0x82a50221, - 0x13002c63, 0x5c211521, 0xb8f94806, 0x8246a502, 0x01053a1c, 0x0508ff56, 0x001505aa, 0x0039000f, 0x00650045, 0x0500008f, 0x23113335, 0x05715e06, - 0x3311332a, 0x07140115, 0x2307020e, 0x21058b55, 0x1a82012e, 0x18020e21, 0x20125250, 0x86501804, 0x08e84b0a, 0x280d1463, 0x011e3736, 0x06070617, - 0x0fce6725, 0x6a272621, 0x06321013, 0x01152107, 0x6507bc9c, 0x0b2c0b37, 0xa3a56e3d, 0xda671c02, 0x5b382c05, 0x0d2d2221, 0x0a010402, 0x18250e0f, - 0x32122050, 0x4275d9fe, 0xfe417542, 0x26958456, 0x57416949, 0x4e5b2276, 0x52180610, 0x5f0808aa, 0x39fee202, 0x1c3307cf, 0x1b323b1b, 0x05151e2a, - 0x120a332c, 0x664c3713, 0x0f1b1c50, 0x1d2c190e, 0x4f4f201e, 0x5bf85201, 0x3b6bf501, 0x430a270a, 0x5bbcfd77, 0x4049a501, 0x1b556b41, 0x29284d2a, - 0x01013659, 0x1a121214, 0x370e0f10, 0x2b31304c, 0x211e1e2a, 0x30441312, 0x3c3e9230, 0x3f3f3c0e, 0x01310482, 0x54a9b2ca, 0x4d2d5881, 0x392f314b, - 0x5d655d6a, 0x088b4a6a, 0x07b8682f, 0x2e2a292f, 0x1038310c, 0x1116271d, 0x0d9f6914, 0x1c1d1b39, 0x005c4344, 0x01040000, 0x0508ff2e, 0x001505c7, - 0x00350013, 0x7986004d, 0x11231303, 0x52300130, 0x302310b5, 0x86133033, 0x30112203, 0x4e218223, 0x276d1551, 0x2e012137, 0x37088e61, 0x230905c4, - 0x19621809, 0x09186218, 0x68050824, 0xb406b381, 0x2bfc687b, 0x323b3a6d, 0x609f02f8, 0xdcfe60bb, 0x48120102, 0x2cb32d12, 0x822db32c, 0xfffd2b08, - 0xabfe9f02, 0x61fd5501, 0x20846203, 0x0b61c322, 0x0ac44118, 0xec781720, 0x5c450805, 0x24242c51, 0x1b1a1b31, 0x0b2d0b32, 0x2c2e2822, 0x0a15152a, - 0x515c130d, 0x1b27262f, 0x01001e1b, 0xd3006b00, 0x31049506, 0x00004400, 0x15163201, 0x020e0714, 0x22230607, 0x2627052e, 0x22232627, 0x0e070607, - 0x08108301, 0x34352645, 0x3e373637, 0x1e323306, 0x17161705, 0x37323316, 0x013f013e, 0x4c06023e, 0x0b072a1e, 0x72326624, 0x42522a6e, 0x1b362f44, - 0x70214913, 0x4643305d, 0x0d101259, 0x2a1e1411, 0x140a0412, 0x4330381c, 0x8b264d40, 0x32310821, 0x15572a48, 0x1f0b0316, 0x1e2aca02, 0x41160f11, - 0x1e723293, 0x5539472a, 0x3184242e, 0x984643a8, 0x0a08151e, 0x141c1e2a, 0x2e221106, 0x2a443a52, 0x281d891d, 0x287c2a48, 0x130e0728, 0x3e008200, - 0xff1e0003, 0x05ce0649, 0x000b00cc, 0x004e002b, 0x06140400, 0x26222123, 0x21333634, 0x83222532, 0x3e0127b8, 0x17323301, 0x03822101, 0xde82e282, - 0x21012729, 0x01092722, 0x8413010e, 0x820620f3, 0x211582e6, 0x2c82012f, 0x32333625, 0x8503011f, 0x13172108, 0x3808e482, 0x151d0b02, 0x1d159ffe, - 0x6101151d, 0x158afe15, 0x6101021d, 0x19111a05, 0x0182010f, 0x010f1c8c, 0x151d078d, 0x82fe0f1c, 0x0f1977fe, 0xbcfeaafe, 0x0a9a1a05, 0x01011a0f, - 0x08548203, 0xd2050554, 0x050a0f0f, 0x03a38b05, 0x080e0a0f, 0x0e0111a4, 0x1d1d2a70, 0x1d5d1d2a, 0x04070715, 0x151410a6, 0xfd18effd, 0x150e0c80, - 0x6802181d, 0xfbd40115, 0x051410bb, 0x010a0f4a, 0x070403e1, 0x5b020404, 0x0f0a1007, 0x15013d02, 0x0f0a0706, 0x95e9fe0c, 0xf3840d09, 0xf3844620, - 0xf3841420, 0xf3ad5020, 0xf3830120, 0x25010f22, 0x1624e982, 0x05071415, 0x2506734b, 0x2330012f, 0xf8822726, 0x3f363726, 0x9c033601, 0x6f20f582, - 0x9123f584, 0xa2f9fc15, 0x52033ef5, 0x48040f0a, 0x04042d01, 0xfe110f0a, 0x0f0d89c7, 0xba05070a, 0x04030501, 0x6c020101, 0x3df7a307, 0x070b0e03, - 0x01687906, 0x05120b0e, 0x0f074d6d, 0x69030f0a, 0x08060503, 0x03020404, 0xf78a0cb4, 0xf784b020, 0xf7ae5220, 0x82363421, 0x361322f9, 0x24068337, - 0x07060714, 0x21048227, 0xf4832223, 0x31262722, 0x2306f141, 0x41051716, 0x5b20f982, 0xa523f984, 0xa354fb15, 0x0fb32ef9, 0x8d010f14, 0x0f140806, - 0x02219605, 0x2bf18312, 0x4f060709, 0x0a0f056b, 0x375a070d, 0x2908faa3, 0x0f0f0a8d, 0xbd54fe0a, 0x0a0f0806, 0x2dc70609, 0x01051305, 0x6a060601, - 0x0a090691, 0x4a790a0f, 0x00030000, 0x0649ff46, 0xf34105e2, 0x06ec4135, 0x37070222, 0x0f210985, 0x05854d01, 0x42233421, 0x1f2406eb, 0xce063601, - 0x7320f982, 0x8d23f984, 0xa2c7f915, 0xe6053ef9, 0x0f0a1206, 0xa3285f01, 0x0f0a0707, 0x0807e00b, 0x03070607, 0x0f026201, 0x4c06110a, 0x08f8a22a, - 0x119e0451, 0x04040a0f, 0x6b74e2fe, 0x0e0a0f04, 0x04049307, 0x01010704, 0x0a05040d, 0x7dcc110f, 0x02000000, 0x7cff7d00, 0xb6058306, 0x12000600, - 0x11010000, 0x17010b37, 0x06140011, 0x26012722, 0x17323634, 0xb7cc0501, 0x01b7e2e2, 0x163e2c0d, 0x821690fa, 0x70053505, 0x9afeb605, 0x01dffe46, - 0x66014621, 0x2c3e31fa, 0x16700516, 0xfa210582, 0x204f8d90, 0x294f881f, 0x23061401, 0x012c3122, 0x0f410027, 0x17162606, 0x05000112, 0x385c891e, - 0xddfe011f, 0xfebd14fe, 0x2c017588, 0x05291c1f, 0x0157016c, 0x1f120252, 0x386b8c2b, 0x052c1f12, 0x7801bdc6, 0x0706ae02, 0x1b242c1f, 0xa9fe82fd, - 0x010aaefe, 0x20c7842c, 0x20c7825f, 0x34c78583, 0x1b071105, 0x24112701, 0x37342622, 0x16323601, 0x05010714, 0x21c78376, 0xae8a1bfb, 0x6601a02b, - 0xfe210146, 0x9afe46df, 0x83d8841d, 0x85c583de, 0x224f87c7, 0x8600002f, 0x05f0444f, 0x3e373626, 0x24363706, 0x2606e243, 0x04060706, 0x8207020e, - 0x07062202, 0x0865860e, 0x1ffcfa28, 0x5331062c, 0x2a3f2322, 0x27493840, 0x01b60192, 0x1f04034c, 0xb61d272c, 0xa3d0e2fe, 0x461f3472, 0x33532b2f, - 0x818c2608, 0x0f1f2408, 0x5ad7760e, 0x7b569959, 0x92275d55, 0x2c0120a8, 0x032b1d1f, 0x865c4312, 0x9f3b6495, 0x78d96f78, 0x82001915, 0x00043400, - 0x0644ff46, 0x00b405ce, 0x000e000d, 0x00300020, 0x45330100, 0x23270a7d, 0x01032531, 0x4523010e, 0x09280a9d, 0x15163201, 0x06010714, 0x36231486, - 0x4564ab04, 0x87280a5d, 0xfeef6ffe, 0x111a05bc, 0x290a9542, 0x1f310215, 0x49fc0c2c, 0x06822916, 0x16b70324, 0x4a45ee02, 0x01a12609, 0x10bbfb48, - 0x08664514, 0x0284fe2a, 0x161f2c30, 0x2327fa12, 0x05220683, 0xa68223d9, 0x0001002e, 0x06090045, 0x001505cf, 0x3700001f, 0x201e2b46, 0x22104378, - 0xd7450a20, 0x8200201b, 0xff13286f, 0x05ed0654, 0x823e00af, 0x1732267e, 0x37011b16, 0x26038817, 0x07031702, 0x82270327, 0x010b2304, 0x03832707, - 0x06033424, 0x0f86010f, 0x37032c08, 0x3f303713, 0x37131701, 0x3f011a32, 0x018b0201, 0x32420239, 0x2c723939, 0x173f3939, 0x278c3939, 0x39847045, - 0x1f2f4c39, 0x825e3939, 0x3f6b2813, 0x03463939, 0x828c0706, 0x4824080e, 0x71543939, 0x39580724, 0x013d5539, 0x380a221a, 0x0239ae05, 0x47024bfd, - 0x0bfd3838, 0x38388d01, 0x530361fe, 0x20260582, 0x5602cbfe, 0x3282fb0d, 0x9760032a, 0x393985fb, 0x6ffe6502, 0xc8310582, 0x393924fd, 0x37da0208, - 0xcffd6d5a, 0x6d033939, 0x314a82fc, 0xfd06a005, 0x38ec649e, 0xf5e3fc38, 0x9f019a01, 0xdf82380b, 0xffa02008, 0x056006ce, 0x00070032, 0x15212500, - 0x35231121, 0x04940133, 0x7cbcfacc, 0x047846f4, 0x920078ec, 0x35212c23, 0x044c0221, 0xfe74fb14, 0x86ac01cc, 0x29279625, 0x5c030403, 0x14fe2cfc, - 0x279e6402, 0xa402bc28, 0x5cfde4fc, 0x279d1c03, 0x01740429, 0xfc9cfdec, 0x9dd403a4, 0x2c052927, 0x54fe3401, 0x8c04ecfb, 0x33232796, 0x82112315, - 0xe40527c7, 0x34fbf47c, 0xeb884405, 0xceff6328, 0x32059d06, 0xd0821f00, 0x15163226, 0x01011930, 0x42058045, 0x112006e9, 0x2c09f042, 0x23198003, - 0x16117d02, 0xfd152319, 0x21068320, 0x0c8483fd, 0x11e00233, 0x19233205, 0xcafdccfd, 0x230e2202, 0xfd121c19, 0x2306828a, 0xdefd6a04, 0x02230d84, - 0x82000e76, 0x00012a00, 0x063b0063, 0x00c5049d, 0x206b851a, 0x22628214, 0x87092722, 0x01303760, 0x09173236, 0x60063601, 0xfe0b2319, 0x123e1290, - 0xc1fe51fd, 0x0d821f12, 0x82700121, 0xaf02330d, 0x02123f01, 0x131923bc, 0x19f8fd10, 0xfecb0319, 0x0c83193d, 0x19080228, 0x0135fc19, 0x638219c3, - 0x9f000322, 0x6120cf82, 0x2124cf82, 0x47003400, 0x35256782, 0x07050e22, 0x24018506, 0x3e321523, 0x052b6105, 0x2136372a, 0x051e3235, 0x26070617, - 0x2105d66b, 0xfe6b1601, 0x15332505, 0x27052e22, 0x0b2f8482, 0x986b6825, 0x7748a48e, 0x348e8492, 0x8e101c30, 0x50fa2d0f, 0x57541d09, 0x4190797d, - 0x5e542123, 0x03212284, 0x2008865c, 0x301a8810, 0x0378ba04, 0x86642e1b, 0x92df86da, 0x0a163b84, 0x2a0e8f06, 0x45201302, 0x375d965c, 0x845e803b, - 0x4ffd211f, 0x78200886, 0x00321987, 0x00aa0003, 0x03560648, 0x0009008e, 0x00250013, 0x25603700, 0xe3441812, 0x18012008, 0x2007d553, 0x083b5eab, - 0x343a3a30, 0x117a03d8, 0x81040a41, 0x0a120197, 0x07821240, 0x03483908, 0x88e78946, 0xc501b2fe, 0x323a3335, 0x2200ff36, 0x7ffd137d, 0x31fe4603, - 0x02227c14, 0x00bafc81, 0x3b010400, 0xc50507ff, 0x07000c05, 0x1b001100, 0x00002d00, 0x15011101, 0x35210382, 0x20819203, 0x65451805, 0xaa032810, - 0x72fe2801, 0x5ce0d8fe, 0x6718052d, 0xc82307c0, 0x180e3308, 0x200af853, 0x736818b3, 0x2802210b, 0x35093d60, 0x2b292e28, 0x1b640fcc, 0x9e02fffd, - 0x631c8efe, 0xfd010210, 0x00830062, 0xae000e24, 0x08830100, 0x02200384, 0x01240b86, 0x1f000d00, 0x02200b86, 0x3d20b382, 0x03240b86, 0x8f002400, - 0x04200b86, 0xd0202382, 0x05240b86, 0xfe000f00, 0x062a0b86, 0x28010c00, 0x01000300, 0x53840904, 0x0b860020, 0x1a000122, 0x17850982, 0x0e000224, - 0x17862d00, 0x48000324, 0x0b864500, 0x23820420, 0x0b86b420, 0x1e000524, 0x0b86de00, 0x18000624, 0x4d830e01, 0x75004630, 0x6e007200, 0x63006100, - 0x20006500, 0x07824900, 0x0f826f20, 0x0000732e, 0x6e727546, 0x20656361, 0x6e6f6349, 0x52200e82, 0x67201f82, 0x6c202d82, 0x72282b82, 0x65520000, - 0x616c7567, 0x46210882, 0x212f8300, 0x07830074, 0x67007222, 0x3a204584, 0x59994982, 0x83002021, 0x8233201f, 0x843220b0, 0x00302403, 0x82340032, - 0x6e6f2a6f, 0x726f4674, 0x3a206567, 0x827b8c20, 0x2d33260f, 0x30322d32, 0xa8258332, 0x825620b0, 0x0072248a, 0x84690073, 0x002022a0, 0x22018230, - 0x842e0031, 0x00303007, 0x72655600, 0x6e6f6973, 0x31303020, 0x8230302e, 0x41b08d10, 0x07411108, 0x02002206, 0x20008b00, 0x840c8b01, 0x8265200b, - 0x01ce0805, 0x03000200, 0x03010201, 0x05010401, 0x07010601, 0x09010801, 0x0b010a01, 0x0d010c01, 0x0f010e01, 0x11011001, 0x13011201, 0x15011401, - 0x17011601, 0x19011801, 0x1b011a01, 0x1d011c01, 0x1f011e01, 0x21012001, 0x23012201, 0x25012401, 0x27012601, 0x29012801, 0x2b012a01, 0x2d012c01, - 0x2f012e01, 0x31013001, 0x33013201, 0x35013401, 0x37013601, 0x39013801, 0x3b013a01, 0x3d013c01, 0x3f013e01, 0x41014001, 0x43014201, 0x45014401, - 0x47014601, 0x49014801, 0x4b014a01, 0x4d014c01, 0x4f014e01, 0x51015001, 0x53015201, 0x55015401, 0x57015601, 0x59015801, 0x5b015a01, 0x5d015c01, - 0x5f015e01, 0x61016001, 0x75076201, 0x3045696e, 0x07863046, 0x07863120, 0x07863220, 0x07863320, 0x07843420, 0x85303121, 0x30312127, 0x31212785, - 0x21278530, 0x27853031, 0x87303121, 0x86352027, 0x8636202f, 0x86372007, 0x86382007, 0x86392007, 0x86412007, 0x86422007, 0x86432007, 0x86442007, - 0x86452007, 0x85462007, 0x30312107, 0x31200786, 0x7f860786, 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, - 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, 0x7f863120, 0x85303221, 0x31322177, 0x7f860786, 0x7f863220, 0x7f863220, 0x7f863220, - 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x7f863220, 0x85303321, 0x31332177, - 0x7f860786, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, 0x7f863320, - 0x7f863320, 0x7f863320, 0x85303421, 0x31342177, 0x7f860786, 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, - 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, 0x7f863420, 0x85303521, 0x31352177, 0x7f860786, 0x7f863520, 0x7f863520, 0x7f863520, - 0x7f863520, 0x7f863520, 0x7f863520, 0x7f863520, 0x7f863520, 0x00423529, 0xff010000, 0x450200ff, 0x0c2408d9, 0x04001400, 0x02221582, 0x13840000, - 0x84050544, 0x55e02305, 0x15822068, 0x0c5b7d18, 0x3131fa05, 0x0000f177, + 0x15820905, 0x01210285, 0x84078510, 0x75462905, 0x40005472, 0x5fe12000, 0x06208385, 0x04856a84, 0x3b208b84, 0x07202683, 0x03830482, 0x0280fc25, + 0x82910009, 0x0052220c, 0x08798220, 0x3500a524, 0x29004b00, 0x6d019100, 0x08007800, 0x47006000, 0x17015200, 0x4a000a00, 0x32018f00, 0x4f001b00, + 0x31822e00, 0x1801ca26, 0xa9003601, 0x28251b82, 0x4d013300, 0x82918200, 0x00033823, 0x017c0122, 0x0039007a, 0x01270139, 0x001300ce, 0x0073014c, + 0x82470022, 0x000b240d, 0x82b50085, 0x005b260f, 0x004b012e, 0x2001845c, 0x2005822a, 0x32038253, 0x005e004b, 0x00c80053, 0x0156015c, 0x006b002e, + 0x8446001e, 0x007d2101, 0x46220185, 0x4b824500, 0x8b00a021, 0x00632801, 0x009f0063, 0x823b01aa, 0x00da2487, 0x822b000b, 0x000022c3, 0x83058203, + 0x851c2003, 0x160123ef, 0x09840300, 0x04001c24, 0x1b82fa00, 0x0800082e, 0x00000200, 0xf4e02000, 0xffff5fe1, 0xf0260984, 0xffff00e1, 0x0b82e3ff, + 0x2b840020, 0x0e000622, 0x04260b82, 0x06000500, 0x2f820700, 0xd9820920, 0x0c000b24, 0x17820d00, 0x10000f26, 0x12001100, 0x14328d82, 0x16001500, + 0x18001700, 0x1a001900, 0x1c001b00, 0xb7821d00, 0x20001f36, 0x22002100, 0x24002300, 0x26002500, 0x28002700, 0x2a002900, 0x30089d82, 0x002d002c, + 0x002f002e, 0x00310030, 0x00330032, 0x00350034, 0x00370036, 0x00390038, 0x003b003a, 0x003d003c, 0x003f003e, 0x00410040, 0x00430042, 0x08f38244, + 0x47004644, 0x49004800, 0x4b004a00, 0x4d004c00, 0x4f004e00, 0x51005000, 0x53005200, 0x55005400, 0x57005600, 0x59005800, 0x5b005a00, 0x5d005c00, + 0x5f005e00, 0x61006000, 0x63006200, 0x65006400, 0x67006600, 0xd4826800, 0xd8850620, 0x0022d582, 0x05820201, 0x8d000221, 0x00012300, 0x118d0300, + 0x22d10d40, 0x826f043b, 0x002c21d4, 0xeb080185, 0x0084003c, 0x056404c6, 0x06c40524, 0x075807d4, 0x09ce08ea, 0x0a760934, 0x0bfe0ab0, 0x0cee0b88, + 0x0de60c2e, 0x0ed80d6a, 0x0fca0e64, 0x105a10a4, 0x117e11bc, 0x139212c6, 0x138c130c, 0x159814d8, 0x17381648, 0x1b801712, 0x1cae1b1e, 0x1dc01c34, + 0x1f081fea, 0x218020bc, 0x218a2134, 0x238622fc, 0x272a27c8, 0x29b028ca, 0x2ac62952, 0x2bbe2a0a, 0x2ef42ba4, 0x2e442e16, 0x2f8e2e68, 0x300c30f0, + 0x31d2306a, 0x3156311e, 0x31de31ac, 0x33ba32ec, 0x34c63364, 0x35bc3440, 0x35b6353a, 0x361a36de, 0x36903642, 0x371c37e4, 0x379e378c, 0x37c637b2, + 0x38ee37da, 0x38143802, 0x387c384a, 0x392439e6, 0x3ae83970, 0x3c943b64, 0x000200b0, 0x0200003b, 0x00aa0415, 0x00070003, 0x0001b12e, 0x07b23c2f, + 0x32ed0004, 0xdc0506b1, 0x0203b23c, 0x00220a82, 0x168303b1, 0x16830520, 0x0607b227, 0xb23cfc01, 0x38178301, 0x11211133, 0x21112125, 0xfeda013b, + 0xfe640161, 0xfbaa049c, 0x34043b56, 0x25528200, 0xf5ff80fc, 0x09828003, 0x59820120, 0x2105002e, 0x33fd2125, 0xb3f99a05, 0x0b0b0007, 0x25081582, + 0x00090203, 0x03f7040f, 0x001b00fd, 0x002f0025, 0x21300100, 0x14151632, 0x0607010e, 0x17161507, 0x011e1716, 0x0b821415, 0x23010e2f, 0x32213721, + 0x34013d36, 0x35212326, 0x08098533, 0x02012b4d, 0x7dbd010a, 0x20311f8e, 0x251e1e20, 0x2b1f2025, 0x23221413, 0x17fe385f, 0x3f1801a2, 0xfe3f4848, + 0x423dfce8, 0x03fc3d42, 0x38778dfd, 0x0c0d324f, 0x0d020803, 0x581c1c0d, 0x35363c3f, 0x8b2c2727, 0x3e313e42, 0x393e8842, 0x833e3a2e, 0x0030088f, + 0x06630091, 0x00a9036f, 0x0017000f, 0x2500002b, 0x21302730, 0x23300730, 0x33371236, 0x01171216, 0x07062330, 0x01263330, 0x27001130, 0x13303330, + 0x062f0385, 0x30110702, 0xfe4cea02, 0x2f8a49c7, 0x82ab2fbf, 0xfe340803, 0x3d3c068d, 0x84023cf7, 0x9b17f7fe, 0x94cb04cc, 0x631dce37, 0x028ce3e3, + 0xfd8c8c2e, 0xc9028cd2, 0xfdb9b9b9, 0x014a01f0, 0x8bfe28d4, 0xfe617501, 0xb3fe329a, 0x00218282, 0x3a048203, 0x030007ab, 0x03580361, 0x00ba038c, + 0x02263700, 0x1d303327, 0x041d1702, 0x90031d17, 0xa3149102, 0xf1478211, 0x95719126, 0x011e2686, 0x013e3317, 0x06734137, 0x3d220d86, 0x02893703, + 0x0b8a0420, 0x11a31785, 0x26f12982, 0x86a17191, 0x0206332e, 0x02262307, 0x27012e27, 0x07010e23, 0x052d0d82, 0x3e272622, 0x011e3701, 0x34353233, + 0x29188326, 0x35262726, 0x32333634, 0x21821716, 0x23012e2b, 0x14150622, 0x1e171617, 0x23028501, 0x25061415, 0x23223b82, 0x28822722, 0x34262726, + 0x3e373637, 0x21823c82, 0x2c840720, 0x14011d35, 0x37323316, 0x013d023e, 0x11333523, 0x5e188e23, 0xcc016818, 0x0e043400, 0x0e040c04, 0x0e390e03, + 0x0e390f7b, 0x0d030f03, 0xcc040e03, 0x64012761, 0x7a196218, 0x5c833a0e, 0x5c820c20, 0x0f3c0f3b, 0x6e4bf002, 0x0c2f0b27, 0x77314d1d, 0x2409342a, + 0x2a2b5a0a, 0x63486570, 0x08138224, 0x31401a3c, 0x16163737, 0x09240931, 0x16154430, 0x43027413, 0x484f090c, 0x212b2c34, 0x12121320, 0x5f222212, + 0x38375a3d, 0x4611531e, 0x5050483c, 0x1a1a1e48, 0xdf7c1627, 0x0170b75a, 0xb8cd70be, 0x014d0010, 0x681a2d47, 0x19671919, 0x44100144, 0x44f0fe44, + 0x19220a82, 0x01101a68, 0x4147015b, 0xaf084d47, 0x7042fe70, 0x45130145, 0x1919671a, 0xfe451a67, 0x380b45ed, 0x0b2a0b36, 0x2c6d2c29, 0x07020d2f, + 0x2d2c1502, 0x31615c51, 0x0b290a34, 0x342d2425, 0x0c15162a, 0x0b020802, 0x3f17171f, 0x6e686529, 0x13133e30, 0x4242292a, 0x2a4242b8, 0x2627282a, + 0x39313147, 0x52a4525c, 0x2109085c, 0x573b1e31, 0x00009efe, 0xff520007, 0x05ae0602, 0x001a001a, 0x00380024, 0x00530049, 0x00800078, 0x26220500, + 0x3e373435, 0x06333702, 0x0e070607, 0x3e330701, 0x16323301, 0x27061415, 0x34013d32, 0x011d2223, 0x02260314, 0x12163327, 0x33171617, 0x12363736, + 0x02272a82, 0x11232107, 0x83173221, 0x13072727, 0x27012e23, 0x79483723, 0x01152707, 0x2e272622, 0x2a823401, 0x33363722, 0x17212382, 0x0b584506, + 0x37369808, 0x0e17011e, 0x01110501, 0x01110115, 0x6ba20135, 0x3a0f106d, 0x3b8a314f, 0x1d1e2a2a, 0x110c0925, 0x5f513147, 0x6d6d6a6e, 0x83211f6c, + 0x50136b21, 0x0c0e0e14, 0x4e140e0e, 0x83216914, 0x658f0120, 0x2c560901, 0x803a392b, 0x17560871, 0x26249b78, 0x029b2426, 0x225d3aad, 0x21232321, + 0x3b2e2e22, 0x1f333356, 0x3e12490d, 0x4c4c4435, 0x12423644, 0x200e380f, 0x01e4fe69, 0xfe72fe28, 0x7f84fdd8, 0x6f3c3d3c, 0x292d2260, 0x56282928, + 0x6a312f32, 0x55776a67, 0x82771977, 0x03560802, 0xbf017018, 0xe7fe466f, 0x4f4f4e46, 0x1901464e, 0x41fe6f46, 0x339e0270, 0x604d6034, 0x13e7fe11, + 0x265536c9, 0x262a3e2a, 0x278efede, 0xb6842a2a, 0x142a2a84, 0x4a252514, 0x32392907, 0x53a1525f, 0x083b365e, 0x4e4a0921, 0x0134feb2, 0x92fe8210, + 0xeffecd01, 0x00820082, 0x2000043f, 0xe006ab00, 0x0b006103, 0x4a001500, 0x00007400, 0x30113037, 0x14163221, 0x19012b06, 0x08b74901, 0xce460120, + 0x17162205, 0x46128216, 0x2e2806d0, 0x3e343501, 0x17323302, 0x2c08d146, 0x14150607, 0x1e011f16, 0x0e141501, 0x05cd4602, 0x85022e21, 0x17162124, + 0x230cc646, 0x013e3736, 0x0806c846, 0x200121d1, 0x5d66665d, 0x2e2aadb4, 0x02ad292f, 0x2a7655dd, 0x230d320c, 0x41362b2b, 0x0a3b2e41, 0x62620a27, + 0x36563d21, 0x2837384c, 0x1a0d320d, 0x2039384b, 0x3c373420, 0x3f205d64, 0x04dd025a, 0x3c4c5e0a, 0x2a294a66, 0x5a426c4d, 0x1359237d, 0x61503d51, + 0x1c205061, 0x1916161d, 0xb75ff38a, 0xb96d9e02, 0x01f5fe6d, 0x2e282b6a, 0xb6fd2b29, 0x2f0b343d, 0x17162b0c, 0x2e27313a, 0x0207020c, 0x2c515c12, + 0x1b1b3148, 0x2d0b321a, 0x1729210b, 0x292a2c18, 0x5d130c0b, 0x374d2f50, 0x4031711d, 0x5482572d, 0x2e588154, 0x2f344750, 0x6e5d663b, 0x0809665d, + 0x202f0f10, 0x9bfe5d34, 0x2e000500, 0xd206ab00, 0x12006103, 0x2a001f00, 0x46013400, 0x42250000, 0x3e280768, 0x021e3201, 0x27020e14, 0x2a058342, + 0x011d0622, 0x11051614, 0x41173221, 0x052c0f5e, 0x2327012e, 0x1d17071d, 0x0e1d170f, 0x0b850287, 0x01330582, 0x33112311, 0x1e171216, 0x3d331701, + 0x0f3d2705, 0x870e3d27, 0x2d0b8802, 0x11331104, 0x01022623, 0x23603c33, 0x02822322, 0x607a5f3a, 0x44232344, 0x4f483c61, 0x4f4f904f, 0x0901df01, + 0x572b2c56, 0x259aa356, 0x9a260082, 0x22086302, 0x32440c09, 0x75622807, 0x08228521, 0x880d0823, 0x21762310, 0x4c42ac85, 0x28282d07, 0x84b68454, + 0x5d5a2754, 0x5d52a252, 0x4f330484, 0x34339e02, 0xf1fe67c1, 0x2a256901, 0xb226293d, 0x44155315, 0x4dae4d7f, 0x70fe0228, 0xfe439e02, 0x884043f4, + 0x90012680, 0x014362fd, 0x055f430c, 0x5400a53a, 0xb8035b06, 0x48002f00, 0x5e005400, 0x30250000, 0x23010e23, 0x35022e22, 0x43078a44, 0x0620083e, + 0x2106e349, 0x01823736, 0x23013d35, 0x23112135, 0x16322101, 0x07061415, 0x1507010e, 0x8217021e, 0x0e07300b, 0x37212302, 0x36373233, 0x2734013d, + 0x4d012b26, 0x41080a5d, 0x740e04e5, 0x5c804a60, 0x30303534, 0x71534343, 0x4a122b9c, 0x4d651812, 0x63797963, 0x1b242428, 0xac10101b, 0x01772f01, + 0x69720106, 0x15141b77, 0x3d1a1a34, 0x11131135, 0x2f4f3910, 0xe98768fe, 0x00821e35, 0xe9356a08, 0x373733d2, 0x3de2d233, 0xa26d3850, 0x38a2686a, + 0x631c1c37, 0x0b2b0b59, 0x757f493b, 0x0a7f7588, 0x1d14130b, 0x7541271e, 0x460341fe, 0x422f6276, 0x03141515, 0x2f160107, 0x32352524, 0x25412d2c, + 0x331c1b75, 0x1c1b3429, 0x27303371, 0x00003330, 0x35000500, 0xcb06ab00, 0x1f006103, 0x53004600, 0x66006000, 0x22250000, 0x131b4426, 0x1e37362c, + 0x07061701, 0x2e222106, 0x21822701, 0x3e373623, 0x05064601, 0x072a1482, 0x3e171632, 0x1e323303, 0x58431502, 0x35a80810, 0x12363521, 0x33113337, + 0x25152315, 0x0e231133, 0x834e0101, 0x69492696, 0x21765841, 0x3b48115b, 0x4d59594d, 0x0f124c3d, 0x3d210e3a, 0x38fc013c, 0x10113c57, 0x5523232c, + 0x2d399029, 0x1722212d, 0x04020d16, 0x251c0b01, 0x482a2030, 0x3e211d35, 0x413b3557, 0x42427541, 0xc9fea102, 0x8e2eb52d, 0xbefe5b5b, 0x8e2307db, + 0x54a9b1ac, 0x4d2e5881, 0x3930324b, 0x5d665d6a, 0x09313f6a, 0x29490923, 0x30442429, 0x80493d30, 0x1c533636, 0x28262729, 0x362d2d29, 0x23150101, + 0x371c0f1b, 0x53332f4d, 0x3e58213c, 0x3e3d0d3d, 0x2f080484, 0x4a5d834d, 0xfe4a2a01, 0xd883553a, 0xed3b6301, 0x4b000800, 0xb50608ff, 0x0c001505, + 0x1c001600, 0x5c002800, 0x72006800, 0x00009b00, 0x30113017, 0x21123544, 0xec831101, 0x21113323, 0x45018715, 0x754c06a6, 0x06b44c2d, 0x33132324, + 0x0b850313, 0x33010f2a, 0x37013e25, 0x07010e23, 0x13820282, 0x23270282, 0x1117011e, 0x4c331123, 0x332005af, 0x02821e82, 0x23113329, 0x560901b0, + 0x44582c2c, 0x01300756, 0x86f466ca, 0xe1fe8501, 0xfdfe0301, 0xe7fa1f01, 0x210cdd4b, 0xdd4b0925, 0x2e0c2207, 0x11dd4b0c, 0x09383008, 0x09e10922, + 0xc9640823, 0x04f1ca81, 0x030d030e, 0xb03b040e, 0x0402d002, 0x1f080b01, 0x16571607, 0x08155715, 0x010b081f, 0x78620105, 0x82124812, 0x8209200b, + 0x12540803, 0x62771247, 0x349f02f8, 0xfe67c233, 0x266901f1, 0x25293e29, 0x9f02bcfd, 0x025abbfd, 0x58c45a9f, 0x57035acf, 0x2b0b3539, 0x6e2c2a0b, + 0x020c2f2c, 0x2d140208, 0x605d512c, 0x290a3431, 0x2d25240a, 0x16162a34, 0x0308020b, 0x1717200a, 0x6964293f, 0x1f7c1f0b, 0x40820282, 0xe0016133, + 0x10104111, 0x90d31141, 0x13144c13, 0xb62d134b, 0x2202822d, 0x82134913, 0x63fe290b, 0x9d279f02, 0x16581627, 0x08820282, 0x0061fd2c, 0x00040000, + 0x06ab0029, 0x534703d7, 0x003a2205, 0x1a534746, 0x26114047, 0x1714011d, 0x43323316, 0x25200704, 0x080adc41, 0x20012921, 0x5d66655e, 0x2e29adb3, + 0x03ad292e, 0x2695840f, 0x57416949, 0x0f223b3b, 0x48110f3d, 0x822c4e3b, 0x3c4e3500, 0x3a0e134c, 0x3d3c220f, 0xae012d01, 0x2301bffe, 0x4101ddfe, + 0x34100547, 0x8154a9b1, 0x26272d59, 0x0822084b, 0x35353930, 0x355d665d, 0x07c64235, 0x9e020b2a, 0xc460ba60, 0x03000060, 0x2e825f51, 0xff6d0108, + 0x05930502, 0x0044001a, 0x826d0055, 0x009e2991, 0x00d500ab, 0x250000e3, 0x2c055847, 0x07062223, 0x3627012e, 0x37013e37, 0x051b4436, 0x020e0722, + 0x21098f52, 0x01820706, 0x2e22232a, 0x16372702, 0x1e171617, 0x2109d648, 0x64440135, 0x3e373706, 0x16323302, 0x32270610, 0x013d023e, 0x22022e34, + 0x0607010e, 0xa14f011d, 0x25258205, 0x36343502, 0xf2483537, 0x82322005, 0x05634a42, 0x011e1524, 0x6b821415, 0x200ce147, 0x0bee4713, 0x1e832520, + 0x82230721, 0x23028299, 0x06272622, 0x23232d82, 0x822e2722, 0x8337204f, 0x853320b0, 0x8807204c, 0x087382c9, 0x4b021655, 0x313a3c3e, 0x0c174330, + 0x130f0c30, 0x20213314, 0x3a502f2b, 0x240a0a21, 0x191d1a2f, 0x1613131a, 0x2d1f1f22, 0x472f362c, 0x500f2a36, 0x120e0e0c, 0x3e1c2e11, 0x473f4541, + 0x5c3e9802, 0x0f0f0e3c, 0x3e5c3c0e, 0x7d76767d, 0x0e1e3123, 0x45311e0e, 0x82071f31, 0x0fb30800, 0xf8fd3110, 0x1f3d593b, 0x3e343c4a, 0x6853371e, + 0x0f382929, 0x3d343f0f, 0x3c10104a, 0x423c3a5a, 0x42427942, 0x3b3b373d, 0x033a3a6f, 0x45161652, 0x38902956, 0x2d21225b, 0x0105010d, 0x260e0e0a, + 0x242a2030, 0x101d3524, 0x561f1f11, 0x2c2b3836, 0xec10113c, 0x3a42413b, 0x9941413b, 0x2e072935, 0x0a262c31, 0x15160a2a, 0x090a1d14, 0x28412d17, + 0x28191a20, 0x06040619, 0x14150d0d, 0x482c2539, 0x0e0e191a, 0x172c2114, 0x1111143e, 0x3a0d0c0c, 0x36340734, 0x2f6afe5c, 0x53404058, 0x59404152, + 0xb8feb72f, 0x331c5cb6, 0x2c752c49, 0x1c1c3448, 0x82242434, 0x252e0809, 0x1c1a1a23, 0x341d0603, 0x51432a47, 0x4e130711, 0x2d40263a, 0x2d0c0d19, + 0x3a262020, 0x1107134e, 0x232a4351, 0x571d3424, 0x32133238, 0x04823737, 0x3a013827, 0x2e102e30, 0x08048430, 0x41491f2a, 0x1b556b40, 0x28284e29, + 0x0101365a, 0x1b121214, 0x360f0e0f, 0x2a32304c, 0x221e1d2b, 0x30441312, 0x3c3f9330, 0x3e3e3c0e, 0x3f210482, 0x37008200, 0x00780004, 0x038806b7, + 0x000f0056, 0x00370027, 0x01000040, 0x23301130, 0x352a0384, 0x15302130, 0x35301330, 0x0f8a3330, 0x33201b83, 0x21221782, 0x27822730, 0x3c0c8154, + 0x3307010e, 0xac01012e, 0xfc01c86c, 0x015b5b53, 0x025c5c24, 0x3afb3c2b, 0x2699266f, 0x08038288, 0x05d7fe43, 0xc6104110, 0xf5024010, 0x3e02c2fd, + 0xc2fd6060, 0x58ee0158, 0x5812fe58, 0x0170b5b5, 0xfe7070be, 0x3a027042, 0x3131c531, 0x050000c5, 0xab000800, 0x6103f806, 0x3e003400, 0x50004600, + 0x00005800, 0x329f4b37, 0x0723272a, 0x16331323, 0x23011712, 0x1190a186, 0x55fd4208, 0x310d2a76, 0x2b2b230d, 0x2e414037, 0x0a280a3a, 0x3c226262, + 0x384c3656, 0x320d2738, 0x374b1a0d, 0x341f203a, 0x5d653c36, 0x025a3e21, 0x3bfa3dd0, 0x2689e46e, 0xd7fe2698, 0x10401104, 0x034010c5, 0x20139128, + 0x2c824bac, 0xb5b50b24, 0x00419e02, 0xf7fd210a, 0x0020108e, 0x03340082, 0xab006000, 0x6103a006, 0x2b001500, 0x00004b00, 0x30033025, 0x240eaa46, + 0x21070206, 0x13b14135, 0x87480520, 0x3c012d1d, 0x471270db, 0x0b2b0a12, 0x0b2b0b04, 0x6d250982, 0x01259525, 0x05c7414b, 0x83840126, 0x684a2696, + 0x28146148, 0x379e02b7, 0x9b2737dd, 0x82028227, 0x41c38308, 0x0b2007d6, 0x82174148, 0x00042acb, 0x06b70047, 0x005603b9, 0x22cd820b, 0x46290021, + 0x1b46183f, 0x1101280a, 0x35231123, 0x4d481521, 0x2928074c, 0xad292e2e, 0xad011402, 0x25070246, 0xc86c7b01, 0x0846fc01, 0x45c1200f, 0x022106ea, + 0x0579423e, 0x06207f82, 0x3408934f, 0x0027001e, 0x004e003d, 0x007e0058, 0x27220500, 0x37343526, 0x25018436, 0x020e3330, 0x944f0607, 0x4f172005, + 0x22240895, 0x0114011d, 0x5606944f, 0x06200a12, 0x212d964f, 0x974f010e, 0x7a033f12, 0x1037366b, 0x271d1d0f, 0x3b8a3128, 0x12123a55, 0x47110c0a, + 0x2f305131, 0xd96d6a6e, 0x924f09fe, 0x13042606, 0x12050c05, 0x12944f05, 0x144d1422, 0x2217944f, 0x4f0e3a0e, 0xfd350e95, 0x3c7f4242, 0x38363d3d, + 0x2d232f30, 0x2a2b5052, 0x35312f33, 0x13904f35, 0x1a691a22, 0x924f0282, 0xb62e2211, 0x15924f2e, 0x08200822, 0x3c0d934f, 0x17010600, 0xe90508ff, + 0x0b001505, 0x2d001700, 0x55004b00, 0x00005f00, 0x30113005, 0x22038233, 0x84153021, 0x500b8707, 0x353305c6, 0x3e373634, 0x17163201, 0x0614011e, + 0x27010e07, 0x82373632, 0x013d2b01, 0x27262734, 0x2223012e, 0x01820706, 0x2107bf46, 0xd14f1105, 0x6ccc2412, 0x82631101, 0xfd220803, 0x266a4210, + 0x26292926, 0x6b836a26, 0x2a2a2625, 0x416b2526, 0x16174026, 0x0d0c0c0d, 0x26401716, 0x0b873f28, 0xf4013f22, 0x250c8348, 0xfd9f02f8, 0x048460c1, + 0x2d57032b, 0x55822b2c, 0x2c2b8254, 0x2009832d, 0x2d0883a9, 0x19191b61, 0x692e2525, 0x1925252e, 0x0c8c1b19, 0x9f02562f, 0xfe6eb86e, 0x2c6a01f5, + 0x2c282e28, 0x36f08200, 0xab000a00, 0x6103f606, 0x30001200, 0x44003a00, 0x00004a00, 0x502e2225, 0x494f0640, 0x2fe6af07, 0x21113311, 0x422c0115, + 0x29294c6a, 0x41426a4c, 0xe09b0785, 0x49665d21, 0x22080864, 0x016d1302, 0x572dac10, 0x82545482, 0x582d2d58, 0x5782a882, 0x1a1b602d, 0x2e242519, + 0x25242e6a, 0x8c1b1a19, 0x5055200c, 0xc1280e6d, 0xc2fd9e02, 0x04000060, 0xab2ccb82, 0x6103b606, 0x22001300, 0x66002d00, 0x2306d343, 0x21301530, + 0x11260388, 0x33300130, 0xe8831732, 0x06070124, 0xba52012b, 0x26272405, 0x5811012b, 0x7e51064d, 0x26272407, 0x82012e27, 0x34352202, 0x0d805137, + 0x14150623, 0x06805117, 0x06072408, 0x4a010e07, 0xcafea301, 0xedfe1301, 0x41edc301, 0x294a3434, 0x34344a29, 0x4beded41, 0x4b2e2e5c, 0x45dc0280, + 0x242705de, 0x40372b2a, 0x45171741, 0x112405df, 0x35563d11, 0x0807e045, 0x3a384a2b, 0x361a1a3f, 0x115d653c, 0x59201f10, 0x609e02b7, 0xdcfe60ba, + 0x15159e02, 0x7da87d54, 0x60151554, 0x5b6c5b5e, 0x22fe2f2f, 0x0ad6456b, 0x51171721, 0x2421075a, 0x095b5124, 0x2c2f2908, 0x0b15142a, 0x515c130c, + 0x1b26272f, 0x00001d1c, 0x008f0004, 0x03710663, 0x001500a9, 0x0039002f, 0x25000043, 0x33300130, 0x5a06ec50, 0x06280653, 0x21010702, 0x14151632, + 0x840bd449, 0x212322df, 0x08a25237, 0x2e09064f, 0xedfea201, 0x1659168d, 0x050b360f, 0x820b380f, 0x2f893709, 0xc2012fb9, 0x77687301, 0x1934291b, + 0x1a1f1e19, 0x1110241b, 0xf54e1d1c, 0x3c3c2106, 0x3407f34e, 0x45460363, 0x3645ecfe, 0xc83729c5, 0x14014525, 0xd2fd8c45, 0x05e44e8c, 0x03142a36, + 0x0b0b0107, 0x35491817, 0x202d2c32, 0x37752521, 0x37342933, 0x3409e34e, 0x08ff3201, 0x1405ce05, 0x33002600, 0x8a007800, 0x00009b00, 0x2ec44e05, + 0xd14a0120, 0x012e3241, 0x23112327, 0x1e133311, 0x11331701, 0x21231133, 0x26048235, 0x2607020e, 0x82373627, 0x15300810, 0x57387002, 0x2c10103d, + 0x2a552322, 0x2d2d3990, 0x17172122, 0x0105010c, 0x31251c0a, 0x35472b1f, 0x573d221e, 0x42423a35, 0x02414175, 0x393b3f44, 0x2108884a, 0x884a3413, + 0x0a0b2206, 0x07884a23, 0x1f233c08, 0x362c2c1f, 0x2937472f, 0x0e0d4f0f, 0x2d12110e, 0x45413e1d, 0xb2fd4740, 0x030e3408, 0x0edb7968, 0x67030834, + 0xbc430179, 0x3e341708, 0x55400212, 0xf8a3a556, 0x30304425, 0x4f81493c, 0x47080d28, 0x0f1b2414, 0x2f4d361d, 0x223c5333, 0x0d3d3e59, 0x3c3f3f3c, + 0x013e3d0d, 0x0729353d, 0x262b312e, 0x160a2a0a, 0x091e1414, 0x402d1709, 0x1a1a2028, 0x05061927, 0x140e0c06, 0x2c253815, 0x0e1a1a48, 0x2b21150e, + 0x10153f17, 0x0806594a, 0x5c363335, 0x63104a03, 0x0200fe1b, 0x1b8dfe9e, 0x01021063, 0x015a62fd, 0x433818f5, 0x5c3a0213, 0x5abcfd5d, 0x1b000300, + 0xe506ab00, 0x39006103, 0x7f005c00, 0x49250000, 0x68430d47, 0x27262205, 0x10694326, 0x260aea54, 0x0e070607, 0x47222101, 0x3e23051d, 0x51323301, + 0x36200fd0, 0xa2055b56, 0x10012722, 0x0d2a7754, 0x70490c32, 0x06914305, 0x1131312c, 0x36573c11, 0x2738374c, 0x7349330c, 0x1123080b, 0x5a1f1f10, + 0x95842902, 0x25241313, 0x76574268, 0x0f3d0f22, 0x4e3a4812, 0x3c4e5858, 0x3a0f124c, 0x9e79220f, 0x0b8b491e, 0x2306b443, 0x2c512e2e, 0x230bb543, + 0x2a2d1717, 0x2d061155, 0x1c1b2627, 0x54a9b11d, 0x2c2c4041, 0x3b4e4d2e, 0x0a005105, 0x1a9a5220, 0x05000028, 0xab004f00, 0x2b55b106, 0x40002109, + 0x22342b55, 0x82352101, 0x21280802, 0x07020615, 0x3d540121, 0x23222260, 0x60222223, 0x24446079, 0x3c604424, 0x904e4e48, 0xde014f4f, 0x2b570901, + 0xa357572c, 0x2e058957, 0x41fe6003, 0xcdfe4201, 0xd636a601, 0x544c0136, 0x2a082bd9, 0x015bbcfd, 0x525b59ea, 0x0052bafe, 0xff2e0007, 0x05d20608, + 0x001c0015, 0x00470033, 0x005d0053, 0x00910073, 0x07063700, 0x4c301530, 0x11320503, 0x37363330, 0x33303736, 0x12160706, 0x012e2317, 0xe5451101, + 0x0cf1450a, 0x30272623, 0x06294933, 0xcf823320, 0x01301122, 0x21187d57, 0xf347012e, 0xf736082d, 0x6d6d1a42, 0x5a184804, 0xae417f5a, 0x8316ba32, + 0xa5019227, 0xc0fead01, 0xddfe2301, 0x22014001, 0x1b7c7374, 0x1b031b6d, 0x18781b6c, 0x4afb2da4, 0x655e2001, 0x5d505e65, 0x48172007, 0x23082914, + 0xd01e4b41, 0xacfe9f02, 0x6d6d1e5c, 0xfe4bce4d, 0xe13d21e8, 0x9f02e2fe, 0xc460bb60, 0xcc080160, 0x32c732cb, 0x29270282, 0xfe4ee2fe, 0x476203f6, + 0xfd210df8, 0x2d3748b5, 0x62000021, 0x05358f83, 0x08ffca00, 0x15053606, 0x17000f00, 0x66003300, 0x00009200, 0x11054e05, 0x3007062a, 0x2e052633, + 0x30232701, 0x2605494e, 0x1e133033, 0x49331701, 0x232106e7, 0x10635930, 0x59272621, 0xad5a0a62, 0x0d615905, 0x27022623, 0x21428233, 0x8e531716, + 0x069c5307, 0x37363727, 0x07020633, 0x2f6c8223, 0x06232726, 0x07010e07, 0xfb3df802, 0x98266e3a, 0x3407704e, 0x30c63130, 0x340e1902, 0x79680208, + 0x0e3408db, 0xfc796703, 0x057c445b, 0x35050e48, 0x1e1e3a2e, 0x3d226262, 0x374c3656, 0x49032738, 0x3a384a1a, 0xeb4d201f, 0x015e0807, 0x1a6a1a90, + 0x092e0d6e, 0x08031718, 0x360b0a24, 0x360e790f, 0x0923090a, 0x23233103, 0x1b6e1c6b, 0x10390d7a, 0x19021919, 0x0d3b1019, 0x70b6b6f8, 0x7070bf01, + 0x027041fe, 0x9494943b, 0x0f641b34, 0x9f02fffd, 0x640f8dfe, 0xfd01021b, 0x3d570361, 0x0b2f0c34, 0x3a16162b, 0x0b2e2731, 0x94590506, 0x1b1a3506, + 0x22410232, 0x2c171728, 0x0d0a2a2a, 0x2f515c13, 0x0b1e364d, 0x3b334b83, 0x706f2ddb, 0x2b29981e, 0xdc3c3cdc, 0x2594252b, 0x83a2a1de, 0xea32235f, + 0x0082663f, 0x32ea3f37, 0x18010400, 0xe8050eff, 0x1b000f05, 0x43003a00, 0x00005700, 0x1a7d4125, 0x23302122, 0x2405fc54, 0x013e2307, 0x430a8237, + 0x0e270898, 0x011e0701, 0x43331101, 0x112007c5, 0x20067641, 0x43278233, 0x360806b5, 0x0c320ccd, 0xdb786703, 0x030c310d, 0x3f037968, 0x1b6d1b7f, + 0x1b6b1b02, 0x24912478, 0x80238b23, 0x02196619, 0x781a671a, 0x25248c24, 0x6cd9fb93, 0x01011101, 0x43269a26, 0x23080780, 0x279a2777, 0x185f1781, + 0x9e02fffd, 0x5f188efe, 0xfd000217, 0x2cb22d62, 0x392db22c, 0xdb3639e5, 0x29a72936, 0x363e0282, 0xe63936da, 0x9e022903, 0x0160c2fd, 0x0f014408, + 0x31c73243, 0x4332c731, 0xfe43f2fe, 0x008200f6, 0x36010622, 0xca20f382, 0x142ff382, 0x2a001e00, 0x46003200, 0x00005200, 0x44233005, 0x15220557, + 0xcb820614, 0x195d1720, 0x2327220f, 0x08306507, 0x21070b50, 0x3f4f0302, 0x0302220e, 0x0b704e21, 0x016da33a, 0x42685a21, 0x18611841, 0x165a1679, + 0x2e2aac85, 0x03ac292f, 0x3bfa3db6, 0x340a6642, 0xc5104010, 0x79fd4010, 0x11716e6e, 0x2b0b1148, 0x2c0b040a, 0x2409820b, 0x016f706e, 0x099c4e68, + 0x02f22008, 0x485d6c9e, 0xbc2f145f, 0x2db52d2f, 0x2e292b5b, 0xfed52b28, 0x70b6b696, 0x6f6fbf01, 0x507041fe, 0x012506fd, 0x014f0159, 0x0b554f4f, + 0xfeb1fe24, 0xae5402b1, 0x02003007, 0x6300a900, 0xa9035706, 0x33001d00, 0x41250000, 0x062005d3, 0x41058d51, 0x16210eb6, 0x050d5005, 0x08081949, + 0x30333040, 0x76033015, 0x139027a0, 0x96646404, 0x3335c218, 0x20a017bc, 0x2002207f, 0x17962081, 0x018a33be, 0x1c0aeb2d, 0x03164f41, 0xce419550, + 0xed4163cc, 0x27a7a720, 0x4f533301, 0x34242601, 0x028234d0, 0xdbfe2434, 0x71d7d74f, 0x461e7202, 0x48021754, 0x2bfd47a1, 0x1e100071, 0x348101e7, + 0xff280106, 0x05d80502, 0x000f001a, 0x00430025, 0x00740056, 0x11d3537c, 0x20335b4f, 0x2ba14e01, 0x2807a651, 0xc76d6702, 0x7301fb01, 0x07704f41, + 0x4f6b8221, 0x6b220582, 0x794f2741, 0x27272509, 0x0d161640, 0x162c0082, 0xd0fd4016, 0x294c6b41, 0x416b4c29, 0x26210787, 0x341c8741, 0x3f282641, + 0x0d0c1716, 0x16170c0d, 0x6ced023f, 0x01fc01c8, 0x05cf514c, 0x2db7fd36, 0x54822c2b, 0x2c2c8155, 0x2c2c2d2d, 0x2c82a981, 0x1a612d2b, 0x2106c04e, + 0xc04e2425, 0x25242105, 0x2406cd4e, 0x2d01031a, 0x232e8357, 0x582d2d58, 0x57202c82, 0xc24f2b8a, 0x05f94e09, 0xe8011a22, 0x36083352, 0xfe330007, + 0x05c206fd, 0x000c0009, 0x003c0016, 0x00860061, 0x5aa2009c, 0x8b6119b9, 0x14f4510f, 0x835a0120, 0x010b2106, 0xa51a7f5a, 0x14112c24, 0x35363216, + 0x14113311, 0x4d020e07, 0x11200593, 0x24063e5b, 0x2c570901, 0x08284a2b, 0x3aad022a, 0x2321215e, 0x2f212123, 0x5205b261, 0x0e390c1d, 0x69200e39, + 0x0501c302, 0x1f080b01, 0x07828208, 0x010b081f, 0x78620104, 0x058b5a13, 0x08200729, 0x77124712, 0x82c0fb62, 0x820c2022, 0x8282261d, 0x0c081e08, + 0x22228302, 0x82124912, 0x08082533, 0x48120720, 0x01332282, 0x388e3864, 0x350c0c65, 0x36577f58, 0x36020c0c, 0x5af8f466, 0x20080fc1, 0x2a2b27b1, + 0x2a83b684, 0x2514132b, 0x20084a25, 0x5e323908, 0x5e53a153, 0x21093c36, 0x014f4a08, 0x05925aa8, 0x01f0fe23, 0x17905a10, 0xa1ff0421, 0x02320823, + 0x514efe9f, 0x01514d4d, 0x445efeb2, 0x1f413232, 0x3232411f, 0xfda20144, 0xfd9f0261, 0x00005abb, 0x4d010400, 0xb30502ff, 0x17001a05, 0x8b005100, + 0x9f529d00, 0x09bb5005, 0x5009d54a, 0x414d14aa, 0x0efe4823, 0x20297a4d, 0x10f14e01, 0x46850121, 0x0138095a, 0x2a76544b, 0x240c320c, 0x40362c2a, + 0x3b171742, 0x610a280a, 0x11103131, 0x2306d948, 0x1a0c330c, 0x29065362, 0x105d653b, 0x5a1f2010, 0x32848bfd, 0x2b230d23, 0x2e32862b, 0x31620a27, + 0x3c111131, 0x384c3656, 0x890d2837, 0x643c2132, 0x1f2b3282, 0xe2015b1f, 0x020d310c, 0x82db7968, 0x67032306, 0x9246f279, 0x3d0b3206, 0x0c2e0c34, + 0x3916162b, 0x17182731, 0x0207020b, 0x0d5e4d12, 0x17282225, 0x4d2b2c17, 0x03210b5e, 0x2133b162, 0x3c487e01, 0x00002f0f, 0xcf000200, 0x31065400, + 0x3400b803, 0xd94e4a00, 0x6a262010, 0x805c0566, 0x095e4a08, 0x22074052, 0x4301020e, 0x87080d35, 0x35022e27, 0x6a020211, 0x3f0f3494, 0x36352d0f, + 0x3a525044, 0x7b252549, 0x4c2a3d3d, 0x465e446b, 0x045b3146, 0x47465d21, 0x20212828, 0x747e4b44, 0x01704e29, 0x51c552f9, 0x4b111085, 0x3b3bae76, + 0x4d55214b, 0x0f3a0f40, 0x481b1c36, 0x0e3a313d, 0x3a170607, 0x59386539, 0x2121213e, 0x2a03513e, 0x381c1d32, 0x0d1a1a34, 0x65731810, 0x2544613a, + 0xfcfd5403, 0x6b6c6c6b, 0x11fe0402, 0x5744435c, 0x5816152b, 0xef015c86, 0x2d10db82, 0x393d0773, 0x0608ff0a, 0x001505f6, 0x0018000e, 0x0049002b, + 0x005d0053, 0x01000063, 0x505c3330, 0x5a2b2009, 0x2623067e, 0x4c11012b, 0x6b5c2ce2, 0x33113a14, 0x02152111, 0x6841ed76, 0x29292525, 0x41682525, + 0x5c4beded, 0xfe804b5c, 0x3a985b49, 0x2ba7013c, 0xa77e2a29, 0x2b292a7e, 0x6b5b5f60, 0x21fe5f5b, 0x572df702, 0x82545582, 0x07822d57, 0x0682a920, + 0x191b6129, 0x2f242519, 0x4c242f69, 0x0c8807bf, 0x2e0e825c, 0x9f02c0fd, 0x0061c2fd, 0x00030005, 0x56fd06ab, 0x59200aa7, 0x2034a756, 0x24bd4c05, + 0xc0560820, 0x0dae6b11, 0xc056a420, 0x4c962006, 0x07210574, 0x07744c83, 0x00676120, 0x2bd4560f, 0x4c13a73a, 0x124b1313, 0x1101effe, 0x13124a12, + 0x63fe134a, 0x9c279e02, 0x16591627, 0x08820282, 0x0062fd22, 0x03310082, 0xab002200, 0x6103de06, 0x33001b00, 0x00006c00, 0x1c415313, 0x4c15e756, + 0x2e211411, 0x10525901, 0x200fbc5c, 0x054d53d7, 0x320cdb28, 0x7968030c, 0xbc564001, 0x5c4c200a, 0x02212fb8, 0x0a214b2a, 0x01021824, 0xa14b62fd, + 0x080f6e09, 0x0827b55c, 0x07000025, 0x02ff7c01, 0x1a058405, 0x5b003700, 0x75006800, 0xa9009c00, 0x0000d500, 0x0e213001, 0x36330701, 0x66023e37, + 0x012e0788, 0x22230607, 0x27012e27, 0x16372726, 0x17821e17, 0x22051579, 0x82070622, 0x013e2314, 0x5e662137, 0x07bf663a, 0x220d246b, 0x6b33011e, + 0x21211724, 0x60718235, 0x26240529, 0x020e2223, 0x3c828383, 0x16320223, 0x79018217, 0xc28206c9, 0x03213708, 0x03d7fe26, 0x0b06020c, 0x2b200d0d, + 0x364b2b1c, 0x3d10101f, 0x2d392c2d, 0x14342323, 0x0c4f1013, 0x2b230d0e, 0x41413b1c, 0x12342b3b, 0x040f3c0f, 0x7c010411, 0x5d666901, 0x3b3b3724, + 0x583717fe, 0x2c11103c, 0x29552323, 0x2d2e3890, 0x17162221, 0x7d6b010c, 0x221e2109, 0x08067d6b, 0x03414159, 0x2339fe50, 0x1b33248f, 0x1c323a1b, + 0x06141f29, 0x0b103f10, 0x4c381212, 0x1c1b5066, 0x2d190f0e, 0x1a201e1d, 0x52011a69, 0x9e274e01, 0x10101428, 0x361c0c17, 0x2a32314e, 0x10113d2a, + 0x16210a0a, 0x143e1815, 0x0d191110, 0x3a0b3a41, 0x02142141, 0xf93e0209, 0x6657fd3e, 0x0121374e, 0x0be66bd1, 0x2827262d, 0x352d2e28, 0x23140101, + 0x6b1d101b, 0x3f0812e6, 0x207f2068, 0x2e2a2a2e, 0x1138310c, 0x0616271c, 0x1d200619, 0x1e1b2e1d, 0x25241b1a, 0x3e45282b, 0x171c1c1c, 0x0000175a, + 0x7a010600, 0x860502ff, 0x28001a05, 0x6c003600, 0x9f009200, 0x0000ca00, 0x680c4068, 0x2522263f, 0x83420621, 0x013e2418, 0x5f031e37, 0x38690634, + 0x21132207, 0x14a75e25, 0x22196b6d, 0x61363521, 0x45420688, 0x6852201e, 0x39350545, 0x2c22225a, 0x0a04030d, 0x30260e0e, 0x24242b1f, 0x11111e34, + 0x0544681e, 0x103d4508, 0x413bec10, 0x423a3b41, 0xfe340342, 0x070c05d6, 0x210c0d0b, 0x4a2b1d2a, 0x0f101f36, 0x392c2d3e, 0x3523222d, 0x0e101413, + 0x1b0d0d34, 0x3c1c2b22, 0x2b3c4040, 0x3c0e1234, 0x0112070f, 0x3810fd7c, 0x11103c58, 0x552c2382, 0x2d389029, 0x1622212e, 0x03030c17, 0x5e07c76d, + 0x412205cd, 0x49427541, 0x6b6b2505, 0x3b1c1b33, 0x21054842, 0x4842400f, 0x1b1c2e07, 0x2c190f0e, 0x1b201d1e, 0x52011a69, 0x2d3068b3, 0xa449f222, + 0x24147542, 0x140a2a0a, 0x0b764221, 0x01017423, 0x2d3b42b9, 0x42605f21, 0x0038243a, 0xff390006, 0x05c70608, 0x000f0015, 0x0028001c, 0x00570032, + 0x0500007a, 0x3407d158, 0x33301330, 0x03171216, 0x23302726, 0x07300706, 0x012e3330, 0x15536e01, 0x750f9953, 0x73531325, 0x17162514, 0x3317011e, + 0x3c058844, 0x1e042311, 0xe206240a, 0x640a2406, 0x872182c9, 0x0b0af121, 0x3c0a0b0c, 0xfc290bb1, 0x0d306e9b, 0xad023008, 0x21215d3b, 0x21212424, + 0x553b2e2f, 0x0d1f3433, 0x343f114a, 0x444c4c44, 0x0e124335, 0x6a1f0e38, 0x0501c402, 0x21090c01, 0x03828204, 0x820c0922, 0x78623e0c, 0x21093736, + 0x17180805, 0x77155205, 0x7f23f862, 0x237f1818, 0xfe709f02, 0xe0017041, 0x24008231, 0x018c24d3, 0x1d4e53a5, 0x53290721, 0x163f124d, 0xf0fe0952, + 0x51081001, 0x124b1416, 0x9f0263fe, 0x5d197576, 0x0b42420e, 0x61fd2fb1, 0x41070000, 0x162c0867, 0x2a002000, 0x3f003500, 0x87006400, 0x2308077f, + 0x32150706, 0x20061c6c, 0x05a5710e, 0x013d3623, 0x0ca37134, 0xff730120, 0x05785613, 0x411e9b76, 0x22082275, 0x10019802, 0x3437504e, 0x2218171d, + 0x2816090a, 0xd5fe2239, 0x2625a766, 0x97a72526, 0x21252521, 0x413bfd97, 0x013f507d, 0x415459a7, 0x0b0c0548, 0x1c1c280b, 0x33472b21, 0x2b275a1c, + 0x57272c2c, 0x292b2824, 0x411d0123, 0x00345185, 0x27010500, 0xd90502ff, 0x2a001a05, 0x3c003400, 0x7d007100, 0x59731482, 0x023e2409, 0x76163233, + 0xe76a1971, 0x0e7b5410, 0x65085e65, 0x7c5d0d5d, 0x0a0c5b0d, 0x76f40221, 0x02261f86, 0x3afb3c73, 0xed6be56f, 0x11402607, 0xfd4010c6, 0x12ea76b6, + 0x26135d6b, 0x3f215e64, 0x542e015a, 0x8c2009d3, 0x21079676, 0x96762d59, 0x08652e08, 0x2f101008, 0xfe5d341f, 0x02b6b69b, 0x0b345b9e, 0x51544e20, + 0x0b2f220a, 0x0ef37602, 0x25098254, 0x4d2f515c, 0xfd6f1d37, 0x00003d09, 0xffce0102, 0x04320516, 0x0021000e, 0x0500003b, 0x27222330, 0x35303526, + 0x2627012e, 0xb0420882, 0x17322f05, 0x011e1716, 0x07021415, 0x32273315, 0x9874013e, 0x022e2205, 0x08436f23, 0x021e2808, 0x558e7004, 0x88552728, + 0x191b2f2f, 0x5039383e, 0x5050c450, 0xbb3e3839, 0x3af09aa0, 0x13124460, 0x60441213, 0x855f3b3a, 0xea5f3109, 0x4c492b2c, 0x41414d0b, 0x7f735c5b, + 0x224142c2, 0x41340082, 0xe17fc242, 0x671cfcfe, 0x374c28f2, 0x459e4537, 0x284c3737, 0x00290a8a, 0x00130003, 0x03ed06b7, 0x22078256, 0x6751003d, + 0x03200561, 0x4b0f5f61, 0x03220694, 0x53580330, 0x23302307, 0x2b6c1130, 0x2311230f, 0x01822130, 0x01303522, 0x23080584, 0x06153021, 0x30210702, + 0x1772cfe3, 0x170d175d, 0x2270175b, 0x0903228a, 0x0c010501, 0x82081e08, 0x071f0882, 0x36059744, 0x07124912, 0x08080820, 0x4812081f, 0x02627712, + 0x0141fe9a, 0x62cefe41, 0xb7320676, 0x9b010301, 0x3131c832, 0xfe4432c8, 0xfdfe45ee, 0x874b9d01, 0x097d6221, 0x04000028, 0xab004c00, 0xe382b406, + 0x1e001426, 0x5f002800, 0xdb5de583, 0x07a6421d, 0x4a011121, 0xb9203299, 0x2106e45d, 0xe45d6019, 0x2a2e2408, 0x681302ac, 0x03270756, 0x03d7fe58, + 0x4a07030c, 0x13212324, 0x06244a0e, 0xec5db720, 0x2e28270e, 0xfed52b29, 0x5a680296, 0x28402a06, 0x1014279e, 0x1d0c1710, 0x0fc54935, 0x49101121, + 0x00200ec5, 0x04300082, 0x02ff7301, 0x1a058d05, 0x7d004c00, 0xe000b400, 0x20171772, 0x06e24a17, 0x84060721, 0x1e152201, 0x05f24a01, 0x06220d84, + 0x11572223, 0x37272105, 0x22721782, 0x0a324b0c, 0x6b262721, 0x364b0659, 0x37012306, 0x55863236, 0x0e235485, 0x41210701, 0x01203368, 0x5149648a, + 0x5002211d, 0x08119267, 0x1d282862, 0x0a11101d, 0x1812120a, 0x331d1a17, 0x0b0b1313, 0x1f201111, 0x2f362c2c, 0x1c1b2423, 0x4f0f1514, 0x12111c0d, + 0x423d1d2d, 0x03474045, 0x2439fe80, 0x1c33238f, 0x321e1d1b, 0x0f0f2a1b, 0x10050a0b, 0x110b0f40, 0x26263713, 0x1c1b5166, 0x16190e0e, 0x201e1d16, + 0x011b691a, 0xfea7fd52, 0x030b03d6, 0x35089349, 0x100f1e37, 0x392d2c3e, 0x3423222d, 0x4e0f1414, 0x220e0d0d, 0x92491b2c, 0x3b0f2106, 0x2405074c, + 0x38fe5002, 0x07b34b24, 0x4b1e2a21, 0x4b2008b3, 0x18205e85, 0x7208b34b, 0x27081035, 0x16170b0c, 0x20291f21, 0x1414191a, 0x04060d0c, 0x14151a06, + 0x2c251c1d, 0x191a2424, 0x0a0a0e0e, 0x16151111, 0x22143e17, 0x20093d72, 0x09614b75, 0x111c1c26, 0x13140e0e, 0x2107644b, 0x654b0e0d, 0x44292305, + 0x664b1f1f, 0x46052105, 0x202b224c, 0x28bb4b62, 0x274f0420, 0x00562806, 0x002e000f, 0x5e8f0337, 0x3f6f135b, 0x26052624, 0x1d332702, 0x35461801, + 0x8a451822, 0xa638c915, 0x1826a65f, 0x20194a46, 0x3d461815, 0x37012215, 0x7745183d, 0x18149414, 0x9111b345, 0xa638a611, 0x215fcc26, 0x46183335, + 0x0128103f, 0x01b2663a, 0x6b0601c9, 0x2105f670, 0xf6702728, 0xeb012312, 0x46185f18, 0x012d4f03, 0x0c040e03, 0x0e040e04, 0x0e7c0e38, 0x18078239, + 0x28520446, 0x62196401, 0x390f7a18, 0x3a6b820f, 0x030e040b, 0x020f3d0f, 0x02bbfdfc, 0xfd595945, 0x7f4242b0, 0x373c3d3c, 0x71302f38, 0x3021077a, + 0x0a7a7134, 0x45104a20, 0x2f5503da, 0x05000000, 0xab004700, 0x6103b906, 0x2f001e00, 0x58280b82, 0x00007000, 0x07010e01, 0x7a0ad76c, 0x332005c6, + 0xd96c1482, 0x243e7b06, 0x25a40520, 0x0f100139, 0x6c6c103d, 0x10401004, 0x7e1e791e, 0x2b289f28, 0x22832bad, 0x7a630288, 0x062718d2, 0x100f0706, + 0x9e520231, 0x01220820, 0x124612f0, 0xfe9e02cf, 0x145115ac, 0x2f249125, 0xfe412fbc, 0xd13441fe, 0x592ef0fe, 0x52534040, 0x797a4140, 0x341b3506, + 0x2c762b49, 0x1b1b3448, 0x2c242434, 0x23252b76, 0x5c1b1a1b, 0x002224a3, 0x934e0600, 0x002f3a08, 0x00630042, 0x0098008d, 0x050000a0, 0x35302130, + 0x37363730, 0x35303536, 0x0adb4934, 0x3e37362a, 0x011e3201, 0x14151617, 0x2505814a, 0x07010e07, 0x2a560521, 0x34352105, 0x37211d82, 0x06937c36, + 0x554d3d83, 0x27262105, 0x22200182, 0xdd752f85, 0x17162305, 0x42180116, 0xf74e0851, 0x79122022, 0x03240cf2, 0xd639fe45, 0x55091153, 0x1c2c055a, + 0x50664c1c, 0x0d0f0e37, 0x1d17160c, 0x26061353, 0x2e3f4901, 0x820e3c2e, 0x3c230800, 0x7c3f2e2e, 0x227c7676, 0x0f101819, 0x07060607, 0x1918100f, + 0x0f191845, 0x07070610, 0x190f1006, 0x4f89fe18, 0x7426200e, 0x6e3afb3d, 0x0f4f7272, 0x68f22c0c, 0x292a2ebf, 0x38310c2f, 0x49261d11, 0x172a06c3, + 0x351e1b17, 0x282b2524, 0xc3492223, 0x17672207, 0x072e7c18, 0xb7181733, 0x5cb6b8fe, 0x1a1a0e0e, 0x752d2423, 0x1a24242c, 0x29a1821a, 0x231b1a0e, + 0x2d752c24, 0x0e832324, 0x4f770321, 0x01231f2d, 0x4f4f014f, 0x20080a2f, 0x06000000, 0x68fe0b01, 0x0f05f505, 0x39001f00, 0x4b004100, 0x6f005500, + 0x30010000, 0x3d262223, 0x14fd4e01, 0xfd4e0620, 0x07f6671a, 0x87770120, 0x18052012, 0x080d6541, 0x1123112e, 0x36331333, 0x23113337, 0x395fe502, + 0x1f5b3835, 0x2a121120, 0x35362526, 0x26353682, 0x6b7c2926, 0x4027a167, 0x0c0c0d2d, 0x27402d0d, 0x0c2d0982, 0x2d0c0d0d, 0x6ded0240, 0xfbfb01c7, + 0x0cff793b, 0x0515042b, 0x19092309, 0x62181862, 0x39088219, 0xb4806705, 0x7b595a06, 0x3a68fe67, 0x34073231, 0x3e3d2b2b, 0x2c81554c, 0x0082172b, + 0x812c2b3d, 0x13ad9655, 0x331aa245, 0x6a2d2525, 0x3324252e, 0x24331b1b, 0x2d6a2e25, 0x67332525, 0x012108ec, 0x09e27624, 0x282e292d, 0x49123e2b, + 0x2db32c12, 0x822cb32d, 0xfffd3908, 0xabfe9e02, 0x62fdaaab, 0x02000000, 0x54008500, 0xb8037b06, 0x4d003400, 0x41357f64, 0x3d08131a, 0x23113313, + 0x9469b701, 0x103e1035, 0x4435362c, 0x49395151, 0x3d7a2526, 0x6c4c2a3e, 0x45465f43, 0x21035b31, 0x2848465d, 0x44212027, 0x29757e4a, 0xfa03714e, + 0x0b2c0b06, 0x24238113, 0x08821381, 0xa1820627, 0x9ae007e1, 0x2d8c6482, 0x178f022a, 0xec22175a, 0x22ec4040, 0xfd380882, 0xfe46037f, 0xfcaa0156, + 0x030000ba, 0x6300b500, 0xa9034b06, 0x15000b00, 0x2119ad7b, 0xd5760109, 0x01b63c11, 0x7f7f7468, 0x34d8e174, 0xd8343939, 0xedfe5a03, 0x1758178c, + 0x050f370a, 0x820a380f, 0x2e883409, 0x03632eba, 0x89e68946, 0xc501b2fe, 0x323a3236, 0x7631fd36, 0x252205a1, 0xa17637c8, 0x8200200a, 0x071b5000, + 0xbf456120, 0x457e2006, 0xce4e46bd, 0x15210834, 0x6d0f3d10, 0x4010036d, 0x1e781f10, 0x27a0287f, 0x832bac2b, 0x63028722, 0x0f3b5c3f, 0x3b0f0e0e, + 0x0543445c, 0x0d1f3128, 0x45311f0d, 0x3b441f31, 0x03312205, 0x3250501a, 0x213edf45, 0x7150ef01, 0x01053a2c, 0x0502ff5b, 0x001a05a5, 0x003e0034, + 0x00810048, 0x050000a2, 0x36272622, 0x079f7937, 0x25103f54, 0x012e0702, 0x3f542223, 0x654b180f, 0x69458914, 0x59762901, 0x0eb1570d, 0x0e17162a, + 0x55500201, 0x26252a76, 0x2314c77f, 0x091e1a0b, 0x210dc87f, 0x45442d01, 0xacb33505, 0x2a2e2e2a, 0x542cfeac, 0x25262b76, 0x372b2a24, 0x16184240, + 0x21114069, 0x4069190d, 0x0574760e, 0x24252108, 0x75584169, 0x112e2d22, 0x584e3b48, 0x4b3d4e58, 0x224d0a13, 0x343dfd79, 0x162b2323, 0x27313916, + 0x230e8c54, 0x081b160a, 0x18128d54, 0x2507bf4b, 0x2b282e29, 0x3d891801, 0x08265069, 0x55a9b12a, 0x2c2d4040, 0x194b4d2d, 0x6a393019, 0x695d665d, + 0x2f06313e, 0x00005249, 0x2e000400, 0xd206b700, 0x0f005603, 0x36002700, 0x15e74118, 0x2015d55e, 0x182d6113, 0xc86c6225, 0x5570fc01, 0x832b09ad, + 0x256940ed, 0x25282825, 0x60406925, 0x812005f6, 0x09e84118, 0xbb700220, 0x9e022705, 0x7d2a2a2a, 0x05827da8, 0x5b5e6027, 0xfe5e5b6c, 0x2d9e8222, + 0x4b010600, 0xb50502ff, 0x15001a05, 0xab821e00, 0x6e004930, 0x0000d801, 0x30353005, 0x36353021, 0x3d743712, 0x30152e05, 0x30153023, 0x30333025, + 0x0e233011, 0x07145e01, 0x44183e20, 0x012008d3, 0x23073d48, 0x0622012e, 0x2407197e, 0x2201021e, 0x08006a26, 0x2206816d, 0x181d0607, 0x2b0e8146, + 0x23113335, 0x0f15010f, 0x0d0f150e, 0x08820584, 0x05270b91, 0x3727012e, 0x840b3f35, 0x840a2002, 0x200b8805, 0x3c179306, 0x33113307, 0xfea10215, + 0x2db52dc9, 0xfe5c5c8e, 0x2307dbbf, 0x3eaf028e, 0x0f0e3c5c, 0xbd44181d, 0x82072009, 0x0f0f2600, 0x10314531, 0x3909820f, 0xfd311f07, 0x279683ee, + 0x58416849, 0x0f213b3b, 0x4811103c, 0x2d2c4d3b, 0x49182c2d, 0x2f220ad8, 0x4c1808bc, 0x2eeb2e3b, 0x0b2c0b22, 0x02206ed2, 0x012453d2, 0xf2a3a601, + 0x0ea54a18, 0x2fe2fe29, 0x52414058, 0x18588351, 0x3b07aa45, 0x752c2524, 0x1a24252b, 0x1a1c1c1a, 0x2b25241a, 0x24252c75, 0x06031c33, 0x8055a9b1, + 0x0f614818, 0x313e342c, 0x49092309, 0x5a0b2929, 0xb1d4f501, 0x0a2254d0, 0x53d00a28, 0xb6415089, 0x235f8655, 0x005abcfd, 0x35080082, 0x025c0002, + 0x05a4065f, 0x00030071, 0x1300001b, 0x01211521, 0x35173727, 0x17371533, 0x23153307, 0x15270717, 0x27073523, 0x5c352337, 0xb8f94806, 0x2a8cc204, + 0x03823c8c, 0x87c6c621, 0xa5022408, 0x8c220246, 0x261a8411, 0x00010000, 0x8225ff5c, 0x00d6225b, 0x08598214, 0x012c3238, 0x1135053e, 0x2c032e34, + 0x9d5c2302, 0x05013401, 0x7fadc7f6, 0x6f3c2e5b, 0xf8feccaa, 0xa9fee5fe, 0x33a502ad, 0x837e7256, 0x0e47667a, 0x5b154ff9, 0x8691907d, 0x478d3e68, + 0x2c220124, 0x47832e01, 0x47823e20, 0xa4063327, 0xfeccfe9d, 0x254889fb, 0x1b010801, 0x48965701, 0x0532ef82, 0x38ff2a00, 0x9405d606, 0x40000400, + 0x4b004400, 0x9b820801, 0x0721302b, 0x0e220421, 0x040e0703, 0x06eb5707, 0x3634352e, 0x1e173233, 0x033e3204, 0x37043e37, 0x16240c83, 0x14151617, + 0x2e391e83, 0x15210303, 0x23150121, 0x35232505, 0x023e3425, 0x17163233, 0x031e3236, 0x21268217, 0x05830614, 0x83323621, 0x3e172505, 0x033e3701, + 0x36204782, 0x28834084, 0x31011e22, 0x17840c86, 0x1e323323, 0x85298302, 0x1415215b, 0x26296185, 0x35012e27, 0x022e3534, 0x2e6e8327, 0x2223010e, + 0x07010e27, 0x06272206, 0x86030e07, 0x2006830c, 0x840e8202, 0x862683b0, 0x022e2103, 0x3b08ba82, 0x010e3427, 0x27010e35, 0x17262726, 0x27343516, + 0x37343706, 0x26222336, 0x46e4015c, 0xe00462fe, 0x4f506154, 0x15070f2f, 0x2c5b403c, 0xa7934b52, 0x1d08354d, 0x0f0f1b15, 0x61504f2f, 0xd4081b9a, + 0xfee401dd, 0x9610fe62, 0x00010001, 0x047afc96, 0x131a240e, 0x1f0e0b1b, 0x02080c17, 0x151b1102, 0x07060211, 0x072e3804, 0x0c0e1010, 0x03081002, + 0x0c130c06, 0x0b06090a, 0x0d133953, 0x11282c04, 0x411e180c, 0x151d0c2f, 0x1a0e040d, 0x14201324, 0x140c1e27, 0x09090a11, 0x21492f09, 0x1a13012c, + 0x04180f12, 0x210f0610, 0x100f1425, 0x171c192a, 0x09122410, 0x2e0e0d21, 0x133b1a13, 0x06070718, 0x2909380d, 0x1a141b0e, 0x2f340d11, 0x10080712, + 0x0b0a1119, 0x10101016, 0x180f171c, 0x221a0c22, 0x0d161108, 0x17160103, 0x1a032706, 0x10080102, 0x02010301, 0x1a120708, 0xbb6eb902, 0x3c52422f, + 0x4a1d0917, 0x2d184b42, 0x0d4d4ea7, 0x161d150f, 0x42523c17, 0x0818972f, 0x6e580190, 0xf0fa2501, 0x0da0faf0, 0x151a291d, 0x110e071b, 0x170e111d, + 0x020b3a26, 0x05070d0c, 0x09103705, 0x0409100b, 0x0d670c08, 0x1f274104, 0x1860373b, 0x1d11070e, 0x09261712, 0x01100f15, 0x38044352, 0x01051204, + 0x17110834, 0x122d0c10, 0x0804030b, 0x070b423e, 0x24111d2f, 0x1110450b, 0x0f331c14, 0x5d090e1d, 0x180c4d0a, 0x2d410c18, 0x21161702, 0x11050817, + 0x26141b0c, 0x220e0522, 0x07170319, 0x030c0c02, 0x11020502, 0x02020204, 0x03820104, 0x1a070224, 0xb3430017, 0x49250807, 0x0f000b00, 0x15010000, + 0x15231533, 0x35233523, 0x21013533, 0x98052115, 0xb464b4b4, 0x0628fbb4, 0x05b8f948, 0x380b8349, 0x5cfdb464, 0x00050046, 0x063eff53, 0x00a7058c, + 0x00280006, 0x0030002c, 0x293d8237, 0x010b3311, 0x23271133, 0x01832707, 0x05840320, 0x35020b26, 0x1737021b, 0x13200182, 0x17290584, 0x15332133, + 0x3335022b, 0x082d8537, 0xa0a1042e, 0x36a0cece, 0x3f334ac9, 0x3a3f2f30, 0x3119373a, 0x27373610, 0x10363727, 0x3a371931, 0x302f3f3a, 0xc94a333f, + 0x8a8af201, 0x887373bc, 0x47082c83, 0xb0010802, 0xdefe2201, 0xd63a50fe, 0xcd72a93e, 0x54fcfee3, 0x92df6a8b, 0x0401d4fe, 0x0189eb01, 0xfe0401eb, + 0x6adfb7af, 0xfcfe548b, 0xa972cde3, 0x6161d63e, 0xfe50fe3a, 0x012201de, 0x030000b0, 0x37ff5c00, 0xe005a406, 0x0d22bb82, 0xb7833b00, 0x01092326, + 0x11031123, 0x01220684, 0xbf85012f, 0x0f200585, 0x0f220f84, 0xc4821f02, 0x1f013f23, 0x86c48501, 0x34032805, 0x011401c8, 0x8598c814, 0xd8023d06, + 0x604e7238, 0x5a60484b, 0x4b27545a, 0x4b455418, 0x5a364b39, 0x25253915, 0x365a1539, 0x45310c82, 0x274b1854, 0x605a5a54, 0x4e604b48, 0xfe5d0172, + 0x229484fc, 0x825d0204, 0x08d183b9, 0xc8fefc30, 0x4204381b, 0x6659512d, 0x582a3721, 0x5a708448, 0x523b4b64, 0x656c438f, 0x3b528f4a, 0x705a644b, + 0x2a584884, 0x59662137, 0x04422d51, 0x00820038, 0x4b00023b, 0xb50622ff, 0x0900d005, 0x00002a00, 0x012d1505, 0x0d352115, 0x13013501, 0x20ad8b17, + 0x24d98d3f, 0x01030703, 0x227e836d, 0x82012604, 0xfa26087a, 0x6d6e4ed3, 0x6d32621f, 0x5e7d7575, 0x94667d61, 0x66944949, 0x7d5e617d, 0x326d7575, + 0x6e6d1f62, 0xcea03e4e, 0x0383a0ce, 0x54033308, 0xeccd7efe, 0x6e54b073, 0xa1b2cc42, 0x99318459, 0x31993536, 0xb2a15984, 0x546e42cc, 0x090190b0, + 0x007efecd, 0x5e000200, 0xa2066aff, 0x0b007b05, 0x5b421f00, 0x2703300e, 0x020b2703, 0x1b371127, 0x37133702, 0x42152113, 0xfe210669, 0x877487c7, + 0xa7022186, 0x33077742, 0x9dfef0fc, 0xb2f7fe62, 0x6501befe, 0x058467fe, 0x67fe8407, 0xbe2f0982, 0x62f7feb2, 0x00749dfe, 0xff530003, 0x42af063e, + 0x2f2c0693, 0x21130000, 0x35010d35, 0x21150321, 0x42050741, 0x1b220580, 0x79823502, 0x2109a042, 0x26850307, 0x5442cb20, 0x03552207, 0x068c4245, + 0x42363b21, 0x36210b8c, 0x068c423b, 0x4b429620, 0x03042807, 0xa0cecea0, 0x4261fcfe, 0xfe212686, 0x202f839b, 0x28008200, 0xffc80004, 0x0538065e, + 0x2bb582a6, 0x001b000f, 0x2500001f, 0x03152135, 0x8b0b7f43, 0x23112e0b, 0x016c0411, 0xb4b4b4cc, 0xfcb4b464, 0x270684c0, 0xa05a3102, 0x9c046464, + 0x21059543, 0x07855cfe, 0xf90e0225, 0x824806b8, 0x01002162, 0x21058f47, 0x6382a502, 0x2113002c, 0x065c2115, 0x02b8f948, 0x1c8246a5, 0x5601053a, + 0xaa0508ff, 0x0f001505, 0x45003900, 0x8f006500, 0x35050000, 0x06231133, 0x2a05715e, 0x15331133, 0x0e071401, 0x55230702, 0x2e21058b, 0x211a8201, + 0x5018020e, 0x04201252, 0x0a865018, 0x6308e84b, 0x36280d14, 0x17011e37, 0x25060706, 0x210fce67, 0x136a2726, 0x07063210, 0x9c011521, 0x376507bc, + 0x3d0b2c0b, 0x02a3a56e, 0x05da671c, 0x215b382c, 0x020d2d22, 0x0f0a0104, 0x5018250e, 0xfe321220, 0x424275d9, 0x56fe4175, 0x49269584, 0x76574169, + 0x104e5b22, 0xaa521806, 0x025f0808, 0xcf39fee2, 0x1b1c3307, 0x2a1b323b, 0x2c05151e, 0x13120a33, 0x50664c37, 0x0e0f1b1c, 0x1e1d2c19, 0x014f4f20, + 0x015bf852, 0x0a3b6bf5, 0x77430a27, 0x015bbcfd, 0x414049a5, 0x2a1b556b, 0x5929284d, 0x14010136, 0x101a1212, 0x4c370e0f, 0x2a2b3130, 0x12211e1e, + 0x30304413, 0x0e3c3e92, 0x823f3f3c, 0xca013104, 0x8154a9b2, 0x4b4d2d58, 0x6a392f31, 0x6a5d655d, 0x2f088b4a, 0x2f07b868, 0x0c2e2a29, 0x1d103831, + 0x14111627, 0x390d9f69, 0x441c1d1b, 0x00005c43, 0x2e010400, 0xc70508ff, 0x13001505, 0x4d003500, 0x03798600, 0x30112313, 0xb5523001, 0x33302310, + 0x03861330, 0x23301122, 0x514e2182, 0x37276d15, 0x612e0121, 0xc437088e, 0x09230905, 0x18196218, 0x24091862, 0x81680508, 0x7bb406b3, 0x6d2bfc68, + 0xf8323b3a, 0xbb609f02, 0x02dcfe60, 0x12481201, 0x2c2cb32d, 0x08822db3, 0x02fffd2b, 0x01abfe9f, 0x0361fd55, 0x22208462, 0x180b61c3, 0x200ac441, + 0x05ec7817, 0x515c4508, 0x3124242c, 0x321b1a1b, 0x220b2d0b, 0x2a2c2e28, 0x0d0a1515, 0x2f515c13, 0x1b1b2726, 0x0001001e, 0x06d3006b, 0x00310495, + 0x01000044, 0x14151632, 0x07020e07, 0x2e222306, 0x27262705, 0x07222326, 0x010e0706, 0x45081083, 0x37343526, 0x063e3736, 0x051e3233, 0x16171617, + 0x3e373233, 0x3e013f01, 0x1e4c0602, 0x240b072a, 0x6e723266, 0x4442522a, 0x131b362f, 0x5d702149, 0x59464330, 0x110d1012, 0x122a1e14, 0x1c140a04, + 0x40433038, 0x218b264d, 0x48323108, 0x1615572a, 0x021f0b03, 0x111e2aca, 0x9341160f, 0x2a1e7232, 0x2e553947, 0xa8318424, 0x1e984643, 0x2a0a0815, + 0x06141c1e, 0x522e2211, 0x1d2a443a, 0x48281d89, 0x28287c2a, 0x00130e07, 0x033e0082, 0x49ff1e00, 0xcc05ce06, 0x2b000b00, 0x00004e00, 0x23061404, + 0x34262221, 0x32213336, 0xb8832225, 0x013e0127, 0x01173233, 0x82038221, 0x29de82e2, 0x22210127, 0x0e010927, 0xf3841301, 0xe6820620, 0x2f211582, + 0x252c8201, 0x1f323336, 0x08850301, 0x82131721, 0x023808e4, 0xfe151d0b, 0x1d1d159f, 0x15610115, 0x1d158afe, 0x05610102, 0x0f19111a, 0x8c018201, + 0x8d010f1c, 0x1c151d07, 0xfe82fe0f, 0xfe0f1977, 0x05bcfeaa, 0x0f0a9a1a, 0x0301011a, 0x54085482, 0x0fd20505, 0x05050a0f, 0x0f03a38b, 0xa4080e0a, + 0x700e0111, 0x2a1d1d2a, 0x151d5d1d, 0xa6040707, 0xfd151410, 0x80fd18ef, 0x1d150e0c, 0x15680218, 0xbbfbd401, 0x4a051410, 0xe1010a0f, 0x04070403, + 0x075b0204, 0x020f0a10, 0x0615013d, 0x0c0f0a07, 0x0995e9fe, 0x20f3840d, 0x20f38446, 0x20f38414, 0x20f3ad50, 0x22f38301, 0x8225010f, 0x151624e9, + 0x4b050714, 0x2f250673, 0x26233001, 0x26f88227, 0x013f3637, 0x829c0336, 0x846f20f5, 0x159123f5, 0xf5a2f9fc, 0x0a52033e, 0x0148040f, 0x0a04042d, + 0xc7fe110f, 0x0a0f0d89, 0x01ba0507, 0x01040305, 0x076c0201, 0x033df7a3, 0x06070b0e, 0x0e016879, 0x6d05120b, 0x0a0f074d, 0x0369030f, 0x04080605, + 0xb4030204, 0x20f78a0c, 0x20f784b0, 0x21f7ae52, 0xf9823634, 0x37361322, 0x14240683, 0x27070607, 0x23210482, 0x22f48322, 0x41312627, 0x162306f1, + 0x82410517, 0x845b20f9, 0x15a523f9, 0xf9a354fb, 0x140fb32e, 0x068d010f, 0x050f1408, 0x12022196, 0x092bf183, 0x6b4f0607, 0x0d0a0f05, 0xa3375a07, + 0x8d2908fa, 0x0a0f0f0a, 0x06bd54fe, 0x090a0f08, 0x052dc706, 0x01010513, 0x916a0606, 0x0f0a0906, 0x004a790a, 0x46000300, 0xe20649ff, 0x35f34105, + 0x2206ec41, 0x85370702, 0x010f2109, 0x2105854d, 0xeb422334, 0x011f2406, 0x82ce0636, 0x847320f9, 0x158d23f9, 0xf9a2c7f9, 0x06e6053e, 0x010f0a12, + 0x07a3285f, 0x0b0f0a07, 0x070807e0, 0x01030706, 0x0a0f0262, 0x2a4c0611, 0x5108f8a2, 0x0f119e04, 0xfe04040a, 0x046b74e2, 0x070e0a0f, 0x04040493, + 0x0d010107, 0x0f0a0504, 0x007dcc11, 0x00020000, 0x067cff7d, 0x00b60583, 0x00120006, 0x37110100, 0x1117010b, 0x22061400, 0x34260127, 0x01173236, + 0xe2b7cc05, 0x0d01b7e2, 0xfa163e2c, 0x05821690, 0x05700535, 0x469afeb6, 0x2101dffe, 0xfa660146, 0x162c3e31, 0x82167005, 0x90fa2105, 0x1f204f8d, + 0x01294f88, 0x22230614, 0x27012c31, 0x060f4100, 0x12171626, 0x1e050001, 0x1f385c89, 0xfeddfe01, 0x88febd14, 0x1f2c0175, 0x6c05291c, 0x52015701, + 0x2b1f1202, 0x12386b8c, 0xc6052c1f, 0x027801bd, 0x1f0706ae, 0xfd1b242c, 0xfea9fe82, 0x2c010aae, 0x5f20c784, 0x8320c782, 0x0534c785, 0x011b0711, + 0x22241127, 0x01373426, 0x14163236, 0x76050107, 0xfb21c783, 0x2bae8a1b, 0x466601a0, 0xdffe2101, 0x1d9afe46, 0xde83d884, 0xc785c583, 0x2f224f87, + 0x4f860000, 0x2605f044, 0x063e3736, 0x43243637, 0x062606e2, 0x0e040607, 0x02820702, 0x0e070622, 0x28086586, 0x2c1ffcfa, 0x22533106, 0x402a3f23, + 0x92274938, 0x4c01b601, 0x2c1f0403, 0xfeb61d27, 0x72a3d0e2, 0x2f461f34, 0x0833532b, 0x08818c26, 0x0e0f1f24, 0x595ad776, 0x557b5699, 0xa892275d, + 0x1f2c0120, 0x12032b1d, 0x95865c43, 0x789f3b64, 0x1578d96f, 0x00820019, 0x46000434, 0xce0644ff, 0x0d00b405, 0x20000e00, 0x00003000, 0x7d453301, + 0x3123270a, 0x0e010325, 0x9d452301, 0x0109280a, 0x14151632, 0x86060107, 0x04362314, 0x5d4564ab, 0xfe87280a, 0xbcfeef6f, 0x42111a05, 0x15290a95, + 0x2c1f3102, 0x1649fc0c, 0x24068229, 0x0216b703, 0x094a45ee, 0x4801a126, 0x1410bbfb, 0x2a086645, 0x300284fe, 0x12161f2c, 0x832327fa, 0xd9052206, + 0x2ea68223, 0x45000100, 0xcf060900, 0x1f001505, 0x46370000, 0x78201e2b, 0x20221043, 0x1bd7450a, 0x6f820020, 0x54ff1328, 0xaf05ed06, 0x7e823e00, + 0x16173226, 0x1737011b, 0x02260388, 0x27070317, 0x04822703, 0x07010b23, 0x24038327, 0x0f060334, 0x080f8601, 0x1337032c, 0x013f3037, 0x32371317, + 0x013f011a, 0x39018b02, 0x39324202, 0x392c7239, 0x39173f39, 0x45278c39, 0x39398470, 0x391f2f4c, 0x13825e39, 0x393f6b28, 0x06034639, 0x0e828c07, + 0x39482408, 0x24715439, 0x39395807, 0x1a013d55, 0x05380a22, 0xfd0239ae, 0x3847024b, 0x010bfd38, 0xfe38388d, 0x82530361, 0xfe202605, 0x0d5602cb, + 0x2a3282fb, 0xfb976003, 0x02393985, 0x826ffe65, 0xfdc83105, 0x08393924, 0x5a37da02, 0x39cffd6d, 0xfc6d0339, 0x05314a82, 0x9efd06a0, 0x3838ec64, + 0x01f5e3fc, 0x0b9f019a, 0x08df8238, 0xceffa020, 0x32056006, 0x00000700, 0x21152125, 0x33352311, 0xcc049401, 0xf47cbcfa, 0xec047846, 0x23920078, + 0x2135212c, 0x14044c02, 0xccfe74fb, 0x2586ac01, 0x03292796, 0xfc5c0304, 0x0214fe2c, 0x28279e64, 0xfca402bc, 0x035cfde4, 0x29279d1c, 0xec017404, + 0xa4fc9cfd, 0x279dd403, 0x012c0529, 0xfb54fe34, 0x968c04ec, 0x15332327, 0xc7821123, 0x7ce40527, 0x0534fbf4, 0x28eb8844, 0x06ceff63, 0x0032059d, + 0x26d0821f, 0x30151632, 0x45010119, 0xe9420580, 0x42112006, 0x032c09f0, 0x02231980, 0x1916117d, 0x20fd1523, 0xfd210683, 0x330c8483, 0x0511e002, + 0xfd192332, 0x02cafdcc, 0x19230e22, 0x8afd121c, 0x04230682, 0x84defd6a, 0x7602230d, 0x0082000e, 0x6300012a, 0x9d063b00, 0x1a00c504, 0x14206b85, + 0x22226282, 0x60870927, 0x36013037, 0x01091732, 0x19600636, 0x90fe0b23, 0xfd123e12, 0x12c1fe51, 0x210d821f, 0x0d827001, 0x01af0233, 0xbc02123f, + 0x10131923, 0x1919f8fd, 0x3dfecb03, 0x280c8319, 0x19190802, 0xc30135fc, 0x22638219, 0x829f0003, 0x826120cf, 0x002124cf, 0x82470034, 0x22352567, + 0x0607050e, 0x23240185, 0x053e3215, 0x2a052b61, 0x35213637, 0x17051e32, 0x6b260706, 0x012105d6, 0x05fe6b16, 0x22153325, 0x8227052e, 0x250b2f84, + 0x8e986b68, 0x927748a4, 0x30348e84, 0x0f8e101c, 0x0950fa2d, 0x7d57541d, 0x23419079, 0x845e5421, 0x5c032122, 0x10200886, 0x04301a88, 0x1b0378ba, + 0xda86642e, 0x8492df86, 0x060a163b, 0x022a0e8f, 0x5c452013, 0x3b375d96, 0x1f845e80, 0x864ffd21, 0x87782008, 0x03003019, 0x4800aa00, 0x8e035606, + 0x15000b00, 0x18002700, 0x18178167, 0x2007e544, 0xd7531801, 0x5eab2007, 0x3a30083d, 0x03d8343a, 0x0a41117a, 0x01978104, 0x12400a12, 0x21080782, + 0x89460348, 0xb2fe88e7, 0x3335c501, 0xff36323a, 0x137d2200, 0x46037ffd, 0x7c1431fe, 0xfc810222, 0x008200ba, 0x3b010436, 0xc50507ff, 0x07000c05, + 0x1d001300, 0x00002f00, 0x15011101, 0x35220382, 0x68183003, 0x05201307, 0x106b4518, 0x01aa0328, 0xfe72fe28, 0x335ce0d8, 0xc6671805, 0x08c82307, + 0x53180e33, 0xb3200afe, 0x0b796818, 0x60280221, 0x283e0943, 0xcc2b292e, 0xfd1b640f, 0xfe9e02ff, 0x10631c8e, 0x62fd0102, 0x36010600, 0xca0508ff, + 0x95820905, 0x2d001f2b, 0x43003700, 0x00004b00, 0x934c1805, 0x0206230f, 0x87712107, 0x3301220a, 0x11591832, 0x0bc07c08, 0x4c180520, 0x022d12e0, + 0x1171dc1f, 0x2b0b1247, 0x2c0b040a, 0x2709820b, 0x2594266e, 0xae016701, 0x07305b18, 0xed84fb24, 0xb75b6841, 0x07ae7c05, 0xb7038129, 0x6e3bfa3d, + 0x18269826, 0x2b0ce45c, 0x389f02f8, 0x9c2638dc, 0x269c2727, 0x703e0882, 0x027041fe, 0x60bb609f, 0x010660c4, 0x7e2a292b, 0x292b7da7, 0x5b5e612b, + 0xfe5f5b6b, 0x4f186122, 0x323a0b0c, 0xc53131c5, 0x03000000, 0x5400da00, 0xb8032606, 0x1a000f00, 0x00005200, 0x55712113, 0x06142205, 0x27018207, + 0x32252123, 0x34013d36, 0x160b5818, 0x64054b56, 0x17220630, 0x005e1716, 0x0b581806, 0x01db3a0e, 0x41425028, 0x32322e2f, 0x42412f2e, 0x01d8fe50, + 0x3a735e28, 0x03a15e39, 0xd2451894, 0x353e0807, 0x1d525045, 0x2525491d, 0x15157a7b, 0x4436354c, 0x3146465e, 0x5d213c23, 0x214f4846, 0x7e4b4420, + 0x27151474, 0xa9037027, 0x35341a1b, 0x359dd09d, 0x781b1a34, 0x72867276, 0xaafd3b3b, 0x45184d86, 0x1d3609dc, 0x06070e1d, 0x37657317, 0x103e2d2d, + 0x3e212111, 0x322a351f, 0x4518373a, 0x312408dd, 0x25222230, 0x0b28f782, 0x08ff0b00, 0x1505ea06, 0x2608f782, 0x0047001d, 0x00640057, 0x00ad0095, + 0x00c900bb, 0x00e600d9, 0x33301300, 0x17011e32, 0x0e071416, 0x30012b02, 0x65363237, 0x30250545, 0x30113023, 0x29cc7101, 0x850f0877, 0x010f310f, + 0x012e3330, 0x010e2301, 0x2e272223, 0x37363402, 0x13be5518, 0x023e3726, 0x3023013d, 0x07ee5f18, 0x21301329, 0x14151632, 0x75300706, 0xa9820deb, + 0x2c829c89, 0x0120aa8a, 0x0b257e9b, 0x405d3cdd, 0x2e008211, 0xdd3c5d40, 0x4b4b42dd, 0x96037742, 0x18010501, 0x21083e4a, 0x4a18010b, 0x46080c3e, + 0x12481108, 0x7c026277, 0xe2082309, 0x63092308, 0x872182c8, 0x0e03f121, 0x0e040c04, 0x0bb13c03, 0x0bc5fb29, 0x34484f0a, 0x24412c2b, 0x60222223, + 0x1f6f593d, 0x120d380e, 0x50483b46, 0x191e4850, 0x7c16281a, 0x76fe5bdf, 0x0a240a28, 0x23382817, 0x240e2876, 0x22094203, 0x365f9309, 0xa7012a0a, + 0x3f405127, 0x523f3fb1, 0x53555a27, 0xfe55539b, 0x18430115, 0x2221654a, 0x821f7c1f, 0x07a47702, 0x41112c08, 0x11411010, 0x028c24d3, 0x133e3008, + 0xb9835413, 0x272b2a83, 0x2108474d, 0x5c3a3108, 0x5c53a353, 0x31200909, 0xfe583a1f, 0x769f029d, 0x2d211260, 0x0660762b, 0x96bbfd21, 0x82002058, + 0x00083b00, 0x0608ff2b, 0x001505d5, 0x00530029, 0x009d0085, 0x00b900ab, 0x00d600c9, 0x3c422500, 0x29664228, 0x42300121, 0x02227d4a, 0x4c1801c7, + 0x02211f99, 0x2122a0d0, 0x3f4231fc, 0x42a5205d, 0x0121212b, 0x2123a19d, 0x3842c503, 0x0e00265a, 0x0100ae00, 0x20008700, 0x240b8602, 0x000d0001, + 0x240b861f, 0x00070002, 0x240b863d, 0x00250003, 0x200b8691, 0x20238204, 0x240b86d3, 0x010f0005, 0x2a0b8601, 0x010c0006, 0x0003002b, 0x86090401, + 0x240b8556, 0x001a0001, 0x20178603, 0x20778202, 0x240b862d, 0x004a0003, 0x200b8645, 0x20238204, 0x240b86b7, 0x001e0005, 0x240b86e1, 0x01180006, + 0x304d8311, 0x00750046, 0x006e0072, 0x00630061, 0x00200065, 0x20078249, 0x2e0f826f, 0x46000073, 0x616e7275, 0x49206563, 0x826e6f63, 0x8252200e, + 0x8267201f, 0x826c202d, 0x0072282b, 0x67655200, 0x82616c75, 0x00462108, 0x74212f83, 0x22078300, 0x84670072, 0x823a2045, 0x21599949, 0x1f830020, + 0x37003126, 0x33002d00, 0x32260382, 0x32003000, 0x71823400, 0x746e6f2a, 0x67726f46, 0x203a2065, 0x0f827d8c, 0x2d373127, 0x30322d33, 0xa8268332, + 0x825620b3, 0x0072248d, 0x84690073, 0x822020a3, 0x00302467, 0x842e0031, 0x00303007, 0x72655600, 0x6e6f6973, 0x31303020, 0x8230302e, 0x41b38d10, + 0x0a41110b, 0x00002306, 0x008b0002, 0x0c8b0120, 0x69200b84, 0xd6080582, 0x00020001, 0x01020103, 0x01040103, 0x01060105, 0x01080107, 0x010a0109, + 0x010c010b, 0x010e010d, 0x0110010f, 0x01120111, 0x01140113, 0x01160115, 0x01180117, 0x011a0119, 0x011c011b, 0x011e011d, 0x0120011f, 0x01220121, + 0x01240123, 0x01260125, 0x01280127, 0x012a0129, 0x012c012b, 0x012e012d, 0x0130012f, 0x01320131, 0x01340133, 0x01360135, 0x01380137, 0x013a0139, + 0x013c013b, 0x013e013d, 0x0140013f, 0x01420141, 0x01440143, 0x01460145, 0x01480147, 0x014a0149, 0x014c014b, 0x014e014d, 0x0150014f, 0x01520151, + 0x01540153, 0x01560155, 0x01580157, 0x015a0159, 0x015c015b, 0x015e015d, 0x0160015f, 0x01620161, 0x01640163, 0x07660165, 0x45696e75, 0x86304630, + 0x86312007, 0x86322007, 0x86332007, 0x84342007, 0x30312107, 0x31212785, 0x21278530, 0x27853031, 0x85303121, 0x30312127, 0x35202787, 0x36202f86, + 0x37200786, 0x38200786, 0x39200786, 0x41200786, 0x42200786, 0x43200786, 0x44200786, 0x45200786, 0x46200786, 0x31210785, 0x20078630, 0x86078631, + 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, 0x8631207f, + 0x8631207f, 0x3032217f, 0x32217785, 0x86078631, 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, + 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, 0x8632207f, 0x3033217f, 0x33217785, 0x86078631, 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, + 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, 0x8633207f, 0x3034217f, 0x34217785, 0x86078631, + 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, 0x8634207f, + 0x8634207f, 0x3035217f, 0x35217785, 0x86078631, 0x8635207f, 0x8635207f, 0x8635207f, 0x8635207f, 0x8635207f, 0x8635207f, 0x8635207f, 0x8635207f, + 0x8635207f, 0x8635207f, 0x8635207f, 0x8635207f, 0x4635287f, 0x01000000, 0x4500ffff, 0x002607f9, 0x14000c00, 0x15820400, 0x00000224, 0x03860100, + 0x00240985, 0x206855e0, 0x002c1182, 0x9ffffde0, 0x00000000, 0xd95a1ce2, 0x7fe4fa05, 0x0000ebea, }; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c9593d7c1..925b272c5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1105,6 +1105,9 @@ float FurnaceGUI::calcBPM(const DivGroovePattern& speeds, float hz, int vN, int } void FurnaceGUI::play(int row) { + if (e->getStreamPlayer()) { + e->killStream(); + } memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); for (int i=0; iopenLoad( "Open File", - {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc", + {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc *.ftm *.0cc *.dnm *.eft *.fub", "all files", "*"}, workingDirSong, dpiScale @@ -1664,7 +1667,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { case GUI_FILE_SAVE_DMF: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( - "Save File", + "Export DMF", {"DefleMask 1.1.3 module", "*.dmf"}, workingDirSong, dpiScale @@ -1673,7 +1676,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { case GUI_FILE_SAVE_DMF_LEGACY: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( - "Save File", + "Export DMF", {"DefleMask 1.0/legacy module", "*.dmf"}, workingDirSong, dpiScale @@ -1891,15 +1894,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { ); break; case GUI_FILE_EXPORT_CMDSTREAM: - if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); - hasOpened=fileDialog->openSave( - "Export Command Stream", - {"text file", "*.txt"}, - workingDirROMExport, - dpiScale - ); - break; - case GUI_FILE_EXPORT_CMDSTREAM_BINARY: if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Command Stream", @@ -2221,7 +2215,7 @@ int FurnaceGUI::load(String path) { return 1; } fclose(f); - if (!e->load(file,(size_t)len)) { + if (!e->load(file,(size_t)len,path.c_str())) { lastError=e->getLastError(); logE("could not open file!"); return 1; @@ -3463,6 +3457,7 @@ bool FurnaceGUI::loop() { DECLARE_METRIC(volMeter) DECLARE_METRIC(settings) DECLARE_METRIC(debug) + DECLARE_METRIC(csPlayer) DECLARE_METRIC(stats) DECLARE_METRIC(memory) DECLARE_METRIC(compatFlags) @@ -4046,6 +4041,7 @@ bool FurnaceGUI::loop() { IMPORT_CLOSE(groovesOpen); IMPORT_CLOSE(xyOscOpen); IMPORT_CLOSE(memoryOpen); + IMPORT_CLOSE(csPlayerOpen); } else if (pendingLayoutImportStep==1) { // let the UI settle } else if (pendingLayoutImportStep==2) { @@ -4160,12 +4156,6 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } - if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) { - openFileDialog(GUI_FILE_SAVE_DMF); - } - if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) { - openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); - } ImGui::Separator(); if (settings.exportOptionsLayout==0) { if (ImGui::BeginMenu("export audio...")) { @@ -4204,6 +4194,10 @@ bool FurnaceGUI::loop() { drawExportCommand(); ImGui::EndMenu(); } + if (ImGui::BeginMenu("export .dmf...")) { + drawExportDMF(); + ImGui::EndMenu(); + } } else if (settings.exportOptionsLayout==2) { if (ImGui::MenuItem("export audio...")) { curExportType=GUI_EXPORT_AUDIO; @@ -4241,6 +4235,10 @@ bool FurnaceGUI::loop() { curExportType=GUI_EXPORT_CMD_STREAM; displayExport=true; } + if (ImGui::MenuItem("export .dmf...")) { + curExportType=GUI_EXPORT_DMF; + displayExport=true; + } } else { if (ImGui::MenuItem("export...",BIND_FOR(GUI_ACTION_EXPORT))) { displayExport=true; @@ -4354,15 +4352,15 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Settings":"settings")) { - #ifndef IS_MOBILE +#ifndef IS_MOBILE if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { doAction(GUI_ACTION_FULLSCREEN); } - #endif +#endif if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } - if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { + if (ImGui::MenuItem("pattern visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; e->enableCommandStream(fancyPattern); e->getCommandStream(cmdStream); @@ -4383,41 +4381,58 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Window":"window")) { - if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; - if (ImGui::MenuItem("subsongs",BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS),subSongsOpen)) subSongsOpen=!subSongsOpen; - if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen; - if (settings.unifiedDataView) { - if (ImGui::MenuItem("assets",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; - } else { - if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; - if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; - if (ImGui::MenuItem("samples",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST),sampleListOpen)) sampleListOpen=!sampleListOpen; + if (ImGui::BeginMenu("song")) { + if (ImGui::MenuItem("song comments", BIND_FOR(GUI_ACTION_WINDOW_NOTES), notesOpen)) notesOpen = !notesOpen; + if (ImGui::MenuItem("song information", BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO), songInfoOpen)) songInfoOpen = !songInfoOpen; + if (ImGui::MenuItem("subsongs", BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS), subSongsOpen)) subSongsOpen = !subSongsOpen; + ImGui::Separator(); + if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; + if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; + if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; + if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; + if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; + if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; + if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("assets")) { + if (settings.unifiedDataView) { + if (ImGui::MenuItem("assets", BIND_FOR(GUI_ACTION_WINDOW_INS_LIST), insListOpen)) insListOpen = !insListOpen; + } else { + if (ImGui::MenuItem("instruments", BIND_FOR(GUI_ACTION_WINDOW_INS_LIST), insListOpen)) insListOpen = !insListOpen; + if (ImGui::MenuItem("samples", BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST), sampleListOpen)) sampleListOpen = !sampleListOpen; + if (ImGui::MenuItem("wavetables", BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST), waveListOpen)) waveListOpen = !waveListOpen; + } + ImGui::Separator(); + if (ImGui::MenuItem("instrument editor", BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT), insEditOpen)) insEditOpen = !insEditOpen; + if (ImGui::MenuItem("sample editor", BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT), sampleEditOpen)) sampleEditOpen = !sampleEditOpen; + if (ImGui::MenuItem("wavetable editor", BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT), waveEditOpen)) waveEditOpen = !waveEditOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("visualizers")) { + if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; + if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; + if (ImGui::MenuItem("oscilloscope (X-Y)",BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen; + if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("tempo")) { + if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen; + if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen; + if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("debug")) { + if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; + if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; + if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; + if (ImGui::MenuItem("memory composition",BIND_FOR(GUI_ACTION_WINDOW_MEMORY),memoryOpen)) memoryOpen=!memoryOpen; + ImGui::EndMenu(); } - if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; - if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; - if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; - if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen; - if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; - if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; - if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; - if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; - if (ImGui::MenuItem("song comments",BIND_FOR(GUI_ACTION_WINDOW_NOTES),notesOpen)) notesOpen=!notesOpen; - ImGui::Separator(); - if (ImGui::MenuItem("instrument editor",BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT),insEditOpen)) insEditOpen=!insEditOpen; - if (ImGui::MenuItem("wavetable editor",BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT),waveEditOpen)) waveEditOpen=!waveEditOpen; - if (ImGui::MenuItem("sample editor",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT),sampleEditOpen)) sampleEditOpen=!sampleEditOpen; ImGui::Separator(); + if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen; if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; - if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; - if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; - if (ImGui::MenuItem("oscilloscope (X-Y)",BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen; - if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; - if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen; - if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; - if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; - if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; - if (ImGui::MenuItem("memory composition",BIND_FOR(GUI_ACTION_WINDOW_MEMORY),memoryOpen)) memoryOpen=!memoryOpen; if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; ImGui::EndMenu(); @@ -4426,7 +4441,6 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; if (ImGui::MenuItem("inspector")) inspectorOpen=!inspectorOpen; - if (ImGui::MenuItem("shader editor")) shaderEditor=!shaderEditor; if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { aboutOpen=true; @@ -4450,7 +4464,7 @@ bool FurnaceGUI::loop() { info="| Groove"; } - info+=fmt::sprintf(" @ %gHz (%g BPM) ",e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD)); + info+=fmt::sprintf(" @ %gHz (%g BPM) ",e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->getVirtualTempoN(),e->getVirtualTempoD())); if (settings.orderRowsBase) { info+=fmt::sprintf("| Order %.2X/%.2X ",playOrder,e->curSubSong->ordersLen-1); @@ -4629,6 +4643,7 @@ bool FurnaceGUI::loop() { globalWinFlags=0; MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); + MEASURE(csPlayer,drawCSPlayer()); MEASURE(log,drawLog()); MEASURE(compatFlags,drawCompatFlags()); MEASURE(stats,drawStats()); @@ -4669,6 +4684,7 @@ bool FurnaceGUI::loop() { MEASURE(volMeter,drawVolMeter()); MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); + MEASURE(csPlayer,drawCSPlayer()); MEASURE(stats,drawStats()); MEASURE(memory,drawMemory()); MEASURE(compatFlags,drawCompatFlags()); @@ -4822,7 +4838,6 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: case GUI_FILE_EXPORT_TEXT: case GUI_FILE_EXPORT_CMDSTREAM: - case GUI_FILE_EXPORT_CMDSTREAM_BINARY: workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_LOAD_MAIN_FONT: @@ -4912,10 +4927,10 @@ bool FurnaceGUI::loop() { if (curFileDialog==GUI_FILE_EXPORT_ZSM) { checkExtension(".zsm"); } - if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM || curFileDialog==GUI_FILE_EXPORT_TEXT) { + if (curFileDialog==GUI_FILE_EXPORT_TEXT) { checkExtension(".txt"); } - if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY) { + if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { checkExtension(".bin"); } if (curFileDialog==GUI_FILE_EXPORT_COLORS) { @@ -5309,11 +5324,8 @@ bool FurnaceGUI::loop() { } break; } - case GUI_FILE_EXPORT_CMDSTREAM: - case GUI_FILE_EXPORT_CMDSTREAM_BINARY: { - bool isBinary=(curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY); - - SafeWriter* w=e->saveCommand(isBinary); + case GUI_FILE_EXPORT_CMDSTREAM: { + SafeWriter* w=e->saveCommand(); if (w!=NULL) { FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { @@ -7248,8 +7260,12 @@ void FurnaceGUI::commitState() { } } -bool FurnaceGUI::finish() { +bool FurnaceGUI::finish(bool saveConfig) { commitState(); + if (saveConfig) { + logI("saving config."); + e->saveConf(); + } rend->quitGUI(); ImGui_ImplSDL2_Shutdown(); quitRender(); @@ -7495,6 +7511,7 @@ FurnaceGUI::FurnaceGUI(): groovesOpen(false), xyOscOpen(false), memoryOpen(false), + csPlayerOpen(false), shortIntro(false), insListDir(false), waveListDir(false), @@ -7799,6 +7816,7 @@ FurnaceGUI::FurnaceGUI(): curTutorial(-1), curTutorialStep(0), audioExportType(0), + dmfExportVersion(0), curExportType(GUI_EXPORT_NONE) { // value keys valueKeys[SDLK_0]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 9407e0c8e..d54635fa3 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -295,6 +295,9 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_POWERNOISE, GUI_COLOR_INSTR_POWERNOISE_SLOPE, GUI_COLOR_INSTR_DAVE, + GUI_COLOR_INSTR_NDS, + GUI_COLOR_INSTR_GBA_DMA, + GUI_COLOR_INSTR_GBA_MINMOD, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, @@ -410,6 +413,29 @@ enum FurnaceGUIColors { GUI_COLOR_PATCHBAY_CONNECTION_BG, GUI_COLOR_PATCHBAY_CONNECTION_HI, + GUI_COLOR_MEMORY_BG, + GUI_COLOR_MEMORY_DATA, + GUI_COLOR_MEMORY_FREE, + GUI_COLOR_MEMORY_PADDING, + GUI_COLOR_MEMORY_RESERVED, + GUI_COLOR_MEMORY_SAMPLE, + GUI_COLOR_MEMORY_SAMPLE_ALT1, + GUI_COLOR_MEMORY_SAMPLE_ALT2, + GUI_COLOR_MEMORY_SAMPLE_ALT3, + GUI_COLOR_MEMORY_WAVE_RAM, + GUI_COLOR_MEMORY_WAVE_STATIC, + GUI_COLOR_MEMORY_ECHO, + GUI_COLOR_MEMORY_N163_LOAD, + GUI_COLOR_MEMORY_N163_PLAY, + GUI_COLOR_MEMORY_BANK0, + GUI_COLOR_MEMORY_BANK1, + GUI_COLOR_MEMORY_BANK2, + GUI_COLOR_MEMORY_BANK3, + GUI_COLOR_MEMORY_BANK4, + GUI_COLOR_MEMORY_BANK5, + GUI_COLOR_MEMORY_BANK6, + GUI_COLOR_MEMORY_BANK7, + GUI_COLOR_LOGLEVEL_ERROR, GUI_COLOR_LOGLEVEL_WARNING, GUI_COLOR_LOGLEVEL_INFO, @@ -458,6 +484,7 @@ enum FurnaceGUIWindows { GUI_WINDOW_XY_OSC, GUI_WINDOW_INTRO_MON, GUI_WINDOW_MEMORY, + GUI_WINDOW_CS_PLAYER, GUI_WINDOW_SPOILER }; @@ -516,7 +543,6 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_VGM, GUI_FILE_EXPORT_ZSM, GUI_FILE_EXPORT_CMDSTREAM, - GUI_FILE_EXPORT_CMDSTREAM_BINARY, GUI_FILE_EXPORT_TEXT, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, @@ -563,7 +589,8 @@ enum FurnaceGUIExportTypes { GUI_EXPORT_ZSM, GUI_EXPORT_CMD_STREAM, GUI_EXPORT_AMIGA_VAL, - GUI_EXPORT_TEXT + GUI_EXPORT_TEXT, + GUI_EXPORT_DMF }; enum FurnaceGUIFMAlgs { @@ -649,6 +676,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_GROOVES, GUI_ACTION_WINDOW_XY_OSC, GUI_ACTION_WINDOW_MEMORY, + GUI_ACTION_WINDOW_CS_PLAYER, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -1217,6 +1245,7 @@ struct FurnaceGUISysDef { const char* extra; String definition; std::vector orig; + std::vector subDefs; FurnaceGUISysDef(const char* n, std::initializer_list def, const char* e=NULL); }; @@ -1617,6 +1646,7 @@ class FurnaceGUI { int opnCore; int opl2Core; int opl3Core; + int esfmCore; int arcadeCoreRender; int ym2612CoreRender; int snCoreRender; @@ -1627,6 +1657,7 @@ class FurnaceGUI { int opnCoreRender; int opl2CoreRender; int opl3CoreRender; + int esfmCoreRender; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1822,6 +1853,7 @@ class FurnaceGUI { opnCore(1), opl2Core(0), opl3Core(0), + esfmCore(0), arcadeCoreRender(1), ym2612CoreRender(0), snCoreRender(0), @@ -1832,6 +1864,7 @@ class FurnaceGUI { opnCoreRender(1), opl2CoreRender(0), opl3CoreRender(0), + esfmCoreRender(0), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), @@ -2044,7 +2077,7 @@ class FurnaceGUI { bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen; - bool groovesOpen, xyOscOpen, memoryOpen; + bool groovesOpen, xyOscOpen, memoryOpen, csPlayerOpen; bool shortIntro; bool insListDir, waveListDir, sampleListDir; @@ -2429,8 +2462,12 @@ class FurnaceGUI { // tutorial int curTutorial, curTutorialStep; + // command stream player + ImGuiListClipper csClipper; + // export options int audioExportType; + int dmfExportVersion; FurnaceGUIExportTypes curExportType; void drawExportAudio(bool onWindow=false); @@ -2439,6 +2476,7 @@ class FurnaceGUI { void drawExportAmigaVal(bool onWindow=false); void drawExportText(bool onWindow=false); void drawExportCommand(bool onWindow=false); + void drawExportDMF(bool onWindow=false); void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); @@ -2553,6 +2591,7 @@ class FurnaceGUI { void drawIntro(double introTime, bool monitor=false); void drawSettings(); void drawDebug(); + void drawCSPlayer(); void drawNewSong(); void drawPalette(); void drawExport(); @@ -2706,7 +2745,7 @@ class FurnaceGUI { bool detectOutOfBoundsWindow(SDL_Rect& failing); int processEvent(SDL_Event* ev); bool loop(); - bool finish(); + bool finish(bool saveConfig=false); bool init(); bool requestQuit(); FurnaceGUI(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index bca38b73e..aee901f4d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -179,6 +179,9 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"PowerNoise (noise)",ICON_FUR_NOISE,ICON_FUR_INS_POWERNOISE}, {"PowerNoise (slope)",ICON_FUR_SAW,ICON_FUR_INS_POWERNOISE_SAW}, {"Dave",ICON_FA_BAR_CHART,ICON_FUR_INS_DAVE}, + {"NDS",ICON_FA_BAR_CHART,ICON_FUR_INS_NDS}, + {"GBA DMA",ICON_FA_GAMEPAD,ICON_FUR_INS_GBA_DMA}, + {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -202,7 +205,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "VOX", "8-bit µ-law PCM", "C219 PCM", - NULL, + "IMA ADPCM", NULL, NULL, "16-bit PCM" @@ -489,10 +492,10 @@ const FurnaceGUIColors fxColors[256]={ GUI_COLOR_PATTERN_EFFECT_MISC, // E3 GUI_COLOR_PATTERN_EFFECT_MISC, // E4 GUI_COLOR_PATTERN_EFFECT_PITCH, // E5 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E6 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E7 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E8 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E9 + GUI_COLOR_PATTERN_EFFECT_MISC, // E6 + GUI_COLOR_PATTERN_EFFECT_TIME, // E7 + GUI_COLOR_PATTERN_EFFECT_MISC, // E8 + GUI_COLOR_PATTERN_EFFECT_MISC, // E9 GUI_COLOR_PATTERN_EFFECT_MISC, // EA GUI_COLOR_PATTERN_EFFECT_MISC, // EB GUI_COLOR_PATTERN_EFFECT_TIME, // EC @@ -511,7 +514,7 @@ const FurnaceGUIColors fxColors[256]={ GUI_COLOR_PATTERN_EFFECT_VOLUME, // F9 GUI_COLOR_PATTERN_EFFECT_VOLUME, // FA GUI_COLOR_PATTERN_EFFECT_INVALID, // FB - GUI_COLOR_PATTERN_EFFECT_INVALID, // FC + GUI_COLOR_PATTERN_EFFECT_TIME, // FC GUI_COLOR_PATTERN_EFFECT_INVALID, // FD GUI_COLOR_PATTERN_EFFECT_INVALID, // FE GUI_COLOR_PATTERN_EFFECT_SONG // FF @@ -605,7 +608,8 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_CLOCK", "Clock", 0), D("WINDOW_GROOVES", "Grooves", 0), D("WINDOW_XY_OSC", "Oscilloscope (X-Y)", 0), - D("WINDOW_MEMORY", "Memory Compositiom", 0), + D("WINDOW_MEMORY", "Memory Composition", 0), + D("WINDOW_CS_PLAYER", "Command Stream Player", 0), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), @@ -991,6 +995,9 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_POWERNOISE,"",ImVec4(1.0f,1.0f,0.8f,1.0f)), D(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"",ImVec4(1.0f,0.6f,0.3f,1.0f)), D(GUI_COLOR_INSTR_DAVE,"",ImVec4(0.7f,0.7f,0.8f,1.0f)), + D(GUI_COLOR_INSTR_NDS,"",ImVec4(0.7f,0.7f,0.8f,1.0f)), + D(GUI_COLOR_INSTR_GBA_DMA,"",ImVec4(0.6f,0.4f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,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)), @@ -1107,6 +1114,29 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_PATCHBAY_CONNECTION_BG,"",ImVec4(0.1f,0.25f,0.3f,1.0f)), D(GUI_COLOR_PATCHBAY_CONNECTION_HI,"",ImVec4(0.2f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_MEMORY_BG,"",ImVec4(0.12f,0.12f,0.12f,1.0f)), + D(GUI_COLOR_MEMORY_DATA,"",ImVec4(1.0f,1.0f,1.0f,0.8f)), + D(GUI_COLOR_MEMORY_FREE,"",ImVec4(0.25f,0.25f,0.25f,1.0f)), + D(GUI_COLOR_MEMORY_PADDING,"",ImVec4(0.25f,0.25f,0.25f,1.0f)), + D(GUI_COLOR_MEMORY_RESERVED,"",ImVec4(0.5f,0.5f,0.6f,1.0f)), + D(GUI_COLOR_MEMORY_SAMPLE,"",ImVec4(1.0f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_MEMORY_SAMPLE_ALT1,"",ImVec4(0.5f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_MEMORY_SAMPLE_ALT2,"",ImVec4(0.2f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_MEMORY_SAMPLE_ALT3,"",ImVec4(0.2f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_MEMORY_WAVE_RAM,"",ImVec4(1.0f,0.5f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_WAVE_STATIC,"",ImVec4(1.0f,0.3f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_ECHO,"",ImVec4(0.2f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_MEMORY_N163_LOAD,"",ImVec4(1.0f,0.5f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_N163_PLAY,"",ImVec4(1.0f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_MEMORY_BANK0,"",ImVec4(1.0f,0.1f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_BANK1,"",ImVec4(1.0f,0.5f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_BANK2,"",ImVec4(1.0f,1.0f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_BANK3,"",ImVec4(0.1f,1.0f,0.1f,1.0f)), + D(GUI_COLOR_MEMORY_BANK4,"",ImVec4(0.1f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_MEMORY_BANK5,"",ImVec4(0.1f,0.1f,1.0f,1.0f)), + D(GUI_COLOR_MEMORY_BANK6,"",ImVec4(0.5f,0.1f,1.0f,1.0f)), + D(GUI_COLOR_MEMORY_BANK7,"",ImVec4(1.0f,0.1f,1.0f,1.0f)), + D(GUI_COLOR_LOGLEVEL_ERROR,"",ImVec4(1.0f,0.2f,0.2f,1.0f)), D(GUI_COLOR_LOGLEVEL_WARNING,"",ImVec4(1.0f,1.0f,0.2f,1.0f)), D(GUI_COLOR_LOGLEVEL_INFO,"",ImVec4(0.4f,1.0f,0.4f,1.0f)), @@ -1205,11 +1235,15 @@ const int availableSystems[]={ DIV_SYSTEM_TED, DIV_SYSTEM_C140, DIV_SYSTEM_C219, + DIV_SYSTEM_GBA_DMA, + DIV_SYSTEM_GBA_MINMOD, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_ESFM, DIV_SYSTEM_PONG, DIV_SYSTEM_POWERNOISE, DIV_SYSTEM_DAVE, + DIV_SYSTEM_NDS, + DIV_SYSTEM_5E01, 0 // don't remove this last one! }; @@ -1301,6 +1335,8 @@ const int chipsSpecial[]={ DIV_SYSTEM_SM8521, DIV_SYSTEM_POWERNOISE, DIV_SYSTEM_DAVE, + DIV_SYSTEM_NDS, + DIV_SYSTEM_5E01, 0 // don't remove this last one! }; @@ -1323,6 +1359,9 @@ const int chipsSample[]={ DIV_SYSTEM_K053260, DIV_SYSTEM_C140, DIV_SYSTEM_C219, + DIV_SYSTEM_NDS, + DIV_SYSTEM_GBA_DMA, + DIV_SYSTEM_GBA_MINMOD, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 519881424..420fa9bfb 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -356,6 +356,10 @@ const char* es5506ControlModes[3]={ "pause", "reverse", NULL }; +const char* minModModeBits[3]={ + "invert right", "invert left", NULL +}; + const int orderedOps[4]={ 0, 2, 1, 3 }; @@ -2501,7 +2505,8 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) { ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VRC6 || - ins->type==DIV_INS_SU) { + ins->type==DIV_INS_SU || + ins->type==DIV_INS_NDS) { P(ImGui::Checkbox("Use sample",&ins->amiga.useSample)); if (ins->type==DIV_INS_X1_010) { if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER @@ -2525,14 +2530,15 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) { ImGui::EndCombo(); } // Wavetable - if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES) { + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES || ins->type==DIV_INS_GBA_DMA || ins->type==DIV_INS_GBA_MINMOD) { + const char* useWaveText=ins->type==DIV_INS_AMIGA?"Use wavetable (Amiga/Generic DAC only)":"Use wavetable"; ImGui::BeginDisabled(ins->amiga.useNoteMap); - P(ImGui::Checkbox("Use wavetable (Amiga/SNES/Generic DAC only)",&ins->amiga.useWave)); + P(ImGui::Checkbox(useWaveText,&ins->amiga.useWave)); if (ins->amiga.useWave) { int len=ins->amiga.waveLen+1; int origLen=len; if (ImGui::InputInt("Width",&len,2,16)) { - if (ins->type==DIV_INS_SNES) { + if (ins->type==DIV_INS_SNES || ins->type==DIV_INS_GBA_DMA) { if (len<16) len=16; if (len>256) len=256; if (len>origLen) { @@ -5395,6 +5401,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv)); P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit)); + P(ImGui::Checkbox("Double wave length (GBA only)",&ins->gb.doubleWave)); ImGui::BeginDisabled(ins->gb.softEnv); if (ImGui::BeginTable("GBParams",2)) { @@ -6021,7 +6028,10 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_GA20 || ins->type==DIV_INS_K053260 || ins->type==DIV_INS_C140 || - ins->type==DIV_INS_C219) { + ins->type==DIV_INS_C219 || + ins->type==DIV_INS_NDS || + ins->type==DIV_INS_GBA_DMA || + ins->type==DIV_INS_GBA_MINMOD) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6456,6 +6466,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_GB || (ins->type==DIV_INS_AMIGA && ins->amiga.useWave) || + (ins->type==DIV_INS_GBA_DMA && ins->amiga.useWave) || (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) || ins->type==DIV_INS_N163 || ins->type==DIV_INS_FDS || @@ -6465,7 +6476,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SCC || ins->type==DIV_INS_SNES || ins->type==DIV_INS_NAMCO || - ins->type==DIV_INS_SM8521) { + ins->type==DIV_INS_SM8521 || + (ins->type==DIV_INS_GBA_MINMOD && ins->amiga.useWave)) { if (ImGui::BeginTabItem("Wavetable")) { switch (ins->type) { case DIV_INS_GB: @@ -6500,6 +6512,7 @@ void FurnaceGUI::drawInsEdit() { wavePreviewHeight=255; break; case DIV_INS_AMIGA: + case DIV_INS_GBA_DMA: wavePreviewLen=ins->amiga.waveLen+1; wavePreviewHeight=255; break; @@ -6507,6 +6520,10 @@ void FurnaceGUI::drawInsEdit() { wavePreviewLen=ins->amiga.waveLen+1; wavePreviewHeight=15; break; + case DIV_INS_GBA_MINMOD: + wavePreviewLen=ins->amiga.waveLen+1; + wavePreviewHeight=255; + break; default: wavePreviewLen=32; wavePreviewHeight=31; @@ -6736,7 +6753,7 @@ 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_K053260) { + ins->type==DIV_INS_K053260 || ins->type==DIV_INS_NDS) { volMax=127; } if (ins->type==DIV_INS_GB) { @@ -6765,13 +6782,13 @@ void FurnaceGUI::drawInsEdit() { volMax=31; } if (ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_YMZ280B || ins->type==DIV_INS_RF5C68 || - ins->type==DIV_INS_GA20 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219) { + ins->type==DIV_INS_GA20 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || ins->type==DIV_INS_GBA_MINMOD) { volMax=255; } if (ins->type==DIV_INS_QSOUND) { volMax=16383; } - if (ins->type==DIV_INS_POKEMINI) { + if (ins->type==DIV_INS_POKEMINI || ins->type==DIV_INS_GBA_DMA) { volMax=2; } @@ -6830,7 +6847,7 @@ void FurnaceGUI::drawInsEdit() { 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_K053260 || - ins->type==DIV_INS_C140) { + ins->type==DIV_INS_C140 || ins->type==DIV_INS_GBA_DMA || ins->type==DIV_INS_GBA_MINMOD) { dutyMax=0; } if (ins->type==DIV_INS_VBOY) { @@ -6921,6 +6938,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise Freq"; dutyMax=3; } + if (ins->type==DIV_INS_NDS) { + dutyLabel="Duty"; + dutyMax=ins->amiga.useSample?0:7; + } const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1)); @@ -6959,6 +6980,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_POWERNOISE_SLOPE) waveMax=0; if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7; if (ins->type==DIV_INS_DAVE) waveMax=4; + if (ins->type==DIV_INS_NDS) waveMax=0; if (ins->type==DIV_INS_PET) { waveMax=8; waveBitMode=true; @@ -7028,6 +7050,12 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_DAVE) { ex1Max=4; } + if (ins->type==DIV_INS_MIKEY) { + ex1Max=12; + } + if (ins->type==DIV_INS_GBA_MINMOD) { + ex1Max=2; + } int panMin=0; int panMax=0; @@ -7042,7 +7070,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_VERA || ins->type==DIV_INS_ADPCMA || ins->type==DIV_INS_ADPCMB || - ins->type==DIV_INS_ESFM) { + ins->type==DIV_INS_ESFM || + ins->type==DIV_INS_GBA_DMA) { panMax=2; panSingle=true; } @@ -7092,7 +7121,7 @@ void FurnaceGUI::drawInsEdit() { panMin=0; panMax=127; } - if (ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219) { + if (ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || ins->type==DIV_INS_GBA_MINMOD) { panMin=0; panMax=255; } @@ -7108,6 +7137,11 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_DAVE) { panMax=63; } + if (ins->type==DIV_INS_NDS) { + panMin=-64; + panMax=63; + panSingleNoBit=true; + } if (volMax>0) { macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); @@ -7198,7 +7232,10 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_ESFM || ins->type==DIV_INS_POWERNOISE || ins->type==DIV_INS_POWERNOISE_SLOPE || - ins->type==DIV_INS_DAVE) { + ins->type==DIV_INS_DAVE || + ins->type==DIV_INS_NDS || + ins->type==DIV_INS_GBA_DMA || + ins->type==DIV_INS_GBA_MINMOD) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { @@ -7234,6 +7271,10 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Group Attack",&ins->std.ex1Macro,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_DAVE) { macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,daveControlBits)); + } else if (ins->type==DIV_INS_MIKEY) { + macroList.push_back(FurnaceGUIMacroDesc("Load LFSR",&ins->std.ex1Macro,0,12,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + } else if (ins->type==DIV_INS_GBA_MINMOD) { + macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex1Macro,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,minModModeBits)); } else { macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } diff --git a/src/gui/memory.cpp b/src/gui/memory.cpp index bf5dd09a2..91b049277 100644 --- a/src/gui/memory.cpp +++ b/src/gui/memory.cpp @@ -19,7 +19,11 @@ #include "gui.h" #include -#include +#include "imgui.h" +#include "imgui_internal.h" + +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); void FurnaceGUI::drawMemory() { if (nextWindow==GUI_WINDOW_MEMORY) { @@ -29,15 +33,137 @@ void FurnaceGUI::drawMemory() { } if (!memoryOpen) return; if (ImGui::Begin("Memory Composition",&memoryOpen,globalWinFlags)) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + char tempID[1024]; + bool have=false; + for (int i=0; isong.systemLen; i++) { DivDispatch* dispatch=e->getDispatch(i); for (int j=0; j<4; j++) { const DivMemoryComposition* mc=dispatch->getMemCompo(j); if (mc==NULL) break; + have=true; ImGui::Text("%s: %s",e->getSystemName(e->song.system[i]),mc->name.c_str()); + ImGui::SameLine(); + ImGui::Text("%d/%d (%.1f%%)",(int)mc->used,(int)mc->capacity,100.0*(double)mc->used/(double)mc->capacity); + + ImVec2 size=ImVec2(ImGui::GetContentRegionAvail().x,36.0f*dpiScale); + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImRect dataRect=rect; + ImRect entryRect=rect; + ImGuiStyle& style=ImGui::GetStyle(); + ImGui::ItemSize(size,style.FramePadding.y); + snprintf(tempID,1023,"MC%d_%d",i,j); + if (ImGui::ItemAdd(rect,ImGui::GetID(tempID))) { + dl->AddRectFilled(rect.Min,rect.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_BG])); + + if (mc->memory!=NULL && mc->waveformView!=DIV_MEMORY_WAVE_NONE) { + dataRect.Max.y-=8.0f*dpiScale; + entryRect.Min.y=entryRect.Max.y-8.0f*dpiScale; + } + + int curHover=-1; + int kIndex=0; + if (mc->capacity>0) for (const DivMemoryEntry& k: mc->entries) { + if (k.begin==k.end) { + kIndex++; + continue; + } + ImVec2 pos1=ImLerp(entryRect.Min,entryRect.Max,ImVec2((double)k.begin/(double)mc->capacity,(k.type==DIV_MEMORY_N163_LOAD)?0.5f:0.0f)); + ImVec2 pos2=ImLerp(entryRect.Min,entryRect.Max,ImVec2((double)k.end/(double)mc->capacity,(k.type==DIV_MEMORY_N163_PLAY)?0.5f:1.0f)); + ImVec2 linePos=pos1; + linePos.y=rect.Max.y; + + dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_FREE+(int)k.type])); + dl->AddLine(pos1,linePos,ImGui::GetColorU32(ImGuiCol_Border),dpiScale); + + if (ImGui::GetMousePos().x>=pos1.x && + ImGui::GetMousePos().x<=pos2.x && + ImGui::GetMousePos().y>=pos1.y && + ImGui::GetMousePos().y<=pos2.y) { + curHover=kIndex; + } + kIndex++; + } + + if (mc->memory!=NULL) { + switch (mc->waveformView) { + case DIV_MEMORY_WAVE_4BIT: + for (int k=0; k<(int)(mc->capacity<<1); k++) { + unsigned char nibble=mc->memory[k>>1]; + if (k&1) { + nibble>>=4; + } else { + nibble&=15; + } + + ImVec2 pos1=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)k/(double)(mc->capacity<<1),1.0f-((float)(nibble+1)/16.0f))); + ImVec2 pos2=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)(k+1)/(double)(mc->capacity<<1),1.0f)); + dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA])); + } + break; + case DIV_MEMORY_WAVE_8BIT_SIGNED: + for (int k=0; k<(int)mc->capacity; k++) { + signed char val=(signed char)mc->memory[k]; + ImVec2 pos1=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)k/(double)(mc->capacity),1.0f-((float)(val+129)/256.0f))); + ImVec2 pos2=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)(k+1)/(double)(mc->capacity),1.0f)); + dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA])); + } + break; + default: + break; + } + } + + if (ImGui::ItemHoverable(rect,ImGui::GetID(tempID),0)) { + if (curHover>=0 && curHover<(int)mc->entries.size()) { + const DivMemoryEntry& entry=mc->entries[curHover]; + if (ImGui::BeginTooltip()) { + switch (entry.type) { + case DIV_MEMORY_SAMPLE: + case DIV_MEMORY_BANK0: + case DIV_MEMORY_BANK1: + case DIV_MEMORY_BANK2: + case DIV_MEMORY_BANK3: + case DIV_MEMORY_BANK4: + case DIV_MEMORY_BANK5: + case DIV_MEMORY_BANK6: + case DIV_MEMORY_BANK7: { + DivSample* sample=e->getSample(entry.asset); + ImGui::Text("%d: %s",curHover,sample->name.c_str()); + if ((int)entry.type>=(int)DIV_MEMORY_BANK0) { + ImGui::Text("bank %d",(int)entry.type-(int)DIV_MEMORY_BANK0); + } + ImGui::Text("%d-%d ($%x-$%x): %d bytes ($%x)",(int)entry.begin,(int)entry.end-1,(int)entry.begin,(int)entry.end-1,(int)(entry.end-entry.begin),(int)(entry.end-entry.begin)); + ImGui::Text("click to open sample editor"); + break; + } + default: + ImGui::Text("%d: %s",curHover,entry.name.c_str()); + ImGui::Text("%d-%d ($%x-$%x): %d bytes ($%x)",(int)entry.begin,(int)entry.end-1,(int)entry.begin,(int)entry.end-1,(int)(entry.end-entry.begin),(int)(entry.end-entry.begin)); + break; + } + + ImGui::EndTooltip(); + } + } + } + } } } + + if (!have) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeight()+ImGui::GetStyle().ItemSpacing.y)*0.5f); + CENTER_TEXT("no chips with memory"); + ImGui::Text("no chips with memory"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MEMORY; ImGui::End(); diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 3fed2da3b..a22f5d444 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -91,7 +91,7 @@ void FurnaceGUI::drawNewSong() { ImGui::TableNextColumn(); int index=0; for (FurnaceGUISysCategory& i: sysCategories) { - if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ + if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { newSongCategory=index; } if (ImGui::IsItemHovered()) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 18a0ebdc9..64fe7c186 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -31,11 +31,15 @@ // } // ); // flags are a string of new line-separated values. +// use SUB_ENTRY instead of ENTRY to add sub-entries to the previous entry. #define CH FurnaceGUISysDefChip #define CATEGORY_BEGIN(x,y) cat=FurnaceGUISysCategory(x,y); #define CATEGORY_END sysCategories.push_back(cat); -#define ENTRY(...) cat.systems.push_back(FurnaceGUISysDef(__VA_ARGS__)); +#define ENTRY(...) \ + cat.systems.push_back(FurnaceGUISysDef(__VA_ARGS__)); +#define SUB_ENTRY(...) \ + cat.systems[cat.systems.size()-1].subDefs.push_back(FurnaceGUISysDef(__VA_ARGS__)); void FurnaceGUI::initSystemPresets() { sysCategories.clear(); @@ -49,60 +53,60 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_SMS, 0.5f, 0, "") } ); - ENTRY( - "Sega Genesis (extended channel 3)", { - CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, "") - } - ); - ENTRY( - "Sega Genesis (CSM)", { - CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, "") - } - ); - ENTRY( - "Sega Genesis (DualPCM)", { - CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, "") - } - ); - ENTRY( - "Sega Genesis (DualPCM, extended channel 3)", { - CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, "") - } - ); - ENTRY( - "Sega Genesis (with Sega CD)", { - CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), - CH(DIV_SYSTEM_RF5C68, 1.0f, 0, - "clockSel=2\n" - "chipType=1\n" - ) - } - ); - ENTRY( - "Sega Genesis (extended channel 3 with Sega CD)", { - CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), - CH(DIV_SYSTEM_RF5C68, 1.0f, 0, - "clockSel=2\n" - "chipType=1\n" - ) - } - ); - ENTRY( - "Sega Genesis (CSM with Sega CD)", { - CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""), - CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), - CH(DIV_SYSTEM_RF5C68, 1.0f, 0, - "clockSel=2\n" - "chipType=1\n" - ) - } - ); + SUB_ENTRY( + "Sega Genesis (extended channel 3)", { + CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, "") + } + ); + SUB_ENTRY( + "Sega Genesis (CSM)", { + CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, "") + } + ); + SUB_ENTRY( + "Sega Genesis (DualPCM)", { + CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, "") + } + ); + SUB_ENTRY( + "Sega Genesis (DualPCM, extended channel 3)", { + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, "") + } + ); + SUB_ENTRY( + "Sega Genesis (with Sega CD)", { + CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_RF5C68, 1.0f, 0, + "clockSel=2\n" + "chipType=1\n" + ) + } + ); + SUB_ENTRY( + "Sega Genesis (extended channel 3 with Sega CD)", { + CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_RF5C68, 1.0f, 0, + "clockSel=2\n" + "chipType=1\n" + ) + } + ); + SUB_ENTRY( + "Sega Genesis (CSM with Sega CD)", { + CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_RF5C68, 1.0f, 0, + "clockSel=2\n" + "chipType=1\n" + ) + } + ); ENTRY( "Sega Master System", { CH(DIV_SYSTEM_SMS, 1.0f, 0, "") @@ -130,6 +134,18 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_GB, 1.0f, 0, "") } ); + ENTRY( + "Game Boy Advance (no software mixing)", { + CH(DIV_SYSTEM_GB, 1.0f, 0, "chipType=3"), + CH(DIV_SYSTEM_GBA_DMA, 0.5f, 0, ""), + } + ); + ENTRY( + "Game Boy Advance (with MinMod)", { + CH(DIV_SYSTEM_GB, 1.0f, 0, "chipType=3"), + CH(DIV_SYSTEM_GBA_MINMOD, 0.5f, 0, ""), + } + ); ENTRY( "Neo Geo Pocket", { CH(DIV_SYSTEM_T6W28, 1.0f, 0, ""), @@ -266,6 +282,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_PV1000, 1.0f, 0, "") } ); + ENTRY( + "NDS", { + CH(DIV_SYSTEM_NDS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("Computers","let's get to work on chiptune today."); @@ -2396,6 +2417,31 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, "chipType=1") } ); + ENTRY( + "Yamaha YMF276 (OPN2)", { + CH(DIV_SYSTEM_YM2612, 1.0f, 0, "chipType=2") + } + ); + ENTRY( + "Yamaha YMF276 (extended channel 3)", { + CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, "chipType=2") + } + ); + ENTRY( + "Yamaha YMF276 (OPN2) CSM", { + CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, "chipType=2") + } + ); + ENTRY( + "Yamaha YMF276 (OPN2) with DualPCM", { + CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, "chipType=2") + } + ); + ENTRY( + "Yamaha YMF276 (extended channel 3) with DualPCM", { + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, "chipType=2") + } + ); ENTRY( "Yamaha YM2413 (OPLL)", { CH(DIV_SYSTEM_OPLL, 1.0f, 0, "") @@ -2703,6 +2749,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_C219, 1.0f, 0, "") } ); + ENTRY( + "NDS", { + CH(DIV_SYSTEM_NDS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("Wavetable","chips which use user-specified waveforms to generate sound."); @@ -2873,6 +2924,11 @@ void FurnaceGUI::initSystemPresets() { }, "tickRate=50" ); + ENTRY( + "NDS", { + CH(DIV_SYSTEM_NDS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 6a3a666ee..a3c70b820 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -379,6 +379,20 @@ void FurnaceGUI::drawSampleEdit() { if (sample->samples>129024) { SAMPLE_WARN(warnLength,"MSM6295: maximum bankswitched sample length is 129024"); } + break; + case DIV_SYSTEM_GBA_DMA: + if (sample->loop) { + if (sample->loopStart&3) { + SAMPLE_WARN(warnLoopStart,"GBA DMA: loop start must be a multiple of 4"); + } + if ((sample->loopEnd-sample->loopStart)&15) { + SAMPLE_WARN(warnLoopEnd,"GBA DMA: loop length must be a multiple of 16"); + } + } + if (sample->samples&15) { + SAMPLE_WARN(warnLength,"GBA DMA: sample length will be padded to multiple of 16"); + } + break; default: break; } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index cc9db0461..33550f62f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -169,6 +169,11 @@ const char* opl3Cores[]={ "YMF262-LLE" }; +const char* esfmCores[]={ + "ESFMu", + "ESFMu (fast)" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -1536,10 +1541,10 @@ void FurnaceGUI::drawSettings() { ImGui::Text("YM2612"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::Combo("##YM2612Core",&settings.ym2612Core,ym2612Cores,2)) settingsChanged=true; + if (ImGui::Combo("##YM2612Core",&settings.ym2612Core,ym2612Cores,3)) settingsChanged=true; ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::Combo("##YM2612CoreRender",&settings.ym2612CoreRender,ym2612Cores,2)) settingsChanged=true; + if (ImGui::Combo("##YM2612CoreRender",&settings.ym2612CoreRender,ym2612Cores,3)) settingsChanged=true; ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1629,6 +1634,17 @@ void FurnaceGUI::drawSettings() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Combo("##OPL3CoreRender",&settings.opl3CoreRender,opl3Cores,3)) settingsChanged=true; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("ESFM"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##ESFMCore",&settings.esfmCore,esfmCores,2)) settingsChanged=true; + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##ESFMCoreRender",&settings.esfmCoreRender,esfmCores,2)) settingsChanged=true; + ImGui::EndTable(); } ImGui::Separator(); @@ -1759,6 +1775,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_MEMORY); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EFFECT_LIST); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CS_PLAYER); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ABOUT); UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW); UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW); @@ -3534,6 +3551,9 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE,"PowerNoise (noise)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"PowerNoise (slope)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_DAVE,"Dave"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_NDS,"NDS"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_DMA,"GBA DMA"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_MINMOD,"GBA MinMod"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -3666,6 +3686,32 @@ void FurnaceGUI::drawSettings() { ImGui::TreePop(); } + if (ImGui::TreeNode("Memory Composition")) { + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BG,"Background"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_DATA,"Waveform data"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_FREE,"Unknown"); + //UI_COLOR_CONFIG(GUI_COLOR_MEMORY_PADDING,""); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_RESERVED,"Reserved"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_SAMPLE,"Sample"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_SAMPLE_ALT1,"Sample (alternate 1)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_SAMPLE_ALT2,"Sample (alternate 2)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_SAMPLE_ALT3,"Sample (alternate 3)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_WAVE_RAM,"Wave RAM"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_WAVE_STATIC,"Wavetable (static)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_ECHO,"Echo buffer"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_N163_LOAD,"Namco 163 load pos"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_N163_PLAY,"Namco 163 play pos"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK0,"Sample (bank 0)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK1,"Sample (bank 1)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK2,"Sample (bank 2)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK3,"Sample (bank 3)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK4,"Sample (bank 4)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK5,"Sample (bank 5)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK6,"Sample (bank 6)"); + UI_COLOR_CONFIG(GUI_COLOR_MEMORY_BANK7,"Sample (bank 7)"); + + ImGui::TreePop(); + } if (ImGui::TreeNode("Log Viewer")) { UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_ERROR,"Log level: Error"); UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_WARNING,"Log level: Warning"); @@ -4084,6 +4130,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.opnCore=conf.getInt("opnCore",1); settings.opl2Core=conf.getInt("opl2Core",0); settings.opl3Core=conf.getInt("opl3Core",0); + settings.esfmCore=conf.getInt("esfmCore",0); settings.arcadeCoreRender=conf.getInt("arcadeCoreRender",1); settings.ym2612CoreRender=conf.getInt("ym2612CoreRender",0); settings.snCoreRender=conf.getInt("snCoreRender",0); @@ -4094,6 +4141,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.opnCoreRender=conf.getInt("opnCoreRender",1); settings.opl2CoreRender=conf.getInt("opl2CoreRender",0); settings.opl3CoreRender=conf.getInt("opl3CoreRender",0); + settings.esfmCoreRender=conf.getInt("esfmCoreRender",0); settings.pcSpeakerOutMethod=conf.getInt("pcSpeakerOutMethod",0); @@ -4122,6 +4170,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.opnCore,0,1); clampSetting(settings.opl2Core,0,2); clampSetting(settings.opl3Core,0,2); + clampSetting(settings.esfmCore,0,1); clampSetting(settings.arcadeCoreRender,0,1); clampSetting(settings.ym2612CoreRender,0,2); clampSetting(settings.snCoreRender,0,1); @@ -4132,6 +4181,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.opnCoreRender,0,1); clampSetting(settings.opl2CoreRender,0,2); clampSetting(settings.opl3CoreRender,0,2); + clampSetting(settings.esfmCoreRender,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -4555,6 +4605,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("opnCore",settings.opnCore); conf.set("opl2Core",settings.opl2Core); conf.set("opl3Core",settings.opl3Core); + conf.set("esfmCore",settings.esfmCore); conf.set("arcadeCoreRender",settings.arcadeCoreRender); conf.set("ym2612CoreRender",settings.ym2612CoreRender); conf.set("snCoreRender",settings.snCoreRender); @@ -4565,6 +4616,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("opnCoreRender",settings.opnCoreRender); conf.set("opl2CoreRender",settings.opl2CoreRender); conf.set("opl3CoreRender",settings.opl3CoreRender); + conf.set("esfmCoreRender",settings.esfmCoreRender); conf.set("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); @@ -4605,6 +4657,7 @@ void FurnaceGUI::commitSettings() { settings.opnCore!=e->getConfInt("opnCore",1) || settings.opl2Core!=e->getConfInt("opl2Core",0) || settings.opl3Core!=e->getConfInt("opl3Core",0) || + settings.esfmCore!=e->getConfInt("esfmCore",0) || settings.arcadeCoreRender!=e->getConfInt("arcadeCoreRender",0) || settings.ym2612CoreRender!=e->getConfInt("ym2612CoreRender",0) || settings.snCoreRender!=e->getConfInt("snCoreRender",0) || @@ -4615,6 +4668,7 @@ void FurnaceGUI::commitSettings() { settings.opnCoreRender!=e->getConfInt("opnCoreRender",1) || settings.opl2CoreRender!=e->getConfInt("opl2CoreRender",0) || settings.opl3CoreRender!=e->getConfInt("opl3CoreRender",0) || + settings.esfmCoreRender!=e->getConfInt("esfmCoreRender",0) || settings.audioQuality!=e->getConfInt("audioQuality",0) || settings.audioHiPass!=e->getConfInt("audioHiPass",1) ); @@ -5581,7 +5635,6 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",uiColors[GUI_COLOR_FILE_SONG_NATIVE],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",uiColors[GUI_COLOR_FILE_WAVE],ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",uiColors[GUI_COLOR_FILE_SONG_NATIVE],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",uiColors[GUI_COLOR_FILE_WAVE],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O); @@ -5597,12 +5650,18 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".pcf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".psf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".mod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc13",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc14",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".smod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ftm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".0cc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dnm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".eft",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fub",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); diff --git a/src/gui/speed.cpp b/src/gui/speed.cpp index 66760d44f..df0265741 100644 --- a/src/gui/speed.cpp +++ b/src/gui/speed.cpp @@ -171,6 +171,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_TEN)) { MARK_MODIFIED if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1; if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255; + e->virtualTempoChanged(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Numerator"); @@ -180,6 +181,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_TEN)) { MARK_MODIFIED if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1; if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255; + e->virtualTempoChanged(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Denominator (set to base tempo)"); diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp index 49c1f6cf6..b8c719267 100644 --- a/src/gui/stats.cpp +++ b/src/gui/stats.cpp @@ -36,24 +36,6 @@ void FurnaceGUI::drawStats() { ImGui::Text("Audio load"); ImGui::SameLine(); ImGui::ProgressBar((double)lastProcTime/maxGot,ImVec2(-FLT_MIN,0),procStr.c_str()); - ImGui::Separator(); - for (int i=0; isong.systemLen; i++) { - DivDispatch* dispatch=e->getDispatch(i); - for (int j=0; dispatch!=NULL && dispatch->getSampleMemCapacity(j)>0; j++) { - size_t capacity=dispatch->getSampleMemCapacity(j); - size_t usage=dispatch->getSampleMemUsage(j); - String usageStr; - if (settings.memUsageUnit==1) { - usageStr=fmt::sprintf("%d/%dKB",usage/1024,capacity/1024); - } else { - usageStr=fmt::sprintf("%d/%d",usage,capacity); - } - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s [%d]", e->getSystemName(e->song.system[i]), j); - ImGui::SameLine(); - ImGui::ProgressBar(((float)usage)/((float)capacity),ImVec2(-FLT_MIN,0),usageStr.c_str()); - } - } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; ImGui::End(); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index be0609c98..876d7e159 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -18,6 +18,7 @@ */ #include "../engine/chipUtils.h" +#include "../engine/platform/gbaminmod.h" #include "gui.h" #include "misc/cpp/imgui_stdlib.h" #include @@ -390,13 +391,85 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_GBA_DMA: { + int dacDepth=flags.getInt("dacDepth",9); + + ImGui::Text("DAC bit depth (reduces output rate):"); + if (CWSliderInt("##DACDepth",&dacDepth,6,9)) { + if (dacDepth<6) dacDepth=6; + if (dacDepth>9) dacDepth=9; + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("dacDepth",dacDepth); + }); + } + break; + } + case DIV_SYSTEM_GBA_MINMOD: { + supportsCustomRate=false; + int volScale=flags.getInt("volScale",4096); + int mixBufs=flags.getInt("mixBufs",15); + int dacDepth=flags.getInt("dacDepth",9); + int channels=flags.getInt("channels",16); + int sampRate=flags.getInt("sampRate",21845); + ImGui::Text("Volume scale:"); + if (CWSliderInt("##VolScale",&volScale,0,32768)) { + if (volScale<0) volScale=0; + if (volScale>32768) volScale=32768; + altered=true; + } rightClickable + ImGui::Text("Mix buffers (allows longer echo delay):"); + if (CWSliderInt("##MixBufs",&mixBufs,2,15)) { + if (mixBufs<2) mixBufs=2; + if (mixBufs>16) mixBufs=16; + altered=true; + } rightClickable + ImGui::Text("DAC bit depth (reduces output rate):"); + if (CWSliderInt("##DACDepth",&dacDepth,6,9)) { + if (dacDepth<6) dacDepth=6; + if (dacDepth>9) dacDepth=9; + altered=true; + } rightClickable + ImGui::Text("Channel limit:"); + if (CWSliderInt("##Channels",&channels,1,16)) { + if (channels<1) channels=1; + if (channels>16) channels=16; + altered=true; + } rightClickable + ImGui::Text("Sample rate:"); + if (CWSliderInt("##SampRate",&sampRate,256,65536)) { + if (sampRate<1) sampRate=21845; + if (sampRate>65536) sampRate=65536; + altered=true; + } rightClickable + DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan); + float maxCPU=dispatch->maxCPU*100; + ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock); + if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]); + ImGui::Text("Max mixer CPU usage: %.0f%%", maxCPU); + if (maxCPU>90) ImGui::PopStyleColor(); + FurnaceGUI::popWarningColor(); + if (altered) { + e->lockSave([&]() { + flags.set("volScale",volScale); + flags.set("mixBufs",mixBufs); + flags.set("dacDepth",dacDepth); + flags.set("channels",channels); + flags.set("sampRate",sampRate); + }); + } + break; + } case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: { int clockSel=flags.getInt("clockSel",0); int patchSet=flags.getInt("patchSet",0); bool noTopHatFreq=flags.getBool("noTopHatFreq",false); - bool fixedAll=flags.getBool("fixedAll",false); + bool fixedAll=flags.getBool("fixedAll",true); ImGui::Text("Clock rate:"); ImGui::Indent(); @@ -511,7 +584,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } - case DIV_SYSTEM_NES: { + case DIV_SYSTEM_NES: + case DIV_SYSTEM_5E01: { int clockSel=flags.getInt("clockSel",0); bool dpcmMode=flags.getBool("dpcmMode",true); @@ -588,6 +662,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl bool keyPriority=flags.getBool("keyPriority",true); bool no1EUpdate=flags.getBool("no1EUpdate",false); bool multiplyRel=flags.getBool("multiplyRel",false); + bool macroRace=flags.getBool("macroRace",false); int testAttack=flags.getInt("testAttack",0); int testDecay=flags.getInt("testDecay",0); int testSustain=flags.getInt("testSustain",0); @@ -667,6 +742,10 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl altered=true; } + if (ImGui::Checkbox("Cutoff macro race conditions (compatibility)",¯oRace)) { + altered=true; + } + if (altered) { e->lockSave([&]() { @@ -674,6 +753,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl flags.set("keyPriority",keyPriority); flags.set("no1EUpdate",no1EUpdate); flags.set("multiplyRel",multiplyRel); + flags.set("macroRace",macroRace); flags.set("testAttack",testAttack); flags.set("testDecay",testDecay); flags.set("testSustain",testSustain); @@ -1596,6 +1676,19 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_LYNX: { + bool tuned=flags.getBool("tuned",false); + if (ImGui::Checkbox("Consistent frequency across all duties",&tuned)) { + altered=true; + e->lockSave([&]() { + flags.set("tuned",tuned); + }); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("note: only works for an initial LFSR value of 0!"); + } + break; + } case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2: @@ -2297,6 +2390,28 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_NDS: { + int chipType=flags.getInt("chipType",0); + + ImGui::Text("Model:"); + ImGui::Indent(); + if (ImGui::RadioButton("DS (4MB RAM)",chipType==0)) { + chipType=0; + altered=true; + } + if (ImGui::RadioButton("DSi (16MB RAM)",chipType==1)) { + chipType=1; + altered=true; + } + ImGui::Unindent(); + + if (altered) { + e->lockSave([&]() { + flags.set("chipType",chipType); + }); + } + break; + } case DIV_SYSTEM_SWAN: case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_PET: diff --git a/src/icon/furIcons.h b/src/icon/furIcons.h index fe70ba1f5..dfa3b86cb 100644 --- a/src/icon/furIcons.h +++ b/src/icon/furIcons.h @@ -1,7 +1,7 @@ // not auto-generated. update every time you change icons.ttf! #define ICON_MIN_FUR 0xe0f0 -#define ICON_MAX_FUR 0xe15c +#define ICON_MAX_FUR 0xe15f // test #define ICON_FUR_TEST0 u8"\ue0f0" @@ -70,6 +70,9 @@ #define ICON_FUR_INS_POWERNOISE u8"\ue15a" #define ICON_FUR_INS_POWERNOISE_SAW u8"\ue15b" #define ICON_FUR_INS_DAVE u8"\ue15c" +#define ICON_FUR_INS_NDS u8"\ue15d" +#define ICON_FUR_INS_GBA_DMA u8"\ue15e" +#define ICON_FUR_INS_GBA_MINMOD u8"\ue15f" // sample editor #define ICON_FUR_SAMPLE_APPLY_SILENCE u8"\ue136" diff --git a/src/main.cpp b/src/main.cpp index 3b23068e6..a954759a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -72,7 +72,6 @@ bool consoleMode=true; #endif bool displayEngineFailError=false; -bool cmdOutBinary=false; bool vgmOutDirect=false; bool safeMode=false; @@ -153,11 +152,6 @@ TAParamResult pSafeModeAudio(String val) { #endif } -TAParamResult pBinary(String val) { - cmdOutBinary=true; - return TA_PARAM_SUCCESS; -} - TAParamResult pDirect(String val) { vgmOutDirect=true; return TA_PARAM_SUCCESS; @@ -218,9 +212,10 @@ TAParamResult pVersion(String) { printf("- YM3812-LLE by nukeykt (GPLv2)\n"); printf("- YMF262-LLE by nukeykt (GPLv2)\n"); printf("- YMF276-LLE by nukeykt (GPLv2)\n"); - printf("- ESFMu by Kagamiin~ (LGPLv2.1)\n"); + printf("- ESFMu (modified version) by Kagamiin~ (LGPLv2.1)\n"); printf("- ymfm by Aaron Giles (BSD 3-clause)\n"); printf("- adpcm by superctr (public domain)\n"); + printf("- adpcm-xq by David Bryant (BSD 3-clause)\n"); printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n"); printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n"); printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n"); @@ -254,6 +249,8 @@ TAParamResult pVersion(String) { printf("- D65010G031 emulator (modified version) by cam900 (zlib license)\n"); printf("- C140/C219 emulator (modified version) by cam900 (zlib license)\n"); printf("- PowerNoise emulator by scratchminer (MIT)\n"); + printf("- ep128emu by Istvan Varga (GPLv2)\n"); + printf("- NDS sound emulator by cam900 (zlib license)\n"); return TA_PARAM_QUIT; } @@ -373,7 +370,6 @@ void initParams() { params.push_back(TAParam("D","direct",false,pDirect,"","set VGM export direct stream mode")); params.push_back(TAParam("Z","zsmout",true,pZSMOut,"","output .zsm data for Commander X16 Zsound")); params.push_back(TAParam("C","cmdout",true,pCmdOut,"","output command stream")); - params.push_back(TAParam("b","binary",false,pBinary,"","set command stream output format to binary")); params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)")); params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (nothing by default)")); params.push_back(TAParam("i","info",false,pInfo,"","get info about a song")); @@ -619,7 +615,7 @@ int main(int argc, char** argv) { return 1; } fclose(f); - if (!e.load(file,(size_t)len)) { + if (!e.load(file,(size_t)len,fileName.c_str())) { reportError(fmt::sprintf("could not open file! (%s)",e.getLastError())); e.everythingOK(); finishLogFile(); @@ -660,7 +656,7 @@ int main(int argc, char** argv) { if (outName!="" || vgmOutName!="" || cmdOutName!="") { if (cmdOutName!="") { - SafeWriter* w=e.saveCommand(cmdOutBinary); + SafeWriter* w=e.saveCommand(); if (w!=NULL) { FILE* f=fopen(cmdOutName.c_str(),"wb"); if (f!=NULL) { @@ -766,13 +762,13 @@ int main(int argc, char** argv) { g.loop(); logI("closing GUI."); - g.finish(); + g.finish(true); #else logE("GUI requested but GUI not compiled!"); #endif logI("stopping engine."); - e.quit(); + e.quit(false); finishLogFile();