diff options
| author | inspectredc <78732756+inspectredc@users.noreply.github.com> | 2025-10-07 03:26:44 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-06 20:26:44 -0600 |
| commit | a50138b71ad0b3b1fdd01cbededeb0f9e900134c (patch) | |
| tree | 9e258592b7edb536a337b3e1b3434b4b8b32b7da | |
| parent | 0d6ee079991605b41056cece077bcc051872edb0 (diff) | |
FZX Audio (#199)
* sequence factory
* matching for fzx
* Portable Soundfont Factory
| -rw-r--r-- | src/Companion.cpp | 4 | ||||
| -rw-r--r-- | src/factories/fzerox/SequenceFactory.cpp | 2014 | ||||
| -rw-r--r-- | src/factories/fzerox/SequenceFactory.h | 76 | ||||
| -rw-r--r-- | src/factories/fzerox/SoundFontFactory.cpp | 655 | ||||
| -rw-r--r-- | src/factories/fzerox/SoundFontFactory.h | 118 |
5 files changed, 2867 insertions, 0 deletions
diff --git a/src/Companion.cpp b/src/Companion.cpp index 4485f3d..c52d87e 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -73,6 +73,8 @@ #ifdef FZERO_SUPPORT #include "factories/fzerox/CourseFactory.h" #include "factories/fzerox/GhostRecordFactory.h" +#include "factories/fzerox/SequenceFactory.h" +#include "factories/fzerox/SoundFontFactory.h" #endif #ifdef MARIO_ARTIST_SUPPORT @@ -187,6 +189,8 @@ void Companion::Init(const ExportType type) { #ifdef FZERO_SUPPORT this->RegisterFactory("FZX:COURSE", std::make_shared<FZX::CourseFactory>()); this->RegisterFactory("FZX:GHOST", std::make_shared<FZX::GhostRecordFactory>()); + this->RegisterFactory("FZX:SEQUENCE", std::make_shared<FZX::SequenceFactory>()); + this->RegisterFactory("FZX:SOUNDFONT", std::make_shared<FZX::SoundFontFactory>()); #endif #ifdef MARIO_ARTIST_SUPPORT diff --git a/src/factories/fzerox/SequenceFactory.cpp b/src/factories/fzerox/SequenceFactory.cpp new file mode 100644 index 0000000..bacdfe0 --- /dev/null +++ b/src/factories/fzerox/SequenceFactory.cpp @@ -0,0 +1,2014 @@ +#include "SequenceFactory.h" +#include "spdlog/spdlog.h" + +#include "Companion.h" +#include "utils/Decompressor.h" +#include "utils/TorchUtils.h" +#include <deque> + +// control flow commands +#define ASEQ_OP_CONTROL_FLOW_FIRST 0xF2 +#define ASEQ_OP_RBLTZ 0xF2 +#define ASEQ_OP_RBEQZ 0xF3 +#define ASEQ_OP_RJUMP 0xF4 +#define ASEQ_OP_BGEZ 0xF5 +#define ASEQ_OP_BREAK 0xF6 +#define ASEQ_OP_LOOPEND 0xF7 +#define ASEQ_OP_LOOP 0xF8 +#define ASEQ_OP_BLTZ 0xF9 +#define ASEQ_OP_BEQZ 0xFA +#define ASEQ_OP_JUMP 0xFB +#define ASEQ_OP_CALL 0xFC +#define ASEQ_OP_DELAY 0xFD +#define ASEQ_OP_DELAY1 0xFE +#define ASEQ_OP_END 0xFF + +// sequence commands +#define ASEQ_OP_SEQ_TESTCHAN 0x00 // low nibble used as argument +#define ASEQ_OP_SEQ_STOPCHAN 0x40 // low nibble used as argument +#define ASEQ_OP_SEQ_SUBIO 0x50 // low nibble used as argument +#define ASEQ_OP_SEQ_LDRES 0x60 // low nibble used as argument +#define ASEQ_OP_SEQ_STIO 0x70 // low nibble used as argument +#define ASEQ_OP_SEQ_LDIO 0x80 // low nibble used as argument +#define ASEQ_OP_SEQ_LDCHAN 0x90 // low nibble used as argument +#define ASEQ_OP_SEQ_RLDCHAN 0xA0 // low nibble used as argument +#define ASEQ_OP_SEQ_LDSEQ 0xB0 // low nibble used as argument +#define ASEQ_OP_SEQ_RUNSEQ 0xC4 +#define ASEQ_OP_SEQ_SCRIPTCTR 0xC5 +#define ASEQ_OP_SEQ_STOP 0xC6 +#define ASEQ_OP_SEQ_STSEQ 0xC7 +#define ASEQ_OP_SEQ_SUB 0xC8 +#define ASEQ_OP_SEQ_AND 0xC9 +#define ASEQ_OP_SEQ_LDI 0xCC +#define ASEQ_OP_SEQ_DYNCALL 0xCD +#define ASEQ_OP_SEQ_RAND 0xCE +#define ASEQ_OP_SEQ_NOTEALLOC 0xD0 +#define ASEQ_OP_SEQ_LDSHORTGATEARR 0xD1 +#define ASEQ_OP_SEQ_LDSHORTVELARR 0xD2 +#define ASEQ_OP_SEQ_MUTEBHV 0xD3 +#define ASEQ_OP_SEQ_MUTE 0xD4 +#define ASEQ_OP_SEQ_MUTESCALE 0xD5 +#define ASEQ_OP_SEQ_FREECHAN 0xD6 +#define ASEQ_OP_SEQ_INITCHAN 0xD7 +#define ASEQ_OP_SEQ_VOLSCALE 0xD9 +#define ASEQ_OP_SEQ_VOLMODE 0xDA +#define ASEQ_OP_SEQ_VOL 0xDB +#define ASEQ_OP_SEQ_TEMPOCHG 0xDC +#define ASEQ_OP_SEQ_TEMPO 0xDD +#define ASEQ_OP_SEQ_RTRANSPOSE 0xDE +#define ASEQ_OP_SEQ_TRANSPOSE 0xDF +#define ASEQ_OP_SEQ_EF 0xEF +#define ASEQ_OP_SEQ_FREENOTELIST 0xF0 +#define ASEQ_OP_SEQ_ALLOCNOTELIST 0xF1 + +// channel commands +#define ASEQ_OP_CHAN_CDELAY 0x00 // low nibble used as argument +#define ASEQ_OP_CHAN_LDSAMPLE 0x10 // low nibble used as argument +#define ASEQ_OP_CHAN_LDCHAN 0x20 // low nibble used as argument +#define ASEQ_OP_CHAN_STCIO 0x30 // low nibble used as argument +#define ASEQ_OP_CHAN_LDCIO 0x40 // low nibble used as argument +#define ASEQ_OP_CHAN_SUBIO 0x50 // low nibble used as argument +#define ASEQ_OP_CHAN_LDIO 0x60 // low nibble used as argument +#define ASEQ_OP_CHAN_STIO 0x70 // lower 3 bits used as argument +#define ASEQ_OP_CHAN_RLDLAYER 0x78 // lower 3 bits used as argument +#define ASEQ_OP_CHAN_TESTLAYER 0x80 // lower 3 bits used as argument +#define ASEQ_OP_CHAN_LDLAYER 0x88 // lower 3 bits used as argument +#define ASEQ_OP_CHAN_DELLAYER 0x90 // lower 3 bits used as argument +#define ASEQ_OP_CHAN_DYNLDLAYER 0x98 // lower 3 bits used as argument +#define ASEQ_OP_CHAN_LDFILTER 0xB0 +#define ASEQ_OP_CHAN_FREEFILTER 0xB1 +#define ASEQ_OP_CHAN_LDSEQTOPTR 0xB2 +#define ASEQ_OP_CHAN_FILTER 0xB3 +#define ASEQ_OP_CHAN_PTRTODYNTBL 0xB4 +#define ASEQ_OP_CHAN_DYNTBLTOPTR 0xB5 +#define ASEQ_OP_CHAN_DYNTBLV 0xB6 +#define ASEQ_OP_CHAN_RANDTOPTR 0xB7 +#define ASEQ_OP_CHAN_RAND 0xB8 +#define ASEQ_OP_CHAN_RANDVEL 0xB9 +#define ASEQ_OP_CHAN_RANDGATE 0xBA +#define ASEQ_OP_CHAN_COMBFILTER 0xBB +#define ASEQ_OP_CHAN_PTRADD 0xBC +#define ASEQ_OP_CHAN_SAMPLESTART 0xBD +#define ASEQ_OP_CHAN_INSTR 0xC1 +#define ASEQ_OP_CHAN_DYNTBL 0xC2 +#define ASEQ_OP_CHAN_SHORT 0xC3 +#define ASEQ_OP_CHAN_NOSHORT 0xC4 +#define ASEQ_OP_CHAN_DYNTBLLOOKUP 0xC5 +#define ASEQ_OP_CHAN_FONT 0xC6 +#define ASEQ_OP_CHAN_STSEQ 0xC7 +#define ASEQ_OP_CHAN_SUB 0xC8 +#define ASEQ_OP_CHAN_AND 0xC9 +#define ASEQ_OP_CHAN_MUTEBHV 0xCA +#define ASEQ_OP_CHAN_LDSEQ 0xCB +#define ASEQ_OP_CHAN_LDI 0xCC +#define ASEQ_OP_CHAN_STOPCHAN 0xCD +#define ASEQ_OP_CHAN_LDPTR 0xCE +#define ASEQ_OP_CHAN_STPTRTOSEQ 0xCF +#define ASEQ_OP_CHAN_EFFECTS 0xD0 +#define ASEQ_OP_CHAN_NOTEALLOC 0xD1 +#define ASEQ_OP_CHAN_SUSTAIN 0xD2 +#define ASEQ_OP_CHAN_BEND 0xD3 +#define ASEQ_OP_CHAN_REVERB 0xD4 +#define ASEQ_OP_CHAN_VIBFREQ 0xD7 +#define ASEQ_OP_CHAN_VIBDEPTH 0xD8 +#define ASEQ_OP_CHAN_RELEASERATE 0xD9 +#define ASEQ_OP_CHAN_ENV 0xDA +#define ASEQ_OP_CHAN_TRANSPOSE 0xDB +#define ASEQ_OP_CHAN_PANWEIGHT 0xDC +#define ASEQ_OP_CHAN_PAN 0xDD +#define ASEQ_OP_CHAN_FREQSCALE 0xDE +#define ASEQ_OP_CHAN_VOL 0xDF +#define ASEQ_OP_CHAN_VOLEXP 0xE0 +#define ASEQ_OP_CHAN_VIBFREQGRAD 0xE1 +#define ASEQ_OP_CHAN_VIBDEPTHGRAD 0xE2 +#define ASEQ_OP_CHAN_VIBDELAY 0xE3 +#define ASEQ_OP_CHAN_DYNCALL 0xE4 +#define ASEQ_OP_CHAN_REVERBIDX 0xE5 +#define ASEQ_OP_CHAN_SAMPLEBOOK 0xE6 +#define ASEQ_OP_CHAN_LDPARAMS 0xE7 +#define ASEQ_OP_CHAN_PARAMS 0xE8 +#define ASEQ_OP_CHAN_NOTEPRI 0xE9 +#define ASEQ_OP_CHAN_STOP 0xEA +#define ASEQ_OP_CHAN_FONTINSTR 0xEB +#define ASEQ_OP_CHAN_VIBRESET 0xEC +#define ASEQ_OP_CHAN_GAIN 0xED +#define ASEQ_OP_CHAN_BENDFINE 0xEE +#define ASEQ_OP_CHAN_EF 0xEF +#define ASEQ_OP_CHAN_FREENOTELIST 0xF0 +#define ASEQ_OP_CHAN_ALLOCNOTELIST 0xF1 + +// layer commands +#define ASEQ_OP_LAYER_NOTEDVG 0x00 +#define ASEQ_OP_LAYER_NOTEDV 0x40 +#define ASEQ_OP_LAYER_NOTEVG 0x80 +#define ASEQ_OP_LAYER_LDELAY 0xC0 +#define ASEQ_OP_LAYER_SHORTVEL 0xC1 +#define ASEQ_OP_LAYER_TRANSPOSE 0xC2 +#define ASEQ_OP_LAYER_SHORTDELAY 0xC3 +#define ASEQ_OP_LAYER_LEGATO 0xC4 +#define ASEQ_OP_LAYER_NOLEGATO 0xC5 +#define ASEQ_OP_LAYER_INSTR 0xC6 +#define ASEQ_OP_LAYER_PORTAMENTO 0xC7 +#define ASEQ_OP_LAYER_NOPORTAMENTO 0xC8 +#define ASEQ_OP_LAYER_SHORTGATE 0xC9 +#define ASEQ_OP_LAYER_NOTEPAN 0xCA +#define ASEQ_OP_LAYER_ENV 0xCB +#define ASEQ_OP_LAYER_NODRUMPAN 0xCC +#define ASEQ_OP_LAYER_STEREO 0xCD +#define ASEQ_OP_LAYER_BENDFINE 0xCE +#define ASEQ_OP_LAYER_RELEASERATE 0xCF +#define ASEQ_OP_LAYER_LDSHORTVEL 0xD0 // low nibble used as an argument +#define ASEQ_OP_LAYER_LDSHORTGATE 0xE0 // low nibble used as an argument + +#define PITCH_A0 0 +#define PITCH_BF0 1 +#define PITCH_B0 2 +#define PITCH_C1 3 +#define PITCH_DF1 4 +#define PITCH_D1 5 +#define PITCH_EF1 6 +#define PITCH_E1 7 +#define PITCH_F1 8 +#define PITCH_GF1 9 +#define PITCH_G1 10 +#define PITCH_AF1 11 +#define PITCH_A1 12 +#define PITCH_BF1 13 +#define PITCH_B1 14 +#define PITCH_C2 15 +#define PITCH_DF2 16 +#define PITCH_D2 17 +#define PITCH_EF2 18 +#define PITCH_E2 19 +#define PITCH_F2 20 +#define PITCH_GF2 21 +#define PITCH_G2 22 +#define PITCH_AF2 23 +#define PITCH_A2 24 +#define PITCH_BF2 25 +#define PITCH_B2 26 +#define PITCH_C3 27 +#define PITCH_DF3 28 +#define PITCH_D3 29 +#define PITCH_EF3 30 +#define PITCH_E3 31 +#define PITCH_F3 32 +#define PITCH_GF3 33 +#define PITCH_G3 34 +#define PITCH_AF3 35 +#define PITCH_A3 36 +#define PITCH_BF3 37 +#define PITCH_B3 38 +#define PITCH_C4 39 +#define PITCH_DF4 40 +#define PITCH_D4 41 +#define PITCH_EF4 42 +#define PITCH_E4 43 +#define PITCH_F4 44 +#define PITCH_GF4 45 +#define PITCH_G4 46 +#define PITCH_AF4 47 +#define PITCH_A4 48 +#define PITCH_BF4 49 +#define PITCH_B4 50 +#define PITCH_C5 51 +#define PITCH_DF5 52 +#define PITCH_D5 53 +#define PITCH_EF5 54 +#define PITCH_E5 55 +#define PITCH_F5 56 +#define PITCH_GF5 57 +#define PITCH_G5 58 +#define PITCH_AF5 59 +#define PITCH_A5 60 +#define PITCH_BF5 61 +#define PITCH_B5 62 +#define PITCH_C6 63 +#define PITCH_DF6 64 +#define PITCH_D6 65 +#define PITCH_EF6 66 +#define PITCH_E6 67 +#define PITCH_F6 68 +#define PITCH_GF6 69 +#define PITCH_G6 70 +#define PITCH_AF6 71 +#define PITCH_A6 72 +#define PITCH_BF6 73 +#define PITCH_B6 74 +#define PITCH_C7 75 +#define PITCH_DF7 76 +#define PITCH_D7 77 +#define PITCH_EF7 78 +#define PITCH_E7 79 +#define PITCH_F7 80 +#define PITCH_GF7 81 +#define PITCH_G7 82 +#define PITCH_AF7 83 +#define PITCH_A7 84 +#define PITCH_BF7 85 +#define PITCH_B7 86 +#define PITCH_C8 87 +#define PITCH_DF8 88 +#define PITCH_D8 89 +#define PITCH_EF8 90 +#define PITCH_E8 91 +#define PITCH_F8 92 +#define PITCH_GF8 93 +#define PITCH_G8 94 +#define PITCH_AF8 95 +#define PITCH_A8 96 +#define PITCH_BF8 97 +#define PITCH_B8 98 +#define PITCH_C9 99 +#define PITCH_DF9 100 +#define PITCH_D9 101 +#define PITCH_EF9 102 +#define PITCH_E9 103 +#define PITCH_F9 104 +#define PITCH_GF9 105 +#define PITCH_G9 106 +#define PITCH_AF9 107 +#define PITCH_A9 108 +#define PITCH_BF9 109 +#define PITCH_B9 110 +#define PITCH_C10 111 +#define PITCH_DF10 112 +#define PITCH_D10 113 +#define PITCH_EF10 114 +#define PITCH_E10 115 +#define PITCH_F10 116 +#define PITCH_BFNEG1 117 +#define PITCH_BNEG1 118 +#define PITCH_C0 119 +#define PITCH_DF0 120 +#define PITCH_D0 121 +#define PITCH_EF0 122 +#define PITCH_E0 123 +#define PITCH_F0 124 +#define PITCH_GF0 125 +#define PITCH_G0 126 +#define PITCH_AF0 127 + +#define READ_U8 (((count++), (reader.ReadUByte()))) +#define READ_S16 (((count+=2), (reader.ReadUInt16()))) +#define READ_CS16 (((count++), (temp = reader.ReadUByte(), (temp & 0x80) ? (count++, ((temp) << 8) | reader.ReadUByte()) : temp))) + +#define FORMAT_HEX(x, w) "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(w) << x << std::nouppercase << std::dec +#define FORMAT_HEX2(x, w) std::hex << std::uppercase << std::setfill('0') << std::setw(w) << x << std::nouppercase << std::dec + +ExportResult FZX::SequenceHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + + return std::nullopt; +} + +ExportResult FZX::SequenceCodeExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + const auto offset = GetSafeNode<uint32_t>(node, "offset"); + const auto data = std::static_pointer_cast<SequenceData>(raw); + + write << "u8 " << symbol << "[] = {\n"; + + std::sort(data->mCmds.begin(), data->mCmds.end()); + + uint32_t lastEndPos = 0; + for (const auto& command : data->mCmds) { + if (lastEndPos != command.pos) { + write << fourSpaceTab << "/* Missing instruction or padding at: " << FORMAT_HEX(lastEndPos, 3) << " ( With Gap " << FORMAT_HEX(command.pos - lastEndPos, 2) << " )*/"; + + if (command.pos - lastEndPos < 0x10) { + for (uint32_t i = lastEndPos; i < command.pos; i++) { + write << " 0,"; + } + } + write << "\n"; + } + lastEndPos = command.pos + command.size; + + if (data->mLabels.contains(command.pos)) { + write << "// L____" << FORMAT_HEX2(command.pos, 3) << ":\n"; + } + + if (command.state == SequenceState::data) { + uint32_t i = 0; + write << fourSpaceTab << "/* DATA LABELS */\n"; + for (const auto& arg: command.args) { + write << fourSpaceTab << "/* " << FORMAT_HEX(command.pos + 2 * i, 3) << " - " << FORMAT_HEX(command.pos + 2 * (i + 1), 3) << " */ "; + write << "S16(" << FORMAT_HEX(std::get<uint16_t>(arg), 3) << "), "; + write << "// L____" << FORMAT_HEX2(std::get<uint16_t>(arg), 3) << "\n"; + i++; + } + write << fourSpaceTab << "/* DATA END */\n"; + continue; + } + + if (command.state == SequenceState::envelope) { + uint32_t i = 0; + write << fourSpaceTab << "/* ENVELOPE START */\n"; + for (const auto& arg: command.args) { + if (i % 2 == 0) { + write << fourSpaceTab << "/* " << FORMAT_HEX(command.pos + 2 * i, 3) << " - " << FORMAT_HEX(command.pos + 2 * (i + 2), 3) << " */ "; + write << "S16(" << FORMAT_HEX(std::get<uint16_t>(arg), 4) << "), "; + } else { + write << "S16(" << FORMAT_HEX(std::get<uint16_t>(arg), 4) << "),\n"; + } + i++; + } + write << fourSpaceTab << "/* ENVELOPE END */\n"; + continue; + } + + write << fourSpaceTab << "/* " << FORMAT_HEX(command.pos, 3) << " - " << FORMAT_HEX(command.pos + command.size, 3) << " */ "; + + if (command.channel != -1) { + write << "/* Channel: " << command.channel << " */ "; + if (command.layer != -1) { + write << "/* Layer: " << command.layer << " */ "; + } + } + switch (command.cmd) { + case ASEQ_OP_RBLTZ: + write << "ASEQ_OP_RBLTZ,"; + break; + case ASEQ_OP_RBEQZ: + write << "ASEQ_OP_RBEQZ,"; + break; + case ASEQ_OP_RJUMP: + write << "ASEQ_OP_RJUMP,"; + break; + case ASEQ_OP_BGEZ: + write << "ASEQ_OP_BGEZ,"; + break; + case ASEQ_OP_BREAK: + write << "ASEQ_OP_BREAK,"; + break; + case ASEQ_OP_LOOPEND: + write << "ASEQ_OP_LOOPEND,"; + break; + case ASEQ_OP_LOOP: + write << "ASEQ_OP_LOOP,"; + break; + case ASEQ_OP_BLTZ: + write << "ASEQ_OP_BLTZ,"; + break; + case ASEQ_OP_BEQZ: + write << "ASEQ_OP_BEQZ,"; + break; + case ASEQ_OP_JUMP: + write << "ASEQ_OP_JUMP,"; + break; + case ASEQ_OP_CALL: + write << "ASEQ_OP_CALL,"; + break; + case ASEQ_OP_DELAY: + write << "ASEQ_OP_DELAY,"; + break; + case ASEQ_OP_DELAY1: + write << "ASEQ_OP_DELAY1,"; + break; + case ASEQ_OP_END: + write << "ASEQ_OP_END,"; + break; + } + if (command.cmd < ASEQ_OP_CONTROL_FLOW_FIRST) { + if (command.state == SequenceState::player) { + if (command.cmd >= 0xC0) { + switch (command.cmd) { + case ASEQ_OP_SEQ_RUNSEQ: + write << "ASEQ_OP_SEQ_RUNSEQ,"; + break; + case ASEQ_OP_SEQ_SCRIPTCTR: + write << "ASEQ_OP_SEQ_SCRIPTCTR,"; + break; + case ASEQ_OP_SEQ_STOP: + write << "ASEQ_OP_SEQ_STOP,"; + break; + case ASEQ_OP_SEQ_STSEQ: + write << "ASEQ_OP_SEQ_STSEQ,"; + break; + case ASEQ_OP_SEQ_SUB: + write << "ASEQ_OP_SEQ_SUB,"; + break; + case ASEQ_OP_SEQ_AND: + write << "ASEQ_OP_SEQ_AND,"; + break; + case ASEQ_OP_SEQ_LDI: + write << "ASEQ_OP_SEQ_LDI,"; + break; + case ASEQ_OP_SEQ_DYNCALL: + write << "ASEQ_OP_SEQ_DYNCALL,"; + break; + case ASEQ_OP_SEQ_RAND: + write << "ASEQ_OP_SEQ_RAND,"; + break; + case ASEQ_OP_SEQ_NOTEALLOC: + write << "ASEQ_OP_SEQ_NOTEALLOC,"; + break; + case ASEQ_OP_SEQ_LDSHORTGATEARR: + write << "ASEQ_OP_SEQ_LDSHORTGATEARR,"; + break; + case ASEQ_OP_SEQ_LDSHORTVELARR: + write << "ASEQ_OP_SEQ_LDSHORTVELARR,"; + break; + case ASEQ_OP_SEQ_MUTEBHV: + write << "ASEQ_OP_SEQ_MUTEBHV,"; + break; + case ASEQ_OP_SEQ_MUTE: + write << "ASEQ_OP_SEQ_MUTE,"; + break; + case ASEQ_OP_SEQ_MUTESCALE: + write << "ASEQ_OP_SEQ_MUTESCALE,"; + break; + case ASEQ_OP_SEQ_FREECHAN: + write << "ASEQ_OP_SEQ_FREECHAN,"; + break; + case ASEQ_OP_SEQ_INITCHAN: + write << "ASEQ_OP_SEQ_INITCHAN,"; + break; + case ASEQ_OP_SEQ_VOLSCALE: + write << "ASEQ_OP_SEQ_VOLSCALE,"; + break; + case ASEQ_OP_SEQ_VOLMODE: + write << "ASEQ_OP_SEQ_VOLMODE,"; + break; + case ASEQ_OP_SEQ_VOL: + write << "ASEQ_OP_SEQ_VOL,"; + break; + case ASEQ_OP_SEQ_TEMPOCHG: + write << "ASEQ_OP_SEQ_TEMPOCHG,"; + break; + case ASEQ_OP_SEQ_TEMPO: + write << "ASEQ_OP_SEQ_TEMPO,"; + break; + case ASEQ_OP_SEQ_RTRANSPOSE: + write << "ASEQ_OP_SEQ_RTRANSPOSE,"; + break; + case ASEQ_OP_SEQ_TRANSPOSE: + write << "ASEQ_OP_SEQ_TRANSPOSE,"; + break; + case ASEQ_OP_SEQ_EF: + write << "ASEQ_OP_SEQ_EF,"; + break; + case ASEQ_OP_SEQ_FREENOTELIST: + write << "ASEQ_OP_SEQ_FREENOTELIST,"; + break; + case ASEQ_OP_SEQ_ALLOCNOTELIST: + write << "ASEQ_OP_SEQ_ALLOCNOTELIST,"; + break; + } + } else { + switch (command.cmd & 0xF0) { + case ASEQ_OP_SEQ_TESTCHAN: + write << "(ASEQ_OP_SEQ_TESTCHAN | "; + break; + case ASEQ_OP_SEQ_STOPCHAN: + write << "(ASEQ_OP_SEQ_STOPCHAN | "; + break; + case ASEQ_OP_SEQ_SUBIO: + write << "(ASEQ_OP_SEQ_SUBIO | "; + break; + case ASEQ_OP_SEQ_LDRES: + write << "(ASEQ_OP_SEQ_LDRES | "; + break; + case ASEQ_OP_SEQ_STIO: + write << "(ASEQ_OP_SEQ_STIO | "; + break; + case ASEQ_OP_SEQ_LDIO: + write << "(ASEQ_OP_SEQ_LDIO | "; + break; + case ASEQ_OP_SEQ_LDCHAN: + write << "(ASEQ_OP_SEQ_LDCHAN | "; + break; + case ASEQ_OP_SEQ_RLDCHAN: + write << "(ASEQ_OP_SEQ_RLDCHAN | "; + break; + case ASEQ_OP_SEQ_LDSEQ: + write << "(ASEQ_OP_SEQ_LDSEQ | "; + break; + } + write << FORMAT_HEX((command.cmd & 0xF), 1) << "),"; + } + } else if (command.state == SequenceState::channel) { + if (command.cmd >= 0xB0) { + switch (command.cmd) { + case ASEQ_OP_CHAN_LDFILTER: + write << "ASEQ_OP_CHAN_LDFILTER,"; + break; + case ASEQ_OP_CHAN_FREEFILTER: + write << "ASEQ_OP_CHAN_FREEFILTER,"; + break; + case ASEQ_OP_CHAN_LDSEQTOPTR: + write << "ASEQ_OP_CHAN_LDSEQTOPTR,"; + break; + case ASEQ_OP_CHAN_FILTER: + write << "ASEQ_OP_CHAN_FILTER,"; + break; + case ASEQ_OP_CHAN_PTRTODYNTBL: + write << "ASEQ_OP_CHAN_PTRTODYNTBL,"; + break; + case ASEQ_OP_CHAN_DYNTBLTOPTR: + write << "ASEQ_OP_CHAN_DYNTBLTOPTR,"; + break; + case ASEQ_OP_CHAN_DYNTBLV: + write << "ASEQ_OP_CHAN_DYNTBLV,"; + break; + case ASEQ_OP_CHAN_RANDTOPTR: + write << "ASEQ_OP_CHAN_RANDTOPTR,"; + break; + case ASEQ_OP_CHAN_RAND: + write << "ASEQ_OP_CHAN_RAND,"; + break; + case ASEQ_OP_CHAN_RANDVEL: + write << "ASEQ_OP_CHAN_RANDVEL,"; + break; + case ASEQ_OP_CHAN_RANDGATE: + write << "ASEQ_OP_CHAN_RANDGATE,"; + break; + case ASEQ_OP_CHAN_COMBFILTER: + write << "ASEQ_OP_CHAN_COMBFILTER,"; + break; + case ASEQ_OP_CHAN_PTRADD: + write << "ASEQ_OP_CHAN_PTRADD,"; + break; + case ASEQ_OP_CHAN_SAMPLESTART: + write << "ASEQ_OP_CHAN_SAMPLESTART,"; + break; + case ASEQ_OP_CHAN_INSTR: + write << "ASEQ_OP_CHAN_INSTR,"; + break; + case ASEQ_OP_CHAN_DYNTBL: + write << "ASEQ_OP_CHAN_DYNTBL,"; + break; + case ASEQ_OP_CHAN_SHORT: + write << "ASEQ_OP_CHAN_SHORT,"; + break; + case ASEQ_OP_CHAN_NOSHORT: + write << "ASEQ_OP_CHAN_NOSHORT,"; + break; + case ASEQ_OP_CHAN_DYNTBLLOOKUP: + write << "ASEQ_OP_CHAN_DYNTBLLOOKUP,"; + break; + case ASEQ_OP_CHAN_FONT: + write << "ASEQ_OP_CHAN_FONT,"; + break; + case ASEQ_OP_CHAN_STSEQ: + write << "ASEQ_OP_CHAN_STSEQ,"; + break; + case ASEQ_OP_CHAN_SUB: + write << "ASEQ_OP_CHAN_SUB,"; + break; + case ASEQ_OP_CHAN_AND: + write << "ASEQ_OP_CHAN_AND,"; + break; + case ASEQ_OP_CHAN_MUTEBHV: + write << "ASEQ_OP_CHAN_MUTEBHV,"; + break; + case ASEQ_OP_CHAN_LDSEQ: + write << "ASEQ_OP_CHAN_LDSEQ,"; + break; + case ASEQ_OP_CHAN_LDI: + write << "ASEQ_OP_CHAN_LDI,"; + break; + case ASEQ_OP_CHAN_STOPCHAN: + write << "ASEQ_OP_CHAN_STOPCHAN,"; + break; + case ASEQ_OP_CHAN_LDPTR: + write << "ASEQ_OP_CHAN_LDPTR,"; + break; + case ASEQ_OP_CHAN_STPTRTOSEQ: + write << "ASEQ_OP_CHAN_STPTRTOSEQ,"; + break; + case ASEQ_OP_CHAN_EFFECTS: + write << "ASEQ_OP_CHAN_EFFECTS,"; + break; + case ASEQ_OP_CHAN_NOTEALLOC: + write << "ASEQ_OP_CHAN_NOTEALLOC,"; + break; + case ASEQ_OP_CHAN_SUSTAIN: + write << "ASEQ_OP_CHAN_SUSTAIN,"; + break; + case ASEQ_OP_CHAN_BEND: + write << "ASEQ_OP_CHAN_BEND,"; + break; + case ASEQ_OP_CHAN_REVERB: + write << "ASEQ_OP_CHAN_REVERB,"; + break; + case ASEQ_OP_CHAN_VIBFREQ: + write << "ASEQ_OP_CHAN_VIBFREQ,"; + break; + case ASEQ_OP_CHAN_VIBDEPTH: + write << "ASEQ_OP_CHAN_VIBDEPTH,"; + break; + case ASEQ_OP_CHAN_RELEASERATE: + write << "ASEQ_OP_CHAN_RELEASERATE,"; + break; + case ASEQ_OP_CHAN_ENV: + write << "ASEQ_OP_CHAN_ENV,"; + break; + case ASEQ_OP_CHAN_TRANSPOSE: + write << "ASEQ_OP_CHAN_TRANSPOSE,"; + break; + case ASEQ_OP_CHAN_PANWEIGHT: + write << "ASEQ_OP_CHAN_PANWEIGHT,"; + break; + case ASEQ_OP_CHAN_PAN: + write << "ASEQ_OP_CHAN_PAN,"; + break; + case ASEQ_OP_CHAN_FREQSCALE: + write << "ASEQ_OP_CHAN_FREQSCALE,"; + break; + case ASEQ_OP_CHAN_VOL: + write << "ASEQ_OP_CHAN_VOL,"; + break; + case ASEQ_OP_CHAN_VOLEXP: + write << "ASEQ_OP_CHAN_VOLEXP,"; + break; + case ASEQ_OP_CHAN_VIBFREQGRAD: + write << "ASEQ_OP_CHAN_VIBFREQGRAD,"; + break; + case ASEQ_OP_CHAN_VIBDEPTHGRAD: + write << "ASEQ_OP_CHAN_VIBDEPTHGRAD,"; + break; + case ASEQ_OP_CHAN_VIBDELAY: + write << "ASEQ_OP_CHAN_VIBDELAY,"; + break; + case ASEQ_OP_CHAN_DYNCALL: + write << "ASEQ_OP_CHAN_DYNCALL,"; + break; + case ASEQ_OP_CHAN_REVERBIDX: + write << "ASEQ_OP_CHAN_REVERBIDX,"; + break; + case ASEQ_OP_CHAN_SAMPLEBOOK: + write << "ASEQ_OP_CHAN_SAMPLEBOOK,"; + break; + case ASEQ_OP_CHAN_LDPARAMS: + write << "ASEQ_OP_CHAN_LDPARAMS,"; + break; + case ASEQ_OP_CHAN_PARAMS: + write << "ASEQ_OP_CHAN_PARAMS,"; + break; + case ASEQ_OP_CHAN_NOTEPRI: + write << "ASEQ_OP_CHAN_NOTEPRI,"; + break; + case ASEQ_OP_CHAN_STOP: + write << "ASEQ_OP_CHAN_STOP,"; + break; + case ASEQ_OP_CHAN_FONTINSTR: + write << "ASEQ_OP_CHAN_FONTINSTR,"; + break; + case ASEQ_OP_CHAN_VIBRESET: + write << "ASEQ_OP_CHAN_VIBRESET,"; + break; + case ASEQ_OP_CHAN_GAIN: + write << "ASEQ_OP_CHAN_GAIN,"; + break; + case ASEQ_OP_CHAN_BENDFINE: + write << "ASEQ_OP_CHAN_BENDFINE,"; + break; + case ASEQ_OP_CHAN_EF: + write << "ASEQ_OP_CHAN_EF,"; + break; + case ASEQ_OP_CHAN_FREENOTELIST: + write << "ASEQ_OP_CHAN_FREENOTELIST,"; + break; + case ASEQ_OP_CHAN_ALLOCNOTELIST: + write << "ASEQ_OP_CHAN_ALLOCNOTELIST,"; + break; + } + } else if (command.cmd >= 0x78) { + switch (command.cmd & 0xF8) { + case ASEQ_OP_CHAN_RLDLAYER: + write << "(ASEQ_OP_CHAN_RLDLAYER | "; + break; + case ASEQ_OP_CHAN_TESTLAYER: + write << "(ASEQ_OP_CHAN_TESTLAYER | "; + break; + case ASEQ_OP_CHAN_LDLAYER: + write << "(ASEQ_OP_CHAN_LDLAYER | "; + break; + case ASEQ_OP_CHAN_DELLAYER: + write << "(ASEQ_OP_CHAN_DELLAYER | "; + break; + case ASEQ_OP_CHAN_DYNLDLAYER: + write << "(ASEQ_OP_CHAN_DYNLDLAYER | "; + break; + } + write << FORMAT_HEX((command.cmd & 0x7), 1) << "),"; + } else { + switch (command.cmd & 0xF0) { + case ASEQ_OP_CHAN_CDELAY: + write << "(ASEQ_OP_CHAN_CDELAY | "; + break; + case ASEQ_OP_CHAN_LDSAMPLE: + write << "(ASEQ_OP_CHAN_LDSAMPLE | "; + break; + case ASEQ_OP_CHAN_LDCHAN: + write << "(ASEQ_OP_CHAN_LDCHAN | "; + break; + case ASEQ_OP_CHAN_STCIO: + write << "(ASEQ_OP_CHAN_STCIO | "; + break; + case ASEQ_OP_CHAN_LDCIO: + write << "(ASEQ_OP_CHAN_LDCIO | "; + break; + case ASEQ_OP_CHAN_SUBIO: + write << "(ASEQ_OP_CHAN_SUBIO | "; + break; + case ASEQ_OP_CHAN_LDIO: + write << "(ASEQ_OP_CHAN_LDIO | "; + break; + case ASEQ_OP_CHAN_STIO: + write << "(ASEQ_OP_CHAN_STIO | "; + break; + } + write << FORMAT_HEX((command.cmd & 0xF), 1) << "),"; + } + } else if (command.state == SequenceState::layer) { + if (command.cmd > 0xC0) { + switch (command.cmd) { + case ASEQ_OP_LAYER_SHORTVEL: + write << "ASEQ_OP_LAYER_SHORTVEL,"; + break; + case ASEQ_OP_LAYER_TRANSPOSE: + write << "ASEQ_OP_LAYER_TRANSPOSE,"; + break; + case ASEQ_OP_LAYER_SHORTDELAY: + write << "ASEQ_OP_LAYER_SHORTDELAY,"; + break; + case ASEQ_OP_LAYER_LEGATO: + write << "ASEQ_OP_LAYER_LEGATO,"; + break; + case ASEQ_OP_LAYER_NOLEGATO: + write << "ASEQ_OP_LAYER_NOLEGATO,"; + break; + case ASEQ_OP_LAYER_INSTR: + write << "ASEQ_OP_LAYER_INSTR,"; + break; + case ASEQ_OP_LAYER_PORTAMENTO: + write << "ASEQ_OP_LAYER_PORTAMENTO,"; + break; + case ASEQ_OP_LAYER_NOPORTAMENTO: + write << "ASEQ_OP_LAYER_NOPORTAMENTO,"; + break; + case ASEQ_OP_LAYER_SHORTGATE: + write << "ASEQ_OP_LAYER_SHORTGATE,"; + break; + case ASEQ_OP_LAYER_NOTEPAN: + write << "ASEQ_OP_LAYER_NOTEPAN,"; + break; + case ASEQ_OP_LAYER_ENV: + write << "ASEQ_OP_LAYER_ENV,"; + break; + case ASEQ_OP_LAYER_NODRUMPAN: + write << "ASEQ_OP_LAYER_NODRUMPAN,"; + break; + case ASEQ_OP_LAYER_STEREO: + write << "ASEQ_OP_LAYER_STEREO,"; + break; + case ASEQ_OP_LAYER_BENDFINE: + write << "ASEQ_OP_LAYER_BENDFINE,"; + break; + case ASEQ_OP_LAYER_RELEASERATE: + write << "ASEQ_OP_LAYER_RELEASERATE,"; + break; + default: + switch (command.cmd & 0xF0) { + case ASEQ_OP_LAYER_LDSHORTVEL: + write << "(ASEQ_OP_LAYER_LDSHORTVEL | "; + write << FORMAT_HEX((command.cmd & 0xF), 1) << "),"; + break; + case ASEQ_OP_LAYER_LDSHORTGATE: + write << "(ASEQ_OP_LAYER_LDSHORTGATE | "; + write << FORMAT_HEX((command.cmd & 0xF), 1) << "),"; + break; + } + break; + } + } else { + if (command.cmd == ASEQ_OP_LAYER_LDELAY) { + write << "ASEQ_OP_LAYER_LDELAY,"; + } else { + switch (command.cmd & 0xC0) { + case ASEQ_OP_LAYER_NOTEDVG: + write << "(ASEQ_OP_LAYER_NOTEDVG | "; + break; + case ASEQ_OP_LAYER_NOTEDV: + write << "(ASEQ_OP_LAYER_NOTEDV | "; + break; + case ASEQ_OP_LAYER_NOTEVG: + write << "(ASEQ_OP_LAYER_NOTEVG | "; + break; + } + + switch (command.cmd - (command.cmd & 0xC0)) { + case PITCH_A0: + write << "PITCH_A0),"; + break; + case PITCH_BF0: + write << "PITCH_BF0),"; + break; + case PITCH_B0: + write << "PITCH_B0),"; + break; + case PITCH_C1: + write << "PITCH_C1),"; + break; + case PITCH_DF1: + write << "PITCH_DF1),"; + break; + case PITCH_D1: + write << "PITCH_D1),"; + break; + case PITCH_EF1: + write << "PITCH_EF1),"; + break; + case PITCH_E1: + write << "PITCH_E1),"; + break; + case PITCH_F1: + write << "PITCH_F1),"; + break; + case PITCH_GF1: + write << "PITCH_GF1),"; + break; + case PITCH_G1: + write << "PITCH_G1),"; + break; + case PITCH_AF1: + write << "PITCH_AF1),"; + break; + case PITCH_A1: + write << "PITCH_A1),"; + break; + case PITCH_BF1: + write << "PITCH_BF1),"; + break; + case PITCH_B1: + write << "PITCH_B1),"; + break; + case PITCH_C2: + write << "PITCH_C2),"; + break; + case PITCH_DF2: + write << "PITCH_DF2),"; + break; + case PITCH_D2: + write << "PITCH_D2),"; + break; + case PITCH_EF2: + write << "PITCH_EF2),"; + break; + case PITCH_E2: + write << "PITCH_E2),"; + break; + case PITCH_F2: + write << "PITCH_F2),"; + break; + case PITCH_GF2: + write << "PITCH_GF2),"; + break; + case PITCH_G2: + write << "PITCH_G2),"; + break; + case PITCH_AF2: + write << "PITCH_AF2),"; + break; + case PITCH_A2: + write << "PITCH_A2),"; + break; + case PITCH_BF2: + write << "PITCH_BF2),"; + break; + case PITCH_B2: + write << "PITCH_B2),"; + break; + case PITCH_C3: + write << "PITCH_C3),"; + break; + case PITCH_DF3: + write << "PITCH_DF3),"; + break; + case PITCH_D3: + write << "PITCH_D3),"; + break; + case PITCH_EF3: + write << "PITCH_EF3),"; + break; + case PITCH_E3: + write << "PITCH_E3),"; + break; + case PITCH_F3: + write << "PITCH_F3),"; + break; + case PITCH_GF3: + write << "PITCH_GF3),"; + break; + case PITCH_G3: + write << "PITCH_G3),"; + break; + case PITCH_AF3: + write << "PITCH_AF3),"; + break; + case PITCH_A3: + write << "PITCH_A3),"; + break; + case PITCH_BF3: + write << "PITCH_BF3),"; + break; + case PITCH_B3: + write << "PITCH_B3),"; + break; + case PITCH_C4: + write << "PITCH_C4),"; + break; + case PITCH_DF4: + write << "PITCH_DF4),"; + break; + case PITCH_D4: + write << "PITCH_D4),"; + break; + case PITCH_EF4: + write << "PITCH_EF4),"; + break; + case PITCH_E4: + write << "PITCH_E4),"; + break; + case PITCH_F4: + write << "PITCH_F4),"; + break; + case PITCH_GF4: + write << "PITCH_GF4),"; + break; + case PITCH_G4: + write << "PITCH_G4),"; + break; + case PITCH_AF4: + write << "PITCH_AF4),"; + break; + case PITCH_A4: + write << "PITCH_A4),"; + break; + case PITCH_BF4: + write << "PITCH_BF4),"; + break; + case PITCH_B4: + write << "PITCH_B4),"; + break; + case PITCH_C5: + write << "PITCH_C5),"; + break; + case PITCH_DF5: + write << "PITCH_DF5),"; + break; + case PITCH_D5: + write << "PITCH_D5),"; + break; + case PITCH_EF5: + write << "PITCH_EF5),"; + break; + case PITCH_E5: + write << "PITCH_E5),"; + break; + case PITCH_F5: + write << "PITCH_F5),"; + break; + case PITCH_GF5: + write << "PITCH_GF5),"; + break; + case PITCH_G5: + write << "PITCH_G5),"; + break; + case PITCH_AF5: + write << "PITCH_AF5),"; + break; + case PITCH_A5: + write << "PITCH_A5),"; + break; + case PITCH_BF5: + write << "PITCH_BF5),"; + break; + case PITCH_B5: + write << "PITCH_B5),"; + break; + case PITCH_C6: + write << "PITCH_C6),"; + break; + case PITCH_DF6: + write << "PITCH_DF6),"; + break; + case PITCH_D6: + write << "PITCH_D6),"; + break; + case PITCH_EF6: + write << "PITCH_EF6),"; + break; + case PITCH_E6: + write << "PITCH_E6),"; + break; + case PITCH_F6: + write << "PITCH_F6),"; + break; + case PITCH_GF6: + write << "PITCH_GF6),"; + break; + case PITCH_G6: + write << "PITCH_G6),"; + break; + case PITCH_AF6: + write << "PITCH_AF6),"; + break; + case PITCH_A6: + write << "PITCH_A6),"; + break; + case PITCH_BF6: + write << "PITCH_BF6),"; + break; + case PITCH_B6: + write << "PITCH_B6),"; + break; + case PITCH_C7: + write << "PITCH_C7),"; + break; + case PITCH_DF7: + write << "PITCH_DF7),"; + break; + case PITCH_D7: + write << "PITCH_D7),"; + break; + case PITCH_EF7: + write << "PITCH_EF7),"; + break; + case PITCH_E7: + write << "PITCH_E7),"; + break; + case PITCH_F7: + write << "PITCH_F7),"; + break; + case PITCH_GF7: + write << "PITCH_GF7),"; + break; + case PITCH_G7: + write << "PITCH_G7),"; + break; + case PITCH_AF7: + write << "PITCH_AF7),"; + break; + case PITCH_A7: + write << "PITCH_A7),"; + break; + case PITCH_BF7: + write << "PITCH_BF7),"; + break; + case PITCH_B7: + write << "PITCH_B7),"; + break; + case PITCH_C8: + write << "PITCH_C8),"; + break; + case PITCH_DF8: + write << "PITCH_DF8),"; + break; + case PITCH_D8: + write << "PITCH_D8),"; + break; + case PITCH_EF8: + write << "PITCH_EF8),"; + break; + case PITCH_E8: + write << "PITCH_E8),"; + break; + case PITCH_F8: + write << "PITCH_F8),"; + break; + case PITCH_GF8: + write << "PITCH_GF8),"; + break; + case PITCH_G8: + write << "PITCH_G8),"; + break; + case PITCH_AF8: + write << "PITCH_AF8),"; + break; + case PITCH_A8: + write << "PITCH_A8),"; + break; + case PITCH_BF8: + write << "PITCH_BF8),"; + break; + case PITCH_B8: + write << "PITCH_B8),"; + break; + case PITCH_C9: + write << "PITCH_C9),"; + break; + case PITCH_DF9: + write << "PITCH_DF9),"; + break; + case PITCH_D9: + write << "PITCH_D9),"; + break; + case PITCH_EF9: + write << "PITCH_EF9),"; + break; + case PITCH_E9: + write << "PITCH_E9),"; + break; + case PITCH_F9: + write << "PITCH_F9),"; + break; + case PITCH_GF9: + write << "PITCH_GF9),"; + break; + case PITCH_G9: + write << "PITCH_G9),"; + break; + case PITCH_AF9: + write << "PITCH_AF9),"; + break; + case PITCH_A9: + write << "PITCH_A9),"; + break; + case PITCH_BF9: + write << "PITCH_BF9),"; + break; + case PITCH_B9: + write << "PITCH_B9),"; + break; + case PITCH_C10: + write << "PITCH_C10),"; + break; + case PITCH_DF10: + write << "PITCH_DF10),"; + break; + case PITCH_D10: + write << "PITCH_D10),"; + break; + case PITCH_EF10: + write << "PITCH_EF10),"; + break; + case PITCH_E10: + write << "PITCH_E10),"; + break; + case PITCH_F10: + write << "PITCH_F10),"; + break; + case PITCH_BFNEG1: + write << "PITCH_BFNEG1),"; + break; + case PITCH_BNEG1: + write << "PITCH_BNEG1),"; + break; + case PITCH_C0: + write << "PITCH_C0),"; + break; + case PITCH_DF0: + write << "PITCH_DF0),"; + break; + case PITCH_D0: + write << "PITCH_D0),"; + break; + case PITCH_EF0: + write << "PITCH_EF0),"; + break; + case PITCH_E0: + write << "PITCH_E0),"; + break; + case PITCH_F0: + write << "PITCH_F0),"; + break; + case PITCH_GF0: + write << "PITCH_GF0),"; + break; + case PITCH_G0: + write << "PITCH_G0),"; + break; + case PITCH_AF0: + write << "PITCH_AF0),"; + break; + } + } + } + } + } + + for (const auto& arg : command.args) { + write << " "; + switch(static_cast<SeqArgType>(arg.index())) { + case SeqArgType::U8: + // if (command.cmd == ASEQ_OP_RJUMP || command.cmd == ASEQ_OP_RBLTZ || command.cmd == ASEQ_OP_RBEQZ) { + // int32_t temp = static_cast<int32_t>((int8_t)(std::get<uint8_t>(arg) & 0xFF)); + // if (temp >= 0) { + // write << FORMAT_HEX(temp, 2) << ","; + // } else { + // write << "-" << FORMAT_HEX((0x100 - (temp & 0xFF)), 2) << ","; + // } + // } else { + write << FORMAT_HEX(static_cast<uint32_t>(std::get<uint8_t>(arg)), 2) << ","; + // } + break; + case SeqArgType::S16: + write << "S16(" << FORMAT_HEX(std::get<uint16_t>(arg), 4) << "),"; + break; + case SeqArgType::CS16: + if (std::get<uint32_t>(arg) & 0x8000) { + write << "CS16(" << FORMAT_HEX((std::get<uint32_t>(arg) & 0x7FFF), 4) << "),"; + } else { + write << FORMAT_HEX(std::get<uint32_t>(arg), 4) << ","; + } + break; + } + } + + if (command.cmd == ASEQ_OP_RJUMP || command.cmd == ASEQ_OP_RBLTZ || command.cmd == ASEQ_OP_RBEQZ) { + write << " /* LABEL: L____" << FORMAT_HEX2(command.pos + static_cast<int32_t>((int8_t)(std::get<uint8_t>(command.args.at(0)) & 0xFF)) + 2, 3) << " */"; + } else if (command.cmd == ASEQ_OP_JUMP || command.cmd == ASEQ_OP_BLTZ || command.cmd == ASEQ_OP_BEQZ || ((command.cmd & 0xF0) == ASEQ_OP_SEQ_LDCHAN && command.state == SequenceState::player) || ((command.cmd & 0xF8) == ASEQ_OP_CHAN_LDLAYER && command.state == SequenceState::channel)) { + write << " /* LABEL: L____" << FORMAT_HEX2(std::get<uint16_t>(command.args.at(0)), 3) << " */"; + } + + write << "\n"; + } + if (data->mHasFooter) { + const std::string mmlPassCheckStr = "== MML PASS CHECK =="; + + write << fourSpaceTab << "/* " << FORMAT_HEX(lastEndPos, 3) << " - " << FORMAT_HEX(lastEndPos + mmlPassCheckStr.length(), 3) << " */ "; + for (size_t i = 0; i < mmlPassCheckStr.length(); i++) { + write << "\'" << mmlPassCheckStr.at(i) << "\'"; + if (i != mmlPassCheckStr.length() - 1) { + write << ", "; + } + } + + write << "\n"; + lastEndPos += mmlPassCheckStr.length(); + } + + write << "};\n\n"; + + return offset + lastEndPos; +} + +ExportResult FZX::SequenceBinaryExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + // Nothing Required Here For Binary Exporting + + return std::nullopt; +} + +std::optional<std::shared_ptr<IParsedData>> FZX::SequenceFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { + auto [_, segment] = Decompressor::AutoDecode(node, buffer); + const auto offset = GetSafeNode<uint32_t>(node, "offset"); + const auto symbol = GetSafeNode<std::string>(node, "symbol"); + const auto size = GetSafeNode<uint32_t>(node, "size"); + LUS::BinaryReader reader(segment.data, segment.size); + uint32_t temp; + + reader.SetEndianness(Torch::Endianness::Big); + + std::vector<SeqCommand> buf; + + uint32_t count = 0; + SequenceState state = SequenceState::player; + bool largeNotes = false; + bool scanPastJump = false; + bool forceLargeNotes = false; + int32_t channel = -1; + int32_t layer = -1; + std::deque<SeqLabelInfo> posStack; + std::deque<uint16_t> dynTableStack; + std::unordered_set<uint32_t> existingPositions; + uint32_t depth = 0; + std::unordered_set<uint32_t> labels; + std::unordered_set<uint16_t> envelopeAddresses; + + /* Following checks are for uncertain behaviours that arise when parsing */ + if (node["has_jump_bug"]) { + scanPastJump = GetSafeNode<bool>(node, "has_jump_bug"); + } + + if (node["force_large_notes"]) { + forceLargeNotes = GetSafeNode<bool>(node, "force_large_notes"); + } + + while (count < size || !posStack.empty()) { + SeqCommand command; + bool nextLabel = false; + command.state = state; + command.pos = count; + command.channel = channel; + command.layer = layer; + + if (!existingPositions.contains(command.pos) && command.pos < size) { + existingPositions.insert(command.pos); + } else { + while (!posStack.empty() && existingPositions.contains(posStack.back().pos)) { + SPDLOG_INFO("POP BACK 0x{:X}", posStack.back().pos); + posStack.pop_back(); + } + if (posStack.empty()) { + SPDLOG_INFO("BREAK 0x{:X} 0x{:X}", count, command.cmd); + break; + } + + SeqLabelInfo& labelInfo = posStack.back(); + + command.state = state = labelInfo.state; + command.pos = count = labelInfo.pos; + command.channel = channel = labelInfo.channel; + command.layer = layer = labelInfo.layer; + largeNotes = labelInfo.largeNotes; + SPDLOG_INFO("POP BACK 0x{:X}", posStack.back().pos); + posStack.pop_back(); + existingPositions.insert(command.pos); + + if (command.pos >= size) { + SPDLOG_WARN("CMD POS START FOUND GREATER THAN LIMIT: 0x{:X}", command.pos); + } + } + if (forceLargeNotes) { + largeNotes = true; + } + + uint32_t startPos = command.pos; + reader.Seek(command.pos, LUS::SeekOffsetType::Start); + + if (command.state == SequenceState::data) { + reader.Seek(command.pos, LUS::SeekOffsetType::Start); + uint16_t dataValue = 0; + while (true) { + dataValue = READ_S16; + + if (!(dataValue >= 0 && dataValue < size && count < size)) { + break; + } + + SequenceState dataState; + if (command.channel == -1) { + dataState = SequenceState::player; + } else if (command.layer == -1) { + dataState = SequenceState::channel; + } else { + dataState = SequenceState::layer; + } + posStack.emplace_back(dataValue, dataState, command.channel, command.layer, largeNotes); + command.args.push_back(dataValue); + labels.insert(dataValue); + } + command.size = count - startPos - sizeof(uint16_t); + buf.push_back(command); + + if (posStack.empty()) { + SPDLOG_INFO("BREAK 0x{:X} 0x{:X}", count, command.cmd); + break; + } + + SeqLabelInfo& labelInfo = posStack.back(); + + state = labelInfo.state; + count = labelInfo.pos; + channel = labelInfo.channel; + layer = labelInfo.layer; + largeNotes = labelInfo.largeNotes; + SPDLOG_INFO("POP BACK 0x{:X}", posStack.back().pos); + posStack.pop_back(); + continue; + } + + command.cmd = READ_U8; + + SPDLOG_INFO("POS: 0x{:X}, CMD: 0x{:X}", command.pos, command.cmd); + + switch (command.cmd) { + case ASEQ_OP_RBLTZ: { + uint8_t jumpRPos = READ_U8; + int8_t jumpRSignedPos = (int8_t) (jumpRPos & 0xFF); + command.args.emplace_back(jumpRPos); + posStack.emplace_back(count + jumpRSignedPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(count + jumpRSignedPos); + break; + } + case ASEQ_OP_RBEQZ: { + uint8_t jumpRPos = READ_U8; + int8_t jumpRSignedPos = (int8_t) (jumpRPos & 0xFF); + command.args.emplace_back(jumpRPos); + posStack.emplace_back(count + jumpRSignedPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(count + jumpRSignedPos); + break; + } + case ASEQ_OP_RJUMP: { + uint8_t jumpRPos = READ_U8; + int8_t jumpRSignedPos = (int8_t) (jumpRPos & 0xFF); + command.args.emplace_back(jumpRPos); + posStack.emplace_back(count + jumpRSignedPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(count + jumpRSignedPos); + nextLabel = true; + break; + } + case ASEQ_OP_BGEZ: { + uint16_t jumpPos = READ_S16; + command.args.emplace_back(jumpPos); + posStack.emplace_back(jumpPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(jumpPos); + break; + } + case ASEQ_OP_BREAK: + depth--; + break; + case ASEQ_OP_LOOPEND: + depth--; + break; + case ASEQ_OP_LOOP: + command.args.emplace_back(READ_U8); + depth++; + break; + case ASEQ_OP_BLTZ: { + uint16_t jumpPos = READ_S16; + command.args.emplace_back(jumpPos); + posStack.emplace_back(jumpPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(jumpPos); + break; + } + case ASEQ_OP_BEQZ: { + uint16_t jumpPos = READ_S16; + command.args.emplace_back(jumpPos); + posStack.emplace_back(jumpPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(jumpPos); + break; + } + case ASEQ_OP_JUMP: { + uint16_t jumpPos = READ_S16; + command.args.emplace_back(jumpPos); + posStack.emplace_back(jumpPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(jumpPos); + // HACKY FIX: Not sure how to appropriately handle + if (!scanPastJump) { + nextLabel = true; + } + break; + } + case ASEQ_OP_CALL: { + uint16_t jumpPos = READ_S16; + command.args.emplace_back(jumpPos); + posStack.emplace_back(jumpPos, command.state, command.channel, command.layer, largeNotes); + labels.insert(jumpPos); + depth++; + break; + } + case ASEQ_OP_DELAY: { + uint32_t delay = READ_CS16; + command.args.emplace_back(delay); + // nextLabel = delay != 0; + break; + } + case ASEQ_OP_DELAY1: + // nextLabel = true; + break; + case ASEQ_OP_END: + if (depth == 0) { + // if (state == SequenceState::layer) { + // // Hack to actually parse more (not representative of code) + // state = SequenceState::channel; + // layer = -1; + // } else { + // } + nextLabel = true; + } else { + depth--; + } + break; + default: + break; + } + + if (command.cmd < ASEQ_OP_CONTROL_FLOW_FIRST) { + if (state == SequenceState::player) { + if (command.cmd >= 0xC0) { + switch (command.cmd) { + case ASEQ_OP_SEQ_RUNSEQ: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_SCRIPTCTR: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_SEQ_STSEQ: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_SEQ_SUB: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_AND: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_LDI: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_DYNCALL: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_SEQ_RAND: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_NOTEALLOC: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_LDSHORTGATEARR: + case ASEQ_OP_SEQ_LDSHORTVELARR: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_SEQ_MUTEBHV: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_MUTESCALE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_FREECHAN: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_SEQ_INITCHAN: { + uint16_t channelMap = READ_S16; + command.args.emplace_back(channelMap); + break; + } + case ASEQ_OP_SEQ_VOLSCALE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_VOLMODE: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_SEQ_VOL: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_TEMPOCHG: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_TEMPO: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_RTRANSPOSE: + case ASEQ_OP_SEQ_TRANSPOSE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_EF: + command.args.emplace_back(READ_S16); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_ALLOCNOTELIST: + command.args.emplace_back(READ_U8); + break; + default: + break; + } + } else { + switch (command.cmd & 0xF0) { + case ASEQ_OP_SEQ_LDRES: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_SEQ_LDCHAN: { + uint16_t chanPos = READ_S16; + command.args.emplace_back(chanPos); + posStack.emplace_back(chanPos, SequenceState::channel, command.cmd & 0xF, command.layer, largeNotes); + labels.insert(chanPos); + SPDLOG_INFO("PLAYER LDCHAN POS: 0x{:X}, Chan: {}", chanPos, command.cmd & 0xF); + break; + } + case ASEQ_OP_SEQ_RLDCHAN: { + uint16_t chanRPos = READ_S16; + command.args.emplace_back(chanRPos); + posStack.emplace_back(count + chanRPos, SequenceState::channel, command.cmd & 0xF, command.layer, largeNotes); + labels.insert(count + chanRPos); + SPDLOG_INFO("PLAYER RLDCHAN POS: 0x{:X}, Chan: {}", count + chanRPos, command.cmd & 0xF); + break; + } + case ASEQ_OP_SEQ_LDSEQ: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_S16); + break; + default: + break; + } + } + } else if (state == SequenceState::channel) { + if (command.cmd >= 0xB0) { + switch (command.cmd) { + case ASEQ_OP_CHAN_LDFILTER: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_FREEFILTER: + break; + case ASEQ_OP_CHAN_LDSEQTOPTR: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_FILTER: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_PTRTODYNTBL: + break; + case ASEQ_OP_CHAN_DYNTBLTOPTR: + break; + case ASEQ_OP_CHAN_DYNTBLV: + break; + case ASEQ_OP_CHAN_RANDTOPTR: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_RAND: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_RANDVEL: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_RANDGATE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_COMBFILTER: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_PTRADD: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_SAMPLESTART: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_INSTR: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_DYNTBL: { + uint16_t dynValue = READ_S16; + command.args.emplace_back(dynValue); + dynTableStack.push_back(dynValue); + break; + } + case ASEQ_OP_CHAN_SHORT: + largeNotes = false; + break; + case ASEQ_OP_CHAN_NOSHORT: + largeNotes = true; + break; + case ASEQ_OP_CHAN_DYNTBLLOOKUP: + break; + case ASEQ_OP_CHAN_FONT: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_STSEQ: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_SUB: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_AND: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_MUTEBHV: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_LDSEQ: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_LDI: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_STOPCHAN: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_LDPTR: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_STPTRTOSEQ: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_EFFECTS: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_NOTEALLOC: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_SUSTAIN: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_BEND: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_REVERB: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VIBFREQ: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VIBDEPTH: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_RELEASERATE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_ENV: { + uint16_t envAddr = READ_S16; + command.args.emplace_back(envAddr); + envelopeAddresses.insert(envAddr); + break; + } + case ASEQ_OP_CHAN_TRANSPOSE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_PANWEIGHT: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_PAN: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_FREQSCALE: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_VOL: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VOLEXP: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VIBFREQGRAD: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VIBDEPTHGRAD: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VIBDELAY: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_DYNCALL: + posStack.emplace_back(dynTableStack.back(), SequenceState::data, command.channel, command.layer, largeNotes); + labels.insert(dynTableStack.back()); + dynTableStack.pop_back(); + break; + case ASEQ_OP_CHAN_REVERBIDX: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_SAMPLEBOOK: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_LDPARAMS: + command.args.emplace_back(READ_S16); + break; + case ASEQ_OP_CHAN_PARAMS: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_NOTEPRI: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_STOP: + nextLabel = true; + break; + case ASEQ_OP_CHAN_FONTINSTR: + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_VIBRESET: + break; + case ASEQ_OP_CHAN_GAIN: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_BENDFINE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_EF: + command.args.emplace_back(READ_S16); + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_FREENOTELIST: + break; + case ASEQ_OP_CHAN_ALLOCNOTELIST: + command.args.emplace_back(READ_U8); + break; + } + } else if (command.cmd >= 0x78) { + switch (command.cmd & 0xF8) { + case ASEQ_OP_CHAN_LDLAYER: { + uint16_t layerPos = READ_S16; + command.args.emplace_back(layerPos); + posStack.emplace_back(layerPos, SequenceState::layer, command.channel, command.cmd & 0x7, largeNotes); + labels.insert(layerPos); + break; + } + case ASEQ_OP_CHAN_RLDLAYER: { + uint16_t layerRPos = READ_S16; + command.args.emplace_back(layerRPos); + posStack.emplace_back(count + layerRPos, SequenceState::layer, command.channel, command.cmd & 0x7, largeNotes); + labels.insert(count + layerRPos); + break; + } + default: + break; + } + } else { + switch (command.cmd & 0xF0) { + case ASEQ_OP_CHAN_CDELAY: + // nextLabel = true; + break; + case ASEQ_OP_CHAN_LDSAMPLE: + break; + case ASEQ_OP_CHAN_LDCHAN: { + uint16_t chanPos = READ_S16; + command.args.emplace_back(chanPos); + posStack.emplace_back(chanPos, SequenceState::channel, command.cmd & 0xF, command.layer, largeNotes); + labels.insert(chanPos); + break; + } + case ASEQ_OP_CHAN_STCIO: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_LDCIO: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_CHAN_SUBIO: + break; + case ASEQ_OP_CHAN_LDIO: + break; + case ASEQ_OP_CHAN_STIO: + break; + } + } + } else if (state == SequenceState::layer) { + if (command.cmd > 0xC0) { + switch (command.cmd) { + case ASEQ_OP_LAYER_SHORTVEL: + case ASEQ_OP_LAYER_NOTEPAN: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_LAYER_SHORTGATE: + case ASEQ_OP_LAYER_TRANSPOSE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_LAYER_LEGATO: + case ASEQ_OP_LAYER_NOLEGATO: + break; + case ASEQ_OP_LAYER_SHORTDELAY: + command.args.emplace_back(READ_CS16); + break; + case ASEQ_OP_LAYER_INSTR: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_LAYER_PORTAMENTO: { + uint8_t portamento = READ_U8; + command.args.emplace_back(portamento); + command.args.emplace_back(READ_U8); + if (portamento & 0x80) { + command.args.emplace_back(READ_U8); + } else { + command.args.emplace_back(READ_CS16); + } + break; + } + case ASEQ_OP_LAYER_NOPORTAMENTO: + break; + case ASEQ_OP_LAYER_ENV: { + uint16_t envAddr = READ_S16; + command.args.emplace_back(envAddr); + command.args.emplace_back(READ_U8); + envelopeAddresses.insert(envAddr); + break; + } + case ASEQ_OP_LAYER_RELEASERATE: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_LAYER_NODRUMPAN: + break; + case ASEQ_OP_LAYER_STEREO: + command.args.emplace_back(READ_U8); + break; + case ASEQ_OP_LAYER_BENDFINE: + command.args.emplace_back(READ_U8); + break; + } + } else { + if (command.cmd == ASEQ_OP_LAYER_LDELAY) { + command.args.emplace_back(READ_CS16); + } else { + switch (command.cmd & 0xC0) { + case ASEQ_OP_LAYER_NOTEDVG: + command.args.emplace_back(READ_CS16); + if (largeNotes) { + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + } + break; + case ASEQ_OP_LAYER_NOTEDV: + if (largeNotes) { + command.args.emplace_back(READ_CS16); + command.args.emplace_back(READ_U8); + } + break; + case ASEQ_OP_LAYER_NOTEVG: + if (largeNotes) { + command.args.emplace_back(READ_U8); + command.args.emplace_back(READ_U8); + } + break; + } + } + } + } + } + + command.size = count - startPos; + buf.push_back(command); + + if (nextLabel) { + if (posStack.empty()) { + SPDLOG_INFO("BREAK 0x{:X} 0x{:X}", count, command.cmd); + break; + } + + SeqLabelInfo& labelInfo = posStack.back(); + + state = labelInfo.state; + count = labelInfo.pos; + channel = labelInfo.channel; + layer = labelInfo.layer; + largeNotes = labelInfo.largeNotes; + SPDLOG_INFO("POP BACK 0x{:X}", posStack.back().pos); + posStack.pop_back(); + continue; + } + } + + for (const auto& envAddr : envelopeAddresses) { + SeqCommand command; + command.pos = envAddr; + command.state = SequenceState::envelope; + + uint16_t addr = envAddr; + reader.Seek(addr, LUS::SeekOffsetType::Start); + while (!existingPositions.contains(addr) && addr < size) { + auto delay = READ_S16; + auto arg = READ_S16; + command.args.emplace_back(delay); + command.args.emplace_back(arg); + existingPositions.insert(addr); + addr += 2 * sizeof(int16_t); + if (arg == 0) { + break; + } + } + command.size = addr - envAddr; + buf.push_back(command); + } + + bool hasFooter = false; + if (node["has_footer"]) { + hasFooter = GetSafeNode<bool>(node, "has_footer"); + } + + return std::make_shared<SequenceData>(buf, labels, hasFooter); +} diff --git a/src/factories/fzerox/SequenceFactory.h b/src/factories/fzerox/SequenceFactory.h new file mode 100644 index 0000000..93056cc --- /dev/null +++ b/src/factories/fzerox/SequenceFactory.h @@ -0,0 +1,76 @@ +#pragma once + +#include <factories/BaseFactory.h> +#include <unordered_set> + +namespace FZX { + +typedef std::variant<uint8_t, uint16_t, uint32_t> SeqArg; + +enum class SeqArgType { + U8, S16, CS16 +}; + +enum class SequenceState { + player, + channel, + layer, + data, + envelope +}; + +struct SeqLabelInfo { + uint32_t pos; + SequenceState state; + uint32_t channel; + uint32_t layer; + bool largeNotes; + SeqLabelInfo(uint32_t pos, SequenceState state, uint32_t channel, uint32_t layer, bool largeNotes) : pos(pos), state(state), channel(channel), layer(layer), largeNotes(largeNotes) {} +}; + +struct SeqCommand { + uint8_t cmd; + uint32_t pos; + SequenceState state; + int32_t channel; + int32_t layer; + uint32_t size; + std::vector<SeqArg> args; + bool operator<(const SeqCommand& command) const { + return pos < command.pos; + } +}; + +class SequenceData : public IParsedData { +public: + std::vector<SeqCommand> mCmds; + std::unordered_set<uint32_t> mLabels; + bool mHasFooter; + + SequenceData(std::vector<SeqCommand> cmds, std::unordered_set<uint32_t> labels, bool hasFooter) : mCmds(std::move(cmds)), mLabels(std::move(labels)), mHasFooter(hasFooter) {} +}; + +class SequenceHeaderExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SequenceBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SequenceCodeExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SequenceFactory : public BaseFactory { +public: + std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; + inline std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override { + return { + REGISTER(Code, SequenceCodeExporter) + REGISTER(Header, SequenceHeaderExporter) + REGISTER(Binary, SequenceBinaryExporter) + }; + } +}; +} diff --git a/src/factories/fzerox/SoundFontFactory.cpp b/src/factories/fzerox/SoundFontFactory.cpp new file mode 100644 index 0000000..86fb250 --- /dev/null +++ b/src/factories/fzerox/SoundFontFactory.cpp @@ -0,0 +1,655 @@ +#include "SoundFontFactory.h" +#include "spdlog/spdlog.h" + +#include "Companion.h" +#include "utils/Decompressor.h" +#include "utils/TorchUtils.h" + +static std::unordered_map<FZX::DataType, std::string> sSoundFontDataTypeMap = { + { FZX::DataType::Drum, "Drum" }, + { FZX::DataType::Sfx, "Sfx" }, + { FZX::DataType::Instrument, "Instrument" }, + { FZX::DataType::Envelope, "Envelope" }, + { FZX::DataType::Sample, "Sample" }, + { FZX::DataType::Book, "Book" }, + { FZX::DataType::Loop, "Loop" }, + { FZX::DataType::DrumOffsets, "DrumOffsets" }, +}; + +typedef enum { + /* 0 */ CODEC_ADPCM, + /* 1 */ CODEC_S8, + /* 2 */ CODEC_S16_INMEMORY, + /* 3 */ CODEC_SMALL_ADPCM, + /* 4 */ CODEC_REVERB, + /* 5 */ CODEC_S16, + /* 6 */ CODEC_UNK6 +} SampleCodec; + +typedef enum { + /* 0 */ MEDIUM_RAM, + /* 1 */ MEDIUM_LBA, + /* 2 */ MEDIUM_CART, + /* 3 */ MEDIUM_DISK_DRIVE +} SampleMedium; + +static std::unordered_map<uint32_t, std::string> sCodecMap = { + { CODEC_ADPCM, "CODEC_ADPCM" }, + { CODEC_S8, "CODEC_S8" }, + { CODEC_S16_INMEMORY, "CODEC_S16_INMEMORY" }, + { CODEC_SMALL_ADPCM, "CODEC_SMALL_ADPCM" }, + { CODEC_REVERB, "CODEC_REVERB" }, + { CODEC_S16, "CODEC_S16" }, + { CODEC_UNK6, "CODEC_UNK6" }, +}; + +static std::unordered_map<uint32_t, std::string> sMediumMap = { + { MEDIUM_RAM, "MEDIUM_RAM" }, + { MEDIUM_LBA, "MEDIUM_LBA" }, + { MEDIUM_CART, "MEDIUM_CART" }, + { MEDIUM_DISK_DRIVE, "MEDIUM_DISK_DRIVE" }, +}; + +#define FORMAT_HEX(x, w) "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(w) << x << std::nouppercase << std::dec +#define FORMAT_FLOAT(x, w, p) std::dec << std::setfill(' ') << std::fixed << std::setprecision(p) << std::setw(w) << x << "f" + +#define DRUM_SIZE 0x10 +#define SFX_SIZE 0x8 +#define INSTRUMENT_SIZE 0x20 +#define ENVELOPE_SIZE 0x4 +#define SAMPLE_SIZE 0x10 +#define BOOK_HEADER_SIZE 0x8 +#define LOOP_SIZE 0x10 +#define LOOP_SIZE2 0x30 +#define ALIGN16(val) (((val) + 0xF) & ~0xF) + +ExportResult FZX::SoundFontHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + const auto soundFontData = std::static_pointer_cast<SoundFontData>(raw); + + write << "extern u32 " << symbol << "Offsets[];\n"; + + for (const auto& entry : soundFontData->mEntries) { + switch (entry.type) { + case DataType::Drum: + write << "extern Drum " << entry.name << "[];\n"; + break; + case DataType::Sfx: + write << "extern SoundEffect " << entry.name << "[];\n"; + break; + case DataType::Instrument: + write << "extern Instrument " << entry.name << ";\n"; + break; + case DataType::Envelope: + write << "extern EnvelopePoint " << entry.name << "[];\n"; + break; + case DataType::Sample: + write << "extern Sample " << entry.name << ";\n"; + break; + case DataType::Book: + write << "extern AdpcmBook " << entry.name << ";\n"; + break; + case DataType::Loop: { + auto loop = std::get<AdpcmLoop>(entry.data); + if (loop.count != 0) { + write << "extern AdpcmLoop " << entry.name << ";\n"; + } else { + write << "extern AdpcmLoopHeader " << entry.name << ";\n"; + } + break; + } + case DataType::DrumOffsets: + write << "extern u32 " << entry.name << "[];\n"; + break; + } + } + + write << "\n"; + + return std::nullopt; +} + +ExportResult FZX::SoundFontCodeExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + const auto offset = GetSafeNode<uint32_t>(node, "offset"); + const auto soundFontData = std::static_pointer_cast<SoundFontData>(raw); + + // Calculate offsets + std::unordered_map<std::string, uint32_t> dataNameToOffsetMap; + std::unordered_map<std::string, uint32_t> dataNameToSizeMap; + uint32_t rollingOffset = 0; + + rollingOffset += sizeof(uint32_t); + if (soundFontData->mSupportSfx) { + rollingOffset += sizeof(uint32_t); + } + bool hasDrums = false; + bool hasSfx = false; + std::string drumOffsetName; + std::string sfxName; + std::vector<std::string> instrumentNameList; + for (const auto& entry : soundFontData->mEntries) { + if (entry.type == DataType::DrumOffsets) { + drumOffsetName = entry.name; + hasDrums = true; + continue; + } + if (entry.type == DataType::Sfx) { + sfxName = entry.name; + hasSfx = true; + continue; + } + if (entry.type != DataType::Instrument) { + continue; + } + + instrumentNameList.push_back(entry.name); + rollingOffset += sizeof(uint32_t); + } + + for (const auto& entry : soundFontData->mEntries) { + uint32_t size; + rollingOffset = ALIGN16(rollingOffset); + + dataNameToOffsetMap[entry.name] = rollingOffset; + + switch (entry.type) { + case DataType::Drum: + size = DRUM_SIZE; + break; + case DataType::Sfx: + size = SFX_SIZE * std::get<std::vector<SoundEffect>>(entry.data).size(); + break; + case DataType::Instrument: + size = INSTRUMENT_SIZE; + break; + case DataType::Envelope: + size = ENVELOPE_SIZE * std::get<std::vector<EnvelopePoint>>(entry.data).size(); + break; + case DataType::Sample: + size = SAMPLE_SIZE; + break; + case DataType::Book: { + auto book = std::get<AdpcmBook>(entry.data); + size = BOOK_HEADER_SIZE + book.order * book.numPredictors * 8 * sizeof(uint16_t); + break; + } + case DataType::Loop: { + auto loop = std::get<AdpcmLoop>(entry.data); + if (loop.count != 0) { + size = LOOP_SIZE2; + } else { + size = LOOP_SIZE; + } + break; + } + case DataType::DrumOffsets: + size = sizeof(uint32_t) * std::get<std::vector<std::string>>(entry.data).size(); + break; + } + + dataNameToSizeMap[entry.name] = size; + SPDLOG_INFO("ENTRY OUT {} {}, 0x{:X} (size 0x{:X})", sSoundFontDataTypeMap[entry.type], entry.name, rollingOffset, size); + rollingOffset += size; + } + uint32_t totalSize = rollingOffset; + + // Write Out + rollingOffset = 0; + + write << "u32 " << symbol + "Offsets[] = {\n"; + + write << fourSpaceTab; + + if (hasDrums) { + write << FORMAT_HEX(dataNameToOffsetMap[drumOffsetName], 4) << ","; + } else { + write << "0,"; + } + rollingOffset += sizeof(uint32_t); + + if (soundFontData->mSupportSfx) { + if (hasSfx) { + write << " " << FORMAT_HEX(dataNameToOffsetMap[sfxName], 4) << ","; + } else { + write << " 0,"; + } + rollingOffset += sizeof(uint32_t); + } + + uint32_t instrumentCount = 0; + for (const auto& instName : instrumentNameList) { + if (instrumentCount % 6 == 0) { + write << "\n" << fourSpaceTab; + } else { + write << " "; + } + + write << FORMAT_HEX(dataNameToOffsetMap[instName], 4) << ","; + instrumentCount++; + rollingOffset += sizeof(uint32_t); + } + + write << "\n};\n\n"; + + static uint32_t padCount = 0; + for (const auto& entry : soundFontData->mEntries) { + if (rollingOffset % 0x10 != 0) { + uint32_t paddingSize = 0x10 - (rollingOffset % 0x10); + write << "static u8 padding" << (padCount++) << "[" << FORMAT_HEX(paddingSize, 1) << "] = { 0 };\n\n"; + } + rollingOffset = ALIGN16(rollingOffset); + + write << "/* Reference Offset " << FORMAT_HEX(rollingOffset, 1) << " */\n"; + switch (entry.type) { + case DataType::Drum: { + auto drum = std::get<Drum>(entry.data); + write << "Drum " << entry.name << " = {\n"; + write << fourSpaceTab << (uint32_t)drum.adsrDecayIndex << ", " << (uint32_t)drum.pan << ", 0,\n"; + write << fourSpaceTab << "{ " << FORMAT_HEX(dataNameToOffsetMap[drum.tunedSample.sampleRef], 1) << ", " << FORMAT_FLOAT(drum.tunedSample.tuning, 7, 7) << " },\n"; + write << fourSpaceTab << FORMAT_HEX(dataNameToOffsetMap[drum.envelopeRef], 1) << ",\n"; + write << "};\n\n"; + break; + } + case DataType::Sfx: { + auto sfxList = std::get<std::vector<SoundEffect>>(entry.data); + write << "SoundEffect " << entry.name << "[] = {\n"; + for (const auto& sfx : sfxList) { + write << fourSpaceTab << "{ { " << FORMAT_HEX(dataNameToOffsetMap[sfx.tunedSample.sampleRef], 1) << ", " << FORMAT_FLOAT(sfx.tunedSample.tuning, 7, 7) << " } },\n"; + } + write << "};\n\n"; + break; + } + case DataType::Instrument: { + auto instrument = std::get<Instrument>(entry.data); + write << "Instrument " << entry.name << " = {\n"; + write << fourSpaceTab << "0, " << (uint32_t)instrument.normalRangeLo << ", " << (uint32_t)instrument.normalRangeHi << ", " << (uint32_t)instrument.adsrDecayIndex << ", " << FORMAT_HEX(dataNameToOffsetMap[instrument.envelopeRef], 1) << ",\n"; + write << fourSpaceTab << "{ " << FORMAT_HEX(dataNameToOffsetMap[instrument.lowPitchTunedSample.sampleRef], 1) << ", " << FORMAT_FLOAT(instrument.lowPitchTunedSample.tuning, 7, 7) << " },\n"; + write << fourSpaceTab << "{ " << FORMAT_HEX(dataNameToOffsetMap[instrument.normalPitchTunedSample.sampleRef], 1) << ", " << FORMAT_FLOAT(instrument.normalPitchTunedSample.tuning, 7, 7) << " },\n"; + write << fourSpaceTab << "{ " << FORMAT_HEX(dataNameToOffsetMap[instrument.highPitchTunedSample.sampleRef], 1) << ", " << FORMAT_FLOAT(instrument.highPitchTunedSample.tuning, 7, 7) << " },\n"; + write << "};\n\n"; + break; + } + case DataType::Envelope: { + auto envelopes = std::get<std::vector<EnvelopePoint>>(entry.data); + write << "EnvelopePoint " << entry.name << "[] = {\n"; + for (const auto envelope : envelopes) { + write << fourSpaceTab << "{ " << envelope.delay << ", " << envelope.arg << " },\n"; + } + write << "};\n\n"; + break; + } + case DataType::Sample: { + auto sample = std::get<Sample>(entry.data); + write << "Sample " << entry.name << " = {\n"; + + write << fourSpaceTab; + // TODO: THIS IS WRONG, ADD SEPARATE FIELD FOR THIS AUDIO SYSTEM UPDATE + if (soundFontData->mSupportSfx) { + write << "0, "; + } + write << sCodecMap.at(sample.codec) << ", " << sMediumMap.at(sample.medium) << ", " << sample.unk_bit26 << ", 0, " << FORMAT_HEX(sample.size, 1) << ",\n"; + write << fourSpaceTab << FORMAT_HEX(sample.rawSampleOffset, 1) << ", " << FORMAT_HEX(dataNameToOffsetMap[sample.loopRef], 1) << ", " << FORMAT_HEX(dataNameToOffsetMap[sample.bookRef], 1); + + write << "\n};\n\n"; + break; + } + case DataType::Book: { + auto book = std::get<AdpcmBook>(entry.data); + write << "AdpcmBook " << entry.name << " = {\n"; + + write << fourSpaceTab << "{ " << book.order << ", " << book.numPredictors << " },"; + + uint32_t bookCount = 0; + for (const auto& bookData : book.book) { + if (bookCount % 6 == 0) { + write << "\n" << fourSpaceTab; + } else { + write << " "; + } + + write << FORMAT_HEX(bookData, 4) << ","; + bookCount++; + } + write << "\n};\n\n"; + break; + } + case DataType::Loop: { + auto loop = std::get<AdpcmLoop>(entry.data); + if (loop.count != 0) { + write << "AdpcmLoop " << entry.name << " = {\n"; + write << fourSpaceTab << "{ " << loop.start << ", " << loop.end << ", " << FORMAT_HEX(loop.count, 1) << ", { 0 } },"; + } else { + write << "AdpcmLoopHeader " << entry.name << " = {\n"; + write << fourSpaceTab << loop.start << ", " << loop.end << ", " << FORMAT_HEX(loop.count, 1) << ", { 0 },"; + } + + if (loop.count != 0) { + uint32_t predictorCount = 0; + for (const auto& predictorState : loop.predictorState) { + if (predictorCount % 6 == 0) { + write << "\n" << fourSpaceTab; + } else { + write << " "; + } + + write << FORMAT_HEX(predictorState, 4) << ","; + predictorCount++; + } + } + + write << "\n};\n\n"; + break; + } + case DataType::DrumOffsets: { + auto drumOffsetListNames = std::get<std::vector<std::string>>(entry.data); + write << "u32 " << entry.name << "[] = {\n"; + + uint32_t drumCount = 0; + for (const auto& drumName : drumOffsetListNames) { + if (drumCount % 6 == 0) { + write << "\n" << fourSpaceTab; + } else { + write << " "; + } + + write << FORMAT_HEX(dataNameToOffsetMap[drumName], 4) << ","; + drumCount++; + } + + write << "\n};\n\n"; + break; + } + } + + rollingOffset += dataNameToSizeMap[entry.name]; + } + + return offset + totalSize; +} + +ExportResult FZX::SoundFontBinaryExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + + return std::nullopt; +} + +std::string FZX::SoundFontFactory::RegisterSoundFontData(std::string symbol, FZX::DataType dataType, uint32_t offset, std::map<uint32_t, std::pair<FZX::DataType, std::string>>& dataMap, std::unordered_map<FZX::DataType, uint32_t>& dataCountMap) { + std::string dataName; + + if (dataMap.contains(offset)) { + return dataMap.at(offset).second; + } + + switch (dataType) { + case FZX::DataType::Drum: { + dataName = symbol + "Drum" + std::to_string(dataCountMap[dataType]++); + break; + } + case FZX::DataType::Sfx: { + dataName = symbol + "Sfx"; + break; + } + case FZX::DataType::Instrument: { + dataName = symbol + "Instrument" + std::to_string(dataCountMap[dataType]++); + break; + } + case FZX::DataType::Sample: { + dataName = symbol + "Sample" + std::to_string(dataCountMap[dataType]++); + break; + } + case FZX::DataType::Envelope: { + dataName = symbol + "Envelope" + std::to_string(dataCountMap[dataType]++); + break; + } + case FZX::DataType::Book: { + dataName = symbol + "Book" + std::to_string(dataCountMap[dataType]++); + break; + } + case FZX::DataType::Loop: { + dataName = symbol + "Loop" + std::to_string(dataCountMap[dataType]++); + break; + } + case FZX::DataType::DrumOffsets: { + dataName = symbol + "DrumOffsets"; + break; + } + default: + throw std::runtime_error("Invalid SoundFont Data Type"); + } + + dataMap[offset] = std::make_pair(dataType, dataName); + + return dataName; +} + +std::optional<std::shared_ptr<IParsedData>> FZX::SoundFontFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { + auto [_, segment] = Decompressor::AutoDecode(node, buffer); + const auto offset = GetSafeNode<uint32_t>(node, "offset"); + const auto symbol = GetSafeNode<std::string>(node, "symbol"); + LUS::BinaryReader reader(segment.data, segment.size); + + reader.SetEndianness(Torch::Endianness::Big); + + std::map<uint32_t, std::pair<DataType, std::string>> dataMap; + std::unordered_map<FZX::DataType, uint32_t> dataCountMap; + + const auto numDrums = GetSafeNode<uint32_t>(node, "num_drums"); + const auto numInstruments = GetSafeNode<uint32_t>(node, "num_instruments"); + const auto supportSfx = GetSafeNode<bool>(node, "support_sfx"); + + uint32_t numSfx = 0; + if (supportSfx) { + numSfx = GetSafeNode<uint32_t>(node, "num_sfx"); + } + + std::unordered_map<std::string, SoundFontType> soundFontMap; + uint32_t drumOffsetListOffset = reader.ReadUInt32(); + if (numDrums > 0) { + RegisterSoundFontData(symbol, DataType::DrumOffsets, drumOffsetListOffset, dataMap, dataCountMap); + } + uint32_t sfxOffset = 0; + if (supportSfx) { + sfxOffset = reader.ReadUInt32(); + if (numSfx > 0) { + RegisterSoundFontData(symbol, DataType::Sfx, sfxOffset, dataMap, dataCountMap); + } + } + + std::vector<uint32_t> instrumentOffsets; + for (uint32_t i = 0; i < numInstruments; i++) { + auto instrumentOffset = reader.ReadUInt32(); + RegisterSoundFontData(symbol, DataType::Instrument, instrumentOffset, dataMap, dataCountMap); + instrumentOffsets.push_back(instrumentOffset); + } + + std::unordered_set<uint32_t> sampleOffsets; + std::unordered_set<uint32_t> envelopeOffsets; + + // Parse Drums + std::vector<Drum> drums; + SPDLOG_INFO("FZX SOUNDFONT SEEK drumOffsetList 0x{:X}", drumOffsetListOffset); + reader.Seek(drumOffsetListOffset, LUS::SeekOffsetType::Start); + + std::unordered_set<uint32_t> drumOffsets; + if (numDrums > 0) { + std::vector<std::string> drumRefList; + + for (uint32_t i = 0; i < numDrums; i++) { + auto drumOffset = reader.ReadUInt32(); + drumRefList.push_back(RegisterSoundFontData(symbol, DataType::Drum, drumOffset, dataMap, dataCountMap)); + drumOffsets.insert(drumOffset); + } + soundFontMap[dataMap.at(drumOffsetListOffset).second] = drumRefList; + } + + for (const auto& drumOffset : drumOffsets) { + SPDLOG_INFO("FZX SOUNDFONT SEEK drum 0x{:X}", drumOffset); + reader.Seek(drumOffset, LUS::SeekOffsetType::Start); + Drum drum; + drum.adsrDecayIndex = reader.ReadUByte(); + drum.pan = reader.ReadUByte(); + drum.isRelocated = reader.ReadUByte(); + reader.ReadUByte(); + + auto sampleOffset = reader.ReadUInt32(); + drum.tunedSample.sampleRef = RegisterSoundFontData(symbol, DataType::Sample, sampleOffset, dataMap, dataCountMap); + sampleOffsets.insert(sampleOffset); + drum.tunedSample.tuning = reader.ReadFloat(); + auto envelopeOffset = reader.ReadUInt32(); + drum.envelopeRef = RegisterSoundFontData(symbol, DataType::Envelope, envelopeOffset, dataMap, dataCountMap); + envelopeOffsets.insert(envelopeOffset); + + soundFontMap[dataMap.at(drumOffset).second] = drum; + } + + // Parse Sfx + if (supportSfx) { + SPDLOG_INFO("FZX SOUNDFONT SEEK sfx 0x{:X}", sfxOffset); + reader.Seek(sfxOffset, LUS::SeekOffsetType::Start); + + if (numSfx > 0) { + std::vector<SoundEffect> soundEffects; + for (uint32_t i = 0; i < numSfx; i++) { + SoundEffect soundEffect; + auto sampleOffset = reader.ReadUInt32(); + soundEffect.tunedSample.sampleRef = RegisterSoundFontData(symbol, DataType::Sample, sampleOffset, dataMap, dataCountMap); + sampleOffsets.insert(sampleOffset); + soundEffect.tunedSample.tuning = reader.ReadFloat(); + soundEffects.push_back(soundEffect); + } + + soundFontMap[dataMap.at(sfxOffset).second] = soundEffects; + } + } + + // Parse Instruments + for (const auto& instrumentOffset : instrumentOffsets) { + Instrument instrument; + uint32_t sampleOffset; + SPDLOG_INFO("FZX SOUNDFONT SEEK instrument 0x{:X}", instrumentOffset); + reader.Seek(instrumentOffset, LUS::SeekOffsetType::Start); + + instrument.isRelocated = reader.ReadUByte(); + instrument.normalRangeLo = reader.ReadUByte(); + instrument.normalRangeHi = reader.ReadUByte(); + instrument.adsrDecayIndex = reader.ReadUByte(); + auto envelopeOffset = reader.ReadUInt32(); + instrument.envelopeRef = RegisterSoundFontData(symbol, DataType::Envelope, envelopeOffset, dataMap, dataCountMap); + envelopeOffsets.insert(envelopeOffset); + + sampleOffset = reader.ReadUInt32(); + if (sampleOffset != 0) { + instrument.lowPitchTunedSample.sampleRef = RegisterSoundFontData(symbol, DataType::Sample, sampleOffset, dataMap, dataCountMap); + sampleOffsets.insert(sampleOffset); + } + instrument.lowPitchTunedSample.tuning = reader.ReadFloat(); + + sampleOffset = reader.ReadUInt32(); + if (sampleOffset != 0) { + instrument.normalPitchTunedSample.sampleRef = RegisterSoundFontData(symbol, DataType::Sample, sampleOffset, dataMap, dataCountMap); + sampleOffsets.insert(sampleOffset); + } + instrument.normalPitchTunedSample.tuning = reader.ReadFloat(); + + sampleOffset = reader.ReadUInt32(); + if (sampleOffset != 0) { + instrument.highPitchTunedSample.sampleRef = RegisterSoundFontData(symbol, DataType::Sample, sampleOffset, dataMap, dataCountMap); + sampleOffsets.insert(sampleOffset); + } + instrument.highPitchTunedSample.tuning = reader.ReadFloat(); + + soundFontMap[dataMap.at(instrumentOffset).second] = instrument; + } + + // Parse Envelopes + for (const auto& envelopeOffset : envelopeOffsets) { + std::vector<EnvelopePoint> envelopes; + SPDLOG_INFO("FZX SOUNDFONT SEEK envelope 0x{:X}", envelopeOffset); + reader.Seek(envelopeOffset, LUS::SeekOffsetType::Start); + while (true) { + EnvelopePoint envelope; + envelope.delay = reader.ReadInt16(); + envelope.arg = reader.ReadInt16(); + envelopes.push_back(envelope); + + if (envelope.delay <= 0) { + break; + } + } + soundFontMap[dataMap.at(envelopeOffset).second] = envelopes; + } + + // Parse Samples + std::unordered_set<uint32_t> loopOffsets; + std::unordered_set<uint32_t> bookOffsets; + for (const auto& sampleOffset : sampleOffsets) { + Sample sample; + SPDLOG_INFO("FZX SOUNDFONT SEEK sample 0x{:X}", sampleOffset); + reader.Seek(sampleOffset, LUS::SeekOffsetType::Start); + + uint32_t sampleBitfield = reader.ReadUInt32(); + sample.codec = (sampleBitfield & (0b0111 << 28)) >> 28; + sample.medium = (sampleBitfield & (0b11 << 26)) >> 26; + sample.unk_bit26 = (sampleBitfield & (1 << 25)) >> 25; + sample.isRelocated = (sampleBitfield & (1 << 24)) >> 24; + sample.size = sampleBitfield & 0x00FFFFFF; + + sample.rawSampleOffset = reader.ReadUInt32(); + auto loopOffset = reader.ReadUInt32(); + sample.loopRef = RegisterSoundFontData(symbol, DataType::Loop, loopOffset, dataMap, dataCountMap); + loopOffsets.insert(loopOffset); + auto bookOffset = reader.ReadUInt32(); + sample.bookRef = RegisterSoundFontData(symbol, DataType::Book, bookOffset, dataMap, dataCountMap); + bookOffsets.insert(bookOffset); + + soundFontMap[dataMap.at(sampleOffset).second] = sample; + } + + // Parse Loops + for (const auto& loopOffset : loopOffsets) { + AdpcmLoop loop; + SPDLOG_INFO("FZX SOUNDFONT SEEK loop 0x{:X}", loopOffset); + reader.Seek(loopOffset, LUS::SeekOffsetType::Start); + + loop.start = reader.ReadUInt32(); + loop.end = reader.ReadUInt32(); + loop.count = reader.ReadUInt32(); + reader.ReadUInt32(); + + if (loop.count != 0) { + for (uint32_t i = 0; i < 16; i++) { + loop.predictorState.push_back(reader.ReadInt16()); + } + } + soundFontMap[dataMap.at(loopOffset).second] = loop; + } + + // Parse Books + for (const auto& bookOffset : bookOffsets) { + AdpcmBook book; + SPDLOG_INFO("FZX SOUNDFONT SEEK book 0x{:X}", bookOffset); + reader.Seek(bookOffset, LUS::SeekOffsetType::Start); + + book.order = reader.ReadInt32(); + book.numPredictors = reader.ReadInt32(); + + for (int32_t i = 0; i < book.order * book.numPredictors * 8; i++) { + book.book.push_back(reader.ReadInt16()); + } + soundFontMap[dataMap.at(bookOffset).second] = book; + } + + // Sort SoundFont Entries into Vector by offset + std::vector<SoundFontEntry> soundFontEntries; + for (const auto& [offset, dataInfoPair] : dataMap) { + SoundFontEntry entry; + entry.type = dataInfoPair.first; + entry.name = dataInfoPair.second; + entry.data = std::move(soundFontMap.at(entry.name)); + SPDLOG_INFO("ENTRY IN {} {}, 0x{:X}", sSoundFontDataTypeMap[entry.type], entry.name, offset); + + soundFontEntries.push_back(entry); + } + + return std::make_shared<SoundFontData>(soundFontEntries, supportSfx); +} diff --git a/src/factories/fzerox/SoundFontFactory.h b/src/factories/fzerox/SoundFontFactory.h new file mode 100644 index 0000000..1669cbe --- /dev/null +++ b/src/factories/fzerox/SoundFontFactory.h @@ -0,0 +1,118 @@ +#pragma once + +#include <factories/BaseFactory.h> +#include <unordered_set> + +namespace FZX { + +typedef struct TunedSample { + std::string sampleRef; + float tuning; +} TunedSample; + +typedef struct Drum { + uint8_t adsrDecayIndex; + uint8_t pan; + uint8_t isRelocated; + TunedSample tunedSample; + std::string envelopeRef; +} Drum; + +typedef struct SoundEffect { + TunedSample tunedSample; +} SoundEffect; + +typedef struct Instrument { + uint8_t isRelocated; + uint8_t normalRangeLo; + uint8_t normalRangeHi; + uint8_t adsrDecayIndex; + std::string envelopeRef; + TunedSample lowPitchTunedSample; + TunedSample normalPitchTunedSample; + TunedSample highPitchTunedSample; +} Instrument; + +typedef struct EnvelopePoint { + int16_t delay; + int16_t arg; +} EnvelopePoint; + +typedef struct Sample { + uint32_t codec; + uint32_t medium; + uint32_t unk_bit26; + uint32_t isRelocated; + uint32_t size; + uint32_t rawSampleOffset; + std::string loopRef; + std::string bookRef; +} Sample; + +typedef struct AdpcmBook { + int32_t order; + int32_t numPredictors; + std::vector<int16_t> book; +} AdpcmBook; + +typedef struct AdpcmLoop { + uint32_t start; + uint32_t end; + uint32_t count; + std::vector<int16_t> predictorState; +} AdpcmLoop; + +enum class DataType { + Drum, + Sfx, + Instrument, + Envelope, + Sample, + Book, + Loop, + DrumOffsets, +}; + +typedef std::variant<Drum, std::vector<SoundEffect>, Instrument, std::vector<EnvelopePoint>, Sample, AdpcmBook, AdpcmLoop, std::vector<std::string>> SoundFontType; + +typedef struct SoundFontEntry { + std::string name; + DataType type; + SoundFontType data; +} SoundFontEntry; + +class SoundFontData : public IParsedData { +public: + + std::vector<SoundFontEntry> mEntries; + bool mSupportSfx; + + SoundFontData(std::vector<SoundFontEntry> entries, bool supportSfx) : mEntries(std::move(entries)), mSupportSfx(supportSfx) {} +}; + +class SoundFontHeaderExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SoundFontBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SoundFontCodeExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SoundFontFactory : public BaseFactory { +public: + std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; + inline std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override { + return { + REGISTER(Code, SoundFontCodeExporter) + REGISTER(Header, SoundFontHeaderExporter) + REGISTER(Binary, SoundFontBinaryExporter) + }; + } +private: + std::string RegisterSoundFontData(std::string symbol, FZX::DataType dataType, uint32_t offset, std::map<uint32_t, std::pair<FZX::DataType, std::string>>& dataMap, std::unordered_map<FZX::DataType, uint32_t>& dataCountMap); +}; +} |
