From 867a4f23bf6facfc55078bbc3b2459cab6889996 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Tue, 4 Mar 2025 21:04:19 +0000 Subject: [PATCH 01/12] Add extended attributes support to instruments --- src/engine/instrument.cpp | 34 ++++++++++++++++++ src/engine/instrument.h | 27 ++++++++++++++- src/gui/insEdit.cpp | 72 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 9821e6d06d..4dae22c340 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -2581,6 +2581,38 @@ void DivInstrument::readFeatureS3(SafeReader& reader, short version) { READ_FEAT_END; } +void DivInstrument::readFeatureXA(SafeReader& reader, short version) { + READ_FEAT_BEGIN; + DivInstrumentXattr xattr; + + while (true) { + xattr.name=reader.readString(); + + if (!xattr.name.length()) { + break; + } + xattr.type=(DivXattrType)reader.readC(); + + switch (xattr.type) { + case DIV_XATTR_STRING: + xattr.str_val=reader.readString(); + break; + case DIV_XATTR_UINT: + case DIV_XATTR_INT: { + unsigned char byte=reader.readC(); + + do { + xattr.int_val <<= 7; + xattr.int_val |= byte & 0x7F; + byte = reader.readC(); + } while (byte & 0x80); + break; + } + } + std.xattrs.push_back(xattr); + } + READ_FEAT_END; +} DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; bool volIsCutoff=false; @@ -2657,6 +2689,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeatureS2(reader,version); } else if (memcmp(featCode,"S3",2)==0) { // SID3 readFeatureS3(reader,version); + } else if (memcmp(featCode,"XA",2)==0) { // extended attributes + readFeatureXA(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 4f83dcebaa..b10d920406 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -24,6 +24,7 @@ #include "../ta-utils.h" #include "../pch.h" #include "../fixedQueue.h" +#include struct DivSong; struct DivInstrument; @@ -149,6 +150,12 @@ enum DivMacroTypeOp: unsigned char { DIV_MACRO_OP_KSR, }; +enum DivXattrType: unsigned char { + DIV_XATTR_STRING, + DIV_XATTR_UINT, + DIV_XATTR_INT +}; + // FM operator structure: // - OPN: // - AM, AR, DR, MULT, RR, SL, TL, RS, DT, D2R, SSG-EG @@ -289,6 +296,21 @@ struct DivInstrumentMacro { } }; +struct DivInstrumentXattr { + String name; + DivXattrType type; + //union { + String str_val; + int int_val; + //}; + + DivInstrumentXattr(): + name("example.empty"), + type(DIV_XATTR_STRING), + str_val(), + int_val(0) {}; +}; + struct DivInstrumentSTD { DivInstrumentMacro volMacro; DivInstrumentMacro arpMacro; @@ -310,6 +332,7 @@ struct DivInstrumentSTD { DivInstrumentMacro ex6Macro; DivInstrumentMacro ex7Macro; DivInstrumentMacro ex8Macro; + std::vector xattrs; struct OpMacro { // ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; @@ -363,7 +386,8 @@ struct DivInstrumentSTD { ex5Macro(DIV_MACRO_EX5), ex6Macro(DIV_MACRO_EX6), ex7Macro(DIV_MACRO_EX7), - ex8Macro(DIV_MACRO_EX8) { + ex8Macro(DIV_MACRO_EX8), + xattrs() { for (int i=0; i<4; i++) { opMacros[i].amMacro.macroType=DIV_MACRO_OP_AM+(i<<5); opMacros[i].arMacro.macroType=DIV_MACRO_OP_AR+(i<<5); @@ -1119,6 +1143,7 @@ struct DivInstrument : DivInstrumentPOD { void readFeaturePN(SafeReader& reader, short version); void readFeatureS2(SafeReader& reader, short version); void readFeatureS3(SafeReader& reader, short version); + void readFeatureXA(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 2a63a191cd..f1fcb31cd8 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -338,6 +338,12 @@ const char* sid3SpecialWaveforms[]={ _N("Clipped Saw") }; +const char* xattrTypeNames[3] = { + _N("String"), + _N("Unsigned integer"), + _N("Integer") +}; + const bool opIsOutput[8][4]={ {false,false,false,true}, {false,false,false,true}, @@ -8637,6 +8643,72 @@ void FurnaceGUI::drawInsEdit() { drawMacros(macroList,macroEditStateMacros); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Attributes")) { + if (ImGui::BeginTable("AttrTable", 4)) { + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text(_("Name")); + ImGui::TableNextColumn(); + ImGui::Text(_("Type")); + ImGui::TableNextColumn(); + ImGui::Text(_("Value")); + ImGui::TableNextColumn(); + ImGui::Text(_("Actions")); + for (unsigned int i=0;istd.xattrs.size();i++) { + ImGui::PushID(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::InputText("##AttrName", &ins->std.xattrs[i].name, ImGuiInputTextFlags_UndoRedo)) { + MARK_MODIFIED; + } + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##AttrType",xattrTypeNames[ins->std.xattrs[i].type])) { + if (ImGui::Selectable(_("String"), ins->std.xattrs[i].type==DIV_XATTR_STRING)) { + MARK_MODIFIED; + ins->std.xattrs[i].type = DIV_XATTR_STRING; + } + if (ImGui::Selectable(_("Unsigned integer"), ins->std.xattrs[i].type==DIV_XATTR_UINT)) { + MARK_MODIFIED; + ins->std.xattrs[i].type = DIV_XATTR_UINT; + } + if (ImGui::Selectable(_("Integer"), ins->std.xattrs[i].type==DIV_XATTR_INT)) { + MARK_MODIFIED; + ins->std.xattrs[i].type = DIV_XATTR_INT; + } + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + switch (ins->std.xattrs[i].type) { + case DIV_XATTR_STRING: + if (ImGui::InputText("##AttrValue", &ins->std.xattrs[i].str_val, ImGuiInputTextFlags_UndoRedo)) { + MARK_MODIFIED; + } + break; + case DIV_XATTR_UINT: + case DIV_XATTR_INT: + if (ImGui::InputInt("##AttrValue", &ins->std.xattrs[i].int_val, ImGuiInputTextFlags_UndoRedo)) { + MARK_MODIFIED; + } + break; + } + ImGui::TableNextColumn(); + if (ImGui::Button(ICON_FA_TIMES "##AttrRemove")) { + } + ImGui::PopID(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button(ICON_FA_PLUS)) { + ins->std.xattrs.push_back(DivInstrumentXattr()); + } + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } if (ins->type==DIV_INS_AY) { if (!ins->amiga.useSample) { From 01194e04e6db6f4ac91338021d82dc05f4d9b4f7 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Fri, 7 Mar 2025 14:44:22 +0000 Subject: [PATCH 02/12] Fix the settings, and add proper support for unsigned integers --- src/engine/instrument.h | 8 +++++--- src/gui/insEdit.cpp | 11 +++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index b10d920406..8807a8d4c5 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -299,10 +299,12 @@ struct DivInstrumentMacro { struct DivInstrumentXattr { String name; DivXattrType type; - //union { - String str_val; + + String str_val; + union { + unsigned int uint_val; int int_val; - //}; + }; DivInstrumentXattr(): name("example.empty"), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f1fcb31cd8..5656b44f63 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -8688,9 +8688,16 @@ void FurnaceGUI::drawInsEdit() { MARK_MODIFIED; } break; - case DIV_XATTR_UINT: + case DIV_XATTR_UINT: { + // this is stupid + unsigned int int_step = 1; + if (ImGui::InputScalar("##AttrValue", ImGuiDataType_U32, &ins->std.xattrs[i].uint_val, &int_step)) { + MARK_MODIFIED; + } + } + break; case DIV_XATTR_INT: - if (ImGui::InputInt("##AttrValue", &ins->std.xattrs[i].int_val, ImGuiInputTextFlags_UndoRedo)) { + if (ImGui::InputInt("##AttrValue", &ins->std.xattrs[i].int_val)) { MARK_MODIFIED; } break; From bdae2f631b3ffde8f033939b39af8385032c31ae Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Fri, 7 Mar 2025 23:39:39 +0000 Subject: [PATCH 03/12] Fix VLQ encoding/decoding --- src/engine/instrument.cpp | 89 ++++++++++++++++++++++++++++++++++++--- src/engine/instrument.h | 1 + 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 4dae22c340..7851d2d148 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1131,6 +1131,56 @@ void DivInstrument::writeFeatureS3(SafeWriter* w) { FEATURE_END; } +void DivInstrument::writeFeatureXA(SafeWriter* w) { + FEATURE_BEGIN("XA"); + for (auto const i : this->std.xattrs) { + w->writeString(i.name, false); + w->writeC(i.type); + switch (i.type) { + case DIV_XATTR_STRING: + w->writeString(i.str_val, false); + break; + case DIV_XATTR_UINT: { + unsigned int value = i.uint_val; + unsigned char vlq_byte = 0; + + while (value) { + vlq_byte = value & 0x7F; + value >>= 7; + vlq_byte |= (value != 0) << 7; + w->writeC(vlq_byte); + } + break; + } + case DIV_XATTR_INT: { + unsigned int value = i.uint_val; + unsigned char vlq_byte = 0; + + // write initial VLQ byte (which contains sign bit) + if (i.int_val < 0) { + value = -value; + vlq_byte |= 0x40; + } + + vlq_byte |= value & 0x3F; + value >>= 6; + vlq_byte |= (value != 0) << 7; + w->writeC(vlq_byte); + + while (value) { + vlq_byte = value & 0x7F; + value >>= 7; + vlq_byte |= (value != 0) << 7; + w->writeC(vlq_byte); + } + break; + } + } + } + // a empty string, as feature termination + w->writeC(0); + FEATURE_END; +} void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -1178,6 +1228,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featurePN=false; bool featureS2=false; bool featureS3=false; + bool featureXA=false; bool checkForWL=false; @@ -1575,6 +1626,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo } } + if (!std.xattrs.empty()) { + featureXA = true; + } // write features if (featureNA) { writeFeatureNA(w); @@ -1647,6 +1701,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureS3) { writeFeatureS3(w); } + if (featureXA) { + writeFeatureXA(w); + } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -2597,19 +2654,39 @@ void DivInstrument::readFeatureXA(SafeReader& reader, short version) { case DIV_XATTR_STRING: xattr.str_val=reader.readString(); break; - case DIV_XATTR_UINT: - case DIV_XATTR_INT: { - unsigned char byte=reader.readC(); - + case DIV_XATTR_UINT: { + unsigned char byte=0; + unsigned int shift_count=0; do { - xattr.int_val <<= 7; - xattr.int_val |= byte & 0x7F; byte = reader.readC(); + xattr.uint_val |= (byte & 0x7F) << shift_count; + shift_count += 7; } while (byte & 0x80); break; } + case DIV_XATTR_INT: { + unsigned char byte=reader.readC(); + unsigned char sign_bit=0; + unsigned int shift_count=6; + + sign_bit = byte & 0x40; + xattr.uint_val |= byte & 0x3F; + while (byte & 0x80) { + byte=reader.readC(); + xattr.uint_val |= (byte & 0x7F) << shift_count; + shift_count += 7; + } + + // if the sign bit is on in the first byte of the VLQ encoded integer, + // negate the integer + if (sign_bit) { + xattr.int_val = -xattr.int_val; + } + break; + } } std.xattrs.push_back(xattr); + xattr = DivInstrumentXattr(); } READ_FEAT_END; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 8807a8d4c5..a0f452bd89 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -1121,6 +1121,7 @@ struct DivInstrument : DivInstrumentPOD { void writeFeaturePN(SafeWriter* w); void writeFeatureS2(SafeWriter* w); void writeFeatureS3(SafeWriter* w); + void writeFeatureXA(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); From 808546322d194efea0ac953f70bbdda57aaf0261 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Sat, 8 Mar 2025 14:24:29 +0000 Subject: [PATCH 04/12] Fix VLQ unsigned encoding emitting no bytes with a zero value --- src/engine/instrument.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 7851d2d148..29d8f7fe66 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1144,12 +1144,13 @@ void DivInstrument::writeFeatureXA(SafeWriter* w) { unsigned int value = i.uint_val; unsigned char vlq_byte = 0; - while (value) { + do { vlq_byte = value & 0x7F; value >>= 7; vlq_byte |= (value != 0) << 7; w->writeC(vlq_byte); - } + } while (value); + break; } case DIV_XATTR_INT: { From c77fdbd93f6ab6db2c33addb282e92aaa3e3df27 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Sat, 8 Mar 2025 15:26:55 +0000 Subject: [PATCH 05/12] Add boolean and floats to the types of extended attributes, fixed a compiler warning --- src/engine/instrument.cpp | 26 +++++++++++++++++++++++++- src/engine/instrument.h | 9 ++++++++- src/gui/insEdit.cpp | 24 ++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 29d8f7fe66..dabc9aa774 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1133,8 +1133,13 @@ void DivInstrument::writeFeatureS3(SafeWriter* w) { void DivInstrument::writeFeatureXA(SafeWriter* w) { FEATURE_BEGIN("XA"); - for (auto const i : this->std.xattrs) { + for (auto const &i : this->std.xattrs) { w->writeString(i.name, false); + if ((i.type & ~1) == DIV_XATTR_BOOLEAN) { + // handle boolean data type here + w->writeC(i.type | i.bool_val); + continue; + } w->writeC(i.type); switch (i.type) { case DIV_XATTR_STRING: @@ -1176,6 +1181,12 @@ void DivInstrument::writeFeatureXA(SafeWriter* w) { } break; } + case DIV_XATTR_FLOAT32: + w->writeF(i.float_val); + break; + default: + // this should be unreachable + break; } } // a empty string, as feature termination @@ -2650,6 +2661,13 @@ void DivInstrument::readFeatureXA(SafeReader& reader, short version) { break; } xattr.type=(DivXattrType)reader.readC(); + if ((xattr.type & ~1) == DIV_XATTR_BOOLEAN) { + xattr.bool_val = xattr.type & 1; + xattr.type = DIV_XATTR_BOOLEAN; + std.xattrs.push_back(xattr); + xattr = DivInstrumentXattr(); + continue; + } switch (xattr.type) { case DIV_XATTR_STRING: @@ -2685,6 +2703,12 @@ void DivInstrument::readFeatureXA(SafeReader& reader, short version) { } break; } + case DIV_XATTR_FLOAT32: + xattr.float_val = reader.readF(); + break; + default: + // this should be unreachable + break; } std.xattrs.push_back(xattr); xattr = DivInstrumentXattr(); diff --git a/src/engine/instrument.h b/src/engine/instrument.h index a0f452bd89..3a4ba3c02d 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -153,7 +153,12 @@ enum DivMacroTypeOp: unsigned char { enum DivXattrType: unsigned char { DIV_XATTR_STRING, DIV_XATTR_UINT, - DIV_XATTR_INT + DIV_XATTR_INT, + DIV_XATTR_FLOAT32, + + // the bool value is stored directly in the type + // as the least significant bit. + DIV_XATTR_BOOLEAN, }; // FM operator structure: @@ -304,6 +309,8 @@ struct DivInstrumentXattr { union { unsigned int uint_val; int int_val; + float float_val; + bool bool_val; }; DivInstrumentXattr(): diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 5656b44f63..c407a35b6f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -338,10 +338,12 @@ const char* sid3SpecialWaveforms[]={ _N("Clipped Saw") }; -const char* xattrTypeNames[3] = { +const char* xattrTypeNames[5] = { _N("String"), _N("Unsigned integer"), - _N("Integer") + _N("Integer"), + _N("Float"), + _N("Boolean"), }; const bool opIsOutput[8][4]={ @@ -8679,6 +8681,14 @@ void FurnaceGUI::drawInsEdit() { MARK_MODIFIED; ins->std.xattrs[i].type = DIV_XATTR_INT; } + if (ImGui::Selectable(_("Float"), ins->std.xattrs[i].type==DIV_XATTR_FLOAT32)) { + MARK_MODIFIED; + ins->std.xattrs[i].type = DIV_XATTR_FLOAT32; + } + if (ImGui::Selectable(_("Boolean"), ins->std.xattrs[i].type==DIV_XATTR_BOOLEAN)) { + MARK_MODIFIED; + ins->std.xattrs[i].type = DIV_XATTR_BOOLEAN; + } ImGui::EndCombo(); } ImGui::TableNextColumn(); @@ -8701,6 +8711,16 @@ void FurnaceGUI::drawInsEdit() { MARK_MODIFIED; } break; + case DIV_XATTR_FLOAT32: + if (ImGui::InputFloat("##AttrValue", &ins->std.xattrs[i].float_val)) { + MARK_MODIFIED; + } + break; + case DIV_XATTR_BOOLEAN: + if (ImGui::Checkbox("##AttrValue", &ins->std.xattrs[i].bool_val)) { + MARK_MODIFIED; + } + break; } ImGui::TableNextColumn(); if (ImGui::Button(ICON_FA_TIMES "##AttrRemove")) { From b70330438c2e0583456fc40223ee8f22362263d4 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Sat, 8 Mar 2025 21:56:59 +0000 Subject: [PATCH 06/12] Add documentation --- papers/newIns.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/papers/newIns.md b/papers/newIns.md index 32152bd322..c45ce645cf 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -159,6 +159,7 @@ the following feature codes are recognized: - `PN`: PowerNoise ins data - `S2`: SID2 ins data - `S3`: SID3 ins data +- `XA`: extended attributes - `EN`: end of features - if you find this feature code, stop reading the instrument. - it will usually appear only when there are sample/wave lists. @@ -770,3 +771,26 @@ size | description 1 | resonance scaling level 1 | resonance scaling center note: `0` is `c_5`, `1` is `c+5`, ..., `179` is `B-9` ``` + +# extended attributes (XA) + +for each attribute, up until a empty string (null byte only) is encountered for an attribute's name. +``` +size | description +-----|------------------------------------ + STR | name + 1 | attribute type + | - 0: string attribute + | - 1: unsigned integer attribute + | - 2: integer attribute + | - 3: floating-point attribute + | - 4: boolean attribute (false) + | - 5: boolean attribute (true) +``` + +if the attribute is a string, then a string is followed afterwards. +if the attribute is an unsigned integer, the integer value encoded in LEB128 is followed afterwards. +however, if the attribute is a integer, the first byte will only have 6 bits of data, the 7th bit in the first byte will be used as the sign bit of the whole integer. +if the 7th bit in the first byte is on, the integer should be negated after decoding. +if the attribute is a float, a 32-bit floating-point integer is followed afterwards. +boolean attributes have their value encoded in the type itself. From aa0b6d4a138b7a804a51ed39e426941af232a72b Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Sun, 9 Mar 2025 07:43:59 +0000 Subject: [PATCH 07/12] Make the delete button work --- src/gui/insEdit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c407a35b6f..8468b129a2 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -8724,6 +8724,7 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); if (ImGui::Button(ICON_FA_TIMES "##AttrRemove")) { + ins->std.xattrs.erase(ins->std.xattrs.begin() + i); } ImGui::PopID(); } From 8a4e2f0489cacb75515ccc96c6e61695a991a682 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Sun, 9 Mar 2025 07:50:12 +0000 Subject: [PATCH 08/12] Fix MSVC warnings --- src/engine/instrument.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index dabc9aa774..ed19a93170 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1137,7 +1137,7 @@ void DivInstrument::writeFeatureXA(SafeWriter* w) { w->writeString(i.name, false); if ((i.type & ~1) == DIV_XATTR_BOOLEAN) { // handle boolean data type here - w->writeC(i.type | i.bool_val); + w->writeC((unsigned char)i.type | i.bool_val); continue; } w->writeC(i.type); @@ -1159,7 +1159,7 @@ void DivInstrument::writeFeatureXA(SafeWriter* w) { break; } case DIV_XATTR_INT: { - unsigned int value = i.uint_val; + int value = i.int_val; unsigned char vlq_byte = 0; // write initial VLQ byte (which contains sign bit) From 1618ab89b8739d2d6d64ed772198a563033514ec Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Sun, 9 Mar 2025 08:00:41 +0000 Subject: [PATCH 09/12] Maybe the casting didn't work for MSVC --- src/engine/instrument.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index ed19a93170..685b71206b 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1137,7 +1137,8 @@ void DivInstrument::writeFeatureXA(SafeWriter* w) { w->writeString(i.name, false); if ((i.type & ~1) == DIV_XATTR_BOOLEAN) { // handle boolean data type here - w->writeC((unsigned char)i.type | i.bool_val); + // the unnecessary casting here is because of MSVC + w->writeC(i.type | (unsigned char)i.bool_val); continue; } w->writeC(i.type); From b348ce23f8362797d53419c95119f5e5190964ce Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Fri, 14 Mar 2025 11:32:48 +0000 Subject: [PATCH 10/12] Move Xattrs out of the DivInstrumentSTD class --- src/engine/instrument.cpp | 8 ++++---- src/engine/instrument.h | 5 ++--- src/gui/insEdit.cpp | 42 +++++++++++++++++++-------------------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 685b71206b..67c90d1b9a 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1133,7 +1133,7 @@ void DivInstrument::writeFeatureS3(SafeWriter* w) { void DivInstrument::writeFeatureXA(SafeWriter* w) { FEATURE_BEGIN("XA"); - for (auto const &i : this->std.xattrs) { + for (auto const &i : this->xattrs) { w->writeString(i.name, false); if ((i.type & ~1) == DIV_XATTR_BOOLEAN) { // handle boolean data type here @@ -1639,7 +1639,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo } } - if (!std.xattrs.empty()) { + if (!xattrs.empty()) { featureXA = true; } // write features @@ -2665,7 +2665,7 @@ void DivInstrument::readFeatureXA(SafeReader& reader, short version) { if ((xattr.type & ~1) == DIV_XATTR_BOOLEAN) { xattr.bool_val = xattr.type & 1; xattr.type = DIV_XATTR_BOOLEAN; - std.xattrs.push_back(xattr); + xattrs.push_back(xattr); xattr = DivInstrumentXattr(); continue; } @@ -2711,7 +2711,7 @@ void DivInstrument::readFeatureXA(SafeReader& reader, short version) { // this should be unreachable break; } - std.xattrs.push_back(xattr); + xattrs.push_back(xattr); xattr = DivInstrumentXattr(); } READ_FEAT_END; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 3a4ba3c02d..45c7f7a2df 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -341,7 +341,6 @@ struct DivInstrumentSTD { DivInstrumentMacro ex6Macro; DivInstrumentMacro ex7Macro; DivInstrumentMacro ex8Macro; - std::vector xattrs; struct OpMacro { // ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; @@ -395,8 +394,7 @@ struct DivInstrumentSTD { ex5Macro(DIV_MACRO_EX5), ex6Macro(DIV_MACRO_EX6), ex7Macro(DIV_MACRO_EX7), - ex8Macro(DIV_MACRO_EX8), - xattrs() { + ex8Macro(DIV_MACRO_EX8) { for (int i=0; i<4; i++) { opMacros[i].amMacro.macroType=DIV_MACRO_OP_AM+(i<<5); opMacros[i].arMacro.macroType=DIV_MACRO_OP_AR+(i<<5); @@ -1076,6 +1074,7 @@ struct DivInstrumentUndoStep { struct DivInstrument : DivInstrumentPOD { String name; + std::vector xattrs; DivInstrument() : name("") { diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8468b129a2..7be11090e3 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -8660,78 +8660,78 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text(_("Value")); ImGui::TableNextColumn(); ImGui::Text(_("Actions")); - for (unsigned int i=0;istd.xattrs.size();i++) { + for (unsigned int i=0;ixattrs.size();i++) { ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::InputText("##AttrName", &ins->std.xattrs[i].name, ImGuiInputTextFlags_UndoRedo)) { + if (ImGui::InputText("##AttrName", &ins->xattrs[i].name, ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED; } ImGui::TableNextColumn(); - if (ImGui::BeginCombo("##AttrType",xattrTypeNames[ins->std.xattrs[i].type])) { - if (ImGui::Selectable(_("String"), ins->std.xattrs[i].type==DIV_XATTR_STRING)) { + if (ImGui::BeginCombo("##AttrType",xattrTypeNames[ins->xattrs[i].type])) { + if (ImGui::Selectable(_("String"), ins->xattrs[i].type==DIV_XATTR_STRING)) { MARK_MODIFIED; - ins->std.xattrs[i].type = DIV_XATTR_STRING; + ins->xattrs[i].type = DIV_XATTR_STRING; } - if (ImGui::Selectable(_("Unsigned integer"), ins->std.xattrs[i].type==DIV_XATTR_UINT)) { + if (ImGui::Selectable(_("Unsigned integer"), ins->xattrs[i].type==DIV_XATTR_UINT)) { MARK_MODIFIED; - ins->std.xattrs[i].type = DIV_XATTR_UINT; + ins->xattrs[i].type = DIV_XATTR_UINT; } - if (ImGui::Selectable(_("Integer"), ins->std.xattrs[i].type==DIV_XATTR_INT)) { + if (ImGui::Selectable(_("Integer"), ins->xattrs[i].type==DIV_XATTR_INT)) { MARK_MODIFIED; - ins->std.xattrs[i].type = DIV_XATTR_INT; + ins->xattrs[i].type = DIV_XATTR_INT; } - if (ImGui::Selectable(_("Float"), ins->std.xattrs[i].type==DIV_XATTR_FLOAT32)) { + if (ImGui::Selectable(_("Float"), ins->xattrs[i].type==DIV_XATTR_FLOAT32)) { MARK_MODIFIED; - ins->std.xattrs[i].type = DIV_XATTR_FLOAT32; + ins->xattrs[i].type = DIV_XATTR_FLOAT32; } - if (ImGui::Selectable(_("Boolean"), ins->std.xattrs[i].type==DIV_XATTR_BOOLEAN)) { + if (ImGui::Selectable(_("Boolean"), ins->xattrs[i].type==DIV_XATTR_BOOLEAN)) { MARK_MODIFIED; - ins->std.xattrs[i].type = DIV_XATTR_BOOLEAN; + ins->xattrs[i].type = DIV_XATTR_BOOLEAN; } ImGui::EndCombo(); } ImGui::TableNextColumn(); - switch (ins->std.xattrs[i].type) { + switch (ins->xattrs[i].type) { case DIV_XATTR_STRING: - if (ImGui::InputText("##AttrValue", &ins->std.xattrs[i].str_val, ImGuiInputTextFlags_UndoRedo)) { + if (ImGui::InputText("##AttrValue", &ins->xattrs[i].str_val, ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED; } break; case DIV_XATTR_UINT: { // this is stupid unsigned int int_step = 1; - if (ImGui::InputScalar("##AttrValue", ImGuiDataType_U32, &ins->std.xattrs[i].uint_val, &int_step)) { + if (ImGui::InputScalar("##AttrValue", ImGuiDataType_U32, &ins->xattrs[i].uint_val, &int_step)) { MARK_MODIFIED; } } break; case DIV_XATTR_INT: - if (ImGui::InputInt("##AttrValue", &ins->std.xattrs[i].int_val)) { + if (ImGui::InputInt("##AttrValue", &ins->xattrs[i].int_val)) { MARK_MODIFIED; } break; case DIV_XATTR_FLOAT32: - if (ImGui::InputFloat("##AttrValue", &ins->std.xattrs[i].float_val)) { + if (ImGui::InputFloat("##AttrValue", &ins->xattrs[i].float_val)) { MARK_MODIFIED; } break; case DIV_XATTR_BOOLEAN: - if (ImGui::Checkbox("##AttrValue", &ins->std.xattrs[i].bool_val)) { + if (ImGui::Checkbox("##AttrValue", &ins->xattrs[i].bool_val)) { MARK_MODIFIED; } break; } ImGui::TableNextColumn(); if (ImGui::Button(ICON_FA_TIMES "##AttrRemove")) { - ins->std.xattrs.erase(ins->std.xattrs.begin() + i); + ins->xattrs.erase(ins->xattrs.begin() + i); } ImGui::PopID(); } ImGui::TableNextRow(); ImGui::TableNextColumn(); if (ImGui::Button(ICON_FA_PLUS)) { - ins->std.xattrs.push_back(DivInstrumentXattr()); + ins->xattrs.push_back(DivInstrumentXattr()); } ImGui::EndTable(); } From a5871d3bdcbd8c0c7ad1c4ddf7e3066f96d45818 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Fri, 14 Mar 2025 15:47:45 +0000 Subject: [PATCH 11/12] Add hash functions (for hashing the xattrs) --- CMakeLists.txt | 1 + src/hashUtils.cpp | 18 ++++++++++++++++++ src/hashUtils.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 src/hashUtils.cpp create mode 100644 src/hashUtils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d740a22698..e5d77d46ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -480,6 +480,7 @@ src/log.cpp src/baseutils.cpp src/fileutils.cpp src/utfutils.cpp +src/hashUtils.cpp extern/itcompress/compression.c diff --git a/src/hashUtils.cpp b/src/hashUtils.cpp new file mode 100644 index 0000000000..6a08381ea6 --- /dev/null +++ b/src/hashUtils.cpp @@ -0,0 +1,18 @@ +#include "hashUtils.h" +#include +#include +#include + +constexpr size_t computePi() { + // 64-bit fixed point integer of pi, used as the constant in the hash combining function + // due to it being a irrational number containing close-to-equal distribution of bits + // also it's pi day + return 0x517cc1b727220a94 >> (64 - (sizeof(size_t) * 8)); +} + +template +size_t combineHash(size_t curValue, const T& value) { + std::hash hash; + curValue ^= hash(value) + computePi() + (curValue << 6) + (curValue >> 2); + return curValue; +} diff --git a/src/hashUtils.h b/src/hashUtils.h new file mode 100644 index 0000000000..ee5070754d --- /dev/null +++ b/src/hashUtils.h @@ -0,0 +1,28 @@ +/** + * 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. + */ + +#ifndef _HASHUTILS_H +#define _HASHUTILS_H + +#include + +template +size_t combineHash(size_t curValue, const T& value); + +#endif From 58b18ce1d2660d7174a32a066bf7942f5fd559f4 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Fri, 14 Mar 2025 18:45:34 +0000 Subject: [PATCH 12/12] Make proper undo for the xattrs now --- CMakeLists.txt | 1 - src/engine/instrument.cpp | 32 +++++++++++++++++++++++++++++++- src/engine/instrument.h | 35 +++++++++++++++++++++++++++++++++++ src/hashUtils.cpp | 18 ------------------ src/hashUtils.h | 15 +++++++++++++-- 5 files changed, 79 insertions(+), 22 deletions(-) delete mode 100644 src/hashUtils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e5d77d46ca..d740a22698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -480,7 +480,6 @@ src/log.cpp src/baseutils.cpp src/fileutils.cpp src/utfutils.cpp -src/hashUtils.cpp extern/itcompress/compression.c diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 67c90d1b9a..87f3d3ff54 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -22,6 +22,7 @@ #include "instrument.h" #include "../ta-log.h" #include "../fileutils.h" +#include "../hashUtils.h" const DivInstrument defaultIns; @@ -480,9 +481,22 @@ void DivInstrumentUndoStep::applyAndReverse(DivInstrument* target) { if (nameValid) { name.swap(target->name); } + + if (xattrsValid) { + target->xattrs=xattrs; + } podPatch.applyAndReverse((DivInstrumentPOD*)target, sizeof(DivInstrumentPOD)); } +static size_t computeXattrsHash(const std::vector *vec) { + size_t hash=0; + std::hash hasher; + for (const auto &i: *vec) { + hash=combineHash(hash, hasher(i)); + } + + return hash; +} bool DivInstrumentUndoStep::makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post) { processTime=processTime_; @@ -493,7 +507,22 @@ bool DivInstrumentUndoStep::makeUndoPatch(size_t processTime_, const DivInstrume name=pre->name; } - return nameValid || podPatch.isValid(); + // check xattrs + size_t hash=0; + size_t post_hash=0; + if (!pre->xattrs.empty() || !post->xattrs.empty()) { + hash=computeXattrsHash(&pre->xattrs); + post_hash=computeXattrsHash(&post->xattrs); + logD("Xattrs pre hash: %lld", hash); + logD("Xattrs post hash: %lld", post_hash); + + if (hash!=post_hash) { + xattrsValid=true; + xattrs=pre->xattrs; + } + } + + return xattrsValid || nameValid || podPatch.isValid(); } bool DivInstrument::recordUndoStepIfChanged(size_t processTime, const DivInstrument* old) { @@ -3886,5 +3915,6 @@ DivInstrument& DivInstrument::operator=( const DivInstrument& ins ) { // undo/redo history is specifically not copied *(DivInstrumentPOD*)this=ins; name=ins.name; + xattrs=ins.xattrs; return *this; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 45c7f7a2df..0692326fe6 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -24,6 +24,7 @@ #include "../ta-utils.h" #include "../pch.h" #include "../fixedQueue.h" +#include "../hashUtils.h" #include struct DivSong; @@ -320,6 +321,36 @@ struct DivInstrumentXattr { int_val(0) {}; }; +// Hasher object for Xattr +template<> +struct std::hash +{ + size_t operator()(const DivInstrumentXattr& xattr) const noexcept { + size_t hash=0; + hash=combineHash(hash, xattr.name); + hash=combineHash(hash, xattr.type); + + switch (xattr.type) { + case DIV_XATTR_STRING: + hash=combineHash(hash, xattr.str_val); + break; + case DIV_XATTR_UINT: + hash=combineHash(hash, xattr.uint_val); + break; + case DIV_XATTR_INT: + hash=combineHash(hash, xattr.int_val); + break; + case DIV_XATTR_FLOAT32: + hash=combineHash(hash, xattr.float_val); + break; + case DIV_XATTR_BOOLEAN: + hash=combineHash(hash, xattr.bool_val); + break; + } + return hash; + } +}; + struct DivInstrumentSTD { DivInstrumentMacro volMacro; DivInstrumentMacro arpMacro; @@ -1060,12 +1091,16 @@ struct DivInstrumentUndoStep { DivInstrumentUndoStep() : name(""), nameValid(false), + xattrsValid(false), processTime(0) { } MemPatch podPatch; String name; bool nameValid; + + std::vector xattrs; + bool xattrsValid; size_t processTime; void applyAndReverse(DivInstrument* target); diff --git a/src/hashUtils.cpp b/src/hashUtils.cpp deleted file mode 100644 index 6a08381ea6..0000000000 --- a/src/hashUtils.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "hashUtils.h" -#include -#include -#include - -constexpr size_t computePi() { - // 64-bit fixed point integer of pi, used as the constant in the hash combining function - // due to it being a irrational number containing close-to-equal distribution of bits - // also it's pi day - return 0x517cc1b727220a94 >> (64 - (sizeof(size_t) * 8)); -} - -template -size_t combineHash(size_t curValue, const T& value) { - std::hash hash; - curValue ^= hash(value) + computePi() + (curValue << 6) + (curValue >> 2); - return curValue; -} diff --git a/src/hashUtils.h b/src/hashUtils.h index ee5070754d..7bde77565a 100644 --- a/src/hashUtils.h +++ b/src/hashUtils.h @@ -21,8 +21,19 @@ #define _HASHUTILS_H #include +#include -template -size_t combineHash(size_t curValue, const T& value); +constexpr size_t computePi() { + // 64-bit fixed point integer of pi, used as the constant in the hash combining function + // due to it being a irrational number containing close-to-equal distribution of bits + // also it's pi day + return 0x517cc1b727220a94 >> (64 - (sizeof(size_t) * 8)); +} +template +size_t combineHash(size_t curValue, const T& value) { + std::hash hash; + curValue ^= hash(value) + computePi() + (curValue << 6) + (curValue >> 2); + return curValue; +} #endif