Merge branch 'tildearrow:master' into sysmgrtooltip_syschaninfo

This commit is contained in:
Eknous 2024-03-20 23:43:14 +04:00 committed by GitHub
commit e19370e110
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
144 changed files with 10991 additions and 1784 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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`

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/genesis/darkstar.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/misc/morepain_TIA.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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.

9
extern/ESFMu/esfm.c vendored
View file

@ -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...)

6
extern/ESFMu/esfm.h vendored
View file

@ -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

View file

@ -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;
}

View file

@ -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)

View file

@ -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;

1
extern/adpcm-xq vendored Submodule

@ -0,0 +1 @@
Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf

View file

@ -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

View file

@ -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

View file

@ -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

Binary file not shown.

View file

@ -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;
}

View file

@ -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; i<DIV_MAX_CSTRACE; i++) {
trace[i]=0;
}
}
};
class DivCSPlayer {
DivEngine* e;
unsigned char* b;
size_t bLen;
SafeReader stream;
DivCSChannelState chan[DIV_MAX_CHANS];
unsigned char fastDelays[16];
@ -77,12 +84,18 @@ class DivCSPlayer {
short vibTable[64];
public:
unsigned char* getData();
size_t getDataLen();
DivCSChannelState* getChanState(int ch);
unsigned char* getFastDelays();
unsigned char* getFastCmds();
void cleanup();
bool tick();
bool init();
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
e(en),
b(buf),
bLen(len),
stream(buf,len) {}
};

View file

@ -21,26 +21,19 @@
#include "../ta-log.h"
#define WRITE_TICK(x) \
if (binary) { \
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; \
} \
} 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; i<chans; i++) {
chanStream[i]=new SafeWriter;
chanStream[i]->init();
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; i<chans; i++) {
chanStream[i]=new SafeWriter;
chanStream[i]->init();
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; i<chans; i++) {
WRITE_TICK(i);
}
cmdStreamEnabled=oldCmdStreamEnabled;
if (binary) {
int sortCand=-1;
int sortPos=0;
while (sortPos<16) {
sortCand=-1;
for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) {
if (cmdPopularity[i]) {
if (sortCand==-1) {
sortCand=i;
} else if (cmdPopularity[sortCand]<cmdPopularity[i]) {
sortCand=i;
}
}
}
if (sortCand==-1) break;
sortedCmdPopularity[sortPos]=cmdPopularity[sortCand];
sortedCmd[sortPos]=sortCand;
cmdPopularity[sortCand]=0;
sortPos++;
}
int sortCand=-1;
int sortPos=0;
while (sortPos<16) {
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]<delayPopularity[i]) {
sortCand=i;
}
for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) {
if (cmdPopularity[i]) {
if (sortCand==-1) {
sortCand=i;
} else if (cmdPopularity[sortCand]<cmdPopularity[i]) {
sortCand=i;
}
}
if (sortCand==-1) break;
sortedDelayPopularity[sortPos]=delayPopularity[sortCand];
sortedDelay[sortPos]=sortCand;
delayPopularity[sortCand]=0;
sortPos++;
}
if (sortCand==-1) break;
for (int i=0; i<chans; i++) {
chanStream[i]->writeC(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; j<cmdLen; j++) {
next=reader->readC();
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]<delayPopularity[i]) {
sortCand=i;
}
}
}
if (sortCand==-1) break;
oldStream->finish();
delete oldStream;
sortedDelayPopularity[sortPos]=delayPopularity[sortCand];
sortedDelay[sortPos]=sortCand;
delayPopularity[sortCand]=0;
sortPos++;
}
for (int i=0; i<chans; i++) {
chanStream[i]->writeC(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; j<cmdLen; j++) {
next=reader->readC();
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; i<chans; i++) {
chanStreamOff[i]=w->tell();
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; i<chans; i++) {
w->writeI(chanStreamOff[i]);
}
for (int i=0; i<chans; i++) {
chanStreamOff[i]=w->tell();
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; i<chans; i++) {
w->writeI(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;

View file

@ -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;

View file

@ -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;

View file

@ -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.initSample<song.sampleLen) {
isUsed[i->amiga.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; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(false);
if (goal>0 || goalRow>0) {
@ -1687,6 +1704,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
}
for (int i=0; i<chans; i++) {
chan[i].cut=-1;
chan[i].cutType=0;
}
repeatPattern=oldRepeatPattern;
if (preserveDrift) {
@ -2116,6 +2134,8 @@ void DivEngine::reset() {
extValue=0;
extValuePresent=0;
speeds=curSubSong->speeds;
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<DIV_MAX_OUTPUTS; i++) {
if (oscBuf[i]!=NULL) delete[] oscBuf[i];

View file

@ -54,8 +54,8 @@ class DivWorkPool;
#define DIV_UNSTABLE
#define DIV_VERSION "dev193"
#define DIV_ENGINE_VERSION 193
#define DIV_VERSION "dev196"
#define DIV_ENGINE_VERSION 196
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -100,11 +100,11 @@ enum DivMIDIModes {
struct DivChannelState {
std::vector<DivDelayedCommand> 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<DivInstrument*>& 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),

View file

@ -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; i<len; i++) {
if (i==m.loop) {
w->writeText(" |");
}
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; i<song.systemLen; i++) {
w->writeText(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; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i];
w->writeText(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; j<ins->fm.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; j<ins->fm.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; j<ins->gb.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; i<song.waveLen; i++) {
DivWavetable* wave=song.wave[i];
w->writeText(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; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeText(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; i<bufLen; i++) {
if ((i&15)==0) w->writeText(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; i<song.subsong.size(); i++) {
DivSubSong* s=song.subsong[i];
w->writeText(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; j<s->speeds.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; j<s->ordersLen; j++) {
w->writeText(fmt::sprintf("%.2X |",j));
for (int k=0; k<chans; k++) {
w->writeText(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; j<s->ordersLen; j++) {
w->writeText(fmt::sprintf("----- ORDER %.2X\n",j));
for (int k=0; k<s->patLen; k++) {
w->writeText(fmt::sprintf("%.2X ",k));
for (int l=0; l<chans; l++) {
DivPattern* p=s->pat[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; m<s->pat[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;
}

View file

@ -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);
}

View file

@ -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,
};

File diff suppressed because it is too large Load diff

View file

@ -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; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_OPLL ||
ds.system[i]==DIV_SYSTEM_OPLL_DRUMS) {
if (!ds.systemFlags[i].has("fixedAll")) {
ds.systemFlags[i].set("fixedAll",false);
}
}
}
}
// C64 macro race
if (ds.version<195) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_C64_8580 || ds.system[i]==DIV_SYSTEM_C64_6581) {
ds.systemFlags[i].set("macroRace",true);
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();

372
src/engine/fileOps/text.cpp Normal file
View file

@ -0,0 +1,372 @@
/**
* 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 "fileOpsCommon.h"
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; i<len; i++) {
if (i==m.loop) {
w->writeText(" |");
}
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; i<song.systemLen; i++) {
w->writeText(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; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i];
w->writeText(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; j<ins->fm.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; j<ins->fm.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; j<ins->gb.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; i<song.waveLen; i++) {
DivWavetable* wave=song.wave[i];
w->writeText(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; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeText(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; i<bufLen; i++) {
if ((i&15)==0) w->writeText(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; i<song.subsong.size(); i++) {
DivSubSong* s=song.subsong[i];
w->writeText(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; j<s->speeds.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; j<s->ordersLen; j++) {
w->writeText(fmt::sprintf("%.2X |",j));
for (int k=0; k<chans; k++) {
w->writeText(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; j<s->ordersLen; j++) {
w->writeText(fmt::sprintf("----- ORDER %.2X\n",j));
for (int k=0; k<s->patLen; k++) {
w->writeText(fmt::sprintf("%.2X ",k));
for (int l=0; l<chans; l++) {
DivPattern* p=s->pat[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; m<s->pat[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;
}

View file

@ -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 {

View file

@ -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));
}
};

View file

@ -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<<chipMem;
memCompo.used=sampleMemLen;
}
int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View file

@ -113,6 +113,8 @@ class DivPlatformAmiga: public DivDispatch {
unsigned short regPool[256];
DivMemoryComposition memCompo;
unsigned char* sampleMem;
size_t sampleMemLen;
@ -162,6 +164,7 @@ class DivPlatformAmiga: 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();
};

View file

@ -560,7 +560,7 @@ int DivPlatformArcade::dispatch(DivCommand c) {
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
}
chan[c.chan].baseFreq=NOTE_LINEAR(c.value);
chan[c.chan].baseFreq=NOTE_LINEAR(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
break;
}

View file

@ -818,7 +818,6 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
clockSel=false;
dacRate=chipClock/dacRateDiv;
} else {
clockSel=flags.getBool("halfClock",false);
switch (flags.getInt("clockSel",0)) {
case 1:
chipClock=COLOR_PAL*2.0/5.0;
@ -880,6 +879,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
if (ay!=NULL) delete ay;
switch (flags.getInt("chipType",0)) {
case 1:
clockSel=flags.getBool("halfClock",false);
ay=new ym2149_device(rate,clockSel);
sunsoft=false;
intellivision=false;
@ -888,16 +888,19 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
ay=new sunsoft_5b_sound_device(rate);
sunsoft=true;
intellivision=false;
clockSel=false;
break;
case 3:
ay=new ay8914_device(rate);
sunsoft=false;
intellivision=true;
clockSel=false;
break;
default:
ay=new ay8910_device(rate);
sunsoft=false;
intellivision=false;
clockSel=false;
break;
}
ay->device_start();

View file

@ -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; i<parent->song.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) {

View file

@ -74,6 +74,7 @@ class DivPlatformC140: public DivDispatch {
FixedQueue<QueuedWrite,2048> 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();

View file

@ -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);

View file

@ -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;

View file

@ -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; i<parent->song.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) {

View file

@ -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;

View file

@ -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;

View file

@ -120,6 +120,7 @@ class DivPlatformESFM: public DivDispatch {
};
FixedQueue<QueuedWrite,2048> 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<DivRegWrite>& wlist);
void setFlags(const DivConfig& flags);
void setFast(bool fast);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformESFM();

View file

@ -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; i<parent->song.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; j<actualLength; j++) {
// convert to 8 bit unsigned
unsigned char val=((unsigned char)(s->data8[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) {

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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 <math.h>
#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<len; h++) {
// internal mixing is always 10-bit
for (int i=0; i<2; i++) {
bool newSamp=h==0;
chan[i].audDat=0;
if (chan[i].active && (chan[i].useWave || (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen))) {
chan[i].audSub+=(1<<outDepth);
if (chan[i].useWave) {
if (chan[i].audPos<(int)chan[i].audLen) {
chan[i].audDat=wtMem[i*256+chan[i].audPos];
}
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 (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].sample<parent->song.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<chan[ch].audLen; i++) {
if (addr+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; i<parent->song.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<length) {
logW("out of GBA DMA PCM memory for sample %d!",i);
break;
}
sampleLoaded[i]=true;
romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos));
// pad to multiple of 16 bytes
memPos=(memPos+15)&~15;
}
sampleMemLen=memPos;
romMemCompo.used=sampleMemLen;
}
void DivPlatformGBADMA::setFlags(const DivConfig& flags) {
outDepth=flags.getInt("dacDepth",9);
chipClock=1<<24;
CHECK_CUSTOM_CLOCK;
rate=chipClock>>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];
}
}

View file

@ -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<int> {
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<int>(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

View file

@ -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 <math.h>
#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<chanMax; i++) {
unsigned short* chReg=&regPool[i*16];
chState[i].address=chReg[0]|((uint64_t)chReg[1]<<16)|((uint64_t)chReg[2]<<32)|((uint64_t)chReg[3]<<48);
chState[i].freq=chReg[8]|((unsigned int)chReg[9]<<16);
chState[i].loopEnd=chReg[10]|((unsigned int)chReg[11]<<16);
chState[i].loopStart=chReg[12]|((unsigned int)chReg[13]<<16);
chState[i].volL=(short)chReg[14];
chState[i].volR=(short)chReg[15];
}
for (size_t h=0; h<len; h++) {
while (sampTimer>=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<chanMax; i++) {
for (size_t j=mixBufOffset; j<4; j++) {
unsigned int lastAddr=chState[i].address>>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 (newAddr<sizeof(wtMem)) {
newSamp=wtMem[newAddr];
}
break;
case 3: // echo
newAddr&=0x00007fff;
if (newAddr>0x800) {
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; i<chanMax; i++) {
oscBuf[i]->data[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; j<mixBufOffset; j++) {
mixOut[0][j]=mixOut[0][(mixBufReadPos&3)+j];
mixOut[1][j]=mixOut[1][(mixBufReadPos&3)+j];
mixBuf[mixBufPage][j]=mixOut[0][j];
mixBuf[mixBufPage+1][j]=mixOut[1][j];
}
mixBufReadPos=0;
mixBufWritePos=mixBufOffset;
// check for echo channels and give them proper addresses
for (int i=0; i<chanMax; i++) {
unsigned char echoDelay=MIN(chan[i].echo&0x0f,mixBufs-1);
if(echoDelay) {
echoDelay=echoDelay*2-((chan[i].echo&0x10)?0:1);
size_t echoPage=(mixBufPage+mixBufs*2-echoDelay)%(mixBufs*2);
chState[i].address=(0x03000800ULL+echoPage*1024)<<32;
chState[i].loopStart=0-echoDelay;
chState[i].loopEnd=0-echoDelay;
}
}
updTimer-=updCycles;
updCyclesTotal+=updCycles;
// recalculate update timer from a new tick rate
float hz=parent->getCurHz();
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<<dacDepth;
sampTimer+=1<<dacDepth;
}
// write back changed cached channel registers
for (int i=0; i<chanMax; i++) {
unsigned short* chReg=&regPool[i*16];
chReg[0]=chState[i].address&0xffff;
chReg[1]=(chState[i].address>>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; i<chanMax; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
chan[i].volChangedL=true;
chan[i].volChangedR=true;
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_FREQUENCY(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].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.chan<chanMax) {
wtMemCompo.entries[c.chan].end=wtMemCompo.entries[c.chan].begin+chan[c.chan].wtLen;
}
if (chan[c.chan].insChanged) {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
}
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,255,chan[c.chan].insChanged);
} 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 (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<chan[ch].wtLen; i++) {
wtMem[addr+i]=(signed char)(chan[ch].ws.output[i]-128);
}
}
void DivPlatformGBAMinMod::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].volChangedL=true;
chan[ch].volChangedR=true;
}
void DivPlatformGBAMinMod::forceIns() {
for (int i=0; i<chanMax; i++) {
chan[i].insChanged=true;
chan[i].volChangedL=true;
chan[i].volChangedR=true;
chan[i].sample=-1;
chan[i].active=false;
}
}
void* DivPlatformGBAMinMod::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformGBAMinMod::getChanMacroInt(int ch) {
return &chan[ch].std;
}
unsigned short DivPlatformGBAMinMod::getPan(int ch) {
return (chan[ch].chPanL<<8)|(chan[ch].chPanR);
}
DivSamplePos DivPlatformGBAMinMod::getSamplePos(int ch) {
if (ch>=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<DivRegWrite>& 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; i<parent->song.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<length) {
logW("out of GBA MinMod PCM memory for sample %d!",i);
break;
}
sampleLoaded[i]=true;
}
sampleMemLen=memPos;
romMemCompo.used=sampleMemLen;
}
void DivPlatformGBAMinMod::setFlags(const DivConfig& flags) {
volScale=flags.getInt("volScale",4096);
mixBufs=flags.getInt("mixBufs",15);
dacDepth=flags.getInt("dacDepth",9);
chanMax=flags.getInt("channels",16);
rate=16777216>>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<chanMax; i++) {
wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256));
}
for (int i=0; i<(int)mixBufs; i++) {
mixMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_ECHO, fmt::sprintf("Buffer %d Left",i),-1,i*2048,i*2048));
mixMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_ECHO, fmt::sprintf("Buffer %d Right",i),-1,i*2048+1024,i*2048+1024));
}
}
int DivPlatformGBAMinMod::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 signed char[getSampleMemCapacity()];
sampleMemLen=0;
romMemCompo=DivMemoryComposition();
romMemCompo.name="Sample ROM";
wtMemCompo=DivMemoryComposition();
wtMemCompo.name="Wavetable RAM";
wtMemCompo.capacity=256*16;
wtMemCompo.memory=(unsigned char*)wtMem;
wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED;
mixMemCompo=DivMemoryComposition();
mixMemCompo.name="Mix/Echo Buffer";
mixMemCompo.capacity=2048*15;
mixMemCompo.memory=(unsigned char*)mixBuf;
mixMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED;
setFlags(flags);
reset();
return 16;
}
void DivPlatformGBAMinMod::quit() {
delete[] sampleMem;
for (int i=0; i<16; i++) {
delete oscBuf[i];
}
}

View file

@ -0,0 +1,132 @@
/**
* 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_MINMOD_H
#define _GBA_MINMOD_H
#include "../dispatch.h"
#include "../waveSynth.h"
class DivPlatformGBAMinMod: public DivDispatch {
struct Channel: public SharedChannel<int> {
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<int>(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<DivRegWrite>& 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

View file

@ -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<len; h++) {
processDAC(rate);
int sum_l=0;
int sum_r=0;
int sample_l=0;
int sample_r=0;
bool was_reg_write=false;
//lleCycle=0;
if (!writes.empty()) {
QueuedWrite& w=writes.front();
if (w.addrOrVal) {
//logV("%.3x=%.2x",w.addr,w.val);
//OPN2_Write(&fm,0x1+((w.addr>>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;
}

View file

@ -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);

View file

@ -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; i<parent->song.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; j<actualLength; j++) {
// convert to 7 bit unsigned
unsigned char val=(unsigned char)(s->data8[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) {

View file

@ -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();

View file

@ -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; i<parent->song.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; j<actualLength; j++) {
sampleMem[memPos++]=s->dataK[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; j<actualLength; j++) {
sampleMem[memPos++]=s->data8[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) {

View file

@ -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;

View file

@ -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 v<lo?lo:(v>hi?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].sample<parent->song.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

View file

@ -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<Lynx::Mikey> 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<DivRegWrite>& wlist);

View file

@ -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) {

View file

@ -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;

View file

@ -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<DivRegWrite>& 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();

View file

@ -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);

593
src/engine/platform/nds.cpp Normal file
View file

@ -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 <math.h>
#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; i<len; i++) {
nds.tick(CLOCK_DIVIDER);
int lout=((nds.loutput()-0x200)<<5); // scale to 16 bit
int rout=((nds.routput()-0x200)<<5); // scale to 16 bit
if (lout>32767) 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 (addr<getSampleMemCapacity()) {
return sampleMem[addr];
}
return 0;
}
void DivPlatformNDS::write_byte(u32 addr, u8 data) {
if (addr<getSampleMemCapacity()) {
sampleMem[addr]=data;
}
}
void DivPlatformNDS::tick(bool sysTick) {
for (int i=0; i<16; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&0x7f)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
writeOutVol(i);
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if ((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].sample<parent->song.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<DivRegWrite>& 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; i<parent->song.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 (actualLength<length) {
logW("out of NDS PCM memory for sample %d!",i);
break;
}
// align memPos to int
memPos=(memPos+3)&~3;
sampleLoaded[i]=true;
}
sampleMemLen=memPos;
memCompo.capacity=(isDSi?16777216:4194304);
memCompo.used=sampleMemLen;
}
void DivPlatformNDS::setFlags(const DivConfig& flags) {
isDSi=flags.getBool("chipType",0);
chipClock=33513982;
rate=chipClock/2/CLOCK_DIVIDER;
for (int i=0; i<16; i++) {
oscBuf[i]->rate=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];
}
}

104
src/engine/platform/nds.h Normal file
View file

@ -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<int> {
unsigned int audPos;
int sample, wave;
int panning, duty;
bool setPos, pcm, busy;
int macroVolMul;
Channel():
SharedChannel<int>(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<DivRegWrite>& 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

View file

@ -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; i<len; i++) {
doPCM;
e1_NP->Tick(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; i<parent->song.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;
}

View file

@ -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<signed char> {
@ -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();

View file

@ -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; i<parent->song.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) {

View file

@ -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();

View file

@ -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) {

View file

@ -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; i<parent->song.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) {

View file

@ -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();

View file

@ -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; i<parent->song.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; j<actualLength; j++) {
// convert to signed magnitude
signed char val=s->data8[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) {

View file

@ -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();

View file

@ -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; i<parent->song.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<alignedSize; j++) {
if (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) {

View file

@ -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();

View file

@ -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; i<parent->song.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 (actualLength<length) {
@ -982,6 +994,11 @@ void DivPlatformSNES::renderSamples(int sysID) {
sampleLoaded[i]=true;
}
sampleMemLen=memPos;
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_ECHO,"Echo Buffer",-1,(65536-echoDelay*2048),65536));
memCompo.capacity=65536;
memCompo.used=sampleMemLen+echoDelay*2048;
memcpy(sampleMem,copyOfSampleMem,65536);
}

View file

@ -668,15 +668,17 @@ void GB_apu_run(GB_gameboy_t *gb)
if (gb->apu.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;

View file

@ -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;

View file

@ -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 {

View file

@ -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<s32>((lmix + (m_bias & 0x3ff)), 0, 0x3ff);
m_routput = clamp<s32>((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<s32>(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<s32>(m_adpcm_out + diff, -0x8000, 0x7fff);
m_adpcm_index = clamp<s32>(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<s32>(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

View file

@ -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<typename T>
static const inline T bitfield(const T in, const u8 pos)
{
return (in >> pos) & 1;
} // bitfield
template<typename T>
static const inline T bitfield(const T in, const u8 pos, const u8 len)
{
return (in >> pos) & ((1 << len) - 1);
} // bitfield
template<typename T>
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

Some files were not shown because too many files have changed in this diff Show more