From 03b7762f05c0cccfc7d5c3860200b4144951471f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 7 Nov 2025 01:21:38 -0500 Subject: [PATCH 01/46] prepare a new info header --- papers/format.md | 848 ++++++++++++++++++++++++-------------------- papers/wavetable.md | 13 + 2 files changed, 471 insertions(+), 390 deletions(-) create mode 100644 papers/wavetable.md diff --git a/papers/format.md b/papers/format.md index c92c7da3f..8b78b9eb2 100644 --- a/papers/format.md +++ b/papers/format.md @@ -120,7 +120,456 @@ size | description 8 | reserved ``` -# song info +# song info (>=239) (DRAFT) + +``` +size | description +-----|------------------------------------ + 4 | "INF2" block ID + 4 | size of this block + --- | **song information** + STR | song name + STR | song author + STR | system name + STR | album/category/game name + STR | song name (Japanese) + STR | song author (Japanese) + STR | system name (Japanese) + STR | album/category/game name (Japanese) + 4f | A-4 tuning + --- | **system definition** + 2 | total number of channels + 2 | number of chips + --- | **chip definition (×numChips)** + 2 | chip ID + 2 | chip channel count + 4f | chip volume + 4f | chip panning + 4f | chip front/rear balance + --- | **groove list** + 1 | number of entries + ??? | groove entries. the format is: + | - 1 byte: length of groove + | - 16 bytes: groove pattern + --- | **patchbay** + 4 | patchbay connection count + 4?? | patchbay + | - see next section for more details. + 1 | automatic patchbay + --- | **song elements** + 1 | element type + 4?? | pointers to elements + | - a zero pointer means "end of list". + | - if the element is unique, only a single pointer will be present. +``` + +## list of sound chips + +this is a list of sound chips, and their nominal channel count. +the channel count is stored in the file in order to allow Furnace to load files with unsupported chips from derivatives of Furnace or newer versions. + +- 0x00: invalid (end of chips in previous versions) +- 0x01: YMU759 - 17 channels +- 0x02: Genesis - 10 channels (compound!) + - flatten to 0x83 + 0x03 +- 0x03: SMS (SN76489) - 4 channels +- 0x04: Game Boy - 4 channels +- 0x05: PC Engine - 6 channels +- 0x06: NES - 5 channels +- 0x07: C64 (8580) - 3 channels +- 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) + - flatten to 0x82 + 0xa9 +- 0x09: Neo Geo CD (YM2610) - 13 channels +- 0x42: Genesis extended - 13 channels (compound!) + - flatten to 0xa0 + 0x03 +- 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) + - flatten to 0x03 + 0x89 +- 0x46: NES + VRC7 - 11 channels (compound!) + - flatten to 0x06 + 0x9d +- 0x47: C64 (6581) - 3 channels +- 0x49: Neo Geo CD extended - 16 channels +- 0x80: AY-3-8910 - 3 channels +- 0x81: Amiga - 4 channels +- 0x82: YM2151 - 8 channels +- 0x83: YM2612 - 6 channels +- 0x84: TIA - 2 channels +- 0x85: VIC-20 - 4 channels +- 0x86: PET - 1 channel +- 0x87: SNES - 8 channels +- 0x88: VRC6 - 3 channels +- 0x89: OPLL (YM2413) - 9 channels +- 0x8a: FDS - 1 channel +- 0x8b: MMC5 - 3 channels +- 0x8c: Namco 163 - 8 channels +- 0x8d: YM2203 - 6 channels +- 0x8e: YM2608 - 16 channels +- 0x8f: OPL (YM3526) - 9 channels +- 0x90: OPL2 (YM3812) - 9 channels +- 0x91: OPL3 (YMF262) - 18 channels +- 0x92: MultiPCM - 28 channels +- 0x93: Intel 8253 (beeper) - 1 channel +- 0x94: POKEY - 4 channels +- 0x95: RF5C68 - 8 channels +- 0x96: WonderSwan - 4 channels +- 0x97: Philips SAA1099 - 6 channels +- 0x98: OPZ (YM2414) - 8 channels +- 0x99: Pokémon Mini - 1 channel +- 0x9a: AY8930 - 3 channels +- 0x9b: SegaPCM - 16 channels +- 0x9c: Virtual Boy - 6 channels +- 0x9d: VRC7 - 6 channels +- 0x9e: YM2610B - 16 channels +- 0x9f: ZX Spectrum (beeper, SFX-like tildearrow engine) - 6 channels +- 0xa0: YM2612 extended - 9 channels +- 0xa1: Konami SCC - 5 channels +- 0xa2: OPL drums (YM3526) - 11 channels +- 0xa3: OPL2 drums (YM3812) - 11 channels +- 0xa4: OPL3 drums (YMF262) - 20 channels +- 0xa5: Neo Geo (YM2610) - 14 channels +- 0xa6: Neo Geo extended (YM2610) - 17 channels +- 0xa7: OPLL drums (YM2413) - 11 channels +- 0xa8: Atari Lynx - 4 channels +- 0xa9: SegaPCM (for DefleMask compatibility) - 5 channels +- 0xaa: MSM6295 - 4 channels +- 0xab: MSM6258 - 1 channel +- 0xac: Commander X16 (VERA) - 17 channels +- 0xad: Bubble System WSG - 2 channels +- 0xae: OPL4 (YMF278B) - 42 channels +- 0xaf: OPL4 drums (YMF278B) - 44 channels +- 0xb0: Seta/Allumer X1-010 - 16 channels +- 0xb1: Ensoniq ES5506 - 32 channels +- 0xb2: Yamaha Y8950 - 10 channels +- 0xb3: Yamaha Y8950 drums - 12 channels +- 0xb4: Konami SCC+ - 5 channels +- 0xb5: tildearrow Sound Unit - 8 channels +- 0xb6: YM2203 extended - 9 channels +- 0xb7: YM2608 extended - 19 channels +- 0xb8: YMZ280B - 8 channels +- 0xb9: Namco WSG - 3 channels +- 0xba: Namco C15 - 8 channels +- 0xbb: Namco C30 - 8 channels +- 0xbc: MSM5232 - 8 channels +- 0xbd: YM2612 DualPCM extended - 11 channels +- 0xbe: YM2612 DualPCM - 7 channels +- 0xbf: T6W28 - 4 channels +- 0xc0: PCM DAC - 1 channel +- 0xc1: YM2612 CSM - 10 channels +- 0xc2: Neo Geo CSM (YM2610) - 18 channels +- 0xc3: YM2203 CSM - 10 channels +- 0xc4: YM2608 CSM - 20 channels +- 0xc5: YM2610B CSM - 20 channels +- 0xc6: K007232 - 2 channels +- 0xc7: GA20 - 4 channels +- 0xc8: SM8521 - 3 channels +- 0xc9: M114S - 16 channels (UNAVAILABLE) +- 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels +- 0xcb: Casio PV-1000 - 3 channels +- 0xcc: K053260 - 4 channels +- 0xcd: TED - 2 channels +- 0xce: Namco C140 - 24 channels +- 0xcf: Namco C219 - 16 channels +- 0xd0: Namco C352 - 32 channels (UNAVAILABLE) +- 0xd1: ESFM - 18 channels +- 0xd2: Ensoniq ES5503 (hard pan) - 32 channels (UNAVAILABLE) +- 0xd4: PowerNoise - 4 channels +- 0xd5: Dave - 6 channels +- 0xd6: NDS - 16 channels +- 0xd7: Game Boy Advance (direct) - 2 channels +- 0xd8: Game Boy Advance (MinMod) - 16 channels +- 0xd9: Bifurcator - 4 channels +- 0xda: SCSP - 32 channels (UNAVAILABLE) +- 0xdb: YMF271 (OPX) - 48 channels (UNAVAILABLE) +- 0xdc: RF5C400 - 32 channels (UNAVAILABLE) +- 0xdd: YM2612 XGM - 9 channels (UNAVAILABLE) +- 0xde: YM2610B extended - 19 channels +- 0xdf: YM2612 XGM extended - 13 channels (UNAVAILABLE) +- 0xe0: QSound - 19 channels +- 0xe1: PS1 - 24 channels (UNAVAILABLE) +- 0xe2: C64 (6581) with PCM - 4 channels +- 0xe3: Watara Supervision - 4 channels +- 0xe5: µPD1771C-017 - 4 channels +- 0xf0: SID2 - 3 channels +- 0xf1: 5E01 - 5 channels +- 0xf5: SID3 - 7 channels +- 0xfc: Pong - 1 channel +- 0xfd: Dummy System - 8 channels +- 0xfe: reserved for development +- 0xff: reserved for development + +notes: + +- (compound!) means that the system is composed of two or more chips, and has to be flattened. +- (UNAVAILABLE) means that the chip hasn't been implemented in Furnace yet. + +## song elements + +the following element types are available: + +``` + ## | ID | description +----|------|----------------------------- + 00 | ---- | end of element list (end of info header) + 01 | SONG | sub-song + 02 | FLAG | chip flags + 03 | ADIR | asset directory** + 04 | INS2 | instrument + 05 | WAVE | wavetable + 06 | SMP2 | sample + 07 | PATN | pattern + 08 | CFLG | compatibility flags* + 09 | CMNT | song comments* + +* element is unique (read only one pointer and assume the pointer list is over) + +** first pointer is for instruments, second for wavetables and third for samples +``` + +# patchbay + +Furnace dev135 adds a "patchbay" which allows for arbitrary connection of chip outputs to system outputs. +it also allows connecting outputs to effects and so on. + +a connection is represented as an unsigned int in the following format: + +- bit 16-31: source port +- bit 0-15: destination port + +a port is in the following format (hexadecimal): `xxxy` + +- `xxx` (bit 4 to 15) represents a portset. +- `y` (bit 0 to 3) is the port in that portset. + +reserved input portsets: +- `000`: system outputs +- `FFF`: "null" portset + +reserved output portsets: +- `000` through `chipCount`: chip outputs +- `FFC`: reference file/music player (>=238) +- `FFD`: wave/sample preview +- `FFE`: metronome +- `FFF`: "null" portset + +# subsong + +``` +size | description +-----|------------------------------------ + 4 | "SONG" block ID + 4 | size of this block + 1 | time base + 1 | speed 1 + 1 | speed 2 + 1 | initial arpeggio time + 4f | ticks per second + | - 60 is NTSC + | - 50 is PAL + 2 | pattern length + | - the limit is 256. + 2 | orders length + | - the limit is 256. + 1 | highlight A + 1 | highlight B + 2 | virtual tempo numerator + 2 | virtual tempo denominator + STR | subsong name + STR | subsong comment + ??? | orders + | - a table of bytes + | - size=channels*ordLen + | - read orders then channels + | - the maximum value of a cell is FF. + ??? | effect columns + | - size=channels + 1?? | channel hide status + | - size=channels + 1?? | channel collapse status + | - size=channels + S?? | channel names + | - a list of channelCount C strings + S?? | channel short names + | - same as above + --- | **speed pattern** (>=139) + 1 | length of speed pattern (fail if this is lower than 1 or higher than 16) + 16 | speed pattern (this overrides speed 1 and speed 2 settings) +``` + +# chip flags + +``` +size | description +-----|------------------------------------ + 4 | "FLAG" block ID + 4 | size of this block + STR | data +``` + +flags are stored in text (`key=value`) format. for example: + +``` +clock=4000000 +stereo=true +``` + +# asset directories (>=156) + +also known as "folder" in the user interface. + +``` +size | description +-----|------------------------------------ + 4 | "ADIR" block ID + 4 | size of this block + 4 | number of directories + --- | **asset directory** (×numberOfDirs) + STR | name (if empty, this is the uncategorized directory) + 2 | number of assets + 1?? | assets in this directory +``` + +# instrument (>=127) + +Furnace dev127 and higher use the new instrument format. + +``` +size | description +-----|------------------------------------ + 4 | "INS2" block ID + 4 | size of this block + 2 | format version + 2 | instrument type + ??? | features... +``` + +see [newIns.md](newIns.md) for more information. + +# wavetable + +``` +size | description +-----|------------------------------------ + 4 | "WAVE" block ID + 4 | size of this block + STR | wavetable name + 4 | wavetable width + 4 | reserved + 4 | wavetable height + 4?? | wavetable data +``` + +# sample (>=102) + +``` +size | description +-----|------------------------------------ + 4 | "SMP2" block ID + 4 | size of this block + STR | sample name + 4 | length + 4 | compatibility rate + 4 | C-4 rate + 1 | depth + | - 0: ZX Spectrum overlay drum (1-bit) + | - 1: 1-bit NES DPCM (1-bit) + | - 3: YMZ ADPCM + | - 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 + | - 14: 12-bit PCM (MultiPCM) + | - 16: 16-bit PCM + 1 | loop direction (>=123) or reserved + | - 0: forward + | - 1: backward + | - 2: ping-pong + 1 | flags (>=129) or reserved + | - 0: BRR emphasis + 1 | flags 2 (>=159) or reserved + | - 0: dither + | - 1: no BRR filters (>=213) + 4 | loop start + | - -1 means no loop + 4 | loop end + | - -1 means no loop + 16 | sample presence bitfields + | - for future use. + | - indicates whether the sample should be present in the memory of a system. + | - read 4 32-bit numbers (for 4 memory banks per system, e.g. YM2610 + | does ADPCM-A and ADPCM-B on separate memory banks). + ??? | sample data + | - size is length +``` + +# pattern (>=157) + +``` +size | description +-----|------------------------------------ + 4 | "PATN" block ID + 4 | size of this block + 1 | subsong + 1 | channel + 2 | pattern index + STR | pattern name (>=51) + ??? | pattern data + | - read a byte per row. + | - if it is 0xff, end of data. the rest of the pattern is empty. + | - if bit 7 is set, then skip N+2 rows. N is bits 0-6. + | - for example, $80 means skip 2 rows, $81 means skip 3, $82 means 4 and so on. + | - if bit 7 is clear, then: + | - bit 0: note present + | - bit 1: ins present + | - bit 2: volume present + | - bit 3: effect 0 present + | - bit 4: effect value 0 present + | - bit 5: other effects (0-3) present + | - bit 6: other effects (4-7) present + | - if none of these bits are set, then skip 1 row. + | - if bit 5 is set, read another byte: + | - bit 0: effect 0 present + | - bit 1: effect value 0 present + | - bit 2: effect 1 present + | - bit 3: effect value 1 present + | - bit 4: effect 2 present + | - bit 5: effect value 2 present + | - bit 6: effect 3 present + | - bit 7: effect value 3 present + | - if bit 6 is set, read another byte: + | - bit 0: effect 4 present + | - bit 1: effect value 4 present + | - bit 2: effect 5 present + | - bit 3: effect value 5 present + | - bit 4: effect 6 present + | - bit 5: effect value 6 present + | - bit 6: effect 7 present + | - bit 7: effect value 7 present + | - then read note, ins, volume, effects and effect values depending on what is present. + | - for note: + | - 0 is C-(-5) + | - 179 is B-9 + | - 180 is note off + | - 181 is note release + | - 182 is macro release +``` + +--- + +# old format blocks + +these were present in previous versions of the Furnace file format. + +## old song info (<239) + +hic sunt dracones! + +this info block is messy because it was devised during Furnace's early days, back when it didn't support sub-songs. +the first sub-song is defined here. compatibility flags are all over the place. ``` size | description @@ -147,134 +596,9 @@ size | description 2 | sample count | - the limit is 256. 4 | pattern count (global) - 32 | list of sound chips - | - possible soundchips: - | - 0x00: end of list - | - 0x01: YMU759 - 17 channels - | - 0x02: Genesis - 10 channels (compound!) - | - 0x03: SMS (SN76489) - 4 channels - | - 0x04: Game Boy - 4 channels - | - 0x05: PC Engine - 6 channels - | - 0x06: NES - 5 channels - | - 0x07: C64 (8580) - 3 channels - | - 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) - | - 0x09: Neo Geo CD (YM2610) - 13 channels - | - 0x42: Genesis extended - 13 channels - | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) - | - 0x46: NES + VRC7 - 11 channels (compound!) - | - 0x47: C64 (6581) - 3 channels - | - 0x49: Neo Geo CD extended - 16 channels - | - 0x80: AY-3-8910 - 3 channels - | - 0x81: Amiga - 4 channels - | - 0x82: YM2151 - 8 channels - | - 0x83: YM2612 - 6 channels - | - 0x84: TIA - 2 channels - | - 0x85: VIC-20 - 4 channels - | - 0x86: PET - 1 channel - | - 0x87: SNES - 8 channels - | - 0x88: VRC6 - 3 channels - | - 0x89: OPLL (YM2413) - 9 channels - | - 0x8a: FDS - 1 channel - | - 0x8b: MMC5 - 3 channels - | - 0x8c: Namco 163 - 8 channels - | - 0x8d: YM2203 - 6 channels - | - 0x8e: YM2608 - 16 channels - | - 0x8f: OPL (YM3526) - 9 channels - | - 0x90: OPL2 (YM3812) - 9 channels - | - 0x91: OPL3 (YMF262) - 18 channels - | - 0x92: MultiPCM - 28 channels - | - 0x93: Intel 8253 (beeper) - 1 channel - | - 0x94: POKEY - 4 channels - | - 0x95: RF5C68 - 8 channels - | - 0x96: WonderSwan - 4 channels - | - 0x97: Philips SAA1099 - 6 channels - | - 0x98: OPZ (YM2414) - 8 channels - | - 0x99: Pokémon Mini - 1 channel - | - 0x9a: AY8930 - 3 channels - | - 0x9b: SegaPCM - 16 channels - | - 0x9c: Virtual Boy - 6 channels - | - 0x9d: VRC7 - 6 channels - | - 0x9e: YM2610B - 16 channels - | - 0x9f: ZX Spectrum (beeper, SFX-like tildearrow engine) - 6 channels - | - 0xa0: YM2612 extended - 9 channels - | - 0xa1: Konami SCC - 5 channels - | - 0xa2: OPL drums (YM3526) - 11 channels - | - 0xa3: OPL2 drums (YM3812) - 11 channels - | - 0xa4: OPL3 drums (YMF262) - 20 channels - | - 0xa5: Neo Geo (YM2610) - 14 channels - | - 0xa6: Neo Geo extended (YM2610) - 17 channels - | - 0xa7: OPLL drums (YM2413) - 11 channels - | - 0xa8: Atari Lynx - 4 channels - | - 0xa9: SegaPCM (for DefleMask compatibility) - 5 channels - | - 0xaa: MSM6295 - 4 channels - | - 0xab: MSM6258 - 1 channel - | - 0xac: Commander X16 (VERA) - 17 channels - | - 0xad: Bubble System WSG - 2 channels - | - 0xae: OPL4 (YMF278B) - 42 channels - | - 0xaf: OPL4 drums (YMF278B) - 44 channels - | - 0xb0: Seta/Allumer X1-010 - 16 channels - | - 0xb1: Ensoniq ES5506 - 32 channels - | - 0xb2: Yamaha Y8950 - 10 channels - | - 0xb3: Yamaha Y8950 drums - 12 channels - | - 0xb4: Konami SCC+ - 5 channels - | - 0xb5: tildearrow Sound Unit - 8 channels - | - 0xb6: YM2203 extended - 9 channels - | - 0xb7: YM2608 extended - 19 channels - | - 0xb8: YMZ280B - 8 channels - | - 0xb9: Namco WSG - 3 channels - | - 0xba: Namco C15 - 8 channels - | - 0xbb: Namco C30 - 8 channels - | - 0xbc: MSM5232 - 8 channels - | - 0xbd: YM2612 DualPCM extended - 11 channels - | - 0xbe: YM2612 DualPCM - 7 channels - | - 0xbf: T6W28 - 4 channels - | - 0xc0: PCM DAC - 1 channel - | - 0xc1: YM2612 CSM - 10 channels - | - 0xc2: Neo Geo CSM (YM2610) - 18 channels - | - 0xc3: YM2203 CSM - 10 channels - | - 0xc4: YM2608 CSM - 20 channels - | - 0xc5: YM2610B CSM - 20 channels - | - 0xc6: K007232 - 2 channels - | - 0xc7: GA20 - 4 channels - | - 0xc8: SM8521 - 3 channels - | - 0xc9: M114S - 16 channels (UNAVAILABLE) - | - 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels - | - 0xcb: Casio PV-1000 - 3 channels - | - 0xcc: K053260 - 4 channels - | - 0xcd: TED - 2 channels - | - 0xce: Namco C140 - 24 channels - | - 0xcf: Namco C219 - 16 channels - | - 0xd0: Namco C352 - 32 channels (UNAVAILABLE) - | - 0xd1: ESFM - 18 channels - | - 0xd2: Ensoniq ES5503 (hard pan) - 32 channels (UNAVAILABLE) - | - 0xd4: PowerNoise - 4 channels - | - 0xd5: Dave - 6 channels - | - 0xd6: NDS - 16 channels - | - 0xd7: Game Boy Advance (direct) - 2 channels - | - 0xd8: Game Boy Advance (MinMod) - 16 channels - | - 0xd9: Bifurcator - 4 channels - | - 0xda: SCSP - 32 channels (UNAVAILABLE) - | - 0xdb: YMF271 (OPX) - 48 channels (UNAVAILABLE) - | - 0xdc: RF5C400 - 32 channels (UNAVAILABLE) - | - 0xdd: YM2612 XGM - 9 channels (UNAVAILABLE) - | - 0xde: YM2610B extended - 19 channels - | - 0xdf: YM2612 XGM extended - 13 channels (UNAVAILABLE) - | - 0xe0: QSound - 19 channels - | - 0xe1: PS1 - 24 channels (UNAVAILABLE) - | - 0xe2: C64 (6581) with PCM - 4 channels - | - 0xe3: Watara Supervision - 4 channels - | - 0xe5: µPD1771C-017 - 4 channels - | - 0xf0: SID2 - 3 channels - | - 0xf1: 5E01 - 5 channels - | - 0xf5: SID3 - 7 channels - | - 0xfc: Pong - 1 channel - | - 0xfd: Dummy System - 8 channels - | - 0xfe: reserved for development - | - 0xff: reserved for development - | - (compound!) means that the system is composed of two or more chips, - | and has to be flattened. - | - (UNAVAILABLE) means that the chip hasn't been implemented in Furnace - | yet. + 32 | list of sound chip IDs + | - 0x00 means "end of chip list". you must read 32 bytes regardless. + | - see list at the top of this file for the list of sound chips. 32 | sound chip volumes (<135) or reserved | - signed char, 64=1.0, 127=~2.0 | - as of version 135 these fields only exist for compatibility reasons. @@ -411,133 +735,11 @@ size | description 4 | sample directories ``` -# patchbay - -Furnace dev135 adds a "patchbay" which allows for arbitrary connection of chip outputs to system outputs. -it also allows connecting outputs to effects and so on. - -a connection is represented as an unsigned int in the following format: - -- bit 16-31: source port -- bit 0-15: destination port - -a port is in the following format (hexadecimal): `xxxy` - -- `xxx` (bit 4 to 15) represents a portset. -- `y` (bit 0 to 3) is the port in that portset. - -reserved input portsets: -- `000`: system outputs -- `FFF`: "null" portset - -reserved output portsets: -- `000` through `01F`: chip outputs -- `FFC`: reference file/music player (>=238) -- `FFD`: wave/sample preview -- `FFE`: metronome -- `FFF`: "null" portset - -# subsong - -from version 95 onwards, Furnace supports storing multiple songs on a single file. -the way it's currently done is really weird, but it provides for some backwards compatibility (previous versions will only load the first subsong which is already defined in the `INFO` block). - -``` -size | description ------|------------------------------------ - 4 | "SONG" block ID - 4 | size of this block - 1 | time base - 1 | speed 1 - 1 | speed 2 - 1 | initial arpeggio time - 4f | ticks per second - | - 60 is NTSC - | - 50 is PAL - 2 | pattern length - | - the limit is 256. - 2 | orders length - | - the limit is 256. - 1 | highlight A - 1 | highlight B - 2 | virtual tempo numerator - 2 | virtual tempo denominator - STR | subsong name - STR | subsong comment - ??? | orders - | - a table of bytes - | - size=channels*ordLen - | - read orders then channels - | - the maximum value of a cell is FF. - ??? | effect columns - | - size=channels - 1?? | channel hide status - | - size=channels - 1?? | channel collapse status - | - size=channels - S?? | channel names - | - a list of channelCount C strings - S?? | channel short names - | - same as above - --- | **speed pattern** (>=139) - 1 | length of speed pattern (fail if this is lower than 1 or higher than 16) - 16 | speed pattern (this overrides speed 1 and speed 2 settings) -``` - -# chip flags - -``` -size | description ------|------------------------------------ - 4 | "FLAG" block ID - 4 | size of this block - STR | data -``` - -flags are stored in text (`key=value`) format. for example: - -``` -clock=4000000 -stereo=true -``` - -# asset directories (>=156) - -also known as "folder" in the user interface. - -``` -size | description ------|------------------------------------ - 4 | "ADIR" block ID - 4 | size of this block - 4 | number of directories - --- | **asset directory** (×numberOfDirs) - STR | name (if empty, this is the uncategorized directory) - 2 | number of assets - 1?? | assets in this directory -``` - -# instrument (>=127) - -Furnace dev127 and higher use the new instrument format. - -``` -size | description ------|------------------------------------ - 4 | "INS2" block ID - 4 | size of this block - 2 | format version - 2 | instrument type - ??? | features... -``` - -see [newIns.md](newIns.md) for more information. - -# old instrument (<127) +## old instrument (<127) instruments in older versions of Furnace used a different format. see [oldIns.md](oldIns.md) for more information. -## C64 compatibility note (>=187) +### C64 compatibility note (>=187) in Furnace dev187 the volume and cutoff macros have been separated, as noted above. however, there are two other changes as well: **inverted relative (non-absolute) cutoff macro**; and a new, improved Special macro. @@ -555,74 +757,7 @@ if version is less than 187, you must convert the Special macro: don't worry about loop or release... -# wavetable - -``` -size | description ------|------------------------------------ - 4 | "WAVE" block ID - 4 | size of this block - STR | wavetable name - 4 | wavetable width - 4 | reserved - 4 | wavetable height - 4?? | wavetable data -``` - -# sample (>=102) - -this is the new sample storage format used in Furnace dev102 and higher. - -``` -size | description ------|------------------------------------ - 4 | "SMP2" block ID - 4 | size of this block - STR | sample name - 4 | length - 4 | compatibility rate - 4 | C-4 rate - 1 | depth - | - 0: ZX Spectrum overlay drum (1-bit) - | - 1: 1-bit NES DPCM (1-bit) - | - 3: YMZ ADPCM - | - 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 - | - 14: 12-bit PCM (MultiPCM) - | - 16: 16-bit PCM - 1 | loop direction (>=123) or reserved - | - 0: forward - | - 1: backward - | - 2: ping-pong - 1 | flags (>=129) or reserved - | - 0: BRR emphasis - 1 | flags 2 (>=159) or reserved - | - 0: dither - | - 1: no BRR filters (>=213) - 4 | loop start - | - -1 means no loop - 4 | loop end - | - -1 means no loop - 16 | sample presence bitfields - | - for future use. - | - indicates whether the sample should be present in the memory of a system. - | - read 4 32-bit numbers (for 4 memory banks per system, e.g. YM2610 - | does ADPCM-A and ADPCM-B on separate memory banks). - ??? | sample data - | - size is length -``` - -# old sample (<102) - -this format is present when saving using previous Furnace versions. +## old sample (<102) ``` size | description @@ -654,60 +789,7 @@ size | description | - version>=58 size is length ``` -# pattern (>=157) - -``` -size | description ------|------------------------------------ - 4 | "PATN" block ID - 4 | size of this block - 1 | subsong - 1 | channel - 2 | pattern index - STR | pattern name (>=51) - ??? | pattern data - | - read a byte per row. - | - if it is 0xff, end of data. the rest of the pattern is empty. - | - if bit 7 is set, then skip N+2 rows. N is bits 0-6. - | - for example, $80 means skip 2 rows, $81 means skip 3, $82 means 4 and so on. - | - if bit 7 is clear, then: - | - bit 0: note present - | - bit 1: ins present - | - bit 2: volume present - | - bit 3: effect 0 present - | - bit 4: effect value 0 present - | - bit 5: other effects (0-3) present - | - bit 6: other effects (4-7) present - | - if none of these bits are set, then skip 1 row. - | - if bit 5 is set, read another byte: - | - bit 0: effect 0 present - | - bit 1: effect value 0 present - | - bit 2: effect 1 present - | - bit 3: effect value 1 present - | - bit 4: effect 2 present - | - bit 5: effect value 2 present - | - bit 6: effect 3 present - | - bit 7: effect value 3 present - | - if bit 6 is set, read another byte: - | - bit 0: effect 4 present - | - bit 1: effect value 4 present - | - bit 2: effect 5 present - | - bit 3: effect value 5 present - | - bit 4: effect 6 present - | - bit 5: effect value 6 present - | - bit 6: effect 7 present - | - bit 7: effect value 7 present - | - then read note, ins, volume, effects and effect values depending on what is present. - | - for note: - | - 0 is C-(-5) - | - 179 is B-9 - | - 180 is note off - | - 181 is note release - | - 182 is macro release -``` - - -# old pattern (<157) +## old pattern (<157) ``` size | description @@ -749,18 +831,4 @@ size | description | - for note/octave, if both values are 0 then it means empty. | - for instrument, volume, effect and effect data, a value of -1 means empty. STR | pattern name (>=51) -``` - -# the Furnace wavetable format (.fuw) - -similar to the instrument format... - -``` -size | description ------|------------------------------------ - 16 | "-Furnace waveta-" format magic - 2 | format version - 2 | reserved -``` - -wavetable data follows. +``` \ No newline at end of file diff --git a/papers/wavetable.md b/papers/wavetable.md new file mode 100644 index 000000000..b54818d99 --- /dev/null +++ b/papers/wavetable.md @@ -0,0 +1,13 @@ +# the Furnace wavetable format (.fuw) + +similar to the instrument format... + +``` +size | description +-----|------------------------------------ + 16 | "-Furnace waveta-" format magic + 2 | format version + 2 | reserved +``` + +wavetable data follows. From 75dde2c98bd5f911a010e2bd373c5c322edbca85 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 7 Nov 2025 19:14:12 -0500 Subject: [PATCH 02/46] update the draft --- papers/format.md | 96 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/papers/format.md b/papers/format.md index 8b78b9eb2..118256eaa 100644 --- a/papers/format.md +++ b/papers/format.md @@ -1,8 +1,6 @@ # the Furnace file format (.fur) -while Furnace works directly with the .dmf format, I had to create a new format to handle future additions to the program. - -this document has the goal of detailing the format. +this document has the goal of describing the file format used by Furnace for loading and saving songs. **notice:** GitHub's Markdown formatter may break on this file as it doesn't seem to treat tables correctly. @@ -146,11 +144,6 @@ size | description 4f | chip volume 4f | chip panning 4f | chip front/rear balance - --- | **groove list** - 1 | number of entries - ??? | groove entries. the format is: - | - 1 byte: length of groove - | - 16 bytes: groove pattern --- | **patchbay** 4 | patchbay connection count 4?? | patchbay @@ -309,7 +302,7 @@ the following element types are available: ## | ID | description ----|------|----------------------------- 00 | ---- | end of element list (end of info header) - 01 | SONG | sub-song + 01 | SNG2 | sub-song 02 | FLAG | chip flags 03 | ADIR | asset directory** 04 | INS2 | instrument @@ -318,6 +311,7 @@ the following element types are available: 07 | PATN | pattern 08 | CFLG | compatibility flags* 09 | CMNT | song comments* + 0a | GROV | groove pattern * element is unique (read only one pointer and assume the pointer list is over) @@ -350,28 +344,29 @@ reserved output portsets: - `FFE`: metronome - `FFF`: "null" portset -# subsong +# subsong (>=239) ``` size | description -----|------------------------------------ - 4 | "SONG" block ID + 4 | "SNG2" block ID 4 | size of this block - 1 | time base - 1 | speed 1 - 1 | speed 2 - 1 | initial arpeggio time 4f | ticks per second | - 60 is NTSC | - 50 is PAL + 1 | initial arpeggio speed + 1 | effect speed divider 2 | pattern length | - the limit is 256. 2 | orders length | - the limit is 256. - 1 | highlight A - 1 | highlight B + 1 | highlight A (rows per beat) + 1 | highlight B (rows per bar) 2 | virtual tempo numerator 2 | virtual tempo denominator + 1 | length of speed pattern in entries (fail if this is lower than 1 or higher than 16) + 2?? | speed pattern (always 16 entries) + | - each speed is an unsigned short STR | subsong name STR | subsong comment ??? | orders @@ -389,9 +384,18 @@ size | description | - a list of channelCount C strings S?? | channel short names | - same as above - --- | **speed pattern** (>=139) - 1 | length of speed pattern (fail if this is lower than 1 or higher than 16) - 16 | speed pattern (this overrides speed 1 and speed 2 settings) +``` + +# groove pattern (>=239) + +``` +size | description +-----|------------------------------------ + 4 | "GROV" block ID + 4 | size of this block + 1 | length of groove in entries (fail if this is lower than 1 or higher than 16) + 2?? | groove pattern (always 16 entries) + | - each speed is an unsigned short ``` # chip flags @@ -514,7 +518,10 @@ size | description 4 | "PATN" block ID 4 | size of this block 1 | subsong - 1 | channel + 1 | channel (<239) + 2 | channel (>=239) + | - the channel index was 8-bit in previous versions. + | - in order to accommodate higher channel counts, it has been extended to 16-bit. 2 | pattern index STR | pattern name (>=51) ??? | pattern data @@ -735,6 +742,51 @@ size | description 4 | sample directories ``` +# old subsong (<239) + +``` +size | description +-----|------------------------------------ + 4 | "SONG" block ID + 4 | size of this block + 1 | time base + 1 | speed 1 + 1 | speed 2 + 1 | initial arpeggio time + 4f | ticks per second + | - 60 is NTSC + | - 50 is PAL + 2 | pattern length + | - the limit is 256. + 2 | orders length + | - the limit is 256. + 1 | highlight A + 1 | highlight B + 2 | virtual tempo numerator + 2 | virtual tempo denominator + STR | subsong name + STR | subsong comment + ??? | orders + | - a table of bytes + | - size=channels*ordLen + | - read orders then channels + | - the maximum value of a cell is FF. + ??? | effect columns + | - size=channels + 1?? | channel hide status + | - size=channels + 1?? | channel collapse status + | - size=channels + S?? | channel names + | - a list of channelCount C strings + S?? | channel short names + | - same as above + --- | **speed pattern** (>=139) + 1 | length of speed pattern (fail if this is lower than 1 or higher than 16) + 16 | speed pattern (this overrides speed 1 and speed 2 settings) +``` + + ## old instrument (<127) instruments in older versions of Furnace used a different format. see [oldIns.md](oldIns.md) for more information. @@ -831,4 +883,4 @@ size | description | - for note/octave, if both values are 0 then it means empty. | - for instrument, volume, effect and effect data, a value of -1 means empty. STR | pattern name (>=51) -``` \ No newline at end of file +``` From 1387551688474a92ff8c04fc4a5ff831b4d52155 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 10 Nov 2025 16:18:27 -0500 Subject: [PATCH 03/46] possibly final revision of new format document now to start work on its implementation --- papers/format.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/papers/format.md b/papers/format.md index 118256eaa..888535b91 100644 --- a/papers/format.md +++ b/papers/format.md @@ -118,7 +118,7 @@ size | description 8 | reserved ``` -# song info (>=239) (DRAFT) +# song info (>=240) ``` size | description @@ -344,7 +344,7 @@ reserved output portsets: - `FFE`: metronome - `FFF`: "null" portset -# subsong (>=239) +# subsong (>=240) ``` size | description @@ -386,7 +386,7 @@ size | description | - same as above ``` -# groove pattern (>=239) +# groove pattern (>=240) ``` size | description @@ -518,8 +518,8 @@ size | description 4 | "PATN" block ID 4 | size of this block 1 | subsong - 1 | channel (<239) - 2 | channel (>=239) + 1 | channel (<240) + 2 | channel (>=240) | - the channel index was 8-bit in previous versions. | - in order to accommodate higher channel counts, it has been extended to 16-bit. 2 | pattern index @@ -571,7 +571,7 @@ size | description these were present in previous versions of the Furnace file format. -## old song info (<239) +## old song info (<240) hic sunt dracones! @@ -742,7 +742,7 @@ size | description 4 | sample directories ``` -# old subsong (<239) +# old subsong (<240) ``` size | description From 20dab0b08b92cffdbe213ab74a20820226d90f77 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 11 Nov 2025 02:53:58 -0500 Subject: [PATCH 04/46] prepare stuff --- src/engine/engine.h | 4 ++-- src/engine/fileOps/fur.cpp | 13 ++++++++++--- src/engine/song.h | 2 ++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index b1ebc6765..66196f894 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -55,8 +55,8 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "dev239" -#define DIV_ENGINE_VERSION 239 +#define DIV_VERSION "inf2 - DO NOT SAVE!" +#define DIV_ENGINE_VERSION 240 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index f3a0ad4b8..285e1aa1b 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -958,7 +958,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { for (int i=0; iDIV_MAX_CHANS) { tchans=DIV_MAX_CHANS; @@ -2170,7 +2171,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } } } - ch+=getChannelCount(ds.system[i]); + ch+=ds.systemChans[i]; } } @@ -2227,6 +2228,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; + // please remove once INF2 is ready. + logE("do not save yet! I still am working on it!"); + lastError="do not save yet! I still am working on it!"; + saveLock.unlock(); + return NULL; + // fail if values are out of range /* if (subSong->ordersLen>DIV_MAX_PATTERNS) { diff --git a/src/engine/song.h b/src/engine/song.h index 4b6e1b7fc..83daab1de 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -284,6 +284,7 @@ struct DivSong { // system DivSystem system[DIV_MAX_CHIPS]; + unsigned short systemChans[DIV_MAX_CHIPS]; unsigned char systemLen; float systemVol[DIV_MAX_CHIPS]; float systemPan[DIV_MAX_CHIPS]; @@ -517,6 +518,7 @@ struct DivSong { oldCenterRate(true) { for (int i=0; i Date: Tue, 11 Nov 2025 05:31:06 -0500 Subject: [PATCH 05/46] rely on song.systemChans --- src/engine/engine.cpp | 2 +- src/engine/song.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0d00756ec..46c43c5b2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2175,7 +2175,7 @@ void DivEngine::recalcChans() { int chanIndex=0; memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool)); for (int i=0; i effects; + // INTERNAL STATE - do not modify. DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound, nullInsESFM; DivWavetable nullWave; DivSample nullSample; From 2f25acd017b6adaaf53a9e5008901eaa4fd6b4bd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 11 Nov 2025 18:33:23 -0500 Subject: [PATCH 06/46] move asset dir functions to another file and get them out of the engine --- CMakeLists.txt | 2 + src/engine/assetDir.cpp | 152 +++++++++++++++++++++++++++++++++++++ src/engine/assetDir.h | 52 +++++++++++++ src/engine/engine.cpp | 78 ------------------- src/engine/engine.h | 13 ---- src/engine/fileOps/fur.cpp | 52 ------------- src/engine/song.h | 11 +-- src/gui/dataList.cpp | 6 +- 8 files changed, 210 insertions(+), 156 deletions(-) create mode 100644 src/engine/assetDir.cpp create mode 100644 src/engine/assetDir.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 85259de3c..57b342527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -782,6 +782,8 @@ src/engine/brrUtils.c src/engine/safeReader.cpp src/engine/safeWriter.cpp src/engine/workPool.cpp + +src/engine/assetDir.cpp src/engine/cmdStream.cpp src/engine/cmdStreamOps.cpp src/engine/config.cpp diff --git a/src/engine/assetDir.cpp b/src/engine/assetDir.cpp new file mode 100644 index 000000000..06e9e52c2 --- /dev/null +++ b/src/engine/assetDir.cpp @@ -0,0 +1,152 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 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 "assetDir.h" +#include "../ta-log.h" + +void moveAsset(std::vector& dir, int before, int after) { + if (before<0 || after<0) return; + for (DivAssetDir& i: dir) { + for (size_t j=0; j& dir, int entry) { + if (entry<0) return; + for (DivAssetDir& i: dir) { + for (size_t j=0; jentry) { + i.entries[j]--; + } + } + } +} + +void checkAssetDir(std::vector& dir, size_t entries) { + bool* inAssetDir=new bool[entries]; + memset(inAssetDir,0,entries*sizeof(bool)); + + for (DivAssetDir& i: dir) { + for (size_t j=0; j=(int)entries) { + i.entries.erase(i.entries.begin()+j); + j--; + continue; + } + + // erase duplicate entry + if (inAssetDir[i.entries[j]]) { + i.entries.erase(i.entries.begin()+j); + j--; + continue; + } + + // mark entry as present + inAssetDir[i.entries[j]]=true; + } + } + + // get unsorted directory + DivAssetDir* unsortedDir=NULL; + for (DivAssetDir& i: dir) { + if (i.name.empty()) { + unsortedDir=&i; + break; + } + } + + // add missing items to unsorted directory + for (size_t i=0; ientries.push_back(i); + } + } + + delete[] inAssetDir; +} + +void putAssetDirData(SafeWriter* w, std::vector& dir) { + size_t blockStartSeek, blockEndSeek; + + w->write("ADIR",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeI(dir.size()); + + for (DivAssetDir& i: dir) { + w->writeString(i.name,false); + w->writeS(i.entries.size()); + for (int j: i.entries) { + w->writeC(j); + } + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); +} + +DivDataErrors readAssetDirData(SafeReader& reader, std::vector& dir) { + char magic[4]; + reader.read(magic,4); + if (memcmp(magic,"ADIR",4)!=0) { + logV("header is invalid: %c%c%c%c",magic[0],magic[1],magic[2],magic[3]); + return DIV_DATA_INVALID_HEADER; + } + reader.readI(); // reserved + + unsigned int numDirs=reader.readI(); + + dir.reserve(numDirs); + for (unsigned int i=0; i +#include "dataErrors.h" +#include "safeReader.h" +#include "safeWriter.h" + +struct DivAssetDir { + String name; + std::vector entries; + + DivAssetDir(): + name("New Directory") {} + DivAssetDir(String n): + name(n) {} +}; + +// check whether an asset directory is complete (UNSAFE) +void checkAssetDir(std::vector& dir, size_t entries); + +// move an asset +void moveAsset(std::vector& dir, int before, int after); + +// remove an asset +void removeAsset(std::vector& dir, int entry); + +// read/write asset dir +void putAssetDirData(SafeWriter* w, std::vector& dir); +DivDataErrors readAssetDirData(SafeReader& reader, std::vector& dir); + +#endif diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 46c43c5b2..81bf1aee7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -709,84 +709,6 @@ void DivEngine::changeSong(size_t songIndex) { prevRow=0; } -void DivEngine::moveAsset(std::vector& dir, int before, int after) { - if (before<0 || after<0) return; - for (DivAssetDir& i: dir) { - for (size_t j=0; j& dir, int entry) { - if (entry<0) return; - for (DivAssetDir& i: dir) { - for (size_t j=0; jentry) { - i.entries[j]--; - } - } - } -} - -void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { - bool* inAssetDir=new bool[entries]; - memset(inAssetDir,0,entries*sizeof(bool)); - - for (DivAssetDir& i: dir) { - for (size_t j=0; j=(int)entries) { - i.entries.erase(i.entries.begin()+j); - j--; - continue; - } - - // erase duplicate entry - if (inAssetDir[i.entries[j]]) { - i.entries.erase(i.entries.begin()+j); - j--; - continue; - } - - // mark entry as present - inAssetDir[i.entries[j]]=true; - } - } - - // get unsorted directory - DivAssetDir* unsortedDir=NULL; - for (DivAssetDir& i: dir) { - if (i.name.empty()) { - unsortedDir=&i; - break; - } - } - - // add missing items to unsorted directory - for (size_t i=0; ientries.push_back(i); - } - } - - delete[] inAssetDir; -} - void DivEngine::copyChannelP(int src, int dest) { if (src<0 || src>=chans) return; if (dest<0 || dest>=chans) return; diff --git a/src/engine/engine.h b/src/engine/engine.h index 66196f894..1c7d989eb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -701,16 +701,6 @@ class DivEngine { void swapSystemUnsafe(int src, int dest, bool preserveOrder=true); - // move an asset - void moveAsset(std::vector& dir, int before, int after); - - // remove an asset - void removeAsset(std::vector& dir, int entry); - - // read/write asset dir - void putAssetDirData(SafeWriter* w, std::vector& dir); - DivDataErrors readAssetDirData(SafeReader& reader, std::vector& dir); - // add every export method here friend class DivROMExport; friend class DivExportAmigaValidation; @@ -819,9 +809,6 @@ class DivEngine { // convert old flags static void convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivSystem sys); - // check whether an asset directory is complete (UNSAFE) - void checkAssetDir(std::vector& dir, size_t entries); - // benchmark (returns time in seconds) double benchmarkPlayback(); double benchmarkSeek(); diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 285e1aa1b..a27bf30d7 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -63,58 +63,6 @@ struct PatToWrite { pat(p) {} }; -void DivEngine::putAssetDirData(SafeWriter* w, std::vector& dir) { - size_t blockStartSeek, blockEndSeek; - - w->write("ADIR",4); - blockStartSeek=w->tell(); - w->writeI(0); - - w->writeI(dir.size()); - - for (DivAssetDir& i: dir) { - w->writeString(i.name,false); - w->writeS(i.entries.size()); - for (int j: i.entries) { - w->writeC(j); - } - } - - blockEndSeek=w->tell(); - w->seek(blockStartSeek,SEEK_SET); - w->writeI(blockEndSeek-blockStartSeek-4); - w->seek(0,SEEK_END); -} - -DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector& dir) { - char magic[4]; - reader.read(magic,4); - if (memcmp(magic,"ADIR",4)!=0) { - logV("header is invalid: %c%c%c%c",magic[0],magic[1],magic[2],magic[3]); - return DIV_DATA_INVALID_HEADER; - } - reader.readI(); // reserved - - unsigned int numDirs=reader.readI(); - - dir.reserve(numDirs); - for (unsigned int i=0; i entries; - - DivAssetDir(): - name("New Directory") {} - DivAssetDir(String n): - name(n) {} -}; - struct DivEffectStorage { DivEffectType id; unsigned short slot, storageVer; diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 0c99b9f61..5c8711e44 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -745,7 +745,7 @@ void FurnaceGUI::drawInsList(bool asChild) { if (dirToDelete!=-1) { e->lockEngine([this,dirToDelete]() { e->song.insDir.erase(e->song.insDir.begin()+dirToDelete); - e->checkAssetDir(e->song.insDir,e->song.ins.size()); + checkAssetDir(e->song.insDir,e->song.ins.size()); }); } } else { @@ -1402,7 +1402,7 @@ void FurnaceGUI::actualWaveList() { if (dirToDelete!=-1) { e->lockEngine([this,dirToDelete]() { e->song.waveDir.erase(e->song.waveDir.begin()+dirToDelete); - e->checkAssetDir(e->song.waveDir,e->song.wave.size()); + checkAssetDir(e->song.waveDir,e->song.wave.size()); }); } } else { @@ -1457,7 +1457,7 @@ void FurnaceGUI::actualSampleList() { if (dirToDelete!=-1) { e->lockEngine([this,dirToDelete]() { e->song.sampleDir.erase(e->song.sampleDir.begin()+dirToDelete); - e->checkAssetDir(e->song.sampleDir,e->song.sample.size()); + checkAssetDir(e->song.sampleDir,e->song.sample.size()); }); } } else { From a654d33df24351f1d0b87b2a29ed8d343709d2ad Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 12 Nov 2025 00:39:28 -0500 Subject: [PATCH 07/46] giga-refactor, part 1 --- src/engine/engine.cpp | 40 -------------------------------- src/engine/engine.h | 14 +----------- src/engine/fileOps/dmf.cpp | 6 ++--- src/engine/fileOps/fc.cpp | 2 ++ src/engine/fileOps/ftm.cpp | 3 ++- src/engine/fileOps/fur.cpp | 30 ++++++++++++------------ src/engine/fileOps/it.cpp | 4 ++-- src/engine/fileOps/mod.cpp | 4 ++-- src/engine/fileOps/s3m.cpp | 4 ++-- src/engine/fileOps/tfm.cpp | 6 +++-- src/engine/fileOps/xm.cpp | 4 ++-- src/engine/song.cpp | 47 +++++++++++++++++++++++++++++++++++--- src/engine/song.h | 24 ++++++++++++++++++- 13 files changed, 102 insertions(+), 86 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 81bf1aee7..5c552290a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2091,46 +2091,6 @@ const char** DivEngine::getRegisterSheet(int sys) { return disCont[sys].dispatch->getRegisterSheet(); } -void DivEngine::recalcChans() { - bool isInsTypePossible[DIV_INS_MAX]; - chans=0; - int chanIndex=0; - memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool)); - for (int i=0; ichanInsType[j][0]!=DIV_INS_NULL) { - isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][0]]=true; - } - - if (sysDefs[song.system[i]]->chanInsType[j][1]!=DIV_INS_NULL) { - isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][1]]=true; - } - } - } - } - - possibleInsTypes.clear(); - for (int i=0; imidiOut!=NULL) { output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); diff --git a/src/engine/engine.h b/src/engine/engine.h index 1c7d989eb..6b5611eb0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -463,7 +463,6 @@ class DivEngine { TAAudioDesc want, got; String exportPath; std::thread* exportThread; - int chans; bool configLoaded; bool active; bool lowQuality; @@ -546,7 +545,6 @@ class DivEngine { std::vector midiIns; std::vector midiOuts; std::vector cmdStream; - std::vector possibleInsTypes; std::vector effectInst; std::vector curChanMask; static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS]; @@ -625,7 +623,6 @@ class DivEngine { bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal); bool perSystemPreEffect(int ch, unsigned char effect, unsigned char effectVal); - void recalcChans(); void reset(); void playSub(bool preserveDrift, int goalRow=0); void runMidiClock(int totalCycles=1); @@ -716,10 +713,6 @@ class DivEngine { DivChannelData* curPat; DivSubSong* curSubSong; DivInstrument* tempIns; - DivSystem sysOfChan[DIV_MAX_CHANS]; - int dispatchOfChan[DIV_MAX_CHANS]; - int dispatchChanOfChan[DIV_MAX_CHANS]; - int dispatchFirstChan[DIV_MAX_CHANS]; bool keyHit[DIV_MAX_CHANS]; float* oscBuf[DIV_MAX_OUTPUTS]; float oscSize; @@ -953,7 +946,7 @@ class DivEngine { const char* getSystemNameJ(DivSystem sys); // get sys definition - const DivSysDef* getSystemDef(DivSystem sys); + static const DivSysDef* getSystemDef(DivSystem sys); // get ROM export definition const DivROMExportDef* getROMExportDef(DivROMExportOptions opt); @@ -1456,7 +1449,6 @@ class DivEngine { DivEngine(): output(NULL), exportThread(NULL), - chans(0), configLoaded(false), active(false), lowQuality(false), @@ -1591,10 +1583,6 @@ class DivEngine { mu5ROM(NULL) { memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); - memset(dispatchFirstChan,0,DIV_MAX_CHANS*sizeof(int)); - memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); - memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int)); - memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int)); memset(vibTable,0,64*sizeof(short)); memset(tremTable,0,128*sizeof(short)); memset(effectSlotMap,-1,4096*sizeof(short)); diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index dd4f93281..492f0c21f 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -1168,6 +1168,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); + ds.recalcChans(); if (active) quitDispatch(); BUSY_BEGIN_SOFT; @@ -1175,7 +1176,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { song.unload(); song=ds; changeSong(0); - recalcChans(); // always convert to normal sample mode (I have no idea how will I do export) convertLegacySampleMode(); saveLock.unlock(); @@ -1289,7 +1289,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="maximum number of wavetables in .dmf is 64"; return NULL; } - for (int i=0; iordersLen; j++) { if (curOrders->ord[i][j]>0x7f) { logE("order %d, %d is out of range (0-127)!",i,j); @@ -1356,7 +1356,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeI(curSubSong->patLen); w->writeC(curSubSong->ordersLen); - for (int i=0; iordersLen; j++) { w->writeC(curOrders->ord[i][j]); if (version>=25) { diff --git a/src/engine/fileOps/fc.cpp b/src/engine/fileOps/fc.cpp index 6e4408a7d..8fbf49502 100644 --- a/src/engine/fileOps/fc.cpp +++ b/src/engine/fileOps/fc.cpp @@ -664,6 +664,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ds.subsong[0]->optimizePatterns(); ds.subsong[0]->rearrangePatterns(); + ds.recalcChans(); + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index 791269891..1fee6f053 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -2802,13 +2802,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } + ds.recalcChans(); + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); song.unload(); song=ds; changeSong(0); - recalcChans(); saveLock.unlock(); BUSY_END; if (active) { diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index a27bf30d7..7a22eed00 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -2129,6 +2129,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } else if (ds.version<237 && ds.linearPitch!=0) { addWarning("this song used partial pitch linearity, which has been removed from Furnace. you may have to adjust your song."); } + ds.recalcChans(); if (active) quitDispatch(); BUSY_BEGIN_SOFT; @@ -2136,7 +2137,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { song.unload(); song=ds; changeSong(0); - recalcChans(); // removal of legacy sample mode if (song.version<239) { if (convertLegacySampleMode()) { @@ -2242,7 +2242,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // low short is pattern number std::vector patsToWrite; if (getConfInt("saveUnusedPatterns",0)==1) { - for (int i=0; iwriteI(0); } - for (int i=0; iordersLen; j++) { w->writeC(subSong->orders.ord[i][j]); } } - for (int i=0; iwriteC(subSong->pat[i].effectCols); } - for (int i=0; iwriteC( (subSong->chanShow[i]?1:0)| (subSong->chanShowChanOsc[i]?2:0) ); } - for (int i=0; iwriteC(subSong->chanCollapse[i]); } - for (int i=0; iwriteString(subSong->chanName[i],false); } - for (int i=0; iwriteString(subSong->chanShortName[i],false); } @@ -2518,32 +2518,32 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeString(subSong->name,false); w->writeString(subSong->notes,false); - for (int i=0; iordersLen; j++) { w->writeC(subSong->orders.ord[i][j]); } } - for (int i=0; iwriteC(subSong->pat[i].effectCols); } - for (int i=0; iwriteC( (subSong->chanShow[i]?1:0)| (subSong->chanShowChanOsc[i]?2:0) ); } - for (int i=0; iwriteC(subSong->chanCollapse[i]); } - for (int i=0; iwriteString(subSong->chanName[i],false); } - for (int i=0; iwriteString(subSong->chanShortName[i],false); } diff --git a/src/engine/fileOps/it.cpp b/src/engine/fileOps/it.cpp index 02d0b00a5..3fe6285a1 100644 --- a/src/engine/fileOps/it.cpp +++ b/src/engine/fileOps/it.cpp @@ -1675,7 +1675,8 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { ds.systemName="PC"; // find subsongs - ds.findSubSongs(maxChan); + ds.recalcChans(); + ds.findSubSongs(); // populate subsongs with default panning values for (size_t i=0; i newSubSongs; for (DivSubSong* i: subsong) { std::vector subSongStart; @@ -689,9 +689,50 @@ void DivSong::findSubSongs(int chans) { for (DivSubSong* i: newSubSongs) { subsong.push_back(i); } - } +void DivSong::recalcChans() { + bool isInsTypePossible[DIV_INS_MAX]; + chans=0; + int chanIndex=0; + memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool)); + for (int i=0; ichanInsType[j][0]!=DIV_INS_NULL) { + isInsTypePossible[sysDef->chanInsType[j][0]]=true; + } + + if (sysDef->chanInsType[j][1]!=DIV_INS_NULL) { + isInsTypePossible[sysDef->chanInsType[j][1]]=true; + } + } + } + } + + possibleInsTypes.clear(); + for (int i=0; iclearData(); @@ -749,4 +790,4 @@ void DivSong::unload() { delete i; } subsong.clear(); -} \ No newline at end of file +} diff --git a/src/engine/song.h b/src/engine/song.h index fef22a7c4..83414452a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -274,6 +274,7 @@ struct DivSong { bool isDMF; // system + int chans; DivSystem system[DIV_MAX_CHIPS]; unsigned short systemChans[DIV_MAX_CHIPS]; unsigned char systemLen; @@ -396,10 +397,17 @@ struct DivSong { DivWavetable nullWave; DivSample nullSample; + DivSystem sysOfChan[DIV_MAX_CHANS]; + int dispatchOfChan[DIV_MAX_CHANS]; + int dispatchChanOfChan[DIV_MAX_CHANS]; + int dispatchFirstChan[DIV_MAX_CHANS]; + + std::vector possibleInsTypes; + /** * find data past 0Bxx effects and place that into new sub-songs. */ - void findSubSongs(int chans); + void findSubSongs(); /** * clear orders and patterns. @@ -421,6 +429,12 @@ struct DivSong { */ void clearSamples(); + /** + * recalculate channel count and internal state. + " call after editing system[] or systemChans[]. + */ + void recalcChans(); + /** * unloads the song, freeing all memory associated with it. * use before destroying the object. @@ -430,6 +444,7 @@ struct DivSong { DivSong(): version(0), isDMF(false), + chans(0), systemLen(2), name(""), author(""), @@ -508,6 +523,11 @@ struct DivSong { oldAlwaysSetVolume(false), oldSampleOffset(false), oldCenterRate(true) { + memset(dispatchFirstChan,0,DIV_MAX_CHANS*sizeof(int)); + memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); + memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int)); + memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int)); + for (int i=0; i Date: Wed, 12 Nov 2025 05:06:51 -0500 Subject: [PATCH 08/46] giga-refactor, part 2 --- src/engine/cmdStream.cpp | 2 +- src/engine/cmdStreamOps.cpp | 42 +++--- src/engine/engine.cpp | 206 +++++++++++++------------- src/engine/export/amigaValidation.cpp | 2 +- src/engine/fileOps/dmf.cpp | 2 + src/engine/fileOps/fc.cpp | 3 +- src/engine/fileOps/ftm.cpp | 2 + src/engine/fileOps/fur.cpp | 1 + src/engine/fileOps/it.cpp | 2 + src/engine/fileOps/mod.cpp | 2 + src/engine/fileOps/s3m.cpp | 2 + src/engine/fileOps/text.cpp | 4 +- src/engine/fileOps/tfm.cpp | 3 + src/engine/fileOps/xm.cpp | 2 + src/engine/legacySample.cpp | 43 +++--- src/engine/playback.cpp | 68 ++++----- src/engine/song.cpp | 20 ++- src/engine/song.h | 10 +- src/engine/sysDef.cpp | 34 ++--- src/engine/vgmOps.cpp | 16 +- src/engine/wavOps.cpp | 28 ++-- src/gui/chanOsc.cpp | 6 +- src/gui/channels.cpp | 2 +- src/gui/debugWindow.cpp | 4 +- src/gui/effectList.cpp | 4 +- src/gui/pattern.cpp | 4 +- src/log.cpp | 5 +- 27 files changed, 282 insertions(+), 237 deletions(-) diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index 568f5ee0c..41c2c3cc8 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -704,7 +704,7 @@ bool DivCSPlayer::init() { // initialize state for (int i=0; igetTotalChannelCount(); i++) { - chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff; + chan[i].volMax=(e->getDispatch(e->song.dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->song.dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; } diff --git a/src/engine/cmdStreamOps.cpp b/src/engine/cmdStreamOps.cpp index 4b7718424..295862a65 100644 --- a/src/engine/cmdStreamOps.cpp +++ b/src/engine/cmdStreamOps.cpp @@ -1303,7 +1303,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options // write header w->write("FCS",4); - w->writeS(chans); + w->writeS(song.chans); // flags w->writeC((options.longPointers?1:0)|(options.bigEndian?2:0)); // reserved @@ -1313,7 +1313,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options w->writeC(0); } // offsets - for (int i=0; iinit(); if (options.longPointers) { @@ -1323,7 +1323,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options } } // max stack sizes - for (int i=0; iwriteC(0); } @@ -1338,7 +1338,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options // PASS 0: play the song and log channel command streams // song beginning marker - for (int i=0; iwriteC(0xd0); chanStream[i]->writeC(i); chanStream[i]->writeC(0x00); @@ -1350,7 +1350,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options chanStream[i]->writeC(0x00); } while (!done) { - for (int i=0; itell()); } if (loopTick==-1) { @@ -1359,7 +1359,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options logI("loop is on tick %d",tick); loopTick=tick; // loop marker - for (int i=0; iwriteC(0xd0); chanStream[i]->writeC(i); chanStream[i]->writeC(0x00); @@ -1410,7 +1410,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options } } cmdStream.clear(); - for (int i=0; iwriteC(0xde); // padding chanStream[i]->writeC(0x00); @@ -1424,7 +1424,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options tick++; } if (!playing || loopTick<0) { - for (int i=0; iwriteC(0xdf); // padding chanStream[i]->writeC(0x00); @@ -1436,7 +1436,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options chanStream[i]->writeC(0x00); } } else { - for (int i=0; iloopTick) { chanStream[i]->writeC(0xda); chanStream[i]->writeI(tickPos[i][loopTick]); @@ -1494,7 +1494,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options } // set preset instruments - for (int h=0; hgetFinalBuf(); for (size_t i=0; isize(); i+=8) { if (buf[i]==0xb8) { @@ -1536,7 +1536,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options } // set preset volumes - for (int h=0; hgetFinalBuf(); for (size_t i=0; isize(); i+=8) { if (buf[i]==0xc7) { @@ -1578,7 +1578,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options } // set speed dial commands - for (int h=0; hgetFinalBuf(); for (size_t i=0; isize(); i+=8) { if (buf[i]==0xd7) { @@ -1601,7 +1601,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options // PASS 2: condense delays if (!options.noDelayCondense) { // calculate delay usage - for (int h=0; hgetFinalBuf(); int delayCount=0; for (size_t i=0; isize(); i+=8) { @@ -1639,7 +1639,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options } // condense delays - for (int h=0; hgetFinalBuf(); int delayPos=-1; int delayCount=0; @@ -1693,7 +1693,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options // PASS 3: note off + one-tick wait // optimize one-tick gaps sometimes used in songs - for (int h=0; hgetFinalBuf(); if (chanStream[h]->size()<8) continue; for (size_t i=0; isize()-8; i+=8) { @@ -1714,12 +1714,12 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options // PASS 4: remove nop's // this includes modifying call addresses to compensate - for (int h=0; htell(); logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); reloc8(chanStream[i]->getFinalBuf(),chanStream[i]->size(),0,globalStream->tell()); @@ -1798,7 +1798,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options // also find new offsets globalStream=stripNopsPacked(globalStream,sortedCmd,chanStreamOff); - for (int h=0; htell(); } @@ -1807,7 +1807,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options w->write(globalStream->getFinalBuf(),globalStream->size()); // calculate max stack sizes - for (int h=0; h callStack; unsigned int maxStackSize=0; unsigned char* buf=w->getFinalBuf(); @@ -1866,7 +1866,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options delete globalStream; w->seek(40,SEEK_SET); - for (int i=0; iwriteI_BE(chanStreamOff[i]); @@ -1884,7 +1884,7 @@ SafeWriter* DivEngine::saveCommand(DivCSProgress* progress, DivCSOptions options logD("maximum stack sizes:"); unsigned int cumulativeStackSize=0; - for (int i=0; iwriteC(chanStackSize[i]); logD("- %d: %d",i,chanStackSize[i]); cumulativeStackSize+=chanStackSize[i]; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 5c552290a..1de23a682 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -179,19 +179,23 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul case 0x92: return _("92xx: Set sample offset (third byte, ×65536)"); } - } else if (chan>=0 && chaneffectHandlers.find(effect); - if (iter!=sysDef->effectHandlers.end()) { - return iter->second.description; - } - iter=sysDef->postEffectHandlers.find(effect); - if (iter!=sysDef->postEffectHandlers.end()) { - return iter->second.description; - } - iter=sysDef->preEffectHandlers.find(effect); - if (iter!=sysDef->preEffectHandlers.end()) { - return iter->second.description; + } else if (chan>=0 && chaneffectHandlers.find(effect); + if (iter!=sysDef->effectHandlers.end()) { + return iter->second.description; + } + iter=sysDef->postEffectHandlers.find(effect); + if (iter!=sysDef->postEffectHandlers.end()) { + return iter->second.description; + } + iter=sysDef->preEffectHandlers.find(effect); + if (iter!=sysDef->preEffectHandlers.end()) { + return iter->second.description; + } } } break; @@ -201,7 +205,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul void DivEngine::calcSongTimestamps() { if (curSubSong!=NULL) { - curSubSong->calcTimestamps(chans,song.grooves,song.jumpTreatment,song.ignoreJumpAtEnd,song.brokenSpeedSel,song.delayBehavior); + curSubSong->calcTimestamps(song.chans,song.grooves,song.jumpTreatment,song.ignoreJumpAtEnd,song.brokenSpeedSel,song.delayBehavior); } } @@ -531,6 +535,7 @@ void DivEngine::initSongWithDesc(const char* description, bool inBase64, bool ol song.systemFlags[index].loadFromBase64(flags.c_str()); } song.systemLen=index; + song.initDefaultSystemChans(); // extra attributes song.subsong[0]->hz=c.getDouble("tickRate",60.0); @@ -564,7 +569,8 @@ void DivEngine::createNew(const char* description, String sysName, bool inBase64 } else { song.systemName=sysName; } - recalcChans(); + song.initDefaultSystemChans(); + song.recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); @@ -602,7 +608,8 @@ void DivEngine::createNewFromDefaults() { song.systemName=sysName; } - recalcChans(); + song.initDefaultSystemChans(); + song.recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); @@ -710,8 +717,8 @@ void DivEngine::changeSong(size_t songIndex) { } void DivEngine::copyChannelP(int src, int dest) { - if (src<0 || src>=chans) return; - if (dest<0 || dest>=chans) return; + if (src<0 || src>=song.chans) return; + if (dest<0 || dest>=song.chans) return; BUSY_BEGIN; saveLock.lock(); copyChannel(src,dest); @@ -720,8 +727,8 @@ void DivEngine::copyChannelP(int src, int dest) { } void DivEngine::swapChannelsP(int src, int dest) { - if (src<0 || src>=chans) return; - if (dest<0 || dest>=chans) return; + if (src<0 || src>=song.chans) return; + if (dest<0 || dest>=song.chans) return; BUSY_BEGIN; saveLock.lock(); swapChannels(src,dest); @@ -874,7 +881,7 @@ void DivEngine::delUnusedIns() { memset(isUsed,0,256*sizeof(bool)); // scan - for (int i=0; ipat[i].data[k]==NULL) continue; @@ -992,12 +999,12 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { lastError=_("invalid index"); return false; } - if (chans-getChannelCount(song.system[index])+getChannelCount(which)>DIV_MAX_CHANS) { + if (song.chans-getChannelCount(song.system[index])+getChannelCount(which)>DIV_MAX_CHANS) { lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); return false; } - int chanCount=chans; + int chanCount=song.chans; quitDispatch(); BUSY_BEGIN; saveLock.lock(); @@ -1005,7 +1012,7 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { if (!preserveOrder) { int firstChan=0; int chanMovement=getChannelCount(which)-getChannelCount(song.system[index]); - while (dispatchOfChan[firstChan]!=index) firstChan++; + while (song.dispatchOfChan[firstChan]!=index) firstChan++; int lastChan=firstChan+getChannelCount(song.system[index]); if (chanMovement!=0) { if (chanMovement>0) { @@ -1030,7 +1037,7 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { song.system[index]=which; song.systemFlags[index].clear(); - recalcChans(); + song.recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); @@ -1047,7 +1054,7 @@ bool DivEngine::addSystem(DivSystem which) { lastError=fmt::sprintf(_("max number of systems is %d"),DIV_MAX_CHIPS); return false; } - if (chans+getChannelCount(which)>DIV_MAX_CHANS) { + if (song.chans+getChannelCount(which)>DIV_MAX_CHANS) { lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); return false; } @@ -1059,7 +1066,7 @@ bool DivEngine::addSystem(DivSystem which) { song.systemPan[song.systemLen]=0; song.systemPanFR[song.systemLen]=0; song.systemFlags[song.systemLen++].clear(); - recalcChans(); + song.recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); @@ -1101,7 +1108,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { lastError=fmt::sprintf(_("max number of systems is %d"),DIV_MAX_CHIPS); return false; } - if (chans+getChannelCount(song.system[index])>DIV_MAX_CHANS) { + if (song.chans+getChannelCount(song.system[index])>DIV_MAX_CHANS) { lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); return false; } @@ -1113,7 +1120,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { song.systemPan[song.systemLen]=song.systemPan[index]; song.systemPanFR[song.systemLen]=song.systemPanFR[index]; song.systemFlags[song.systemLen++]=song.systemFlags[index]; - recalcChans(); + song.recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); @@ -1184,7 +1191,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { swapSystemUnsafe(i,i-1,false); } - recalcChans(); + song.recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); @@ -1206,14 +1213,14 @@ bool DivEngine::removeSystem(int index, bool preserveOrder) { lastError=_("invalid index"); return false; } - int chanCount=chans; + int chanCount=song.chans; quitDispatch(); BUSY_BEGIN; saveLock.lock(); if (!preserveOrder) { int firstChan=0; - while (dispatchOfChan[firstChan]!=index) firstChan++; + while (song.dispatchOfChan[firstChan]!=index) firstChan++; for (int i=0; i=chans) return NULL; + if (ch<0 || ch>=song.chans) return NULL; return &chan[ch]; } unsigned short DivEngine::getChanPan(int ch) { - if (ch<0 || ch>=chans) return 0; - return disCont[dispatchOfChan[ch]].dispatch->getPan(dispatchChanOfChan[ch]); + if (ch<0 || ch>=song.chans) return 0; + return disCont[song.dispatchOfChan[ch]].dispatch->getPan(song.dispatchChanOfChan[ch]); } void* DivEngine::getDispatchChanState(int ch) { - if (ch<0 || ch>=chans) return NULL; - return disCont[dispatchOfChan[ch]].dispatch->getChanState(dispatchChanOfChan[ch]); + if (ch<0 || ch>=song.chans) return NULL; + return disCont[song.dispatchOfChan[ch]].dispatch->getChanState(song.dispatchChanOfChan[ch]); } void DivEngine::getChanPaired(int ch, std::vector& ret) { - if (ch<0 || ch>=chans) return; - disCont[dispatchOfChan[ch]].dispatch->getPaired(dispatchChanOfChan[ch],ret); + if (ch<0 || ch>=song.chans) return; + disCont[song.dispatchOfChan[ch]].dispatch->getPaired(song.dispatchChanOfChan[ch],ret); } DivChannelModeHints DivEngine::getChanModeHints(int ch) { - if (ch<0 || ch>=chans) return DivChannelModeHints(); - return disCont[dispatchOfChan[ch]].dispatch->getModeHints(dispatchChanOfChan[ch]); + if (ch<0 || ch>=song.chans) return DivChannelModeHints(); + return disCont[song.dispatchOfChan[ch]].dispatch->getModeHints(song.dispatchChanOfChan[ch]); } unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) { @@ -1547,18 +1554,18 @@ unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) { } DivMacroInt* DivEngine::getMacroInt(int chan) { - if (chan<0 || chan>=chans) return NULL; - return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]); + if (chan<0 || chan>=song.chans) return NULL; + return disCont[song.dispatchOfChan[chan]].dispatch->getChanMacroInt(song.dispatchChanOfChan[chan]); } DivSamplePos DivEngine::getSamplePos(int chan) { - if (chan<0 || chan>=chans) return DivSamplePos(); - return disCont[dispatchOfChan[chan]].dispatch->getSamplePos(dispatchChanOfChan[chan]); + if (chan<0 || chan>=song.chans) return DivSamplePos(); + return disCont[song.dispatchOfChan[chan]].dispatch->getSamplePos(song.dispatchChanOfChan[chan]); } DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) { - if (chan<0 || chan>=chans) return NULL; - return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]); + if (chan<0 || chan>=song.chans) return NULL; + return disCont[song.dispatchOfChan[chan]].dispatch->getOscBuffer(song.dispatchChanOfChan[chan]); } void DivEngine::enableCommandStream(bool enable) { @@ -1694,7 +1701,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { if (goal>0 || goalRow>0) { for (int i=0; iforceIns(); } - for (int i=0; imidiOut!=NULL) { output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); - for (int i=0; i=0) { output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0)); } @@ -2053,8 +2060,8 @@ void DivEngine::stop() { } // reset all chan oscs - for (int i=0; igetOscBuffer(dispatchChanOfChan[i]); + for (int i=0; igetOscBuffer(song.dispatchChanOfChan[i]); if (buf!=NULL) { buf->reset(); } @@ -2094,7 +2101,7 @@ const char** DivEngine::getRegisterSheet(int sys) { void DivEngine::reset() { if (output) if (output->midiOut!=NULL) { output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); - for (int i=0; i=0) { output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0)); } @@ -2102,7 +2109,7 @@ void DivEngine::reset() { } for (int i=0; idispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; + if (idispatch(DivCommand(DIV_CMD_GET_VOLMAX,song.dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; if (!song.linearPitch) chan[i].vibratoFine=4; } @@ -2375,16 +2382,16 @@ int DivEngine::getMaxVolumeChan(int ch) { int DivEngine::mapVelocity(int ch, float vel) { if (ch<0) return 0; - if (ch>=chans) return 0; - if (disCont[dispatchOfChan[ch]].dispatch==NULL) return 0; - return disCont[dispatchOfChan[ch]].dispatch->mapVelocity(dispatchChanOfChan[ch],vel); + if (ch>=song.chans) return 0; + if (disCont[song.dispatchOfChan[ch]].dispatch==NULL) return 0; + return disCont[song.dispatchOfChan[ch]].dispatch->mapVelocity(song.dispatchChanOfChan[ch],vel); } float DivEngine::getGain(int ch, int vol) { if (ch<0) return 0; - if (ch>=chans) return 0; - if (disCont[dispatchOfChan[ch]].dispatch==NULL) return 0; - return disCont[dispatchOfChan[ch]].dispatch->getGain(dispatchChanOfChan[ch],vol); + if (ch>=song.chans) return 0; + if (disCont[song.dispatchOfChan[ch]].dispatch==NULL) return 0; + return disCont[song.dispatchOfChan[ch]].dispatch->getGain(song.dispatchChanOfChan[ch],vol); } unsigned char DivEngine::getOrder() { @@ -2494,7 +2501,7 @@ void DivEngine::toggleMute(int chan) { void DivEngine::toggleSolo(int chan) { bool solo=false; - for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); + if (disCont[song.dispatchOfChan[i]].dispatch!=NULL) { + disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],isMuted[i]); } } } else { - for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); + if (disCont[song.dispatchOfChan[i]].dispatch!=NULL) { + disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],isMuted[i]); } } } @@ -2527,18 +2534,18 @@ void DivEngine::toggleSolo(int chan) { void DivEngine::muteChannel(int chan, bool mute) { BUSY_BEGIN; isMuted[chan]=mute; - if (disCont[dispatchOfChan[chan]].dispatch!=NULL) { - disCont[dispatchOfChan[chan]].dispatch->muteChannel(dispatchChanOfChan[chan],isMuted[chan]); + if (disCont[song.dispatchOfChan[chan]].dispatch!=NULL) { + disCont[song.dispatchOfChan[chan]].dispatch->muteChannel(song.dispatchChanOfChan[chan],isMuted[chan]); } BUSY_END; } void DivEngine::unmuteAll() { BUSY_BEGIN; - for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); + if (disCont[song.dispatchOfChan[i]].dispatch!=NULL) { + disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],isMuted[i]); } } BUSY_END; @@ -2609,8 +2616,8 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) { DivInstrument* ins=new DivInstrument; int insCount=(int)song.ins.size(); DivInstrumentType prefType; - if (refChan>chans) { - refChan=chans-1; + if (refChan>song.chans) { + refChan=song.chans-1; } if (refChan<0) { prefType=fallbackType; @@ -2634,7 +2641,7 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) { break; } if (refChan>=0) { - if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) { + if (song.sysOfChan[refChan]==DIV_SYSTEM_QSOUND) { *ins=song.nullInsQSound; } } @@ -2694,7 +2701,7 @@ void DivEngine::delInstrumentUnsafe(int index) { delete song.ins[index]; song.ins.erase(song.ins.begin()+index); song.insLen=song.ins.size(); - for (int i=0; ipat[i].data[k]==NULL) continue; @@ -3004,7 +3011,7 @@ void DivEngine::addOrder(int pos, bool duplicate, bool where) { } } else { bool used[DIV_MAX_PATTERNS]; - for (int i=0; iordersLen; j++) { used[curOrders->ord[i][j]]=true; @@ -3053,7 +3060,7 @@ void DivEngine::deepCloneOrder(int pos, bool where) { if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return; warnings=""; BUSY_BEGIN_SOFT; - for (int i=0; iord[i][pos]; @@ -3077,14 +3084,14 @@ void DivEngine::deepCloneOrder(int pos, bool where) { } if (where) { // at the end saveLock.lock(); - for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); - for (int i=0; iordersLen; j>pos; j--) { curOrders->ord[i][j]=curOrders->ord[i][j-1]; } @@ -3182,7 +3189,7 @@ void DivEngine::moveOrderDown(int& pos) { } void DivEngine::exchangeIns(int one, int two) { - for (int i=0; ipat[i].data[k]==NULL) continue; @@ -3468,7 +3475,7 @@ void DivEngine::patchDisconnectAll(unsigned int portSet) { } void DivEngine::noteOn(int chan, int ins, int note, int vol) { - if (chan<0 || chan>=chans) return; + if (chan<0 || chan>=song.chans) return; BUSY_BEGIN; pendingNotes.push_back(DivNoteEvent(chan,ins,note,vol,true)); if (!playing) { @@ -3480,7 +3487,7 @@ void DivEngine::noteOn(int chan, int ins, int note, int vol) { } void DivEngine::noteOff(int chan) { - if (chan<0 || chan>=chans) return; + if (chan<0 || chan>=song.chans) return; BUSY_BEGIN; pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); if (!playing) { @@ -3497,7 +3504,7 @@ int DivEngine::getViableChannel(int chan, int off, int ins) { // if there isn't an instrument, just offset chan by off if (ins==-1) { - return (chan+off)%chans; + return (chan+off)%song.chans; } bool isViable[DIV_MAX_CHANS]; @@ -3507,7 +3514,7 @@ int DivEngine::getViableChannel(int chan, int off, int ins) { // this is a copy of the routine in autoNoteOn...... I am lazy DivInstrument* insInst=getIns(ins); - for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) { if (insInst->type==DIV_INS_OPL) { if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { @@ -3527,12 +3534,12 @@ int DivEngine::getViableChannel(int chan, int off, int ins) { // screw it if none of the channels are viable if (!isAtLeastOneViable) { - return (chan+off)%chans; + return (chan+off)%song.chans; } // now offset (confined to viable channels) int channelsCycled=0; - int i=(chan+1)%chans; + int i=(chan+1)%song.chans; int attempts=0; while (true) { if (isViable[i]) { @@ -3543,7 +3550,7 @@ int DivEngine::getViableChannel(int chan, int off, int ins) { } } - if (++i>=chans) { + if (++i>=song.chans) { i=0; } @@ -3555,7 +3562,7 @@ int DivEngine::getViableChannel(int chan, int off, int ins) { } // fail-safe - return (chan+off)%chans; + return (chan+off)%song.chans; } bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol, int transpose) { @@ -3563,7 +3570,7 @@ bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol, int transpose) { bool canPlayAnyway=false; bool notInViableChannel=false; if (midiBaseChan<0) midiBaseChan=0; - if (midiBaseChan>=chans) midiBaseChan=chans-1; + if (midiBaseChan>=song.chans) midiBaseChan=song.chans-1; int finalChan=midiBaseChan; int finalChanType=getChannelType(finalChan); @@ -3576,7 +3583,7 @@ bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol, int transpose) { // 1. check which channels are viable for this instrument DivInstrument* insInst=getIns(ins); if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true; - for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) { if (insInst->type==DIV_INS_OPL) { if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { @@ -3604,7 +3611,7 @@ bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol, int transpose) { pendingNotes.push_back(DivNoteEvent(finalChan,ins,note+transpose,vol,true)); return true; } - if (++finalChan>=chans) { + if (++finalChan>=song.chans) { finalChan=0; } } while (finalChan!=midiBaseChan); @@ -3615,7 +3622,7 @@ bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol, int transpose) { if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel) && chan[finalChan].midiAge=chans) { + if (++finalChan>=song.chans) { finalChan=0; } } while (finalChan!=midiBaseChan); @@ -3630,8 +3637,8 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) { if (!playing) { return; } - //if (ch<0 || ch>=chans) return; - for (int i=0; i=song.chans) return; + for (int i=0; i=chans) chan=0; + if (chan<0 || chan>=song.chans) chan=0; midiBaseChan=chan; } @@ -3912,7 +3919,7 @@ void DivEngine::initDispatch(bool isRender) { autoPatchbay(); saveLock.unlock(); } - recalcChans(); + song.recalcChans(); BUSY_END; } @@ -3928,7 +3935,6 @@ void DivEngine::quitDispatch() { midiClockDrift=0; midiTimeCycles=0; midiTimeDrift=0; - chans=0; playing=false; curSpeed=0; endOfSong=false; diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index 30224e84f..38b61a41f 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -91,7 +91,7 @@ void DivExportAmigaValidation::run() { size_t lastTick=0; //bool writeLoop=false; int loopPos=-1; - for (int i=0; ichans; i++) { + for (int i=0; isong.chans; i++) { e->chan[i].wentThroughNote=false; e->chan[i].goneThroughNote=false; } diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index 492f0c21f..2557e4b6d 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -1168,6 +1168,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); + ds.initDefaultSystemChans(); ds.recalcChans(); if (active) quitDispatch(); @@ -1175,6 +1176,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); // always convert to normal sample mode (I have no idea how will I do export) convertLegacySampleMode(); diff --git a/src/engine/fileOps/fc.cpp b/src/engine/fileOps/fc.cpp index 8fbf49502..027dda971 100644 --- a/src/engine/fileOps/fc.cpp +++ b/src/engine/fileOps/fc.cpp @@ -664,6 +664,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ds.subsong[0]->optimizePatterns(); ds.subsong[0]->rearrangePatterns(); + ds.initDefaultSystemChans(); ds.recalcChans(); if (active) quitDispatch(); @@ -671,8 +672,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); - recalcChans(); saveLock.unlock(); BUSY_END; if (active) { diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index 1fee6f053..b2df53798 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -2802,6 +2802,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } + ds.initDefaultSystemChans(); ds.recalcChans(); if (active) quitDispatch(); @@ -2809,6 +2810,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 7a22eed00..6436ba98a 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -2136,6 +2136,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); // removal of legacy sample mode if (song.version<239) { diff --git a/src/engine/fileOps/it.cpp b/src/engine/fileOps/it.cpp index 3fe6285a1..3ea6c2bfc 100644 --- a/src/engine/fileOps/it.cpp +++ b/src/engine/fileOps/it.cpp @@ -1675,6 +1675,7 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { ds.systemName="PC"; // find subsongs + ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); @@ -1708,6 +1709,7 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; diff --git a/src/engine/fileOps/mod.cpp b/src/engine/fileOps/mod.cpp index 7f933b27f..774a0004b 100644 --- a/src/engine/fileOps/mod.cpp +++ b/src/engine/fileOps/mod.cpp @@ -431,6 +431,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.insLen=ds.ins.size(); // find subsongs + ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); @@ -439,6 +440,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; diff --git a/src/engine/fileOps/s3m.cpp b/src/engine/fileOps/s3m.cpp index b866b2920..3c03ad2d9 100644 --- a/src/engine/fileOps/s3m.cpp +++ b/src/engine/fileOps/s3m.cpp @@ -1179,6 +1179,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { } // find subsongs + ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); @@ -1215,6 +1216,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; diff --git a/src/engine/fileOps/text.cpp b/src/engine/fileOps/text.cpp index 3dbc598d3..bab5dc7ea 100644 --- a/src/engine/fileOps/text.cpp +++ b/src/engine/fileOps/text.cpp @@ -302,7 +302,7 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) { for (int j=0; jordersLen; j++) { w->writeText(fmt::sprintf("%.2X |",j)); - for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); } w->writeText("\n"); @@ -318,7 +318,7 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) { for (int k=0; kpatLen; k++) { w->writeText(fmt::sprintf("%.2X ",k)); - for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); short note, octave; noteToSplitNote(p->newData[k][DIV_PAT_NOTE],note,octave); diff --git a/src/engine/fileOps/tfm.cpp b/src/engine/fileOps/tfm.cpp index 557ad351b..7ea370f1d 100644 --- a/src/engine/fileOps/tfm.cpp +++ b/src/engine/fileOps/tfm.cpp @@ -711,6 +711,7 @@ bool DivEngine::loadTFMv1(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; @@ -905,6 +906,7 @@ bool DivEngine::loadTFMv2(unsigned char* file, size_t len) { info.loopPos=loopPos; TFMParsePattern(info); + ds.initDefaultSystemChans(); ds.recalcChans(); if (active) quitDispatch(); @@ -912,6 +914,7 @@ bool DivEngine::loadTFMv2(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; diff --git a/src/engine/fileOps/xm.cpp b/src/engine/fileOps/xm.cpp index fe0ec35c1..57e59ab3a 100644 --- a/src/engine/fileOps/xm.cpp +++ b/src/engine/fileOps/xm.cpp @@ -1377,6 +1377,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { } // find subsongs + ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); @@ -1385,6 +1386,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + hasLoadedSomething=true; changeSong(0); saveLock.unlock(); BUSY_END; diff --git a/src/engine/legacySample.cpp b/src/engine/legacySample.cpp index 21734c903..db827adda 100644 --- a/src/engine/legacySample.cpp +++ b/src/engine/legacySample.cpp @@ -22,6 +22,7 @@ #include "engine.h" #include +// TODO: this function could be in DivSong instead bool DivEngine::convertLegacySampleMode() { logD("converting legacy sample mode..."); int legacyInsInit=-1; @@ -114,7 +115,7 @@ bool DivEngine::convertLegacySampleMode() { }; for (DivSubSong* h: song.subsong) { - for (int i=0; i9) { + if (song.dispatchChanOfChan[i]<8 || song.dispatchChanOfChan[i]>9) { continue; } sampleMode=1; @@ -205,7 +206,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2610_CSM: // Neo Geo CD ADPCM channels - if (dispatchChanOfChan[i]<11) { + if (song.dispatchChanOfChan[i]<11) { continue; } sampleMode=1; @@ -214,7 +215,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2610B: // ADPCM channels - if (dispatchChanOfChan[i]<9) { + if (song.dispatchChanOfChan[i]<9) { continue; } sampleMode=1; @@ -223,7 +224,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2610B_EXT: // ADPCM channels - if (dispatchChanOfChan[i]<12) { + if (song.dispatchChanOfChan[i]<12) { continue; } sampleMode=1; @@ -232,7 +233,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2610B_CSM: // ADPCM channels - if (dispatchChanOfChan[i]<13) { + if (song.dispatchChanOfChan[i]<13) { continue; } sampleMode=1; @@ -241,7 +242,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2608: // ADPCM channel - if (dispatchChanOfChan[i]!=15) { + if (song.dispatchChanOfChan[i]!=15) { continue; } sampleMode=1; @@ -249,7 +250,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2608_EXT: // ADPCM channel - if (dispatchChanOfChan[i]!=18) { + if (song.dispatchChanOfChan[i]!=18) { continue; } sampleMode=1; @@ -257,7 +258,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_YM2608_CSM: // ADPCM channel - if (dispatchChanOfChan[i]!=19) { + if (song.dispatchChanOfChan[i]!=19) { continue; } sampleMode=1; @@ -279,21 +280,21 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_Y8950: // Y8950 ADPCM - if (dispatchChanOfChan[i]!=9) { + if (song.dispatchChanOfChan[i]!=9) { continue; } sampleMode=1; break; case DIV_SYSTEM_Y8950_DRUMS: // Y8950 ADPCM - if (dispatchChanOfChan[i]!=11) { + if (song.dispatchChanOfChan[i]!=11) { continue; } sampleMode=1; break; case DIV_SYSTEM_SWAN: // PCM channel - if (dispatchChanOfChan[i]!=1) { + if (song.dispatchChanOfChan[i]!=1) { continue; } noteOffDisablesSampleMode=true; @@ -301,7 +302,7 @@ bool DivEngine::convertLegacySampleMode() { break; case DIV_SYSTEM_VRC6: // pulse DAC mode - if (dispatchChanOfChan[i]>=2) { + if (song.dispatchChanOfChan[i]>=2) { continue; } hasLegacyToggle=true; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 72852444d..abd79aa6c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -498,16 +498,16 @@ int DivEngine::dispatchCmd(DivCommand c) { // map the channel to channel of chip // c.dis is a copy of c.chan because we'll use it in the next call - c.chan=dispatchChanOfChan[c.dis]; + c.chan=song.dispatchChanOfChan[c.dis]; // dispatch command to chip dispatch - return disCont[dispatchOfChan[c.dis]].dispatch->dispatch(c); + return disCont[song.dispatchOfChan[c.dis]].dispatch->dispatch(c); } // this function handles per-chip normal effects bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effectVal) { // don't process invalid chips - DivSysDef* sysDef=sysDefs[sysOfChan[ch]]; + DivSysDef* sysDef=sysDefs[song.sysOfChan[ch]]; if (sysDef==NULL) return false; // find the effect handler auto iter=sysDef->effectHandlers.find(effect); @@ -530,7 +530,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe // this handles per-chip post effects... bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) { // don't process invalid chips - DivSysDef* sysDef=sysDefs[sysOfChan[ch]]; + DivSysDef* sysDef=sysDefs[song.sysOfChan[ch]]; if (sysDef==NULL) return false; // find the effect handler auto iter=sysDef->postEffectHandlers.find(effect); @@ -552,7 +552,7 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char // ...and this handles chip pre-effects bool DivEngine::perSystemPreEffect(int ch, unsigned char effect, unsigned char effectVal) { - DivSysDef* sysDef=sysDefs[sysOfChan[ch]]; + DivSysDef* sysDef=sysDefs[song.sysOfChan[ch]]; if (sysDef==NULL) return false; auto iter=sysDef->preEffectHandlers.find(effect); if (iter==sysDef->preEffectHandlers.end()) return false; @@ -721,7 +721,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // this hack is disabled due to its dirtiness and the fact I // don't feel like being compatible with a buggy tracker any further if (effectVal==nextSpeed) { - //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; + //if (song.sysOfChan[i]!=DIV_SYSTEM_YM2610 && song.sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; } else { chan[i].delayLocked=false; } @@ -784,12 +784,12 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].stopOnOff=false; } // depending on the system, portamento may still be disabled - if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { + if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); // this here is a now-disabled hack which makes the noise channel also stop when square 3 is - /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && song.sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; }*/ @@ -812,11 +812,11 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].stopOnOff=false; } - if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { + if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); - /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && song.sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; }*/ @@ -839,7 +839,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // ...unless there's a way to trigger keyOn twice if (!chan[i].keyOn) { // the behavior of arpeggio reset upon note off varies per system - if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) { + if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsArp(song.dispatchChanOfChan[i])) { chan[i].arp=0; dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); } @@ -876,7 +876,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: legacy ALWAYS_SET_VOLUME behavior (oldAlwaysSetVolume) // - prior to its addition, volume changes wouldn't be effective depending on the system if the volume is the same as the current one // - afterwards, volume change is made regardless in order to set the bottom byte of volume ("subvolume") - if (!song.oldAlwaysSetVolume || disCont[dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->newData[whatRow][DIV_PAT_VOL]) { + if (!song.oldAlwaysSetVolume || disCont[song.dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->newData[whatRow][DIV_PAT_VOL]) { // here we let dispatchCmd() know we can do MIDI aftertouch if there isn't a note if (pat->newData[whatRow][DIV_PAT_NOTE]==-1) { chan[i].midiAftertouch=true; @@ -1039,7 +1039,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: limit slide range // - this confines pitch slides from dispatch->getPortaFloor to C-8 (I think) // - yep, the lowest portamento note depends on the system... - chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60; + chan[i].portaNote=song.limitSlides?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; chan[i].portaSpeed=effectVal; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; @@ -1684,7 +1684,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaNote=song.limitSlides?0x60:255; } else { // COMPAT FLAG: limit slide range - chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60; + chan[i].portaNote=song.limitSlides?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; } chan[i].portaSpeed=effectVal; chan[i].portaStop=true; @@ -1726,7 +1726,7 @@ void DivEngine::nextRow() { if (view==DIV_STATUS_PATTERN && !skipping) { strcpy(pb1,""); strcpy(pb3,""); - for (int i=0; iord[i][curOrder]); strcat(pb1,pb); @@ -1792,13 +1792,13 @@ void DivEngine::nextRow() { } // process row pre on all channels - for (int i=0; iord[i][curOrder],false); if (pat->newData[curRow][DIV_PAT_NOTE]!=-1) { // if there is a note @@ -1904,8 +1904,8 @@ void DivEngine::nextRow() { if (!chan[i].legato) { // check whether we should fire a pre-note event bool wantPreNote=false; - if (disCont[dispatchOfChan[i]].dispatch!=NULL) { - wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote(); + if (disCont[song.dispatchOfChan[i]].dispatch!=NULL) { + wantPreNote=disCont[song.dispatchOfChan[i]].dispatch->getWantPreNote(); if (wantPreNote) { bool doPreparePreNote=true; int addition=0; @@ -2039,7 +2039,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // this is a check that nullifies any note off event that right after a note on // it prevents a situation where some notes do not play for (int i=pendingNotes.size()-1; i>=0; i--) { - if (pendingNotes[i].channel<0 || pendingNotes[i].channel>=chans) continue; + if (pendingNotes[i].channel<0 || pendingNotes[i].channel>=song.chans) continue; if (pendingNotes[i].on) { isOn[pendingNotes[i].channel]=true; } else { @@ -2058,7 +2058,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // fetch event DivNoteEvent& note=pendingNotes.front(); // don't if channel is out of bounds or event is canceled - if (note.nop || note.channel<0 || note.channel>=chans) { + if (note.nop || note.channel<0 || note.channel>=song.chans) { pendingNotes.pop_front(); continue; } @@ -2077,10 +2077,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } // set volume as long as there's one associated with the event // and the chip has per-channel volume - if (note.volume>=0 && !disCont[dispatchOfChan[note.channel]].dispatch->isVolGlobal()) { + if (note.volume>=0 && !disCont[song.dispatchOfChan[note.channel]].dispatch->isVolGlobal()) { // map velocity to curve and then to equivalent chip volume float curvedVol=pow((float)note.volume/127.0f,midiVolExp); - int mappedVol=disCont[dispatchOfChan[note.channel]].dispatch->mapVelocity(dispatchChanOfChan[note.channel],curvedVol); + int mappedVol=disCont[song.dispatchOfChan[note.channel]].dispatch->mapVelocity(song.dispatchChanOfChan[note.channel],curvedVol); // fire command dispatchCmd(DivCommand(DIV_CMD_VOLUME,note.channel,mappedVol)); } @@ -2094,11 +2094,11 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { chan[note.channel].lastIns=note.ins; } else { // note off - DivMacroInt* macroInt=disCont[dispatchOfChan[note.channel]].dispatch->getChanMacroInt(dispatchChanOfChan[note.channel]); + DivMacroInt* macroInt=disCont[song.dispatchOfChan[note.channel]].dispatch->getChanMacroInt(song.dispatchChanOfChan[note.channel]); if (macroInt!=NULL) { // if the current instrument has a release point in any macros and // volume is per-channel, send a note release instead of a note off - if (macroInt->hasRelease && !disCont[dispatchOfChan[note.channel]].dispatch->isVolGlobal()) { + if (macroInt->hasRelease && !disCont[song.dispatchOfChan[note.channel]].dispatch->isVolGlobal()) { dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,note.channel)); } else { dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); @@ -2123,7 +2123,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // delayed row's state before it has a chance to do anything. a typical example would be // a delay scheduling a note-on to be simultaneous with the next row, and the next row also // containing a delayed note. if we don't apply the delayed row first, the world explodes. - for (int i=0; i0) { if (--chan[i].rowDelay==0) { @@ -2189,7 +2189,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } // process stuff such as effects - if (!shallStop) for (int i=0; ikeyOffAffectsPorta(dispatchChanOfChan[i])) { + if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); @@ -2564,8 +2564,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { shallStop=false; shallStopSched=false; // reset all chan oscs - for (int i=0; igetOscBuffer(dispatchChanOfChan[i]); + for (int i=0; igetOscBuffer(song.dispatchChanOfChan[i]); if (buf!=NULL) { buf->reset(); } @@ -2858,7 +2858,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi case TA_MIDI_NOTE_OFF: { if (midiIsDirect) { // in direct mode, map the event directly to the channel - if (chan<0 || chan>=chans) break; + if (chan<0 || chan>=song.chans) break; pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false,false,true)); } else { // find a suitable channel and add this event to the queue @@ -2877,7 +2877,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (msg.data[1]==0) { if (midiIsDirect) { // in direct mode, map the event directly to the channel - if (chan<0 || chan>=chans) break; + if (chan<0 || chan>=song.chans) break; pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false,false,true)); } else { // find a suitable channel and add this event to the queue @@ -2886,7 +2886,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } else { if (midiIsDirect) { // in direct mode, map the event directly to the channel - if (chan<0 || chan>=chans) break; + if (chan<0 || chan>=song.chans) break; pendingNotes.push_back(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true,false,true)); } else { // find a suitable channel and add this event to the queue diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 7bf33dd9d..dad02d708 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "song.h" +#include "engine.h" #include "../ta-log.h" #include #include @@ -691,7 +691,20 @@ void DivSong::findSubSongs() { } } +void DivSong::initDefaultSystemChans() { + for (int i=0; ichannels; + } + } +} + void DivSong::recalcChans() { + logV("DivSong: recalcChans() called"); + bool isInsTypePossible[DIV_INS_MAX]; chans=0; int chanIndex=0; @@ -707,7 +720,7 @@ void DivSong::recalcChans() { dispatchFirstChan[chanIndex]=firstChan; chanIndex++; - DivSysDef* sysDef=DivEngine::getSystemDef(system[i]); + const DivSysDef* sysDef=DivEngine::getSystemDef(system[i]); if (sysDef!=NULL) { if (sysDef->chanInsType[j][0]!=DIV_INS_NULL) { isInsTypePossible[sysDef->chanInsType[j][0]]=true; @@ -729,10 +742,9 @@ void DivSong::recalcChans() { checkAssetDir(waveDir,wave.size()); checkAssetDir(sampleDir,sample.size()); - hasLoadedSomething=true; + logV("%d channels (%d chips)",chans,systemLen); } - void DivSong::clearSongData() { for (DivSubSong* i: subsong) { i->clearData(); diff --git a/src/engine/song.h b/src/engine/song.h index 83414452a..e0bde3f10 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -429,9 +429,15 @@ struct DivSong { */ void clearSamples(); + /** + * set systemChans[] to default values. + * call recalcChans() afterwards. + */ + void initDefaultSystemChans(); + /** * recalculate channel count and internal state. - " call after editing system[] or systemChans[]. + * call after editing system[] or systemChans[]. */ void recalcChans(); @@ -538,6 +544,8 @@ struct DivSong { subsong.push_back(new DivSubSong); system[0]=DIV_SYSTEM_YM2612; system[1]=DIV_SYSTEM_SMS; + systemChans[0]=6; + systemChans[1]=4; // OPLL default instrument contest winner - piano_guitar_idk by Weeppiko nullInsOPLL.type=DIV_INS_OPLL; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 9f00cb7cf..2baaee2c6 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -51,11 +51,11 @@ int DivEngine::getChannelCount(DivSystem sys) { } int DivEngine::getTotalChannelCount() { - return chans; + return song.chans; } std::vector& DivEngine::getPossibleInsTypes() { - return possibleInsTypes; + return song.possibleInsTypes; } // for pre-dev103 modules @@ -311,41 +311,41 @@ bool DivEngine::isSTDSystem(DivSystem sys) { } const char* DivEngine::getChannelName(int chan) { - if (chan<0 || chan>chans) return "??"; + if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); - if (sysDefs[sysOfChan[chan]]==NULL) return "??"; + if (sysDefs[song.sysOfChan[chan]]==NULL) return "??"; - const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]]; + const char* ret=sysDefs[song.sysOfChan[chan]]->chanNames[song.dispatchChanOfChan[chan]]; if (ret==NULL) return "??"; return ret; } const char* DivEngine::getChannelShortName(int chan) { - if (chan<0 || chan>chans) return "??"; + if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); - if (sysDefs[sysOfChan[chan]]==NULL) return "??"; + if (sysDefs[song.sysOfChan[chan]]==NULL) return "??"; - const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]]; + const char* ret=sysDefs[song.sysOfChan[chan]]->chanShortNames[song.dispatchChanOfChan[chan]]; if (ret==NULL) return "??"; return ret; } int DivEngine::getChannelType(int chan) { - if (chan<0 || chan>chans) return DIV_CH_NOISE; - if (sysDefs[sysOfChan[chan]]==NULL) return DIV_CH_NOISE; - return sysDefs[sysOfChan[chan]]->chanTypes[dispatchChanOfChan[chan]]; + if (chan<0 || chan>song.chans) return DIV_CH_NOISE; + if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_CH_NOISE; + return sysDefs[song.sysOfChan[chan]]->chanTypes[song.dispatchChanOfChan[chan]]; } DivInstrumentType DivEngine::getPreferInsType(int chan) { - if (chan<0 || chan>chans) return DIV_INS_STD; - if (sysDefs[sysOfChan[chan]]==NULL) return DIV_INS_STD; - return sysDefs[sysOfChan[chan]]->chanInsType[dispatchChanOfChan[chan]][0]; + if (chan<0 || chan>song.chans) return DIV_INS_STD; + if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_INS_STD; + return sysDefs[song.sysOfChan[chan]]->chanInsType[song.dispatchChanOfChan[chan]][0]; } DivInstrumentType DivEngine::getPreferInsSecondType(int chan) { - if (chan<0 || chan>chans) return DIV_INS_NULL; - if (sysDefs[sysOfChan[chan]]==NULL) return DIV_INS_NULL; - return sysDefs[sysOfChan[chan]]->chanInsType[dispatchChanOfChan[chan]][1]; + if (chan<0 || chan>song.chans) return DIV_INS_NULL; + if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_INS_NULL; + return sysDefs[song.sysOfChan[chan]]->chanInsType[song.dispatchChanOfChan[chan]][1]; } int DivEngine::minVGMVersion(DivSystem which) { diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index cab97710f..6b59a7e86 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -2689,8 +2689,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p bool alreadyWroteLoop=false; int ord=-1; int exportChans=0; - for (int i=0; iwriteC(0x01); w->writeC(prevOrder); w->writeC(prevRow); - for (int i=0; iwriteC(curSubSong->orders.ord[i][prevOrder]); } } diff --git a/src/engine/wavOps.cpp b/src/engine/wavOps.cpp index 899f729f8..d65af451d 100644 --- a/src/engine/wavOps.cpp +++ b/src/engine/wavOps.cpp @@ -63,7 +63,7 @@ void DivEngine::getTotalAudioFiles(int &files) { break; } case DIV_EXPORT_MODE_MANY_CHAN: { - for (int i=0; i=chans) break; + if (i>=song.chans) break; if (getChannelType(i)!=5) break; i++; } @@ -425,7 +425,7 @@ void DivEngine::runExportThread() { logI("rendering to files..."); - for (int i=0; imuteChannel(dispatchChanOfChan[j],isMuted[j]); + for (int j=0; jmuteChannel(song.dispatchChanOfChan[j],isMuted[j]); } } @@ -549,7 +549,7 @@ void DivEngine::runExportThread() { if (getChannelType(i)==5) { i++; while (true) { - if (i>=chans) break; + if (i>=song.chans) break; if (getChannelType(i)!=5) break; i++; } @@ -564,10 +564,10 @@ void DivEngine::runExportThread() { delete[] outBuf[i]; } - for (int i=0; imuteChannel(dispatchChanOfChan[i],false); + if (disCont[song.dispatchOfChan[i]].dispatch!=NULL) { + disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],false); } } @@ -642,7 +642,7 @@ bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) { quitDispatch(); initDispatch(true); renderSamplesP(); - for (int i=0; igetSystemName(e->sysOfChan[ch]); + text+=e->getSystemName(e->song.sysOfChan[ch]); break; } case 'p': { - text+=FurnaceGUI::getSystemPartNumber(e->sysOfChan[ch],e->song.systemFlags[e->dispatchOfChan[ch]]); + text+=FurnaceGUI::getSystemPartNumber(e->song.sysOfChan[ch],e->song.systemFlags[e->song.dispatchOfChan[ch]]); break; } case 'S': { - text+=fmt::sprintf("%d",e->dispatchOfChan[ch]); + text+=fmt::sprintf("%d",e->song.dispatchOfChan[ch]); break; } case 'v': { diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 091a4103a..34f7caab3 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -78,7 +78,7 @@ void FurnaceGUI::drawChannels() { ImGui::Button(ICON_FA_ARROWS "##ChanDrag"); ImGui::EndDragDropSource(); } else if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(_("%s #%d\n(drag to swap channels)\n(Shift-drag to copy channel contents)"),e->getSystemName(e->sysOfChan[i]),e->dispatchChanOfChan[i]); + ImGui::SetTooltip(_("%s #%d\n(drag to swap channels)\n(Shift-drag to copy channel contents)"),e->getSystemName(e->song.sysOfChan[i]),e->song.dispatchChanOfChan[i]); } if (ImGui::BeginDragDropTarget()) { const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_CHAN"); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 8e152ebec..96d0a2dce 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -118,11 +118,11 @@ void FurnaceGUI::drawDebug() { ImGui::Columns(e->getTotalChannelCount()); for (int i=0; igetTotalChannelCount(); i++) { void* ch=e->getDispatchChanState(i); - ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->dispatchOfChan[i],e->dispatchChanOfChan[i]); + ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->song.dispatchOfChan[i],e->song.dispatchChanOfChan[i]); if (ch==NULL) { ImGui::Text("NULL"); } else { - putDispatchChan(ch,e->dispatchChanOfChan[i],e->sysOfChan[i]); + putDispatchChan(ch,e->song.dispatchChanOfChan[i],e->song.sysOfChan[i]); } ImGui::NextColumn(); } diff --git a/src/gui/effectList.cpp b/src/gui/effectList.cpp index e4339e301..284b8cb1a 100644 --- a/src/gui/effectList.cpp +++ b/src/gui/effectList.cpp @@ -15,7 +15,7 @@ void FurnaceGUI::drawEffectList() { float availB=ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing(); if (availB>0) { ImGui::PushTextWrapPos(availB); - ImGui::TextWrapped(_("Chip at cursor: %s"),e->getSystemName(e->sysOfChan[cursor.xCoarse])); + ImGui::TextWrapped(_("Chip at cursor: %s"),e->getSystemName(e->song.sysOfChan[cursor.xCoarse])); ImGui::PopTextWrapPos(); } effectSearch.Draw(_("Search")); @@ -55,7 +55,7 @@ void FurnaceGUI::drawEffectList() { } prevName=name; if (fxColors[i]==GUI_COLOR_PATTERN_EFFECT_PANNING) { - DivDispatch* dispatch=e->getDispatch(e->dispatchOfChan[cursor.xCoarse]); + DivDispatch* dispatch=e->getDispatch(e->song.dispatchOfChan[cursor.xCoarse]); if (dispatch!=NULL) { int outputs=dispatch->getOutputCount(); if (outputs<2) { diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 46f799c27..e905d8762 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -1458,7 +1458,7 @@ void FurnaceGUI::drawPattern() { for (int j=0; j<8; j++) { if (pair.pairs[j]==-1) continue; - int pairCh=e->dispatchFirstChan[i]+pair.pairs[j]; + int pairCh=e->song.dispatchFirstChan[i]+pair.pairs[j]; if (!e->curSubSong->chanShow[pairCh]) { continue; } @@ -1514,7 +1514,7 @@ void FurnaceGUI::drawPattern() { for (int j=0; j<8; j++) { if (pair.pairs[j]==-1) continue; - int pairCh=e->dispatchFirstChan[i]+pair.pairs[j]; + int pairCh=e->song.dispatchFirstChan[i]+pair.pairs[j]; if (!e->curSubSong->chanShow[pairCh]) { continue; } diff --git a/src/log.cpp b/src/log.cpp index 215b4ddfc..0504c8f00 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -32,8 +32,8 @@ int logLevel=LOGLEVEL_TRACE; int logLevel=LOGLEVEL_TRACE; // until done #endif -FILE* logOut; -FILE* logFile; +FILE* logOut=NULL; +FILE* logFile=NULL; char* logFileBuf; char* logFileWriteBuf; unsigned int logFilePosI; @@ -140,6 +140,7 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { } if (logLevel Date: Wed, 12 Nov 2025 05:14:31 -0500 Subject: [PATCH 09/46] fix system alteration functions --- src/engine/engine.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1de23a682..5d70ea816 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1036,6 +1036,7 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { } song.system[index]=which; + song.systemChans[index]=getChannelCount(which); song.systemFlags[index].clear(); song.recalcChans(); saveLock.unlock(); @@ -1062,6 +1063,7 @@ bool DivEngine::addSystem(DivSystem which) { BUSY_BEGIN; saveLock.lock(); song.system[song.systemLen]=which; + song.systemChans[song.systemLen]=getChannelCount(which); song.systemVol[song.systemLen]=1.0; song.systemPan[song.systemLen]=0; song.systemPanFR[song.systemLen]=0; @@ -1116,6 +1118,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { BUSY_BEGIN; saveLock.lock(); song.system[song.systemLen]=song.system[index]; + song.systemChans[song.systemLen]=song.systemChans[index]; song.systemVol[song.systemLen]=song.systemVol[index]; song.systemPan[song.systemLen]=song.systemPan[index]; song.systemPanFR[song.systemLen]=song.systemPanFR[index]; @@ -1241,6 +1244,7 @@ bool DivEngine::removeSystem(int index, bool preserveOrder) { song.systemLen--; for (int i=index; i Date: Wed, 12 Nov 2025 06:05:36 -0500 Subject: [PATCH 10/46] giga-refactor, part 3 make use of systemChans --- src/engine/engine.cpp | 29 +++++++++++++++-------------- src/engine/fileOps/fur.cpp | 5 ++++- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 5d70ea816..a27fe8a69 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -999,7 +999,8 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { lastError=_("invalid index"); return false; } - if (song.chans-getChannelCount(song.system[index])+getChannelCount(which)>DIV_MAX_CHANS) { + unsigned short newChanCount=getChannelCount(which); + if (song.chans-song.systemChans[index]+newChanCount>DIV_MAX_CHANS) { lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); return false; } @@ -1011,9 +1012,9 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { if (!preserveOrder) { int firstChan=0; - int chanMovement=getChannelCount(which)-getChannelCount(song.system[index]); + int chanMovement=newChanCount-song.systemChans[index]; while (song.dispatchOfChan[firstChan]!=index) firstChan++; - int lastChan=firstChan+getChannelCount(song.system[index]); + int lastChan=firstChan+song.systemChans[index]; if (chanMovement!=0) { if (chanMovement>0) { // add channels @@ -1036,7 +1037,7 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { } song.system[index]=which; - song.systemChans[index]=getChannelCount(which); + song.systemChans[index]=newChanCount; song.systemFlags[index].clear(); song.recalcChans(); saveLock.unlock(); @@ -1110,7 +1111,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { lastError=fmt::sprintf(_("max number of systems is %d"),DIV_MAX_CHIPS); return false; } - if (song.chans+getChannelCount(song.system[index])>DIV_MAX_CHANS) { + if (song.chans+song.systemChans[index]>DIV_MAX_CHANS) { lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); return false; } @@ -1155,13 +1156,13 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { int srcChan=0; int destChan=0; for (int i=0; ipat[destChan+j].effectCols=i->pat[srcChan+j].effectCols; i->chanShow[destChan+j]=i->chanShow[srcChan+j]; i->chanShowChanOsc[destChan+j]=i->chanShowChanOsc[srcChan+j]; @@ -1224,11 +1225,11 @@ bool DivEngine::removeSystem(int index, bool preserveOrder) { if (!preserveOrder) { int firstChan=0; while (song.dispatchOfChan[firstChan]!=index) firstChan++; - for (int i=0; i0) swapList.reserve(song.systemLen); for (int i=0; i0) chanList.reserve(channelCount); for (int j=0; j Date: Wed, 12 Nov 2025 19:03:48 +0400 Subject: [PATCH 11/46] per-channel colors cherry-picked from ffcfc51a086de91ed67a5b144fec82b010b5acb8, but without the tri-state checkbox sorry for being impatient again, im bored and have nothing to do --- src/engine/song.cpp | 1 + src/engine/song.h | 2 ++ src/gui/channels.cpp | 25 ++++++++++++++++++++++++- src/gui/gui.cpp | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 798c5f1c4..ff6e0e10e 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -642,6 +642,7 @@ void DivSong::findSubSongs(int chans) { memcpy(theCopy->chanShow,i->chanShow,DIV_MAX_CHANS*sizeof(bool)); memcpy(theCopy->chanShowChanOsc,i->chanShowChanOsc,DIV_MAX_CHANS*sizeof(bool)); memcpy(theCopy->chanCollapse,i->chanCollapse,DIV_MAX_CHANS); + memcpy(theCopy->chanColor,i->chanColor,DIV_MAX_CHANS*sizeof(unsigned int)); for (int k=0; kchanName[k]=i->chanName[k]; diff --git a/src/engine/song.h b/src/engine/song.h index 4b6e1b7fc..38da9b7d1 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -219,6 +219,7 @@ struct DivSubSong { unsigned char chanCollapse[DIV_MAX_CHANS]; String chanName[DIV_MAX_CHANS]; String chanShortName[DIV_MAX_CHANS]; + unsigned int chanColor[DIV_MAX_CHANS]; // song timestamps DivSongTimestamps ts; @@ -249,6 +250,7 @@ struct DivSubSong { chanShow[i]=true; chanShowChanOsc[i]=true; chanCollapse[i]=0; + chanColor[i]=0; } } }; diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 091a4103a..a1cc0eb4a 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -38,12 +38,13 @@ void FurnaceGUI::drawChannels() { //ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH)); } if (ImGui::Begin("Channels",&channelsOpen,globalWinFlags,_("Channels"))) { - if (ImGui::BeginTable("ChannelList",5)) { + if (ImGui::BeginTable("ChannelList",6)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed,0.0f); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::Text(_("Pat")); @@ -52,6 +53,9 @@ void FurnaceGUI::drawChannels() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); ImGui::Text(_("Name")); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::Text(_("Color")); for (int i=0; igetTotalChannelCount(); i++) { ImGui::PushID(i); ImGui::TableNextRow(); @@ -106,6 +110,25 @@ void FurnaceGUI::drawChannels() { if (ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->curSubSong->chanShortName[i])) { MARK_MODIFIED; } + ImGui::TableNextColumn(); + ImVec4 curColor=e->curSubSong->chanColor[i]?ImGui::ColorConvertU32ToFloat4(e->curSubSong->chanColor[i]):uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(i)]; + ImGui::ColorButton("##ChanColor",curColor); + if (ImGui::BeginPopupContextItem("##ChanColorEditPopup", ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::ColorPicker4("##ChanColorEdit", (float*)&curColor); + e->curSubSong->chanColor[i]=ImGui::ColorConvertFloat4ToU32(curColor); + MARK_MODIFIED; + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::BeginDisabled(e->curSubSong->chanColor[i]==0); + if (ImGui::Button(ICON_FA_REFRESH "##ChanColorReset")) { + e->curSubSong->chanColor[i]=0; + MARK_MODIFIED; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(_("Reset color")); + } + ImGui::EndDisabled(); ImGui::PopID(); } ImGui::EndTable(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e80930911..16376718a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -942,7 +942,7 @@ ImVec4 FurnaceGUI::channelColor(int ch) { return uiColors[GUI_COLOR_CHANNEL_BG]; break; case 1: - return uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(ch)]; + return e->curSubSong->chanColor[ch]?ImGui::ColorConvertU32ToFloat4(e->curSubSong->chanColor[ch]):uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(ch)]; break; case 2: return uiColors[GUI_COLOR_INSTR_STD+e->getPreferInsType(ch)]; From c681ac507e1f7a38c968a0e854bc045bedaacdc6 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Wed, 12 Nov 2025 20:33:41 +0400 Subject: [PATCH 12/46] chan color on chanosc channels todo: gradient. storing a gradient per channel would be absurd. my idea is when the bg is set to chan color, make the gradient bg transparent, then mix the grad with the color in get() --- src/gui/chanOsc.cpp | 5 ++++- src/gui/gui.cpp | 3 +++ src/gui/gui.h | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 30c854520..429cef6c2 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -360,7 +360,10 @@ void FurnaceGUI::drawChanOsc() { } } else { ImGui::SetNextItemWidth(400.0f*dpiScale); + ImGui::BeginDisabled(chanOscUseChanColor); ImGui::ColorPicker4(_("Color"),(float*)&chanOscColor); + ImGui::EndDisabled(); + ImGui::Checkbox(_("Set to channel color"), &chanOscUseChanColor); } ImGui::AlignTextToFramePadding(); @@ -793,7 +796,7 @@ void FurnaceGUI::drawChanOsc() { } } } - ImU32 color=ImGui::GetColorU32(chanOscColor); + ImU32 color=ImGui::GetColorU32(chanOscUseChanColor?channelColor(oscChans[i]):chanOscColor); if (chanOscUseGrad) { float xVal=computeGradPos(chanOscColorX,ch); float yVal=computeGradPos(chanOscColorY,ch); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 16376718a..db0c78d0e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -8402,6 +8402,7 @@ void FurnaceGUI::syncState() { chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f); chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f); chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); + chanOscUseChanColor=e->getConfBool("chanOscUseChanColor", false); chanOscGrad.fromString(e->getConfString("chanOscGrad","")); chanOscGrad.render(); @@ -8576,6 +8577,7 @@ void FurnaceGUI::commitState(DivConfig& conf) { conf.set("chanOscTextColorA",chanOscTextColor.w); conf.set("chanOscUseGrad",chanOscUseGrad); conf.set("chanOscGrad",chanOscGrad.toString()); + conf.set("chanOscUseChanColor",chanOscUseChanColor); // commit x-y osc state conf.set("xyOscXChannel",xyOscXChannel); @@ -9200,6 +9202,7 @@ FurnaceGUI::FurnaceGUI(): chanOscUseGrad(false), chanOscNormalize(false), chanOscRandomPhase(false), + chanOscUseChanColor(false), chanOscTextFormat("%c"), chanOscColor(1.0f,1.0f,1.0f,1.0f), chanOscTextColor(1.0f,1.0f,1.0f,0.75f), diff --git a/src/gui/gui.h b/src/gui/gui.h index ec2cb5766..78049de8d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2698,7 +2698,7 @@ class FurnaceGUI { // per-channel oscilloscope int chanOscCols, chanOscAutoColsType, chanOscColorX, chanOscColorY, chanOscCenterStrat; float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify, chanOscLineSize; - bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize, chanOscRandomPhase; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize, chanOscRandomPhase, chanOscUseChanColor; String chanOscTextFormat; ImVec4 chanOscColor, chanOscTextColor; Gradient2D chanOscGrad; From b5fc6ff39dcdb94d303aba0dc3f2babfd78223de Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Thu, 13 Nov 2025 00:32:29 +0400 Subject: [PATCH 13/46] fix oscref_channel not taking into account hidden channels i love when children annoy me then i get ignored for the rest of the day --- src/gui/chanOsc.cpp | 8 ++++---- src/gui/gui.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 429cef6c2..0c696c314 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -56,7 +56,7 @@ static void _drawOsc(const ImDrawList* drawList, const ImDrawCmd* cmd) { } } -float FurnaceGUI::computeGradPos(int type, int chan) { +float FurnaceGUI::computeGradPos(int type, int chan, int totalChans) { switch (type) { case GUI_OSCREF_NONE: return 0.0f; @@ -74,7 +74,7 @@ float FurnaceGUI::computeGradPos(int type, int chan) { return chanOscVol[chan]; break; case GUI_OSCREF_CHANNEL: - return (float)chan/(float)(e->getTotalChannelCount()-1); + return (float)chan/(float)(totalChans-1); break; case GUI_OSCREF_BRIGHT: return chanOscBright[chan]; @@ -798,8 +798,8 @@ void FurnaceGUI::drawChanOsc() { } ImU32 color=ImGui::GetColorU32(chanOscUseChanColor?channelColor(oscChans[i]):chanOscColor); if (chanOscUseGrad) { - float xVal=computeGradPos(chanOscColorX,ch); - float yVal=computeGradPos(chanOscColorY,ch); + float xVal=computeGradPos(chanOscColorX,ch,oscBufs.size()); + float yVal=computeGradPos(chanOscColorY,ch,oscBufs.size()); xVal=CLAMP(xVal,0.0f,1.0f); yVal=CLAMP(yVal,0.0f,1.0f); diff --git a/src/gui/gui.h b/src/gui/gui.h index 78049de8d..03060ba6e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -3102,7 +3102,7 @@ class FurnaceGUI { bool importConfig(String path); bool exportConfig(String path); - float computeGradPos(int type, int chan); + float computeGradPos(int type, int chan, int totalChans); void resetColors(); void resetKeybinds(); From 68768004b0f91589fef4fd099da51d7510ff43de Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 12 Nov 2025 16:41:51 -0500 Subject: [PATCH 14/46] giga-refactor, part 4 add minimum and maximum channel count properties to DivSysDef --- src/engine/engine.h | 6 +- src/engine/sysDef.cpp | 341 ++++++++++++++++++++++++++++-------------- 2 files changed, 231 insertions(+), 116 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 6b5611eb0..7d9a2378e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -362,7 +362,7 @@ struct DivSysDef { const char* description; unsigned char id; unsigned char id_DMF; - int channels; + int channels, minChans, maxChans; bool isFM, isSTD, isCompound; // width 0: variable // height 0: no wavetable support @@ -379,7 +379,7 @@ struct DivSysDef { const EffectHandlerMap postEffectHandlers; const EffectHandlerMap preEffectHandlers; DivSysDef( - const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, + const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, int minCh, int maxCh, bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, unsigned int formatMask, unsigned short waveWid, unsigned short waveHei, const char* desc, std::initializer_list chNames, @@ -396,6 +396,8 @@ struct DivSysDef { id(fileID), id_DMF(fileID_DMF), channels(chans), + minChans(minCh), + maxChans(maxCh), isFM(isFMChip), isSTD(isSTDChip), isCompound(compound), diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 2baaee2c6..24fd6246b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -367,7 +367,8 @@ int DivEngine::minVGMVersion(DivSystem which) { // define systems like: // sysDefs[DIV_SYSTEM_ID]=new DivSysDef( -// "Name", "Name (japanese, optional)", fileID, fileID_DMF, channels, isFM, isSTD, vgmVersion, waveWidth, waveHeight, +// "Name", "Name (japanese, optional)", fileID, fileID_DMF, channels, minChans, maxChans, +// isFM, isSTD, vgmVersion, waveWidth, waveHeight, // "Description", // {"Channel Names", ...}, // {"Channel Short Names", ...}, @@ -814,7 +815,8 @@ void DivEngine::registerSystems() { // this chip uses YMZ ADPCM, but the emulator uses ADPCM-B because I got it wrong back then. sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef( - _("Yamaha YMU759 (MA-2)"), NULL, 0x01, 0x01, 17, true, false, 0, false, (1U<", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef( - _("Sega Genesis Extended Channel 3"), NULL, 0x42, 0x42, 13, true, true, 0, true, 0, 0, 0, + _("Sega Genesis Extended Channel 3"), NULL, 0x42, 0x42, 13, 13, 13, + true, true, 0, true, 0, 0, 0, "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_SMS]=new DivSysDef( - _("TI SN76489"), NULL, 0x03, 0x03, 4, false, true, 0x150, false, 0, 0, 0, + _("TI SN76489"), NULL, 0x03, 0x03, 4, 4, 4, + false, true, 0x150, false, 0, 0, 0, _("a square/noise sound chip found on the Sega Master System, ColecoVision, Tandy, TI's own 99/4A and a few other places."), {_("Square 1"), _("Square 2"), _("Square 3"), _("Noise")}, {"S1", "S2", "S3", "NO"}, @@ -848,13 +853,15 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SMS_OPLL]=new DivSysDef( - _("Sega Master System + FM Expansion"), NULL, 0x43, 0x43, 13, true, true, 0, true, 0, 0, 0, + _("Sega Master System + FM Expansion"), NULL, 0x43, 0x43, 13, 13, 13, + true, true, 0, true, 0, 0, 0, "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_GB]=new DivSysDef( - _("Game Boy"), NULL, 0x04, 0x04, 4, false, true, 0x161, false, 0, 32, 16, + _("Game Boy"), NULL, 0x04, 0x04, 4, 4, 4, + false, true, 0x161, false, 0, 32, 16, _("the most popular portable game console of the era."), {_("Pulse 1"), _("Pulse 2"), _("Wavetable"), _("Noise")}, {"S1", "S2", "WA", "NO"}, @@ -871,7 +878,8 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_PCE]=new DivSysDef( - _("PC Engine/TurboGrafx-16"), NULL, 0x05, 0x05, 6, false, true, 0x161, false, 1U<", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef( - _("Famicom Disk System"), NULL, 0, 0x86, 6, false, true, 0, true, 0, 0, 0, + _("Famicom Disk System"), NULL, 0, 0x86, 6, 6, 6, + false, true, 0, true, 0, 0, 0, "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef( - _("Commodore 64 (SID 6581)"), NULL, 0x47, 0x47, 3, false, true, 0, false, 0, 0, 0, + _("Commodore 64 (SID 6581)"), NULL, 0x47, 0x47, 3, 3, 3, + false, true, 0, false, 0, 0, 0, _("this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR."), {_("Channel 1"), _("Channel 2"), _("Channel 3")}, {"CH1", "CH2", "CH3"}, @@ -933,7 +945,8 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef( - _("Commodore 64 (SID 8580)"), NULL, 0x07, 0x07, 3, false, true, 0, false, 0, 0, 0, + _("Commodore 64 (SID 8580)"), NULL, 0x07, 0x07, 3, 3, 3, + false, true, 0, false, 0, 0, 0, _("this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.\nthis is the newer revision of the chip."), {_("Channel 1"), _("Channel 2"), _("Channel 3")}, {"CH1", "CH2", "CH3"}, @@ -945,13 +958,15 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef( - _("DefleCade"), NULL, 0x08, 0x08, 13, true, false, 0, true, 0, 0, 0, + _("DefleCade"), NULL, 0x08, 0x08, 13, 13, 13, + true, false, 0, true, 0, 0, 0, "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef( - _("Neo Geo CD"), NULL, 0x09, 0x09, 13, true, true, 0x151, false, (1U<", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef( - _("AY-3-8910"), NULL, 0x80, 0, 3, false, true, 0x151, false, 1U< Date: Thu, 13 Nov 2025 01:39:21 -0500 Subject: [PATCH 15/46] giga-refactor, part 5 --- src/engine/fileOps/ftm.cpp | 27 +++++++++++++++++---------- src/engine/song.h | 12 +++++++++++- src/gui/gui.h | 2 +- src/gui/sysManager.cpp | 4 ++-- src/gui/sysMiscInfo.cpp | 12 ++++++++---- 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index b2df53798..daf27cb4e 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -654,6 +654,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si int curr_chan = 0; int map_ch = 0; + ds.systemChans[systemID]=5; ds.system[systemID++] = DIV_SYSTEM_NES; ds.systemFlags[0].set("resetSweep",true); // FamiTracker behavior @@ -668,6 +669,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } if (expansions & 1) { + ds.systemChans[systemID]=3; ds.system[systemID++] = DIV_SYSTEM_VRC6; for (int ch = 0; ch < 3; ch++) { @@ -685,6 +687,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si vrc6_saw_chan = map_ch - 1; } if (expansions & 8) { + ds.systemChans[systemID]=3; ds.system[systemID++] = DIV_SYSTEM_MMC5; for (int ch = 0; ch < (eft ? 3 : 2); ch++) { @@ -707,6 +710,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si if (expansions & 16) { ds.system[systemID] = DIV_SYSTEM_N163; ds.systemFlags[systemID].set("channels", (int)n163Chans - 1); + ds.systemChans[systemID]=CLAMP(n163Chans,1,8); systemID++; for (int ch = 0; ch < (int)n163Chans; ch++) { @@ -716,12 +720,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si map_ch++; } - for (int ch = 0; ch < (8 - (int)n163Chans); ch++) { + /*for (int ch = 0; ch < (8 - (int)n163Chans); ch++) { map_channels[curr_chan] = map_ch; // do not populate and skip the missing N163 channels! map_ch++; - } + }*/ } if (expansions & 4) { + ds.systemChans[systemID]=1; ds.system[systemID++] = DIV_SYSTEM_FDS; map_channels[curr_chan] = map_ch; @@ -730,6 +735,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si map_ch++; } if (expansions & 2) { + ds.systemChans[systemID]=6; ds.system[systemID++] = DIV_SYSTEM_VRC7; for (int ch = 0; ch < 6; ch++) { @@ -741,6 +747,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } if (expansions & 32) { ds.system[systemID] = DIV_SYSTEM_AY8910; + ds.systemChans[systemID]=3; ds.systemFlags[systemID++].set("chipType", 2); // Sunsoft 5B for (int ch = 0; ch < 3; ch++) { @@ -751,6 +758,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } if (expansions & 64) { + ds.systemChans[systemID]=3; ds.system[systemID++] = DIV_SYSTEM_AY8930; for (int ch = 0; ch < 3; ch++) { @@ -761,6 +769,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } if (expansions & 128) { + ds.systemChans[systemID]=6; ds.system[systemID++] = DIV_SYSTEM_SAA1099; for (int ch = 0; ch < 6; ch++) { @@ -770,6 +779,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } if (expansions & 256) { + ds.systemChans[systemID]=5; ds.system[systemID++] = DIV_SYSTEM_5E01; for (int ch = 0; ch < 5; ch++) { @@ -779,6 +789,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } if (expansions & 512) { + ds.systemChans[systemID]=3; ds.system[systemID++] = DIV_SYSTEM_C64_6581; for (int ch = 0; ch < 3; ch++) { @@ -788,6 +799,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } if (expansions & 1024) { + ds.systemChans[systemID]=3; ds.system[systemID++] = DIV_SYSTEM_C64_8580; for (int ch = 0; ch < 3; ch++) { @@ -797,6 +809,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } if (expansions & 2048) { + ds.systemChans[systemID]=4; ds.system[systemID++] = DIV_SYSTEM_POKEY; for (int ch = 0; ch < 4; ch++) { @@ -817,13 +830,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si calcChans--; // no PCM channel for MMC5 in famitracker } - calcChans += getChannelCount(ds.system[i]); - total_chans += getChannelCount(ds.system[i]); - - if (ds.system[i] == DIV_SYSTEM_N163) { - calcChans -= getChannelCount(ds.system[i]); - calcChans += (int)n163Chans; - } + calcChans += ds.systemChans[i]; + total_chans += ds.systemChans[i]; } if (calcChans != tchans) { // TODO: would ignore trigger CVE? too bad if so! @@ -2802,7 +2810,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } } - ds.initDefaultSystemChans(); ds.recalcChans(); if (active) quitDispatch(); diff --git a/src/engine/song.h b/src/engine/song.h index e0bde3f10..d34f7bc9d 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -392,14 +392,24 @@ struct DivSong { std::vector effects; - // INTERNAL STATE - do not modify. + /** + * INTERNAL STATE - do not modify. + */ + // default/"null" instruments (when instrument is none/-1) DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound, nullInsESFM; + // default assets, returned by getWave()/getSample() in DivEngine DivWavetable nullWave; DivSample nullSample; + // channel information arrays. + // chip of a channel DivSystem sysOfChan[DIV_MAX_CHANS]; + // dispatch (chip index) of a channel int dispatchOfChan[DIV_MAX_CHANS]; + // tracker channel to chip channel mapping + // -1 means "nowhere". int dispatchChanOfChan[DIV_MAX_CHANS]; + // the first channel of a chip, indexed per channel int dispatchFirstChan[DIV_MAX_CHANS]; std::vector possibleInsTypes; diff --git a/src/gui/gui.h b/src/gui/gui.h index ec2cb5766..4a2d96e9a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -3083,7 +3083,7 @@ class FurnaceGUI { void drawRefPlayer(); void drawMultiInsSetup(); - float drawSystemChannelInfo(const DivSysDef* whichDef, int keyHitOffset=-1, float width=-1.0f); + float drawSystemChannelInfo(const DivSysDef* whichDef, int keyHitOffset=-1, float width=-1.0f, int chanCount=-1); void drawSystemChannelInfoText(const DivSysDef* whichDef); void drawVolMeterInternal(ImDrawList* dl, ImRect rect, float* data, int chans, bool aspectRatio); diff --git a/src/gui/sysManager.cpp b/src/gui/sysManager.cpp index 4ebb1f888..28d7b0c7a 100644 --- a/src/gui/sysManager.cpp +++ b/src/gui/sysManager.cpp @@ -145,7 +145,7 @@ void FurnaceGUI::drawSysManager() { // channel LEDs and chip config button float height=0; if (settings.rackShowLEDs) { - height=drawSystemChannelInfo(sysDef,dispatchOff,ImGui::GetContentRegionAvail().x-(ImGui::CalcTextSize(ICON_FA_CHEVRON_DOWN).x+ImGui::GetStyle().ItemSpacing.x)); + height=drawSystemChannelInfo(sysDef,dispatchOff,ImGui::GetContentRegionAvail().x-(ImGui::CalcTextSize(ICON_FA_CHEVRON_DOWN).x+ImGui::GetStyle().ItemSpacing.x),e->song.systemChans[i]); } ImGuiID openedID=ImGui::GetID("OpenSysConfig"); @@ -168,7 +168,7 @@ void FurnaceGUI::drawSysManager() { ImGui::EndChild(); ImGui::PopID(); - dispatchOff+=sysDef->channels; + dispatchOff+=e->song.systemChans[i]; } if (e->song.systemLenchannels; + ImDrawList* dl=ImGui::GetWindowDrawList(); const ImVec2 p=ImGui::GetCursorScreenPos(); if (tooltipWidth<=0.0f) tooltipWidth=ImGui::GetContentRegionAvail().x; ImVec2 sep=ImGui::GetStyle().ItemSpacing; sep.x*=0.5f; ImVec2 ledSize=ImVec2( - (tooltipWidth-sep.x*(whichDef->channels-1))/(float)whichDef->channels, + (tooltipWidth-sep.x*(chanCount-1))/(float)chanCount, settings.iconSize*dpiScale ); if (ledSize.x<8.0f*dpiScale) ledSize.x=8.0f*dpiScale; float x=p.x, y=p.y; - for (int i=0; ichannels; i++) { + for (int i=0; itooltipWidth+p.x) { x=p.x; y+=ledSize.y+sep.y; } - ImVec4 color=uiColors[whichDef->chanTypes[i]+GUI_COLOR_CHANNEL_FM]; + ImVec4 color=uiColors[GUI_COLOR_CHANNEL_BG]; + if (ichannels) color=uiColors[whichDef->chanTypes[i]+GUI_COLOR_CHANNEL_FM]; if (keyHitOffset>=0) { if (e->isChannelMuted(keyHitOffset+i)) { color=uiColors[GUI_COLOR_CHANNEL_MUTED]; From ae4476bd7f5d2b00b365882082137d67938469a1 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Thu, 13 Nov 2025 13:48:36 +0400 Subject: [PATCH 16/46] auto column types begone what was i thinking??? --- src/gui/chanOsc.cpp | 41 +++++++++-------------------------------- src/gui/gui.cpp | 6 +++--- src/gui/gui.h | 5 +++-- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 0c696c314..49aaceecc 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -77,7 +77,7 @@ float FurnaceGUI::computeGradPos(int type, int chan, int totalChans) { return (float)chan/(float)(totalChans-1); break; case GUI_OSCREF_BRIGHT: - return chanOscBright[chan]; + return chanOscBright[chan]; // this array is set to only 0 (???) break; case GUI_OSCREF_NOTE_TRIGGER: return keyHit1[chan]; @@ -143,10 +143,12 @@ void FurnaceGUI::drawChanOsc() { ImGui::Text(_("Columns")); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::BeginDisabled(chanOscAutoCols); if (ImGui::InputInt("##COSColumns",&chanOscCols,1,3)) { if (chanOscCols<1) chanOscCols=1; if (chanOscCols>64) chanOscCols=64; } + ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::Text(_("Size (ms)")); @@ -160,18 +162,7 @@ void FurnaceGUI::drawChanOsc() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); - ImGui::Text(_("Automatic columns")); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - const char* previewColType=autoColsTypes[chanOscAutoColsType&3]; - if (ImGui::BeginCombo("##AutoCols",previewColType)) { - for (int j=0; j<4; j++) { - const bool isSelected=(chanOscAutoColsType==j); - if (ImGui::Selectable(autoColsTypes[j],isSelected)) chanOscAutoColsType=j; - if (isSelected) ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } + ImGui::Checkbox(_("Automatic columns"),&chanOscAutoCols); ImGui::TableNextColumn(); if (ImGui::Checkbox(_("Center waveform"),&chanOscWaveCorr)) { @@ -423,7 +414,7 @@ void FurnaceGUI::drawChanOsc() { for (int i=0; igetOscBuffer(i); if (buf!=NULL && e->curSubSong->chanShowChanOsc[i]) { - oscBufs.push_back(buf); + oscBufs.push_back(buf); // isnt this odd how there are 3 vectors of the same size? oscFFTs.push_back(&chanOscChan[i]); oscChans.push_back(i); } @@ -593,25 +584,11 @@ void FurnaceGUI::drawChanOsc() { } } chanOscWorkPool->wait(); - - // 0: none - // 1: sqrt(chans) - // 2: sqrt(chans+1) - // 3: sqrt(chans)+1 - switch (chanOscAutoColsType) { - case 1: - chanOscCols=sqrt(oscChans.size()); - break; - case 2: - chanOscCols=sqrt(oscChans.size()+1); - break; - case 3: - chanOscCols=sqrt(oscChans.size())+1; - break; - } - if (chanOscCols<1) chanOscCols=1; - if (chanOscCols>64) chanOscCols=64; + if (chanOscAutoCols) { + chanOscCols=sqrt(oscBufs.size()); + if (chanOscCols>64) chanOscCols=64; + } int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; // render diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index db0c78d0e..2284d0ea1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -8379,7 +8379,7 @@ void FurnaceGUI::syncState() { pianoLabelsMode=e->getConfInt("pianoLabelsMode",pianoLabelsMode); chanOscCols=e->getConfInt("chanOscCols",3); - chanOscAutoColsType=e->getConfInt("chanOscAutoColsType",0); + chanOscAutoCols=e->getConfBool("chanOscAutoColsType",0); chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); chanOscCenterStrat=e->getConfInt("chanOscCenterStrat",1); @@ -8553,7 +8553,7 @@ void FurnaceGUI::commitState(DivConfig& conf) { // commit per-chan osc state conf.set("chanOscCols",chanOscCols); - conf.set("chanOscAutoColsType",chanOscAutoColsType); + conf.set("chanOscAutoColsType",chanOscAutoCols); conf.set("chanOscColorX",chanOscColorX); conf.set("chanOscColorY",chanOscColorY); conf.set("chanOscCenterStrat",chanOscCenterStrat); @@ -9187,7 +9187,6 @@ FurnaceGUI::FurnaceGUI(): oscInput1(0.0f), oscZoomSlider(false), chanOscCols(3), - chanOscAutoColsType(0), chanOscColorX(GUI_OSCREF_CENTER), chanOscColorY(GUI_OSCREF_CENTER), chanOscCenterStrat(1), @@ -9203,6 +9202,7 @@ FurnaceGUI::FurnaceGUI(): chanOscNormalize(false), chanOscRandomPhase(false), chanOscUseChanColor(false), + chanOscAutoCols(false), chanOscTextFormat("%c"), chanOscColor(1.0f,1.0f,1.0f,1.0f), chanOscTextColor(1.0f,1.0f,1.0f,0.75f), diff --git a/src/gui/gui.h b/src/gui/gui.h index 03060ba6e..024d40c33 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2696,9 +2696,10 @@ class FurnaceGUI { bool oscZoomSlider; // per-channel oscilloscope - int chanOscCols, chanOscAutoColsType, chanOscColorX, chanOscColorY, chanOscCenterStrat; + int chanOscCols, chanOscColorX, chanOscColorY, chanOscCenterStrat; float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify, chanOscLineSize; - bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize, chanOscRandomPhase, chanOscUseChanColor; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; + bool chanOscNormalize, chanOscRandomPhase, chanOscUseChanColor, chanOscAutoCols; String chanOscTextFormat; ImVec4 chanOscColor, chanOscTextColor; Gradient2D chanOscGrad; From 44ec4f70da65c6e1a3a28633f80a1dd1cc7aa249 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Thu, 13 Nov 2025 18:21:25 +0400 Subject: [PATCH 17/46] channel color for gradient part 1 very not work --- src/gui/chanOsc.cpp | 62 ++++++++++++++++++++++++++++++++++++++------ src/gui/gradient.cpp | 17 ++++++++---- src/gui/gui.cpp | 6 ++--- src/gui/gui.h | 4 +-- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 49aaceecc..8e421d23b 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -341,9 +341,30 @@ void FurnaceGUI::drawChanOsc() { } ImGui::TableNextColumn(); - if (ImGui::ColorEdit4(_("Background"),(float*)&chanOscGrad.bgColor)) { + ImGui::Text(_("Background:")); + ImGui::Indent(); + if (ImGui::RadioButton(_("Solid color"),chanOscColorMode==0)) { + chanOscColorMode=0; updateChanOscGradTex=true; } + if (chanOscColorMode==0) { + ImGui::Indent(); + if (ImGui::ColorEdit4(_("Color"),(float*)&chanOscGrad.bgColor)) { + updateChanOscGradTex=true; + } + ImGui::Unindent(); + } + if (ImGui::RadioButton(_("Channel color"),chanOscColorMode==1)) { + chanOscColorMode=1; + chanOscGrad.bgColor.x=0.0f; + chanOscGrad.bgColor.y=0.0f; + chanOscGrad.bgColor.z=0.0f; + chanOscGrad.bgColor.w=0.0f; + updateChanOscGradTex=true; + } + // in preparation of image texture + ImGui::Unindent(); + ImGui::Combo(_("X Axis##AxisX"),&chanOscColorX,LocalizedComboGetter,chanOscRefs,GUI_OSCREF_MAX); ImGui::Combo(_("Y Axis##AxisY"),&chanOscColorY,LocalizedComboGetter,chanOscRefs,GUI_OSCREF_MAX); @@ -351,10 +372,13 @@ void FurnaceGUI::drawChanOsc() { } } else { ImGui::SetNextItemWidth(400.0f*dpiScale); - ImGui::BeginDisabled(chanOscUseChanColor); - ImGui::ColorPicker4(_("Color"),(float*)&chanOscColor); + bool chanOscColorModeB=chanOscColorMode; + ImGui::BeginDisabled(chanOscColorModeB); + ImGui::ColorEdit4(_("Color"),(float*)&chanOscColor); ImGui::EndDisabled(); - ImGui::Checkbox(_("Set to channel color"), &chanOscUseChanColor); + if (ImGui::Checkbox(_("Set to channel color"), &chanOscColorModeB)) { + chanOscColorMode=chanOscColorModeB; + } } ImGui::AlignTextToFramePadding(); @@ -773,7 +797,15 @@ void FurnaceGUI::drawChanOsc() { } } } - ImU32 color=ImGui::GetColorU32(chanOscUseChanColor?channelColor(oscChans[i]):chanOscColor); + ImU32 color; + switch (chanOscColorMode) { + case 0: + color=ImGui::GetColorU32(chanOscColor); + break; + case 1: + color=ImGui::GetColorU32(channelColor(oscChans[i])); + break; + } if (chanOscUseGrad) { float xVal=computeGradPos(chanOscColorX,ch,oscBufs.size()); float yVal=computeGradPos(chanOscColorY,ch,oscBufs.size()); @@ -781,7 +813,22 @@ void FurnaceGUI::drawChanOsc() { xVal=CLAMP(xVal,0.0f,1.0f); yVal=CLAMP(yVal,0.0f,1.0f); - color=chanOscGrad.get(xVal,1.0f-yVal); + switch (chanOscColorMode) { + case 0: + color=chanOscGrad.get(xVal,1.0f-yVal); + break; + case 1: + color=ImAlphaBlendColors(color,chanOscGrad.get(xVal,1.0f-yVal)); + break; + } + // char buf[256]; + // snprintf(buf, 256, "%f:%f",xVal,yVal); + // dl->AddText(inRect.Min,-1,buf); + // dl->AddCircleFilled( + // ImVec2( + // ImLerp(inRect.Min.x,inRect.Max.x,xVal), + // ImLerp(inRect.Min.y,inRect.Max.y,1.0f-yVal) + // ), 2, 0xffff0000); } if (rend->supportsDrawOsc() && settings.shaderOsc) { @@ -886,10 +933,9 @@ void FurnaceGUI::drawChanOsc() { text+=fmt::sprintf("%s",noteName(chanState->note+60)); break; } - case 'l': { + case 'l': text+='\n'; break; - } case '%': text+='%'; break; diff --git a/src/gui/gradient.cpp b/src/gui/gradient.cpp index b33564f0b..be4e08eb1 100644 --- a/src/gui/gradient.cpp +++ b/src/gui/gradient.cpp @@ -109,21 +109,28 @@ void Gradient2D::render() { if (dist>1) dist=1; ImU32 shadeColor=ImGui::ColorConvertFloat4ToU32( + // ps: multiplying dist to the color channels fixes old + // mixing, but breaks if the bg is 0. and vice versa (not + // multiplying fixes 0 bg mixing) ImVec4( i.color.x*i.color.w*dist, i.color.y*i.color.w*dist, i.color.z*i.color.w*dist, - 1.0f + 1.0f*dist // this idk ) ); ImU32 origColor=g[j*width+k]; + // note: this really breaks the color mixing if theres a background + // and the bitshifts are necessary to avoid overflow (but prob only for alpha) + // this needs to be redone, its a temporary proof-of-concept solution anyway g[j*width+k]=( - (MIN( 0xff, (origColor&0xff) + (shadeColor&0xff) )) | // R - (MIN( 0xff00, (origColor&0xff00) + (shadeColor&0xff00) )) | // G - (MIN(0xff0000,(origColor&0xff0000)+(shadeColor&0xff0000))) | // B - (origColor&0xff000000) // A + (MIN(0xff,(origColor &0xff)+(shadeColor &0xff)) ) | // R + (MIN(0xff,(origColor>> 8&0xff)+(shadeColor>> 8&0xff))<< 8) | // G + (MIN(0xff,(origColor>>16&0xff)+(shadeColor>>16&0xff))<<16) | // B + (MIN(0xff,(origColor>>24&0xff)+(shadeColor>>24&0xff))<<24) // A ); + // ps 2: replacing this with ImAlphaBlendColors doesnt work } } } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2284d0ea1..ab04f7830 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -8402,7 +8402,7 @@ void FurnaceGUI::syncState() { chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f); chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f); chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); - chanOscUseChanColor=e->getConfBool("chanOscUseChanColor", false); + chanOscColorMode=e->getConfInt("chanOscColorMode",0); chanOscGrad.fromString(e->getConfString("chanOscGrad","")); chanOscGrad.render(); @@ -8577,7 +8577,7 @@ void FurnaceGUI::commitState(DivConfig& conf) { conf.set("chanOscTextColorA",chanOscTextColor.w); conf.set("chanOscUseGrad",chanOscUseGrad); conf.set("chanOscGrad",chanOscGrad.toString()); - conf.set("chanOscUseChanColor",chanOscUseChanColor); + conf.set("chanOscColorMode",chanOscColorMode); // commit x-y osc state conf.set("xyOscXChannel",xyOscXChannel); @@ -9190,6 +9190,7 @@ FurnaceGUI::FurnaceGUI(): chanOscColorX(GUI_OSCREF_CENTER), chanOscColorY(GUI_OSCREF_CENTER), chanOscCenterStrat(1), + chanOscColorMode(0), chanOscWindowSize(20.0f), chanOscTextX(0.0f), chanOscTextY(0.0f), @@ -9201,7 +9202,6 @@ FurnaceGUI::FurnaceGUI(): chanOscUseGrad(false), chanOscNormalize(false), chanOscRandomPhase(false), - chanOscUseChanColor(false), chanOscAutoCols(false), chanOscTextFormat("%c"), chanOscColor(1.0f,1.0f,1.0f,1.0f), diff --git a/src/gui/gui.h b/src/gui/gui.h index 024d40c33..0a0076e57 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2696,10 +2696,10 @@ class FurnaceGUI { bool oscZoomSlider; // per-channel oscilloscope - int chanOscCols, chanOscColorX, chanOscColorY, chanOscCenterStrat; + int chanOscCols, chanOscColorX, chanOscColorY, chanOscCenterStrat, chanOscColorMode; float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify, chanOscLineSize; bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; - bool chanOscNormalize, chanOscRandomPhase, chanOscUseChanColor, chanOscAutoCols; + bool chanOscNormalize, chanOscRandomPhase, chanOscAutoCols; String chanOscTextFormat; ImVec4 chanOscColor, chanOscTextColor; Gradient2D chanOscGrad; From 7b11fe68ad754844f18a206fbc0bbd7e67b38501 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 13 Nov 2025 16:33:35 -0500 Subject: [PATCH 18/46] giga-refactor, part 6 make engine dispatchChanOfChan-aware --- src/engine/cmdStream.cpp | 7 ++++++- src/engine/engine.cpp | 22 +++++++++++++++++----- src/engine/playback.cpp | 37 ++++++++++++++++++++----------------- src/engine/song.cpp | 10 ++++++++-- src/engine/sysDef.cpp | 5 +++++ src/engine/wavOps.cpp | 4 ++-- src/gui/debugWindow.cpp | 2 ++ 7 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index 41c2c3cc8..6b5d9ee93 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -704,7 +704,12 @@ bool DivCSPlayer::init() { // initialize state for (int i=0; igetTotalChannelCount(); i++) { - chan[i].volMax=(e->getDispatch(e->song.dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->song.dispatchChanOfChan[i]))<<8)|0xff; + if (e->song.dispatchChanOfChan[i]>=0) { + chan[i].volMax=(e->getDispatch(e->song.dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->song.dispatchChanOfChan[i]))<<8)|0xff; + } else { + // fallback + chan[i].volMax=0xfff; + } chan[i].volume=chan[i].volMax; } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a27fe8a69..6fd6fef4f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1536,21 +1536,25 @@ DivChannelState* DivEngine::getChanState(int ch) { unsigned short DivEngine::getChanPan(int ch) { if (ch<0 || ch>=song.chans) return 0; + if (song.dispatchChanOfChan[ch]<0) return 0; return disCont[song.dispatchOfChan[ch]].dispatch->getPan(song.dispatchChanOfChan[ch]); } void* DivEngine::getDispatchChanState(int ch) { if (ch<0 || ch>=song.chans) return NULL; + if (song.dispatchChanOfChan[ch]<0) return NULL; return disCont[song.dispatchOfChan[ch]].dispatch->getChanState(song.dispatchChanOfChan[ch]); } void DivEngine::getChanPaired(int ch, std::vector& ret) { if (ch<0 || ch>=song.chans) return; + if (song.dispatchChanOfChan[ch]<0) return; disCont[song.dispatchOfChan[ch]].dispatch->getPaired(song.dispatchChanOfChan[ch],ret); } DivChannelModeHints DivEngine::getChanModeHints(int ch) { if (ch<0 || ch>=song.chans) return DivChannelModeHints(); + if (song.dispatchChanOfChan[ch]<0) return DivChannelModeHints(); return disCont[song.dispatchOfChan[ch]].dispatch->getModeHints(song.dispatchChanOfChan[ch]); } @@ -1564,16 +1568,19 @@ unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) { DivMacroInt* DivEngine::getMacroInt(int chan) { if (chan<0 || chan>=song.chans) return NULL; + if (song.dispatchChanOfChan[chan]<0) return NULL; return disCont[song.dispatchOfChan[chan]].dispatch->getChanMacroInt(song.dispatchChanOfChan[chan]); } DivSamplePos DivEngine::getSamplePos(int chan) { if (chan<0 || chan>=song.chans) return DivSamplePos(); + if (song.dispatchChanOfChan[chan]<0) return DivSamplePos(); return disCont[song.dispatchOfChan[chan]].dispatch->getSamplePos(song.dispatchChanOfChan[chan]); } DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) { if (chan<0 || chan>=song.chans) return NULL; + if (song.dispatchChanOfChan[chan]<0) return NULL; return disCont[song.dispatchOfChan[chan]].dispatch->getOscBuffer(song.dispatchChanOfChan[chan]); } @@ -2070,6 +2077,7 @@ void DivEngine::stop() { // reset all chan oscs for (int i=0; igetOscBuffer(song.dispatchChanOfChan[i]); if (buf!=NULL) { buf->reset(); @@ -2118,7 +2126,9 @@ void DivEngine::reset() { } for (int i=0; idispatch(DivCommand(DIV_CMD_GET_VOLMAX,song.dispatchChanOfChan[i]))<<8)|0xff; + if (i=0) { + chan[i].volMax=(disCont[song.dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,song.dispatchChanOfChan[i]))<<8)|0xff; + } chan[i].volume=chan[i].volMax; if (!song.linearPitch) chan[i].vibratoFine=4; } @@ -2393,6 +2403,7 @@ int DivEngine::mapVelocity(int ch, float vel) { if (ch<0) return 0; if (ch>=song.chans) return 0; if (disCont[song.dispatchOfChan[ch]].dispatch==NULL) return 0; + if (song.dispatchChanOfChan[ch]<0) return 0; return disCont[song.dispatchOfChan[ch]].dispatch->mapVelocity(song.dispatchChanOfChan[ch],vel); } @@ -2400,6 +2411,7 @@ float DivEngine::getGain(int ch, int vol) { if (ch<0) return 0; if (ch>=song.chans) return 0; if (disCont[song.dispatchOfChan[ch]].dispatch==NULL) return 0; + if (song.dispatchChanOfChan[ch]<0) return 0; return disCont[song.dispatchOfChan[ch]].dispatch->getGain(song.dispatchChanOfChan[ch],vol); } @@ -2525,14 +2537,14 @@ void DivEngine::toggleSolo(int chan) { if (!solo) { for (int i=0; i=0) { disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],isMuted[i]); } } } else { for (int i=0; i=0) { disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],isMuted[i]); } } @@ -2543,7 +2555,7 @@ void DivEngine::toggleSolo(int chan) { void DivEngine::muteChannel(int chan, bool mute) { BUSY_BEGIN; isMuted[chan]=mute; - if (disCont[song.dispatchOfChan[chan]].dispatch!=NULL) { + if (disCont[song.dispatchOfChan[chan]].dispatch!=NULL && song.dispatchChanOfChan[chan]>=0) { disCont[song.dispatchOfChan[chan]].dispatch->muteChannel(song.dispatchChanOfChan[chan],isMuted[chan]); } BUSY_END; @@ -2553,7 +2565,7 @@ void DivEngine::unmuteAll() { BUSY_BEGIN; for (int i=0; i=0) { disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],isMuted[i]); } } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index abd79aa6c..f9bda8a9b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -496,6 +496,11 @@ int DivEngine::dispatchCmd(DivCommand c) { } } + // don't dispatch if the channel doesn't exist + if (song.dispatchChanOfChan[c.dis]<0) { + return 0; + } + // map the channel to channel of chip // c.dis is a copy of c.chan because we'll use it in the next call c.chan=song.dispatchChanOfChan[c.dis]; @@ -784,15 +789,10 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].stopOnOff=false; } // depending on the system, portamento may still be disabled - if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { + if (song.dispatchChanOfChan[i]>=0) if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); - // this here is a now-disabled hack which makes the noise channel also stop when square 3 is - /*if (i==2 && song.sysOfChan[i]==DIV_SYSTEM_SMS) { - chan[i+1].portaNote=-1; - chan[i+1].portaSpeed=-1; - }*/ } // another compatibility hack which schedules a second reset later just in case chan[i].scheduledSlideReset=true; @@ -812,14 +812,10 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].stopOnOff=false; } - if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { + if (song.dispatchChanOfChan[i]>=0) if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); - /*if (i==2 && song.sysOfChan[i]==DIV_SYSTEM_SMS) { - chan[i+1].portaNote=-1; - chan[i+1].portaSpeed=-1; - }*/ } chan[i].scheduledSlideReset=true; } @@ -839,7 +835,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // ...unless there's a way to trigger keyOn twice if (!chan[i].keyOn) { // the behavior of arpeggio reset upon note off varies per system - if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsArp(song.dispatchChanOfChan[i])) { + if (song.dispatchChanOfChan[i]>=0) if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsArp(song.dispatchChanOfChan[i])) { chan[i].arp=0; dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); } @@ -1039,7 +1035,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: limit slide range // - this confines pitch slides from dispatch->getPortaFloor to C-8 (I think) // - yep, the lowest portamento note depends on the system... - chan[i].portaNote=song.limitSlides?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; + chan[i].portaNote=(song.limitSlides && song.dispatchChanOfChan[i]>=0)?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; chan[i].portaSpeed=effectVal; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; @@ -1684,7 +1680,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaNote=song.limitSlides?0x60:255; } else { // COMPAT FLAG: limit slide range - chan[i].portaNote=song.limitSlides?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; + chan[i].portaNote=(song.limitSlides && song.dispatchChanOfChan[i]>=0)?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; } chan[i].portaSpeed=effectVal; chan[i].portaStop=true; @@ -2080,7 +2076,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (note.volume>=0 && !disCont[song.dispatchOfChan[note.channel]].dispatch->isVolGlobal()) { // map velocity to curve and then to equivalent chip volume float curvedVol=pow((float)note.volume/127.0f,midiVolExp); - int mappedVol=disCont[song.dispatchOfChan[note.channel]].dispatch->mapVelocity(song.dispatchChanOfChan[note.channel],curvedVol); + int mappedVol=0; + if (song.dispatchChanOfChan[note.channel]>=0) { + mappedVol=disCont[song.dispatchOfChan[note.channel]].dispatch->mapVelocity(song.dispatchChanOfChan[note.channel],curvedVol); + } // fire command dispatchCmd(DivCommand(DIV_CMD_VOLUME,note.channel,mappedVol)); } @@ -2094,7 +2093,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { chan[note.channel].lastIns=note.ins; } else { // note off - DivMacroInt* macroInt=disCont[song.dispatchOfChan[note.channel]].dispatch->getChanMacroInt(song.dispatchChanOfChan[note.channel]); + DivMacroInt* macroInt=NULL; + if (song.dispatchChanOfChan[note.channel]>=0) { + macroInt=disCont[song.dispatchOfChan[note.channel]].dispatch->getChanMacroInt(song.dispatchChanOfChan[note.channel]); + } if (macroInt!=NULL) { // if the current instrument has a release point in any macros and // volume is per-channel, send a note release instead of a note off @@ -2463,7 +2465,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { chan[i].stopOnOff=false; } // depending on the system, portamento may still be disabled - if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { + if (song.dispatchChanOfChan[i]>=0) if (disCont[song.dispatchOfChan[i]].dispatch->keyOffAffectsPorta(song.dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); @@ -2565,6 +2567,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { shallStopSched=false; // reset all chan oscs for (int i=0; igetOscBuffer(song.dispatchChanOfChan[i]); if (buf!=NULL) { buf->reset(); diff --git a/src/engine/song.cpp b/src/engine/song.cpp index dad02d708..970dae4b6 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -710,17 +710,23 @@ void DivSong::recalcChans() { int chanIndex=0; memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool)); for (int i=0; imaxChans) { + dispatchChanOfChan[chanIndex]=j; + } else { + dispatchChanOfChan[chanIndex]=-1; + } dispatchFirstChan[chanIndex]=firstChan; chanIndex++; - const DivSysDef* sysDef=DivEngine::getSystemDef(system[i]); if (sysDef!=NULL) { if (sysDef->chanInsType[j][0]!=DIV_INS_NULL) { isInsTypePossible[sysDef->chanInsType[j][0]]=true; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 24fd6246b..4d3f78e08 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -314,6 +314,7 @@ const char* DivEngine::getChannelName(int chan) { if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); if (sysDefs[song.sysOfChan[chan]]==NULL) return "??"; + if (song.dispatchChanOfChan[chan]<0) return "??"; const char* ret=sysDefs[song.sysOfChan[chan]]->chanNames[song.dispatchChanOfChan[chan]]; if (ret==NULL) return "??"; @@ -324,6 +325,7 @@ const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); if (sysDefs[song.sysOfChan[chan]]==NULL) return "??"; + if (song.dispatchChanOfChan[chan]<0) return "??"; const char* ret=sysDefs[song.sysOfChan[chan]]->chanShortNames[song.dispatchChanOfChan[chan]]; if (ret==NULL) return "??"; @@ -333,18 +335,21 @@ const char* DivEngine::getChannelShortName(int chan) { int DivEngine::getChannelType(int chan) { if (chan<0 || chan>song.chans) return DIV_CH_NOISE; if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_CH_NOISE; + if (song.dispatchChanOfChan[chan]<0) return DIV_CH_NOISE; return sysDefs[song.sysOfChan[chan]]->chanTypes[song.dispatchChanOfChan[chan]]; } DivInstrumentType DivEngine::getPreferInsType(int chan) { if (chan<0 || chan>song.chans) return DIV_INS_STD; if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_INS_STD; + if (song.dispatchChanOfChan[chan]<0) return DIV_INS_STD; return sysDefs[song.sysOfChan[chan]]->chanInsType[song.dispatchChanOfChan[chan]][0]; } DivInstrumentType DivEngine::getPreferInsSecondType(int chan) { if (chan<0 || chan>song.chans) return DIV_INS_NULL; if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_INS_NULL; + if (song.dispatchChanOfChan[chan]<0) return DIV_INS_NULL; return sysDefs[song.sysOfChan[chan]]->chanInsType[song.dispatchChanOfChan[chan]][1]; } diff --git a/src/engine/wavOps.cpp b/src/engine/wavOps.cpp index d65af451d..dbe6f24fa 100644 --- a/src/engine/wavOps.cpp +++ b/src/engine/wavOps.cpp @@ -487,7 +487,7 @@ void DivEngine::runExportThread() { } } for (int j=0; j=0) { disCont[song.dispatchOfChan[j]].dispatch->muteChannel(song.dispatchChanOfChan[j],isMuted[j]); } } @@ -566,7 +566,7 @@ void DivEngine::runExportThread() { for (int i=0; i=0) { disCont[song.dispatchOfChan[i]].dispatch->muteChannel(song.dispatchChanOfChan[i],false); } } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 96d0a2dce..81fae0061 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -121,6 +121,8 @@ void FurnaceGUI::drawDebug() { ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->song.dispatchOfChan[i],e->song.dispatchChanOfChan[i]); if (ch==NULL) { ImGui::Text("NULL"); + } else if (e->song.dispatchChanOfChan[i]<0) { + ImGui::Text("---"); } else { putDispatchChan(ch,e->song.dispatchChanOfChan[i],e->song.sysOfChan[i]); } From f93dd893c87cd58e3d210036cd4b11ffa9e03b63 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Fri, 14 Nov 2025 18:37:23 +0400 Subject: [PATCH 19/46] chan color in piano and real volume chan feedback (w/ gamma correction) now i can recreate the bee video! :> --- src/gui/gui.cpp | 2 +- src/gui/gui.h | 8 +++++++- src/gui/pattern.cpp | 20 +++++++++++++++++--- src/gui/piano.cpp | 16 ++++++++-------- src/gui/settings.cpp | 18 +++++++++++++++++- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ab04f7830..f6861edca 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -9385,7 +9385,7 @@ FurnaceGUI::FurnaceGUI(): memset(lastAudioLoads,0,sizeof(float)*120); - memset(pianoKeyHit,0,sizeof(float)*180); + memset(pianoKeyHit,0,sizeof(pianoKeyState)*180); // posiblly repace with a for loop memset(pianoKeyPressed,0,sizeof(bool)*180); memset(queryReplaceEffectMode,0,sizeof(int)*8); diff --git a/src/gui/gui.h b/src/gui/gui.h index 0a0076e57..e83bf0900 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2090,6 +2090,7 @@ class FurnaceGUI { int sampleImportInstDetune; int mixerStyle; int mixerLayout; + float channelFeedbackGamma; String mainFontPath; String headFontPath; String patFontPath; @@ -2344,6 +2345,7 @@ class FurnaceGUI { sampleImportInstDetune(0), mixerStyle(1), mixerLayout(0), + channelFeedbackGamma(1.0f), mainFontPath(""), headFontPath(""), patFontPath(""), @@ -2839,7 +2841,11 @@ class FurnaceGUI { int pianoOctaves, pianoOctavesEdit; bool pianoOptions, pianoSharePosition, pianoOptionsSet; - float pianoKeyHit[180]; + struct pianoKeyState { + float value; + int chan; + }; + pianoKeyState pianoKeyHit[180]; bool pianoKeyPressed[180]; bool pianoReadonly; int pianoOffset, pianoOffsetEdit; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 46f799c27..65f45cb0c 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -666,7 +666,8 @@ void FurnaceGUI::drawPattern() { if (!muted) { int note=e->getChanState(i)->note+60; if (note>=0 && note<180) { - pianoKeyHit[note]=1.0; + pianoKeyHit[note].value=1.0; + pianoKeyHit[note].chan=i; } } } @@ -679,7 +680,8 @@ void FurnaceGUI::drawPattern() { if (!muted) { int note=e->getChanState(i)->note+60; if (note>=0 && note<180) { - pianoKeyHit[note]=amount; + pianoKeyHit[note].value=amount; + pianoKeyHit[note].chan=i; } } } else if (settings.channelFeedbackStyle==3 && e->isRunning()) { @@ -688,7 +690,19 @@ void FurnaceGUI::drawPattern() { if (!muted) { int note=e->getChanState(i)->note+60; if (note>=0 && note<180) { - pianoKeyHit[note]=active?1.0f:0.0f; + pianoKeyHit[note].value=active?1.0f:0.0f; + pianoKeyHit[note].chan=i; + } + } + } else if (settings.channelFeedbackStyle==4 && e->isRunning()) { + float amount=powf(chanOscVol[i],settings.channelFeedbackGamma); + if (!e->getChanState(i)->keyOn) amount=0.0f; + keyHit[i]=amount*0.2f; + if (!muted) { + int note=e->getChanState(i)->note+60; + if (note>=0 && note<180) { + pianoKeyHit[note].value=amount; + pianoKeyHit[note].chan=i; } } } diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 6bcfbdda1..3df1c187e 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -297,12 +297,12 @@ void FurnaceGUI::drawPiano() { int note=i+12*off; if (note<0) continue; if (note>=180) continue; - float pkh=pianoKeyHit[note]; + float pkh=pianoKeyHit[note].value; ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; if (pianoKeyPressed[note]) { color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; } else { - ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; + ImVec4 colorHit=1?channelColor(pianoKeyHit[note].chan):(isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]); color.x+=(colorHit.x-color.x)*pkh; color.y+=(colorHit.y-color.y)*pkh; color.z+=(colorHit.z-color.z)*pkh; @@ -355,12 +355,12 @@ void FurnaceGUI::drawPiano() { if (note<0) continue; if (note>=180) continue; - float pkh=pianoKeyHit[note]; + float pkh=pianoKeyHit[note].value; ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; if (pianoKeyPressed[note]) { color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; + ImVec4 colorHit=1?channelColor(pianoKeyHit[note].chan):uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; color.x+=(colorHit.x-color.x)*pkh; color.y+=(colorHit.y-color.y)*pkh; color.z+=(colorHit.z-color.z)*pkh; @@ -383,12 +383,12 @@ void FurnaceGUI::drawPiano() { int note=topKeyNotes[j]+12*(i+off); if (note<0) continue; if (note>=180) continue; - float pkh=pianoKeyHit[note]; + float pkh=pianoKeyHit[note].value; ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; if (pianoKeyPressed[note]) { color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + ImVec4 colorHit=1?channelColor(pianoKeyHit[note].chan):uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; color.x+=(colorHit.x-color.x)*pkh; color.y+=(colorHit.y-color.y)*pkh; color.z+=(colorHit.z-color.z)*pkh; @@ -407,8 +407,8 @@ void FurnaceGUI::drawPiano() { const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; for (int i=0; i<180; i++) { - pianoKeyHit[i]-=reduction; - if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; + pianoKeyHit[i].value-=reduction; + if (pianoKeyHit[i].value<0) pianoKeyHit[i].value=0; } } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 7c785d169..ea614b76e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3505,6 +3505,19 @@ void FurnaceGUI::drawSettings() { settings.channelFeedbackStyle=3; settingsChanged=true; } + if (ImGui::RadioButton(_("Volume (Real)##CHF4"),settings.channelFeedbackStyle==4)) { + settings.channelFeedbackStyle=4; + settingsChanged=true; + } + if (settings.channelFeedbackStyle==4) { + ImGui::Indent(); + if (ImGui::SliderFloat(_("Gamma##CHF"),&settings.channelFeedbackGamma,0.0f,2.0f)) { + if (settings.channelFeedbackGamma<0.0f) settings.channelFeedbackGamma=0.0f; + if (settings.channelFeedbackGamma>2.0f) settings.channelFeedbackGamma=2.0f; + settingsChanged=true; + } + ImGui::Unindent(); + } ImGui::Unindent(); ImGui::Text(_("Channel font:")); @@ -5123,6 +5136,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.channelStyle=conf.getInt("channelStyle",1); settings.channelVolStyle=conf.getInt("channelVolStyle",0); settings.channelFeedbackStyle=conf.getInt("channelFeedbackStyle",1); + settings.channelFeedbackGamma=conf.getFloat("channelFeedbackGamma",1.0f); settings.channelFont=conf.getInt("channelFont",1); settings.channelTextCenter=conf.getInt("channelTextCenter",1); @@ -5399,7 +5413,8 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.channelTextColors,0,2); clampSetting(settings.channelStyle,0,5); clampSetting(settings.channelVolStyle,0,4); - clampSetting(settings.channelFeedbackStyle,0,3); + clampSetting(settings.channelFeedbackStyle,0,4); + clampSetting(settings.channelFeedbackGamma,0.0f,2.0f); clampSetting(settings.channelFont,0,1); clampSetting(settings.channelTextCenter,0,1); clampSetting(settings.maxRecentFile,0,30); @@ -5711,6 +5726,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("channelStyle",settings.channelStyle); conf.set("channelVolStyle",settings.channelVolStyle); conf.set("channelFeedbackStyle",settings.channelFeedbackStyle); + conf.set("channelFeedbackGamma",settings.channelFeedbackGamma); conf.set("channelFont",settings.channelFont); conf.set("channelTextCenter",settings.channelTextCenter); From a1bca68e4ae7a8779890cd38d73ecce5c9a02dc1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 15 Nov 2025 03:00:40 -0500 Subject: [PATCH 20/46] asgfdlhjkashdjfl --- src/engine/engine.cpp | 4 ++++ src/engine/engine.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 6fd6fef4f..c062488bc 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -994,6 +994,10 @@ void DivEngine::delUnusedSamples() { BUSY_END; } +bool DivEngine::sysChanCountChange(int firstChan, int before, int after) { + +} + bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { if (index<0 || index>=song.systemLen) { lastError=_("invalid index"); diff --git a/src/engine/engine.h b/src/engine/engine.h index 7d9a2378e..16785587b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -687,6 +687,7 @@ class DivEngine { void copyChannel(int src, int dest); void swapChannels(int src, int dest); void stompChannel(int ch); + bool sysChanCountChange(int firstChan, int before, int after); // recalculate patchbay (UNSAFE) void recalcPatchbay(); @@ -1338,6 +1339,9 @@ class DivEngine { // change system bool changeSystem(int index, DivSystem which, bool preserveOrder=true); + // set system channel count + bool setSystemChans(int index, int ch, bool preserveOrder=true); + // add system bool addSystem(DivSystem which); From 7bb770ad01c9ce303bd980d72313547688260295 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 15 Nov 2025 05:09:11 -0500 Subject: [PATCH 21/46] giga-refactor, part 7 --- src/engine/engine.cpp | 86 ++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index c062488bc..b9d94c773 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -995,7 +995,32 @@ void DivEngine::delUnusedSamples() { } bool DivEngine::sysChanCountChange(int firstChan, int before, int after) { + if (song.chans-before+after>DIV_MAX_CHANS) { + return false; + } + int chanMovement=after-before; + int lastChan=firstChan+before; + if (chanMovement!=0) { + if (chanMovement>0) { + // add channels + for (int i=song.chans+chanMovement-1; i>=lastChan+chanMovement; i--) { + swapChannels(i,i-chanMovement); + } + for (int i=lastChan; i0) { - // add channels - for (int i=chanCount+chanMovement-1; i>=lastChan+chanMovement; i--) { - swapChannels(i,i-chanMovement); - } - for (int i=lastChan; i=song.systemLen) { + lastError=_("invalid index"); + return false; + } + if (song.chans-song.systemChans[index]+ch>DIV_MAX_CHANS) { + lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); + return false; + } + + quitDispatch(); + BUSY_BEGIN; + saveLock.lock(); + + if (!preserveOrder) { + int firstChan=0; + while (song.dispatchOfChan[firstChan]!=index) firstChan++; + if (!sysChanCountChange(firstChan,song.systemChans[index],ch)) { + logE("it should not be failing here!"); + abort(); + } + } + + song.systemChans[index]=ch; + song.systemFlags[index].clear(); + song.recalcChans(); + saveLock.unlock(); + BUSY_END; + initDispatch(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; + + return true; +} + bool DivEngine::addSystem(DivSystem which) { if (song.systemLen>=DIV_MAX_CHIPS) { lastError=fmt::sprintf(_("max number of systems is %d"),DIV_MAX_CHIPS); From 9b3e6cea5ba1395eb11c80e9ec4c6b86145f5756 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 15 Nov 2025 10:00:17 -0500 Subject: [PATCH 22/46] giga-refactor, part 8 allow setting channel count --- src/gui/sysConf.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 09d3a5ede..2dced97a5 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -28,6 +28,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl bool mustRender=false; bool restart=modifyOnChange; bool supportsCustomRate=true; + bool supportsChannelCount=(chan>=0); switch (type) { case DIV_SYSTEM_YM2612: @@ -2775,8 +2776,38 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } } - if (supportsCustomRate) { + bool separatedYet=false; + if (supportsChannelCount) { ImGui::Separator(); + separatedYet=true; + const DivSysDef* sysDef=e->getSystemDef(type); + int chCount=e->song.systemChans[chan]; + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(_("Channels")); + ImGui::SameLine(); + ImGui::SetNextItemWidth(120.0f*dpiScale); + ImGui::InputInt("##ChCount",&chCount,0,0); + if (ImGui::IsItemDeactivatedAfterEdit() && chCount!=e->song.systemChans[chan]) { + if (e->setSystemChans(chan,chCount,preserveChanPos)) { + MARK_MODIFIED; + recalcTimestamps=true; + if (e->song.autoSystem) { + autoDetectSystem(); + } + updateWindowTitle(); + updateROMExportAvail(); + } else { + showError(fmt::sprintf(_("cannot change chip! (%s)"),e->getLastError())); + } + } + if (sysDef!=NULL) { + ImGui::SameLine(); + ImGui::Text("(%d - %d)",sysDef->minChans,sysDef->maxChans); + } + } + + if (supportsCustomRate) { + if (!separatedYet) ImGui::Separator(); int customClock=flags.getInt("customClock",0); bool usingCustomClock=customClock>=MIN_CUSTOM_CLOCK; From 90a9a86e09c7b9f240e64c10a5b4e42b0d0fb34f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 01:40:19 -0500 Subject: [PATCH 23/46] giga-refactor, part 9 new format saving compatibility flags now part of own struct --- papers/format.md | 11 +- src/engine/cmdStream.cpp | 2 +- src/engine/dispatch.h | 8 +- src/engine/engine.cpp | 16 +- src/engine/engine.h | 2 +- src/engine/fileOps/dmf.cpp | 90 +-- src/engine/fileOps/fc.cpp | 12 +- src/engine/fileOps/ftm.cpp | 4 +- src/engine/fileOps/fur.cpp | 718 ++++++++++------------- src/engine/fileOps/it.cpp | 14 +- src/engine/fileOps/mod.cpp | 10 +- src/engine/fileOps/s3m.cpp | 12 +- src/engine/fileOps/tfm.cpp | 4 +- src/engine/fileOps/xm.cpp | 16 +- src/engine/macroInt.cpp | 2 +- src/engine/platform/amiga.cpp | 6 +- src/engine/platform/arcade.cpp | 6 +- src/engine/platform/ay.cpp | 8 +- src/engine/platform/ay8930.cpp | 8 +- src/engine/platform/bifurcator.cpp | 6 +- src/engine/platform/bubsyswsg.cpp | 6 +- src/engine/platform/c140.cpp | 6 +- src/engine/platform/c64.cpp | 4 +- src/engine/platform/dave.cpp | 6 +- src/engine/platform/es5506.cpp | 14 +- src/engine/platform/esfm.cpp | 8 +- src/engine/platform/fds.cpp | 8 +- src/engine/platform/fmshared_OPN.h | 6 +- src/engine/platform/ga20.cpp | 6 +- src/engine/platform/gb.cpp | 10 +- src/engine/platform/gbadma.cpp | 4 +- src/engine/platform/gbaminmod.cpp | 6 +- src/engine/platform/genesis.cpp | 16 +- src/engine/platform/genesisext.cpp | 12 +- src/engine/platform/k007232.cpp | 6 +- src/engine/platform/k053260.cpp | 6 +- src/engine/platform/lynx.cpp | 8 +- src/engine/platform/mmc5.cpp | 6 +- src/engine/platform/msm5232.cpp | 12 +- src/engine/platform/msm6258.cpp | 2 +- src/engine/platform/msm6295.cpp | 2 +- src/engine/platform/multipcm.cpp | 4 +- src/engine/platform/n163.cpp | 8 +- src/engine/platform/namcowsg.cpp | 10 +- src/engine/platform/nds.cpp | 6 +- src/engine/platform/nes.cpp | 12 +- src/engine/platform/opl.cpp | 10 +- src/engine/platform/opll.cpp | 6 +- src/engine/platform/pce.cpp | 20 +- src/engine/platform/pcmdac.cpp | 4 +- src/engine/platform/pcspkr.cpp | 6 +- src/engine/platform/pet.cpp | 6 +- src/engine/platform/pokemini.cpp | 6 +- src/engine/platform/pokey.cpp | 10 +- src/engine/platform/pong.cpp | 6 +- src/engine/platform/powernoise.cpp | 6 +- src/engine/platform/pv1000.cpp | 4 +- src/engine/platform/qsound.cpp | 6 +- src/engine/platform/rf5c68.cpp | 6 +- src/engine/platform/saa.cpp | 10 +- src/engine/platform/scc.cpp | 6 +- src/engine/platform/scvtone.cpp | 8 +- src/engine/platform/segapcm.cpp | 20 +- src/engine/platform/sid2.cpp | 4 +- src/engine/platform/sid3.cpp | 6 +- src/engine/platform/sm8521.cpp | 10 +- src/engine/platform/sms.cpp | 18 +- src/engine/platform/snes.cpp | 4 +- src/engine/platform/su.cpp | 10 +- src/engine/platform/supervision.cpp | 6 +- src/engine/platform/swan.cpp | 6 +- src/engine/platform/t6w28.cpp | 10 +- src/engine/platform/ted.cpp | 6 +- src/engine/platform/tia.cpp | 4 +- src/engine/platform/tx81z.cpp | 8 +- src/engine/platform/vb.cpp | 6 +- src/engine/platform/vera.cpp | 4 +- src/engine/platform/vic20.cpp | 4 +- src/engine/platform/vrc6.cpp | 6 +- src/engine/platform/x1_010.cpp | 6 +- src/engine/platform/ym2203.cpp | 10 +- src/engine/platform/ym2203ext.cpp | 6 +- src/engine/platform/ym2608.cpp | 10 +- src/engine/platform/ym2608ext.cpp | 10 +- src/engine/platform/ym2610.cpp | 10 +- src/engine/platform/ym2610b.cpp | 10 +- src/engine/platform/ym2610bext.cpp | 10 +- src/engine/platform/ym2610ext.cpp | 10 +- src/engine/platform/ymz280b.cpp | 8 +- src/engine/platform/zxbeeper.cpp | 4 +- src/engine/platform/zxbeeperquadtone.cpp | 6 +- src/engine/playback.cpp | 124 ++-- src/engine/song.cpp | 228 ++++++- src/engine/song.h | 163 +++-- src/engine/vgmOps.cpp | 2 +- src/gui/compatFlags.cpp | 152 ++--- src/gui/exportOptions.cpp | 2 +- src/gui/insEdit.cpp | 16 +- src/gui/sysConf.cpp | 4 +- 99 files changed, 1145 insertions(+), 1047 deletions(-) diff --git a/papers/format.md b/papers/format.md index 888535b91..79f5446a4 100644 --- a/papers/format.md +++ b/papers/format.md @@ -135,7 +135,9 @@ size | description STR | system name (Japanese) STR | album/category/game name (Japanese) 4f | A-4 tuning + 1 | automatic system name --- | **system definition** + 4f | master volume, 1.0f=100% 2 | total number of channels 2 | number of chips --- | **chip definition (×numChips)** @@ -149,11 +151,10 @@ size | description 4?? | patchbay | - see next section for more details. 1 | automatic patchbay - --- | **song elements** + --- | **song elements (repeated until element type is 0)** 1 | element type - 4?? | pointers to elements - | - a zero pointer means "end of list". - | - if the element is unique, only a single pointer will be present. + 4 | number of elements + 4?? | pointers to elements (×numElements) ``` ## list of sound chips @@ -313,7 +314,7 @@ the following element types are available: 09 | CMNT | song comments* 0a | GROV | groove pattern -* element is unique (read only one pointer and assume the pointer list is over) +* element is unique (number of elements shall be 1) ** first pointer is for instruments, second for wavetables and third for samples ``` diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index 6b5d9ee93..9060d85a6 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -596,7 +596,7 @@ bool DivCSPlayer::tick() { } if (chan[i].portaSpeed) { - e->dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(e->song.linearPitch?e->song.pitchSlideSpeed:1),chan[i].portaTarget)); + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(e->song.compatFlags.linearPitch?e->song.compatFlags.pitchSlideSpeed:1),chan[i].portaTarget)); } if (chan[i].arp && !chan[i].portaSpeed) { if (chan[i].arpTicks==0) { diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index c7c3b797f..5c277bb35 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -1082,10 +1082,10 @@ class DivDispatch { #define NOTE_FNUM_BLOCK(x,bits,blk) parent->calcBaseFreqFNumBlock(chipClock,CHIP_FREQBASE,x,bits,blk) // this is for volume scaling calculation. -#define VOL_SCALE_LINEAR(x,y,range) ((parent->song.ceilVolumeScaling)?((((x)*(y))+(range-1))/(range)):(((x)*(y))/(range))) +#define VOL_SCALE_LINEAR(x,y,range) ((parent->song.compatFlags.ceilVolumeScaling)?((((x)*(y))+(range-1))/(range)):(((x)*(y))/(range))) #define VOL_SCALE_LOG(x,y,range) (CLAMP(((x)+(y))-(range),0,(range))) -#define VOL_SCALE_LINEAR_BROKEN(x,y,range) ((parent->song.newVolumeScaling)?(VOL_SCALE_LINEAR(x,y,range)):(VOL_SCALE_LOG(x,y,range))) -#define VOL_SCALE_LOG_BROKEN(x,y,range) ((parent->song.newVolumeScaling)?(VOL_SCALE_LOG(x,y,range)):(VOL_SCALE_LINEAR(x,y,range))) +#define VOL_SCALE_LINEAR_BROKEN(x,y,range) ((parent->song.compatFlags.newVolumeScaling)?(VOL_SCALE_LINEAR(x,y,range)):(VOL_SCALE_LOG(x,y,range))) +#define VOL_SCALE_LOG_BROKEN(x,y,range) ((parent->song.compatFlags.newVolumeScaling)?(VOL_SCALE_LOG(x,y,range)):(VOL_SCALE_LINEAR(x,y,range))) // these are here for convenience. // it is encouraged to use these, since you get an exact value this way. @@ -1098,7 +1098,7 @@ class DivDispatch { if ((x)<(xMin)) (x)=(xMin); \ if ((x)>(xMax)) (x)=(xMax); -#define NEW_ARP_STRAT (parent->song.linearPitch && !parent->song.oldArpStrategy) +#define NEW_ARP_STRAT (parent->song.compatFlags.linearPitch && !parent->song.compatFlags.oldArpStrategy) #define HACKY_LEGATO_MESS chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode && !NEW_ARP_STRAT #endif diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index b9d94c773..b6bb98de4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -168,7 +168,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul return _("FFxx: Stop song"); default: if ((effect&0xf0)==0x90) { - if (song.oldSampleOffset) { + if (song.compatFlags.oldSampleOffset) { return _("9xxx: Set sample offset*256"); } switch (effect) { @@ -205,7 +205,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul void DivEngine::calcSongTimestamps() { if (curSubSong!=NULL) { - curSubSong->calcTimestamps(song.chans,song.grooves,song.jumpTreatment,song.ignoreJumpAtEnd,song.brokenSpeedSel,song.delayBehavior); + curSubSong->calcTimestamps(song.chans,song.grooves,song.compatFlags.jumpTreatment,song.compatFlags.ignoreJumpAtEnd,song.compatFlags.brokenSpeedSel,song.compatFlags.delayBehavior); } } @@ -1813,7 +1813,7 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) }*/ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { - if (song.linearPitch) { // linear + if (song.compatFlags.linearPitch) { // linear return (note<<7); } double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); @@ -1863,7 +1863,7 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri return bf|((block)<<(bits)); int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits, int fixedBlock) { - if (song.linearPitch) { // linear + if (song.compatFlags.linearPitch) { // linear return (note<<7); } int bf=calcBaseFreq(clock,divider,note,false); @@ -1876,10 +1876,10 @@ int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int int DivEngine::calcFreq(int base, int pitch, int arp, bool arpFixed, bool period, int octave, int pitch2, double clock, double divider, int blockBits, int fixedBlock) { // linear pitch - if (song.linearPitch) { + if (song.compatFlags.linearPitch) { // do frequency calculation here int nbase=base+pitch+pitch2; - if (!song.oldArpStrategy) { + if (!song.compatFlags.oldArpStrategy) { if (arpFixed) { nbase=(arp<<7)+pitch+pitch2; } else { @@ -2178,7 +2178,7 @@ void DivEngine::reset() { chan[i].volMax=(disCont[song.dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,song.dispatchChanOfChan[i]))<<8)|0xff; } chan[i].volume=chan[i].volMax; - if (!song.linearPitch) chan[i].vibratoFine=4; + if (!song.compatFlags.linearPitch) chan[i].vibratoFine=4; } extValue=0; extValuePresent=0; @@ -2436,7 +2436,7 @@ double DivEngine::getSamplePreviewRate() { } double DivEngine::getCenterRate() { - return song.oldCenterRate?8363.0:8372.0; + return song.compatFlags.oldCenterRate?8363.0:8372.0; } String DivEngine::getConfigPath() { diff --git a/src/engine/engine.h b/src/engine/engine.h index 16785587b..5a173c829 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -55,7 +55,7 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "inf2 - DO NOT SAVE!" +#define DIV_VERSION "inf2/dev240 - DO NOT LOAD!" #define DIV_ENGINE_VERSION 240 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index 2557e4b6d..a009848bc 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -172,56 +172,56 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // compatibility flags if (!getConfInt("noDMFCompat",0)) { - ds.limitSlides=true; - ds.linearPitch=1; - ds.loopModality=0; - ds.properNoiseLayout=false; - ds.waveDutyIsVol=false; + ds.compatFlags.limitSlides=true; + ds.compatFlags.linearPitch=1; + ds.compatFlags.loopModality=0; + ds.compatFlags.properNoiseLayout=false; + ds.compatFlags.waveDutyIsVol=false; // TODO: WHAT?! geodude.dmf fails when this is true // but isn't that how Defle behaves??? - ds.resetMacroOnPorta=false; - ds.legacyVolumeSlides=true; - ds.compatibleArpeggio=true; - ds.noteOffResetsSlides=true; - ds.targetResetsSlides=true; - ds.arpNonPorta=false; - ds.algMacroBehavior=false; - ds.brokenShortcutSlides=false; - ds.ignoreDuplicateSlides=true; - ds.brokenDACMode=true; - ds.oneTickCut=false; - ds.newInsTriggersInPorta=true; - ds.arp0Reset=true; - ds.brokenSpeedSel=true; - ds.noSlidesOnFirstTick=false; - ds.rowResetsArpPos=false; - ds.ignoreJumpAtEnd=true; - ds.buggyPortaAfterSlide=true; - ds.gbInsAffectsEnvelope=true; - ds.ignoreDACModeOutsideIntendedChannel=false; - ds.e1e2AlsoTakePriority=true; - ds.fbPortaPause=true; - ds.snDutyReset=true; - ds.oldOctaveBoundary=false; - ds.noOPN2Vol=true; - ds.newVolumeScaling=false; - ds.volMacroLinger=false; - ds.brokenOutVol=true; - ds.brokenOutVol2=true; - ds.e1e2StopOnSameNote=true; - ds.brokenPortaArp=false; - ds.snNoLowPeriods=true; - ds.disableSampleMacro=true; - ds.preNoteNoEffect=true; - ds.oldDPCM=true; - ds.delayBehavior=0; - ds.jumpTreatment=2; - ds.oldAlwaysSetVolume=true; + ds.compatFlags.resetMacroOnPorta=false; + ds.compatFlags.legacyVolumeSlides=true; + ds.compatFlags.compatibleArpeggio=true; + ds.compatFlags.noteOffResetsSlides=true; + ds.compatFlags.targetResetsSlides=true; + ds.compatFlags.arpNonPorta=false; + ds.compatFlags.algMacroBehavior=false; + ds.compatFlags.brokenShortcutSlides=false; + ds.compatFlags.ignoreDuplicateSlides=true; + ds.compatFlags.brokenDACMode=true; + ds.compatFlags.oneTickCut=false; + ds.compatFlags.newInsTriggersInPorta=true; + ds.compatFlags.arp0Reset=true; + ds.compatFlags.brokenSpeedSel=true; + ds.compatFlags.noSlidesOnFirstTick=false; + ds.compatFlags.rowResetsArpPos=false; + ds.compatFlags.ignoreJumpAtEnd=true; + ds.compatFlags.buggyPortaAfterSlide=true; + ds.compatFlags.gbInsAffectsEnvelope=true; + ds.compatFlags.ignoreDACModeOutsideIntendedChannel=false; + ds.compatFlags.e1e2AlsoTakePriority=true; + ds.compatFlags.fbPortaPause=true; + ds.compatFlags.snDutyReset=true; + ds.compatFlags.oldOctaveBoundary=false; + ds.compatFlags.noOPN2Vol=true; + ds.compatFlags.newVolumeScaling=false; + ds.compatFlags.volMacroLinger=false; + ds.compatFlags.brokenOutVol=true; + ds.compatFlags.brokenOutVol2=true; + ds.compatFlags.e1e2StopOnSameNote=true; + ds.compatFlags.brokenPortaArp=false; + ds.compatFlags.snNoLowPeriods=true; + ds.compatFlags.disableSampleMacro=true; + ds.compatFlags.preNoteNoEffect=true; + ds.compatFlags.oldDPCM=true; + ds.compatFlags.delayBehavior=0; + ds.compatFlags.jumpTreatment=2; + ds.compatFlags.oldAlwaysSetVolume=true; // 1.1 compat flags if (ds.version>24) { - ds.waveDutyIsVol=true; - ds.legacyVolumeSlides=false; + ds.compatFlags.waveDutyIsVol=true; + ds.compatFlags.legacyVolumeSlides=false; } // Neo Geo detune is caused by Defle running Neo Geo at the wrong clock. diff --git a/src/engine/fileOps/fc.cpp b/src/engine/fileOps/fc.cpp index 027dda971..0b45e7ac6 100644 --- a/src/engine/fileOps/fc.cpp +++ b/src/engine/fileOps/fc.cpp @@ -147,12 +147,12 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_FC; - //ds.linearPitch=0; - //ds.pitchMacroIsLinear=false; - //ds.noSlidesOnFirstTick=true; - //ds.rowResetsArpPos=true; - ds.pitchSlideSpeed=8; - ds.ignoreJumpAtEnd=false; + //ds.compatFlags.linearPitch=0; + //ds.compatFlags.pitchMacroIsLinear=false; + //ds.compatFlags.noSlidesOnFirstTick=true; + //ds.compatFlags.rowResetsArpPos=true; + ds.compatFlags.pitchSlideSpeed=8; + ds.compatFlags.ignoreJumpAtEnd=false; // load here if (!reader.seek(0,SEEK_SET)) { diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index daf27cb4e..60ebdcd87 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -495,7 +495,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si } ds.subsong.clear(); - ds.linearPitch = 0; + ds.compatFlags.linearPitch = 0; unsigned int pal = 0; @@ -2378,7 +2378,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si CHECK_BLOCK_VERSION(3); unsigned int linear_pitch = reader.readI(); - ds.linearPitch = linear_pitch == 0 ? 0 : 1; + ds.compatFlags.linearPitch = linear_pitch == 0 ? 0 : 1; if (blockVersion >= 2) { int fineTuneCents = reader.readC() * 100; diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 99c1a798a..7f72ade40 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -673,144 +673,151 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { addWarning("this module was created with a downstream version of Furnace. certain features may not be compatible."); } + if (ds.version>=240) { + logE("not yet! I am still working on it!"); + lastError="not yet! I am still working on it!"; + delete[] file; + return false; + } + 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!"); } if (ds.version<37) { // compat flags not stored back then - ds.limitSlides=true; - ds.linearPitch=1; - ds.loopModality=0; + ds.compatFlags.limitSlides=true; + ds.compatFlags.linearPitch=1; + ds.compatFlags.loopModality=0; } if (ds.version<43) { - ds.properNoiseLayout=false; - ds.waveDutyIsVol=false; + ds.compatFlags.properNoiseLayout=false; + ds.compatFlags.waveDutyIsVol=false; } if (ds.version<45) { - ds.resetMacroOnPorta=true; - ds.legacyVolumeSlides=true; - ds.compatibleArpeggio=true; - ds.noteOffResetsSlides=true; - ds.targetResetsSlides=true; + ds.compatFlags.resetMacroOnPorta=true; + ds.compatFlags.legacyVolumeSlides=true; + ds.compatFlags.compatibleArpeggio=true; + ds.compatFlags.noteOffResetsSlides=true; + ds.compatFlags.targetResetsSlides=true; } if (ds.version<46) { - ds.arpNonPorta=true; - ds.algMacroBehavior=true; + ds.compatFlags.arpNonPorta=true; + ds.compatFlags.algMacroBehavior=true; } else { - ds.arpNonPorta=false; - ds.algMacroBehavior=false; + ds.compatFlags.arpNonPorta=false; + ds.compatFlags.algMacroBehavior=false; } if (ds.version<49) { - ds.brokenShortcutSlides=true; + ds.compatFlags.brokenShortcutSlides=true; } if (ds.version<50) { - ds.ignoreDuplicateSlides=false; + ds.compatFlags.ignoreDuplicateSlides=false; } if (ds.version<62) { - ds.stopPortaOnNoteOff=true; + ds.compatFlags.stopPortaOnNoteOff=true; } if (ds.version<64) { - ds.brokenDACMode=false; + ds.compatFlags.brokenDACMode=false; } if (ds.version<65) { - ds.oneTickCut=false; + ds.compatFlags.oneTickCut=false; } if (ds.version<66) { - ds.newInsTriggersInPorta=false; + ds.compatFlags.newInsTriggersInPorta=false; } if (ds.version<69) { - ds.arp0Reset=false; + ds.compatFlags.arp0Reset=false; } if (ds.version<71) { - ds.noSlidesOnFirstTick=false; - ds.rowResetsArpPos=false; - ds.ignoreJumpAtEnd=true; + ds.compatFlags.noSlidesOnFirstTick=false; + ds.compatFlags.rowResetsArpPos=false; + ds.compatFlags.ignoreJumpAtEnd=true; } if (ds.version<72) { - ds.buggyPortaAfterSlide=true; - ds.gbInsAffectsEnvelope=false; + ds.compatFlags.buggyPortaAfterSlide=true; + ds.compatFlags.gbInsAffectsEnvelope=false; } if (ds.version<78) { - ds.sharedExtStat=false; + ds.compatFlags.sharedExtStat=false; } if (ds.version<83) { - ds.ignoreDACModeOutsideIntendedChannel=true; - ds.e1e2AlsoTakePriority=false; + ds.compatFlags.ignoreDACModeOutsideIntendedChannel=true; + ds.compatFlags.e1e2AlsoTakePriority=false; } if (ds.version<84) { - ds.newSegaPCM=false; + ds.compatFlags.newSegaPCM=false; } if (ds.version<85) { - ds.fbPortaPause=true; + ds.compatFlags.fbPortaPause=true; } if (ds.version<86) { - ds.snDutyReset=true; + ds.compatFlags.snDutyReset=true; } if (ds.version<90) { - ds.pitchMacroIsLinear=false; + ds.compatFlags.pitchMacroIsLinear=false; } if (ds.version<97) { - ds.oldOctaveBoundary=true; + ds.compatFlags.oldOctaveBoundary=true; } if (ds.version<97) { // actually should be 98 but yky uses this feature ahead of time - ds.noOPN2Vol=true; + ds.compatFlags.noOPN2Vol=true; } if (ds.version<99) { - ds.newVolumeScaling=false; - ds.volMacroLinger=false; - ds.brokenOutVol=true; + ds.compatFlags.newVolumeScaling=false; + ds.compatFlags.volMacroLinger=false; + ds.compatFlags.brokenOutVol=true; } if (ds.version<100) { - ds.e1e2StopOnSameNote=false; + ds.compatFlags.e1e2StopOnSameNote=false; } if (ds.version<101) { - ds.brokenPortaArp=true; + ds.compatFlags.brokenPortaArp=true; } if (ds.version<108) { - ds.snNoLowPeriods=true; + ds.compatFlags.snNoLowPeriods=true; } if (ds.version<110) { - ds.delayBehavior=1; + ds.compatFlags.delayBehavior=1; } if (ds.version<113) { - ds.jumpTreatment=1; + ds.compatFlags.jumpTreatment=1; } if (ds.version<115) { ds.autoSystem=false; } if (ds.version<117) { - ds.disableSampleMacro=true; + ds.compatFlags.disableSampleMacro=true; } if (ds.version<121) { - ds.brokenOutVol2=false; + ds.compatFlags.brokenOutVol2=false; } if (ds.version<130) { - ds.oldArpStrategy=true; + ds.compatFlags.oldArpStrategy=true; } if (ds.version<138) { - ds.brokenPortaLegato=true; + ds.compatFlags.brokenPortaLegato=true; } if (ds.version<155) { - ds.brokenFMOff=true; + ds.compatFlags.brokenFMOff=true; } if (ds.version<168) { - ds.preNoteNoEffect=true; + ds.compatFlags.preNoteNoEffect=true; } if (ds.version<183) { - ds.oldDPCM=true; + ds.compatFlags.oldDPCM=true; } if (ds.version<184) { - ds.resetArpPhaseOnNewNote=false; + ds.compatFlags.resetArpPhaseOnNewNote=false; } if (ds.version<188) { - ds.ceilVolumeScaling=false; + ds.compatFlags.ceilVolumeScaling=false; } if (ds.version<191) { - ds.oldAlwaysSetVolume=true; + ds.compatFlags.oldAlwaysSetVolume=true; } if (ds.version<200) { - ds.oldSampleOffset=true; + ds.compatFlags.oldSampleOffset=true; } ds.isDMF=false; @@ -1003,89 +1010,89 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { // compatibility flags if (ds.version>=37) { - ds.limitSlides=reader.readC(); - ds.linearPitch=reader.readC(); - ds.loopModality=reader.readC(); + ds.compatFlags.limitSlides=reader.readC(); + ds.compatFlags.linearPitch=reader.readC(); + ds.compatFlags.loopModality=reader.readC(); if (ds.version>=43) { - ds.properNoiseLayout=reader.readC(); + ds.compatFlags.properNoiseLayout=reader.readC(); } else { reader.readC(); } if (ds.version>=43) { - ds.waveDutyIsVol=reader.readC(); + ds.compatFlags.waveDutyIsVol=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { - ds.resetMacroOnPorta=reader.readC(); + ds.compatFlags.resetMacroOnPorta=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { - ds.legacyVolumeSlides=reader.readC(); + ds.compatFlags.legacyVolumeSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { - ds.compatibleArpeggio=reader.readC(); + ds.compatFlags.compatibleArpeggio=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { - ds.noteOffResetsSlides=reader.readC(); + ds.compatFlags.noteOffResetsSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { - ds.targetResetsSlides=reader.readC(); + ds.compatFlags.targetResetsSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=47) { - ds.arpNonPorta=reader.readC(); + ds.compatFlags.arpNonPorta=reader.readC(); } else { reader.readC(); } if (ds.version>=47) { - ds.algMacroBehavior=reader.readC(); + ds.compatFlags.algMacroBehavior=reader.readC(); } else { reader.readC(); } if (ds.version>=49) { - ds.brokenShortcutSlides=reader.readC(); + ds.compatFlags.brokenShortcutSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=50) { - ds.ignoreDuplicateSlides=reader.readC(); + ds.compatFlags.ignoreDuplicateSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=62) { - ds.stopPortaOnNoteOff=reader.readC(); - ds.continuousVibrato=reader.readC(); + ds.compatFlags.stopPortaOnNoteOff=reader.readC(); + ds.compatFlags.continuousVibrato=reader.readC(); } else { reader.readC(); reader.readC(); } if (ds.version>=64) { - ds.brokenDACMode=reader.readC(); + ds.compatFlags.brokenDACMode=reader.readC(); } else { reader.readC(); } if (ds.version>=65) { - ds.oneTickCut=reader.readC(); + ds.compatFlags.oneTickCut=reader.readC(); } else { reader.readC(); } if (ds.version>=66) { - ds.newInsTriggersInPorta=reader.readC(); + ds.compatFlags.newInsTriggersInPorta=reader.readC(); } else { reader.readC(); } if (ds.version>=69) { - ds.arp0Reset=reader.readC(); + ds.compatFlags.arp0Reset=reader.readC(); } else { reader.readC(); } @@ -1167,101 +1174,101 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { if (ds.version>=70) { // extended compat flags - ds.brokenSpeedSel=reader.readC(); + ds.compatFlags.brokenSpeedSel=reader.readC(); if (ds.version>=71) { - ds.noSlidesOnFirstTick=reader.readC(); - ds.rowResetsArpPos=reader.readC(); - ds.ignoreJumpAtEnd=reader.readC(); + ds.compatFlags.noSlidesOnFirstTick=reader.readC(); + ds.compatFlags.rowResetsArpPos=reader.readC(); + ds.compatFlags.ignoreJumpAtEnd=reader.readC(); } else { reader.readC(); reader.readC(); reader.readC(); } if (ds.version>=72) { - ds.buggyPortaAfterSlide=reader.readC(); - ds.gbInsAffectsEnvelope=reader.readC(); + ds.compatFlags.buggyPortaAfterSlide=reader.readC(); + ds.compatFlags.gbInsAffectsEnvelope=reader.readC(); } else { reader.readC(); reader.readC(); } if (ds.version>=78) { - ds.sharedExtStat=reader.readC(); + ds.compatFlags.sharedExtStat=reader.readC(); } else { reader.readC(); } if (ds.version>=83) { - ds.ignoreDACModeOutsideIntendedChannel=reader.readC(); - ds.e1e2AlsoTakePriority=reader.readC(); + ds.compatFlags.ignoreDACModeOutsideIntendedChannel=reader.readC(); + ds.compatFlags.e1e2AlsoTakePriority=reader.readC(); } else { reader.readC(); reader.readC(); } if (ds.version>=84) { - ds.newSegaPCM=reader.readC(); + ds.compatFlags.newSegaPCM=reader.readC(); } else { reader.readC(); } if (ds.version>=85) { - ds.fbPortaPause=reader.readC(); + ds.compatFlags.fbPortaPause=reader.readC(); } else { reader.readC(); } if (ds.version>=86) { - ds.snDutyReset=reader.readC(); + ds.compatFlags.snDutyReset=reader.readC(); } else { reader.readC(); } if (ds.version>=90) { - ds.pitchMacroIsLinear=reader.readC(); + ds.compatFlags.pitchMacroIsLinear=reader.readC(); } else { reader.readC(); } if (ds.version>=94) { - ds.pitchSlideSpeed=reader.readC(); + ds.compatFlags.pitchSlideSpeed=reader.readC(); } else { reader.readC(); } if (ds.version>=97) { - ds.oldOctaveBoundary=reader.readC(); + ds.compatFlags.oldOctaveBoundary=reader.readC(); } else { reader.readC(); } if (ds.version>=98) { - ds.noOPN2Vol=reader.readC(); + ds.compatFlags.noOPN2Vol=reader.readC(); } else { reader.readC(); } if (ds.version>=99) { - ds.newVolumeScaling=reader.readC(); - ds.volMacroLinger=reader.readC(); - ds.brokenOutVol=reader.readC(); + ds.compatFlags.newVolumeScaling=reader.readC(); + ds.compatFlags.volMacroLinger=reader.readC(); + ds.compatFlags.brokenOutVol=reader.readC(); } else { reader.readC(); reader.readC(); reader.readC(); } if (ds.version>=100) { - ds.e1e2StopOnSameNote=reader.readC(); + ds.compatFlags.e1e2StopOnSameNote=reader.readC(); } else { reader.readC(); } if (ds.version>=101) { - ds.brokenPortaArp=reader.readC(); + ds.compatFlags.brokenPortaArp=reader.readC(); } else { reader.readC(); } if (ds.version>=108) { - ds.snNoLowPeriods=reader.readC(); + ds.compatFlags.snNoLowPeriods=reader.readC(); } else { reader.readC(); } if (ds.version>=110) { - ds.delayBehavior=reader.readC(); + ds.compatFlags.delayBehavior=reader.readC(); } else { reader.readC(); } if (ds.version>=113) { - ds.jumpTreatment=reader.readC(); + ds.compatFlags.jumpTreatment=reader.readC(); } else { reader.readC(); } @@ -1271,17 +1278,17 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { reader.readC(); } if (ds.version>=117) { - ds.disableSampleMacro=reader.readC(); + ds.compatFlags.disableSampleMacro=reader.readC(); } else { reader.readC(); } if (ds.version>=121) { - ds.brokenOutVol2=reader.readC(); + ds.compatFlags.brokenOutVol2=reader.readC(); } else { reader.readC(); } if (ds.version>=130) { - ds.oldArpStrategy=reader.readC(); + ds.compatFlags.oldArpStrategy=reader.readC(); } else { reader.readC(); } @@ -1341,39 +1348,39 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { if (ds.version>=136) ds.patchbayAuto=reader.readC(); if (ds.version>=138) { - ds.brokenPortaLegato=reader.readC(); + ds.compatFlags.brokenPortaLegato=reader.readC(); if (ds.version>=155) { - ds.brokenFMOff=reader.readC(); + ds.compatFlags.brokenFMOff=reader.readC(); } else { reader.readC(); } if (ds.version>=168) { - ds.preNoteNoEffect=reader.readC(); + ds.compatFlags.preNoteNoEffect=reader.readC(); } else { reader.readC(); } if (ds.version>=183) { - ds.oldDPCM=reader.readC(); + ds.compatFlags.oldDPCM=reader.readC(); } else { reader.readC(); } if (ds.version>=184) { - ds.resetArpPhaseOnNewNote=reader.readC(); + ds.compatFlags.resetArpPhaseOnNewNote=reader.readC(); } else { reader.readC(); } if (ds.version>=188) { - ds.ceilVolumeScaling=reader.readC(); + ds.compatFlags.ceilVolumeScaling=reader.readC(); } else { reader.readC(); } if (ds.version>=191) { - ds.oldAlwaysSetVolume=reader.readC(); + ds.compatFlags.oldAlwaysSetVolume=reader.readC(); } else { reader.readC(); } if (ds.version>=200) { - ds.oldSampleOffset=reader.readC(); + ds.compatFlags.oldSampleOffset=reader.readC(); } else { reader.readC(); } @@ -2127,9 +2134,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } // warn on partial pitch linearity - if (ds.linearPitch>1) { - ds.linearPitch=1; - } else if (ds.version<237 && ds.linearPitch!=0) { + if (ds.compatFlags.linearPitch>1) { + ds.compatFlags.linearPitch=1; + } else if (ds.version<237 && ds.compatFlags.linearPitch!=0) { addWarning("this song used partial pitch linearity, which has been removed from Furnace. you may have to adjust your song."); } ds.recalcChans(); @@ -2174,17 +2181,15 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { std::vector wavePtr; std::vector samplePtr; std::vector patPtr; + std::vector groovePtr; int assetDirPtr[3]; - size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek, assetDirPtrSeek; - size_t subSongIndex=0; - DivSubSong* subSong=song.subsong[subSongIndex]; - warnings=""; + int compatFlagPtr=0; + int commentPtr=0; + size_t blockStartSeek, blockEndSeek; + size_t sng2PtrSeek=0, flagPtrSeek=0, adirPtrSeek=0, ins2PtrSeek=0, wavePtrSeek=0, smp2PtrSeek=0, patnPtrSeek=0, cflgPtrSeek=0; + size_t cmntPtrSeek=0, grovPtrSeek=0; - // please remove once INF2 is ready. - logE("do not save yet! I still am working on it!"); - lastError="do not save yet! I still am working on it!"; - saveLock.unlock(); - return NULL; + warnings=""; // fail if values are out of range /* @@ -2271,227 +2276,131 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } /// SONG INFO - w->write("INFO",4); + w->write("INF2",4); blockStartSeek=w->tell(); w->writeI(0); - w->writeC(subSong->timeBase); - // these are for compatibility - w->writeC(subSong->speeds.val[0]); - w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); - w->writeC(subSong->arpLen); - w->writeF(subSong->hz); - w->writeS(subSong->patLen); - w->writeS(subSong->ordersLen); - w->writeC(subSong->hilightA); - w->writeC(subSong->hilightB); - w->writeS(song.insLen); - w->writeS(song.waveLen); - w->writeS(song.sampleLen); - w->writeI(patsToWrite.size()); - - for (int i=0; i=song.systemLen) { - w->writeC(0); - } else { - w->writeC(systemToFileFur(song.system[i])); - } - } - - for (int i=0; iwriteC(song.systemVol[i]*64.0f); - } - - for (int i=0; iwriteC(song.systemPan[i]*127.0f); - } - - // chip flags (we'll seek here later) - sysFlagsPtrSeek=w->tell(); - for (int i=0; iwriteI(0); - } - - // song name + // song information w->writeString(song.name,false); - // song author w->writeString(song.author,false); - - w->writeF(song.tuning); - - // compatibility flags - w->writeC(song.limitSlides); - w->writeC(song.linearPitch); - w->writeC(song.loopModality); - w->writeC(song.properNoiseLayout); - w->writeC(song.waveDutyIsVol); - w->writeC(song.resetMacroOnPorta); - w->writeC(song.legacyVolumeSlides); - w->writeC(song.compatibleArpeggio); - w->writeC(song.noteOffResetsSlides); - w->writeC(song.targetResetsSlides); - w->writeC(song.arpNonPorta); - w->writeC(song.algMacroBehavior); - w->writeC(song.brokenShortcutSlides); - w->writeC(song.ignoreDuplicateSlides); - w->writeC(song.stopPortaOnNoteOff); - w->writeC(song.continuousVibrato); - w->writeC(song.brokenDACMode); - w->writeC(song.oneTickCut); - w->writeC(song.newInsTriggersInPorta); - w->writeC(song.arp0Reset); - - ptrSeek=w->tell(); - // instrument pointers (we'll seek here later) - for (int i=0; iwriteI(0); - } - - // wavetable pointers (we'll seek here later) - for (int i=0; iwriteI(0); - } - - // sample pointers (we'll seek here later) - for (int i=0; iwriteI(0); - } - - // pattern pointers (we'll seek here later) - for (size_t i=0; iwriteI(0); - } - - for (int i=0; iordersLen; j++) { - w->writeC(subSong->orders.ord[i][j]); - } - } - - for (int i=0; iwriteC(subSong->pat[i].effectCols); - } - - for (int i=0; iwriteC( - (subSong->chanShow[i]?1:0)| - (subSong->chanShowChanOsc[i]?2:0) - ); - } - - for (int i=0; iwriteC(subSong->chanCollapse[i]); - } - - for (int i=0; iwriteString(subSong->chanName[i],false); - } - - for (int i=0; iwriteString(subSong->chanShortName[i],false); - } - - w->writeString(song.notes,false); - - w->writeF(song.masterVol); - - // extended compat flags - w->writeC(song.brokenSpeedSel); - w->writeC(song.noSlidesOnFirstTick); - w->writeC(song.rowResetsArpPos); - w->writeC(song.ignoreJumpAtEnd); - w->writeC(song.buggyPortaAfterSlide); - w->writeC(song.gbInsAffectsEnvelope); - w->writeC(song.sharedExtStat); - w->writeC(song.ignoreDACModeOutsideIntendedChannel); - w->writeC(song.e1e2AlsoTakePriority); - w->writeC(song.newSegaPCM); - w->writeC(song.fbPortaPause); - w->writeC(song.snDutyReset); - w->writeC(song.pitchMacroIsLinear); - w->writeC(song.pitchSlideSpeed); - w->writeC(song.oldOctaveBoundary); - w->writeC(song.noOPN2Vol); - w->writeC(song.newVolumeScaling); - w->writeC(song.volMacroLinger); - w->writeC(song.brokenOutVol); - w->writeC(song.e1e2StopOnSameNote); - w->writeC(song.brokenPortaArp); - w->writeC(song.snNoLowPeriods); - w->writeC(song.delayBehavior); - w->writeC(song.jumpTreatment); - w->writeC(song.autoSystem); - w->writeC(song.disableSampleMacro); - w->writeC(song.brokenOutVol2); - w->writeC(song.oldArpStrategy); - - // first subsong virtual tempo - w->writeS(subSong->virtualTempoN); - w->writeS(subSong->virtualTempoD); - - // subsong list - w->writeString(subSong->name,false); - w->writeString(subSong->notes,false); - w->writeC((unsigned char)(song.subsong.size()-1)); - w->writeC(0); // reserved - w->writeC(0); - w->writeC(0); - subSongPtrSeek=w->tell(); - // subsong pointers (we'll seek here later) - for (size_t i=0; i<(song.subsong.size()-1); i++) { - w->writeI(0); - } - - // additional metadata w->writeString(song.systemName,false); w->writeString(song.category,false); w->writeString(song.nameJ,false); w->writeString(song.authorJ,false); w->writeString(song.systemNameJ,false); w->writeString(song.categoryJ,false); + w->writeF(song.tuning); + w->writeC(song.autoSystem); + + // system definition + w->writeF(song.masterVol); + w->writeS(song.chans); + w->writeS(song.systemLen); - // system output config for (int i=0; iwriteS(systemToFileFur(song.system[i])); + w->writeS(song.systemChans[i]); w->writeF(song.systemVol[i]); w->writeF(song.systemPan[i]); w->writeF(song.systemPanFR[i]); } + + // patchbay w->writeI(song.patchbay.size()); for (unsigned int i: song.patchbay) { w->writeI(i); } w->writeC(song.patchbayAuto); - // even more compat flags - w->writeC(song.brokenPortaLegato); - w->writeC(song.brokenFMOff); - w->writeC(song.preNoteNoEffect); - w->writeC(song.oldDPCM); - w->writeC(song.resetArpPhaseOnNewNote); - w->writeC(song.ceilVolumeScaling); - w->writeC(song.oldAlwaysSetVolume); - w->writeC(song.oldSampleOffset); - - // speeds of first song - w->writeC(subSong->speeds.len); - for (int i=0; i<16; i++) { - w->writeC(subSong->speeds.val[i]); - } - - // groove list - w->writeC((unsigned char)song.grooves.size()); - for (const DivGroovePattern& i: song.grooves) { - w->writeC(i.len); - for (int j=0; j<16; j++) { - w->writeC(i.val[j]); + /// song elements + // sub-songs + if (!song.subsong.empty()) { + w->writeC(0x01); + w->writeI(song.subsong.size()); + sng2PtrSeek=w->tell(); + for (size_t i=0; iwriteI(0); } } - - // asset dir pointers (we'll seek here later) - assetDirPtrSeek=w->tell(); - w->writeI(0); - w->writeI(0); - w->writeI(0); + // chip flags + if (true) { + w->writeC(0x02); + w->writeI(song.systemLen); + flagPtrSeek=w->tell(); + for (int i=0; iwriteI(0); + } + } + // asset directories + if (true) { + w->writeC(0x03); + w->writeI(3); + adirPtrSeek=w->tell(); + w->writeI(0); + w->writeI(0); + w->writeI(0); + } + // instruments + if (!song.ins.empty()) { + w->writeC(0x04); + w->writeI(song.ins.size()); + ins2PtrSeek=w->tell(); + for (size_t i=0; iwriteI(0); + } + } + // wavetables + if (!song.wave.empty()) { + w->writeC(0x05); + w->writeI(song.wave.size()); + wavePtrSeek=w->tell(); + for (size_t i=0; iwriteI(0); + } + } + // samples + if (!song.sample.empty()) { + w->writeC(0x06); + w->writeI(song.sample.size()); + smp2PtrSeek=w->tell(); + for (size_t i=0; iwriteI(0); + } + } + // patterns + if (!patsToWrite.empty()) { + w->writeC(0x07); + w->writeI(patsToWrite.size()); + patnPtrSeek=w->tell(); + for (size_t i=0; iwriteI(0); + } + } + // compat flags + if (!song.compatFlags.areDefaults()) { + w->writeC(0x08); + w->writeI(1); + cflgPtrSeek=w->tell(); + w->writeI(0); + } + // song comments + if (!song.notes.empty()) { + w->writeC(0x09); + w->writeI(1); + cmntPtrSeek=w->tell(); + w->writeI(0); + } + // groove patterns + if (!song.grooves.empty()) { + w->writeC(0x0a); + w->writeI(song.grooves.size()); + grovPtrSeek=w->tell(); + for (size_t i=0; iwriteI(0); + } + } + + w->writeC(0); blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); @@ -2499,68 +2408,10 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->seek(0,SEEK_END); /// SUBSONGS - subSongPtr.reserve(song.subsong.size() - 1); - for (subSongIndex=1; subSongIndextell()); - w->write("SONG",4); - blockStartSeek=w->tell(); - w->writeI(0); - - w->writeC(subSong->timeBase); - w->writeC(subSong->speeds.val[0]); - w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); - w->writeC(subSong->arpLen); - w->writeF(subSong->hz); - w->writeS(subSong->patLen); - w->writeS(subSong->ordersLen); - w->writeC(subSong->hilightA); - w->writeC(subSong->hilightB); - w->writeS(subSong->virtualTempoN); - w->writeS(subSong->virtualTempoD); - - w->writeString(subSong->name,false); - w->writeString(subSong->notes,false); - - for (int i=0; iordersLen; j++) { - w->writeC(subSong->orders.ord[i][j]); - } - } - - for (int i=0; iwriteC(subSong->pat[i].effectCols); - } - - for (int i=0; iwriteC( - (subSong->chanShow[i]?1:0)| - (subSong->chanShowChanOsc[i]?2:0) - ); - } - - for (int i=0; iwriteC(subSong->chanCollapse[i]); - } - - for (int i=0; iwriteString(subSong->chanName[i],false); - } - - for (int i=0; iwriteString(subSong->chanShortName[i],false); - } - - // speeds - w->writeC(subSong->speeds.len); - for (int i=0; i<16; i++) { - w->writeC(subSong->speeds.val[i]); - } - - blockEndSeek=w->tell(); - w->seek(blockStartSeek,SEEK_SET); - w->writeI(blockEndSeek-blockStartSeek-4); - w->seek(0,SEEK_END); + song.subsong[i]->putData(w,song.chans); } /// CHIP FLAGS @@ -2585,6 +2436,27 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->seek(0,SEEK_END); } + /// COMPAT FLAGS + if (!song.compatFlags.areDefaults()) { + compatFlagPtr=w->tell(); + song.compatFlags.putData(w); + } + + /// SONG COMMENTS + if (!song.notes.empty()) { + commentPtr=w->tell(); + w->write("CMNT",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeString(song.notes,false); + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); + } + /// ASSET DIRECTORIES assetDirPtr[0]=w->tell(); putAssetDirData(w,song.insDir); @@ -2724,43 +2596,71 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } /// POINTERS - w->seek(ptrSeek,SEEK_SET); - - for (int i=0; iwriteI(insPtr[i]); + // sub-songs + if (sng2PtrSeek) { + w->seek(sng2PtrSeek,SEEK_SET); + for (size_t i=0; i<=song.subsong.size(); i++) { + w->writeI(subSongPtr[i]); + } } - - // wavetable pointers - for (int i=0; iwriteI(wavePtr[i]); + // chip flags + if (flagPtrSeek) { + w->seek(flagPtrSeek,SEEK_SET); + for (int i=0; iwriteI(sysFlagsPtr[i]); + } } - - // sample pointers - for (int i=0; iwriteI(samplePtr[i]); + // asset directories + if (adirPtrSeek) { + w->seek(adirPtrSeek,SEEK_SET); + w->writeI(assetDirPtr[0]); + w->writeI(assetDirPtr[1]); + w->writeI(assetDirPtr[2]); } - - // pattern pointers - for (int i: patPtr) { - w->writeI(i); + // instruments + if (ins2PtrSeek) { + w->seek(ins2PtrSeek,SEEK_SET); + for (int i: insPtr) { + w->writeI(i); + } } - - // subsong pointers - w->seek(subSongPtrSeek,SEEK_SET); - for (size_t i=0; i<(song.subsong.size()-1); i++) { - w->writeI(subSongPtr[i]); + // wavetables + if (wavePtrSeek) { + w->seek(wavePtrSeek,SEEK_SET); + for (int i: wavePtr) { + w->writeI(i); + } } - - // flag pointers - w->seek(sysFlagsPtrSeek,SEEK_SET); - for (size_t i=0; iwriteI(sysFlagsPtr[i]); + // samples + if (smp2PtrSeek) { + w->seek(smp2PtrSeek,SEEK_SET); + for (int i: samplePtr) { + w->writeI(i); + } } - - // asset dir pointers - w->seek(assetDirPtrSeek,SEEK_SET); - for (size_t i=0; i<3; i++) { - w->writeI(assetDirPtr[i]); + // patterns + if (patnPtrSeek) { + w->seek(patnPtrSeek,SEEK_SET); + for (int i: patPtr) { + w->writeI(i); + } + } + // compat flags + if (cflgPtrSeek) { + w->seek(cflgPtrSeek,SEEK_SET); + w->writeI(compatFlagPtr); + } + // song comments + if (cmntPtrSeek) { + w->seek(cmntPtrSeek,SEEK_SET); + w->writeI(commentPtr); + } + // groove patterns + if (grovPtrSeek) { + w->seek(grovPtrSeek,SEEK_SET); + for (int i: groovePtr) { + w->writeI(i); + } } saveLock.unlock(); diff --git a/src/engine/fileOps/it.cpp b/src/engine/fileOps/it.cpp index 3ea6c2bfc..6424a8f41 100644 --- a/src/engine/fileOps/it.cpp +++ b/src/engine/fileOps/it.cpp @@ -219,10 +219,10 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { try { DivSong ds; ds.version=DIV_VERSION_IT; - ds.noSlidesOnFirstTick=true; - ds.rowResetsArpPos=true; - ds.ignoreJumpAtEnd=false; - ds.pitchSlideSpeed=8; + ds.compatFlags.noSlidesOnFirstTick=true; + ds.compatFlags.rowResetsArpPos=true; + ds.compatFlags.ignoreJumpAtEnd=false; + ds.compatFlags.pitchSlideSpeed=8; logV("Impulse Tracker module"); @@ -277,9 +277,9 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { } if (flags&8) { - ds.linearPitch=1; + ds.compatFlags.linearPitch=1; } else { - ds.linearPitch=0; + ds.compatFlags.linearPitch=0; } unsigned char globalVol=reader.readC(); @@ -1667,7 +1667,7 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { for (int i=0; i<(maxChan+32)>>5; i++) { ds.system[i]=DIV_SYSTEM_ES5506; ds.systemFlags[i].set("amigaVol",true); - if (!ds.linearPitch) { + if (!ds.compatFlags.linearPitch) { ds.systemFlags[i].set("amigaPitch",true); } } diff --git a/src/engine/fileOps/mod.cpp b/src/engine/fileOps/mod.cpp index 774a0004b..67e87bc68 100644 --- a/src/engine/fileOps/mod.cpp +++ b/src/engine/fileOps/mod.cpp @@ -41,11 +41,11 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; - ds.linearPitch=0; - ds.noSlidesOnFirstTick=true; - ds.rowResetsArpPos=true; - ds.ignoreJumpAtEnd=false; - ds.delayBehavior=0; + ds.compatFlags.linearPitch=0; + ds.compatFlags.noSlidesOnFirstTick=true; + ds.compatFlags.rowResetsArpPos=true; + ds.compatFlags.ignoreJumpAtEnd=false; + ds.compatFlags.delayBehavior=0; int insCount=31; bool bypassLimits=false; diff --git a/src/engine/fileOps/s3m.cpp b/src/engine/fileOps/s3m.cpp index 3c03ad2d9..4537b3f66 100644 --- a/src/engine/fileOps/s3m.cpp +++ b/src/engine/fileOps/s3m.cpp @@ -79,12 +79,12 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { try { DivSong ds; ds.version=DIV_VERSION_S3M; - ds.linearPitch=0; - ds.pitchMacroIsLinear=false; - ds.noSlidesOnFirstTick=true; - ds.rowResetsArpPos=true; - ds.ignoreJumpAtEnd=false; - ds.pitchSlideSpeed=12; + ds.compatFlags.linearPitch=0; + ds.compatFlags.pitchMacroIsLinear=false; + ds.compatFlags.noSlidesOnFirstTick=true; + ds.compatFlags.rowResetsArpPos=true; + ds.compatFlags.ignoreJumpAtEnd=false; + ds.compatFlags.pitchSlideSpeed=12; logV("Scream Tracker 3 module"); diff --git a/src/engine/fileOps/tfm.cpp b/src/engine/fileOps/tfm.cpp index 7ea370f1d..75100351a 100644 --- a/src/engine/fileOps/tfm.cpp +++ b/src/engine/fileOps/tfm.cpp @@ -551,7 +551,7 @@ bool DivEngine::loadTFMv1(unsigned char* file, size_t len) { ds.systemLen=1; ds.system[0]=DIV_SYSTEM_YM2612; - ds.loopModality=1; + ds.compatFlags.loopModality=1; unsigned char speed=reader.readCNoRLE(); unsigned char interleaveFactor=reader.readCNoRLE(); @@ -746,7 +746,7 @@ bool DivEngine::loadTFMv2(unsigned char* file, size_t len) { ds.systemLen=1; ds.system[0]=DIV_SYSTEM_YM2612; - ds.loopModality=1; + ds.compatFlags.loopModality=1; unsigned char magic[8]={0}; diff --git a/src/engine/fileOps/xm.cpp b/src/engine/fileOps/xm.cpp index 57e59ab3a..73c9a1697 100644 --- a/src/engine/fileOps/xm.cpp +++ b/src/engine/fileOps/xm.cpp @@ -207,12 +207,12 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { try { DivSong ds; ds.version=DIV_VERSION_XM; - //ds.linearPitch=0; - //ds.pitchMacroIsLinear=false; - ds.noSlidesOnFirstTick=true; - ds.rowResetsArpPos=true; - ds.ignoreJumpAtEnd=false; - ds.pitchSlideSpeed=8; + //ds.compatFlags.linearPitch=0; + //ds.compatFlags.pitchMacroIsLinear=false; + ds.compatFlags.noSlidesOnFirstTick=true; + ds.compatFlags.rowResetsArpPos=true; + ds.compatFlags.ignoreJumpAtEnd=false; + ds.compatFlags.pitchSlideSpeed=8; logV("Extended Module"); @@ -251,7 +251,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { unsigned short totalChans=reader.readS(); unsigned short patCount=reader.readS(); ds.insLen=(unsigned short)reader.readS(); - ds.linearPitch=(reader.readS()&1)?1:0; + ds.compatFlags.linearPitch=(reader.readS()&1)?1:0; ds.subsong[0]->speeds.val[0]=reader.readS(); ds.subsong[0]->speeds.len=1; double bpm=(unsigned short)reader.readS(); @@ -304,7 +304,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { for (int i=0; i<(totalChans+31)>>5; i++) { ds.system[i]=DIV_SYSTEM_ES5506; ds.systemFlags[i].set("amigaVol",true); - ds.systemFlags[i].set("amigaPitch",(ds.linearPitch==0)); + ds.systemFlags[i].set("amigaPitch",(ds.compatFlags.linearPitch==0)); ds.systemFlags[i].set("volScale",3900); } ds.systemLen=(totalChans+31)>>5; diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 9978c7278..f32a02592 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -43,7 +43,7 @@ void DivMacroStruct::prepare(DivInstrumentMacro& source, DivEngine* e) { mode=source.mode; type=(source.open>>1)&3; activeRelease=source.open&8; - linger=(source.macroType==DIV_MACRO_VOL && e->song.volMacroLinger); + linger=(source.macroType==DIV_MACRO_VOL && e->song.compatFlags.volMacroLinger); lfoPos=LFO_PHASE; } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 69d04193b..d3d9fb676 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -668,7 +668,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].writeVol=true; } @@ -752,9 +752,9 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index d36b40216..c3de63d51 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -260,7 +260,7 @@ void DivPlatformArcade::tick(bool sysTick) { } else { rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7)); } - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -379,7 +379,7 @@ void DivPlatformArcade::tick(bool sysTick) { for (int i=0; i<8; i++) { if (chan[i].freqChanged) { chan[i].freq=chan[i].baseFreq+chan[i].pitch-128+chan[i].pitch2; - if (!parent->song.oldArpStrategy) { + if (!parent->song.compatFlags.oldArpStrategy) { if (chan[i].fixedArp) { chan[i].freq=(chan[i].baseNoteOverride<<7)+chan[i].pitch-128+chan[i].pitch2; } else { @@ -862,7 +862,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index f32437737..a5458c42a 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -716,7 +716,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } //chan[c.chan].keyOn=true; @@ -735,7 +735,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (!(chan[c.chan].nextPSGMode.val&8)) { @@ -942,9 +942,9 @@ int DivPlatformAY8910::dispatch(DivCommand c) { 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_AY)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_PRE_NOTE: diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 653ffdc13..d7454b4d2 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -544,7 +544,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } //chan[c.chan].keyOn=true; @@ -563,7 +563,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (!(chan[c.chan].nextPSGMode.val&8)) { @@ -764,9 +764,9 @@ int DivPlatformAY8930::dispatch(DivCommand c) { 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_AY8930)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY8930)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_PRE_NOTE: diff --git a/src/engine/platform/bifurcator.cpp b/src/engine/platform/bifurcator.cpp index 200a59dec..f67f69f50 100644 --- a/src/engine/platform/bifurcator.cpp +++ b/src/engine/platform/bifurcator.cpp @@ -176,7 +176,7 @@ int DivPlatformBifurcator::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -250,9 +250,9 @@ int DivPlatformBifurcator::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_BIFURCATOR_STATE_LOAD: diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 5b615dbba..d6c07f445 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -164,7 +164,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { chan[c.chan].keyOn=true; rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -243,9 +243,9 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { 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_SCC)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/c140.cpp b/src/engine/platform/c140.cpp index 559d7225f..f6ea8893a 100644 --- a/src/engine/platform/c140.cpp +++ b/src/engine/platform/c140.cpp @@ -357,7 +357,7 @@ int DivPlatformC140::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].volChangedL=true; chan[c.chan].volChangedR=true; @@ -445,9 +445,9 @@ int DivPlatformC140::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index d2615a9be..51b1f25e2 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -574,12 +574,12 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta || parent->song.preNoteNoEffect) { + if (parent->song.compatFlags.resetMacroOnPorta || parent->song.compatFlags.preNoteNoEffect) { chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_C64)); chan[c.chan].keyOn=true; } } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_PRE_NOTE: diff --git a/src/engine/platform/dave.cpp b/src/engine/platform/dave.cpp index 9e12ef095..365f358b5 100644 --- a/src/engine/platform/dave.cpp +++ b/src/engine/platform/dave.cpp @@ -358,7 +358,7 @@ int DivPlatformDave::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].writeVol=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].insChanged=false; @@ -471,9 +471,9 @@ int DivPlatformDave::dispatch(DivCommand c) { 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_DAVE)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_DAVE)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 3160781f4..6086b1d10 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -23,7 +23,7 @@ #include #define PITCH_OFFSET ((double)(16*2048*(chanMax+1))) -#define NOTE_ES5506(c,note) ((amigaPitch && !parent->song.linearPitch)?parent->calcBaseFreq(COLOR_NTSC*16,chan[c].pcm.freqOffs,note,true):parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false)) +#define NOTE_ES5506(c,note) ((amigaPitch && !parent->song.compatFlags.linearPitch)?parent->calcBaseFreq(COLOR_NTSC*16,chan[c].pcm.freqOffs,note,true):parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false)) #define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__)); }} #define immWrite(a,...) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__));} @@ -603,7 +603,7 @@ void DivPlatformES5506::tick(bool sysTick) { const unsigned int length=s->samples-1; const unsigned int end=start+(length<<11); const unsigned int nextBank=(offES5506>>22)&3; - const double nextFreqOffs=((amigaPitch && !parent->song.linearPitch)?16:PITCH_OFFSET)*off; + const double nextFreqOffs=((amigaPitch && !parent->song.compatFlags.linearPitch)?16:PITCH_OFFSET)*off; chan[i].pcm.loopMode=loopMode; chan[i].pcm.bank=nextBank; chan[i].pcm.start=start; @@ -746,7 +746,7 @@ void DivPlatformES5506::tick(bool sysTick) { chan[i].pcm.nextPos=0; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - if (amigaPitch && !parent->song.linearPitch) { + if (amigaPitch && !parent->song.compatFlags.linearPitch) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch*16,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,2,chan[i].pitch2*16,16*COLOR_NTSC,chan[i].pcm.freqOffs); chan[i].freq=PITCH_OFFSET*(COLOR_NTSC/chan[i].freq)/(chipClock/16.0); chan[i].freq=CLAMP(chan[i].freq,0,0x1ffff); @@ -767,7 +767,7 @@ void DivPlatformES5506::tick(bool sysTick) { } chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800; chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd)<<11))&0xffffff80; - chan[i].pcm.freqOffs=((amigaPitch && !parent->song.linearPitch)?16:PITCH_OFFSET)*off; + chan[i].pcm.freqOffs=((amigaPitch && !parent->song.compatFlags.linearPitch)?16:PITCH_OFFSET)*off; unsigned int startPos=chan[i].pcm.direction?chan[i].pcm.end:chan[i].pcm.start; if (chan[i].pcm.nextPos) { const unsigned int start=chan[i].pcm.start; @@ -1212,7 +1212,7 @@ int DivPlatformES5506::dispatch(DivCommand c) { int nextFreq=chan[c.chan].baseFreq; int destFreq=NOTE_ES5506(c.chan,c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; - if (amigaPitch && !parent->song.linearPitch) { + if (amigaPitch && !parent->song.compatFlags.linearPitch) { c.value*=16; } if (destFreq>nextFreq) { @@ -1244,9 +1244,9 @@ int DivPlatformES5506::dispatch(DivCommand c) { } 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_ES5506)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_ES5506)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { chan[c.chan].nextNote=chan[c.chan].note; chan[c.chan].noteChanged.note=1; } diff --git a/src/engine/platform/esfm.cpp b/src/engine/platform/esfm.cpp index 90b2f6607..8173edf8d 100644 --- a/src/engine/platform/esfm.cpp +++ b/src/engine/platform/esfm.cpp @@ -337,7 +337,7 @@ void DivPlatformESFM::tick(bool sysTick) { if (chan[i].freqChanged) { int mul=2; int fixedBlock=chan[i].state.fm.block; - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { mul=octave(chan[i].baseFreq,fixedBlock)*2; } chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,mul,chan[i].pitch2,chipClock,CHIP_FREQBASE); @@ -569,7 +569,7 @@ int DivPlatformESFM::dispatch(DivCommand c) { bool return2=false; int mul=1; int fixedBlock=0; - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { fixedBlock=chan[c.chan].state.fm.block; mul=octave(chan[c.chan].baseFreq,fixedBlock); } @@ -586,7 +586,7 @@ int DivPlatformESFM::dispatch(DivCommand c) { return2=true; } } - if (!chan[c.chan].portaPause && !parent->song.linearPitch) { + if (!chan[c.chan].portaPause && !parent->song.compatFlags.linearPitch) { if (mul!=octave(newFreq,fixedBlock)) { chan[c.chan].portaPause=true; break; @@ -987,7 +987,7 @@ int DivPlatformESFM::dispatch(DivCommand c) { return 63; break; case DIV_CMD_PRE_PORTA: - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index 958f86fce..6865d6581 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -132,7 +132,7 @@ void DivPlatformFDS::tick(bool sysTick) { if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; if (i==3) { - if (parent->song.properNoiseLayout) { + if (parent->song.compatFlags.properNoiseLayout) { chan[i].duty&=1; } else if (chan[i].duty>1) { chan[i].duty=1; @@ -264,7 +264,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -390,9 +390,9 @@ int DivPlatformFDS::dispatch(DivCommand c) { 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_FDS)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FDS)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GET_VOLMAX: diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index ca3542b40..b1aa07b32 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -31,7 +31,7 @@ int newFreq; \ bool return2=false; \ if (_targetChan.portaPause) { \ - if (parent->song.oldOctaveBoundary) { \ + if (parent->song.compatFlags.oldOctaveBoundary) { \ if ((_targetChan.portaPauseFreq&0xf800)>(_targetChan.baseFreq&0xf800)) { \ _targetChan.baseFreq=((_targetChan.baseFreq&0x7ff)>>1)|(_targetChan.portaPauseFreq&0xf800); \ } else { \ @@ -59,7 +59,7 @@ /* what the heck! */ \ if (!_targetChan.portaPause) { \ if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { \ - if (parent->song.fbPortaPause) { \ + if (parent->song.compatFlags.fbPortaPause) { \ _targetChan.portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); \ _targetChan.portaPause=true; \ break; \ @@ -68,7 +68,7 @@ } \ } \ if ((newFreq&0x7ff)0) { \ - if (parent->song.fbPortaPause) { \ + if (parent->song.compatFlags.fbPortaPause) { \ _targetChan.portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); \ _targetChan.portaPause=true; \ break; \ diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index b45a8a8a4..7090b3f4f 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -261,7 +261,7 @@ int DivPlatformGA20::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].volumeChanged=true; } @@ -332,9 +332,9 @@ int DivPlatformGA20::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 819df4c16..1fb343170 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -224,7 +224,7 @@ void DivPlatformGB::tick(bool sysTick) { if (i!=2) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); } else if (!chan[i].softEnv) { - if (parent->song.waveDutyIsVol) { + if (parent->song.compatFlags.waveDutyIsVol) { rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[(chan[i].std.duty.val&3)<<2]); } } @@ -439,7 +439,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].envVol; } } else if (chan[c.chan].softEnv && c.chan!=2) { - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].envVol=chan[c.chan].outVol; } @@ -478,7 +478,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].soundLen=ins->gb.soundLen; chan[c.chan].vol=chan[c.chan].envVol; chan[c.chan].outVol=chan[c.chan].vol; - if (parent->song.gbInsAffectsEnvelope) { + if (parent->song.compatFlags.gbInsAffectsEnvelope) { rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); } } @@ -567,9 +567,9 @@ int DivPlatformGB::dispatch(DivCommand c) { 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_GB)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_GB)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GB_SWEEP_DIR: diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp index 2a848cc09..bbc592a69 100644 --- a/src/engine/platform/gbadma.cpp +++ b/src/engine/platform/gbadma.cpp @@ -237,7 +237,7 @@ int DivPlatformGBADMA::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].envVol=2; } if (chan[c.chan].useWave) { @@ -321,7 +321,7 @@ int DivPlatformGBADMA::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } chan[c.chan].inPorta=c.value; break; diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index 89af91c62..75070ebb0 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -417,7 +417,7 @@ int DivPlatformGBAMinMod::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -503,9 +503,9 @@ int DivPlatformGBAMinMod::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index d7edd37a2..0d607319c 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -68,7 +68,7 @@ void DivPlatformGenesis::processDAC(int iRate) { if (chan[i].dacSample!=-1) { DivSample* s=parent->getSample(chan[i].dacSample); if (!isMuted[i] && s->samples>0 && chan[i].dacPossamples) { - if (parent->song.noOPN2Vol) { + if (parent->song.compatFlags.noOPN2Vol) { chan[i].dacOutput=s->data8[chan[i].dacDirection?(s->samples-chan[i].dacPos-1):chan[i].dacPos]; } else { chan[i].dacOutput=(s->data8[chan[i].dacDirection?(s->samples-chan[i].dacPos-1):chan[i].dacPos]*dacVolTable[chan[i].outVol])>>7; @@ -110,7 +110,7 @@ void DivPlatformGenesis::processDAC(int iRate) { if (s->samples>0 && chan[5].dacPossamples) { if (!isMuted[5]) { int sample; - if (parent->song.noOPN2Vol) { + if (parent->song.compatFlags.noOPN2Vol) { sample=s->data8[chan[5].dacDirection?(s->samples-chan[5].dacPos-1):chan[5].dacPos]; } else { sample=(s->data8[chan[5].dacDirection?(s->samples-chan[5].dacPos-1):chan[5].dacPos]*dacVolTable[chan[5].outVol])>>7; @@ -122,7 +122,7 @@ void DivPlatformGenesis::processDAC(int iRate) { chan[5].dacPos=s->loopStart; } else if (chan[5].dacPos>=s->samples) { chan[5].dacSample=-1; - if (parent->song.brokenDACMode) { + if (parent->song.compatFlags.brokenDACMode) { rWrite(0x2b,0); } } @@ -768,7 +768,7 @@ void DivPlatformGenesis::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -863,7 +863,7 @@ void DivPlatformGenesis::tick(bool sysTick) { for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { - if (i==0x2b && pendingWrites[i]!=0 && !parent->song.brokenDACMode) { + if (i==0x2b && pendingWrites[i]!=0 && !parent->song.compatFlags.brokenDACMode) { if (chan[5].keyOn) chan[5].keyOn=false; chan[5].keyOff=true; } @@ -894,7 +894,7 @@ void DivPlatformGenesis::tick(bool sysTick) { for (int i=0; isong.linearPitch) { + if (parent->song.compatFlags.linearPitch) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block); } else { int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); @@ -1131,7 +1131,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan>=5 && c.chansong.brokenDACMode) { + if (parent->song.compatFlags.brokenDACMode) { rWrite(0x2b,0); if (chan[c.chan].dacMode) break; } @@ -1200,7 +1200,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { int destFreq=NOTE_FREQUENCY(c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 02fa2be79..034a75986 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -153,7 +153,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; @@ -169,7 +169,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>opChan[ch].baseFreq) { @@ -197,7 +197,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } case DIV_CMD_SAMPLE_MODE: { // not ignored actually! - if (!parent->song.ignoreDACModeOutsideIntendedChannel) { + if (!parent->song.compatFlags.ignoreDACModeOutsideIntendedChannel) { chan[5].dacMode=c.value; rWrite(0x2b,c.value<<7); } @@ -547,7 +547,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { if (opChan[i].std.alg.had) { chan[extChanOffs].state.alg=opChan[i].std.alg.val; rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[j]; DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[j]; if (isOpMuted[j] || !op.enable) { @@ -578,7 +578,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { if (opChan[i].std.panL.had) { opChan[i].pan=opChan[i].std.panL.val&3; - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int j=0; j<4; j++) { if (i==j) continue; opChan[j].pan=opChan[i].pan; @@ -648,7 +648,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { unsigned char hardResetMask=0; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,2,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block); } else { int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,2,opChan[i].pitch2); diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index 0fa8aaace..e67485214 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -324,7 +324,7 @@ int DivPlatformK007232::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; if (!isMuted[c.chan]) { chan[c.chan].volumeChanged=true; @@ -405,9 +405,9 @@ int DivPlatformK007232::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/k053260.cpp b/src/engine/platform/k053260.cpp index 4b1b4bc39..6206aaac5 100644 --- a/src/engine/platform/k053260.cpp +++ b/src/engine/platform/k053260.cpp @@ -255,7 +255,7 @@ int DivPlatformK053260::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -331,9 +331,9 @@ int DivPlatformK053260::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index d2e99ceb3..d25a94107 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -361,7 +361,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { chan[c.chan].active=true; WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127))); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -429,7 +429,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { } } chan[c.chan].freqChanged=true; - if (chan[c.chan].pcm && parent->song.linearPitch) { + if (chan[c.chan].pcm && parent->song.compatFlags.linearPitch) { chan[c.chan].sampleBaseFreq=chan[c.chan].baseFreq; } if (return2) { @@ -451,9 +451,9 @@ int DivPlatformLynx::dispatch(DivCommand c) { } 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_MIKEY)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 5f66585e2..c85fc9854 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -229,7 +229,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } rWrite(0x5000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); @@ -313,9 +313,9 @@ int DivPlatformMMC5::dispatch(DivCommand c) { 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_STD)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/msm5232.cpp b/src/engine/platform/msm5232.cpp index d0325ae60..31648f6fe 100644 --- a/src/engine/platform/msm5232.cpp +++ b/src/engine/platform/msm5232.cpp @@ -157,7 +157,7 @@ void DivPlatformMSM5232::tick(bool sysTick) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); chan[i].freq=chan[i].baseFreq+chan[i].pitch+chan[i].pitch2-(12<<7); - if (!parent->song.oldArpStrategy) { + if (!parent->song.compatFlags.oldArpStrategy) { if (chan[i].fixedArp) { chan[i].freq=(chan[i].baseNoteOverride<<7)+(chan[i].pitch)-(12<<7); } else { @@ -206,7 +206,7 @@ int DivPlatformMSM5232::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].insChanged=false; @@ -249,13 +249,13 @@ int DivPlatformMSM5232::dispatch(DivCommand c) { int destFreq=NOTE_LINEAR(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*parent->song.pitchSlideSpeed; + chan[c.chan].baseFreq+=c.value*parent->song.compatFlags.pitchSlideSpeed; if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*parent->song.pitchSlideSpeed; + chan[c.chan].baseFreq-=c.value*parent->song.compatFlags.pitchSlideSpeed; if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -291,9 +291,9 @@ int DivPlatformMSM5232::dispatch(DivCommand c) { 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_PCE)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index 8fbcccaad..f5e4e8b56 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -101,7 +101,7 @@ void DivPlatformMSM6258::acquire(short** buf, size_t len) { void DivPlatformMSM6258::tick(bool sysTick) { for (int i=0; i<1; i++) { - if (!parent->song.disableSampleMacro) { + if (!parent->song.compatFlags.disableSampleMacro) { chan[i].std.next(); if (chan[i].std.duty.had) { if (rateSel!=(chan[i].std.duty.val&3)) { diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index c084802f1..d4d1fe19d 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -122,7 +122,7 @@ void DivPlatformMSM6295::acquire(short** buf, size_t len) { void DivPlatformMSM6295::tick(bool sysTick) { for (int i=0; i<4; i++) { - if (!parent->song.disableSampleMacro) { + if (!parent->song.compatFlags.disableSampleMacro) { chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].std.vol.val,chan[i].vol,8); diff --git a/src/engine/platform/multipcm.cpp b/src/engine/platform/multipcm.cpp index 7e1a0ce29..3a91d6c4b 100644 --- a/src/engine/platform/multipcm.cpp +++ b/src/engine/platform/multipcm.cpp @@ -389,9 +389,9 @@ int DivPlatformMultiPCM::dispatch(DivCommand c) { return 127; 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_MULTIPCM)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index b10a28eb0..2476e7e53 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -386,13 +386,13 @@ int DivPlatformN163::dispatch(DivCommand c) { int destFreq=destFreqD; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:16); + chan[c.chan].baseFreq+=c.value*((parent->song.compatFlags.linearPitch)?1:16); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:16); + chan[c.chan].baseFreq-=c.value*((parent->song.compatFlags.linearPitch)?1:16); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -456,12 +456,12 @@ int DivPlatformN163::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) { + if (parent->song.compatFlags.resetMacroOnPorta) { chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_N163)); chan[c.chan].keyOn=true; } } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GET_VOLMAX: diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 6fea2e923..95c2583dd 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -353,7 +353,7 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -406,13 +406,13 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:8); + chan[c.chan].baseFreq+=c.value*((parent->song.compatFlags.linearPitch)?1:8); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:8); + chan[c.chan].baseFreq-=c.value*((parent->song.compatFlags.linearPitch)?1:8); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -440,9 +440,9 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) { 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_PCE)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GET_VOLMAX: diff --git a/src/engine/platform/nds.cpp b/src/engine/platform/nds.cpp index 7fe0a3cd1..fa77f2296 100644 --- a/src/engine/platform/nds.cpp +++ b/src/engine/platform/nds.cpp @@ -334,7 +334,7 @@ int DivPlatformNDS::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -422,9 +422,9 @@ int DivPlatformNDS::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index d56b57288..5330548d0 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -326,7 +326,7 @@ void DivPlatformNES::tick(bool sysTick) { if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; if (i==3) { - if (parent->song.properNoiseLayout) { + if (parent->song.compatFlags.properNoiseLayout) { chan[i].duty&=1; } else if (chan[i].duty>1) { chan[i].duty=1; @@ -372,7 +372,7 @@ void DivPlatformNES::tick(bool sysTick) { ntPos+=chan[i].pitch2; if (isE) { chan[i].freq=31-(ntPos&31); - } else if (parent->song.properNoiseLayout) { + } else if (parent->song.compatFlags.properNoiseLayout) { chan[i].freq=15-(ntPos&15); } else { if (ntPos<0) ntPos=0; @@ -578,12 +578,12 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_NES)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (c.chan==2) { rWrite(0x4000+c.chan*4,linearCount); - } else if (!parent->song.brokenOutVol2) { + } else if (!parent->song.compatFlags.brokenOutVol2) { rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); } if (resetSweep && c.chan<2) { @@ -752,9 +752,9 @@ int DivPlatformNES::dispatch(DivCommand c) { 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_NES)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_NES)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b2bcf042f..ff8c69799 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1515,7 +1515,7 @@ void DivPlatformOPL::tick(bool sysTick) { if (chan[i].freqChanged) { int mul=2; int fixedBlock=chan[i].state.block; - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { mul=octave(chan[i].baseFreq,fixedBlock)*2; } chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,mul,chan[i].pitch2,chipClock,CHIP_FREQBASE); @@ -2082,7 +2082,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { bool return2=false; int mul=1; int fixedBlock=0; - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { fixedBlock=chan[c.chan].state.block; mul=octave(chan[c.chan].baseFreq,fixedBlock); } @@ -2099,7 +2099,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { return2=true; } } - if (!chan[c.chan].portaPause && !parent->song.linearPitch) { + if (!chan[c.chan].portaPause && !parent->song.compatFlags.linearPitch) { if (mul!=octave(newFreq,fixedBlock)) { chan[c.chan].portaPause=true; break; @@ -2583,9 +2583,9 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (PCM_CHECK(c.chan) && chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { chan[c.chan].baseFreq=(PCM_CHECK(c.chan))?NOTE_PCM(chan[c.chan].note): ((c.chan==adpcmChan)?(NOTE_ADPCMB(chan[c.chan].note)):(NOTE_FREQUENCY(chan[c.chan].note))); } diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index e29d43e08..b64ccbbf5 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -341,7 +341,7 @@ void DivPlatformOPLL::tick(bool sysTick) { if (chan[i].freqChanged) { int mul=2; int fixedBlock=chan[i].state.block; - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { mul=octave(chan[i].baseFreq,fixedBlock)*2; } chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,mul,chan[i].pitch2,chipClock,CHIP_FREQBASE); @@ -684,7 +684,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { bool return2=false; int mul=1; int fixedBlock=0; - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { fixedBlock=chan[c.chan].state.block; mul=octave(chan[c.chan].baseFreq,fixedBlock); } @@ -956,7 +956,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (c.chan>=9 && !properDrums) return 0; - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_PRE_NOTE: diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 3023aa1fc..c410c3d09 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -103,7 +103,7 @@ void DivPlatformPCE::acquireDirect(blip_buffer_t** bb, size_t len) { signed char dacData=((signed char)((unsigned char)s->data8[chan[i].dacPos]^0x80))>>3; chan[i].dacOut=CLAMP(dacData,-16,15); if (!isMuted[i]) { - chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].outVol)); + chWrite(i,0x04,parent->song.compatFlags.disableSampleMacro?0xdf:(0xc0|chan[i].outVol)); chWrite(i,0x06,chan[i].dacOut&0x1f); } else { chWrite(i,0x04,0xc0); @@ -233,7 +233,7 @@ void DivPlatformPCE::tick(bool sysTick) { chan[i].dacPos=0; } chan[i].dacPeriod=0; - chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol)); + chWrite(i,0x04,parent->song.compatFlags.disableSampleMacro?0xdf:(0xc0|chan[i].vol)); addWrite(0xffff0000+(i<<8),chan[i].dacSample); chan[i].keyOn=true; } @@ -269,11 +269,11 @@ void DivPlatformPCE::tick(bool sysTick) { if (i>=4) { int noiseSeek=(chan[i].fixedArp?chan[i].baseNoteOverride:(chan[i].note+chan[i].arpOff))+chan[i].pitch2; - if (!parent->song.properNoiseLayout && noiseSeek<0) noiseSeek=0; + if (!parent->song.compatFlags.properNoiseLayout && noiseSeek<0) noiseSeek=0; if (!NEW_ARP_STRAT) { noiseSeek=chan[i].noiseSeek; } - chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.compatFlags.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } if (chan[i].keyOn) { //rWrite(16+i*5,0x80); @@ -324,7 +324,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; } else { if (dumpWrites) { - chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); + chWrite(c.chan,0x04,parent->song.compatFlags.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); } } @@ -341,7 +341,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -359,7 +359,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chWrite(c.chan,0x04,0x80|chan[c.chan].vol); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -476,9 +476,9 @@ int DivPlatformPCE::dispatch(DivCommand c) { 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_PCE)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -503,7 +503,7 @@ void DivPlatformPCE::muteChannel(int ch, bool mute) { isMuted[ch]=mute; chWrite(ch,0x05,isMuted[ch]?0:chan[ch].pan); if (!isMuted[ch] && (chan[ch].pcm && chan[ch].dacSample!=-1)) { - chWrite(ch,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol)); + chWrite(ch,0x04,parent->song.compatFlags.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol)); chWrite(ch,0x06,chan[ch].dacOut&0x1f); } } diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 42b9babd9..817ec5a5d 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -365,7 +365,7 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) { chan[0].active=true; chan[0].keyOn=true; chan[0].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[0].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[0].std.vol.will) { chan[0].envVol=64; } if (chan[0].useWave) { @@ -446,7 +446,7 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) { } case DIV_CMD_PRE_PORTA: if (chan[0].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[0].macroInit(parent->getIns(chan[0].ins,DIV_INS_AMIGA)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[0].macroInit(parent->getIns(chan[0].ins,DIV_INS_AMIGA)); } chan[0].inPorta=c.value; break; diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index c35ba724d..25a53196f 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -470,7 +470,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -537,9 +537,9 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { 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_BEEPER)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index fc78165d7..d299d0d62 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -158,7 +158,7 @@ int DivPlatformPET::dispatch(DivCommand c) { chan[0].active=true; chan[0].keyOn=true; chan[0].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[0].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[0].std.vol.will) { chan[0].outVol=chan[0].vol; } break; @@ -227,9 +227,9 @@ int DivPlatformPET::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[0].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[0].macroInit(parent->getIns(chan[0].ins,DIV_INS_PET)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[0].macroInit(parent->getIns(chan[0].ins,DIV_INS_PET)); } - if (!chan[0].inPorta && c.value && !parent->song.brokenPortaArp && chan[0].std.arp.will && !NEW_ARP_STRAT) chan[0].baseFreq=NOTE_PERIODIC(chan[0].note); + if (!chan[0].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[0].std.arp.will && !NEW_ARP_STRAT) chan[0].baseFreq=NOTE_PERIODIC(chan[0].note); chan[0].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/pokemini.cpp b/src/engine/platform/pokemini.cpp index 9e29277e4..5f4516e41 100644 --- a/src/engine/platform/pokemini.cpp +++ b/src/engine/platform/pokemini.cpp @@ -175,7 +175,7 @@ int DivPlatformPokeMini::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -241,9 +241,9 @@ int DivPlatformPokeMini::dispatch(DivCommand c) { 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_POKEMINI)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index dbb0d7fd8..35a362247 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -184,7 +184,7 @@ void DivPlatformPOKEY::tick(bool sysTick) { for (int i=0; i<4; i++) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,parent->song.linearPitch?chan[i].pitch:0,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,parent->song.linearPitch?chan[i].pitch2:0,chipClock,CHIP_DIVIDER); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,parent->song.compatFlags.linearPitch?chan[i].pitch:0,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,parent->song.compatFlags.linearPitch?chan[i].pitch2:0,chipClock,CHIP_DIVIDER); if ((i==0 && !(audctl&64)) || (i==2 && !(audctl&32)) || i==1 || i==3) { chan[i].freq/=7; @@ -223,7 +223,7 @@ void DivPlatformPOKEY::tick(bool sysTick) { } // non-linear pitch - if (!parent->song.linearPitch) { + if (!parent->song.compatFlags.linearPitch) { chan[i].freq-=chan[i].pitch; } @@ -297,7 +297,7 @@ int DivPlatformPOKEY::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].ctlChanged=true; } @@ -383,9 +383,9 @@ int DivPlatformPOKEY::dispatch(DivCommand c) { 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_POKEY)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEY)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/pong.cpp b/src/engine/platform/pong.cpp index 19c478711..ce855c22a 100644 --- a/src/engine/platform/pong.cpp +++ b/src/engine/platform/pong.cpp @@ -101,7 +101,7 @@ int DivPlatformPong::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -168,9 +168,9 @@ int DivPlatformPong::dispatch(DivCommand c) { 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_BEEPER)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/powernoise.cpp b/src/engine/platform/powernoise.cpp index c7870e31e..8051ed2ce 100644 --- a/src/engine/platform/powernoise.cpp +++ b/src/engine/platform/powernoise.cpp @@ -288,7 +288,7 @@ int DivPlatformPowerNoise::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].keyOn=true; @@ -372,9 +372,9 @@ int DivPlatformPowerNoise::dispatch(DivCommand c) { } 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_POWERNOISE)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POWERNOISE)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); } chan[c.chan].inPorta=c.value; diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index 11cab5457..633260635 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -182,9 +182,9 @@ int DivPlatformPV1000::dispatch(DivCommand c) { 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_PV1000)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PV1000)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 6b8b4089a..213de85f1 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -488,7 +488,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].keyOff=false; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; if (chan[c.chan].isNewQSound) { chan[c.chan].resVol=(chan[c.chan].outVol*16383)/255; @@ -594,9 +594,9 @@ int DivPlatformQSound::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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=QS_NOTE_FREQUENCY(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 831d47e1c..45a16255a 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -210,7 +210,7 @@ int DivPlatformRF5C68::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -284,9 +284,9 @@ int DivPlatformRF5C68::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 624bde5bf..5283a5176 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -211,7 +211,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (isMuted[c.chan]) { @@ -261,13 +261,13 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:(8-chan[c.chan].freqH)); + chan[c.chan].baseFreq+=c.value*((parent->song.compatFlags.linearPitch)?1:(8-chan[c.chan].freqH)); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:(8-chan[c.chan].freqH)); + chan[c.chan].baseFreq-=c.value*((parent->song.compatFlags.linearPitch)?1:(8-chan[c.chan].freqH)); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -318,9 +318,9 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { 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_SAA1099)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_PRE_NOTE: diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 56e160181..f500bf4a5 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -172,7 +172,7 @@ int DivPlatformSCC::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (!isMuted[c.chan]) { @@ -253,9 +253,9 @@ int DivPlatformSCC::dispatch(DivCommand c) { 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_SCC)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/scvtone.cpp b/src/engine/platform/scvtone.cpp index e5dfe9a43..2d78d3a9d 100644 --- a/src/engine/platform/scvtone.cpp +++ b/src/engine/platform/scvtone.cpp @@ -100,7 +100,7 @@ void DivPlatformSCV::tick(bool sysTick) { } else { chan[i].freq=(chan[i].baseFreq+chan[i].pitch+chan[i].pitch2+143); } - if (!parent->song.oldArpStrategy) { + if (!parent->song.compatFlags.oldArpStrategy) { if (chan[i].fixedArp) { chan[i].freq=(chan[i].baseNoteOverride)+chan[i].pitch+chan[i].pitch2; } else { @@ -186,7 +186,7 @@ int DivPlatformSCV::dispatch(DivCommand c) { chan[c.chan].keyOn=true; //chwrite(c.chan,0x04,0x80|chan[c.chan].vol); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].insChanged=false; @@ -267,9 +267,9 @@ int DivPlatformSCV::dispatch(DivCommand c) { } 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_UPD1771C)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_UPD1771C)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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; diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index c659b8f2a..877d76f4d 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -66,7 +66,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) { for (int i=0; i<16; i++) { chan[i].std.next(); - if (parent->song.newSegaPCM) { + if (parent->song.compatFlags.newSegaPCM) { 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].chVolL=(chan[i].outVol*chan[i].chPanL)/127; @@ -89,13 +89,13 @@ void DivPlatformSegaPCM::tick(bool sysTick) { chan[i].pcm.freq=-1; } - if (parent->song.newSegaPCM) if (chan[i].std.panL.had) { + if (parent->song.compatFlags.newSegaPCM) if (chan[i].std.panL.had) { chan[i].chPanL=chan[i].std.panL.val&127; chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127; rWrite(2+(i<<3),chan[i].chVolL); } - if (parent->song.newSegaPCM) if (chan[i].std.panR.had) { + if (parent->song.compatFlags.newSegaPCM) if (chan[i].std.panR.had) { chan[i].chPanR=chan[i].std.panR.val&127; chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127; rWrite(3+(i<<3),chan[i].chVolR); @@ -120,7 +120,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=chan[i].baseFreq+(chan[i].pitch)-128+(oldSlides?0:chan[i].pitch2); - if (!parent->song.oldArpStrategy) { + if (!parent->song.compatFlags.oldArpStrategy) { if (chan[i].fixedArp) { chan[i].freq=(chan[i].baseNoteOverride<<7)+chan[i].pitch-128+(chan[i].pitch2<<(oldSlides?1:0)); } else { @@ -204,10 +204,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].pcm.freq=-1; } chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; - if (parent->song.newSegaPCM) { + if (parent->song.compatFlags.newSegaPCM) { chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127; chan[c.chan].chVolR=(chan[c.chan].outVol*chan[c.chan].chPanR)/127; rWrite(2+(c.chan<<3),chan[c.chan].chVolL); @@ -240,7 +240,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (parent->song.newSegaPCM) { + if (parent->song.compatFlags.newSegaPCM) { chan[c.chan].chVolL=(c.value*chan[c.chan].chPanL)/127; chan[c.chan].chVolR=(c.value*chan[c.chan].chPanR)/127; } else { @@ -262,7 +262,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - if (parent->song.newSegaPCM) { + if (parent->song.compatFlags.newSegaPCM) { chan[c.chan].chPanL=c.value>>1; chan[c.chan].chPanR=c.value2>>1; chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127; @@ -284,7 +284,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { case DIV_CMD_NOTE_PORTA: { int destFreq=((c.value2+chan[c.chan].sampleNoteDelta)<<7); int newFreq; - int mul=(oldSlides || !parent->song.linearPitch)?8:1; + int mul=(oldSlides || !parent->song.compatFlags.linearPitch)?8:1; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { newFreq=chan[c.chan].baseFreq+c.value*mul; @@ -334,7 +334,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<7); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<7); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: diff --git a/src/engine/platform/sid2.cpp b/src/engine/platform/sid2.cpp index 010fec0cb..8e6339a9c 100644 --- a/src/engine/platform/sid2.cpp +++ b/src/engine/platform/sid2.cpp @@ -445,12 +445,12 @@ int DivPlatformSID2::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta || parent->song.preNoteNoEffect) { + if (parent->song.compatFlags.resetMacroOnPorta || parent->song.compatFlags.preNoteNoEffect) { chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SID2)); chan[c.chan].keyOn=true; } } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GET_VOLMAX: diff --git a/src/engine/platform/sid3.cpp b/src/engine/platform/sid3.cpp index 284488428..9dd44849c 100644 --- a/src/engine/platform/sid3.cpp +++ b/src/engine/platform/sid3.cpp @@ -845,7 +845,7 @@ int DivPlatformSID3::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } //chan[c.chan].keyOn=true; @@ -997,12 +997,12 @@ int DivPlatformSID3::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta || parent->song.preNoteNoEffect) { + if (parent->song.compatFlags.resetMacroOnPorta || parent->song.compatFlags.preNoteNoEffect) { chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SID3)); chan[c.chan].keyOn=true; } } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_PANNING: { diff --git a/src/engine/platform/sm8521.cpp b/src/engine/platform/sm8521.cpp index 5c1153aab..2801ca6b2 100644 --- a/src/engine/platform/sm8521.cpp +++ b/src/engine/platform/sm8521.cpp @@ -208,7 +208,7 @@ int DivPlatformSM8521::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -267,13 +267,13 @@ int DivPlatformSM8521::dispatch(DivCommand c) { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:8); + chan[c.chan].baseFreq+=c.value*((parent->song.compatFlags.linearPitch)?1:8); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:8); + chan[c.chan].baseFreq-=c.value*((parent->song.compatFlags.linearPitch)?1:8); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -293,9 +293,9 @@ int DivPlatformSM8521::dispatch(DivCommand c) { 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_SM8521)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SM8521)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 636cf6560..1b6b6d543 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -190,7 +190,7 @@ void DivPlatformSMS::acquireDirect(blip_buffer_t** bb, size_t len) { double DivPlatformSMS::NOTE_SN(int ch, int note) { double CHIP_DIVIDER=toneDivider; if (ch==3) CHIP_DIVIDER=noiseDivider; - if (parent->song.linearPitch || !easyNoise) { + if (parent->song.compatFlags.linearPitch || !easyNoise) { return NOTE_PERIODIC(note); } int easyStartingPeriod=16; @@ -210,7 +210,7 @@ int DivPlatformSMS::snCalcFreq(int ch) { if (chan[ch].fixedArp) { curFreq=chan[ch].baseNoteOverride<<7; } - if (parent->song.linearPitch && easyNoise && curFreq>easyThreshold) { + if (parent->song.compatFlags.linearPitch && easyNoise && curFreq>easyThreshold) { int ret=(((easyStartingPeriod<<7))-(curFreq-(easyThreshold)))>>7; if (ret<0) ret=0; return ret; @@ -242,7 +242,7 @@ void DivPlatformSMS::tick(bool sysTick) { } if (i==3) { if (chan[i].std.duty.had) { - if (chan[i].std.duty.val!=snNoiseMode || parent->song.snDutyReset) { + if (chan[i].std.duty.val!=snNoiseMode || parent->song.compatFlags.snDutyReset) { snNoiseMode=chan[i].std.duty.val; if (chan[i].std.duty.val<2) { chan[3].freqChanged=false; @@ -277,7 +277,7 @@ void DivPlatformSMS::tick(bool sysTick) { if (chan[i].freqChanged) { chan[i].freq=snCalcFreq(i); if (chan[i].freq>1023) chan[i].freq=1023; - if (parent->song.snNoLowPeriods) { + if (parent->song.compatFlags.snNoLowPeriods) { if (chan[i].freq<8) chan[i].freq=1; } else { if (chan[i].freq<0) chan[i].freq=0; @@ -297,7 +297,7 @@ void DivPlatformSMS::tick(bool sysTick) { chan[3].freq=snCalcFreq(3); //parent->calcFreq(chan[3].baseFreq,chan[3].pitch,chan[3].fixedArp?chan[3].baseNoteOverride:chan[3].arpOff,chan[3].fixedArp,true,0,chan[3].pitch2,chipClock,noiseDivider); if (chan[3].freq>1023) chan[3].freq=1023; - if (parent->song.snNoLowPeriods) { + if (parent->song.compatFlags.snNoLowPeriods) { if (chan[3].actualNote>0x5d) chan[3].freq=0x01; } if (chan[3].freq<0) chan[3].freq=0; @@ -359,13 +359,13 @@ int DivPlatformSMS::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOff=false; - //if (!parent->song.brokenOutVol2) { + //if (!parent->song.compatFlags.brokenOutVol2) { chan[c.chan].writeVol=true; chan[c.chan].outVol=chan[c.chan].vol; //rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); //} chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -451,9 +451,9 @@ int DivPlatformSMS::dispatch(DivCommand c) { 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_STD)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_SN(c.chan,chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_SN(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 6d7c90629..be76b48b3 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -390,7 +390,7 @@ int DivPlatformSNES::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); // this is the fix. it needs testing. - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { if (chan[c.chan].outVol!=chan[c.chan].vol) chan[c.chan].shallWriteVol=true; chan[c.chan].outVol=chan[c.chan].vol; } @@ -482,7 +482,7 @@ int DivPlatformSNES::dispatch(DivCommand c) { } 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_SNES)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SNES)); } chan[c.chan].inPorta=c.value; break; diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 84c84f6d1..5195dabbd 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -327,7 +327,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { chan[c.chan].hwSeqDelay=0; chWrite(c.chan,0x02,chan[c.chan].vol); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].insChanged=false; @@ -484,13 +484,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { int destFreq=NOTE_SU(c.chan,c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:(1+(chan[c.chan].baseFreq>>9))); + chan[c.chan].baseFreq+=c.value*((parent->song.compatFlags.linearPitch)?1:(1+(chan[c.chan].baseFreq>>9))); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:(1+(chan[c.chan].baseFreq>>9))); + chan[c.chan].baseFreq-=c.value*((parent->song.compatFlags.linearPitch)?1:(1+(chan[c.chan].baseFreq>>9))); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -519,9 +519,9 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { 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_SU)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SU)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_SU(c.chan,chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_SU(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_C64_PW_SLIDE: diff --git a/src/engine/platform/supervision.cpp b/src/engine/platform/supervision.cpp index 0a391f1ca..57ab61d34 100644 --- a/src/engine/platform/supervision.cpp +++ b/src/engine/platform/supervision.cpp @@ -297,7 +297,7 @@ int DivPlatformSupervision::dispatch(DivCommand c) { chan[c.chan].keyOn=true; //chwrite(c.chan,0x04,0x80|chan[c.chan].vol); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].insChanged=false; @@ -384,9 +384,9 @@ int DivPlatformSupervision::dispatch(DivCommand c) { 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_SUPERVISION)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SUPERVISION)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index ca5c0906c..19ba44719 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -405,7 +405,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -538,9 +538,9 @@ int DivPlatformSwan::dispatch(DivCommand c) { 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_SWAN)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SWAN)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/t6w28.cpp b/src/engine/platform/t6w28.cpp index a02e47681..44276f82d 100644 --- a/src/engine/platform/t6w28.cpp +++ b/src/engine/platform/t6w28.cpp @@ -95,7 +95,7 @@ void DivPlatformT6W28::writeOutVol(int ch) { double DivPlatformT6W28::NOTE_SN(int ch, int note) { double CHIP_DIVIDER=16; if (ch==3) CHIP_DIVIDER=15; - if (parent->song.linearPitch || !easyNoise) { + if (parent->song.compatFlags.linearPitch || !easyNoise) { return NOTE_PERIODIC(note); } if (note>107) { @@ -105,7 +105,7 @@ double DivPlatformT6W28::NOTE_SN(int ch, int note) { } int DivPlatformT6W28::snCalcFreq(int ch) { - if (parent->song.linearPitch && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(107<<7)) { + if (parent->song.compatFlags.linearPitch && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(107<<7)) { int ret=(((13<<7)+0x40)-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(107<<7)))>>7; if (ret<0) ret=0; return ret; @@ -187,7 +187,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; writeOutVol(c.chan); } @@ -270,9 +270,9 @@ int DivPlatformT6W28::dispatch(DivCommand c) { 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_T6W28)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_T6W28)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_SN(c.chan,chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_SN(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/ted.cpp b/src/engine/platform/ted.cpp index 46c38ebd6..d505c44a0 100644 --- a/src/engine/platform/ted.cpp +++ b/src/engine/platform/ted.cpp @@ -149,7 +149,7 @@ int DivPlatformTED::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } chan[c.chan].insChanged=false; @@ -230,9 +230,9 @@ int DivPlatformTED::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_TED)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_TED)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index e8e3c0d3d..45e3fccc0 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -295,7 +295,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { chan[c.chan].keyOn=true; rWrite(0x15+c.chan,chan[c.chan].shape); chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].insChanged) { @@ -395,7 +395,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { 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_TIA)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_TIA)); } chan[c.chan].inPorta=c.value; break; diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 658eface1..6cf6906a1 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -229,7 +229,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0x40:0)|(chan[i].chVolR<<7)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -321,7 +321,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { } // fixed pitch - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { bool freqChangeOp=false; if (op.egt) { @@ -416,7 +416,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { for (int i=0; i<8; i++) { if (chan[i].freqChanged) { chan[i].freq=chan[i].baseFreq+chan[i].pitch-128+chan[i].pitch2; - if (!parent->song.oldArpStrategy) { + if (!parent->song.compatFlags.oldArpStrategy) { if (chan[i].fixedArp) { chan[i].freq=(chan[i].baseNoteOverride<<7)+chan[i].pitch-128+chan[i].pitch2; } else { @@ -1006,7 +1006,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index ae75f0044..a0be3efb5 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -297,7 +297,7 @@ int DivPlatformVB::dispatch(DivCommand c) { } chWrite(4,0x00,0x80); } - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; writeEnv(c.chan); } @@ -445,9 +445,9 @@ int DivPlatformVB::dispatch(DivCommand c) { 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_PCE)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 29f6df07d..62ea0d6dd 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -347,7 +347,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA)); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -421,7 +421,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { 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_VERA)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA)); } if (!chan[c.chan].inPorta && c.value && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=calcNoteFreq(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 0a3e763a2..21f8f1e6b 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -247,9 +247,9 @@ int DivPlatformVIC20::dispatch(DivCommand c) { 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_VIC)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VIC)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 18a7be76a..62e2bd658 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -297,7 +297,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } //chan[c.chan].keyOn=true; @@ -418,9 +418,9 @@ int DivPlatformVRC6::dispatch(DivCommand c) { 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_VRC6)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VRC6)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 36f0d26ee..b8615f30f 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -614,7 +614,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].wave<0) { @@ -729,9 +729,9 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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_X1_010)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_X1_010)); } - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_FREQ: diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 98fd77a36..562043820 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -551,7 +551,7 @@ void DivPlatformYM2203::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -661,7 +661,7 @@ void DivPlatformYM2203::tick(bool sysTick) { for (int i=0; i<3; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block); } else { int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2); @@ -797,7 +797,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); + if (parent->song.compatFlags.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -867,7 +867,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } break; } - if (c.chan>(psgChanOffs-1) || parent->song.linearPitch) { // PSG + if (c.chan>(psgChanOffs-1) || parent->song.compatFlags.linearPitch) { // PSG int destFreq=NOTE_FNUM_BLOCK(c.value2,11,chan[c.chan].state.block); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1156,7 +1156,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (c.chan>(2+isCSM)) { if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } } chan[c.chan].inPorta=c.value; diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index c4679f22c..1a389de42 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -146,7 +146,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>opChan[ch].baseFreq) { @@ -472,7 +472,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { if (opChan[i].std.alg.had) { chan[extChanOffs].state.alg=opChan[i].std.alg.val; rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[j]; DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[j]; if (isOpMuted[j] || !op.enable) { @@ -551,7 +551,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { unsigned char hardResetMask=0; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block); } else { int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 26ecfbc63..87f2a604c 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -794,7 +794,7 @@ void DivPlatformYM2608::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -916,7 +916,7 @@ void DivPlatformYM2608::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block); } else { int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2); @@ -1238,7 +1238,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); + if (parent->song.compatFlags.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -1340,7 +1340,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } break; } - if (c.chan>(5+isCSM) || parent->song.linearPitch) { // PSG, ADPCM-B + if (c.chan>(5+isCSM) || parent->song.compatFlags.linearPitch) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1651,7 +1651,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (c.chan>5) { if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } } chan[c.chan].inPorta=c.value; diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 670df9f6e..926b90cf8 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -150,7 +150,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; @@ -166,7 +166,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>opChan[ch].baseFreq) { @@ -510,7 +510,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { if (opChan[i].std.alg.had) { chan[extChanOffs].state.alg=opChan[i].std.alg.val; rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[j]; DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[j]; if (isOpMuted[j] || !op.enable) { @@ -541,7 +541,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { if (opChan[i].std.panL.had) { opChan[i].pan=opChan[i].std.panL.val&3; - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int j=0; j<4; j++) { if (i==j) continue; opChan[j].pan=opChan[i].pan; @@ -612,7 +612,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { unsigned char hardResetMask=0; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block); } else { int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index e8e2123a9..cc3b70f66 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -714,7 +714,7 @@ void DivPlatformYM2610::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -836,7 +836,7 @@ void DivPlatformYM2610::tick(bool sysTick) { for (int i=0; i<(psgChanOffs-isCSM); i++) { if (i==1 && extMode) continue; if (chan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block); } else { int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); @@ -1178,7 +1178,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); + if (parent->song.compatFlags.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -1280,7 +1280,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } - if (c.chan>=psgChanOffs || parent->song.linearPitch) { // PSG, ADPCM-B + if (c.chan>=psgChanOffs || parent->song.compatFlags.linearPitch) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1591,7 +1591,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (c.chan>=psgChanOffs) { if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } } chan[c.chan].inPorta=c.value; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 413bdcbae..12f75e1b9 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -783,7 +783,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i] || !op.enable) { @@ -905,7 +905,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { for (int i=0; i<(psgChanOffs-isCSM); i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block); } else { int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2); @@ -1247,7 +1247,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); + if (parent->song.compatFlags.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -1349,7 +1349,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } - if (c.chan>=psgChanOffs || parent->song.linearPitch) { // PSG, ADPCM-B + if (c.chan>=psgChanOffs || parent->song.compatFlags.linearPitch) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1660,7 +1660,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (c.chan>=psgChanOffs) { if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } } chan[c.chan].inPorta=c.value; diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index d08c14c34..075540580 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -146,7 +146,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; @@ -162,7 +162,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>opChan[ch].baseFreq) { @@ -503,7 +503,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { if (opChan[i].std.alg.had) { chan[extChanOffs].state.alg=opChan[i].std.alg.val; rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[j]; DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[j]; if (isOpMuted[j] || !op.enable) { @@ -534,7 +534,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { if (opChan[i].std.panL.had) { opChan[i].pan=opChan[i].std.panL.val&3; - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int j=0; j<4; j++) { if (i==j) continue; opChan[j].pan=opChan[i].pan; @@ -604,7 +604,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { unsigned char hardResetMask=0; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block); } else { int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 0ae983065..fa55460ce 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -146,7 +146,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { } else { opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; opChan[i].pan=opChan[ch].pan; @@ -162,7 +162,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>opChan[ch].baseFreq) { @@ -503,7 +503,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { if (opChan[i].std.alg.had) { chan[extChanOffs].state.alg=opChan[i].std.alg.val; rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); - if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + if (!parent->song.compatFlags.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[j]; DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[j]; if (isOpMuted[j] || !op.enable) { @@ -534,7 +534,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { if (opChan[i].std.panL.had) { opChan[i].pan=opChan[i].std.panL.val&3; - if (parent->song.sharedExtStat) { + if (parent->song.compatFlags.sharedExtStat) { for (int j=0; j<4; j++) { if (i==j) continue; opChan[j].pan=opChan[i].pan; @@ -604,7 +604,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { unsigned char hardResetMask=0; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - if (parent->song.linearPitch) { + if (parent->song.compatFlags.linearPitch) { opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block); } else { int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2); diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index 6684f4011..55350818a 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -241,7 +241,7 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { 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) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } break; @@ -287,7 +287,7 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_FREQUENCY(c.value2+chan[c.chan].sampleNoteDelta); bool return2=false; - int multiplier=(parent->song.linearPitch)?1:256; + int multiplier=(parent->song.compatFlags.linearPitch)?1:256; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value*multiplier; if (chan[c.chan].baseFreq>=destFreq) { @@ -316,9 +316,9 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { } 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 (parent->song.compatFlags.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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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: diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index 7e0dbe74d..3475d36f2 100644 --- a/src/engine/platform/zxbeeper.cpp +++ b/src/engine/platform/zxbeeper.cpp @@ -212,9 +212,9 @@ int DivPlatformZXBeeper::dispatch(DivCommand c) { 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_BEEPER)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GET_VOLMAX: diff --git a/src/engine/platform/zxbeeperquadtone.cpp b/src/engine/platform/zxbeeperquadtone.cpp index 5fd27689c..3c3b93f92 100644 --- a/src/engine/platform/zxbeeperquadtone.cpp +++ b/src/engine/platform/zxbeeperquadtone.cpp @@ -190,7 +190,7 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); chan[c.chan].insChanged=false; - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; writeOutVol(c.chan); } @@ -293,9 +293,9 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) { 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_POKEMINI)); + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI)); } - 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); + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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_GET_VOLMAX: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f9bda8a9b..a59fb3c8d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -664,9 +664,9 @@ void DivEngine::processRow(int i, bool afterDelay) { // - 2: DefleMask (same as 1) // in the case of normal, the jump row (changePos) is not reset to 0 // this means that you can do 0Dxx 0Byy and it'll work, taking you to row xx of order yy - if (changeOrd==-1 || song.jumpTreatment==0) { + if (changeOrd==-1 || song.compatFlags.jumpTreatment==0) { changeOrd=effectVal; - if (song.jumpTreatment==1 || song.jumpTreatment==2) { + if (song.compatFlags.jumpTreatment==1 || song.compatFlags.jumpTreatment==2) { changePos=0; } } @@ -676,23 +676,23 @@ void DivEngine::processRow(int i, bool afterDelay) { // if there is a 0Dxx effect on the very last order, it is ignored // COMPAT FLAG: simultaneous jump treatment - if (song.jumpTreatment==2) { + if (song.compatFlags.jumpTreatment==2) { // - 2: DefleMask (jump to next order unless it is the last one and ignoreJumpAtEnd is on) - if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + if ((curOrder<(curSubSong->ordersLen-1) || !song.compatFlags.ignoreJumpAtEnd)) { // changeOrd -2 means increase order by 1 // it overrides a previous 0Bxx effect changeOrd=-2; changePos=effectVal; } - } else if (song.jumpTreatment==1) { + } else if (song.compatFlags.jumpTreatment==1) { // - 1: old Furnace (same as 2 but ignored if 0Bxx is present) - if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.compatFlags.ignoreJumpAtEnd)) { changeOrd=-2; changePos=effectVal; } } else { // - 0: normal - if (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd) { + if (curOrder<(curSubSong->ordersLen-1) || !song.compatFlags.ignoreJumpAtEnd) { // set the target order if not set, allowing you to use 0B and 0D regardless of position if (changeOrd<0) { changeOrd=-2; @@ -710,8 +710,8 @@ void DivEngine::processRow(int i, bool afterDelay) { // - delays equal or greater to the speed are ignored // - 2: lax (default) // - no delay is ever ignored unless overridden by another - bool comparison=(song.delayBehavior==1)?(effectVal<=nextSpeed):(effectVal<(nextSpeed*(curSubSong->timeBase+1))); - if (song.delayBehavior==2) comparison=true; + bool comparison=(song.compatFlags.delayBehavior==1)?(effectVal<=nextSpeed):(effectVal<(nextSpeed*(curSubSong->timeBase+1))); + if (song.compatFlags.delayBehavior==2) comparison=true; if (comparison) { // set the delay row, order and timer chan[i].rowDelay=effectVal; @@ -762,7 +762,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: legacy volume slides // - sets volume to max once a vol slide down has finished (thus setting volume to volMax+1) - if (song.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) { + if (song.compatFlags.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) { logV("forcing volume"); chan[i].volume=chan[i].volMax; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); @@ -780,7 +780,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: reset slides on note off (inverted in the GUI) // - a portamento/pitch slide will be halted upon encountering note off // - this will not occur if the stopPortaOnNoteOff flag is on and this is a portamento - if (chan[i].inPorta && song.noteOffResetsSlides) { + if (chan[i].inPorta && song.compatFlags.noteOffResetsSlides) { // stopOnOff will be false if stopPortaOnNoteOff flag is off if (chan[i].stopOnOff) { chan[i].portaNote=-1; @@ -805,7 +805,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].keyOn=false; chan[i].keyOff=true; // same thing here regarding reset slide behavior - if (chan[i].inPorta && song.noteOffResetsSlides) { + if (chan[i].inPorta && song.compatFlags.noteOffResetsSlides) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; @@ -843,7 +843,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].doNote=true; // COMPAT FLAG: compatible arpeggio // - once a new note plays, arp will not be applied for this tick - if (chan[i].arp!=0 && song.compatibleArpeggio) { + if (chan[i].arp!=0 && song.compatFlags.compatibleArpeggio) { chan[i].arpYield=true; } } @@ -872,7 +872,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: legacy ALWAYS_SET_VOLUME behavior (oldAlwaysSetVolume) // - prior to its addition, volume changes wouldn't be effective depending on the system if the volume is the same as the current one // - afterwards, volume change is made regardless in order to set the bottom byte of volume ("subvolume") - if (!song.oldAlwaysSetVolume || disCont[song.dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->newData[whatRow][DIV_PAT_VOL]) { + if (!song.compatFlags.oldAlwaysSetVolume || disCont[song.dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->newData[whatRow][DIV_PAT_VOL]) { // here we let dispatchCmd() know we can do MIDI aftertouch if there isn't a note if (pat->newData[whatRow][DIV_PAT_NOTE]==-1) { chan[i].midiAftertouch=true; @@ -982,7 +982,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // - 02xx still works // - a previous portamento (03xx) will prevent this slide from occurring // - E1xy/E2xy also will if *another* flag is set - if (song.ignoreDuplicateSlides && (lastSlide==0x01 || lastSlide==0x1337)) break; + if (song.compatFlags.ignoreDuplicateSlides && (lastSlide==0x01 || lastSlide==0x1337)) break; lastSlide=0x01; if (effectVal==0) { chan[i].portaNote=-1; @@ -994,12 +994,12 @@ void DivEngine::processRow(int i, bool afterDelay) { // - this prompts dispatch to stop processing arp macros during a slide // - this only happens if pitch linearity is set to None // - if we don't let dispatch know, the slide will never occur as arp takes over - if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + if (!song.compatFlags.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { // COMPAT FLAG: limit slide range // - this confines pitch slides from dispatch->getPortaFloor to C-8 (I think) // - yep, the lowest portamento note depends on the system... - chan[i].portaNote=song.limitSlides?0x60:255; + chan[i].portaNote=song.compatFlags.limitSlides?0x60:255; chan[i].portaSpeed=effectVal; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); // most of these are used for compat flag handling @@ -1013,7 +1013,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // - this prompts dispatch to stop processing arp macros during a slide // - this only happens if pitch linearity is set to None // - if we don't let dispatch know, the slide will never occur as arp takes over - if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); + if (!song.compatFlags.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); } break; case 0x02: // pitch slide down @@ -1022,7 +1022,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // - 01xx still works // - a previous portamento (03xx) will prevent this slide from occurring // - E1xy/E2xy also will if *another* flag is set - if (song.ignoreDuplicateSlides && (lastSlide==0x02 || lastSlide==0x1337)) break; + if (song.compatFlags.ignoreDuplicateSlides && (lastSlide==0x02 || lastSlide==0x1337)) break; lastSlide=0x02; if (effectVal==0) { chan[i].portaNote=-1; @@ -1030,12 +1030,12 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].inPorta=false; // COMPAT FLAG: arpeggio inhibits non-porta slides - if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + if (!song.compatFlags.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { // COMPAT FLAG: limit slide range // - this confines pitch slides from dispatch->getPortaFloor to C-8 (I think) // - yep, the lowest portamento note depends on the system... - chan[i].portaNote=(song.limitSlides && song.dispatchChanOfChan[i]>=0)?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; + chan[i].portaNote=(song.compatFlags.limitSlides && song.dispatchChanOfChan[i]>=0)?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; chan[i].portaSpeed=effectVal; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; @@ -1044,7 +1044,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].wasShorthandPorta=false; chan[i].inPorta=false; // COMPAT FLAG: arpeggio inhibits non-porta slides - if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); + if (!song.compatFlags.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); } break; case 0x03: // portamento @@ -1064,7 +1064,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: buggy portamento after sliding // - you might want to slide up or down and then 03xx to return to the original note // - if a porta to the same note is attempted after slide, for some reason it does not occur - if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { + if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.compatFlags.buggyPortaAfterSlide) { chan[i].portaNote=chan[i].note; chan[i].portaSpeed=-1; } else { @@ -1084,7 +1084,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: stop portamento on note off // - if a portamento is called and then a note off occurs, stop portamento before the next note // - ...unless noteOffResetsSlides is disabled - chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! + chan[i].stopOnOff=song.compatFlags.stopPortaOnNoteOff; // what?! chan[i].scheduledSlideReset=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1)); // this is used to inhibit any other slide commands if the respective compat flag is enabled @@ -1144,7 +1144,7 @@ void DivEngine::processRow(int i, bool afterDelay) { calledPorta=true; // COMPAT FLAG: buggy portamento after sliding // yes, this also affects 06xy. - if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { + if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.compatFlags.buggyPortaAfterSlide) { chan[i].portaNote=chan[i].note; chan[i].portaSpeed=-1; } else { @@ -1157,7 +1157,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // this is the same as 03xx. chan[i].portaStop=true; if (chan[i].keyOn) chan[i].doNote=false; - chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! + chan[i].stopOnOff=song.compatFlags.stopPortaOnNoteOff; // what?! chan[i].scheduledSlideReset=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1)); lastSlide=0x1337; // i hate this so much @@ -1224,7 +1224,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].arp=effectVal; // COMPAT FLAG: reset note to base on arp stop (inverted in the GUI) // - a 0000 effect resets arpeggio position - if (chan[i].arp==0 && song.arp0Reset) { + if (chan[i].arp==0 && song.compatFlags.arp0Reset) { chan[i].resetArp=true; } dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); @@ -1246,7 +1246,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // COMPAT FLAG: old sample offset effect // - before 0.6.3 the sample offset effect was 9xxx, where `xxx` is multiplied by 256 // - the effect was then changed to 90xx/91xx/92xx, allowing you to set the low, mid and high bytes of the offset respectively - if (song.oldSampleOffset) { + if (song.compatFlags.oldSampleOffset) { // send sample position now dispatchCmd(DivCommand(DIV_CMD_SAMPLE_POS,i,(((effect&0x0f)<<8)|effectVal)*256)); } else { @@ -1279,7 +1279,7 @@ void DivEngine::processRow(int i, bool afterDelay) { // - ignore cut if equal or greater than speed // - 2: lax (default) // - no cut is ever ignored unless overridden by another - if (effectVal>0 && (song.delayBehavior==2 || effectVal0 && (song.compatFlags.delayBehavior==2 || effectVal0 && (song.delayBehavior==2 || effectVal0 && (song.compatFlags.delayBehavior==2 || effectVal0 && (song.delayBehavior==2 || effectVal0 && (song.compatFlags.delayBehavior==2 || effectVal=0)?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; + chan[i].portaNote=(song.compatFlags.limitSlides && song.dispatchChanOfChan[i]>=0)?disCont[song.dispatchOfChan[i]].dispatch->getPortaFloor(song.dispatchChanOfChan[i]):-60; } chan[i].portaSpeed=effectVal; chan[i].portaStop=true; @@ -1688,13 +1688,13 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].scheduledSlideReset=false; chan[i].inPorta=false; // COMPAT FLAG: arpeggio inhibits non-porta slides - if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); - dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch?song.pitchSlideSpeed:1),chan[i].portaNote)); + if (!song.compatFlags.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); + dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.compatFlags.linearPitch?song.compatFlags.pitchSlideSpeed:1),chan[i].portaNote)); chan[i].portaNote=-1; chan[i].portaSpeed=-1; chan[i].inPorta=false; // COMPAT FLAG: arpeggio inhibits non-porta slides - if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + if (!song.compatFlags.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); break; } } @@ -1797,7 +1797,7 @@ void DivEngine::nextRow() { for (int i=0; i=2)?speeds.val[1]:speeds.val[0]; unsigned char speed1=speeds.val[0]; @@ -1913,7 +1913,7 @@ void DivEngine::nextRow() { // COMPAT FLAG: pre-note does not take effect into consideration // - a bug which does not cancel pre-note before a portamento or during legato // - fixed in 0.6pre9 - if (!song.preNoteNoEffect) { + if (!song.compatFlags.preNoteNoEffect) { // handle portamento if (pat->newData[curRow][DIV_PAT_FX(j)]==0x03 && pat->newData[curRow][DIV_PAT_FXVAL(j)]!=0 && pat->newData[curRow][DIV_PAT_FXVAL(j)]!=-1) { doPreparePreNote=false; @@ -1947,7 +1947,7 @@ void DivEngine::nextRow() { // COMPAT FLAG: auto-insert one tick gap between notes // - simulates behavior of certain Amiga/C64 sound drivers where a one-tick cut occurred before another note - if (song.oneTickCut) { + if (song.compatFlags.oneTickCut) { bool doPrepareCut=true; int addition=0; @@ -2158,7 +2158,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // - 0: reset channels. call playSub() to seek back to the loop position // - 1: soft-reset channels. same as 0 for now // - 2: don't reset - if (song.loopModality!=2) { + if (song.compatFlags.loopModality!=2) { playSub(true); } } @@ -2205,7 +2205,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // volume slides and tremolo // COMPAT FLAG: don't slide on the first tick of a row // - Amiga/PC tracker behavior where slides and vibrato do not take course during the first tick of a row - if (!song.noSlidesOnFirstTick || !firstTick) { + if (!song.compatFlags.noSlidesOnFirstTick || !firstTick) { // volume slides if (chan[i].volSpeed!=0) { // the call to GET_VOLUME is part of a compatibility process @@ -2255,7 +2255,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // COMPAT FLAG: legacy volume slides // - sets volume to max once a vol slide down has finished (thus setting volume to volMax+1) // - there is more to this, such as the first step of volume macro resulting in unpredictable behavior, but I don't feel like implementing THAT... - if (song.legacyVolumeSlides) { + if (song.compatFlags.legacyVolumeSlides) { chan[i].volume=chan[i].volMax+1; } else { chan[i].volume=0; @@ -2418,7 +2418,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // portamento and pitch slides // COMPAT FLAG: don't slide on the first tick of a row // - Amiga/PC tracker behavior where slides and vibrato do not take course during the first tick of a row - if (!song.noSlidesOnFirstTick || !firstTick) { + if (!song.compatFlags.noSlidesOnFirstTick || !firstTick) { // portamento only runs if the channel has been used and the porta speed is higher than 0 if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { // send a portamento update command to the dispatch. @@ -2428,7 +2428,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // - 1: full (pitch slides linear... we multiply the portamento speed by a user-defined multiplier) // COMPAT FLAG: reset pitch slide/portamento upon reaching target (inverted in the GUI) // - when disabled, portamento remains active after it has finished - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.compatFlags.linearPitch?song.compatFlags.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.compatFlags.targetResetsSlides) { // if we are here, it means we reached the target and shall stop chan[i].portaSpeed=0; dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); @@ -2454,7 +2454,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // COMPAT FLAG: reset slides on note off (inverted in the GUI) // - a portamento/pitch slide will be halted upon encountering note off // - this will not occur if the stopPortaOnNoteOff flag is on and this is a portamento - if (chan[i].inPorta && song.noteOffResetsSlides) { + if (chan[i].inPorta && song.compatFlags.noteOffResetsSlides) { chan[i].keyOff=true; chan[i].keyOn=false; // stopOnOff will be false if stopPortaOnNoteOff flag is off @@ -2503,7 +2503,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } // COMPAT FLAG: reset arp position on row change // - simulates Amiga/PC tracker behavior where the next row resets arp pos - if (song.rowResetsArpPos && firstTick) { + if (song.compatFlags.rowResetsArpPos && firstTick) { chan[i].arpStage=-1; } // arpeggio (actually) diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 970dae4b6..ef1366b53 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -22,6 +22,8 @@ #include #include +static DivCompatFlags defaultFlags; + TimeMicros DivSongTimestamps::getTimes(int order, int row) { if (order<0 || order>=DIV_MAX_PATTERNS) return TimeMicros(-1,0); if (row<0 || row>=DIV_MAX_ROWS) return TimeMicros(-1,0); @@ -448,6 +450,66 @@ void DivSubSong::calcTimestamps(int chans, std::vector& groove logV("song length: %s; %" PRIu64 " ticks",ts.totalTime.toString(6,TA_TIME_FORMAT_AUTO),ts.totalTicks); } +void DivSubSong::putData(SafeWriter* w, int chans) { + size_t blockStartSeek, blockEndSeek; + w->write("SNG2",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeF(hz); + w->writeC(arpLen); + w->writeC(timeBase); + w->writeS(patLen); + w->writeS(ordersLen); + w->writeC(hilightA); + w->writeC(hilightB); + w->writeS(virtualTempoN); + w->writeS(virtualTempoD); + + // speeds + w->writeS(speeds.len); + for (int i=0; i<16; i++) { + w->writeS(speeds.val[i]); + } + + w->writeString(name,false); + w->writeString(notes,false); + + for (int i=0; iwriteC(orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(pat[i].effectCols); + } + + for (int i=0; iwriteC( + (chanShow[i]?1:0)| + (chanShowChanOsc[i]?2:0) + ); + } + + for (int i=0; iwriteC(chanCollapse[i]); + } + + for (int i=0; iwriteString(chanName[i],false); + } + + for (int i=0; iwriteString(chanShortName[i],false); + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); +} + void DivSubSong::clearData() { for (int i=0; iordersLen) { - i->calcTimestamps(chans,grooves,jumpTreatment,ignoreJumpAtEnd,brokenSpeedSel,delayBehavior,curStart); + i->calcTimestamps(chans,grooves,compatFlags.jumpTreatment,compatFlags.ignoreJumpAtEnd,compatFlags.brokenSpeedSel,compatFlags.delayBehavior,curStart); if (!i->ts.isLoopable) break; // make sure we don't pick the same range twice @@ -809,3 +871,167 @@ void DivSong::unload() { } subsong.clear(); } + +void DivGroovePattern::putData(SafeWriter* w) { + size_t blockStartSeek, blockEndSeek; + w->write("GROV",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeS(len); + for (int i=0; i<16; i++) { + w->writeS(val[i]); + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); +} + +void DivCompatFlags::setDefaults() { + limitSlides=false; + linearPitch=1; + pitchSlideSpeed=4; + loopModality=2; + delayBehavior=2; + jumpTreatment=0; + properNoiseLayout=true; + waveDutyIsVol=false; + resetMacroOnPorta=false; + legacyVolumeSlides=false; + compatibleArpeggio=false; + noteOffResetsSlides=true; + targetResetsSlides=true; + arpNonPorta=false; + algMacroBehavior=false; + brokenShortcutSlides=false; + ignoreDuplicateSlides=false; + stopPortaOnNoteOff=false; + continuousVibrato=false; + brokenDACMode=false; + oneTickCut=false; + newInsTriggersInPorta=true; + arp0Reset=true; + brokenSpeedSel=false; + noSlidesOnFirstTick=false; + rowResetsArpPos=false; + ignoreJumpAtEnd=false; + buggyPortaAfterSlide=false; + gbInsAffectsEnvelope=true; + sharedExtStat=true; + ignoreDACModeOutsideIntendedChannel=false; + e1e2AlsoTakePriority=false; + newSegaPCM=true; + fbPortaPause=false; + snDutyReset=false; + pitchMacroIsLinear=true; + oldOctaveBoundary=false; + noOPN2Vol=false; + newVolumeScaling=true; + volMacroLinger=true; + brokenOutVol=false; + brokenOutVol2=false; + e1e2StopOnSameNote=false; + brokenPortaArp=false; + snNoLowPeriods=false; + disableSampleMacro=false; + oldArpStrategy=false; + brokenPortaLegato=false; + brokenFMOff=false; + preNoteNoEffect=false; + oldDPCM=false; + resetArpPhaseOnNewNote=false; + ceilVolumeScaling=false; + oldAlwaysSetVolume=false; + oldSampleOffset=false; + oldCenterRate=true; +} + +bool DivCompatFlags::areDefaults() { + return (*this==defaultFlags); +} + +#define CHECK_AND_STORE_BOOL(_x) \ + if (_x!=defaultFlags._x) { \ + c.set(#_x,_x); \ + } + +#define CHECK_AND_STORE_UNSIGNED_CHAR(_x) \ + if (_x!=defaultFlags._x) { \ + c.set(#_x,(int)_x); \ + } + +void DivCompatFlags::putData(SafeWriter* w) { + DivConfig c; + size_t blockStartSeek, blockEndSeek; + + CHECK_AND_STORE_BOOL(limitSlides); + CHECK_AND_STORE_UNSIGNED_CHAR(linearPitch); + CHECK_AND_STORE_UNSIGNED_CHAR(pitchSlideSpeed); + CHECK_AND_STORE_UNSIGNED_CHAR(loopModality); + CHECK_AND_STORE_UNSIGNED_CHAR(delayBehavior); + CHECK_AND_STORE_UNSIGNED_CHAR(jumpTreatment); + CHECK_AND_STORE_BOOL(properNoiseLayout); + CHECK_AND_STORE_BOOL(waveDutyIsVol); + CHECK_AND_STORE_BOOL(resetMacroOnPorta); + CHECK_AND_STORE_BOOL(legacyVolumeSlides); + CHECK_AND_STORE_BOOL(compatibleArpeggio); + CHECK_AND_STORE_BOOL(noteOffResetsSlides); + CHECK_AND_STORE_BOOL(targetResetsSlides); + CHECK_AND_STORE_BOOL(arpNonPorta); + CHECK_AND_STORE_BOOL(algMacroBehavior); + CHECK_AND_STORE_BOOL(brokenShortcutSlides); + CHECK_AND_STORE_BOOL(ignoreDuplicateSlides); + CHECK_AND_STORE_BOOL(stopPortaOnNoteOff); + CHECK_AND_STORE_BOOL(continuousVibrato); + CHECK_AND_STORE_BOOL(brokenDACMode); + CHECK_AND_STORE_BOOL(oneTickCut); + CHECK_AND_STORE_BOOL(newInsTriggersInPorta); + CHECK_AND_STORE_BOOL(arp0Reset); + CHECK_AND_STORE_BOOL(brokenSpeedSel); + CHECK_AND_STORE_BOOL(noSlidesOnFirstTick); + CHECK_AND_STORE_BOOL(rowResetsArpPos); + CHECK_AND_STORE_BOOL(ignoreJumpAtEnd); + CHECK_AND_STORE_BOOL(buggyPortaAfterSlide); + CHECK_AND_STORE_BOOL(gbInsAffectsEnvelope); + CHECK_AND_STORE_BOOL(sharedExtStat); + CHECK_AND_STORE_BOOL(ignoreDACModeOutsideIntendedChannel); + CHECK_AND_STORE_BOOL(e1e2AlsoTakePriority); + CHECK_AND_STORE_BOOL(newSegaPCM); + CHECK_AND_STORE_BOOL(fbPortaPause); + CHECK_AND_STORE_BOOL(snDutyReset); + CHECK_AND_STORE_BOOL(pitchMacroIsLinear); + CHECK_AND_STORE_BOOL(oldOctaveBoundary); + CHECK_AND_STORE_BOOL(noOPN2Vol); + CHECK_AND_STORE_BOOL(newVolumeScaling); + CHECK_AND_STORE_BOOL(volMacroLinger); + CHECK_AND_STORE_BOOL(brokenOutVol); + CHECK_AND_STORE_BOOL(brokenOutVol2); + CHECK_AND_STORE_BOOL(e1e2StopOnSameNote); + CHECK_AND_STORE_BOOL(brokenPortaArp); + CHECK_AND_STORE_BOOL(snNoLowPeriods); + CHECK_AND_STORE_BOOL(disableSampleMacro); + CHECK_AND_STORE_BOOL(oldArpStrategy); + CHECK_AND_STORE_BOOL(brokenPortaLegato); + CHECK_AND_STORE_BOOL(brokenFMOff); + CHECK_AND_STORE_BOOL(preNoteNoEffect); + CHECK_AND_STORE_BOOL(oldDPCM); + CHECK_AND_STORE_BOOL(resetArpPhaseOnNewNote); + CHECK_AND_STORE_BOOL(ceilVolumeScaling); + CHECK_AND_STORE_BOOL(oldAlwaysSetVolume); + CHECK_AND_STORE_BOOL(oldSampleOffset); + CHECK_AND_STORE_BOOL(oldCenterRate); + + String data=c.toString(); + w->write("CFLG",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeString(data,false); + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); +} diff --git a/src/engine/song.h b/src/engine/song.h index d34f7bc9d..35cf30c6f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -163,6 +163,7 @@ enum DivEffectType: unsigned short { struct DivGroovePattern { unsigned char val[16]; unsigned char len; + void putData(SafeWriter* w); DivGroovePattern(): len(1) { memset(val,6,16); @@ -229,6 +230,11 @@ struct DivSubSong { */ void calcTimestamps(int chans, std::vector& grooves, int jumpTreatment, int ignoreJumpAtEnd, int brokenSpeedSel, int delayBehavior, int firstPat=0); + /** + * write sub-song block to a SafeWriter. + */ + void putData(SafeWriter* w, int chans); + void clearData(); void removeUnusedPatterns(); void optimizePatterns(); @@ -269,42 +275,7 @@ struct DivEffectStorage { storageLen(0) {} }; -struct DivSong { - unsigned short version; - bool isDMF; - - // system - int chans; - DivSystem system[DIV_MAX_CHIPS]; - unsigned short systemChans[DIV_MAX_CHIPS]; - unsigned char systemLen; - float systemVol[DIV_MAX_CHIPS]; - float systemPan[DIV_MAX_CHIPS]; - float systemPanFR[DIV_MAX_CHIPS]; - DivConfig systemFlags[DIV_MAX_CHIPS]; - - // song information - String name, author, systemName; - - // legacy song information - // those will be stored in .fur and mapped to VGM as: - // category -> game name - // writer -> ripper - // createdDate -> year - String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate; - - // more VGM specific stuff - String nameJ, authorJ, categoryJ, systemNameJ; - - // other things - String notes; - - // module details - int insLen, waveLen, sampleLen; - float masterVol; - float tuning; - - // compatibility flags +struct DivCompatFlags { bool limitSlides; // linear pitch unsigned char linearPitch; @@ -364,9 +335,7 @@ struct DivSong { bool brokenPortaArp; bool snNoLowPeriods; bool disableSampleMacro; - bool autoSystem; bool oldArpStrategy; - bool patchbayAuto; bool brokenPortaLegato; bool brokenFMOff; bool preNoteNoEffect; @@ -375,9 +344,67 @@ struct DivSong { bool ceilVolumeScaling; bool oldAlwaysSetVolume; bool oldSampleOffset; - // TODO: this flag is not saved to the file yet. + // new flags as of dev240 bool oldCenterRate; + void setDefaults(); + bool areDefaults(); + void putData(SafeWriter* w); + + bool operator==(const DivCompatFlags& other) { + return (memcmp(this,&other,sizeof(DivCompatFlags))==0); + } + bool operator!=(const DivCompatFlags& other) { + return (memcmp(this,&other,sizeof(DivCompatFlags))!=0); + } + + DivCompatFlags() { + memset(this,0,sizeof(DivCompatFlags)); + setDefaults(); + } +}; + +struct DivSong { + unsigned short version; + bool isDMF; + + // system + int chans; + DivSystem system[DIV_MAX_CHIPS]; + unsigned short systemChans[DIV_MAX_CHIPS]; + unsigned char systemLen; + float systemVol[DIV_MAX_CHIPS]; + float systemPan[DIV_MAX_CHIPS]; + float systemPanFR[DIV_MAX_CHIPS]; + DivConfig systemFlags[DIV_MAX_CHIPS]; + + // song information + String name, author, systemName; + + // legacy song information + // those will be stored in .fur and mapped to VGM as: + // category -> game name + // writer -> ripper + // createdDate -> year + String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate; + + // more VGM specific stuff + String nameJ, authorJ, categoryJ, systemNameJ; + + // other things + String notes; + + // module details + int insLen, waveLen, sampleLen; + float masterVol; + float tuning; + + bool autoSystem; + bool patchbayAuto; + + // compatibility flags + DivCompatFlags compatFlags; + std::vector ins; std::vector wave; std::vector sample; @@ -481,64 +508,8 @@ struct DivSong { sampleLen(0), masterVol(1.0f), tuning(440.0f), - limitSlides(false), - linearPitch(1), - pitchSlideSpeed(4), - loopModality(2), - delayBehavior(2), - jumpTreatment(0), - properNoiseLayout(true), - waveDutyIsVol(false), - resetMacroOnPorta(false), - legacyVolumeSlides(false), - compatibleArpeggio(false), - noteOffResetsSlides(true), - targetResetsSlides(true), - arpNonPorta(false), - algMacroBehavior(false), - brokenShortcutSlides(false), - ignoreDuplicateSlides(false), - stopPortaOnNoteOff(false), - continuousVibrato(false), - brokenDACMode(false), - oneTickCut(false), - newInsTriggersInPorta(true), - arp0Reset(true), - brokenSpeedSel(false), - noSlidesOnFirstTick(false), - rowResetsArpPos(false), - ignoreJumpAtEnd(false), - buggyPortaAfterSlide(false), - gbInsAffectsEnvelope(true), - sharedExtStat(true), - ignoreDACModeOutsideIntendedChannel(false), - e1e2AlsoTakePriority(false), - newSegaPCM(true), - fbPortaPause(false), - snDutyReset(false), - pitchMacroIsLinear(true), - oldOctaveBoundary(false), - noOPN2Vol(false), - newVolumeScaling(true), - volMacroLinger(true), - brokenOutVol(false), - brokenOutVol2(false), - e1e2StopOnSameNote(false), - brokenPortaArp(false), - snNoLowPeriods(false), - disableSampleMacro(false), autoSystem(true), - oldArpStrategy(false), - patchbayAuto(true), - brokenPortaLegato(false), - brokenFMOff(false), - preNoteNoEffect(false), - oldDPCM(false), - resetArpPhaseOnNewNote(false), - ceilVolumeScaling(false), - oldAlwaysSetVolume(false), - oldSampleOffset(false), - oldCenterRate(true) { + patchbayAuto(true) { memset(dispatchFirstChan,0,DIV_MAX_CHANS*sizeof(int)); memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int)); diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 6b59a7e86..9cdeb9767 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -2736,7 +2736,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p countDown--; break; } - if (song.loopModality!=2) countDown=0; + if (song.compatFlags.loopModality!=2) countDown=0; if (countDown>0 && !beenOneLoopAlready) { loopTickSong++; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 6a1573d21..994a780b3 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -32,134 +32,134 @@ void FurnaceGUI::drawCompatFlags() { ImGui::TextWrapped(_("these flags are designed to provide better DefleMask/older Furnace compatibility.\nit is recommended to disable most of these unless you rely on specific quirks.")); if (ImGui::BeginTabBar("settingsTab")) { if (ImGui::BeginTabItem(_("DefleMask"))) { - if (ImGui::Checkbox(_("Limit slide range"),&e->song.limitSlides)) { + if (ImGui::Checkbox(_("Limit slide range"),&e->song.compatFlags.limitSlides)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves.")); } - if (InvCheckbox(_("Compatible noise layout on NES and PC Engine"),&e->song.properNoiseLayout)) { + if (InvCheckbox(_("Compatible noise layout on NES and PC Engine"),&e->song.compatFlags.properNoiseLayout)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("use a rather unusual compatible noise frequency layout.\nremoves some noise frequencies on PC Engine.")); } - if (ImGui::Checkbox(_("Game Boy instrument duty is wave volume"),&e->song.waveDutyIsVol)) { + if (ImGui::Checkbox(_("Game Boy instrument duty is wave volume"),&e->song.compatFlags.waveDutyIsVol)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if enabled, an instrument with duty macro in the wave channel will be mapped to wavetable volume.")); } - if (ImGui::Checkbox(_("Restart macro on portamento"),&e->song.resetMacroOnPorta)) { + if (ImGui::Checkbox(_("Restart macro on portamento"),&e->song.compatFlags.resetMacroOnPorta)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, a portamento effect will reset the channel's macro if used in combination with a note.")); } - if (ImGui::Checkbox(_("Legacy volume slides"),&e->song.legacyVolumeSlides)) { + if (ImGui::Checkbox(_("Legacy volume slides"),&e->song.compatFlags.legacyVolumeSlides)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("simulate glitchy volume slide behavior by silently overflowing the volume when the slide goes below 0.")); } - if (ImGui::Checkbox(_("Compatible arpeggio"),&e->song.compatibleArpeggio)) { + if (ImGui::Checkbox(_("Compatible arpeggio"),&e->song.compatFlags.compatibleArpeggio)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("delay arpeggio by one tick on every new note.")); } - if (ImGui::Checkbox(_("Disable DAC when sample ends"),&e->song.brokenDACMode)) { + if (ImGui::Checkbox(_("Disable DAC when sample ends"),&e->song.compatFlags.brokenDACMode)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing.")); } - if (ImGui::Checkbox(_("Broken speed alternation"),&e->song.brokenSpeedSel)) { + if (ImGui::Checkbox(_("Broken speed alternation"),&e->song.compatFlags.brokenSpeedSel)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("determines next speed based on whether the row is odd/even instead of alternating between speeds.")); } - if (ImGui::Checkbox(_("Ignore duplicate slide effects"),&e->song.ignoreDuplicateSlides)) { + if (ImGui::Checkbox(_("Ignore duplicate slide effects"),&e->song.compatFlags.ignoreDuplicateSlides)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if this is on, only the first slide of a row in a channel will be considered.")); } - if (ImGui::Checkbox(_("Ignore 0Dxx on the last order"),&e->song.ignoreJumpAtEnd)) { + if (ImGui::Checkbox(_("Ignore 0Dxx on the last order"),&e->song.compatFlags.ignoreJumpAtEnd)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if this is on, a jump to next row effect will not take place when it is on the last order of a song.")); } - if (ImGui::Checkbox(_("Buggy portamento after pitch slide"),&e->song.buggyPortaAfterSlide)) { + if (ImGui::Checkbox(_("Buggy portamento after pitch slide"),&e->song.compatFlags.buggyPortaAfterSlide)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("simulates a bug in where portamento does not work after sliding.")); } - if (ImGui::Checkbox(_("FM pitch slide octave boundary odd behavior"),&e->song.fbPortaPause)) { + if (ImGui::Checkbox(_("FM pitch slide octave boundary odd behavior"),&e->song.compatFlags.fbPortaPause)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if this is on, a pitch slide that crosses the octave boundary will stop for one tick and then continue from the nearest octave boundary.\nfor .dmf compatibility.")); } - if (InvCheckbox(_("Don't apply Game Boy envelope on note-less instrument change"),&e->song.gbInsAffectsEnvelope)) { + if (InvCheckbox(_("Don't apply Game Boy envelope on note-less instrument change"),&e->song.compatFlags.gbInsAffectsEnvelope)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if this is on, an instrument change will not affect the envelope.")); } - if (ImGui::Checkbox(_("Ignore DAC mode change outside of intended channel in ExtCh mode"),&e->song.ignoreDACModeOutsideIntendedChannel)) { + if (ImGui::Checkbox(_("Ignore DAC mode change outside of intended channel in ExtCh mode"),&e->song.compatFlags.ignoreDACModeOutsideIntendedChannel)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if this is on, 17xx has no effect on the operator channels in YM2612.")); } - if (ImGui::Checkbox(_("E1xy/E2xy also take priority over slide stops"),&e->song.e1e2AlsoTakePriority)) { + if (ImGui::Checkbox(_("E1xy/E2xy also take priority over slide stops"),&e->song.compatFlags.e1e2AlsoTakePriority)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("does this make any sense by now?")); } - if (ImGui::Checkbox(_("E1xy/E2xy stop when repeating the same note"),&e->song.e1e2StopOnSameNote)) { + if (ImGui::Checkbox(_("E1xy/E2xy stop when repeating the same note"),&e->song.compatFlags.e1e2StopOnSameNote)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("ugh, if only this wasn't a thing...")); } - if (ImGui::Checkbox(_("SN76489 duty macro always resets phase"),&e->song.snDutyReset)) { + if (ImGui::Checkbox(_("SN76489 duty macro always resets phase"),&e->song.compatFlags.snDutyReset)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, duty macro will always reset phase, even if its value hasn't changed.")); } - if (InvCheckbox(_("Broken volume scaling strategy"),&e->song.newVolumeScaling)) { + if (InvCheckbox(_("Broken volume scaling strategy"),&e->song.compatFlags.newVolumeScaling)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled:\n- log scaling: multiply\n- linear scaling: subtract\nwhen disabled:\n- log scaling: subtract\n- linear scaling: multiply")); } - if (InvCheckbox(_("Don't persist volume macro after it finishes"),&e->song.volMacroLinger)) { + if (InvCheckbox(_("Don't persist volume macro after it finishes"),&e->song.compatFlags.volMacroLinger)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, a value in the volume column that happens after the volume macro is done will disregard the macro.")); } - if (ImGui::Checkbox(_("Broken output volume on instrument change"),&e->song.brokenOutVol)) { + if (ImGui::Checkbox(_("Broken output volume on instrument change"),&e->song.compatFlags.brokenOutVol)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("if enabled, no checks for the presence of a volume macro will be made.\nthis will cause the last macro value to linger unless a value in the volume column is present.")); } - if (ImGui::Checkbox(_("Broken output volume - Episode 2 (PLEASE KEEP ME DISABLED)"),&e->song.brokenOutVol2)) { + if (ImGui::Checkbox(_("Broken output volume - Episode 2 (PLEASE KEEP ME DISABLED)"),&e->song.compatFlags.brokenOutVol2)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("these compatibility flags are getting SO damn ridiculous and out of control.\nas you may have guessed, this one exists due to yet ANOTHER DefleMask-specific behavior.\nplease keep this off at all costs, because I will not support it when ROM export comes.\noh, and don't start an argument out of it. Furnace isn't a DefleMask replacement, and no,\nI am not trying to make it look like one with all these flags.\n\noh, and what about the other flags that don't have to do with DefleMask?\nthose are for .mod import, future FamiTracker import and personal taste!\n\nend of rant")); } - if (ImGui::Checkbox(_("Treat SN76489 periods under 8 as 1"),&e->song.snNoLowPeriods)) { + if (ImGui::Checkbox(_("Treat SN76489 periods under 8 as 1"),&e->song.compatFlags.snNoLowPeriods)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -168,115 +168,115 @@ void FurnaceGUI::drawCompatFlags() { ImGui::EndTabItem(); } if (ImGui::BeginTabItem(_("Old Furnace"))) { - if (ImGui::Checkbox(_("Arpeggio inhibits non-porta slides"),&e->song.arpNonPorta)) { + if (ImGui::Checkbox(_("Arpeggio inhibits non-porta slides"),&e->song.compatFlags.arpNonPorta)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.5.5")); } - if (ImGui::Checkbox(_("Wack FM algorithm macro"),&e->song.algMacroBehavior)) { + if (ImGui::Checkbox(_("Wack FM algorithm macro"),&e->song.compatFlags.algMacroBehavior)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.5.5")); } - if (ImGui::Checkbox(_("Broken shortcut slides (E1xy/E2xy)"),&e->song.brokenShortcutSlides)) { + if (ImGui::Checkbox(_("Broken shortcut slides (E1xy/E2xy)"),&e->song.compatFlags.brokenShortcutSlides)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.5.7")); } - if (ImGui::Checkbox(_("Stop portamento on note off"),&e->song.stopPortaOnNoteOff)) { + if (ImGui::Checkbox(_("Stop portamento on note off"),&e->song.compatFlags.stopPortaOnNoteOff)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (InvCheckbox(_("Don't allow instrument change during slides"),&e->song.newInsTriggersInPorta)) { + if (InvCheckbox(_("Don't allow instrument change during slides"),&e->song.compatFlags.newInsTriggersInPorta)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (InvCheckbox(_("Don't reset note to base on arpeggio stop"),&e->song.arp0Reset)) { + if (InvCheckbox(_("Don't reset note to base on arpeggio stop"),&e->song.compatFlags.arp0Reset)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (InvCheckbox(_("ExtCh channel status is not shared among operators"),&e->song.sharedExtStat)) { + if (InvCheckbox(_("ExtCh channel status is not shared among operators"),&e->song.compatFlags.sharedExtStat)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (InvCheckbox(_("Disable new SegaPCM features (macros and better panning)"),&e->song.newSegaPCM)) { + if (InvCheckbox(_("Disable new SegaPCM features (macros and better panning)"),&e->song.compatFlags.newSegaPCM)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (ImGui::Checkbox(_("Old FM octave boundary behavior"),&e->song.oldOctaveBoundary)) { + if (ImGui::Checkbox(_("Old FM octave boundary behavior"),&e->song.compatFlags.oldOctaveBoundary)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (ImGui::Checkbox(_("Disable OPN2 DAC volume control"),&e->song.noOPN2Vol)) { + if (ImGui::Checkbox(_("Disable OPN2 DAC volume control"),&e->song.compatFlags.noOPN2Vol)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1")); } - if (ImGui::Checkbox(_("Broken initial position of portamento after arpeggio"),&e->song.brokenPortaArp)) { + if (ImGui::Checkbox(_("Broken initial position of portamento after arpeggio"),&e->song.compatFlags.brokenPortaArp)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre1.5")); } - if (ImGui::Checkbox(_("Disable new sample features"),&e->song.disableSampleMacro)) { + if (ImGui::Checkbox(_("Disable new sample features"),&e->song.compatFlags.disableSampleMacro)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre2")); } - if (ImGui::Checkbox(_("Old arpeggio macro + pitch slide strategy"),&e->song.oldArpStrategy)) { + if (ImGui::Checkbox(_("Old arpeggio macro + pitch slide strategy"),&e->song.compatFlags.oldArpStrategy)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre2")); } - if (ImGui::Checkbox(_("Broken portamento during legato"),&e->song.brokenPortaLegato)) { + if (ImGui::Checkbox(_("Broken portamento during legato"),&e->song.compatFlags.brokenPortaLegato)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre4")); } - if (ImGui::Checkbox(_("Broken macros in some FM chips after note off"),&e->song.brokenFMOff)) { + if (ImGui::Checkbox(_("Broken macros in some FM chips after note off"),&e->song.compatFlags.brokenFMOff)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre5")); } - if (ImGui::Checkbox(_("Pre-note does not take effects into consideration"),&e->song.preNoteNoEffect)) { + if (ImGui::Checkbox(_("Pre-note does not take effects into consideration"),&e->song.compatFlags.preNoteNoEffect)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6pre9")); } - if (ImGui::Checkbox(_("Disable new NES DPCM features"),&e->song.oldDPCM)) { + if (ImGui::Checkbox(_("Disable new NES DPCM features"),&e->song.compatFlags.oldDPCM)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6.1")); } - if (ImGui::Checkbox(_("Legacy technical ALWAYS_SET_VOLUME behavior"),&e->song.oldAlwaysSetVolume)) { + if (ImGui::Checkbox(_("Legacy technical ALWAYS_SET_VOLUME behavior"),&e->song.compatFlags.oldAlwaysSetVolume)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("behavior changed in 0.6.1\nthis flag will be removed if I find out that none of the songs break after disabling it.")); } - if (ImGui::Checkbox(_("Old sample offset effect"),&e->song.oldSampleOffset)) { + if (ImGui::Checkbox(_("Old sample offset effect"),&e->song.compatFlags.oldSampleOffset)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -285,13 +285,13 @@ void FurnaceGUI::drawCompatFlags() { ImGui::EndTabItem(); } if (ImGui::BeginTabItem(_(".mod import"))) { - if (ImGui::Checkbox(_("Don't slide on the first tick of a row"),&e->song.noSlidesOnFirstTick)) { + if (ImGui::Checkbox(_("Don't slide on the first tick of a row"),&e->song.compatFlags.noSlidesOnFirstTick)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("simulates ProTracker's behavior of not applying volume/pitch slides on the first tick of a row.")); } - if (ImGui::Checkbox(_("Reset arpeggio position on row change"),&e->song.rowResetsArpPos)) { + if (ImGui::Checkbox(_("Reset arpeggio position on row change"),&e->song.compatFlags.rowResetsArpPos)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -302,15 +302,15 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::BeginTabItem(_("Pitch/Playback"))) { ImGui::Text(_("Pitch linearity:")); ImGui::Indent(); - if (ImGui::RadioButton(_("None"),!e->song.linearPitch)) { - e->song.linearPitch=0; + if (ImGui::RadioButton(_("None"),!e->song.compatFlags.linearPitch)) { + e->song.compatFlags.linearPitch=0; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("like ProTracker/FamiTracker")); } - if (ImGui::RadioButton(_("Full"),e->song.linearPitch)) { - e->song.linearPitch=1; + if (ImGui::RadioButton(_("Full"),e->song.compatFlags.linearPitch)) { + e->song.compatFlags.linearPitch=1; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -318,34 +318,34 @@ void FurnaceGUI::drawCompatFlags() { } ImGui::Unindent(); - if (e->song.linearPitch) { + if (e->song.compatFlags.linearPitch) { ImGui::SameLine(); ImGui::SetNextItemWidth(120.0f*dpiScale); - if (ImGui::InputScalar(_("Pitch slide speed multiplier"),ImGuiDataType_U8,&e->song.pitchSlideSpeed,&_ONE,&_ONE)) { - if (e->song.pitchSlideSpeed<1) e->song.pitchSlideSpeed=1; - if (e->song.pitchSlideSpeed>64) e->song.pitchSlideSpeed=64; + if (ImGui::InputScalar(_("Pitch slide speed multiplier"),ImGuiDataType_U8,&e->song.compatFlags.pitchSlideSpeed,&_ONE,&_ONE)) { + if (e->song.compatFlags.pitchSlideSpeed<1) e->song.compatFlags.pitchSlideSpeed=1; + if (e->song.compatFlags.pitchSlideSpeed>64) e->song.compatFlags.pitchSlideSpeed=64; MARK_MODIFIED; } } ImGui::Text(_("Loop modality:")); ImGui::Indent(); - if (ImGui::RadioButton(_("Reset channels"),e->song.loopModality==0)) { - e->song.loopModality=0; + if (ImGui::RadioButton(_("Reset channels"),e->song.compatFlags.loopModality==0)) { + e->song.compatFlags.loopModality=0; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("select to reset channels on loop. may trigger a voltage click on every loop!")); } - if (ImGui::RadioButton(_("Soft reset channels"),e->song.loopModality==1)) { - e->song.loopModality=1; + if (ImGui::RadioButton(_("Soft reset channels"),e->song.compatFlags.loopModality==1)) { + e->song.compatFlags.loopModality=1; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("select to turn channels off on loop.")); } - if (ImGui::RadioButton(_("Do nothing"),e->song.loopModality==2)) { - e->song.loopModality=2; + if (ImGui::RadioButton(_("Do nothing"),e->song.compatFlags.loopModality==2)) { + e->song.compatFlags.loopModality=2; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -355,22 +355,22 @@ void FurnaceGUI::drawCompatFlags() { ImGui::Text(_("Cut/delay effect policy:")); ImGui::Indent(); - if (ImGui::RadioButton(_("Strict"),e->song.delayBehavior==0)) { - e->song.delayBehavior=0; + if (ImGui::RadioButton(_("Strict"),e->song.compatFlags.delayBehavior==0)) { + e->song.compatFlags.delayBehavior=0; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("only when time is less than speed (like DefleMask/ProTracker)")); } - if (ImGui::RadioButton(_("Strict (old)"),e->song.delayBehavior==1)) { - e->song.delayBehavior=1; + if (ImGui::RadioButton(_("Strict (old)"),e->song.compatFlags.delayBehavior==1)) { + e->song.compatFlags.delayBehavior=1; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("only when time is less than or equal to speed (original buggy behavior)")); } - if (ImGui::RadioButton(_("Lax"),e->song.delayBehavior==2)) { - e->song.delayBehavior=2; + if (ImGui::RadioButton(_("Lax"),e->song.compatFlags.delayBehavior==2)) { + e->song.compatFlags.delayBehavior=2; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -380,22 +380,22 @@ void FurnaceGUI::drawCompatFlags() { ImGui::Text(_("Simultaneous jump (0B+0D) treatment:")); ImGui::Indent(); - if (ImGui::RadioButton(_("Normal"),e->song.jumpTreatment==0)) { - e->song.jumpTreatment=0; + if (ImGui::RadioButton(_("Normal"),e->song.compatFlags.jumpTreatment==0)) { + e->song.compatFlags.jumpTreatment=0; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("accept 0B+0D to jump to a specific row of an order")); } - if (ImGui::RadioButton(_("Old Furnace"),e->song.jumpTreatment==1)) { - e->song.jumpTreatment=1; + if (ImGui::RadioButton(_("Old Furnace"),e->song.compatFlags.jumpTreatment==1)) { + e->song.compatFlags.jumpTreatment=1; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("only accept the first jump effect")); } - if (ImGui::RadioButton(_("DefleMask"),e->song.jumpTreatment==2)) { - e->song.jumpTreatment=2; + if (ImGui::RadioButton(_("DefleMask"),e->song.compatFlags.jumpTreatment==2)) { + e->song.compatFlags.jumpTreatment=2; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -405,7 +405,7 @@ void FurnaceGUI::drawCompatFlags() { ImGui::EndTabItem(); } if (ImGui::BeginTabItem(_("Other"))) { - if (ImGui::Checkbox(_("Auto-insert one tick gap between notes"),&e->song.oneTickCut)) { + if (ImGui::Checkbox(_("Auto-insert one tick gap between notes"),&e->song.compatFlags.oneTickCut)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { @@ -414,37 +414,37 @@ void FurnaceGUI::drawCompatFlags() { ImGui::Separator(); - if (InvCheckbox(_("Don't reset slides after note off"),&e->song.noteOffResetsSlides)) { + if (InvCheckbox(_("Don't reset slides after note off"),&e->song.compatFlags.noteOffResetsSlides)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, note off will not reset the channel's slide effect.")); } - if (InvCheckbox(_("Don't reset portamento after reaching target"),&e->song.targetResetsSlides)) { + if (InvCheckbox(_("Don't reset portamento after reaching target"),&e->song.compatFlags.targetResetsSlides)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, the slide effect will not be disabled after it reaches its target.")); } - if (ImGui::Checkbox(_("Continuous vibrato"),&e->song.continuousVibrato)) { + if (ImGui::Checkbox(_("Continuous vibrato"),&e->song.compatFlags.continuousVibrato)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, vibrato phase/position will not be reset on a new note.")); } - if (InvCheckbox(_("Pitch macro is not linear"),&e->song.pitchMacroIsLinear)) { + if (InvCheckbox(_("Pitch macro is not linear"),&e->song.compatFlags.pitchMacroIsLinear)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, the pitch macro of an instrument is in frequency/period space.")); } - if (ImGui::Checkbox(_("Reset arpeggio effect position on new note"),&e->song.resetArpPhaseOnNewNote)) { + if (ImGui::Checkbox(_("Reset arpeggio effect position on new note"),&e->song.compatFlags.resetArpPhaseOnNewNote)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("when enabled, arpeggio effect (00xy) position is reset on a new note.")); } - if (ImGui::Checkbox(_("Volume scaling rounds up"),&e->song.ceilVolumeScaling)) { + if (ImGui::Checkbox(_("Volume scaling rounds up"),&e->song.compatFlags.ceilVolumeScaling)) { MARK_MODIFIED; } if (ImGui::IsItemHovered()) { diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 2af9d1f47..2c1918cac 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -266,7 +266,7 @@ void FurnaceGUI::drawExportVGM(bool onWindow) { ImGui::EndCombo(); } ImGui::Checkbox(_("loop"),&vgmExportLoop); - if (vgmExportLoop && e->song.loopModality==2) { + if (vgmExportLoop && e->song.compatFlags.loopModality==2) { ImGui::Text(_("loop trail:")); ImGui::Indent(); if (ImGui::RadioButton(_("auto-detect"),vgmExportTrailingTicks==-1)) { diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 5dca05399..f22a1dbb2 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -3448,10 +3448,10 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) { const char* sampleTabName=_("Sample"); if (ins->type==DIV_INS_NES) sampleTabName=_("DPCM"); if (ImGui::BeginTabItem(sampleTabName)) { - if (ins->type==DIV_INS_NES && e->song.oldDPCM) { + if (ins->type==DIV_INS_NES && e->song.compatFlags.oldDPCM) { ImGui::Text(_("new DPCM features disabled (compatibility)!")); if (ImGui::Button(_("click here to enable them."))) { - e->song.oldDPCM=false; + e->song.compatFlags.oldDPCM=false; MARK_MODIFIED; } ImGui::EndTabItem(); @@ -4694,7 +4694,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) { op.egt=egtOn; } if (egtOn) { - pushWarningColor(susOn && !e->song.linearPitch); + pushWarningColor(susOn && !e->song.compatFlags.linearPitch); if (ImGui::Checkbox(_("Pitch control"),&susOn)) { PARAMETER op.sus=susOn; // HACK: reset zoom and scroll in fixed pitch macros so that they draw correctly @@ -4703,7 +4703,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) { } popWarningColor(); if (ImGui::IsItemHovered()) { - if (susOn && !e->song.linearPitch) { + if (susOn && !e->song.compatFlags.linearPitch) { ImGui::SetTooltip(_("only works on linear pitch! go to Compatibility Flags > Pitch/Playback and set Pitch linearity to Full.")); } else { ImGui::SetTooltip(_("use op's arpeggio and pitch macros control instead of block/f-num macros")); @@ -5470,7 +5470,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) { P(CWSliderScalar("##FINE",ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN,tempID)); rightClickable } else { bool susOn=op.sus; - pushWarningColor(susOn && !e->song.linearPitch); + pushWarningColor(susOn && !e->song.compatFlags.linearPitch); if (ImGui::Checkbox(_("Pitch control"),&susOn)) { PARAMETER op.sus=susOn; // HACK: reset zoom and scroll in fixed pitch macros so that they draw correctly @@ -5479,7 +5479,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) { } popWarningColor(); if (ImGui::IsItemHovered()) { - if (susOn && !e->song.linearPitch) { + if (susOn && !e->song.compatFlags.linearPitch) { ImGui::SetTooltip(_("only works on linear pitch! go to Compatibility Flags > Pitch/Playback and set Pitch linearity to Full.")); } else { ImGui::SetTooltip(_("use op's arpeggio and pitch macros control instead of block/f-num macros")); @@ -5793,7 +5793,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) { bool susOn=op.sus; if (fixedOn) { ImGui::SameLine(); - pushWarningColor(susOn && !e->song.linearPitch); + pushWarningColor(susOn && !e->song.compatFlags.linearPitch); if (ImGui::Checkbox(_("Pitch control"),&susOn)) { PARAMETER op.sus=susOn; // HACK: reset zoom and scroll in fixed pitch macros so that they draw correctly @@ -5802,7 +5802,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) { } popWarningColor(); if (ImGui::IsItemHovered()) { - if (susOn && !e->song.linearPitch) { + if (susOn && !e->song.compatFlags.linearPitch) { ImGui::SetTooltip(_("only works on linear pitch! go to Compatibility Flags > Pitch/Playback and set Pitch linearity to Full.")); } else { ImGui::SetTooltip(_("use op's arpeggio and pitch macros control instead of block/f-num macros")); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 2dced97a5..69b90c701 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1322,11 +1322,11 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl if (ImGui::Checkbox(_("Amiga channel volumes (64)"),&amigaVol)) { altered=true; } - pushWarningColor(amigaPitch && e->song.linearPitch); + pushWarningColor(amigaPitch && e->song.compatFlags.linearPitch); if (ImGui::Checkbox(_("Amiga-like pitch (non-linear pitch only)"),&amigaPitch)) { altered=true; } - if (amigaPitch && e->song.linearPitch) { + if (amigaPitch && e->song.compatFlags.linearPitch) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("pitch linearity is set to linear. this won't do anything!"); } From 14fd246edb882e8cf808fb26578b5c4890760888 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Sun, 16 Nov 2025 11:15:21 +0400 Subject: [PATCH 24/46] improve chanosc note text and chan volume feedback --- src/gui/chanOsc.cpp | 13 ++++++++++--- src/gui/pattern.cpp | 8 ++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 8e421d23b..b298f3dd1 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -928,9 +928,16 @@ void FurnaceGUI::drawChanOsc() { } case 'n': { DivChannelState* chanState=e->getChanState(ch); - if (chanState==NULL || !(chanState->keyOn)) break; - // no more conversion necessary after the note/octave unification :> - text+=fmt::sprintf("%s",noteName(chanState->note+60)); + // ik its pretty hacky but it works + // the templated stuff is after the member we need to access so it shouldnt matter + // and no segfaults should occur + SharedChannel* chan=(SharedChannel*)e->getDispatchChanState(ch); + if (chanState==NULL || chan==NULL || !chan->active) { + text+="---"; + } else { + // no more conversion necessary after the note/octave unification :> + text+=fmt::sprintf("%s",noteName(chanState->note+60)); + } break; } case 'l': diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 65f45cb0c..b152a4f87 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -675,9 +675,9 @@ void FurnaceGUI::drawPattern() { } if (settings.channelFeedbackStyle==2 && e->isRunning()) { float amount=((float)(e->getChanState(i)->volume>>8)/(float)e->getMaxVolumeChan(i)); - if (!e->getChanState(i)->keyOn) amount=0.0f; + if (e->getChanState(i)->keyOff) amount=0.0f; keyHit[i]=amount*0.2f; - if (!muted) { + if (!muted && e->getChanState(i)->keyOn) { int note=e->getChanState(i)->note+60; if (note>=0 && note<180) { pianoKeyHit[note].value=amount; @@ -696,9 +696,9 @@ void FurnaceGUI::drawPattern() { } } else if (settings.channelFeedbackStyle==4 && e->isRunning()) { float amount=powf(chanOscVol[i],settings.channelFeedbackGamma); - if (!e->getChanState(i)->keyOn) amount=0.0f; + if (e->getChanState(i)->keyOff) amount=0.0f; keyHit[i]=amount*0.2f; - if (!muted) { + if (!muted && e->getChanState(i)->keyOn) { int note=e->getChanState(i)->note+60; if (note>=0 && note<180) { pianoKeyHit[note].value=amount; From dc0eda0445cbe697fe7e3f669e3001ccfc938e78 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 02:59:02 -0500 Subject: [PATCH 25/46] giga-refactor, part 10 fix saving --- src/engine/fileOps/fur.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 7f72ade40..f023e4fd8 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -2599,7 +2599,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // sub-songs if (sng2PtrSeek) { w->seek(sng2PtrSeek,SEEK_SET); - for (size_t i=0; i<=song.subsong.size(); i++) { + for (size_t i=0; iwriteI(subSongPtr[i]); } } From 94044ef21082c7054e5e1c7c1d6474639d8608c4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 05:42:47 -0500 Subject: [PATCH 26/46] giga-refactor, part 1 work on loading - NOT WORKING --- src/engine/fileOps/fur.cpp | 1431 +++++++++++++++++++++--------------- src/engine/song.cpp | 18 + src/engine/song.h | 15 + 3 files changed, 868 insertions(+), 596 deletions(-) diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index f023e4fd8..4eba95539 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -639,26 +639,46 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS } } +#define READ_ELEMENT_PTRS(_p) { \ + unsigned int numElements=reader.readI(); \ + for (unsigned int i=0; i insPtr; std::vector wavePtr; std::vector samplePtr; - unsigned int subSongPtr[256]; - unsigned int sysFlagsPtr[DIV_MAX_CHIPS]; - unsigned int assetDirPtr[3]; + std::vector subSongPtr; + std::vector sysFlagsPtr; + std::vector assetDirPtr; + unsigned int compatFlagPtr=0; + unsigned int commentPtr=0; + std::vector groovePtr; std::vector patPtr; - int numberOfSubSongs=0; + int tchans=0; char magic[5]; memset(magic,0,5); SafeReader reader=SafeReader(file,len); warnings=""; - assetDirPtr[0]=0; - assetDirPtr[1]=0; - assetDirPtr[2]=0; + try { DivSong ds; DivSubSong* subSong=ds.subsong[0]; + /// HEADER if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; @@ -673,18 +693,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { addWarning("this module was created with a downstream version of Furnace. certain features may not be compatible."); } - if (ds.version>=240) { - logE("not yet! I am still working on it!"); - lastError="not yet! I am still working on it!"; - delete[] file; - return false; - } - 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!"); } + // version-related compat flags if (ds.version<37) { // compat flags not stored back then ds.compatFlags.limitSlides=true; ds.compatFlags.linearPitch=1; @@ -824,6 +838,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { reader.readS(); // reserved int infoSeek=reader.readI(); + /// SONG INFO if (!reader.seek(infoSeek,SEEK_SET)) { logE("couldn't seek to info header at %d!",infoSeek); lastError="couldn't seek to info header!"; @@ -831,591 +846,809 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { return false; } - // read header - reader.read(magic,4); - if (strcmp(magic,"INFO")!=0) { - logE("invalid info header!"); - lastError="invalid info header!"; - delete[] file; - return false; - } - reader.readI(); - - subSong->timeBase=reader.readC(); - subSong->speeds.len=2; - subSong->speeds.val[0]=reader.readC(); - subSong->speeds.val[1]=reader.readC(); - subSong->arpLen=reader.readC(); - subSong->hz=reader.readF(); - - subSong->patLen=reader.readS(); - subSong->ordersLen=reader.readS(); - - subSong->hilightA=reader.readC(); - subSong->hilightB=reader.readC(); - - ds.insLen=reader.readS(); - ds.waveLen=reader.readS(); - ds.sampleLen=reader.readS(); - int numberOfPats=reader.readI(); - - if (subSong->patLen<0) { - logE("pattern length is negative!"); - lastError="pattern lengrh is negative!"; - delete[] file; - return false; - } - if (subSong->patLen>DIV_MAX_ROWS) { - logE("pattern length is too large!"); - lastError="pattern length is too large!"; - delete[] file; - return false; - } - if (subSong->ordersLen<0) { - logE("song length is negative!"); - lastError="song length is negative!"; - delete[] file; - return false; - } - if (subSong->ordersLen>DIV_MAX_PATTERNS) { - logE("song is too long!"); - lastError="song is too long!"; - delete[] file; - return false; - } - if (ds.insLen<0 || ds.insLen>256) { - logE("invalid instrument count!"); - lastError="invalid instrument count!"; - delete[] file; - return false; - } - if (ds.waveLen<0 || ds.waveLen>32768) { - logE("invalid wavetable count!"); - lastError="invalid wavetable count!"; - delete[] file; - return false; - } - if (ds.sampleLen<0 || ds.sampleLen>32768) { - logE("invalid sample count!"); - lastError="invalid sample count!"; - delete[] file; - return false; - } - if (numberOfPats<0) { - logE("invalid pattern count!"); - lastError="invalid pattern count!"; - delete[] file; - return false; - } - - logD("systems:"); - ds.systemLen=0; - for (int i=0; i=240) { + // read header (NEW) + reader.read(magic,4); + if (strcmp(magic,"INF2")!=0) { + logE("invalid info header! (new)"); + lastError="invalid info header! (new)"; delete[] file; return false; } - if (ds.system[i]!=DIV_SYSTEM_NULL) ds.systemLen=i+1; - } - int tchans=0; - for (int i=0; iDIV_MAX_CHANS) { - tchans=DIV_MAX_CHANS; - logW("too many channels!"); - } - logV("system len: %d",ds.systemLen); - if (ds.systemLen<1) { - logE("zero chips!"); - lastError="zero chips!"; - delete[] file; - return false; - } - - // system volume - for (int i=0; ii; j--) { - ds.system[j]=ds.system[j-1]; - ds.systemVol[j]=ds.systemVol[j-1]; - ds.systemPan[j]=ds.systemPan[j-1]; - } - if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; - - if (ds.system[i]==DIV_SYSTEM_GENESIS) { - ds.system[i]=DIV_SYSTEM_YM2612; - if (i<31) { - ds.system[i+1]=DIV_SYSTEM_SMS; - ds.systemVol[i+1]=ds.systemVol[i]*0.375f; - } - } - if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { - ds.system[i]=DIV_SYSTEM_YM2612_EXT; - if (i<31) { - ds.system[i+1]=DIV_SYSTEM_SMS; - ds.systemVol[i+1]=ds.systemVol[i]*0.375f; - } - } - if (ds.system[i]==DIV_SYSTEM_ARCADE) { - ds.system[i]=DIV_SYSTEM_YM2151; - if (i<31) { - ds.system[i+1]=DIV_SYSTEM_SEGAPCM_COMPAT; - } - } - i++; - } - } - - // TODO: don't call this - ds.initDefaultSystemChans(); - - ds.name=reader.readString(); - ds.author=reader.readString(); - logI("%s by %s",ds.name.c_str(),ds.author.c_str()); - - if (ds.version>=33) { - ds.tuning=reader.readF(); - } else { reader.readI(); - } - // compatibility flags - if (ds.version>=37) { - ds.compatFlags.limitSlides=reader.readC(); - ds.compatFlags.linearPitch=reader.readC(); - ds.compatFlags.loopModality=reader.readC(); - if (ds.version>=43) { - ds.compatFlags.properNoiseLayout=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=43) { - ds.compatFlags.waveDutyIsVol=reader.readC(); - } else { - reader.readC(); + // clear all sub-songs + for (DivSubSong* i: ds.subsong) { + delete i; } + ds.subsong.clear(); + subSong=NULL; - if (ds.version>=45) { - ds.compatFlags.resetMacroOnPorta=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.compatFlags.legacyVolumeSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.compatFlags.compatibleArpeggio=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.compatFlags.noteOffResetsSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.compatFlags.targetResetsSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=47) { - ds.compatFlags.arpNonPorta=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=47) { - ds.compatFlags.algMacroBehavior=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=49) { - ds.compatFlags.brokenShortcutSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=50) { - ds.compatFlags.ignoreDuplicateSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=62) { - ds.compatFlags.stopPortaOnNoteOff=reader.readC(); - ds.compatFlags.continuousVibrato=reader.readC(); - } else { - reader.readC(); - reader.readC(); - } - if (ds.version>=64) { - ds.compatFlags.brokenDACMode=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=65) { - ds.compatFlags.oneTickCut=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=66) { - ds.compatFlags.newInsTriggersInPorta=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=69) { - ds.compatFlags.arp0Reset=reader.readC(); - } else { - reader.readC(); - } - } else { - for (int i=0; i<20; i++) reader.readC(); - } - - // pointers - insPtr.reserve(ds.insLen); - for (int i=0; iordersLen); - for (int i=0; iordersLen; j++) { - subSong->orders.ord[i][j]=reader.readC(); - } - } - - for (int i=0; ipat[i].effectCols=reader.readC(); - if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>DIV_MAX_EFFECTS) { - logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); - lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); - delete[] file; - return false; - } - } - - if (ds.version>=39) { - for (int i=0; ichanShow[i]=reader.readC(); - subSong->chanShowChanOsc[i]=subSong->chanShow[i]; - } else { - unsigned char tempchar=reader.readC(); - subSong->chanShow[i]=tempchar&1; - subSong->chanShowChanOsc[i]=(tempchar&2); - } - } - - for (int i=0; ichanCollapse[i]=reader.readC(); - } - - if (ds.version<92) { - for (int i=0; ichanCollapse[i]>0) subSong->chanCollapse[i]=3; - } - } - - for (int i=0; ichanName[i]=reader.readString(); - } - - for (int i=0; ichanShortName[i]=reader.readString(); - } - - ds.notes=reader.readString(); - } - - if (ds.version>=59) { - ds.masterVol=reader.readF(); - } else { - ds.masterVol=2.0f; - } - - if (ds.version>=70) { - // extended compat flags - ds.compatFlags.brokenSpeedSel=reader.readC(); - if (ds.version>=71) { - ds.compatFlags.noSlidesOnFirstTick=reader.readC(); - ds.compatFlags.rowResetsArpPos=reader.readC(); - ds.compatFlags.ignoreJumpAtEnd=reader.readC(); - } else { - reader.readC(); - reader.readC(); - reader.readC(); - } - if (ds.version>=72) { - ds.compatFlags.buggyPortaAfterSlide=reader.readC(); - ds.compatFlags.gbInsAffectsEnvelope=reader.readC(); - } else { - reader.readC(); - reader.readC(); - } - if (ds.version>=78) { - ds.compatFlags.sharedExtStat=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=83) { - ds.compatFlags.ignoreDACModeOutsideIntendedChannel=reader.readC(); - ds.compatFlags.e1e2AlsoTakePriority=reader.readC(); - } else { - reader.readC(); - reader.readC(); - } - if (ds.version>=84) { - ds.compatFlags.newSegaPCM=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=85) { - ds.compatFlags.fbPortaPause=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=86) { - ds.compatFlags.snDutyReset=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=90) { - ds.compatFlags.pitchMacroIsLinear=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=94) { - ds.compatFlags.pitchSlideSpeed=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=97) { - ds.compatFlags.oldOctaveBoundary=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=98) { - ds.compatFlags.noOPN2Vol=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=99) { - ds.compatFlags.newVolumeScaling=reader.readC(); - ds.compatFlags.volMacroLinger=reader.readC(); - ds.compatFlags.brokenOutVol=reader.readC(); - } else { - reader.readC(); - reader.readC(); - reader.readC(); - } - if (ds.version>=100) { - ds.compatFlags.e1e2StopOnSameNote=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=101) { - ds.compatFlags.brokenPortaArp=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=108) { - ds.compatFlags.snNoLowPeriods=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=110) { - ds.compatFlags.delayBehavior=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=113) { - ds.compatFlags.jumpTreatment=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=115) { - ds.autoSystem=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=117) { - ds.compatFlags.disableSampleMacro=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=121) { - ds.compatFlags.brokenOutVol2=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=130) { - ds.compatFlags.oldArpStrategy=reader.readC(); - } else { - reader.readC(); - } - } - - // first song virtual tempo - if (ds.version>=96) { - subSong->virtualTempoN=reader.readS(); - subSong->virtualTempoD=reader.readS(); - } else { - reader.readI(); - } - - // subsongs - if (ds.version>=95) { - subSong->name=reader.readString(); - subSong->notes=reader.readString(); - numberOfSubSongs=(unsigned char)reader.readC(); - reader.readC(); // reserved - reader.readC(); - reader.readC(); - // pointers - for (int i=0; i=103) { + // song information + ds.name=reader.readString(); + ds.author=reader.readString(); ds.systemName=reader.readString(); ds.category=reader.readString(); ds.nameJ=reader.readString(); ds.authorJ=reader.readString(); ds.systemNameJ=reader.readString(); ds.categoryJ=reader.readString(); - } else { - ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); - ds.autoSystem=true; - } + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); + + ds.tuning=reader.readF(); + ds.autoSystem=reader.readC(); - // system output config - if (ds.version>=135) { + // system definition + ds.masterVol=reader.readF(); + ds.chans=(unsigned short)reader.readS(); + ds.systemLen=(unsigned short)reader.readS(); + + // TODO: remove after implementing dynamic stuff + for (int i=0; i0xff || sysID==0) { + logE("unrecognized system ID %.4x",sysID); + lastError=fmt::sprintf("unrecognized system ID %.4x!",sysID); + delete[] file; + return false; + } + ds.system[i]=systemFromFileFur(sysID); + if (ds.system[i]==DIV_SYSTEM_NULL) { + logE("unrecognized system ID %.4x",sysID); + lastError=fmt::sprintf("unrecognized system ID %.4x!",sysID); + delete[] file; + return false; + } + + // reject compound systems + const DivSysDef* sysDef=getSystemDef(ds.system[i]); + if (sysDef==NULL) { + logE("no definition for system ID %.4x",sysID); + lastError=fmt::sprintf("no definition for system ID %.4x!",sysID); + delete[] file; + return false; + } + + if (sysDef->isCompound) { + logE("system ID %.4x is compound",sysID); + lastError=fmt::sprintf("system ID %.4x is compound!",sysID); + delete[] file; + return false; + } + + ds.systemChans[i]=(unsigned short)reader.readS(); + tchans+=ds.systemChans[i]; + + if (ds.systemChans[i]<1) { + logE("invalid channel count for chip"); + lastError=fmt::sprintf("invalid channel count for chip!"); + delete[] file; + return false; + } + ds.systemVol[i]=reader.readF(); ds.systemPan[i]=reader.readF(); ds.systemPanFR[i]=reader.readF(); } + if (ds.chans!=tchans) { + logE("chip channel counts and total channels do not match"); + lastError=fmt::sprintf("chip channel counts and total channels do not match!"); + delete[] file; + return false; + } + // patchbay unsigned int conns=reader.readI(); if (conns>0) ds.patchbay.reserve(conns); for (unsigned int i=0; i=136) ds.patchbayAuto=reader.readC(); + // elements + bool hasElement=true; + while (hasElement) { + DivFileElementType elementType=(DivFileElementType)reader.readC(); + switch (elementType) { + case DIV_ELEMENT_SUBSONG: + READ_ELEMENT_PTRS(subSongPtr); + break; + case DIV_ELEMENT_CHIP_FLAGS: + READ_ELEMENT_PTRS(sysFlagsPtr); + if (sysFlagsPtr.size()!=ds.systemLen) { + logE("more chip flag pointers than there should be"); + lastError=fmt::sprintf("more chip flag pointers than there should be!"); + delete[] file; + return false; + } + break; + case DIV_ELEMENT_ASSET_DIR: + READ_ELEMENT_PTRS(assetDirPtr); + break; + case DIV_ELEMENT_INSTRUMENT: + READ_ELEMENT_PTRS(insPtr); + if (insPtr.size()>256) { + logE("invalid instrument count!"); + lastError="invalid instrument count!"; + delete[] file; + return false; + } + ds.insLen=insPtr.size(); + break; + case DIV_ELEMENT_WAVETABLE: + READ_ELEMENT_PTRS(wavePtr); + if (wavePtr.size()>32768) { + logE("invalid wavetable count!"); + lastError="invalid wavetable count!"; + delete[] file; + return false; + } + ds.waveLen=wavePtr.size(); + break; + case DIV_ELEMENT_SAMPLE: + READ_ELEMENT_PTRS(samplePtr); + if (samplePtr.size()>32768) { + logE("invalid sample count!"); + lastError="invalid sample count!"; + delete[] file; + return false; + } + ds.sampleLen=samplePtr.size(); + break; + case DIV_ELEMENT_PATTERN: + READ_ELEMENT_PTRS(patPtr); + break; + case DIV_ELEMENT_COMPAT_FLAGS: + READ_ELEMENT_UNIQUE(compatFlagPtr); + break; + case DIV_ELEMENT_COMMENTS: + READ_ELEMENT_UNIQUE(commentPtr); + break; + case DIV_ELEMENT_GROOVE: + READ_ELEMENT_PTRS(groovePtr); + break; + case DIV_ELEMENT_END: + hasElement=false; + break; + } + } + } else { + // read header (OLD) + reader.read(magic,4); + if (strcmp(magic,"INFO")!=0) { + logE("invalid info header! (old)"); + lastError="invalid info header! (old)"; + delete[] file; + return false; + } + reader.readI(); - if (ds.version>=138) { - ds.compatFlags.brokenPortaLegato=reader.readC(); - if (ds.version>=155) { - ds.compatFlags.brokenFMOff=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=168) { - ds.compatFlags.preNoteNoEffect=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=183) { - ds.compatFlags.oldDPCM=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=184) { - ds.compatFlags.resetArpPhaseOnNewNote=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=188) { - ds.compatFlags.ceilVolumeScaling=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=191) { - ds.compatFlags.oldAlwaysSetVolume=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=200) { - ds.compatFlags.oldSampleOffset=reader.readC(); - } else { - reader.readC(); - } - } + subSong->timeBase=reader.readC(); + subSong->speeds.len=2; + subSong->speeds.val[0]=reader.readC(); + subSong->speeds.val[1]=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); - if (ds.version>=139) { - subSong->speeds.len=reader.readC(); - for (int i=0; i<16; i++) { - subSong->speeds.val[i]=reader.readC(); + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); + + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); + + ds.insLen=reader.readS(); + ds.waveLen=reader.readS(); + ds.sampleLen=reader.readS(); + int numberOfPats=reader.readI(); + + if (subSong->patLen<0) { + logE("pattern length is negative!"); + lastError="pattern lengrh is negative!"; + delete[] file; + return false; + } + if (subSong->patLen>DIV_MAX_ROWS) { + logE("pattern length is too large!"); + lastError="pattern length is too large!"; + delete[] file; + return false; + } + if (subSong->ordersLen<0) { + logE("song length is negative!"); + lastError="song length is negative!"; + delete[] file; + return false; + } + if (subSong->ordersLen>DIV_MAX_PATTERNS) { + logE("song is too long!"); + lastError="song is too long!"; + delete[] file; + return false; + } + if (ds.insLen<0 || ds.insLen>256) { + logE("invalid instrument count!"); + lastError="invalid instrument count!"; + delete[] file; + return false; + } + if (ds.waveLen<0 || ds.waveLen>32768) { + logE("invalid wavetable count!"); + lastError="invalid wavetable count!"; + delete[] file; + return false; + } + if (ds.sampleLen<0 || ds.sampleLen>32768) { + logE("invalid sample count!"); + lastError="invalid sample count!"; + delete[] file; + return false; + } + if (numberOfPats<0) { + logE("invalid pattern count!"); + lastError="invalid pattern count!"; + delete[] file; + return false; } - // grooves - unsigned char grooveCount=reader.readC(); - ds.grooves.reserve(grooveCount); - for (int i=0; iDIV_MAX_CHANS) { + tchans=DIV_MAX_CHANS; + logW("too many channels!"); + } + logV("system len: %d",ds.systemLen); + if (ds.systemLen<1) { + logE("zero chips!"); + lastError="zero chips!"; + delete[] file; + return false; + } + + // system volume + for (int i=0; ii; j--) { + ds.system[j]=ds.system[j-1]; + ds.systemVol[j]=ds.systemVol[j-1]; + ds.systemPan[j]=ds.systemPan[j-1]; + } + if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; + + if (ds.system[i]==DIV_SYSTEM_GENESIS) { + ds.system[i]=DIV_SYSTEM_YM2612; + if (i<31) { + ds.system[i+1]=DIV_SYSTEM_SMS; + ds.systemVol[i+1]=ds.systemVol[i]*0.375f; + } + } + if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { + ds.system[i]=DIV_SYSTEM_YM2612_EXT; + if (i<31) { + ds.system[i+1]=DIV_SYSTEM_SMS; + ds.systemVol[i+1]=ds.systemVol[i]*0.375f; + } + } + if (ds.system[i]==DIV_SYSTEM_ARCADE) { + ds.system[i]=DIV_SYSTEM_YM2151; + if (i<31) { + ds.system[i+1]=DIV_SYSTEM_SEGAPCM_COMPAT; + } + } + i++; + } + } + + // TODO: don't call this + ds.initDefaultSystemChans(); + + ds.name=reader.readString(); + ds.author=reader.readString(); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); + + if (ds.version>=33) { + ds.tuning=reader.readF(); + } else { + reader.readI(); + } + + // compatibility flags + if (ds.version>=37) { + ds.compatFlags.limitSlides=reader.readC(); + ds.compatFlags.linearPitch=reader.readC(); + ds.compatFlags.loopModality=reader.readC(); + if (ds.version>=43) { + ds.compatFlags.properNoiseLayout=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=43) { + ds.compatFlags.waveDutyIsVol=reader.readC(); + } else { + reader.readC(); } - ds.grooves.push_back(gp); + if (ds.version>=45) { + ds.compatFlags.resetMacroOnPorta=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.compatFlags.legacyVolumeSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.compatFlags.compatibleArpeggio=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.compatFlags.noteOffResetsSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.compatFlags.targetResetsSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=47) { + ds.compatFlags.arpNonPorta=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=47) { + ds.compatFlags.algMacroBehavior=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=49) { + ds.compatFlags.brokenShortcutSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=50) { + ds.compatFlags.ignoreDuplicateSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=62) { + ds.compatFlags.stopPortaOnNoteOff=reader.readC(); + ds.compatFlags.continuousVibrato=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=64) { + ds.compatFlags.brokenDACMode=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=65) { + ds.compatFlags.oneTickCut=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=66) { + ds.compatFlags.newInsTriggersInPorta=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=69) { + ds.compatFlags.arp0Reset=reader.readC(); + } else { + reader.readC(); + } + } else { + for (int i=0; i<20; i++) reader.readC(); + } + + // pointers + insPtr.reserve(ds.insLen); + for (int i=0; iordersLen); + for (int i=0; iordersLen; j++) { + subSong->orders.ord[i][j]=reader.readC(); + } + } + + for (int i=0; ipat[i].effectCols=reader.readC(); + if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>DIV_MAX_EFFECTS) { + logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); + delete[] file; + return false; + } + } + + if (ds.version>=39) { + for (int i=0; ichanShow[i]=reader.readC(); + subSong->chanShowChanOsc[i]=subSong->chanShow[i]; + } else { + unsigned char tempchar=reader.readC(); + subSong->chanShow[i]=tempchar&1; + subSong->chanShowChanOsc[i]=(tempchar&2); + } + } + + for (int i=0; ichanCollapse[i]=reader.readC(); + } + + if (ds.version<92) { + for (int i=0; ichanCollapse[i]>0) subSong->chanCollapse[i]=3; + } + } + + for (int i=0; ichanName[i]=reader.readString(); + } + + for (int i=0; ichanShortName[i]=reader.readString(); + } + + ds.notes=reader.readString(); + } + + if (ds.version>=59) { + ds.masterVol=reader.readF(); + } else { + ds.masterVol=2.0f; + } + + if (ds.version>=70) { + // extended compat flags + ds.compatFlags.brokenSpeedSel=reader.readC(); + if (ds.version>=71) { + ds.compatFlags.noSlidesOnFirstTick=reader.readC(); + ds.compatFlags.rowResetsArpPos=reader.readC(); + ds.compatFlags.ignoreJumpAtEnd=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + if (ds.version>=72) { + ds.compatFlags.buggyPortaAfterSlide=reader.readC(); + ds.compatFlags.gbInsAffectsEnvelope=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=78) { + ds.compatFlags.sharedExtStat=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=83) { + ds.compatFlags.ignoreDACModeOutsideIntendedChannel=reader.readC(); + ds.compatFlags.e1e2AlsoTakePriority=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=84) { + ds.compatFlags.newSegaPCM=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=85) { + ds.compatFlags.fbPortaPause=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=86) { + ds.compatFlags.snDutyReset=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=90) { + ds.compatFlags.pitchMacroIsLinear=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=94) { + ds.compatFlags.pitchSlideSpeed=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=97) { + ds.compatFlags.oldOctaveBoundary=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=98) { + ds.compatFlags.noOPN2Vol=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=99) { + ds.compatFlags.newVolumeScaling=reader.readC(); + ds.compatFlags.volMacroLinger=reader.readC(); + ds.compatFlags.brokenOutVol=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + if (ds.version>=100) { + ds.compatFlags.e1e2StopOnSameNote=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=101) { + ds.compatFlags.brokenPortaArp=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=108) { + ds.compatFlags.snNoLowPeriods=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=110) { + ds.compatFlags.delayBehavior=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=113) { + ds.compatFlags.jumpTreatment=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=115) { + ds.autoSystem=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=117) { + ds.compatFlags.disableSampleMacro=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=121) { + ds.compatFlags.brokenOutVol2=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=130) { + ds.compatFlags.oldArpStrategy=reader.readC(); + } else { + reader.readC(); + } + } + + // first song virtual tempo + if (ds.version>=96) { + subSong->virtualTempoN=reader.readS(); + subSong->virtualTempoD=reader.readS(); + } else { + reader.readI(); + } + + // subsongs + if (ds.version>=95) { + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + int numberOfSubSongs=(unsigned char)reader.readC(); + reader.readC(); // reserved + reader.readC(); + reader.readC(); + // pointers + for (int i=0; i=103) { + ds.systemName=reader.readString(); + ds.category=reader.readString(); + ds.nameJ=reader.readString(); + ds.authorJ=reader.readString(); + ds.systemNameJ=reader.readString(); + ds.categoryJ=reader.readString(); + } else { + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); + ds.autoSystem=true; + } + + // system output config + if (ds.version>=135) { + for (int i=0; i0) ds.patchbay.reserve(conns); + for (unsigned int i=0; i=136) ds.patchbayAuto=reader.readC(); + + if (ds.version>=138) { + ds.compatFlags.brokenPortaLegato=reader.readC(); + if (ds.version>=155) { + ds.compatFlags.brokenFMOff=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=168) { + ds.compatFlags.preNoteNoEffect=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=183) { + ds.compatFlags.oldDPCM=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=184) { + ds.compatFlags.resetArpPhaseOnNewNote=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=188) { + ds.compatFlags.ceilVolumeScaling=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=191) { + ds.compatFlags.oldAlwaysSetVolume=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=200) { + ds.compatFlags.oldSampleOffset=reader.readC(); + } else { + reader.readC(); + } + } + + if (ds.version>=139) { + subSong->speeds.len=reader.readC(); + for (int i=0; i<16; i++) { + subSong->speeds.val[i]=reader.readC(); + } + + // grooves + unsigned char grooveCount=reader.readC(); + ds.grooves.reserve(grooveCount); + for (int i=0; i=156) { + assetDirPtr.push_back(reader.readI()); + assetDirPtr.push_back(reader.readI()); + assetDirPtr.push_back(reader.readI()); } } - if (ds.version>=156) { - assetDirPtr[0]=reader.readI(); - assetDirPtr[1]=reader.readI(); - assetDirPtr[2]=reader.readI(); + // read compatibility flags + if (compatFlagPtr) { + DivConfig c; + if (!reader.seek(compatFlagPtr,SEEK_SET)) { + logE("couldn't seek to compat flags!"); + lastError=fmt::sprintf("couldn't seek to compat flags!"); + ds.unload(); + delete[] file; + return false; + } + + if (!song.compatFlags.readData(reader)) { + logE("invalid compat flag header!"); + lastError="invalid compat flag header!"; + ds.unload(); + delete[] file; + return false; + } } + // read song comments + if (commentPtr) { + if (!reader.seek(commentPtr,SEEK_SET)) { + logE("couldn't seek to song comments!"); + lastError=fmt::sprintf("couldn't seek to song comments!"); + ds.unload(); + delete[] file; + return false; + } + + reader.read(magic,4); + if (strcmp(magic,"CMNT")!=0) { + logE("invalid comment header!"); + lastError="invalid comment header!"; + ds.unload(); + delete[] file; + return false; + } + reader.readI(); + + song.notes=reader.readString(); + } + + // TODO: read grooves + // read system flags if (ds.version>=119) { logD("reading chip flags..."); - for (int i=0; i=156) { logD("reading asset directories..."); - if (!reader.seek(assetDirPtr[0],SEEK_SET)) { - logE("couldn't seek to ins dir!"); - lastError=fmt::sprintf("couldn't read instrument directory"); - ds.unload(); - delete[] file; - return false; - } - if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) { - lastError="invalid instrument directory data!"; - ds.unload(); - delete[] file; - return false; + if (assetDirPtr.size()>0) { + if (!reader.seek(assetDirPtr[0],SEEK_SET)) { + logE("couldn't seek to ins dir!"); + lastError=fmt::sprintf("couldn't read instrument directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) { + lastError="invalid instrument directory data!"; + ds.unload(); + delete[] file; + return false; + } } - if (!reader.seek(assetDirPtr[1],SEEK_SET)) { - logE("couldn't seek to wave dir!"); - lastError=fmt::sprintf("couldn't read wavetable directory"); - ds.unload(); - delete[] file; - return false; - } - if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) { - lastError="invalid wavetable directory data!"; - ds.unload(); - delete[] file; - return false; + if (assetDirPtr.size()>1) { + if (!reader.seek(assetDirPtr[1],SEEK_SET)) { + logE("couldn't seek to wave dir!"); + lastError=fmt::sprintf("couldn't read wavetable directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) { + lastError="invalid wavetable directory data!"; + ds.unload(); + delete[] file; + return false; + } } - if (!reader.seek(assetDirPtr[2],SEEK_SET)) { - logE("couldn't seek to sample dir!"); - lastError=fmt::sprintf("couldn't read sample directory"); - ds.unload(); - delete[] file; - return false; - } - if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) { - lastError="invalid sample directory data!"; - ds.unload(); - delete[] file; - return false; + if (assetDirPtr.size()>2) { + if (!reader.seek(assetDirPtr[2],SEEK_SET)) { + logE("couldn't seek to sample dir!"); + lastError=fmt::sprintf("couldn't read sample directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) { + lastError="invalid sample directory data!"; + ds.unload(); + delete[] file; + return false; + } } } // read subsongs if (ds.version>=95) { - ds.subsong.reserve(numberOfSubSongs); - for (int i=0; i +#include #include static DivCompatFlags defaultFlags; @@ -952,6 +953,23 @@ bool DivCompatFlags::areDefaults() { return (*this==defaultFlags); } +bool DivCompatFlags::readData(SafeReader& reader) { + DivConfig c; + unsigned char magic[4]; + + reader.read(magic,4); + if (memcmp(magic,"CFLG",4)!=0) { + return false; + } + reader.readI(); + + String data=reader.readString(); + c.loadFromMemory(data.c_str()); + + // TODO: this + return true; +} + #define CHECK_AND_STORE_BOOL(_x) \ if (_x!=defaultFlags._x) { \ c.set(#_x,_x); \ diff --git a/src/engine/song.h b/src/engine/song.h index 35cf30c6f..15bd303e3 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -160,6 +160,20 @@ enum DivEffectType: unsigned short { DIV_EFFECT_FILTER }; +enum DivFileElementType: unsigned char { + DIV_ELEMENT_END=0, + DIV_ELEMENT_SUBSONG, + DIV_ELEMENT_CHIP_FLAGS, + DIV_ELEMENT_ASSET_DIR, + DIV_ELEMENT_INSTRUMENT, + DIV_ELEMENT_WAVETABLE, + DIV_ELEMENT_SAMPLE, + DIV_ELEMENT_PATTERN, + DIV_ELEMENT_COMPAT_FLAGS, + DIV_ELEMENT_COMMENTS, + DIV_ELEMENT_GROOVE +}; + struct DivGroovePattern { unsigned char val[16]; unsigned char len; @@ -349,6 +363,7 @@ struct DivCompatFlags { void setDefaults(); bool areDefaults(); + bool readData(SafeReader& reader); void putData(SafeWriter* w); bool operator==(const DivCompatFlags& other) { From 334d8708e277d8be7c53a2b84e9c3ea971f0211b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 07:13:30 -0500 Subject: [PATCH 27/46] giga-refactor, part 12 loading works --- src/engine/fileOps/fur.cpp | 161 +++++++++++++++++++------------------ src/engine/song.cpp | 156 ++++++++++++++++++++++++++++++++++- src/engine/song.h | 18 ++++- 3 files changed, 250 insertions(+), 85 deletions(-) diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 4eba95539..34ca3c62e 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -883,11 +883,19 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.chans=(unsigned short)reader.readS(); ds.systemLen=(unsigned short)reader.readS(); + if (ds.systemLen<1) { + logE("zero chips!"); + lastError="zero chips!"; + delete[] file; + return false; + } + // TODO: remove after implementing dynamic stuff for (int i=0; i0xff || sysID==0) { @@ -923,6 +931,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.systemChans[i]=(unsigned short)reader.readS(); tchans+=ds.systemChans[i]; + logD("- %d: %.2x (%s, %d channels)",i,sysID,getSystemName(ds.system[i]),ds.systemChans[i]); + if (ds.systemChans[i]<1) { logE("invalid channel count for chip"); lastError=fmt::sprintf("invalid channel count for chip!"); @@ -952,13 +962,28 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { // elements bool hasElement=true; + bool seenElement[DIV_ELEMENT_MAX]; + memset(seenElement,0,DIV_ELEMENT_MAX*sizeof(bool)); + logD("elements present in this song:"); while (hasElement) { DivFileElementType elementType=(DivFileElementType)reader.readC(); + if (elementType256) { logE("invalid instrument count!"); @@ -981,6 +1008,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.insLen=insPtr.size(); break; case DIV_ELEMENT_WAVETABLE: + logD("- wavetables"); READ_ELEMENT_PTRS(wavePtr); if (wavePtr.size()>32768) { logE("invalid wavetable count!"); @@ -991,6 +1019,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.waveLen=wavePtr.size(); break; case DIV_ELEMENT_SAMPLE: + logD("- samples"); READ_ELEMENT_PTRS(samplePtr); if (samplePtr.size()>32768) { logE("invalid sample count!"); @@ -1001,22 +1030,42 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.sampleLen=samplePtr.size(); break; case DIV_ELEMENT_PATTERN: + logD("- patterns"); READ_ELEMENT_PTRS(patPtr); break; case DIV_ELEMENT_COMPAT_FLAGS: + logD("- compat flags"); READ_ELEMENT_UNIQUE(compatFlagPtr); break; case DIV_ELEMENT_COMMENTS: + logD("- song comments"); READ_ELEMENT_UNIQUE(commentPtr); break; case DIV_ELEMENT_GROOVE: + logD("- grooves"); READ_ELEMENT_PTRS(groovePtr); break; case DIV_ELEMENT_END: hasElement=false; break; + default: { + logD("- UNKNOWN"); + // skip element + unsigned int numElements=reader.readI(); + for (unsigned int i=0; itimeBase=reader.readC(); subSong->speeds.len=2; - subSong->speeds.val[0]=reader.readC(); - subSong->speeds.val[1]=reader.readC(); + subSong->speeds.val[0]=(unsigned char)reader.readC(); + subSong->speeds.val[1]=(unsigned char)reader.readC(); subSong->arpLen=reader.readC(); subSong->hz=reader.readF(); @@ -1048,7 +1097,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { if (subSong->patLen<0) { logE("pattern length is negative!"); - lastError="pattern lengrh is negative!"; + lastError="pattern length is negative!"; delete[] file; return false; } @@ -1182,7 +1231,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } } - // TODO: don't call this ds.initDefaultSystemChans(); ds.name=reader.readString(); @@ -1576,7 +1624,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { if (ds.version>=139) { subSong->speeds.len=reader.readC(); for (int i=0; i<16; i++) { - subSong->speeds.val[i]=reader.readC(); + subSong->speeds.val[i]=(unsigned char)reader.readC(); } // grooves @@ -1584,9 +1632,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.grooves.reserve(grooveCount); for (int i=0; i=119) { @@ -1736,7 +1805,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { if (ds.version>=95) { ds.subsong.reserve(subSongPtr.size()); for (size_t i=0; ireadData(reader,ds.version,ds.chans)) { + logE("%d: invalid subsong data!",i); + lastError="invalid subsong data!"; ds.unload(); delete[] file; return false; } - reader.readI(); - - subSong=ds.subsong[i+1]; - subSong->timeBase=reader.readC(); - subSong->speeds.len=2; - subSong->speeds.val[0]=reader.readC(); - subSong->speeds.val[1]=reader.readC(); - subSong->arpLen=reader.readC(); - subSong->hz=reader.readF(); - - subSong->patLen=reader.readS(); - subSong->ordersLen=reader.readS(); - - subSong->hilightA=reader.readC(); - subSong->hilightB=reader.readC(); - - if (ds.version>=96) { - subSong->virtualTempoN=reader.readS(); - subSong->virtualTempoD=reader.readS(); - } else { - reader.readI(); - } - - subSong->name=reader.readString(); - subSong->notes=reader.readString(); - - logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); - for (int j=0; jordersLen; k++) { - subSong->orders.ord[j][k]=reader.readC(); - } - } - - for (int i=0; ipat[i].effectCols=reader.readC(); - } - - for (int i=0; ichanShow[i]=reader.readC(); - subSong->chanShowChanOsc[i]=subSong->chanShow[i]; - } else { - unsigned char tempchar=reader.readC(); - subSong->chanShow[i]=tempchar&1; - subSong->chanShowChanOsc[i]=tempchar&2; - } - } - - for (int i=0; ichanCollapse[i]=reader.readC(); - } - - for (int i=0; ichanName[i]=reader.readString(); - } - - for (int i=0; ichanShortName[i]=reader.readString(); - } - - if (ds.version>=139) { - subSong->speeds.len=reader.readC(); - for (int i=0; i<16; i++) { - subSong->speeds.val[i]=reader.readC(); - } - } } } diff --git a/src/engine/song.cpp b/src/engine/song.cpp index c9cdb02dc..8d682c749 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -451,6 +451,142 @@ void DivSubSong::calcTimestamps(int chans, std::vector& groove logV("song length: %s; %" PRIu64 " ticks",ts.totalTime.toString(6,TA_TIME_FORMAT_AUTO),ts.totalTicks); } +bool DivSubSong::readData(SafeReader& reader, int version, int chans) { + unsigned char magic[4]; + + reader.read(magic,4); + + if (version>=240) { + if (memcmp(magic,"SNG2",4)!=0) { + logE("invalid subsong header!"); + return false; + } + reader.readI(); + + hz=reader.readF(); + arpLen=reader.readC(); + timeBase=reader.readC(); + + patLen=reader.readS(); + ordersLen=reader.readS(); + + hilightA=reader.readC(); + hilightB=reader.readC(); + + virtualTempoN=reader.readS(); + virtualTempoD=reader.readS(); + + speeds.len=reader.readC(); + for (int i=0; i<16; i++) { + speeds.val[i]=reader.readS(); + } + + name=reader.readString(); + notes=reader.readString(); + + logD("reading orders (%d)...",ordersLen); + for (int j=0; j=96) { + virtualTempoN=reader.readS(); + virtualTempoD=reader.readS(); + } else { + reader.readI(); + } + + name=reader.readString(); + notes=reader.readString(); + + logD("reading orders (%d)...",ordersLen); + for (int j=0; j=139) { + speeds.len=reader.readC(); + for (int i=0; i<16; i++) { + speeds.val[i]=(unsigned char)reader.readC(); + } + } + } + + return true; +} + void DivSubSong::putData(SafeWriter* w, int chans) { size_t blockStartSeek, blockEndSeek; w->write("SNG2",4); @@ -468,7 +604,7 @@ void DivSubSong::putData(SafeWriter* w, int chans) { w->writeS(virtualTempoD); // speeds - w->writeS(speeds.len); + w->writeC(speeds.len); for (int i=0; i<16; i++) { w->writeS(speeds.val[i]); } @@ -873,13 +1009,29 @@ void DivSong::unload() { subsong.clear(); } +bool DivGroovePattern::readData(SafeReader& reader) { + unsigned char magic[4]; + + reader.read(magic,4); + + if (memcmp(magic,"GROV",4)!=0) { + logE("invalid groove header!"); + return false; + } + reader.readI(); + + + + return true; +} + void DivGroovePattern::putData(SafeWriter* w) { size_t blockStartSeek, blockEndSeek; w->write("GROV",4); blockStartSeek=w->tell(); w->writeI(0); - w->writeS(len); + w->writeC(len); for (int i=0; i<16; i++) { w->writeS(val[i]); } diff --git a/src/engine/song.h b/src/engine/song.h index 15bd303e3..e8499700b 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -171,17 +171,22 @@ enum DivFileElementType: unsigned char { DIV_ELEMENT_PATTERN, DIV_ELEMENT_COMPAT_FLAGS, DIV_ELEMENT_COMMENTS, - DIV_ELEMENT_GROOVE + DIV_ELEMENT_GROOVE, + + DIV_ELEMENT_MAX }; struct DivGroovePattern { - unsigned char val[16]; - unsigned char len; + unsigned short val[16]; + unsigned short len; + bool readData(SafeReader& reader); void putData(SafeWriter* w); DivGroovePattern(): len(1) { - memset(val,6,16); + for (int i=0; i<16; i++) { + val[i]=6; } + } }; struct DivSongTimestamps { @@ -244,6 +249,11 @@ struct DivSubSong { */ void calcTimestamps(int chans, std::vector& grooves, int jumpTreatment, int ignoreJumpAtEnd, int brokenSpeedSel, int delayBehavior, int firstPat=0); + /** + * read sub-song data. + */ + bool readData(SafeReader& reader, int version, int chans); + /** * write sub-song block to a SafeWriter. */ From 663f32d9d4d221fe54fea70a53260e6b03a23add Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 15:45:24 -0500 Subject: [PATCH 28/46] giga-refactor, part 13 get rid of time base --- src/engine/engine.cpp | 2 +- src/engine/fileOps/dmf.cpp | 14 ++++++++------ src/engine/fileOps/fur.cpp | 6 +++++- src/engine/fileOps/text.cpp | 1 - src/engine/playback.cpp | 18 ++++++++---------- src/engine/song.cpp | 24 ++++++++++++++---------- src/engine/song.h | 4 ++-- src/gui/gui.cpp | 4 +--- src/gui/speed.cpp | 5 ++++- 9 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index b6bb98de4..153923c0c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -775,7 +775,7 @@ int DivEngine::duplicateSubSong(int index) { theCopy->notes=theOrig->notes; theCopy->hilightA=theOrig->hilightA; theCopy->hilightB=theOrig->hilightB; - theCopy->timeBase=theOrig->timeBase; + theCopy->effectDivider=theOrig->effectDivider; theCopy->arpLen=theOrig->arpLen; theCopy->speeds=theOrig->speeds; theCopy->virtualTempoN=theOrig->virtualTempoN; diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index a009848bc..98f2edecb 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -256,11 +256,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { bool customTempo=false; - ds.subsong[0]->timeBase=reader.readC(); + unsigned char oldTimeBase=reader.readC(); ds.subsong[0]->speeds.len=2; - ds.subsong[0]->speeds.val[0]=reader.readC(); + ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC(); if (ds.version>0x07) { - ds.subsong[0]->speeds.val[1]=reader.readC(); + ds.subsong[0]->speeds.val[1]=(unsigned char)reader.readC(); bool pal=reader.readC(); ds.subsong[0]->hz=pal?60:50; customTempo=reader.readC(); @@ -317,7 +317,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } if (ds.system[0]==DIV_SYSTEM_YMU759) { - switch (ds.subsong[0]->timeBase) { + switch (oldTimeBase) { case 0: ds.subsong[0]->hz=248; break; @@ -340,8 +340,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.subsong[0]->hz=248; break; } - ds.subsong[0]->timeBase=0; addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); + } else { + ds.subsong[0]->speeds.val[0]*=(oldTimeBase+1); + ds.subsong[0]->speeds.val[1]*=(oldTimeBase+1); } logV("%x",reader.tell()); @@ -1346,7 +1348,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { int intHz=curSubSong->hz; - w->writeC(curSubSong->timeBase); + w->writeC(0); w->writeC(curSubSong->speeds.val[0]); w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]); w->writeC((intHz<=53)?0:1); diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 34ca3c62e..12b5c6826 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -1077,7 +1077,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } reader.readI(); - subSong->timeBase=reader.readC(); + unsigned char oldTimeBase=reader.readC(); subSong->speeds.len=2; subSong->speeds.val[0]=(unsigned char)reader.readC(); subSong->speeds.val[1]=(unsigned char)reader.readC(); @@ -1641,6 +1641,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } } + for (int i=0; i<16; i++) { + subSong->speeds.val[i]*=(oldTimeBase+1); + } + if (ds.version>=156) { assetDirPtr.push_back(reader.readI()); assetDirPtr.push_back(reader.readI()); diff --git a/src/engine/fileOps/text.cpp b/src/engine/fileOps/text.cpp index bab5dc7ea..8b12c2482 100644 --- a/src/engine/fileOps/text.cpp +++ b/src/engine/fileOps/text.cpp @@ -296,7 +296,6 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) { } 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")); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index a59fb3c8d..687a0ef9d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -705,12 +705,12 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal!=0) { // COMPAT FLAG: cut/delay effect policy (delayBehavior) // - 0: strict - // - delays equal or greater to the speed * timeBase are ignored + // - delays equal or greater to the speed are ignored (formerly time base was involved but that has been removed now) // - 1: strict old - // - delays equal or greater to the speed are ignored + // - delays greater to the speed are ignored // - 2: lax (default) // - no delay is ever ignored unless overridden by another - bool comparison=(song.compatFlags.delayBehavior==1)?(effectVal<=nextSpeed):(effectVal<(nextSpeed*(curSubSong->timeBase+1))); + bool comparison=(song.compatFlags.delayBehavior==1)?(effectVal<=nextSpeed):(effectVal<(nextSpeed)); if (song.compatFlags.delayBehavior==2) comparison=true; if (comparison) { // set the delay row, order and timer @@ -1868,16 +1868,16 @@ void DivEngine::nextRow() { // if the pattern length is odd and the current order is odd, use speed 2 for even rows and speed 1 for odd ones if ((curSubSong->patLen&1) && curOrder&1) { - ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); + ticks=((curRow&1)?speed2:speed1); nextSpeed=(curRow&1)?speed1:speed2; } else { - ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1); + ticks=((curRow&1)?speed1:speed2); nextSpeed=(curRow&1)?speed2:speed1; } } else { // normal speed alternation // set the number of ticks and cycle to the next speed - ticks=speeds.val[curSpeed]*(curSubSong->timeBase+1); + ticks=speeds.val[curSpeed]; curSpeed++; if (curSpeed>=speeds.len) curSpeed=0; // cache the next speed for future operations @@ -2633,20 +2633,18 @@ void DivEngine::runMidiClock(int totalCycles) { output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); } - // calculate tempo using highlight, timeBase, tick rate, speeds and virtual tempo + // calculate tempo using highlight, tick rate, speeds and virtual tempo double hl=curSubSong->hilightA; if (hl<=0.0) hl=4.0; - double timeBase=curSubSong->timeBase+1; double speedSum=0; double vD=virtualTempoD; for (int i=0; i& groove if (effectVal!=0) { // COMPAT FLAG: cut/delay effect policy (delayBehavior) // - 0: strict - // - delays equal or greater to the speed * timeBase are ignored + // - delays equal or greater to the speed are ignored (formerly time base was involved but that has been removed now) // - 1: strict old - // - delays equal or greater to the speed are ignored + // - delays greater to the speed are ignored // - 2: lax (default) // - no delay is ever ignored unless overridden by another - bool comparison=(delayBehavior==1)?(effectVal<=nextSpeed):(effectVal<(nextSpeed*(timeBase+1))); + bool comparison=(delayBehavior==1)?(effectVal<=nextSpeed):(effectVal<(nextSpeed)); if (delayBehavior==2) comparison=true; if (comparison) { // set the delay row, order and timer @@ -327,16 +327,16 @@ void DivSubSong::calcTimestamps(int chans, std::vector& groove // we subtract firstPat from curOrder as firstPat is used by a function which finds sub-songs // (the beginning of a new sub-song will be in order 0) if ((patLen&1) && (curOrder-firstPat)&1) { - ticks=((curRow&1)?speed2:speed1)*(timeBase+1); + ticks=((curRow&1)?speed2:speed1); nextSpeed=(curRow&1)?speed1:speed2; } else { - ticks=((curRow&1)?speed1:speed2)*(timeBase+1); + ticks=((curRow&1)?speed1:speed2); nextSpeed=(curRow&1)?speed2:speed1; } } else { // normal speed alternation // set the number of ticks and cycle to the next speed - ticks=curSpeeds.val[curSpeed]*(timeBase+1); + ticks=curSpeeds.val[curSpeed]; curSpeed++; if (curSpeed>=curSpeeds.len) curSpeed=0; // cache the next speed for future operations @@ -465,7 +465,7 @@ bool DivSubSong::readData(SafeReader& reader, int version, int chans) { hz=reader.readF(); arpLen=reader.readC(); - timeBase=reader.readC(); + effectDivider=reader.readC(); patLen=reader.readS(); ordersLen=reader.readS(); @@ -519,7 +519,7 @@ bool DivSubSong::readData(SafeReader& reader, int version, int chans) { } reader.readI(); - timeBase=reader.readC(); + unsigned char oldTimeBase=reader.readC(); speeds.len=2; speeds.val[0]=(unsigned char)reader.readC(); speeds.val[1]=(unsigned char)reader.readC(); @@ -582,6 +582,10 @@ bool DivSubSong::readData(SafeReader& reader, int version, int chans) { speeds.val[i]=(unsigned char)reader.readC(); } } + + for (int i=0; i<16; i++) { + speeds.val[i]*=(oldTimeBase+1); + } } return true; @@ -595,7 +599,7 @@ void DivSubSong::putData(SafeWriter* w, int chans) { w->writeF(hz); w->writeC(arpLen); - w->writeC(timeBase); + w->writeC(effectDivider); w->writeS(patLen); w->writeS(ordersLen); w->writeC(hilightA); @@ -819,7 +823,7 @@ void DivSong::findSubSongs() { theCopy->notes=i->notes; theCopy->hilightA=i->hilightA; theCopy->hilightB=i->hilightB; - theCopy->timeBase=i->timeBase; + theCopy->effectDivider=i->effectDivider; theCopy->arpLen=i->arpLen; theCopy->speeds=i->speeds; theCopy->virtualTempoN=i->virtualTempoN; diff --git a/src/engine/song.h b/src/engine/song.h index e8499700b..7c2745d1d 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -226,7 +226,7 @@ struct DivSongTimestamps { struct DivSubSong { String name, notes; unsigned char hilightA, hilightB; - unsigned char timeBase, arpLen; + unsigned char effectDivider, arpLen; DivGroovePattern speeds; short virtualTempoN, virtualTempoD; float hz; @@ -269,7 +269,7 @@ struct DivSubSong { DivSubSong(): hilightA(4), hilightB(16), - timeBase(0), + effectDivider(0), arpLen(1), virtualTempoN(150), virtualTempoD(150), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e80930911..5573b94fe 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1295,16 +1295,14 @@ void FurnaceGUI::prepareLayout() { float FurnaceGUI::calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD) { float hl=e->curSubSong->hilightA; if (hl<=0.0f) hl=4.0f; - float timeBase=e->curSubSong->timeBase+1; float speedSum=0; for (int i=0; icurSubSong->timeBase+1; if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED @@ -210,6 +212,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { recalcTimestamps=true; } ImGui::SameLine(); + */ ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speeds,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD)); ImGui::TableNextRow(); From f6d0979209486c5d292d34fa309039eb9a55eac0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 17:05:43 -0500 Subject: [PATCH 29/46] don't allow chip channel count to be set to 0 --- src/engine/engine.cpp | 4 ++++ src/gui/sysConf.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 153923c0c..86dcfb5ff 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1067,6 +1067,10 @@ bool DivEngine::setSystemChans(int index, int ch, bool preserveOrder) { lastError=_("invalid index"); return false; } + if (ch<1) { + lastError=_("channel count should be at least 1"); + return false; + } if (song.chans-song.systemChans[index]+ch>DIV_MAX_CHANS) { lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS); return false; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 69b90c701..3348d2d02 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2797,7 +2797,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl updateWindowTitle(); updateROMExportAvail(); } else { - showError(fmt::sprintf(_("cannot change chip! (%s)"),e->getLastError())); + showError(e->getLastError()); } } if (sysDef!=NULL) { From 06c698e0545aa08ade133cd8275917a526c13241 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 17:33:26 -0500 Subject: [PATCH 30/46] don't show channel selector on unsupported chip --- src/gui/sysConf.cpp | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 3348d2d02..b6ace47f5 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2777,10 +2777,36 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } bool separatedYet=false; + const DivSysDef* sysDef=e->getSystemDef(type); + if (sysDef!=NULL) { + if (sysDef->minChans==sysDef->maxChans && sysDef->channels==sysDef->minChans) { + supportsChannelCount=false; + } + if (!supportsChannelCount) { + if (e->song.systemChans[chan]!=sysDef->channels) { + ImGui::Separator(); + separatedYet=true; + + ImGui::TextUnformatted(_("irregular channel count detected!")); + if (ImGui::Button(_("click here to fix it."))) { + if (e->setSystemChans(chan,sysDef->channels,preserveChanPos)) { + MARK_MODIFIED; + recalcTimestamps=true; + if (e->song.autoSystem) { + autoDetectSystem(); + } + updateWindowTitle(); + updateROMExportAvail(); + } else { + showError(e->getLastError()); + } + } + } + } + } if (supportsChannelCount) { ImGui::Separator(); separatedYet=true; - const DivSysDef* sysDef=e->getSystemDef(type); int chCount=e->song.systemChans[chan]; ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(_("Channels")); From 96152f90f3d73cb6269a0b732f70e7c7fca8cfcc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 20:06:34 -0500 Subject: [PATCH 31/46] don't allow going out of bounds --- src/gui/sysConf.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b6ace47f5..527afa878 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2812,7 +2812,12 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl ImGui::TextUnformatted(_("Channels")); ImGui::SameLine(); ImGui::SetNextItemWidth(120.0f*dpiScale); - ImGui::InputInt("##ChCount",&chCount,0,0); + if (ImGui::InputInt("##ChCount",&chCount,0,0)) { + if (sysDef!=NULL) { + if (chCountminChans) chCount=sysDef->minChans; + if (chCount>sysDef->maxChans) chCount=sysDef->maxChans; + } + } if (ImGui::IsItemDeactivatedAfterEdit() && chCount!=e->song.systemChans[chan]) { if (e->setSystemChans(chan,chCount,preserveChanPos)) { MARK_MODIFIED; From 849702407985e864948c3d524792a8b6328825b4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 16 Nov 2025 20:24:27 -0500 Subject: [PATCH 32/46] GUI: speeds should be unsigned short --- src/gui/speed.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/speed.cpp b/src/gui/speed.cpp index 51878b1b6..16b216930 100644 --- a/src/gui/speed.cpp +++ b/src/gui/speed.cpp @@ -130,7 +130,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { ImGui::SetNextItemWidth(avail); if (ImGui::InputText("##SpeedG",&grooveString)) { - decodeMMLStr(grooveString,intVersion,intVersionLen,ignoredLoop,1,255,ignoredRel); + decodeMMLStr(grooveString,intVersion,intVersionLen,ignoredLoop,1,65535,ignoredRel); if (intVersionLen<1) { intVersionLen=1; intVersion[0]=6; @@ -153,7 +153,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { } } else { ImGui::SetNextItemWidth(halfAvail); - if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speeds.val[0],&_ONE,&_THREE)) { MARK_MODIFIED + if (ImGui::InputScalar("##Speed1",ImGuiDataType_U16,&e->curSubSong->speeds.val[0],&_ONE,&_THREE)) { MARK_MODIFIED if (e->curSubSong->speeds.val[0]<1) e->curSubSong->speeds.val[0]=1; if (e->isPlaying()) play(); recalcTimestamps=true; @@ -161,7 +161,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { if (e->curSubSong->speeds.len>1) { ImGui::SameLine(); ImGui::SetNextItemWidth(halfAvail); - if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speeds.val[1],&_ONE,&_THREE)) { MARK_MODIFIED + if (ImGui::InputScalar("##Speed2",ImGuiDataType_U16,&e->curSubSong->speeds.val[1],&_ONE,&_THREE)) { MARK_MODIFIED if (e->curSubSong->speeds.val[1]<1) e->curSubSong->speeds.val[1]=1; if (e->isPlaying()) play(); recalcTimestamps=true; From 9b35ca77c586426ae7312b9e903b0113221fdce4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 17 Nov 2025 19:38:45 -0500 Subject: [PATCH 33/46] giga-refactor, part 14 - READ a new way to define chip channels has been introduced, replacing the old one. it looks cleaner and is more flexible (even supporting dynamic channel count). it works by defining a function in the chip definition, which returns a DivChanDef with channel information (name, short name, type and instrument type(s)). alternatively, a list can be provided in the DivChanDefFunc() constructor, in the event channels differ greatly and/or the number of channels is small. some helper templates, such as stockChanDef and simpleChanDef also exist, which automatically map channel names and types regardless of count. --- src/engine/engine.h | 126 +--- src/engine/song.cpp | 15 +- src/engine/song.h | 122 +--- src/engine/sysDef.cpp | 1408 ++++++++++++++++++++++++--------------- src/engine/sysDef.h | 272 ++++++++ src/gui/sysMiscInfo.cpp | 43 +- 6 files changed, 1171 insertions(+), 815 deletions(-) create mode 100644 src/engine/sysDef.h diff --git a/src/engine/engine.h b/src/engine/engine.h index 5a173c829..869d0a12c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -27,6 +27,7 @@ #include "export.h" #include "dataErrors.h" #include "safeWriter.h" +#include "sysDef.h" #include "cmdStream.h" #include "filePlayer.h" #include "../audio/taAudio.h" @@ -332,131 +333,6 @@ struct DivEffectContainer { } }; -typedef int EffectValConversion(unsigned char,unsigned char); - -struct EffectHandler { - DivDispatchCmds dispatchCmd; - const char* description; - EffectValConversion* val; - EffectValConversion* val2; - EffectHandler( - DivDispatchCmds dispatchCmd_, - const char* description_, - EffectValConversion val_=NULL, - EffectValConversion val2_=NULL - ): - dispatchCmd(dispatchCmd_), - description(description_), - val(val_), - val2(val2_) {} -}; - -struct DivDoNotHandleEffect { -}; - -typedef std::unordered_map EffectHandlerMap; - -struct DivSysDef { - const char* name; - const char* nameJ; - const char* description; - unsigned char id; - unsigned char id_DMF; - int channels, minChans, maxChans; - bool isFM, isSTD, isCompound; - // width 0: variable - // height 0: no wavetable support - unsigned short waveWidth, waveHeight; - unsigned int vgmVersion; - unsigned int sampleFormatMask; - const char* chanNames[DIV_MAX_CHANS]; - const char* chanShortNames[DIV_MAX_CHANS]; - int chanTypes[DIV_MAX_CHANS]; - // 0: primary - // 1: alternate (usually PCM) - DivInstrumentType chanInsType[DIV_MAX_CHANS][2]; - const EffectHandlerMap effectHandlers; - const EffectHandlerMap postEffectHandlers; - const EffectHandlerMap preEffectHandlers; - DivSysDef( - const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, int minCh, int maxCh, - bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, unsigned int formatMask, unsigned short waveWid, unsigned short waveHei, - const char* desc, - std::initializer_list chNames, - std::initializer_list chShortNames, - std::initializer_list chTypes, - std::initializer_list chInsType1, - std::initializer_list chInsType2={}, - const EffectHandlerMap fxHandlers_={}, - const EffectHandlerMap postFxHandlers_={}, - const EffectHandlerMap preFxHandlers_={}): - name(sysName), - nameJ(sysNameJ), - description(desc), - id(fileID), - id_DMF(fileID_DMF), - channels(chans), - minChans(minCh), - maxChans(maxCh), - isFM(isFMChip), - isSTD(isSTDChip), - isCompound(compound), - waveWidth(waveWid), - waveHeight(waveHei), - vgmVersion(vgmVer), - sampleFormatMask(formatMask), - effectHandlers(fxHandlers_), - postEffectHandlers(postFxHandlers_), - preEffectHandlers(preFxHandlers_) { - memset(chanNames,0,DIV_MAX_CHANS*sizeof(void*)); - memset(chanShortNames,0,DIV_MAX_CHANS*sizeof(void*)); - memset(chanTypes,0,DIV_MAX_CHANS*sizeof(int)); - for (int i=0; i=DIV_MAX_CHANS) break; - } - - index=0; - for (const char* i: chShortNames) { - chanShortNames[index++]=i; - if (index>=DIV_MAX_CHANS) break; - } - - index=0; - for (int i: chTypes) { - chanTypes[index++]=i; - if (index>=DIV_MAX_CHANS) break; - } - - index=0; - for (DivInstrumentType i: chInsType1) { - chanInsType[index++][0]=i; - if (index>=DIV_MAX_CHANS) break; - } - - index=0; - for (DivInstrumentType i: chInsType2) { - chanInsType[index++][1]=i; - if (index>=DIV_MAX_CHANS) break; - } - } -}; - -enum DivChanTypes { - DIV_CH_FM=0, - DIV_CH_PULSE=1, - DIV_CH_NOISE=2, - DIV_CH_WAVE=3, - DIV_CH_PCM=4, - DIV_CH_OP=5 -}; - extern const char* cmdName[]; class DivEngine { diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 5d8451aa0..4045bd572 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -928,17 +928,20 @@ void DivSong::recalcChans() { dispatchChanOfChan[chanIndex]=-1; } dispatchFirstChan[chanIndex]=firstChan; - chanIndex++; - if (sysDef!=NULL) { - if (sysDef->chanInsType[j][0]!=DIV_INS_NULL) { - isInsTypePossible[sysDef->chanInsType[j][0]]=true; + chanDef[chanIndex]=sysDef->getChanDef(j); + if (chanDef[chanIndex].insType[0]!=DIV_INS_NULL) { + isInsTypePossible[chanDef[chanIndex].insType[0]]=true; } - if (sysDef->chanInsType[j][1]!=DIV_INS_NULL) { - isInsTypePossible[sysDef->chanInsType[j][1]]=true; + if (chanDef[chanIndex].insType[1]!=DIV_INS_NULL) { + isInsTypePossible[chanDef[chanIndex].insType[1]]=true; } + } else { + chanDef[chanIndex]=DivChanDef(); } + + chanIndex++; } } diff --git a/src/engine/song.h b/src/engine/song.h index 7c2745d1d..eb6dfc804 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -32,125 +32,7 @@ #include "pattern.h" #include "wavetable.h" #include "sample.h" - -enum DivSystem { - DIV_SYSTEM_NULL=0, - DIV_SYSTEM_YMU759, - DIV_SYSTEM_GENESIS, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_GENESIS_EXT, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_SMS, - DIV_SYSTEM_SMS_OPLL, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_GB, - DIV_SYSTEM_PCE, - DIV_SYSTEM_NES, - DIV_SYSTEM_NES_VRC7, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_NES_FDS, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_C64_6581, - DIV_SYSTEM_C64_8580, - DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_MSX2, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_YM2610, - DIV_SYSTEM_YM2610_EXT, - - DIV_SYSTEM_AY8910, - DIV_SYSTEM_AMIGA, - DIV_SYSTEM_YM2151, - DIV_SYSTEM_YM2612, - DIV_SYSTEM_TIA, - DIV_SYSTEM_SAA1099, - DIV_SYSTEM_AY8930, - DIV_SYSTEM_VIC20, - DIV_SYSTEM_PET, - DIV_SYSTEM_SNES, - DIV_SYSTEM_VRC6, - DIV_SYSTEM_OPLL, - DIV_SYSTEM_FDS, - DIV_SYSTEM_MMC5, - DIV_SYSTEM_N163, - DIV_SYSTEM_YM2203, - DIV_SYSTEM_YM2203_EXT, - DIV_SYSTEM_YM2608, - DIV_SYSTEM_YM2608_EXT, - DIV_SYSTEM_OPL, - DIV_SYSTEM_OPL2, - DIV_SYSTEM_OPL3, - DIV_SYSTEM_MULTIPCM, - DIV_SYSTEM_PCSPKR, - DIV_SYSTEM_POKEY, - DIV_SYSTEM_RF5C68, - DIV_SYSTEM_SWAN, - DIV_SYSTEM_OPZ, - DIV_SYSTEM_POKEMINI, - DIV_SYSTEM_SEGAPCM, - DIV_SYSTEM_VBOY, - DIV_SYSTEM_VRC7, - DIV_SYSTEM_YM2610B, - DIV_SYSTEM_SFX_BEEPER, - DIV_SYSTEM_SFX_BEEPER_QUADTONE, - DIV_SYSTEM_YM2612_EXT, - DIV_SYSTEM_SCC, - DIV_SYSTEM_OPL_DRUMS, - DIV_SYSTEM_OPL2_DRUMS, - DIV_SYSTEM_OPL3_DRUMS, - DIV_SYSTEM_YM2610_FULL, - DIV_SYSTEM_YM2610_FULL_EXT, - DIV_SYSTEM_OPLL_DRUMS, - DIV_SYSTEM_LYNX, - DIV_SYSTEM_QSOUND, - DIV_SYSTEM_VERA, - DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT, - DIV_SYSTEM_X1_010, - DIV_SYSTEM_BUBSYS_WSG, - DIV_SYSTEM_OPL4, - DIV_SYSTEM_OPL4_DRUMS, - DIV_SYSTEM_ES5506, - DIV_SYSTEM_Y8950, - DIV_SYSTEM_Y8950_DRUMS, - DIV_SYSTEM_SCC_PLUS, - DIV_SYSTEM_SOUND_UNIT, - DIV_SYSTEM_MSM6295, - DIV_SYSTEM_MSM6258, - DIV_SYSTEM_YMZ280B, - DIV_SYSTEM_NAMCO, - DIV_SYSTEM_NAMCO_15XX, - DIV_SYSTEM_NAMCO_CUS30, - DIV_SYSTEM_YM2612_DUALPCM, - DIV_SYSTEM_YM2612_DUALPCM_EXT, - DIV_SYSTEM_MSM5232, - DIV_SYSTEM_T6W28, - DIV_SYSTEM_K007232, - DIV_SYSTEM_GA20, - DIV_SYSTEM_PCM_DAC, - DIV_SYSTEM_PONG, - DIV_SYSTEM_DUMMY, - DIV_SYSTEM_YM2612_CSM, - DIV_SYSTEM_YM2610_CSM, - DIV_SYSTEM_YM2610B_CSM, - DIV_SYSTEM_YM2203_CSM, - DIV_SYSTEM_YM2608_CSM, - DIV_SYSTEM_SM8521, - DIV_SYSTEM_PV1000, - DIV_SYSTEM_K053260, - DIV_SYSTEM_TED, - DIV_SYSTEM_C140, - DIV_SYSTEM_C219, - DIV_SYSTEM_ESFM, - DIV_SYSTEM_POWERNOISE, - DIV_SYSTEM_DAVE, - DIV_SYSTEM_NDS, - DIV_SYSTEM_GBA_DMA, - DIV_SYSTEM_GBA_MINMOD, - DIV_SYSTEM_5E01, - DIV_SYSTEM_BIFURCATOR, - DIV_SYSTEM_SID2, - DIV_SYSTEM_SUPERVISION, - DIV_SYSTEM_UPD1771C, - DIV_SYSTEM_SID3, - DIV_SYSTEM_C64_PCM, - - DIV_SYSTEM_MAX -}; +#include "sysDef.h" enum DivEffectType: unsigned short { DIV_EFFECT_NULL=0, @@ -463,6 +345,8 @@ struct DivSong { int dispatchChanOfChan[DIV_MAX_CHANS]; // the first channel of a chip, indexed per channel int dispatchFirstChan[DIV_MAX_CHANS]; + // cached DivChanDef for each channel. + DivChanDef chanDef[DIV_MAX_CHANS]; std::vector possibleInsTypes; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 4d3f78e08..36098c711 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -313,44 +313,38 @@ bool DivEngine::isSTDSystem(DivSystem sys) { const char* DivEngine::getChannelName(int chan) { if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); - if (sysDefs[song.sysOfChan[chan]]==NULL) return "??"; if (song.dispatchChanOfChan[chan]<0) return "??"; - const char* ret=sysDefs[song.sysOfChan[chan]]->chanNames[song.dispatchChanOfChan[chan]]; - if (ret==NULL) return "??"; - return ret; + return song.chanDef[chan].name.c_str(); } const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); - if (sysDefs[song.sysOfChan[chan]]==NULL) return "??"; if (song.dispatchChanOfChan[chan]<0) return "??"; - const char* ret=sysDefs[song.sysOfChan[chan]]->chanShortNames[song.dispatchChanOfChan[chan]]; - if (ret==NULL) return "??"; - return ret; + return song.chanDef[chan].shortName.c_str(); } int DivEngine::getChannelType(int chan) { if (chan<0 || chan>song.chans) return DIV_CH_NOISE; - if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_CH_NOISE; if (song.dispatchChanOfChan[chan]<0) return DIV_CH_NOISE; - return sysDefs[song.sysOfChan[chan]]->chanTypes[song.dispatchChanOfChan[chan]]; + + return song.chanDef[chan].type; } DivInstrumentType DivEngine::getPreferInsType(int chan) { if (chan<0 || chan>song.chans) return DIV_INS_STD; - if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_INS_STD; if (song.dispatchChanOfChan[chan]<0) return DIV_INS_STD; - return sysDefs[song.sysOfChan[chan]]->chanInsType[song.dispatchChanOfChan[chan]][0]; + + return song.chanDef[chan].insType[0]; } DivInstrumentType DivEngine::getPreferInsSecondType(int chan) { if (chan<0 || chan>song.chans) return DIV_INS_NULL; - if (sysDefs[song.sysOfChan[chan]]==NULL) return DIV_INS_NULL; if (song.dispatchChanOfChan[chan]<0) return DIV_INS_NULL; - return sysDefs[song.sysOfChan[chan]]->chanInsType[song.dispatchChanOfChan[chan]][1]; + + return song.chanDef[chan].insType[1]; } int DivEngine::minVGMVersion(DivSystem which) { @@ -375,15 +369,21 @@ int DivEngine::minVGMVersion(DivSystem which) { // "Name", "Name (japanese, optional)", fileID, fileID_DMF, channels, minChans, maxChans, // isFM, isSTD, vgmVersion, waveWidth, waveHeight, // "Description", -// {"Channel Names", ...}, -// {"Channel Short Names", ...}, -// {chanTypes, ...}, -// {chanPreferInsType, ...}, -// {chanPreferInsType2, ...}, (optional) +// Channel Definition Function, // {{effect, {DIV_CMD_xx, "Description"}}, ...}, (effect handler, optional) // {{effect, {DIV_CMD_xx, "Description"}}, ...} (post effect handler, optional) // ); +// +// Channel Definition Function can be any of the following: +// - DivChanDefFunc(): invalid/default +// - DivChanDefFunc({...}): list of channel definitions. for example: +// DivChanDefFunc({ +// DivChanDef("Square", "S", DIV_CH_PULSE, DIV_INS_MY_SYSTEM), +// DivChanDef("PCM", "P", DIV_CH_PCM, DIV_INS_MY_SYSTEM) +// }) +// - DivChanDefFunc([]() -> DivChanDef {...}): custom function +// these are effect handler helpers. template int constVal(unsigned char, unsigned char) { return val; }; @@ -422,6 +422,38 @@ template int effectValLongShift(unsigned char c return (((((unsigned int)cmd)&((1<<(bits-8))-1))<<8)|((unsigned int)val))< std::function stockChanDef=[](unsigned short ch) -> DivChanDef { + return DivChanDef( + fmt::sprintf(_("Channel %d"),ch+1), + fmt::sprintf("%d",ch+1), + type, + insType, + insType2 + ); +}; + +template std::function simpleChanDef=[](unsigned short ch) -> DivChanDef { + return DivChanDef( + fmt::sprintf(_("Channel %d"),ch+1), + fmt::sprintf(_("CH%d"),ch+1), + type, + insType, + insType2 + ); +}; + +template std::function fmChanDef=[](unsigned short ch) -> DivChanDef { + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("F%d",ch+1), + type, + insType, + insType2 + ); +}; + void DivEngine::registerSystems() { logD("registering systems..."); @@ -823,35 +855,38 @@ void DivEngine::registerSystems() { _("Yamaha YMU759 (MA-2)"), NULL, 0x01, 0x01, 17, 17, 17, true, false, 0, false, (1U< DivChanDef { + if (ch==16) { + return DivChanDef(_("PCM"),_("PCM"),DIV_CH_PCM,DIV_INS_AMIGA); + } + return stockChanDef(ch); + }) ); sysDefs[DIV_SYSTEM_GENESIS]=new DivSysDef( _("Sega Genesis/Mega Drive"), "セガメガドライブ", 0x02, 0x02, 10, 10, 10, true, true, 0, true, 0, 0, 0, "", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef( _("Sega Genesis Extended Channel 3"), NULL, 0x42, 0x42, 13, 13, 13, true, true, 0, true, 0, 0, 0, "", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_SMS]=new DivSysDef( _("TI SN76489"), NULL, 0x03, 0x03, 4, 4, 4, false, true, 0x150, false, 0, 0, 0, _("a square/noise sound chip found on the Sega Master System, ColecoVision, Tandy, TI's own 99/4A and a few other places."), - {_("Square 1"), _("Square 2"), _("Square 3"), _("Noise")}, - {"S1", "S2", "S3", "NO"}, - {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, - {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, - {}, + DivChanDefFunc({ + DivChanDef(_("Square 1"), "S1", DIV_CH_PULSE, DIV_INS_STD), + DivChanDef(_("Square 2"), "S2", DIV_CH_PULSE, DIV_INS_STD), + DivChanDef(_("Square 3"), "S3", DIV_CH_PULSE, DIV_INS_STD), + DivChanDef(_("Noise") , "NO", DIV_CH_NOISE, DIV_INS_STD) + }), { {0x20, {DIV_CMD_STD_NOISE_MODE, _("20xy: Set noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)")}} } @@ -861,18 +896,19 @@ void DivEngine::registerSystems() { _("Sega Master System + FM Expansion"), NULL, 0x43, 0x43, 13, 13, 13, true, true, 0, true, 0, 0, 0, "", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_GB]=new DivSysDef( _("Game Boy"), NULL, 0x04, 0x04, 4, 4, 4, false, true, 0x161, false, 0, 32, 16, _("the most popular portable game console of the era."), - {_("Pulse 1"), _("Pulse 2"), _("Wavetable"), _("Noise")}, - {"S1", "S2", "WA", "NO"}, - {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE}, - {DIV_INS_GB, DIV_INS_GB, DIV_INS_GB, DIV_INS_GB}, - {}, + DivChanDefFunc({ + DivChanDef(_("Pulse 1") , "S1", DIV_CH_PULSE, DIV_INS_GB), + DivChanDef(_("Pulse 2") , "S2", DIV_CH_PULSE, DIV_INS_GB), + DivChanDef(_("Wavetable"), "WA", DIV_CH_WAVE , DIV_INS_GB), + DivChanDef(_("Noise") , "NO", DIV_CH_NOISE, DIV_INS_GB) + }), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, {0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Set noise length (0: long; 1: short)")}}, @@ -886,11 +922,7 @@ void DivEngine::registerSystems() { _("PC Engine/TurboGrafx-16"), NULL, 0x05, 0x05, 6, 6, 6, false, true, 0x161, false, 1U<), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, {0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Toggle noise mode")}}, @@ -903,11 +935,13 @@ void DivEngine::registerSystems() { _("NES (Ricoh 2A03)"), NULL, 0x06, 0x06, 5, 5, 5, false, true, 0x161, false, (1U<", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef( _("Famicom Disk System"), NULL, 0, 0x86, 6, 6, 6, false, true, 0, true, 0, 0, 0, "", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef( _("Commodore 64 (SID 6581)"), NULL, 0x47, 0x47, 3, 3, 3, false, true, 0, false, 0, 0, 0, _("this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR."), - {_("Channel 1"), _("Channel 2"), _("Channel 3")}, - {"CH1", "CH2", "CH3"}, - {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, - {}, + DivChanDefFunc(simpleChanDef), {}, c64PostEffectHandlerMap ); @@ -953,11 +983,7 @@ void DivEngine::registerSystems() { _("Commodore 64 (SID 8580)"), NULL, 0x07, 0x07, 3, 3, 3, false, true, 0, false, 0, 0, 0, _("this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.\nthis is the newer revision of the chip."), - {_("Channel 1"), _("Channel 2"), _("Channel 3")}, - {"CH1", "CH2", "CH3"}, - {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, - {}, + DivChanDefFunc(simpleChanDef), {}, c64PostEffectHandlerMap ); @@ -966,18 +992,28 @@ void DivEngine::registerSystems() { _("DefleCade"), NULL, 0x08, 0x08, 13, 13, 13, true, false, 0, true, 0, 0, 0, "", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef( _("Neo Geo CD"), NULL, 0x09, 0x09, 13, 13, 13, true, true, 0x151, false, (1U<", - {}, {}, {}, {} + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef( _("AY-3-8910"), NULL, 0x80, 0, 3, 3, 3, false, true, 0x151, false, 1U<), {}, { {0x10, {DIV_CMD_AMIGA_FILTER, _("10xx: Toggle filter (0 disables; 1 enables)")}}, @@ -1038,11 +1083,7 @@ void DivEngine::registerSystems() { _("Yamaha YM2151 (OPM)"), NULL, 0x82, 0, 8, 8, 8, true, false, 0x150, false, 0, 0, 0, _("this was Yamaha's first integrated FM chip.\nit was used in several synthesizers, computers and arcade boards."), - {_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6"), _("FM 7"), _("FM 8")}, - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, - {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, - {DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM}, - {}, + DivChanDefFunc(fmChanDef), fmEffectHandlerMap, fmOPMPostEffectHandlerMap ); @@ -1051,11 +1092,14 @@ void DivEngine::registerSystems() { _("Yamaha YM2612 (OPN2)"), NULL, 0x83, 0, 6, 6, 6, true, false, 0x150, false, 1U<), {}, waveOnlyEffectHandlerMap ); @@ -1077,11 +1117,14 @@ void DivEngine::registerSystems() { _("Philips SAA1099"), NULL, 0x97, 0, 6, 6, 6, false, true, 0x171, false, 0, 0, 0, _("supposedly an upgrade from the AY-3-8910, this was present on the Creative Music System (Game Blaster) and SAM Coupé."), - {_("PSG 1"), _("PSG 2"), _("PSG 3"), _("PSG 4"), _("PSG 5"), _("PSG 6")}, - {"S1", "S2", "S3", "S4", "S5", "S6"}, - {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099}, - {}, + DivChanDefFunc({ + DivChanDef(_("PSG 1"), "S1", DIV_CH_PULSE, DIV_INS_SAA1099), + DivChanDef(_("PSG 2"), "S2", DIV_CH_PULSE, DIV_INS_SAA1099), + DivChanDef(_("PSG 3"), "S3", DIV_CH_PULSE, DIV_INS_SAA1099), + DivChanDef(_("PSG 4"), "S4", DIV_CH_PULSE, DIV_INS_SAA1099), + DivChanDef(_("PSG 5"), "S5", DIV_CH_PULSE, DIV_INS_SAA1099), + DivChanDef(_("PSG 6"), "S6", DIV_CH_PULSE, DIV_INS_SAA1099), + }), {}, { {0x10, {DIV_CMD_STD_NOISE_MODE, _("10xy: Set channel mode (x: noise; y: tone)")}}, @@ -1094,11 +1137,11 @@ void DivEngine::registerSystems() { _("Microchip AY8930"), NULL, 0x9a, 0, 3, 3, 3, false, true, 0x151, false, 1U<), { {0x18, {DIV_CMD_SNES_ECHO_ENABLE, _("18xx: Enable echo buffer")}}, {0x19, {DIV_CMD_SNES_ECHO_DELAY, _("19xx: Set echo delay (0 to F)")}}, @@ -1173,11 +1211,11 @@ void DivEngine::registerSystems() { _("Konami VRC6"), NULL, 0x88, 0, 3, 3, 3, false, true, 0, false, 1U<), fmEffectHandlerMap, fmOPLLPostEffectHandlerMap ); @@ -1200,11 +1234,9 @@ void DivEngine::registerSystems() { _("Famicom Disk System (chip)"), NULL, 0x8a, 0, 1, 1, 1, false, true, 0x161, false, 0, 64, 64, _("a disk drive for the Famicom which also contains one wavetable channel."), - {_("FDS")}, - {"FDS"}, - {DIV_CH_WAVE}, - {DIV_INS_FDS}, - {}, + DivChanDefFunc({ + DivChanDef(_("FDS"), "FDS", DIV_CH_WAVE, DIV_INS_FDS) + }), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, }, @@ -1222,11 +1254,11 @@ void DivEngine::registerSystems() { _("MMC5"), NULL, 0x8b, 0, 3, 3, 3, false, true, 0, false, 1U<), { {0x18, {DIV_CMD_N163_CHANNEL_LIMIT, _("18xx: Change channel limits (0 to 7, x + 1)")}}, {0x20, {DIV_CMD_N163_GLOBAL_WAVE_LOAD, _("20xx: Load a waveform into memory")}}, @@ -1259,11 +1287,14 @@ void DivEngine::registerSystems() { _("Yamaha YM2203 (OPN)"), NULL, 0x8d, 0, 6, 6, 6, true, true, 0x151, false, 1U<), fmEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1356,11 +1442,7 @@ void DivEngine::registerSystems() { _("Yamaha YM3812 (OPL2)"), NULL, 0x90, 0, 9, 9, 9, true, false, 0x151, false, 0, 0, 0, _("OPL, but what if you had more waveforms to choose than the normal sine?"), - {_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6"), _("FM 7"), _("FM 8"), _("FM 9")}, - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, - {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, - {}, + DivChanDefFunc(fmChanDef), fmEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1369,11 +1451,22 @@ void DivEngine::registerSystems() { _("Yamaha YMF262 (OPL3)"), NULL, 0x91, 0, 18, 18, 18, true, false, 0x151, false, 0, 0, 0, _("OPL2, but what if you had twice the channels, 4-op mode, stereo and even more waveforms?"), - {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18")}, - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, - {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, - {}, + DivChanDefFunc([](unsigned short ch) -> DivChanDef { + if (ch<12 && !(ch&1)) { + return DivChanDef( + fmt::sprintf(_("4OP %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_OP, + DIV_INS_OPL + ); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL + ); + }), fmEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1382,11 +1475,7 @@ void DivEngine::registerSystems() { _("MultiPCM"), NULL, 0x92, 0, 28, 28, 28, false, true, 0x161, false, (1U<), {}, multiPCMPostEffectHandlerMap ); @@ -1395,31 +1484,25 @@ void DivEngine::registerSystems() { _("PC Speaker"), NULL, 0x93, 0, 1, 1, 1, false, true, 0, false, 0, 0, 0, _("good luck! you get one square and no volume control."), - {_("Square")}, - {"SQ"}, - {DIV_CH_PULSE}, - {DIV_INS_BEEPER} + DivChanDefFunc({ + DivChanDef(_("Square"), "SQ", DIV_CH_PULSE, DIV_INS_BEEPER) + }) ); sysDefs[DIV_SYSTEM_PONG]=new DivSysDef( _("Pong"), NULL, 0xfc, 0, 1, 1, 1, false, true, 0, false, 0, 0, 0, _("please don't use this chip. it was added as a joke."), - {_("Square")}, - {"SQ"}, - {DIV_CH_PULSE}, - {DIV_INS_BEEPER} + DivChanDefFunc({ + DivChanDef(_("Square"), "SQ", DIV_CH_PULSE, DIV_INS_BEEPER) + }) ); sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef( _("POKEY"), NULL, 0x94, 0, 4, 4, 4, false, true, 0x161, false, 0, 0, 0, _("TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE)."), - {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4")}, - {"CH1", "CH2", "CH3", "CH4"}, - {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY}, - {}, + DivChanDefFunc(simpleChanDef), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform (0 to 7)")}}, {0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Set AUDCTL")}}, @@ -1431,22 +1514,19 @@ void DivEngine::registerSystems() { _("Ricoh RF5C68"), NULL, 0x95, 0, 8, 8, 8, false, true, 0x151, false, 1U<) ); sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef( _("WonderSwan"), NULL, 0x96, 0, 4, 4, 4, false, true, 0x171, false, 1U<), { {0x2f, {DIV_CMD_FM_HARD_RESET, _("2Fxx: Toggle hard envelope reset on new notes")}}, }, @@ -1477,21 +1553,16 @@ void DivEngine::registerSystems() { _("Pokémon Mini"), NULL, 0x99, 0, 1, 1, 1, false, true, 0, false, 0, 0, 0, _("this one is like PC Speaker but has duty cycles."), - {_("Pulse")}, - {"P"}, - {DIV_CH_PULSE}, - {DIV_INS_POKEMINI} + DivChanDefFunc({ + DivChanDef(_("Pulse"), "P", DIV_CH_PULSE, DIV_INS_POKEMINI) + }) ); sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef( _("SegaPCM"), NULL, 0x9b, 0, 16, 16, 16, false, true, 0x151, false, 1U<), {}, segaPCMPostEffectHandlerMap ); @@ -1500,11 +1571,22 @@ void DivEngine::registerSystems() { _("Virtual Boy"), NULL, 0x9c, 0, 6, 6, 6, false, true, 0x171, false, 1U< DivChanDef { + if (ch==5) { + return DivChanDef( + _("Noise"), + "NO", + DIV_CH_NOISE, + DIV_INS_VBOY + ); + } + return DivChanDef( + fmt::sprintf(_("Channel %d"),ch+1), + fmt::sprintf(_("CH%d"),ch+1), + DIV_CH_WAVE, + DIV_INS_VBOY + ); + }), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, {0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Set noise length (0 to 7)")}}, @@ -1519,11 +1601,7 @@ void DivEngine::registerSystems() { _("Konami VRC7"), NULL, 0x9d, 0, 6, 6, 6, true, false, 0x151, false, 0, 0, 0, _("like OPLL, but even more cost reductions applied. three FM channels went missing, and drums mode did as well..."), - {_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6")}, - {"F1", "F2", "F3", "F4", "F5", "F6"}, - {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, - {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, - {}, + DivChanDefFunc(fmChanDef), fmEffectHandlerMap, fmOPLLPostEffectHandlerMap ); @@ -1532,11 +1610,24 @@ void DivEngine::registerSystems() { _("Yamaha YM2610B (OPNB2)"), NULL, 0x9e, 0, 16, 16, 16, true, false, 0x151, false, (1U<), { {0x12, {DIV_CMD_STD_NOISE_MODE, _("12xx: Set pulse width")}}, {0x17, {DIV_CMD_SAMPLE_MODE, _("17xx: Trigger overlay drum")}}, @@ -1560,11 +1647,17 @@ void DivEngine::registerSystems() { _("Yamaha YM2612 (OPN2) Extended Channel 3"), NULL, 0xa0, 0, 9, 9, 9, true, false, 0x150, false, 1U<), waveOnlyEffectHandlerMap ); @@ -1600,11 +1696,26 @@ void DivEngine::registerSystems() { _("Yamaha YM3526 (OPL) with drums"), NULL, 0xa2, 0, 11, 11, 11, true, false, 0x151, false, 0, 0, 0, _("the OPL chip but with drums mode enabled."), - {_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6"), _("Kick/FM 7"), _("Snare"), _("Tom"), _("Top"), _("HiHat")}, - {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, - {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS}, - {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + DivChanDefFunc([](unsigned short ch) -> DivChanDef { + switch (ch) { + case 6: + return DivChanDef(_("Kick/FM 7"), _("BD"), DIV_CH_NOISE, DIV_INS_OPL); + case 7: + return DivChanDef(_("Snare") , _("SD"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 8: + return DivChanDef(_("Tom") , _("TM"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 9: + return DivChanDef(_("Top") , _("TP"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 10: + return DivChanDef(_("HiHat") , _("HH"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("F%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL + ); + }), fmOPLDrumsEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1613,11 +1724,26 @@ void DivEngine::registerSystems() { _("Yamaha YM3812 (OPL2) with drums"), NULL, 0xa3, 0, 11, 11, 11, true, false, 0x151, false, 0, 0, 0, _("the OPL2 chip but with drums mode enabled."), - {_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6"), _("Kick/FM 7"), _("Snare"), _("Tom"), _("Top"), _("HiHat")}, - {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, - {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS}, - {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + DivChanDefFunc([](unsigned short ch) -> DivChanDef { + switch (ch) { + case 6: + return DivChanDef(_("Kick/FM 7"), _("BD"), DIV_CH_NOISE, DIV_INS_OPL); + case 7: + return DivChanDef(_("Snare") , _("SD"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 8: + return DivChanDef(_("Tom") , _("TM"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 9: + return DivChanDef(_("Top") , _("TP"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 10: + return DivChanDef(_("HiHat") , _("HH"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("F%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL + ); + }), fmOPLDrumsEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1626,11 +1752,34 @@ void DivEngine::registerSystems() { _("Yamaha YMF262 (OPL3) with drums"), NULL, 0xa4, 0, 20, 20, 20, true, false, 0x151, false, 0, 0, 0, _("the OPL3 chip but with drums mode enabled."), - {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("Kick/FM 16"), _("Snare"), _("Tom"), _("Top"), _("HiHat")}, - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, - {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS}, - {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + DivChanDefFunc([](unsigned short ch) -> DivChanDef { + switch (ch) { + case 15: + return DivChanDef(_("Kick/FM 16"), _("BD"), DIV_CH_NOISE, DIV_INS_OPL); + case 16: + return DivChanDef(_("Snare") , _("SD"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 17: + return DivChanDef(_("Tom") , _("TM"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 18: + return DivChanDef(_("Top") , _("TP"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 19: + return DivChanDef(_("HiHat") , _("HH"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + } + if (ch<12 && !(ch&1)) { + return DivChanDef( + fmt::sprintf(_("4OP %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_OP, + DIV_INS_OPL + ); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL + ); + }), fmOPLDrumsEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1639,11 +1788,22 @@ void DivEngine::registerSystems() { _("Yamaha YM2610 (OPNB)"), NULL, 0xa5, 0, 14, 14, 14, true, false, 0x151, false, (1U< DivChanDef { + switch (ch) { + case 6: + return DivChanDef(_("Kick") , _("BD"), DIV_CH_NOISE, DIV_INS_OPLL); + case 7: + return DivChanDef(_("Snare"), _("SD"), DIV_CH_NOISE, DIV_INS_OPLL); + case 8: + return DivChanDef(_("Tom") , _("TM"), DIV_CH_NOISE, DIV_INS_OPLL); + case 9: + return DivChanDef(_("Top") , _("TP"), DIV_CH_NOISE, DIV_INS_OPLL); + case 10: + return DivChanDef(_("HiHat"), _("HH"), DIV_CH_NOISE, DIV_INS_OPLL); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("F%d",ch+1), + DIV_CH_FM, + DIV_INS_OPLL + ); + }), fmOPLDrumsEffectHandlerMap, fmOPLLPostEffectHandlerMap ); @@ -1699,11 +1903,7 @@ void DivEngine::registerSystems() { _("Atari Lynx"), NULL, 0xa8, 0, 4, 4, 4, false, true, 0x172, false, 1U<), {}, lynxEffectHandlerMap ); @@ -1722,11 +1922,24 @@ void DivEngine::registerSystems() { _("Capcom QSound"), NULL, 0xe0, 0, 19, 19, 19, false, true, 0x161, false, (1U< DivChanDef { + if (ch>=16) { + return DivChanDef( + fmt::sprintf(_("ADPCM %d"),ch-15), + fmt::sprintf("A%d",ch-15), + DIV_CH_NOISE, + DIV_INS_QSOUND, + DIV_INS_AMIGA + ); + } + return DivChanDef( + fmt::sprintf(_("PCM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_PCM, + DIV_INS_QSOUND, + DIV_INS_AMIGA + ); + }), qSoundEffectHandlerMap ); @@ -1734,11 +1947,22 @@ void DivEngine::registerSystems() { _("VERA"), NULL, 0xac, 0, 17, 17, 17, false, true, 0, false, (1U< DivChanDef { + if (ch==16) { + return DivChanDef( + _("PCM"), + _("PCM"), + DIV_CH_PCM, + DIV_INS_AMIGA + ); + } + return DivChanDef( + fmt::sprintf(_("Channel %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_PULSE, + DIV_INS_VERA + ); + }), { {0x20, {DIV_CMD_WAVE, _("20xx: Set waveform")}}, {0x22, {DIV_CMD_STD_NOISE_MODE, _("22xx: Set duty cycle (0 to 3F)")}}, @@ -1749,11 +1973,27 @@ void DivEngine::registerSystems() { _("Yamaha YM2610B (OPNB2) Extended Channel 3"), NULL, 0xde, 0, 19, 19, 19, true, false, 0x151, false, (1U<), {}, segaPCMPostEffectHandlerMap ); @@ -1791,11 +2044,7 @@ void DivEngine::registerSystems() { _("Seta/Allumer X1-010"), NULL, 0xb0, 0, 16, 16, 16, false, true, 0x171, false, 1U<), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, {0x11, {DIV_CMD_X1_010_ENVELOPE_SHAPE, _("11xx: Set envelope shape")}}, @@ -1815,10 +2064,7 @@ void DivEngine::registerSystems() { _("Konami Bubble System WSG"), NULL, 0xad, 0, 2, 2, 2, false, true, 0, false, 0, 32, 16, _("this is the wavetable part of the Bubble System, which also had two AY-3-8910s."), - {_("Channel 1"), _("Channel 2")}, - {"CH1", "CH2"}, - {DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC}, + DivChanDefFunc(simpleChanDef), {}, waveOnlyEffectHandlerMap ); @@ -1827,11 +2073,33 @@ void DivEngine::registerSystems() { _("Yamaha YMF278B (OPL4)"), NULL, 0xae, 0, 42, 42, 42, true, true, 0x151, false, (1U< DivChanDef { + if (ch>=18) { + return DivChanDef( + fmt::sprintf(_("PCM %d"),ch-17), + fmt::sprintf("P%d",ch-17), + DIV_CH_PCM, + DIV_INS_MULTIPCM, + DIV_INS_AMIGA + ); + } + if (ch<12 && !(ch&1)) { + return DivChanDef( + fmt::sprintf(_("4OP %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_OP, + DIV_INS_OPL, + DIV_INS_OPL + ); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL, + DIV_INS_OPL + ); + }), fmEffectHandlerMap, fmOPL4PostEffectHandlerMap ); @@ -1840,11 +2108,45 @@ void DivEngine::registerSystems() { _("Yamaha YMF278B (OPL4) with drums"), NULL, 0xaf, 0, 44, 44, 44, true, true, 0x151, false, (1U< DivChanDef { + if (ch>=20) { + return DivChanDef( + fmt::sprintf(_("PCM %d"),ch-19), + fmt::sprintf("P%d",ch-19), + DIV_CH_PCM, + DIV_INS_MULTIPCM, + DIV_INS_AMIGA + ); + } + switch (ch) { + case 15: + return DivChanDef(_("Kick/FM 16"), _("BD"), DIV_CH_NOISE, DIV_INS_OPL); + case 16: + return DivChanDef(_("Snare") , _("SD"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 17: + return DivChanDef(_("Tom") , _("TM"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 18: + return DivChanDef(_("Top") , _("TP"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 19: + return DivChanDef(_("HiHat") , _("HH"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + } + if (ch<12 && !(ch&1)) { + return DivChanDef( + fmt::sprintf(_("4OP %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_OP, + DIV_INS_OPL, + DIV_INS_OPL + ); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL, + DIV_INS_OPL + ); + }), fmOPLDrumsEffectHandlerMap, fmOPL4PostEffectHandlerMap ); @@ -1882,11 +2184,7 @@ void DivEngine::registerSystems() { _("Ensoniq ES5506"), NULL, 0xb1, 0, 32, 5, 32, false, true, 0/*0x171*/, false, (1U<), es5506PreEffectHandlerMap, es5506PostEffectHandlerMap ); @@ -1895,11 +2193,23 @@ void DivEngine::registerSystems() { _("Yamaha Y8950"), NULL, 0xb2, 0, 10, 10, 10, true, false, 0x151, false, 1U< DivChanDef { + if (ch==9) { + return DivChanDef( + _("ADPCM"), + _("P"), + DIV_CH_PCM, + DIV_INS_ADPCMB, + DIV_INS_AMIGA + ); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("F%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL + ); + }), fmEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1908,11 +2218,28 @@ void DivEngine::registerSystems() { _("Yamaha Y8950 with drums"), NULL, 0xb3, 0, 12, 12, 12, true, false, 0x151, false, 1U< DivChanDef { + switch (ch) { + case 6: + return DivChanDef(_("Kick/FM 7"), _("BD"), DIV_CH_NOISE, DIV_INS_OPL); + case 7: + return DivChanDef(_("Snare") , _("SD"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 8: + return DivChanDef(_("Tom") , _("TM"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 9: + return DivChanDef(_("Top") , _("TP"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 10: + return DivChanDef(_("HiHat") , _("HH"), DIV_CH_NOISE, DIV_INS_OPL_DRUMS, DIV_INS_OPL); + case 11: + return DivChanDef(_("ADPCM") , _("P") , DIV_CH_PCM , DIV_INS_ADPCMB , DIV_INS_AMIGA); + } + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("F%d",ch+1), + DIV_CH_FM, + DIV_INS_OPL + ); + }), fmOPLDrumsEffectHandlerMap, fmOPLPostEffectHandlerMap ); @@ -1921,11 +2248,7 @@ void DivEngine::registerSystems() { _("Konami SCC+"), NULL, 0xb4, 0, 5, 5, 5, false, true, 0x161, false, 0, 32, 256, _("this is a variant of Konami's SCC chip with the last channel's wavetable being independent."), - {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5")}, - {"CH1", "CH2", "CH3", "CH4", "CH5"}, - {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, - {}, + DivChanDefFunc(simpleChanDef), waveOnlyEffectHandlerMap ); @@ -1964,11 +2287,7 @@ void DivEngine::registerSystems() { _("tildearrow Sound Unit"), NULL, 0xb5, 0, 8, 8, 8, false, true, 0, false, 1U<), {}, suEffectHandlerMap ); @@ -1977,11 +2296,7 @@ void DivEngine::registerSystems() { _("OKI MSM6295"), NULL, 0xaa, 0, 4, 4, 4, false, true, 0x161, false, 1U<), { {0x20, {DIV_CMD_SAMPLE_FREQ, _("20xx: Set chip output rate (0: clock/132; 1: clock/165)")}}, } @@ -1991,11 +2306,9 @@ void DivEngine::registerSystems() { _("OKI MSM6258"), NULL, 0xab, 0, 1, 1, 1, false, true, 0x150, false, 1U< DivChanDef { + return DivChanDef( + fmt::sprintf(_("PCM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_PCM, + DIV_INS_YMZ280B, + DIV_INS_AMIGA + ); + }) ); EffectHandlerMap namcoEffectHandlerMap={ @@ -2026,11 +2343,7 @@ void DivEngine::registerSystems() { _("Namco WSG"), NULL, 0xb9, 0, 3, 3, 3, false, true, 0, false, 0, 32, 16, _("a wavetable sound chip used in Pac-Man, among other early Namco arcade games."), - {_("Channel 1"), _("Channel 2"), _("Channel 3")}, - {"CH1", "CH2", "CH3"}, - {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO}, - {}, + DivChanDefFunc(simpleChanDef), namcoEffectHandlerMap ); @@ -2038,11 +2351,7 @@ void DivEngine::registerSystems() { _("Namco C15 WSG"), NULL, 0xba, 0, 8, 8, 8, false, true, 0, false, 0, 32, 16, _("successor of the original Namco WSG chip, used in later Namco arcade games."), - {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8")}, - {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, - {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO}, - {}, + DivChanDefFunc(simpleChanDef), namcoEffectHandlerMap ); @@ -2050,11 +2359,7 @@ void DivEngine::registerSystems() { _("Namco C30 WSG"), NULL, 0xbb, 0, 8, 8, 8, false, true, 0, false, 0, 32, 16, _("like Namco C15 but with stereo sound."), - {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8")}, - {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, - {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO}, - {}, + DivChanDefFunc(simpleChanDef), namcoC30EffectHandlerMap ); @@ -2062,11 +2367,7 @@ void DivEngine::registerSystems() { _("OKI MSM5232"), NULL, 0xbc, 0, 8, 8, 8, false, true, 0, false, 0, 0, 0, _("a square wave additive synthesis chip made by OKI. used in some arcade machines and instruments."), - {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8")}, - {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, - {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232}, - {}, + DivChanDefFunc(simpleChanDef), {}, { {0x10, {DIV_CMD_WAVE, _("10xy: Set group control (x: sustain; y: part toggle bitmask)")}}, @@ -2080,11 +2381,15 @@ void DivEngine::registerSystems() { _("Yamaha YM2612 (OPN2) with DualPCM"), NULL, 0xbe, 0, 7, 7, 7, true, false, 0x150, false, 1U<), { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, } @@ -2136,33 +2446,26 @@ void DivEngine::registerSystems() { _("Konami K007232"), NULL, 0xc6, 0, 2, 2, 2, false, true, 0, false, 1U<) ); sysDefs[DIV_SYSTEM_GA20]=new DivSysDef( _("Irem GA20"), NULL, 0xc7, 0, 4, 4, 4, false, true, 0x171, false, 1U<) ); sysDefs[DIV_SYSTEM_SUPERVISION]=new DivSysDef( _("Watara Supervision"), NULL, 0xe3, 0, 4, 4, 4, false, true, 0, false, 1U<), { {0xdf, {DIV_CMD_SAMPLE_DIR, _("DFxx: Set sample playback direction (0: normal; 1: reverse)")}} } @@ -2241,22 +2543,14 @@ void DivEngine::registerSystems() { _("MOS Technology TED"), NULL, 0xcd, 0, 2, 2, 2, false, true, 0, false, 0, 0, 0, _("two square waves (one may be turned into noise). used in the Commodore Plus/4, 16 and 116."), - {_("Channel 1"), _("Channel 2")}, - {"CH1", "CH2"}, - {DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_TED, DIV_INS_TED}, - {} + DivChanDefFunc(simpleChanDef) ); sysDefs[DIV_SYSTEM_C140]=new DivSysDef( _("Namco C140"), NULL, 0xce, 0, 24, 24, 24, false, true, 0x161, false, (1U<), {} ); @@ -2264,11 +2558,7 @@ void DivEngine::registerSystems() { _("Namco C219"), NULL, 0xcf, 0, 16, 16, 16, false, true, 0x161, false, (1U<), {}, { {0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Set noise mode")}}, @@ -2280,11 +2570,14 @@ void DivEngine::registerSystems() { _("ESS ES1xxx series (ESFM)"), NULL, 0xd1, 0, 18, 18, 18, true, false, 0, false, 0, 0, 0, _("a unique FM synth featured in PC sound cards.\nbased on the OPL3 design, but with lots of its features extended."), - {_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6"), _("FM 7"), _("FM 8"), _("FM 9"), _("FM 10"), _("FM 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18")}, - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, - {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, - {DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM, DIV_INS_ESFM}, - {}, + DivChanDefFunc([](unsigned short ch) -> DivChanDef { + return DivChanDef( + fmt::sprintf(_("FM %d"),ch+1), + fmt::sprintf("%d",ch+1), + DIV_CH_FM, + DIV_INS_ESFM + ); + }), { {0x2e, {DIV_CMD_FM_HARD_RESET, _("2Exx: Toggle hard envelope reset on new notes")}}, }, @@ -2295,11 +2588,12 @@ void DivEngine::registerSystems() { _("PowerNoise"), NULL, 0xd4, 0, 4, 4, 4, false, false, 0, false, 0, 0, 0, _("a fantasy sound chip designed by jvsTSX and The Beesh-Spweesh!\nused in the Hexheld fantasy console."), - {_("Noise 1"), _("Noise 2"), _("Noise 3"), _("Slope")}, - {"N1", "N2", "N3", "SL"}, - {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_WAVE}, - {DIV_INS_POWERNOISE, DIV_INS_POWERNOISE, DIV_INS_POWERNOISE, DIV_INS_POWERNOISE_SLOPE}, - {}, + DivChanDefFunc({ + DivChanDef(_("Noise 1"), "N1", DIV_CH_NOISE, DIV_INS_POWERNOISE), + DivChanDef(_("Noise 2"), "N2", DIV_CH_NOISE, DIV_INS_POWERNOISE), + DivChanDef(_("Noise 3"), "N3", DIV_CH_NOISE, DIV_INS_POWERNOISE), + DivChanDef(_("Slope") , "SL", DIV_CH_WAVE , DIV_INS_POWERNOISE_SLOPE), + }), { {0x20, {DIV_CMD_POWERNOISE_COUNTER_LOAD, _("20xx: Load low byte of noise channel LFSR (00 to FF) or slope channel accumulator (00 to 7F)"), constVal<0>, effectVal}}, {0x21, {DIV_CMD_POWERNOISE_COUNTER_LOAD, _("21xx: Load high byte of noise channel LFSR (00 to FF)"), constVal<1>, effectVal}}, @@ -2313,11 +2607,14 @@ void DivEngine::registerSystems() { _("Dave"), NULL, 0xd5, 0, 6, 6, 6, false, true, 0, false, 1U<), {}, { {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}}, @@ -2365,11 +2657,24 @@ void DivEngine::registerSystems() { _("Nintendo DS"), NULL, 0xd6, 0, 16, 16, 16, false, true, 0, false, (1U<), { {0x10, {DIV_CMD_BIFURCATOR_STATE_LOAD, _("10xx: Load low byte of channel sample state"), constVal<0>, effectVal}}, {0x11, {DIV_CMD_BIFURCATOR_STATE_LOAD, _("11xx: Load high byte of channel sample state"), constVal<1>, effectVal}}, @@ -2420,11 +2723,7 @@ void DivEngine::registerSystems() { _("SID2"), NULL, 0xf0, 0, 3, 3, 3, false, true, 0, false, 0, 0, 0, _("a fantasy sound chip created by LTVA. it is similar to the SID chip, but with many of its problems fixed."), - {_("Channel 1"), _("Channel 2"), _("Channel 3")}, - {"CH1", "CH2", "CH3"}, - {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_SID2, DIV_INS_SID2, DIV_INS_SID2}, - {}, + DivChanDefFunc(simpleChanDef), {}, SID2PostEffectHandlerMap ); @@ -2433,11 +2732,22 @@ void DivEngine::registerSystems() { _("SID3"), NULL, 0xf5, 0, 7, 7, 7, false, true, 0, false, (1U< DivChanDef { + if (ch==6) { + return DivChanDef( + _("Wave"), + "WA", + DIV_CH_WAVE, + DIV_INS_SID3 + ); + } + return DivChanDef( + fmt::sprintf(_("Channel %d"),ch+1), + fmt::sprintf(_("CH%d"),ch+1), + DIV_CH_NOISE, + DIV_INS_SID3 + ); + }), {}, SID3PostEffectHandlerMap ); @@ -2446,11 +2756,22 @@ void DivEngine::registerSystems() { _("Commodore 64 (SID 6581) with software PCM"), NULL, 0xe2, 0, 4, 4, 4, false, true, 0, false, (1U< DivChanDef { + if (ch==3) { + return DivChanDef( + _("PCM"), + "P", + DIV_CH_PCM, + DIV_INS_AMIGA + ); + } + return DivChanDef( + fmt::sprintf(_("Channel %d"),ch+1), + fmt::sprintf(_("CH%d"),ch+1), + DIV_CH_NOISE, + DIV_INS_C64 + ); + }), {}, c64PostEffectHandlerMap ); @@ -2459,10 +2780,7 @@ void DivEngine::registerSystems() { _("Dummy System"), NULL, 0xfd, 0, 8, 1, 128, false, true, 0, false, 0, 0, 0, _("this is a system designed for testing purposes."), - {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8")}, - {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, - {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD} + DivChanDefFunc(stockChanDef) ); for (int i=0; i +#include + +typedef int EffectValConversion(unsigned char,unsigned char); + +struct EffectHandler { + DivDispatchCmds dispatchCmd; + const char* description; + EffectValConversion* val; + EffectValConversion* val2; + EffectHandler( + DivDispatchCmds dispatchCmd_, + const char* description_, + EffectValConversion val_=NULL, + EffectValConversion val2_=NULL + ): + dispatchCmd(dispatchCmd_), + description(description_), + val(val_), + val2(val2_) {} +}; + +struct DivDoNotHandleEffect { +}; + +typedef std::unordered_map EffectHandlerMap; + +enum DivChanTypes { + DIV_CH_FM=0, + DIV_CH_PULSE=1, + DIV_CH_NOISE=2, + DIV_CH_WAVE=3, + DIV_CH_PCM=4, + DIV_CH_OP=5 +}; + +struct DivChanDef { + String name; + String shortName; + int type; + // 0: primary + // 1: alternate (usually PCM) + DivInstrumentType insType[2]; + + DivChanDef(const String& n, const String& sn, int t, DivInstrumentType insT, DivInstrumentType insT2=DIV_INS_NULL): + name(n), + shortName(sn), + type(t), + insType{insT,insT2} {} + DivChanDef(): + name("??"), + shortName("??"), + type(DIV_CH_NOISE), + insType{DIV_INS_NULL,DIV_INS_NULL} {} +}; + +struct DivChanDefFunc { + std::vector list; + std::function func; + + DivChanDef operator()(int& ch) const { + if (ch<0) { + return DivChanDef("??","??",DIV_CH_NOISE,DIV_INS_NULL); + } + if (ch<(int)list.size()) return list[ch]; + if (func==NULL) { + return DivChanDef("??","??",DIV_CH_NOISE,DIV_INS_NULL); + } + return func(ch); + } + + DivChanDefFunc(std::initializer_list l): + list(l), func(NULL) {} + DivChanDefFunc(std::function f): + list(), func(f) {} + DivChanDefFunc(): + list(), func(NULL) {} +}; + +struct DivSysDef { + const char* name; + const char* nameJ; + const char* description; + unsigned char id; + unsigned char id_DMF; + int channels, minChans, maxChans; + bool isFM, isSTD, isCompound; + // width 0: variable + // height 0: no wavetable support + unsigned short waveWidth, waveHeight; + unsigned int vgmVersion; + unsigned int sampleFormatMask; + + DivChanDefFunc getChanDef; + + const EffectHandlerMap effectHandlers; + const EffectHandlerMap postEffectHandlers; + const EffectHandlerMap preEffectHandlers; + DivSysDef( + const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, int minCh, int maxCh, + bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, unsigned int formatMask, unsigned short waveWid, unsigned short waveHei, + const char* desc, + DivChanDefFunc gcdFunc, + const EffectHandlerMap fxHandlers_={}, + const EffectHandlerMap postFxHandlers_={}, + const EffectHandlerMap preFxHandlers_={}): + name(sysName), + nameJ(sysNameJ), + description(desc), + id(fileID), + id_DMF(fileID_DMF), + channels(chans), + minChans(minCh), + maxChans(maxCh), + isFM(isFMChip), + isSTD(isSTDChip), + isCompound(compound), + waveWidth(waveWid), + waveHeight(waveHei), + vgmVersion(vgmVer), + sampleFormatMask(formatMask), + getChanDef(gcdFunc), + effectHandlers(fxHandlers_), + postEffectHandlers(postFxHandlers_), + preEffectHandlers(preFxHandlers_) { + } +}; + +enum DivSystem { + DIV_SYSTEM_NULL=0, + DIV_SYSTEM_YMU759, + DIV_SYSTEM_GENESIS, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_GENESIS_EXT, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_SMS, + DIV_SYSTEM_SMS_OPLL, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_GB, + DIV_SYSTEM_PCE, + DIV_SYSTEM_NES, + DIV_SYSTEM_NES_VRC7, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_NES_FDS, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_C64_6581, + DIV_SYSTEM_C64_8580, + DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_MSX2, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_YM2610, + DIV_SYSTEM_YM2610_EXT, + + DIV_SYSTEM_AY8910, + DIV_SYSTEM_AMIGA, + DIV_SYSTEM_YM2151, + DIV_SYSTEM_YM2612, + DIV_SYSTEM_TIA, + DIV_SYSTEM_SAA1099, + DIV_SYSTEM_AY8930, + DIV_SYSTEM_VIC20, + DIV_SYSTEM_PET, + DIV_SYSTEM_SNES, + DIV_SYSTEM_VRC6, + DIV_SYSTEM_OPLL, + DIV_SYSTEM_FDS, + DIV_SYSTEM_MMC5, + DIV_SYSTEM_N163, + DIV_SYSTEM_YM2203, + DIV_SYSTEM_YM2203_EXT, + DIV_SYSTEM_YM2608, + DIV_SYSTEM_YM2608_EXT, + DIV_SYSTEM_OPL, + DIV_SYSTEM_OPL2, + DIV_SYSTEM_OPL3, + DIV_SYSTEM_MULTIPCM, + DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_POKEY, + DIV_SYSTEM_RF5C68, + DIV_SYSTEM_SWAN, + DIV_SYSTEM_OPZ, + DIV_SYSTEM_POKEMINI, + DIV_SYSTEM_SEGAPCM, + DIV_SYSTEM_VBOY, + DIV_SYSTEM_VRC7, + DIV_SYSTEM_YM2610B, + DIV_SYSTEM_SFX_BEEPER, + DIV_SYSTEM_SFX_BEEPER_QUADTONE, + DIV_SYSTEM_YM2612_EXT, + DIV_SYSTEM_SCC, + DIV_SYSTEM_OPL_DRUMS, + DIV_SYSTEM_OPL2_DRUMS, + DIV_SYSTEM_OPL3_DRUMS, + DIV_SYSTEM_YM2610_FULL, + DIV_SYSTEM_YM2610_FULL_EXT, + DIV_SYSTEM_OPLL_DRUMS, + DIV_SYSTEM_LYNX, + DIV_SYSTEM_QSOUND, + DIV_SYSTEM_VERA, + DIV_SYSTEM_YM2610B_EXT, + DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_X1_010, + DIV_SYSTEM_BUBSYS_WSG, + DIV_SYSTEM_OPL4, + DIV_SYSTEM_OPL4_DRUMS, + DIV_SYSTEM_ES5506, + DIV_SYSTEM_Y8950, + DIV_SYSTEM_Y8950_DRUMS, + DIV_SYSTEM_SCC_PLUS, + DIV_SYSTEM_SOUND_UNIT, + DIV_SYSTEM_MSM6295, + DIV_SYSTEM_MSM6258, + DIV_SYSTEM_YMZ280B, + DIV_SYSTEM_NAMCO, + DIV_SYSTEM_NAMCO_15XX, + DIV_SYSTEM_NAMCO_CUS30, + DIV_SYSTEM_YM2612_DUALPCM, + DIV_SYSTEM_YM2612_DUALPCM_EXT, + DIV_SYSTEM_MSM5232, + DIV_SYSTEM_T6W28, + DIV_SYSTEM_K007232, + DIV_SYSTEM_GA20, + DIV_SYSTEM_PCM_DAC, + DIV_SYSTEM_PONG, + DIV_SYSTEM_DUMMY, + DIV_SYSTEM_YM2612_CSM, + DIV_SYSTEM_YM2610_CSM, + DIV_SYSTEM_YM2610B_CSM, + DIV_SYSTEM_YM2203_CSM, + DIV_SYSTEM_YM2608_CSM, + DIV_SYSTEM_SM8521, + DIV_SYSTEM_PV1000, + DIV_SYSTEM_K053260, + DIV_SYSTEM_TED, + DIV_SYSTEM_C140, + DIV_SYSTEM_C219, + DIV_SYSTEM_ESFM, + DIV_SYSTEM_POWERNOISE, + DIV_SYSTEM_DAVE, + DIV_SYSTEM_NDS, + DIV_SYSTEM_GBA_DMA, + DIV_SYSTEM_GBA_MINMOD, + DIV_SYSTEM_5E01, + DIV_SYSTEM_BIFURCATOR, + DIV_SYSTEM_SID2, + DIV_SYSTEM_SUPERVISION, + DIV_SYSTEM_UPD1771C, + DIV_SYSTEM_SID3, + DIV_SYSTEM_C64_PCM, + + DIV_SYSTEM_MAX +}; + +#endif diff --git a/src/gui/sysMiscInfo.cpp b/src/gui/sysMiscInfo.cpp index fb1f0d8a6..0810113bf 100644 --- a/src/gui/sysMiscInfo.cpp +++ b/src/gui/sysMiscInfo.cpp @@ -312,12 +312,14 @@ float FurnaceGUI::drawSystemChannelInfo(const DivSysDef* whichDef, int keyHitOff if (ledSize.x<8.0f*dpiScale) ledSize.x=8.0f*dpiScale; float x=p.x, y=p.y; for (int i=0; igetChanDef(i); + if (x+ledSize.x-0.125>tooltipWidth+p.x) { x=p.x; y+=ledSize.y+sep.y; } ImVec4 color=uiColors[GUI_COLOR_CHANNEL_BG]; - if (ichannels) color=uiColors[whichDef->chanTypes[i]+GUI_COLOR_CHANNEL_FM]; + if (ichannels) color=uiColors[chanDef.type+GUI_COLOR_CHANNEL_FM]; if (keyHitOffset>=0) { if (e->isChannelMuted(keyHitOffset+i)) { color=uiColors[GUI_COLOR_CHANNEL_MUTED]; @@ -345,7 +347,8 @@ void FurnaceGUI::drawSystemChannelInfoText(const DivSysDef* whichDef) { memset(chanCount,0,CHANNEL_TYPE_MAX); // count channel types for (int i=0; ichannels; i++) { - switch (whichDef->chanInsType[i][0]) { + DivChanDef chanDef=whichDef->getChanDef(i); + switch (chanDef.insType[0]) { case DIV_INS_STD: // square case DIV_INS_BEEPER: case DIV_INS_TED: @@ -360,17 +363,17 @@ void FurnaceGUI::drawSystemChannelInfoText(const DivSysDef* whichDef) { chanCount[CHANNEL_TYPE_PULSE]++; break; } - if (whichDef->chanTypes[i]==DIV_CH_NOISE) { // sn/t6w noise + if (chanDef.type==DIV_CH_NOISE) { // sn/t6w noise chanCount[CHANNEL_TYPE_NOISE]++; } else { // DIV_CH_PULSE, any sqr chan chanCount[CHANNEL_TYPE_SQUARE]++; } break; case DIV_INS_NES: - if (whichDef->chanTypes[i]==DIV_CH_WAVE) { + if (chanDef.type==DIV_CH_WAVE) { chanCount[whichDef->id==0xf1?CHANNEL_TYPE_WAVE:CHANNEL_TYPE_TRIANGLE]++; // triangle, wave for 5E01 } else { - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; } break; case DIV_INS_AY: @@ -380,23 +383,23 @@ void FurnaceGUI::drawSystemChannelInfoText(const DivSysDef* whichDef) { case DIV_INS_OPL_DRUMS: case DIV_INS_OPL: case DIV_INS_OPLL: - if (whichDef->chanTypes[i]==DIV_CH_OP) { + if (chanDef.type==DIV_CH_OP) { chanCount[CHANNEL_TYPE_FM]++; // opl3 4op break; } - if (whichDef->chanTypes[i]==DIV_CH_NOISE) { + if (chanDef.type==DIV_CH_NOISE) { chanCount[CHANNEL_TYPE_DRUMS]++; // drums } else { - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; } break; case DIV_INS_FM: - if (whichDef->chanTypes[i]==DIV_CH_OP) { + if (chanDef.type==DIV_CH_OP) { chanCount[CHANNEL_TYPE_OPERATOR]++; // ext. ops - } else if (whichDef->chanTypes[i]==DIV_CH_NOISE) { + } else if (chanDef.type==DIV_CH_NOISE) { break; // csm timer } else { - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; } break; case DIV_INS_ADPCMA: @@ -413,33 +416,33 @@ void FurnaceGUI::drawSystemChannelInfoText(const DivSysDef* whichDef) { chanCount[CHANNEL_TYPE_SAMPLE]++; break; case DIV_INS_NDS: - if (whichDef->chanTypes[i]!=DIV_CH_PCM) { // the psg chans can also play samples?? + if (chanDef.type!=DIV_CH_PCM) { // the psg chans can also play samples?? chanCount[CHANNEL_TYPE_SAMPLE]++; } - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; break; case DIV_INS_VERA: - if (whichDef->chanTypes[i]==DIV_CH_PULSE) { + if (chanDef.type==DIV_CH_PULSE) { chanCount[CHANNEL_TYPE_WAVE]++; } else { // sample chan chanCount[CHANNEL_TYPE_SAMPLE]++; } break; case DIV_INS_DAVE: - if (whichDef->chanTypes[i]==DIV_CH_WAVE) { + if (chanDef.type==DIV_CH_WAVE) { chanCount[CHANNEL_TYPE_OTHER]++; } else { - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; } break; case DIV_INS_SWAN: - if (whichDef->chanTypes[i]!=DIV_CH_WAVE) { + if (chanDef.type!=DIV_CH_WAVE) { chanCount[CHANNEL_TYPE_WAVETABLE]++; } - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; break; case DIV_INS_SID3: - if (whichDef->chanTypes[i]!=DIV_CH_WAVE) { + if (chanDef.type!=DIV_CH_WAVE) { chanCount[CHANNEL_TYPE_OTHER]++; } else { chanCount[CHANNEL_TYPE_WAVE]++; @@ -456,7 +459,7 @@ void FurnaceGUI::drawSystemChannelInfoText(const DivSysDef* whichDef) { chanCount[CHANNEL_TYPE_OTHER]++; break; default: - chanCount[whichDef->chanTypes[i]]++; + chanCount[chanDef.type]++; break; } } From 1a113a4d521d856a83c20516a8756f21e62a8856 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 17 Nov 2025 21:09:43 -0500 Subject: [PATCH 34/46] giga-refactor, part 15 dynamic PCM DAC --- src/engine/platform/pcmdac.cpp | 786 ++++++++++++++++++--------------- src/engine/platform/pcmdac.h | 8 +- src/engine/sysDef.cpp | 3 +- src/gui/sysConf.cpp | 9 + src/gui/sysMiscInfo.cpp | 2 +- 5 files changed, 441 insertions(+), 367 deletions(-) diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 817ec5a5d..425eb96a8 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -29,310 +29,341 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { const int depthScale=(15-outDepth); int output=0; + int outSum[2]; - oscBuf->begin(len); - - for (size_t h=0; hputSample(h,0); - continue; - } - if (chan[0].useWave || (chan[0].sample>=0 && chan[0].samplesong.sampleLen)) { - chan[0].audSub+=chan[0].freq; - if (chan[0].useWave) { - while (chan[0].audSub>=0x10000) { - chan[0].audSub-=0x10000; - chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1; - if (chan[0].audPos>=(int)chan[0].audLen) { - chan[0].audPos%=chan[0].audLen; - chan[0].audDir=false; - } - chan[0].audDat[0]=chan[0].audDat[1]; - chan[0].audDat[1]=chan[0].audDat[2]; - chan[0].audDat[2]=chan[0].audDat[3]; - chan[0].audDat[3]=chan[0].audDat[4]; - chan[0].audDat[4]=chan[0].audDat[5]; - chan[0].audDat[5]=chan[0].audDat[6]; - chan[0].audDat[6]=chan[0].audDat[7]; - chan[0].audDat[7]=(chan[0].ws.output[chan[0].audPos]-0x80)<<8; - } - - const short s0=chan[0].audDat[0]; - const short s1=chan[0].audDat[1]; - const short s2=chan[0].audDat[2]; - const short s3=chan[0].audDat[3]; - const short s4=chan[0].audDat[4]; - const short s5=chan[0].audDat[5]; - const short s6=chan[0].audDat[6]; - const short s7=chan[0].audDat[7]; - - switch (interp) { - case 1: // linear - output=s6+(((int)((int)s7-(int)s6)*((chan[0].audSub>>1)&0x7fff))>>15); - break; - case 2: { // cubic - float* cubicTable=DivFilterTables::getCubicTable(); - float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2]; - float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3]; - if (result<-32768) result=-32768; - if (result>32767) result=32767; - output=result; - break; - } - case 3: { // sinc - float* sincTable=DivFilterTables::getSincTable8(); - float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2]; - float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2]; - float result=( - s0*t2[3]+ - s1*t2[2]+ - s2*t2[1]+ - s3*t2[0]+ - s4*t1[0]+ - s5*t1[1]+ - s6*t1[2]+ - s7*t1[3] - ); - if (result<-32768) result=-32768; - if (result>32767) result=32767; - output=result; - break; - } - default: // none - output=s7; - break; - } - } else { - DivSample* s=parent->getSample(chan[0].sample); - if (s->samples>0) { - while (chan[0].audSub>=0x10000) { - chan[0].audSub-=0x10000; - chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1; - if (chan[0].audDir) { - if (s->isLoopable()) { - switch (s->loopMode) { - case DIV_SAMPLE_LOOP_FORWARD: - case DIV_SAMPLE_LOOP_PINGPONG: - if (chan[0].audPosloopStart) { - chan[0].audPos=s->loopStart+(s->loopStart-chan[0].audPos); - chan[0].audDir=false; - } - break; - case DIV_SAMPLE_LOOP_BACKWARD: - if (chan[0].audPosloopStart) { - chan[0].audPos=s->loopEnd-1-(s->loopStart-chan[0].audPos); - chan[0].audDir=true; - } - break; - default: - if (chan[0].audPos<0) { - chan[0].sample=-1; - } - break; - } - } else if (chan[0].audPos>=(int)s->samples) { - chan[0].sample=-1; - } - } else { - if (s->isLoopable()) { - switch (s->loopMode) { - case DIV_SAMPLE_LOOP_FORWARD: - if (chan[0].audPos>=s->loopEnd) { - chan[0].audPos=(chan[0].audPos+s->loopStart)-s->loopEnd; - chan[0].audDir=false; - } - break; - case DIV_SAMPLE_LOOP_BACKWARD: - case DIV_SAMPLE_LOOP_PINGPONG: - if (chan[0].audPos>=s->loopEnd) { - chan[0].audPos=s->loopEnd-1-(s->loopEnd-1-chan[0].audPos); - chan[0].audDir=true; - } - break; - default: - if (chan[0].audPos>=(int)s->samples) { - chan[0].sample=-1; - } - break; - } - } else if (chan[0].audPos>=(int)s->samples) { - chan[0].sample=-1; - } - } - chan[0].audDat[0]=chan[0].audDat[1]; - chan[0].audDat[1]=chan[0].audDat[2]; - chan[0].audDat[2]=chan[0].audDat[3]; - chan[0].audDat[3]=chan[0].audDat[4]; - chan[0].audDat[4]=chan[0].audDat[5]; - chan[0].audDat[5]=chan[0].audDat[6]; - chan[0].audDat[6]=chan[0].audDat[7]; - if (chan[0].audPos>=0 && chan[0].audPos<(int)s->samples) { - chan[0].audDat[7]=s->data16[chan[0].audPos]; - } else { - chan[0].audDat[7]=0; - } - } - } else { - chan[0].sample=-1; - chan[0].audSub=0; - chan[0].audPos=0; - } - - const short s0=chan[0].audDat[0]; - const short s1=chan[0].audDat[1]; - const short s2=chan[0].audDat[2]; - const short s3=chan[0].audDat[3]; - const short s4=chan[0].audDat[4]; - const short s5=chan[0].audDat[5]; - const short s6=chan[0].audDat[6]; - const short s7=chan[0].audDat[7]; - - switch (interp) { - case 1: // linear - output=s6+(((int)((int)s7-(int)s6)*((chan[0].audSub>>1)&0x7fff))>>15); - break; - case 2: { // cubic - float* cubicTable=DivFilterTables::getCubicTable(); - float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2]; - float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3]; - if (result<-32768) result=-32768; - if (result>32767) result=32767; - output=result; - break; - } - case 3: { // sinc - float* sincTable=DivFilterTables::getSincTable8(); - float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2]; - float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2]; - float result=( - s0*t2[3]+ - s1*t2[2]+ - s2*t2[1]+ - s3*t2[0]+ - s4*t1[0]+ - s5*t1[1]+ - s6*t1[2]+ - s7*t1[3] - ); - if (result<-32768) result=-32768; - if (result>32767) result=32767; - output=result; - break; - } - default: // none - output=s7; - break; - } - } - } - if (isMuted) { - output=0; - } else { - output=((output*MIN(volMax,chan[0].vol)*MIN(chan[0].envVol,64))>>6)/volMax; - } - oscBuf->putSample(h,((output>>depthScale)<>1); - if (outStereo) { - buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<>(depthScale+8))<>depthScale)<end(len); + for (int i=0; i=0 && chan[i].samplesong.sampleLen)) { + chan[i].audSub+=chan[i].freq; + if (chan[i].useWave) { + while (chan[i].audSub>=0x10000) { + chan[i].audSub-=0x10000; + chan[i].audPos+=((!chan[i].useWave) && chan[i].audDir)?-1:1; + if (chan[i].audPos>=(int)chan[i].audLen) { + chan[i].audPos%=chan[i].audLen; + chan[i].audDir=false; + } + chan[i].audDat[0]=chan[i].audDat[1]; + chan[i].audDat[1]=chan[i].audDat[2]; + chan[i].audDat[2]=chan[i].audDat[3]; + chan[i].audDat[3]=chan[i].audDat[4]; + chan[i].audDat[4]=chan[i].audDat[5]; + chan[i].audDat[5]=chan[i].audDat[6]; + chan[i].audDat[6]=chan[i].audDat[7]; + chan[i].audDat[7]=(chan[i].ws.output[chan[i].audPos]-0x80)<<8; + } + + const short s0=chan[i].audDat[0]; + const short s1=chan[i].audDat[1]; + const short s2=chan[i].audDat[2]; + const short s3=chan[i].audDat[3]; + const short s4=chan[i].audDat[4]; + const short s5=chan[i].audDat[5]; + const short s6=chan[i].audDat[6]; + const short s7=chan[i].audDat[7]; + + switch (interp) { + case 1: // linear + output=s6+(((int)((int)s7-(int)s6)*((chan[i].audSub>>1)&0x7fff))>>15); + break; + case 2: { // cubic + float* cubicTable=DivFilterTables::getCubicTable(); + float* t=&cubicTable[((chan[i].audSub&0xffff)>>6)<<2]; + float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3]; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + case 3: { // sinc + float* sincTable=DivFilterTables::getSincTable8(); + float* t1=&sincTable[(8191-((chan[i].audSub&0xffff)>>3))<<2]; + float* t2=&sincTable[((chan[i].audSub&0xffff)>>3)<<2]; + float result=( + s0*t2[3]+ + s1*t2[2]+ + s2*t2[1]+ + s3*t2[0]+ + s4*t1[0]+ + s5*t1[1]+ + s6*t1[2]+ + s7*t1[3] + ); + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + default: // none + output=s7; + break; + } + } else { + DivSample* s=parent->getSample(chan[i].sample); + if (s->samples>0) { + while (chan[i].audSub>=0x10000) { + chan[i].audSub-=0x10000; + chan[i].audPos+=((!chan[i].useWave) && chan[i].audDir)?-1:1; + if (chan[i].audDir) { + if (s->isLoopable()) { + switch (s->loopMode) { + case DIV_SAMPLE_LOOP_FORWARD: + case DIV_SAMPLE_LOOP_PINGPONG: + if (chan[i].audPosloopStart) { + chan[i].audPos=s->loopStart+(s->loopStart-chan[i].audPos); + chan[i].audDir=false; + } + break; + case DIV_SAMPLE_LOOP_BACKWARD: + if (chan[i].audPosloopStart) { + chan[i].audPos=s->loopEnd-1-(s->loopStart-chan[i].audPos); + chan[i].audDir=true; + } + break; + default: + if (chan[i].audPos<0) { + chan[i].sample=-1; + } + break; + } + } else if (chan[i].audPos>=(int)s->samples) { + chan[i].sample=-1; + } + } else { + if (s->isLoopable()) { + switch (s->loopMode) { + case DIV_SAMPLE_LOOP_FORWARD: + if (chan[i].audPos>=s->loopEnd) { + chan[i].audPos=(chan[i].audPos+s->loopStart)-s->loopEnd; + chan[i].audDir=false; + } + break; + case DIV_SAMPLE_LOOP_BACKWARD: + case DIV_SAMPLE_LOOP_PINGPONG: + if (chan[i].audPos>=s->loopEnd) { + chan[i].audPos=s->loopEnd-1-(s->loopEnd-1-chan[i].audPos); + chan[i].audDir=true; + } + break; + default: + if (chan[i].audPos>=(int)s->samples) { + chan[i].sample=-1; + } + break; + } + } else if (chan[i].audPos>=(int)s->samples) { + chan[i].sample=-1; + } + } + chan[i].audDat[0]=chan[i].audDat[1]; + chan[i].audDat[1]=chan[i].audDat[2]; + chan[i].audDat[2]=chan[i].audDat[3]; + chan[i].audDat[3]=chan[i].audDat[4]; + chan[i].audDat[4]=chan[i].audDat[5]; + chan[i].audDat[5]=chan[i].audDat[6]; + chan[i].audDat[6]=chan[i].audDat[7]; + if (chan[i].audPos>=0 && chan[i].audPos<(int)s->samples) { + chan[i].audDat[7]=s->data16[chan[i].audPos]; + } else { + chan[i].audDat[7]=0; + } + } + } else { + chan[i].sample=-1; + chan[i].audSub=0; + chan[i].audPos=0; + } + + const short s0=chan[i].audDat[0]; + const short s1=chan[i].audDat[1]; + const short s2=chan[i].audDat[2]; + const short s3=chan[i].audDat[3]; + const short s4=chan[i].audDat[4]; + const short s5=chan[i].audDat[5]; + const short s6=chan[i].audDat[6]; + const short s7=chan[i].audDat[7]; + + switch (interp) { + case 1: // linear + output=s6+(((int)((int)s7-(int)s6)*((chan[i].audSub>>1)&0x7fff))>>15); + break; + case 2: { // cubic + float* cubicTable=DivFilterTables::getCubicTable(); + float* t=&cubicTable[((chan[i].audSub&0xffff)>>6)<<2]; + float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3]; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + case 3: { // sinc + float* sincTable=DivFilterTables::getSincTable8(); + float* t1=&sincTable[(8191-((chan[i].audSub&0xffff)>>3))<<2]; + float* t2=&sincTable[((chan[i].audSub&0xffff)>>3)<<2]; + float result=( + s0*t2[3]+ + s1*t2[2]+ + s2*t2[1]+ + s3*t2[0]+ + s4*t1[0]+ + s5*t1[1]+ + s6*t1[2]+ + s7*t1[3] + ); + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + default: // none + output=s7; + break; + } + } + } + if (isMuted[i]) { + output=0; + } else { + output=((output*MIN(volMax,chan[i].vol)*MIN(chan[i].envVol,64))>>6)/volMax; + } + oscBuf[i].putSample(h,((output>>depthScale)<>1); + if (outStereo) { + outSum[0]+=((output*chan[i].panL)>>(depthScale+8))<>(depthScale+8))<>depthScale)<32767) outSum[0]=32767; + if (outSum[1]<-32768) outSum[1]=-32768; + if (outSum[1]>32767) outSum[1]=32767; + + buf[0][h]=outSum[0]; + buf[1][h]=outSum[1]; + } + + for (int i=0; icalcArp(chan[0].note,chan[0].std.arp.val)); + for (int i=0; igetIns(chan[0].ins,DIV_INS_AMIGA); - double off=1.0; - if (!chan[0].useWave && chan[0].sample>=0 && chan[0].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[0].sample); - off=(s->centerRate>=1)?((double)s->centerRate/parent->getCenterRate()):1.0; - } - chan[0].freq=off*parent->calcFreq(chan[0].baseFreq,chan[0].pitch,chan[0].fixedArp?chan[0].baseNoteOverride:chan[0].arpOff,chan[0].fixedArp,false,2,chan[0].pitch2,chipClock,CHIP_FREQBASE); - if (chan[0].freq>16777215) chan[0].freq=16777215; - if (chan[0].keyOn) { - if (!chan[0].std.vol.had) { - chan[0].envVol=64; + 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[0].keyOn=false; + chan[i].freqChanged=true; } - if (chan[0].keyOff) { - chan[0].keyOff=false; + 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) { + chan[i].ws.tick(); + } + 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) { + int val=chan[i].std.panL.val&0x7f; + chan[i].panL=val*2; + } + if (chan[i].std.panR.had) { + int val=chan[i].std.panR.val&0x7f; + chan[i].panR=val*2; + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].audDir=false; + chan[i].audPos=0; + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); + double off=1.0; + if (!chan[i].useWave && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].sample); + off=(s->centerRate>=1)?((double)s->centerRate/parent->getCenterRate()):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,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); + if (chan[i].freq>16777215) chan[i].freq=16777215; + if (chan[i].keyOn) { + if (!chan[i].std.vol.had) { + chan[i].envVol=64; + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; } - chan[0].freqChanged=false; } } int DivPlatformPCMDAC::dispatch(DivCommand c) { + if (c.chan>=chans) return 0; switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[0].ins,DIV_INS_AMIGA); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (ins->amiga.useWave) { chan[c.chan].sampleNote=DIV_NOTE_NULL; chan[c.chan].sampleNoteDelta=0; - chan[0].useWave=true; - chan[0].audLen=ins->amiga.waveLen+1; - if (chan[0].insChanged) { - if (chan[0].wave<0) { - chan[0].wave=0; - chan[0].ws.setWidth(chan[0].audLen); - chan[0].ws.changeWave1(chan[0].wave); + chan[c.chan].useWave=true; + chan[c.chan].audLen=ins->amiga.waveLen+1; + 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[0].sample=ins->amiga.getSample(c.value); + 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; @@ -340,120 +371,120 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) { chan[c.chan].sample=ins->amiga.getSample(chan[c.chan].sampleNote); c.value=ins->amiga.getFreq(chan[c.chan].sampleNote); } - chan[0].useWave=false; + chan[c.chan].useWave=false; } if (c.value!=DIV_NOTE_NULL) { - chan[0].baseFreq=round(NOTE_FREQUENCY(c.value)); + chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value)); } - if (chan[0].useWave || chan[0].sample<0 || chan[0].sample>=parent->song.sampleLen) { - chan[0].sample=-1; + if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; chan[c.chan].sampleNote=DIV_NOTE_NULL; chan[c.chan].sampleNoteDelta=0; } - if (chan[0].setPos) { - chan[0].setPos=false; + if (chan[c.chan].setPos) { + chan[c.chan].setPos=false; } else { - chan[0].audDir=false; - chan[0].audPos=0; + chan[c.chan].audDir=false; + chan[c.chan].audPos=0; } - chan[0].audSub=0; - memset(chan[0].audDat,0,8*sizeof(short)); + chan[c.chan].audSub=0; + memset(chan[c.chan].audDat,0,8*sizeof(short)); if (c.value!=DIV_NOTE_NULL) { - chan[0].freqChanged=true; - chan[0].note=c.value; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; } - chan[0].active=true; - chan[0].keyOn=true; - chan[0].macroInit(ins); - if (!parent->song.compatFlags.brokenOutVol && !chan[0].std.vol.will) { - chan[0].envVol=64; + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].envVol=64; } - if (chan[0].useWave) { - chan[0].ws.init(ins,chan[0].audLen,255,chan[0].insChanged); + if (chan[c.chan].useWave) { + chan[c.chan].ws.init(ins,chan[c.chan].audLen,255,chan[c.chan].insChanged); } - chan[0].insChanged=false; + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: - chan[0].sample=-1; - chan[0].active=false; - chan[0].keyOff=true; - chan[0].macroInit(NULL); + 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[0].std.release(); + chan[c.chan].std.release(); break; case DIV_CMD_INSTRUMENT: - if (chan[0].ins!=c.value || c.value2==1) { - chan[0].ins=c.value; - chan[0].insChanged=true; + 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[0].vol!=c.value) { - chan[0].vol=c.value; - if (!chan[0].std.vol.has) { - chan[0].envVol=64; + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].envVol=64; } } break; case DIV_CMD_GET_VOLUME: - return chan[0].vol; + return chan[c.chan].vol; break; case DIV_CMD_PANNING: - chan[0].panL=c.value; - chan[0].panR=c.value2; + chan[c.chan].panL=c.value; + chan[c.chan].panR=c.value2; break; case DIV_CMD_PITCH: - chan[0].pitch=c.value; - chan[0].freqChanged=true; + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; break; case DIV_CMD_WAVE: - if (!chan[0].useWave) break; - chan[0].wave=c.value; - chan[0].keyOn=true; - chan[0].ws.changeWave1(chan[0].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: { int destFreq=round(NOTE_FREQUENCY(c.value2+chan[c.chan].sampleNoteDelta)); bool return2=false; - if (destFreq>chan[0].baseFreq) { - chan[0].baseFreq+=c.value; - if (chan[0].baseFreq>=destFreq) { - chan[0].baseFreq=destFreq; + 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[0].baseFreq-=c.value; - if (chan[0].baseFreq<=destFreq) { - chan[0].baseFreq=destFreq; + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; return2=true; } } - chan[0].freqChanged=true; + chan[c.chan].freqChanged=true; if (return2) { - chan[0].inPorta=false; + chan[c.chan].inPorta=false; return 2; } break; } case DIV_CMD_LEGATO: { - chan[0].baseFreq=round(NOTE_FREQUENCY(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[0].std.arp.val):(0)))); - chan[0].freqChanged=true; - chan[0].note=c.value; + chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value+chan[c.chan].sampleNoteDelta+((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[0].active && c.value2) { - if (parent->song.compatFlags.resetMacroOnPorta) chan[0].macroInit(parent->getIns(chan[0].ins,DIV_INS_AMIGA)); + if (chan[c.chan].active && c.value2) { + if (parent->song.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } - chan[0].inPorta=c.value; + chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: - if (chan[0].useWave) break; - chan[0].audPos=c.value; - chan[0].setPos=true; + if (chan[c.chan].useWave) break; + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; break; case DIV_CMD_GET_VOLMAX: return volMax; @@ -474,31 +505,38 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) { } void DivPlatformPCMDAC::muteChannel(int ch, bool mute) { - isMuted=mute; + if (ch>=chans) return; + isMuted[ch]=mute; } void DivPlatformPCMDAC::forceIns() { - chan[0].insChanged=true; - chan[0].freqChanged=true; - chan[0].audDir=false; - chan[0].audPos=0; - chan[0].sample=-1; + for (int i=0; i=chans) return NULL; + return &chan[ch]; } DivDispatchOscBuffer* DivPlatformPCMDAC::getOscBuffer(int ch) { - return oscBuf; + if (ch>=chans) return NULL; + return &oscBuf[ch]; } void DivPlatformPCMDAC::reset() { - chan[0]=DivPlatformPCMDAC::Channel(); - chan[0].std.setEngine(parent); - chan[0].ws.setEngine(parent); - chan[0].ws.init(NULL,32,255); - memset(chan[0].audDat,0,8*sizeof(short)); + for (int i=0; i=chans) return NULL; + return &chan[ch].std; } unsigned short DivPlatformPCMDAC::getPan(int ch) { - return (chan[0].panL<<8)|chan[0].panR; + if (ch>=chans) return 0; + return (chan[ch].panL<<8)|chan[ch].panR; } DivSamplePos DivPlatformPCMDAC::getSamplePos(int ch) { - if (ch>=1) return DivSamplePos(); + if (ch>=chans) return DivSamplePos(); return DivSamplePos( chan[ch].sample, chan[ch].audPos, @@ -523,19 +563,25 @@ DivSamplePos DivPlatformPCMDAC::getSamplePos(int ch) { } void DivPlatformPCMDAC::notifyInsChange(int ins) { - if (chan[0].ins==ins) { - chan[0].insChanged=true; + for (int i=0; isetRate(rate); + for (int i=0; i1.0f) volMult=1.0f; } int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; - oscBuf=new DivDispatchOscBuffer; - isMuted=false; + oscBuf=new DivDispatchOscBuffer[channels]; + chan=new Channel[channels]; + isMuted=new bool[channels]; + chans=channels; + for (int i=0; i), diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 527afa878..dddebe280 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1953,6 +1953,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl int bitDepth=flags.getInt("outDepth",15)+1; int interpolation=flags.getInt("interpolation",0); int volMax=flags.getInt("volMax",255); + float volMult=flags.getFloat("volMult",1.0f); bool stereo=flags.getBool("stereo",false); ImGui::Text(_("Output rate:")); @@ -1977,6 +1978,13 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl altered=true; } + ImGui::Text(_("Volume multiplier:")); + if (CWSliderFloat("##VolMult",&volMult,0.0f,1.0f)) { + if (volMult<0.0f) volMult=0.0f; + if (volMult>1.0f) volMult=1.0f; + altered=true; + } rightClickable + ImGui::Text(_("Interpolation:")); ImGui::Indent(); if (ImGui::RadioButton(_("None"),interpolation==0)) { @@ -2004,6 +2012,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl flags.set("stereo",stereo); flags.set("interpolation",interpolation); flags.set("volMax",volMax); + flags.set("volMult",volMult); }); } break; diff --git a/src/gui/sysMiscInfo.cpp b/src/gui/sysMiscInfo.cpp index 0810113bf..8d6680984 100644 --- a/src/gui/sysMiscInfo.cpp +++ b/src/gui/sysMiscInfo.cpp @@ -319,7 +319,7 @@ float FurnaceGUI::drawSystemChannelInfo(const DivSysDef* whichDef, int keyHitOff y+=ledSize.y+sep.y; } ImVec4 color=uiColors[GUI_COLOR_CHANNEL_BG]; - if (ichannels) color=uiColors[chanDef.type+GUI_COLOR_CHANNEL_FM]; + if (imaxChans) color=uiColors[chanDef.type+GUI_COLOR_CHANNEL_FM]; if (keyHitOffset>=0) { if (e->isChannelMuted(keyHitOffset+i)) { color=uiColors[GUI_COLOR_CHANNEL_MUTED]; From 503239450739395f2fc8eeaa909dfd442b0a17dc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 18 Nov 2025 03:27:37 -0500 Subject: [PATCH 35/46] giga-refactor, part 16 fix ds.chans not being set when loading old .fur --- src/engine/fileOps/fur.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 12b5c6826..fd3db86d6 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -1232,6 +1232,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { } ds.initDefaultSystemChans(); + ds.chans=tchans; ds.name=reader.readString(); ds.author=reader.readString(); @@ -1819,6 +1820,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { return false; } + logW("ds.chans: %d",ds.chans); if (!subSong->readData(reader,ds.version,ds.chans)) { logE("%d: invalid subsong data!",i); lastError="invalid subsong data!"; From bdd916a4c7c1e660574b8b6dc95f64f868739f7a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 18 Nov 2025 15:40:53 -0500 Subject: [PATCH 36/46] PCM DAC: fix mixing --- src/engine/platform/pcmdac.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 425eb96a8..023cad6d7 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -49,6 +49,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { outSum[1]=0; for (int i=0; i Date: Wed, 19 Nov 2025 01:26:18 -0500 Subject: [PATCH 37/46] ...... --- src/gui/sysConf.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index dddebe280..79d882d88 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1304,8 +1304,14 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl int volScale=flags.getInt("volScale",4095); bool amigaVol=flags.getBool("amigaVol",false); bool amigaPitch=flags.getBool("amigaPitch",false); - ImGui::Text(_("Initial channel limit:")); - if (CWSliderInt("##OTTO_InitialChannelLimit",&channels,5,32)) { + + int minChans=5; + if (chan>=0) { + minChans=e->song.systemChans[chan]; + if (minChans>32) minChans=32; + } + ImGui::Text(_("Output rate:")); + if (CWSliderInt("##OTTO_InitialChannelLimit",&channels,minChans,32,"Label me please!")) { if (channels<5) channels=5; if (channels>32) channels=32; altered=true; From caea061cbb268bd093b6004b5bc69166e5289bc1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 19 Nov 2025 01:47:14 -0500 Subject: [PATCH 38/46] giga-refactor, part 17 don't clear chip flags when changing channel count --- src/engine/engine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 86dcfb5ff..92d22dca4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1090,7 +1090,6 @@ bool DivEngine::setSystemChans(int index, int ch, bool preserveOrder) { } song.systemChans[index]=ch; - song.systemFlags[index].clear(); song.recalcChans(); saveLock.unlock(); BUSY_END; From afb9b07f3bfcbada66d20ee348c6485e6f0ee0a7 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Wed, 19 Nov 2025 14:05:00 +0400 Subject: [PATCH 39/46] join the vectors into one --- src/gui/chanOsc.cpp | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index b298f3dd1..325d4c746 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -87,10 +87,6 @@ float FurnaceGUI::computeGradPos(int type, int chan, int totalChans) { } void FurnaceGUI::calcChanOsc() { - std::vector oscBufs; - std::vector oscFFTs; - std::vector oscChans; - int chans=e->getTotalChannelCount(); for (int i=0; i oscBufs; - std::vector oscFFTs; - std::vector oscChans; + struct OscData { + DivDispatchOscBuffer* buf; + ChanOscStatus* fft; + int chan; + }; + std::vector oscData; int chans=e->getTotalChannelCount(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -438,18 +437,16 @@ void FurnaceGUI::drawChanOsc() { for (int i=0; igetOscBuffer(i); if (buf!=NULL && e->curSubSong->chanShowChanOsc[i]) { - oscBufs.push_back(buf); // isnt this odd how there are 3 vectors of the same size? - oscFFTs.push_back(&chanOscChan[i]); - oscChans.push_back(i); + oscData.push_back({buf,&chanOscChan[i],i}); } } // process - for (size_t i=0; irelatedBuf=oscBufs[i]; - fft_->relatedCh=oscChans[i]; + fft_->relatedBuf=oscData[i].buf; + fft_->relatedCh=oscData[i].chan; if (fft_->relatedBuf!=NULL) { // prepare @@ -610,19 +607,19 @@ void FurnaceGUI::drawChanOsc() { chanOscWorkPool->wait(); if (chanOscAutoCols) { - chanOscCols=sqrt(oscBufs.size()); + chanOscCols=sqrt(oscData.size()); if (chanOscCols>64) chanOscCols=64; } - int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; + int rows=(oscData.size()+(chanOscCols-1))/chanOscCols; // render - for (size_t i=0; i Date: Wed, 19 Nov 2025 05:20:32 -0500 Subject: [PATCH 40/46] giga-refactor, part 18 ES5506 and Namco 163 channel count improvements --- src/gui/sysConf.cpp | 46 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 79d882d88..4dee0049f 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1276,12 +1276,17 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl altered=true; } ImGui::Unindent(); - ImGui::Text(_("Initial channel limit:")); - if (CWSliderInt("##N163_InitialChannelLimit",&channels,1,8)) { - if (channels<1) channels=1; - if (channels>8) channels=8; - altered=true; - } rightClickable + if (chan>=0) { + if (channels!=e->song.systemChans[chan]) { + pushWarningColor(true); + ImGui::Text(_("the legacy channel limit is not equal to the channel count!\neither set the channel count to %d, or click the following button to fix it without changing the channel count."),channels); + if (ImGui::Button(_("Fix channel limit"))) { + channels=e->song.systemChans[chan]; + altered=true; + } + popWarningColor(); + } + } if (ImGui::Checkbox(_("Disable hissing"),&multiplex)) { altered=true; } @@ -1310,12 +1315,25 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl minChans=e->song.systemChans[chan]; if (minChans>32) minChans=32; } - ImGui::Text(_("Output rate:")); - if (CWSliderInt("##OTTO_InitialChannelLimit",&channels,minChans,32,"Label me please!")) { + + DivDispatch* dispatch=e->getDispatch(chan); + double masterClock=16000000.0; + if (dispatch!=NULL) { + masterClock=dispatch->chipClock; + } + String outRateLabel=fmt::sprintf("/%d (%.0fHz)",channels,round(masterClock/(16.0*(double)channels))); + + ImGui::Text(_("Output rate divider:")); + pushWarningColor(channels32) channels=32; altered=true; } rightClickable + if (ImGui::IsItemHovered() && channelslockSave([&]() { + flags.set("channels",e->song.systemChans[chan]-1); + }); + } } else { showError(e->getLastError()); } @@ -2842,6 +2866,12 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } updateWindowTitle(); updateROMExportAvail(); + + if (type==DIV_SYSTEM_N163) { + e->lockSave([&]() { + flags.set("channels",e->song.systemChans[chan]-1); + }); + } } else { showError(e->getLastError()); } From 5f2c309abdec8975abacaca1a9a470521e21dfe1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 19 Nov 2025 19:00:39 -0500 Subject: [PATCH 41/46] IT/S3M/XM import: use systemChans --- src/engine/fileOps/it.cpp | 10 +++------- src/engine/fileOps/s3m.cpp | 3 ++- src/engine/fileOps/xm.cpp | 10 +++------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/engine/fileOps/it.cpp b/src/engine/fileOps/it.cpp index 6424a8f41..f5a624243 100644 --- a/src/engine/fileOps/it.cpp +++ b/src/engine/fileOps/it.cpp @@ -1619,12 +1619,6 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { logV("maxChan: %d",maxChan); - // set channel visibility - for (int i=maxChan; i<((maxChan+32)&(~31)); i++) { - ds.subsong[0]->chanShow[i]=false; - ds.subsong[0]->chanShowChanOsc[i]=false; - } - // copy patterns to the rest of subsongs int copiesMade=0; for (size_t i=1; i>5; i++) { ds.system[i]=DIV_SYSTEM_ES5506; + ds.systemChans[i]=MIN(32,chansToCount); + chansToCount-=ds.systemChans[i]; ds.systemFlags[i].set("amigaVol",true); if (!ds.compatFlags.linearPitch) { ds.systemFlags[i].set("amigaPitch",true); @@ -1675,7 +1672,6 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) { ds.systemName="PC"; // find subsongs - ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); diff --git a/src/engine/fileOps/s3m.cpp b/src/engine/fileOps/s3m.cpp index 4537b3f66..9b82077c7 100644 --- a/src/engine/fileOps/s3m.cpp +++ b/src/engine/fileOps/s3m.cpp @@ -345,6 +345,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { ds.systemName="PC"; if (hasPCM) { ds.system[ds.systemLen]=DIV_SYSTEM_ES5506; + ds.systemChans[ds.systemLen]=32; // for now ds.systemVol[ds.systemLen]=(float)globalVol/64.0; ds.systemPan[ds.systemLen]=0; ds.systemFlags[ds.systemLen].set("volScale",3900); @@ -354,6 +355,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { } if (hasFM) { ds.system[ds.systemLen]=opl2 ? DIV_SYSTEM_OPL2 : DIV_SYSTEM_OPL3; + ds.systemChans[ds.systemLen]=opl2?9:18; // for now ds.systemVol[ds.systemLen]=1.0f; ds.systemPan[ds.systemLen]=0; ds.systemLen++; @@ -1179,7 +1181,6 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { } // find subsongs - ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); diff --git a/src/engine/fileOps/xm.cpp b/src/engine/fileOps/xm.cpp index 73c9a1697..e448879da 100644 --- a/src/engine/fileOps/xm.cpp +++ b/src/engine/fileOps/xm.cpp @@ -301,8 +301,11 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { } } + int chansToCount=totalChans; for (int i=0; i<(totalChans+31)>>5; i++) { ds.system[i]=DIV_SYSTEM_ES5506; + ds.systemChans[i]=MIN(32,chansToCount); + chansToCount-=ds.systemChans[i]; ds.systemFlags[i].set("amigaVol",true); ds.systemFlags[i].set("amigaPitch",(ds.compatFlags.linearPitch==0)); ds.systemFlags[i].set("volScale",3900); @@ -1370,14 +1373,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) { return false; } - // set channel visibility - for (int i=totalChans; i<((totalChans+32)&(~31)); i++) { - ds.subsong[0]->chanShow[i]=false; - ds.subsong[0]->chanShowChanOsc[i]=false; - } - // find subsongs - ds.initDefaultSystemChans(); ds.recalcChans(); ds.findSubSongs(); From 26993d2b4014830dcdca7bea29e1d4c29d299c11 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 19 Nov 2025 19:35:18 -0500 Subject: [PATCH 42/46] giga-refactor, part 19 fix Namco 163 stuff --- src/engine/platform/n163.cpp | 5 ++++- src/gui/sysConf.cpp | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 2476e7e53..4f362b93a 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -593,7 +593,7 @@ void DivPlatformN163::setFlags(const DivConfig& flags) { break; } CHECK_CUSTOM_CLOCK; - initChanMax=chanMax=flags.getInt("channels",0)&7; + initChanMax=chanMax=flags.getInt("channels",7)&7; multiplex=!flags.getBool("multiplex",false); // not accurate in real hardware rate=chipClock; rate/=15; @@ -602,6 +602,7 @@ void DivPlatformN163::setFlags(const DivConfig& flags) { for (int i=0; i<8; i++) { oscBuf[i]->setRate(rate);//=rate/(initChanMax+1); } + logV("N163: initChanMax: %d",initChanMax); lenCompensate=flags.getBool("lenCompensate",false); @@ -618,6 +619,8 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, const DivConf oscBuf[i]=new DivDispatchOscBuffer; } + logV("N163: init(%d)",channels); + memCompo.used=0; memCompo.capacity=128; memCompo.memory=regPool; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 4dee0049f..288e5e03d 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1279,8 +1279,20 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl if (chan>=0) { if (channels!=e->song.systemChans[chan]) { pushWarningColor(true); - ImGui::Text(_("the legacy channel limit is not equal to the channel count!\neither set the channel count to %d, or click the following button to fix it without changing the channel count."),channels); - if (ImGui::Button(_("Fix channel limit"))) { + ImGui::Text(_("the legacy channel limit is not equal to the channel count!\neither set the channel count to %d, or click one of the following buttons:"),channels); + if (ImGui::Button(_("Fix channel count"))) { + if (e->setSystemChans(chan,channels,preserveChanPos)) { + MARK_MODIFIED; + recalcTimestamps=true; + if (e->song.autoSystem) { + autoDetectSystem(); + } + updateWindowTitle(); + updateROMExportAvail(); + altered=true; + } + } + if (ImGui::Button(_("Give me more channels"))) { channels=e->song.systemChans[chan]; altered=true; } @@ -2835,6 +2847,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl e->lockSave([&]() { flags.set("channels",e->song.systemChans[chan]-1); }); + altered=true; } } else { showError(e->getLastError()); @@ -2871,6 +2884,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl e->lockSave([&]() { flags.set("channels",e->song.systemChans[chan]-1); }); + altered=true; } } else { showError(e->getLastError()); From 47e29104988ba9fef94dd382577edf9e99e0c128 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 21 Nov 2025 04:58:56 -0500 Subject: [PATCH 43/46] giga-refactor, part 20 convert compat SegaPCM to normal SegaPCM with 5 channels convert Neo Geo CD to YM2610 --- src/engine/dispatchContainer.cpp | 3 -- src/engine/engine.cpp | 4 +- src/engine/engine.h | 2 +- src/engine/fileOps/dmf.cpp | 52 +++++++++++++++++++------- src/engine/fileOps/fur.cpp | 20 +++++++--- src/engine/legacySample.cpp | 3 -- src/engine/sysDef.cpp | 63 ++++++-------------------------- src/engine/sysDef.h | 4 +- src/engine/vgmOps.cpp | 9 ----- src/gui/debug.cpp | 10 +---- src/gui/gui.h | 2 +- src/gui/guiConst.cpp | 6 --- src/gui/presets.cpp | 10 ++--- src/gui/sampleEdit.cpp | 1 - src/gui/sysConf.cpp | 7 +--- src/gui/sysMiscInfo.cpp | 3 -- 16 files changed, 79 insertions(+), 120 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index c5ed67e91..f1893ffd1 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -345,7 +345,6 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformArcade*)dispatch)->setYMFM(eng->getConfInt("arcadeCore",0)==0); } break; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: dispatch=new DivPlatformYM2610; if (isRender) { @@ -354,7 +353,6 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnbCore",1)); } break; - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: dispatch=new DivPlatformYM2610Ext; if (isRender) { @@ -599,7 +597,6 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformQSound; break; case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; case DIV_SYSTEM_X1_010: diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 92d22dca4..92d748df4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2248,11 +2248,11 @@ int DivEngine::getEffectiveSampleRate(int rate) { return 1278409/(1280000/rate); case DIV_SYSTEM_PCE: return 1789773/(1789773/rate); - case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_SEGAPCM: return (31250*MIN(255,(rate*255/31250)))/255; case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: + case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; case DIV_SYSTEM_VERA: return (48828*MIN(128,(rate*128/48828)))/128; diff --git a/src/engine/engine.h b/src/engine/engine.h index 869d0a12c..5a274d2c8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -56,7 +56,7 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "inf2/dev240 - DO NOT LOAD!" +#define DIV_VERSION "dev240" #define DIV_ENGINE_VERSION 240 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp index 98f2edecb..878eec1c1 100644 --- a/src/engine/fileOps/dmf.cpp +++ b/src/engine/fileOps/dmf.cpp @@ -397,7 +397,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { ins->type=DIV_INS_C64; } - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT + if (ds.system[0]==DIV_SYSTEM_YM2610_CRAP || ds.system[0]==DIV_SYSTEM_YM2610_CRAP_EXT || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { if (!mode) { @@ -629,7 +629,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } // piece of crap offset by 1 - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) { + if (ds.system[0]==DIV_SYSTEM_YM2610_CRAP || ds.system[0]==DIV_SYSTEM_YM2610_CRAP_EXT) { ins->std.waveMacro.val[j]++; } } @@ -1169,8 +1169,21 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.systemFlags[0].set("brokenPitch",true); } - ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); ds.initDefaultSystemChans(); + + // flatten 5-channel SegaPCM and Neo Geo CD + for (int i=0; iordersLen; j++) { if (curOrders->ord[i][j]>0x7f) { logE("order %d, %d is out of range (0-127)!",i,j); @@ -1320,7 +1340,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } else if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS_EXT)); sys=DIV_SYSTEM_GENESIS_EXT; - } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { + } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM) { w->writeC(systemToFileDMF(DIV_SYSTEM_ARCADE)); sys=DIV_SYSTEM_ARCADE; } else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { @@ -1335,6 +1355,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } else if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_SCC) { w->writeC(systemToFileDMF(DIV_SYSTEM_MSX2)); sys=DIV_SYSTEM_MSX2; + } else if (song.system[0]==DIV_SYSTEM_YM2610_FULL) { + w->writeC(systemToFileDMF(DIV_SYSTEM_YM2610_CRAP)); + sys=DIV_SYSTEM_YM2610_CRAP; + } else if (song.system[0]==DIV_SYSTEM_YM2610_FULL_EXT) { + w->writeC(systemToFileDMF(DIV_SYSTEM_YM2610_CRAP_EXT)); + sys=DIV_SYSTEM_YM2610_CRAP_EXT; } else { w->writeC(systemToFileDMF(song.system[0])); sys=song.system[0]; @@ -1360,7 +1386,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeI(curSubSong->patLen); w->writeC(curSubSong->ordersLen); - for (int i=0; iordersLen; j++) { w->writeC(curOrders->ord[i][j]); if (version>=25) { @@ -1429,8 +1455,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { case DIV_SYSTEM_PCE: i->type=DIV_INS_PCE; break; - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: i->type=DIV_INS_AY; break; default: @@ -1582,7 +1608,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(realWaveMacroLen); for (int j=0; jwriteI(i->std.waveMacro.val[j]-1); } else { w->writeI(i->std.waveMacro.val[j]); @@ -1646,7 +1672,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { bool relWarning=false; - for (int i=0; iwriteC(curPat[i].effectCols); @@ -1675,13 +1701,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { alwaysConvert=true; } break; - case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_CRAP: if (i>=7) { convertSampleUsage=true; alwaysConvert=true; } break; - case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_CRAP_EXT: if (i>=10) { convertSampleUsage=true; alwaysConvert=true; diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index fd3db86d6..ae84ce173 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -166,8 +166,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS break; } break; - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_CRAP: + case DIV_SYSTEM_YM2610_CRAP_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: @@ -1234,6 +1234,17 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.initDefaultSystemChans(); ds.chans=tchans; + // flatten 5-channel SegaPCM and Neo Geo CD + for (int i=0; i", + DivChanDefFunc() ); - sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef( + sysDefs[DIV_SYSTEM_YM2610_CRAP_EXT]=new DivSysDef( _("Neo Geo CD Extended Channel 2"), NULL, 0x49, 0x49, 16, 16, 16, - true, true, 0x151, false, (1U<", + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_MSX2]=new DivSysDef( @@ -2033,11 +1994,9 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef( _("SegaPCM (compatible 5-channel mode)"), NULL, 0xa9, 0, 5, 5, 5, - false, true, 0x151, false, 1U<), - {}, - segaPCMPostEffectHandlerMap + false, true, 0x151, true, 1U<", + DivChanDefFunc() ); sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef( diff --git a/src/engine/sysDef.h b/src/engine/sysDef.h index acad043ea..6e2037b59 100644 --- a/src/engine/sysDef.h +++ b/src/engine/sysDef.h @@ -166,8 +166,8 @@ enum DivSystem { DIV_SYSTEM_C64_8580, DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! ** DIV_SYSTEM_MSX2, // ** COMPOUND SYSTEM - DO NOT USE! ** - DIV_SYSTEM_YM2610, - DIV_SYSTEM_YM2610_EXT, + DIV_SYSTEM_YM2610_CRAP, + DIV_SYSTEM_YM2610_CRAP_EXT, DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 9cdeb9767..15da9da0b 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -168,7 +168,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } break; case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: for (int i=0; i<16; i++) { w->writeC(0xc0); w->writeS((0x86|baseAddr2S)+(i<<3)); @@ -182,10 +181,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0); } break; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610_CSM: @@ -979,7 +976,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.val); break; case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: w->writeC(0xc0); w->writeS(baseAddr2S|(write.addr&0xffff)); w->writeC(write.val); @@ -989,10 +985,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS_BE(baseAddr2S|(write.addr&0x1fff)); w->writeC(write.val); break; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610_CSM: @@ -1507,7 +1501,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p } break; case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: if (!hasSegaPCM) { hasSegaPCM=4000000; CHIP_VOL(4,0.67); @@ -1537,10 +1530,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610_CSM: diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index a77e4af1f..58a4ff853 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -278,8 +278,6 @@ void putDispatchChip(void* data, int type) { ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); break; } - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: { DivPlatformYM2610* ch=(DivPlatformYM2610*)data; @@ -362,8 +360,7 @@ void putDispatchChip(void* data, int type) { ImGui::TextColored(ch->useYMFM?colorOn:colorOff,">> UseYMFM"); break; } - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: { + case DIV_SYSTEM_SEGAPCM: { DivPlatformSegaPCM* ch=(DivPlatformSegaPCM*)data; ImGui::Text("> SegaPCM"); COMMON_CHIP_DEBUG; @@ -629,7 +626,6 @@ void putDispatchChan(void* data, int chanNum, int type) { } break; } - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: { DivPlatformOPN::OPNChannelStereo* ch=(DivPlatformOPN::OPNChannelStereo*)data; ImGui::Text("> YM2610"); @@ -642,7 +638,6 @@ void putDispatchChan(void* data, int chanNum, int type) { OPNB_CHAN_DEBUG; break; } - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: { if (chanNum>=1 && chanNum<=4) { DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data; @@ -739,8 +734,7 @@ void putDispatchChan(void* data, int chanNum, int type) { COMMON_CHAN_DEBUG_BOOL; break; } - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: { + case DIV_SYSTEM_SEGAPCM: { DivPlatformSegaPCM::Channel* ch=(DivPlatformSegaPCM::Channel*)data; ImGui::Text("> SegaPCM"); COMMON_CHAN_DEBUG; diff --git a/src/gui/gui.h b/src/gui/gui.h index 4a2d96e9a..7b727eec1 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -81,7 +81,7 @@ #define FM_PREVIEW_SIZE 512 #define CHECK_HIDDEN_SYSTEM(x) \ - (x==DIV_SYSTEM_YMU759 || x==DIV_SYSTEM_DUMMY || x==DIV_SYSTEM_SEGAPCM_COMPAT || x==DIV_SYSTEM_PONG || x==DIV_SYSTEM_UPD1771C) + (x==DIV_SYSTEM_YMU759 || x==DIV_SYSTEM_DUMMY || x==DIV_SYSTEM_PONG || x==DIV_SYSTEM_UPD1771C) enum FurnaceGUIRenderBackend { GUI_BACKEND_SDL=0, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 84300b6e7..c94d711f6 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -1274,9 +1274,6 @@ const int availableSystems[]={ DIV_SYSTEM_C64_PCM, DIV_SYSTEM_YM2151, DIV_SYSTEM_SEGAPCM, - DIV_SYSTEM_SEGAPCM_COMPAT, - DIV_SYSTEM_YM2610, - DIV_SYSTEM_YM2610_EXT, DIV_SYSTEM_YM2610_FULL, DIV_SYSTEM_YM2610_FULL_EXT, DIV_SYSTEM_YM2610_CSM, @@ -1376,8 +1373,6 @@ const int chipsFM[]={ DIV_SYSTEM_YM2612_DUALPCM, DIV_SYSTEM_YM2612_DUALPCM_EXT, DIV_SYSTEM_YM2151, - DIV_SYSTEM_YM2610, - DIV_SYSTEM_YM2610_EXT, DIV_SYSTEM_YM2610_FULL, DIV_SYSTEM_YM2610_FULL_EXT, DIV_SYSTEM_YM2610_CSM, @@ -1476,7 +1471,6 @@ const int chipsSpecial[]={ // sample const int chipsSample[]={ DIV_SYSTEM_SEGAPCM, - DIV_SYSTEM_SEGAPCM_COMPAT, DIV_SYSTEM_AMIGA, DIV_SYSTEM_QSOUND, DIV_SYSTEM_X1_010, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index c8d8ca449..4f8d9f4ba 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -3988,17 +3988,17 @@ void FurnaceGUI::initSystemPresets() { ENTRY( _("Arcade (YM2151 and SegaPCM)"), { CH(DIV_SYSTEM_YM2151, 1.0f, 0, ""), - CH(DIV_SYSTEM_SEGAPCM_COMPAT, 1.0f, 0, "") + CH(DIV_SYSTEM_SEGAPCM, 1.0f, 0, "") } ); ENTRY( - _("Neo Geo CD"), { - CH(DIV_SYSTEM_YM2610, 1.0f, 0, "") + _("Neo Geo"), { + CH(DIV_SYSTEM_YM2610_FULL, 1.0f, 0, "") } ); ENTRY( - _("Neo Geo CD (extended channel 2)"), { - CH(DIV_SYSTEM_YM2610_EXT, 1.0f, 0, "") + _("Neo Geo (extended channel 2)"), { + CH(DIV_SYSTEM_YM2610_FULL_EXT, 1.0f, 0, "") } ); ENTRY( diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 6fcc0dca2..9160fdfa9 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -396,7 +396,6 @@ void FurnaceGUI::drawSampleEdit() { } break; case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: if (sample->samples>65280) { SAMPLE_WARN(warnLength,_("SegaPCM: maximum sample length is 65280")); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 288e5e03d..6688ee1cb 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -796,8 +796,6 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_CSM: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: @@ -821,7 +819,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } ImGui::Unindent(); - if (type==DIV_SYSTEM_YM2610_EXT || type==DIV_SYSTEM_YM2610_FULL_EXT || type==DIV_SYSTEM_YM2610B_EXT || type==DIV_SYSTEM_YM2610_CSM || type==DIV_SYSTEM_YM2610B_CSM) { + if (type==DIV_SYSTEM_YM2610_FULL_EXT || type==DIV_SYSTEM_YM2610B_EXT || type==DIV_SYSTEM_YM2610_CSM || type==DIV_SYSTEM_YM2610B_CSM) { if (ImGui::Checkbox(_("Disable ExtCh FM macros (compatibility)"),&noExtMacros)) { altered=true; } @@ -2420,8 +2418,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: { + case DIV_SYSTEM_SEGAPCM: { bool oldSlides=flags.getBool("oldSlides",false); if (ImGui::Checkbox(_("Legacy slides and pitch (compatibility)"),&oldSlides)) { diff --git a/src/gui/sysMiscInfo.cpp b/src/gui/sysMiscInfo.cpp index 8d6680984..026ac4c92 100644 --- a/src/gui/sysMiscInfo.cpp +++ b/src/gui/sysMiscInfo.cpp @@ -189,7 +189,6 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { return "YM2414"; break; case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: return "SegaPCM"; break; case DIV_SYSTEM_VRC7: @@ -207,9 +206,7 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { case DIV_SYSTEM_SCC: return "SCC"; break; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_CSM: - case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: return "YM2610"; From 1882a4f12d656b74f46c868cd2ad136ff6293158 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 21 Nov 2025 10:51:03 -0500 Subject: [PATCH 44/46] consider channel color for the rest of functions --- src/engine/engine.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0d00756ec..70ee0a559 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -642,6 +642,7 @@ void DivEngine::copyChannel(int src, int dest) { curSubSong->chanShow[dest]=curSubSong->chanShow[src]; curSubSong->chanShowChanOsc[dest]=curSubSong->chanShowChanOsc[src]; curSubSong->chanCollapse[dest]=curSubSong->chanCollapse[src]; + curSubSong->chanColor[dest]=curSubSong->chanColor[src]; } void DivEngine::swapChannels(int src, int dest) { @@ -670,17 +671,21 @@ void DivEngine::swapChannels(int src, int dest) { bool prevChanShow=curSubSong->chanShow[src]; bool prevChanShowChanOsc=curSubSong->chanShowChanOsc[src]; unsigned char prevChanCollapse=curSubSong->chanCollapse[src]; + unsigned int prevChanColor=curSubSong->chanColor[src]; curSubSong->chanName[src]=curSubSong->chanName[dest]; curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; curSubSong->chanShow[src]=curSubSong->chanShow[dest]; curSubSong->chanShowChanOsc[src]=curSubSong->chanShowChanOsc[dest]; curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest]; + curSubSong->chanColor[src]=curSubSong->chanColor[dest]; + curSubSong->chanName[dest]=prevChanName; curSubSong->chanShortName[dest]=prevChanShortName; curSubSong->chanShow[dest]=prevChanShow; curSubSong->chanShowChanOsc[dest]=prevChanShowChanOsc; curSubSong->chanCollapse[dest]=prevChanCollapse; + curSubSong->chanColor[dest]=prevChanColor; } void DivEngine::stompChannel(int ch) { @@ -695,6 +700,7 @@ void DivEngine::stompChannel(int ch) { curSubSong->chanShow[ch]=true; curSubSong->chanShowChanOsc[ch]=true; curSubSong->chanCollapse[ch]=false; + curSubSong->chanColor[ch]=0; } void DivEngine::changeSong(size_t songIndex) { @@ -860,6 +866,8 @@ int DivEngine::duplicateSubSong(int index) { memcpy(theCopy->chanShowChanOsc,theOrig->chanShowChanOsc,DIV_MAX_CHANS*sizeof(bool)); memcpy(theCopy->chanCollapse,theOrig->chanCollapse,DIV_MAX_CHANS); + memcpy(theCopy->chanColor,theOrig->chanColor,DIV_MAX_CHANS*sizeof(unsigned int)); + for (int i=0; ichanName[i]=theOrig->chanName[i]; theCopy->chanShortName[i]=theOrig->chanShortName[i]; @@ -1236,6 +1244,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) { i->chanCollapse[destChan+j]=i->chanCollapse[srcChan+j]; i->chanName[destChan+j]=i->chanName[srcChan+j]; i->chanShortName[destChan+j]=i->chanShortName[srcChan+j]; + i->chanColor[destChan+j]=i->chanColor[srcChan+j]; for (int k=0; kpat[srcChan+j].data[k]!=NULL) { i->pat[srcChan+j].data[k]->copyOn(i->pat[destChan+j].getPattern(k,true)); @@ -1387,6 +1396,7 @@ void DivEngine::swapSystemUnsafe(int src, int dest, bool preserveOrder) { bool prevChanShow[DIV_MAX_CHANS]; bool prevChanShowChanOsc[DIV_MAX_CHANS]; unsigned char prevChanCollapse[DIV_MAX_CHANS]; + unsigned int prevChanColor[DIV_MAX_CHANS]; for (int j=0; jchanShow[j]; prevChanShowChanOsc[j]=song.subsong[i]->chanShowChanOsc[j]; prevChanCollapse[j]=song.subsong[i]->chanCollapse[j]; + prevChanColor[j]=song.subsong[i]->chanColor[j]; } for (int j=0; jchanShow[j]=prevChanShow[swappedChannels[j]]; song.subsong[i]->chanShowChanOsc[j]=prevChanShowChanOsc[swappedChannels[j]]; song.subsong[i]->chanCollapse[j]=prevChanCollapse[swappedChannels[j]]; + song.subsong[i]->chanColor[j]=prevChanColor[swappedChannels[j]]; } } } From c3394594a63670d346cc733512c1e02ea8788a24 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 21 Nov 2025 11:08:13 -0500 Subject: [PATCH 45/46] giga-refactor, part 21 store channel colors! --- papers/format.md | 46 ++++++++++++++++++++++++++++++--------------- src/engine/song.cpp | 8 ++++++++ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/papers/format.md b/papers/format.md index 79f5446a4..138dea437 100644 --- a/papers/format.md +++ b/papers/format.md @@ -164,24 +164,12 @@ the channel count is stored in the file in order to allow Furnace to load files - 0x00: invalid (end of chips in previous versions) - 0x01: YMU759 - 17 channels -- 0x02: Genesis - 10 channels (compound!) - - flatten to 0x83 + 0x03 -- 0x03: SMS (SN76489) - 4 channels +- 0x03: SN76489/Sega PSG - 4 channels - 0x04: Game Boy - 4 channels - 0x05: PC Engine - 6 channels - 0x06: NES - 5 channels - 0x07: C64 (8580) - 3 channels -- 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) - - flatten to 0x82 + 0xa9 -- 0x09: Neo Geo CD (YM2610) - 13 channels -- 0x42: Genesis extended - 13 channels (compound!) - - flatten to 0xa0 + 0x03 -- 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) - - flatten to 0x03 + 0x89 -- 0x46: NES + VRC7 - 11 channels (compound!) - - flatten to 0x06 + 0x9d - 0x47: C64 (6581) - 3 channels -- 0x49: Neo Geo CD extended - 16 channels - 0x80: AY-3-8910 - 3 channels - 0x81: Amiga - 4 channels - 0x82: YM2151 - 8 channels @@ -223,7 +211,6 @@ the channel count is stored in the file in order to allow Furnace to load files - 0xa6: Neo Geo extended (YM2610) - 17 channels - 0xa7: OPLL drums (YM2413) - 11 channels - 0xa8: Atari Lynx - 4 channels -- 0xa9: SegaPCM (for DefleMask compatibility) - 5 channels - 0xaa: MSM6295 - 4 channels - 0xab: MSM6258 - 1 channel - 0xac: Commander X16 (VERA) - 17 channels @@ -292,9 +279,35 @@ the channel count is stored in the file in order to allow Furnace to load files notes: -- (compound!) means that the system is composed of two or more chips, and has to be flattened. - (UNAVAILABLE) means that the chip hasn't been implemented in Furnace yet. +### special IDs + +the following is a list of legacy chip/system IDs. +these must be either flattened (converted to two equivalent chips) or converted to another chip. + +those marked with `(compound!)` were from an era where two chips were part of a single system ID. + +these IDs will never be present in new song files (with the `INF2` info header). if you see them, reject the file. + +- 0x02: Genesis - 10 channels (compound!) + - flatten to 0x83 (YM2612) + 0x03 (SN76489/Sega PSG) +- 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) + - flatten to 0x82 (YM2151) + 0x9b (SegaPCM) + - channel count of the latter shall be 5! +- 0x09: Neo Geo CD (YM2610) - 13 channels + - convert to 0xa5 (YM2610 proper) and set channel count to 13 +- 0x42: Genesis extended - 13 channels (compound!) + - flatten to 0xa0 (YM2612 extended) + 0x03 (SN76489/Sega PSG) +- 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) + - flatten to 0x03 (SN76489/Sega PSG) + 0x89 (OPLL) +- 0x46: NES + VRC7 - 11 channels (compound!) + - flatten to 0x06 (NES) + 0x9d (VRC7) +- 0x49: Neo Geo CD extended - 16 channels + - convert to 0xa6 (YM2610 extended proper) and set channel count to 16 +- 0xa9: SegaPCM (for DefleMask compatibility) - 5 channels + - convert to 0x9b (SegaPCM) and set channel count to 5 + ## song elements the following element types are available: @@ -385,6 +398,9 @@ size | description | - a list of channelCount C strings S?? | channel short names | - same as above + 4?? | channel colors + | - read 4 values per color (RGBA) + | - if 0, use default color ``` # groove pattern (>=240) diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 4bf04aebb..d29693317 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -512,6 +512,10 @@ bool DivSubSong::readData(SafeReader& reader, int version, int chans) { for (int i=0; iwriteString(chanShortName[i],false); } + for (int i=0; iwriteI(chanColor[i]); + } + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); From f4bd61df41c488c0b58509d8568bf0a8f174a47e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 21 Nov 2025 11:14:24 -0500 Subject: [PATCH 46/46] fix the doc --- papers/format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/format.md b/papers/format.md index 138dea437..56730dc8f 100644 --- a/papers/format.md +++ b/papers/format.md @@ -399,7 +399,7 @@ size | description S?? | channel short names | - same as above 4?? | channel colors - | - read 4 values per color (RGBA) + | - read 4 values per color (ABGR) | - if 0, use default color ```