Merge branch 'tildearrow:master' into sysmgrtooltip_syschaninfo
This commit is contained in:
commit
e19370e110
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
BIN
demos/arcade/Grape_Jelly_Alpha5232.fur
Normal file
BIN
demos/arcade/Grape_Jelly_Alpha5232.fur
Normal file
Binary file not shown.
Binary file not shown.
BIN
demos/genesis/SparkmanMD.fur
Normal file
BIN
demos/genesis/SparkmanMD.fur
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
demos/genesis/darkstar.fur
Normal file
BIN
demos/genesis/darkstar.fur
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
demos/misc/deepmist_dave.fur
Normal file
BIN
demos/misc/deepmist_dave.fur
Normal file
Binary file not shown.
BIN
demos/misc/morepain_TIA.fur
Normal file
BIN
demos/misc/morepain_TIA.fur
Normal file
Binary file not shown.
BIN
demos/misc/walkontheroof_T6W28.fur
Normal file
BIN
demos/misc/walkontheroof_T6W28.fur
Normal file
Binary file not shown.
BIN
demos/multichip/Namco_C30_C219_Loop.fur
Normal file
BIN
demos/multichip/Namco_C30_C219_Loop.fur
Normal file
Binary file not shown.
BIN
demos/specs2/KeygenTypeBeat.fur
Normal file
BIN
demos/specs2/KeygenTypeBeat.fur
Normal file
Binary file not shown.
BIN
demos/specs2/rastaline_dub.fur
Normal file
BIN
demos/specs2/rastaline_dub.fur
Normal file
Binary file not shown.
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
5
doc/8-advanced/memory-composition.md
Normal file
5
doc/8-advanced/memory-composition.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# memory composition
|
||||
|
||||
this window displays the memory usage of chips that support memory (e.g. for samples).
|
||||
|
||||

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

|
||||

|
||||
|
|
4
extern/ESFMu/README.md
vendored
4
extern/ESFMu/README.md
vendored
|
@ -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
9
extern/ESFMu/esfm.c
vendored
|
@ -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
6
extern/ESFMu/esfm.h
vendored
|
@ -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
|
||||
|
|
3
extern/ESFMu/esfm_registers.c
vendored
3
extern/ESFMu/esfm_registers.c
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
14
extern/YMF276-LLE/fmopn2.c
vendored
14
extern/YMF276-LLE/fmopn2.c
vendored
|
@ -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)
|
||||
|
|
3
extern/YMF276-LLE/fmopn2.h
vendored
3
extern/YMF276-LLE/fmopn2.h
vendored
|
@ -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
1
extern/adpcm-xq
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
430
res/icons.sfd
430
res/icons.sfd
|
@ -21,7 +21,7 @@ OS2Version: 0
|
|||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 0
|
||||
CreationTime: 1691897631
|
||||
ModificationTime: 1706927377
|
||||
ModificationTime: 1710664281
|
||||
PfmFamily: 81
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
|
@ -53,7 +53,7 @@ FitToEm: 0
|
|||
WinInfo: 57540 21 12
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
BeginChars: 65536 98
|
||||
BeginChars: 65536 102
|
||||
|
||||
StartChar: space
|
||||
Encoding: 32 32 0
|
||||
|
@ -6833,7 +6833,7 @@ EndChar
|
|||
StartChar: uniE15B
|
||||
Encoding: 57691 57691 97
|
||||
Width: 1792
|
||||
Flags: HWO
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
|
@ -6882,5 +6882,429 @@ SplineSet
|
|||
1282.36914062 744.515625 1209.40039062 868.03125 1136.43164062 991.546875 c 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
|
||||
StartChar: uniE15C
|
||||
Encoding: 57692 57692 98
|
||||
Width: 1792
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
543.03125 -247.515625 m 1
|
||||
469.75 -24.15625 396.46875 199.203125 323.1875 422.5625 c 1
|
||||
360.635742188 422.5625 398.083007812 422.5625 435.53125 422.5625 c 1
|
||||
471.051757812 312.171875 506.573242188 201.78125 542.09375 91.390625 c 1
|
||||
563.526367188 13.6298828125 584.958007812 -64.1298828125 606.390625 -141.890625 c 1
|
||||
607.666992188 -141.890625 608.942382812 -141.890625 610.21875 -141.890625 c 1
|
||||
631.989257812 -64.1298828125 653.760742188 13.6298828125 675.53125 91.390625 c 1
|
||||
711.051757812 201.78125 746.573242188 312.171875 782.09375 422.5625 c 1
|
||||
818.578125 422.5625 855.0625 422.5625 891.546875 422.5625 c 1
|
||||
817.301757812 199.203125 743.057617188 -24.15625 668.8125 -247.515625 c 1
|
||||
626.885742188 -247.515625 584.958007812 -247.515625 543.03125 -247.515625 c 1
|
||||
1028.5 -247.515625 m 1
|
||||
1028.5 -24.15625 1028.5 199.203125 1028.5 422.5625 c 1
|
||||
1171.546875 422.5625 1314.59375 422.5625 1457.640625 422.5625 c 1
|
||||
1457.640625 390.557617188 1457.640625 358.551757812 1457.640625 326.546875 c 1
|
||||
1350.73925781 326.546875 1243.83886719 326.546875 1136.9375 326.546875 c 1
|
||||
1136.9375 264.463867188 1136.9375 202.379882812 1136.9375 140.296875 c 1
|
||||
1233.91699219 140.296875 1330.89550781 140.296875 1427.875 140.296875 c 1
|
||||
1427.875 108.317382812 1427.875 76.3388671875 1427.875 44.359375 c 1
|
||||
1330.89550781 44.359375 1233.91699219 44.359375 1136.9375 44.359375 c 1
|
||||
1136.9375 -20.9267578125 1136.9375 -86.2138671875 1136.9375 -151.5 c 1
|
||||
1243.83886719 -151.5 1350.73925781 -151.5 1457.640625 -151.5 c 1
|
||||
1457.640625 -183.504882812 1457.640625 -215.510742188 1457.640625 -247.515625 c 1
|
||||
1314.59375 -247.515625 1171.546875 -247.515625 1028.5 -247.515625 c 1
|
||||
310.0625 1288.578125 m 1
|
||||
389.125 1288.578125 468.1875 1288.578125 547.25 1288.578125 c 1
|
||||
590.0625 1288.578125 628.96875 1281.546875 663.8125 1267.484375 c 128
|
||||
698.65625 1253.34375 728.5 1232.40625 753.1875 1204.59375 c 128
|
||||
777.71875 1176.703125 796.78125 1141.859375 810.21875 1099.90625 c 128
|
||||
823.65625 1058.03125 830.375 1009.203125 830.375 953.578125 c 256
|
||||
830.375 897.875 823.65625 849.046875 810.21875 807.171875 c 128
|
||||
796.78125 765.21875 777.71875 730.375 753.1875 702.484375 c 128
|
||||
728.5 674.671875 698.65625 653.734375 663.8125 639.59375 c 128
|
||||
628.96875 625.53125 590.0625 618.5 547.25 618.5 c 1
|
||||
468.1875 618.5 389.125 618.5 310.0625 618.5 c 1
|
||||
310.0625 841.859375 310.0625 1065.21875 310.0625 1288.578125 c 1
|
||||
547.25 714.515625 m 1
|
||||
597.09375 714.515625 637.40625 730.21875 668.1875 761.546875 c 128
|
||||
698.8125 792.875 714.28125 838.96875 714.28125 899.75 c 1
|
||||
714.28125 935.609375 714.28125 971.46875 714.28125 1007.328125 c 1
|
||||
714.28125 1068.109375 698.8125 1114.203125 668.1875 1145.53125 c 128
|
||||
637.40625 1176.9375 597.09375 1192.5625 547.25 1192.5625 c 1
|
||||
504.333007812 1192.5625 461.416992188 1192.5625 418.5 1192.5625 c 1
|
||||
418.5 1033.21386719 418.5 873.864257812 418.5 714.515625 c 1
|
||||
461.416992188 714.515625 504.333007812 714.515625 547.25 714.515625 c 1
|
||||
1368.65625 618.5 m 1
|
||||
1348.5 678.96875 1328.34375 739.4375 1308.1875 799.90625 c 1
|
||||
1224.64550781 799.90625 1141.10449219 799.90625 1057.5625 799.90625 c 1
|
||||
1038.03125 739.4375 1018.5 678.96875 998.96875 618.5 c 1
|
||||
962.198242188 618.5 925.426757812 618.5 888.65625 618.5 c 1
|
||||
964.801757812 841.859375 1040.94824219 1065.21875 1117.09375 1288.578125 c 1
|
||||
1162.5625 1288.578125 1208.03125 1288.578125 1253.5 1288.578125 c 1
|
||||
1329.64550781 1065.21875 1405.79199219 841.859375 1481.9375 618.5 c 1
|
||||
1444.17675781 618.5 1406.41699219 618.5 1368.65625 618.5 c 1
|
||||
1185.21875 1188.734375 m 1
|
||||
1183.65625 1188.734375 1182.09375 1188.734375 1180.53125 1188.734375 c 1
|
||||
1148.1875 1090.16699219 1115.84375 991.598632812 1083.5 893.03125 c 1
|
||||
1149.4375 893.03125 1215.375 893.03125 1281.3125 893.03125 c 1
|
||||
1249.28125 991.598632812 1217.25 1090.16699219 1185.21875 1188.734375 c 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
|
||||
StartChar: uniE15D
|
||||
Encoding: 57693 57693 99
|
||||
Width: 1792
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
218.8515625 936.797851562 m 1
|
||||
317.614257812 936.797851562 416.377929688 936.797851562 515.140625 936.797851562 c 1
|
||||
568.8515625 936.797851562 617.2890625 928.008789062 661.0390625 910.430664062 c 128
|
||||
704.59375 892.754882812 741.703125 866.583007812 772.5625 831.817382812 c 128
|
||||
803.421875 796.954101562 827.25 753.399414062 844.046875 700.958007812 c 128
|
||||
860.84375 648.614257812 869.2421875 587.579101562 869.2421875 518.047851562 c 256
|
||||
869.2421875 448.418945312 860.84375 387.383789062 844.046875 335.040039062 c 128
|
||||
827.25 282.598632812 803.421875 239.043945312 772.5625 204.180664062 c 128
|
||||
741.703125 169.415039062 704.59375 143.243164062 661.0390625 125.567382812 c 128
|
||||
617.2890625 107.989257812 568.8515625 99.2001953125 515.140625 99.2001953125 c 1
|
||||
416.377929688 99.2001953125 317.614257812 99.2001953125 218.8515625 99.2001953125 c 1
|
||||
218.8515625 378.399414062 218.8515625 657.598632812 218.8515625 936.797851562 c 1
|
||||
515.140625 219.219726562 m 1
|
||||
577.640625 219.219726562 628.03125 238.848632812 666.3125 278.008789062 c 128
|
||||
704.7890625 317.168945312 723.9296875 374.786132812 723.9296875 450.762695312 c 1
|
||||
723.9296875 495.586914062 723.9296875 540.411132812 723.9296875 585.235351562 c 1
|
||||
723.9296875 661.211914062 704.7890625 718.829101562 666.3125 757.989257812 c 128
|
||||
628.03125 797.247070312 577.640625 816.778320312 515.140625 816.778320312 c 1
|
||||
461.559570312 816.778320312 407.979492188 816.778320312 354.3984375 816.778320312 c 1
|
||||
354.3984375 617.591796875 354.3984375 418.405273438 354.3984375 219.219726562 c 1
|
||||
407.979492188 219.219726562 461.559570312 219.219726562 515.140625 219.219726562 c 1
|
||||
1269.6328125 84.8447265625 m 0
|
||||
1199.125 84.8447265625 1139.359375 97.6376953125 1090.140625 123.223632812 c 128
|
||||
1040.921875 148.809570312 998.734375 183.184570312 963.578125 226.446289062 c 1
|
||||
994.762695312 255.645507812 1025.94824219 284.844726562 1057.1328125 314.043945312 c 1
|
||||
1086.8203125 278.008789062 1119.4375 250.762695312 1154.984375 232.403320312 c 128
|
||||
1190.53125 214.043945312 1231.15625 204.766601562 1276.859375 204.766601562 c 0
|
||||
1330.375 204.766601562 1370.8046875 216.778320312 1397.953125 240.801757812 c 128
|
||||
1425.1015625 264.825195312 1438.7734375 297.247070312 1438.7734375 337.969726562 c 0
|
||||
1438.7734375 370.782226562 1429.203125 396.758789062 1410.0625 415.997070312 c 128
|
||||
1390.7265625 435.235351562 1356.7421875 449.590820312 1307.9140625 459.161132812 c 1
|
||||
1283.109375 463.587890625 1258.3046875 468.014648438 1233.5 472.442382812 c 1
|
||||
1152.0546875 487.579101562 1090.7265625 514.434570312 1049.90625 552.813476562 c 128
|
||||
1009.0859375 591.192382812 988.7734375 644.024414062 988.7734375 711.211914062 c 0
|
||||
988.7734375 748.028320312 995.8046875 781.426757812 1009.8671875 811.407226562 c 128
|
||||
1023.734375 841.387695312 1043.4609375 866.583007812 1068.65625 886.993164062 c 128
|
||||
1093.8515625 907.403320312 1124.3203125 923.223632812 1160.453125 934.356445312 c 128
|
||||
1196.390625 945.586914062 1236.8203125 951.153320312 1281.546875 951.153320312 c 0
|
||||
1344.828125 951.153320312 1399.515625 940.215820312 1446 918.243164062 c 128
|
||||
1492.2890625 896.172851562 1531.9375 864.434570312 1564.75 822.833007812 c 1
|
||||
1533.17480469 794.837890625 1501.59863281 766.842773438 1470.0234375 738.848632812 c 1
|
||||
1448.34375 766.778320312 1421.9765625 789.239257812 1390.7265625 806.036132812 c 128
|
||||
1359.671875 822.833007812 1320.8046875 831.231445312 1274.3203125 831.231445312 c 0
|
||||
1226.46875 831.231445312 1189.1640625 821.563476562 1162.796875 802.422851562 c 128
|
||||
1136.4296875 783.184570312 1123.1484375 755.157226562 1123.1484375 718.438476562 c 0
|
||||
1123.1484375 683.184570312 1133.890625 657.012695312 1155.5703125 639.825195312 c 128
|
||||
1177.25 622.637695312 1210.84375 609.551757812 1256.3515625 600.762695312 c 1
|
||||
1281.15625 595.586914062 1305.9609375 590.411132812 1330.765625 585.235351562 c 1
|
||||
1414.75 569.219726562 1476.2734375 541.973632812 1514.9453125 503.594726562 c 128
|
||||
1553.8125 465.215820312 1573.1484375 412.383789062 1573.1484375 345.196289062 c 0
|
||||
1573.1484375 306.036132812 1566.3125 270.391601562 1552.8359375 238.360351562 c 128
|
||||
1539.1640625 206.426757812 1519.4375 178.985351562 1493.4609375 156.231445312 c 128
|
||||
1467.2890625 133.379882812 1435.6484375 115.801757812 1397.953125 103.399414062 c 128
|
||||
1360.453125 90.9970703125 1317.484375 84.8447265625 1269.6328125 84.8447265625 c 0
|
||||
EndSplineSet
|
||||
EndChar
|
||||
|
||||
StartChar: uniE15E
|
||||
Encoding: 57694 57694 100
|
||||
Width: 1792
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
11.3125 422.5625 m 1
|
||||
84.90625 422.5625 158.5 422.5625 232.09375 422.5625 c 1
|
||||
271.78125 422.5625 307.171875 416.15625 338.1875 403.34375 c 128
|
||||
369.28125 390.53125 395.453125 370.53125 416.9375 343.34375 c 128
|
||||
438.34375 316.15625 454.671875 281.46875 465.921875 239.203125 c 128
|
||||
477.09375 196.9375 482.71875 146.390625 482.71875 87.5625 c 256
|
||||
482.71875 28.65625 477.09375 -21.890625 465.921875 -64.15625 c 128
|
||||
454.671875 -106.421875 438.34375 -141.109375 416.9375 -168.296875 c 128
|
||||
395.453125 -195.484375 369.28125 -215.484375 338.1875 -228.296875 c 128
|
||||
307.171875 -241.109375 271.78125 -247.515625 232.09375 -247.515625 c 1
|
||||
158.5 -247.515625 84.90625 -247.515625 11.3125 -247.515625 c 1
|
||||
11.3125 -24.15625 11.3125 199.203125 11.3125 422.5625 c 1
|
||||
232.09375 -158.21875 m 1
|
||||
276.3125 -158.21875 310.84375 -144 335.84375 -115.484375 c 128
|
||||
360.765625 -87.046875 373.265625 -45.25 373.265625 9.75 c 1
|
||||
373.265625 61.5986328125 373.265625 113.448242188 373.265625 165.296875 c 1
|
||||
373.265625 220.296875 360.765625 262.09375 335.84375 290.53125 c 128
|
||||
310.84375 319.046875 276.3125 333.265625 232.09375 333.265625 c 1
|
||||
192.432617188 333.265625 152.770507812 333.265625 113.109375 333.265625 c 1
|
||||
113.109375 169.4375 113.109375 5.609375 113.109375 -158.21875 c 1
|
||||
152.770507812 -158.21875 192.432617188 -158.21875 232.09375 -158.21875 c 1
|
||||
1031.15625 165.296875 m 1
|
||||
1033.39550781 203.370117188 1035.63574219 241.442382812 1037.875 279.515625 c 1
|
||||
1034.046875 279.515625 1030.21875 279.515625 1026.390625 279.515625 c 1
|
||||
1011.02636719 242.067382812 995.661132812 204.620117188 980.296875 167.171875 c 1
|
||||
936.78125 76.3125 893.265625 -14.546875 849.75 -105.40625 c 1
|
||||
806.546875 -14.546875 763.34375 76.3125 720.140625 167.171875 c 1
|
||||
704.776367188 203.995117188 689.411132812 240.817382812 674.046875 277.640625 c 1
|
||||
670.21875 277.640625 666.390625 277.640625 662.5625 277.640625 c 1
|
||||
664.801757812 240.192382812 667.041992188 202.745117188 669.28125 165.296875 c 1
|
||||
669.28125 27.6923828125 669.28125 -109.911132812 669.28125 -247.515625 c 1
|
||||
636.625 -247.515625 603.96875 -247.515625 571.3125 -247.515625 c 1
|
||||
571.3125 -24.15625 571.3125 199.203125 571.3125 422.5625 c 1
|
||||
611.3125 422.5625 651.3125 422.5625 691.3125 422.5625 c 1
|
||||
727.484375 344.489257812 763.65625 266.416992188 799.828125 188.34375 c 1
|
||||
815.504882812 144.176757812 831.182617188 100.010742188 846.859375 55.84375 c 1
|
||||
849.723632812 55.84375 852.588867188 55.84375 855.453125 55.84375 c 1
|
||||
871.15625 100.010742188 886.859375 144.176757812 902.5625 188.34375 c 1
|
||||
938.395507812 266.416992188 974.229492188 344.489257812 1010.0625 422.5625 c 1
|
||||
1049.75 422.5625 1089.4375 422.5625 1129.125 422.5625 c 1
|
||||
1129.125 199.203125 1129.125 -24.15625 1129.125 -247.515625 c 1
|
||||
1096.46875 -247.515625 1063.8125 -247.515625 1031.15625 -247.515625 c 1
|
||||
1031.15625 -109.911132812 1031.15625 27.6923828125 1031.15625 165.296875 c 1
|
||||
1666.703125 -247.515625 m 1
|
||||
1649.41113281 -185.745117188 1632.12011719 -123.973632812 1614.828125 -62.203125 c 1
|
||||
1539.64550781 -62.203125 1464.46386719 -62.203125 1389.28125 -62.203125 c 1
|
||||
1371.98925781 -123.973632812 1354.69824219 -185.745117188 1337.40625 -247.515625 c 1
|
||||
1304.125 -247.515625 1270.84375 -247.515625 1237.5625 -247.515625 c 1
|
||||
1304.4375 -24.15625 1371.3125 199.203125 1438.1875 422.5625 c 1
|
||||
1481.390625 422.5625 1524.59375 422.5625 1567.796875 422.5625 c 1
|
||||
1635.01074219 199.203125 1702.22363281 -24.15625 1769.4375 -247.515625 c 1
|
||||
1735.19238281 -247.515625 1700.94824219 -247.515625 1666.703125 -247.515625 c 1
|
||||
1528.421875 232.484375 m 1
|
||||
1521.390625 265.114257812 1514.359375 297.745117188 1507.328125 330.375 c 1
|
||||
1503.16113281 330.375 1498.99511719 330.375 1494.828125 330.375 c 1
|
||||
1487.796875 297.745117188 1480.765625 265.114257812 1473.734375 232.484375 c 1
|
||||
1453.890625 162.09375 1434.046875 91.703125 1414.203125 21.3125 c 1
|
||||
1473.08300781 21.3125 1531.96386719 21.3125 1590.84375 21.3125 c 1
|
||||
1570.03613281 91.703125 1549.22949219 162.09375 1528.421875 232.484375 c 1
|
||||
456.3125 717.40625 m 1
|
||||
452.458007812 717.40625 448.604492188 717.40625 444.75 717.40625 c 1
|
||||
438.421875 685.375 422.09375 658.96875 395.84375 638.1875 c 128
|
||||
369.59375 617.40625 332.484375 607.015625 284.4375 607.015625 c 0
|
||||
249.90625 607.015625 218.03125 613.34375 188.96875 626.15625 c 128
|
||||
159.828125 638.96875 134.359375 659.28125 112.640625 687.171875 c 128
|
||||
90.84375 714.984375 73.890625 750.84375 61.78125 794.671875 c 128
|
||||
49.59375 838.5 43.5 891.15625 43.5 952.5625 c 256
|
||||
43.5 1014.046875 49.4375 1066.625 61.234375 1110.53125 c 128
|
||||
73.109375 1154.359375 90.375 1190.375 113.109375 1218.5 c 128
|
||||
135.84375 1246.625 163.1875 1267.328125 195.21875 1280.453125 c 128
|
||||
227.171875 1293.578125 263.34375 1300.0625 303.65625 1300.0625 c 0
|
||||
363.1875 1300.0625 411.546875 1287.328125 448.65625 1261.703125 c 128
|
||||
485.765625 1236.078125 514.515625 1199.59375 535.0625 1152.25 c 1
|
||||
507.223632812 1135.921875 479.385742188 1119.59375 451.546875 1103.265625 c 1
|
||||
439.984375 1135.921875 422.5625 1161.859375 399.203125 1181.078125 c 128
|
||||
375.84375 1200.296875 344.28125 1209.828125 304.59375 1209.828125 c 0
|
||||
256.625 1209.828125 219.359375 1194.515625 192.796875 1163.8125 c 128
|
||||
166.234375 1133.03125 152.953125 1090.21875 152.953125 1035.140625 c 1
|
||||
152.953125 980.739257812 152.953125 926.338867188 152.953125 871.9375 c 1
|
||||
152.953125 816.9375 166.234375 774.046875 192.796875 743.265625 c 128
|
||||
219.359375 712.5625 256.625 697.25 304.59375 697.25 c 0
|
||||
324.4375 697.25 343.03125 700.0625 360.296875 705.84375 c 128
|
||||
377.5625 711.625 392.796875 719.90625 405.921875 730.84375 c 128
|
||||
419.046875 741.703125 429.28125 755.296875 436.625 771.625 c 128
|
||||
443.96875 787.953125 447.640625 806.3125 447.640625 826.78125 c 1
|
||||
447.640625 846.3125 447.640625 865.84375 447.640625 885.375 c 1
|
||||
406.364257812 885.375 365.088867188 885.375 323.8125 885.375 c 1
|
||||
323.8125 914.489257812 323.8125 943.604492188 323.8125 972.71875 c 1
|
||||
398.057617188 972.71875 472.301757812 972.71875 546.546875 972.71875 c 1
|
||||
546.546875 854.645507812 546.546875 736.573242188 546.546875 618.5 c 1
|
||||
516.46875 618.5 486.390625 618.5 456.3125 618.5 c 1
|
||||
456.3125 651.46875 456.3125 684.4375 456.3125 717.40625 c 1
|
||||
710.375 1288.578125 m 1
|
||||
800.948242188 1288.578125 891.520507812 1288.578125 982.09375 1288.578125 c 1
|
||||
1033.890625 1288.578125 1073.265625 1273.890625 1100.140625 1244.4375 c 128
|
||||
1127.015625 1214.984375 1140.453125 1172.09375 1140.453125 1115.765625 c 0
|
||||
1140.453125 1072.875 1131.15625 1039.28125 1112.640625 1014.984375 c 128
|
||||
1094.046875 990.6875 1067.484375 976.9375 1032.953125 973.734375 c 1
|
||||
1032.953125 969.879882812 1032.953125 966.026367188 1032.953125 962.171875 c 1
|
||||
1052.171875 962.171875 1069.59375 958.5 1085.296875 951.15625 c 128
|
||||
1100.921875 943.8125 1114.515625 933.34375 1126.078125 919.90625 c 128
|
||||
1137.5625 906.46875 1146.546875 890.53125 1152.953125 871.9375 c 128
|
||||
1159.359375 853.34375 1162.5625 833.1875 1162.5625 811.46875 c 0
|
||||
1162.5625 782.640625 1158.890625 756.390625 1151.546875 732.71875 c 128
|
||||
1144.125 709.046875 1133.734375 688.734375 1120.296875 671.78125 c 128
|
||||
1106.859375 654.828125 1090.6875 641.703125 1071.859375 632.40625 c 128
|
||||
1052.953125 623.109375 1032.015625 618.5 1008.96875 618.5 c 1
|
||||
909.4375 618.5 809.90625 618.5 710.375 618.5 c 1
|
||||
710.375 841.859375 710.375 1065.21875 710.375 1288.578125 c 1
|
||||
812.171875 707.796875 m 1
|
||||
867.848632812 707.796875 923.526367188 707.796875 979.203125 707.796875 c 1
|
||||
1003.5 707.796875 1022.09375 714.359375 1034.90625 727.484375 c 128
|
||||
1047.640625 740.609375 1054.046875 761.546875 1054.046875 790.375 c 1
|
||||
1054.046875 805.088867188 1054.046875 819.801757812 1054.046875 834.515625 c 1
|
||||
1054.046875 863.265625 1047.640625 884.28125 1034.90625 897.40625 c 128
|
||||
1022.09375 910.53125 1003.5 917.09375 979.203125 917.09375 c 1
|
||||
923.526367188 917.09375 867.848632812 917.09375 812.171875 917.09375 c 1
|
||||
812.171875 847.328125 812.171875 777.5625 812.171875 707.796875 c 1
|
||||
812.171875 1004.4375 m 1
|
||||
862.40625 1004.4375 912.640625 1004.4375 962.875 1004.4375 c 1
|
||||
985.296875 1004.4375 1002.5625 1010.375 1014.671875 1022.171875 c 128
|
||||
1026.859375 1034.046875 1032.953125 1053.34375 1032.953125 1080.296875 c 1
|
||||
1032.953125 1094.671875 1032.953125 1109.046875 1032.953125 1123.421875 c 1
|
||||
1032.953125 1150.375 1026.859375 1169.671875 1014.671875 1181.546875 c 128
|
||||
1002.5625 1193.34375 985.296875 1199.28125 962.875 1199.28125 c 1
|
||||
912.640625 1199.28125 862.40625 1199.28125 812.171875 1199.28125 c 1
|
||||
812.171875 1134.33300781 812.171875 1069.38574219 812.171875 1004.4375 c 1
|
||||
1645.765625 618.5 m 1
|
||||
1628.47363281 680.270507812 1611.18261719 742.041992188 1593.890625 803.8125 c 1
|
||||
1518.70800781 803.8125 1443.52636719 803.8125 1368.34375 803.8125 c 1
|
||||
1351.05175781 742.041992188 1333.76074219 680.270507812 1316.46875 618.5 c 1
|
||||
1283.1875 618.5 1249.90625 618.5 1216.625 618.5 c 1
|
||||
1283.5 841.859375 1350.375 1065.21875 1417.25 1288.578125 c 1
|
||||
1460.453125 1288.578125 1503.65625 1288.578125 1546.859375 1288.578125 c 1
|
||||
1614.07324219 1065.21875 1681.28613281 841.859375 1748.5 618.5 c 1
|
||||
1714.25488281 618.5 1680.01074219 618.5 1645.765625 618.5 c 1
|
||||
1507.484375 1098.5 m 1
|
||||
1500.453125 1131.12988281 1493.421875 1163.76074219 1486.390625 1196.390625 c 1
|
||||
1482.22363281 1196.390625 1478.05761719 1196.390625 1473.890625 1196.390625 c 1
|
||||
1466.859375 1163.76074219 1459.828125 1131.12988281 1452.796875 1098.5 c 1
|
||||
1432.953125 1028.109375 1413.109375 957.71875 1393.265625 887.328125 c 1
|
||||
1452.14550781 887.328125 1511.02636719 887.328125 1569.90625 887.328125 c 1
|
||||
1549.09863281 957.71875 1528.29199219 1028.109375 1507.484375 1098.5 c 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
|
||||
StartChar: uniE15F
|
||||
Encoding: 57695 57695 101
|
||||
Width: 1792
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
711.3125 165.296875 m 1
|
||||
713.551757812 203.370117188 715.791992188 241.442382812 718.03125 279.515625 c 1
|
||||
714.203125 279.515625 710.375 279.515625 706.546875 279.515625 c 1
|
||||
691.182617188 242.067382812 675.817382812 204.620117188 660.453125 167.171875 c 1
|
||||
616.9375 76.3125 573.421875 -14.546875 529.90625 -105.40625 c 1
|
||||
486.703125 -14.546875 443.5 76.3125 400.296875 167.171875 c 1
|
||||
384.932617188 203.995117188 369.567382812 240.817382812 354.203125 277.640625 c 1
|
||||
350.375 277.640625 346.546875 277.640625 342.71875 277.640625 c 1
|
||||
344.958007812 240.192382812 347.198242188 202.745117188 349.4375 165.296875 c 1
|
||||
349.4375 27.6923828125 349.4375 -109.911132812 349.4375 -247.515625 c 1
|
||||
316.78125 -247.515625 284.125 -247.515625 251.46875 -247.515625 c 1
|
||||
251.46875 -24.15625 251.46875 199.203125 251.46875 422.5625 c 1
|
||||
291.46875 422.5625 331.46875 422.5625 371.46875 422.5625 c 1
|
||||
407.640625 344.489257812 443.8125 266.416992188 479.984375 188.34375 c 1
|
||||
495.661132812 144.176757812 511.338867188 100.010742188 527.015625 55.84375 c 1
|
||||
529.879882812 55.84375 532.745117188 55.84375 535.609375 55.84375 c 1
|
||||
551.3125 100.010742188 567.015625 144.176757812 582.71875 188.34375 c 1
|
||||
618.551757812 266.416992188 654.385742188 344.489257812 690.21875 422.5625 c 1
|
||||
729.90625 422.5625 769.59375 422.5625 809.28125 422.5625 c 1
|
||||
809.28125 199.203125 809.28125 -24.15625 809.28125 -247.515625 c 1
|
||||
776.625 -247.515625 743.96875 -247.515625 711.3125 -247.515625 c 1
|
||||
711.3125 -109.911132812 711.3125 27.6923828125 711.3125 165.296875 c 1
|
||||
1431.3125 165.296875 m 1
|
||||
1433.55175781 203.370117188 1435.79199219 241.442382812 1438.03125 279.515625 c 1
|
||||
1434.203125 279.515625 1430.375 279.515625 1426.546875 279.515625 c 1
|
||||
1411.18261719 242.067382812 1395.81738281 204.620117188 1380.453125 167.171875 c 1
|
||||
1336.9375 76.3125 1293.421875 -14.546875 1249.90625 -105.40625 c 1
|
||||
1206.703125 -14.546875 1163.5 76.3125 1120.296875 167.171875 c 1
|
||||
1104.93261719 203.995117188 1089.56738281 240.817382812 1074.203125 277.640625 c 1
|
||||
1070.375 277.640625 1066.546875 277.640625 1062.71875 277.640625 c 1
|
||||
1064.95800781 240.192382812 1067.19824219 202.745117188 1069.4375 165.296875 c 1
|
||||
1069.4375 27.6923828125 1069.4375 -109.911132812 1069.4375 -247.515625 c 1
|
||||
1036.78125 -247.515625 1004.125 -247.515625 971.46875 -247.515625 c 1
|
||||
971.46875 -24.15625 971.46875 199.203125 971.46875 422.5625 c 1
|
||||
1011.46875 422.5625 1051.46875 422.5625 1091.46875 422.5625 c 1
|
||||
1127.640625 344.489257812 1163.8125 266.416992188 1199.984375 188.34375 c 1
|
||||
1215.66113281 144.176757812 1231.33886719 100.010742188 1247.015625 55.84375 c 1
|
||||
1249.87988281 55.84375 1252.74511719 55.84375 1255.609375 55.84375 c 1
|
||||
1271.3125 100.010742188 1287.015625 144.176757812 1302.71875 188.34375 c 1
|
||||
1338.55175781 266.416992188 1374.38574219 344.489257812 1410.21875 422.5625 c 1
|
||||
1449.90625 422.5625 1489.59375 422.5625 1529.28125 422.5625 c 1
|
||||
1529.28125 199.203125 1529.28125 -24.15625 1529.28125 -247.515625 c 1
|
||||
1496.625 -247.515625 1463.96875 -247.515625 1431.3125 -247.515625 c 1
|
||||
1431.3125 -109.911132812 1431.3125 27.6923828125 1431.3125 165.296875 c 1
|
||||
456.3125 717.40625 m 1
|
||||
452.458007812 717.40625 448.604492188 717.40625 444.75 717.40625 c 1
|
||||
438.421875 685.375 422.09375 658.96875 395.84375 638.1875 c 128
|
||||
369.59375 617.40625 332.484375 607.015625 284.4375 607.015625 c 0
|
||||
249.90625 607.015625 218.03125 613.34375 188.96875 626.15625 c 128
|
||||
159.828125 638.96875 134.359375 659.28125 112.640625 687.171875 c 128
|
||||
90.84375 714.984375 73.890625 750.84375 61.78125 794.671875 c 128
|
||||
49.59375 838.5 43.5 891.15625 43.5 952.5625 c 256
|
||||
43.5 1014.046875 49.4375 1066.625 61.234375 1110.53125 c 128
|
||||
73.109375 1154.359375 90.375 1190.375 113.109375 1218.5 c 128
|
||||
135.84375 1246.625 163.1875 1267.328125 195.21875 1280.453125 c 128
|
||||
227.171875 1293.578125 263.34375 1300.0625 303.65625 1300.0625 c 0
|
||||
363.1875 1300.0625 411.546875 1287.328125 448.65625 1261.703125 c 128
|
||||
485.765625 1236.078125 514.515625 1199.59375 535.0625 1152.25 c 1
|
||||
507.223632812 1135.921875 479.385742188 1119.59375 451.546875 1103.265625 c 1
|
||||
439.984375 1135.921875 422.5625 1161.859375 399.203125 1181.078125 c 128
|
||||
375.84375 1200.296875 344.28125 1209.828125 304.59375 1209.828125 c 0
|
||||
256.625 1209.828125 219.359375 1194.515625 192.796875 1163.8125 c 128
|
||||
166.234375 1133.03125 152.953125 1090.21875 152.953125 1035.140625 c 1
|
||||
152.953125 980.739257812 152.953125 926.338867188 152.953125 871.9375 c 1
|
||||
152.953125 816.9375 166.234375 774.046875 192.796875 743.265625 c 128
|
||||
219.359375 712.5625 256.625 697.25 304.59375 697.25 c 0
|
||||
324.4375 697.25 343.03125 700.0625 360.296875 705.84375 c 128
|
||||
377.5625 711.625 392.796875 719.90625 405.921875 730.84375 c 128
|
||||
419.046875 741.703125 429.28125 755.296875 436.625 771.625 c 128
|
||||
443.96875 787.953125 447.640625 806.3125 447.640625 826.78125 c 1
|
||||
447.640625 846.3125 447.640625 865.84375 447.640625 885.375 c 1
|
||||
406.364257812 885.375 365.088867188 885.375 323.8125 885.375 c 1
|
||||
323.8125 914.489257812 323.8125 943.604492188 323.8125 972.71875 c 1
|
||||
398.057617188 972.71875 472.301757812 972.71875 546.546875 972.71875 c 1
|
||||
546.546875 854.645507812 546.546875 736.573242188 546.546875 618.5 c 1
|
||||
516.46875 618.5 486.390625 618.5 456.3125 618.5 c 1
|
||||
456.3125 651.46875 456.3125 684.4375 456.3125 717.40625 c 1
|
||||
710.375 1288.578125 m 1
|
||||
800.948242188 1288.578125 891.520507812 1288.578125 982.09375 1288.578125 c 1
|
||||
1033.890625 1288.578125 1073.265625 1273.890625 1100.140625 1244.4375 c 128
|
||||
1127.015625 1214.984375 1140.453125 1172.09375 1140.453125 1115.765625 c 0
|
||||
1140.453125 1072.875 1131.15625 1039.28125 1112.640625 1014.984375 c 128
|
||||
1094.046875 990.6875 1067.484375 976.9375 1032.953125 973.734375 c 1
|
||||
1032.953125 969.879882812 1032.953125 966.026367188 1032.953125 962.171875 c 1
|
||||
1052.171875 962.171875 1069.59375 958.5 1085.296875 951.15625 c 128
|
||||
1100.921875 943.8125 1114.515625 933.34375 1126.078125 919.90625 c 128
|
||||
1137.5625 906.46875 1146.546875 890.53125 1152.953125 871.9375 c 128
|
||||
1159.359375 853.34375 1162.5625 833.1875 1162.5625 811.46875 c 0
|
||||
1162.5625 782.640625 1158.890625 756.390625 1151.546875 732.71875 c 128
|
||||
1144.125 709.046875 1133.734375 688.734375 1120.296875 671.78125 c 128
|
||||
1106.859375 654.828125 1090.6875 641.703125 1071.859375 632.40625 c 128
|
||||
1052.953125 623.109375 1032.015625 618.5 1008.96875 618.5 c 1
|
||||
909.4375 618.5 809.90625 618.5 710.375 618.5 c 1
|
||||
710.375 841.859375 710.375 1065.21875 710.375 1288.578125 c 1
|
||||
812.171875 707.796875 m 1
|
||||
867.848632812 707.796875 923.526367188 707.796875 979.203125 707.796875 c 1
|
||||
1003.5 707.796875 1022.09375 714.359375 1034.90625 727.484375 c 128
|
||||
1047.640625 740.609375 1054.046875 761.546875 1054.046875 790.375 c 1
|
||||
1054.046875 805.088867188 1054.046875 819.801757812 1054.046875 834.515625 c 1
|
||||
1054.046875 863.265625 1047.640625 884.28125 1034.90625 897.40625 c 128
|
||||
1022.09375 910.53125 1003.5 917.09375 979.203125 917.09375 c 1
|
||||
923.526367188 917.09375 867.848632812 917.09375 812.171875 917.09375 c 1
|
||||
812.171875 847.328125 812.171875 777.5625 812.171875 707.796875 c 1
|
||||
812.171875 1004.4375 m 1
|
||||
862.40625 1004.4375 912.640625 1004.4375 962.875 1004.4375 c 1
|
||||
985.296875 1004.4375 1002.5625 1010.375 1014.671875 1022.171875 c 128
|
||||
1026.859375 1034.046875 1032.953125 1053.34375 1032.953125 1080.296875 c 1
|
||||
1032.953125 1094.671875 1032.953125 1109.046875 1032.953125 1123.421875 c 1
|
||||
1032.953125 1150.375 1026.859375 1169.671875 1014.671875 1181.546875 c 128
|
||||
1002.5625 1193.34375 985.296875 1199.28125 962.875 1199.28125 c 1
|
||||
912.640625 1199.28125 862.40625 1199.28125 812.171875 1199.28125 c 1
|
||||
812.171875 1134.33300781 812.171875 1069.38574219 812.171875 1004.4375 c 1
|
||||
1645.765625 618.5 m 1
|
||||
1628.47363281 680.270507812 1611.18261719 742.041992188 1593.890625 803.8125 c 1
|
||||
1518.70800781 803.8125 1443.52636719 803.8125 1368.34375 803.8125 c 1
|
||||
1351.05175781 742.041992188 1333.76074219 680.270507812 1316.46875 618.5 c 1
|
||||
1283.1875 618.5 1249.90625 618.5 1216.625 618.5 c 1
|
||||
1283.5 841.859375 1350.375 1065.21875 1417.25 1288.578125 c 1
|
||||
1460.453125 1288.578125 1503.65625 1288.578125 1546.859375 1288.578125 c 1
|
||||
1614.07324219 1065.21875 1681.28613281 841.859375 1748.5 618.5 c 1
|
||||
1714.25488281 618.5 1680.01074219 618.5 1645.765625 618.5 c 1
|
||||
1507.484375 1098.5 m 1
|
||||
1500.453125 1131.12988281 1493.421875 1163.76074219 1486.390625 1196.390625 c 1
|
||||
1482.22363281 1196.390625 1478.05761719 1196.390625 1473.890625 1196.390625 c 1
|
||||
1466.859375 1163.76074219 1459.828125 1131.12988281 1452.796875 1098.5 c 1
|
||||
1432.953125 1028.109375 1413.109375 957.71875 1393.265625 887.328125 c 1
|
||||
1452.14550781 887.328125 1511.02636719 887.328125 1569.90625 887.328125 c 1
|
||||
1549.09863281 957.71875 1528.29199219 1028.109375 1507.484375 1098.5 c 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
EndChars
|
||||
EndSplineFont
|
||||
|
|
BIN
res/icons.ttf
BIN
res/icons.ttf
Binary file not shown.
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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
372
src/engine/fileOps/text.cpp
Normal 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;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
529
src/engine/platform/gbadma.cpp
Normal file
529
src/engine/platform/gbadma.cpp
Normal 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];
|
||||
}
|
||||
}
|
101
src/engine/platform/gbadma.h
Normal file
101
src/engine/platform/gbadma.h
Normal 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
|
782
src/engine/platform/gbaminmod.cpp
Normal file
782
src/engine/platform/gbaminmod.cpp
Normal 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=®Pool[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=®Pool[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];
|
||||
}
|
||||
}
|
132
src/engine/platform/gbaminmod.h
Normal file
132
src/engine/platform/gbaminmod.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
593
src/engine/platform/nds.cpp
Normal 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
104
src/engine/platform/nds.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
634
src/engine/platform/sound/nds.cpp
Normal file
634
src/engine/platform/sound/nds.cpp
Normal 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
|
415
src/engine/platform/sound/nds.hpp
Normal file
415
src/engine/platform/sound/nds.hpp
Normal 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
Loading…
Reference in a new issue