ts-flp 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -595,9 +595,62 @@ function varIntSize(value) {
595
595
  }
596
596
 
597
597
  // src/parser/FlpParser.ts
598
+ var KNOWN_DWORD_IDS = new Set(
599
+ Object.keys(EVENT_KIND).map(Number).filter((id) => id >= DWORD && id < TEXT)
600
+ );
598
601
  var FLP_HEADER_MAGIC = "FLhd";
599
602
  var FLP_DATA_MAGIC = "FLdt";
600
603
  var FLP_HEADER_SIZE = 6;
604
+ var LOOKAHEAD_BYTES = 200;
605
+ var LOOKAHEAD_VARINT_MAX = 1e5;
606
+ function readVarIntRaw(buf, off, end) {
607
+ let shift = 0;
608
+ let size = 0;
609
+ const start = off;
610
+ while (true) {
611
+ if (off >= end) return { size: -1, bytesRead: 0 };
612
+ const byte = buf[off];
613
+ off++;
614
+ size |= (byte & 127) << shift;
615
+ shift += 7;
616
+ if (!(byte & 128)) break;
617
+ }
618
+ return { size, bytesRead: off - start };
619
+ }
620
+ function scoreAlignment(buf, off, end, bytesToCheck) {
621
+ const limit = Math.min(off + bytesToCheck, end);
622
+ let textDataCount = 0;
623
+ let consecutiveSmallByte = 0;
624
+ let maxConsecutiveSmallByte = 0;
625
+ while (off < limit) {
626
+ const id = buf[off];
627
+ if (id < WORD) {
628
+ off += 2;
629
+ if (id < 32 && !KNOWN_DWORD_IDS.has(id)) {
630
+ consecutiveSmallByte++;
631
+ maxConsecutiveSmallByte = Math.max(maxConsecutiveSmallByte, consecutiveSmallByte);
632
+ } else {
633
+ consecutiveSmallByte = 0;
634
+ }
635
+ } else if (id < DWORD) {
636
+ off += 3;
637
+ consecutiveSmallByte = 0;
638
+ } else if (id < TEXT) {
639
+ off += 5;
640
+ consecutiveSmallByte = 0;
641
+ } else {
642
+ const vi = readVarIntRaw(buf, off + 1, end);
643
+ if (vi.size >= 0 && vi.size < LOOKAHEAD_VARINT_MAX && off + 1 + vi.bytesRead + vi.size <= end) {
644
+ textDataCount++;
645
+ off = off + 1 + vi.bytesRead + vi.size;
646
+ } else {
647
+ return -100;
648
+ }
649
+ consecutiveSmallByte = 0;
650
+ }
651
+ }
652
+ return textDataCount * 10 - maxConsecutiveSmallByte * 3;
653
+ }
601
654
  function parseFlp(buffer) {
602
655
  const reader = new BinaryReader(buffer);
603
656
  const headerMagic = reader.readBytes(4).toString("ascii");
@@ -630,7 +683,6 @@ function parseFlp(buffer) {
630
683
  const events = [];
631
684
  let flVersion = "0.0.0";
632
685
  let useUnicode = false;
633
- let detectedVersion = false;
634
686
  const eventsStart = reader.tell();
635
687
  const eventsEnd = eventsStart + eventsSize;
636
688
  while (reader.tell() < eventsEnd) {
@@ -645,8 +697,32 @@ function parseFlp(buffer) {
645
697
  payloadSize = 2;
646
698
  headerSize2 = 1;
647
699
  } else if (eventId < TEXT) {
648
- payloadSize = 4;
649
- headerSize2 = 1;
700
+ if (KNOWN_DWORD_IDS.has(eventId)) {
701
+ payloadSize = 4;
702
+ headerSize2 = 1;
703
+ } else {
704
+ const afterDword = eventStart + 1 + 4;
705
+ const vi = readVarIntRaw(buffer, eventStart + 1, eventsEnd);
706
+ if (vi.size < 0 || vi.size > LOOKAHEAD_VARINT_MAX || eventStart + 1 + vi.bytesRead + vi.size > eventsEnd) {
707
+ payloadSize = 4;
708
+ headerSize2 = 1;
709
+ } else if (vi.size === 3) {
710
+ payloadSize = 4;
711
+ headerSize2 = 1;
712
+ } else {
713
+ const afterVarInt = eventStart + 1 + vi.bytesRead + vi.size;
714
+ const dwordScore = scoreAlignment(buffer, afterDword, eventsEnd, LOOKAHEAD_BYTES);
715
+ const varIntScore = scoreAlignment(buffer, afterVarInt, eventsEnd, LOOKAHEAD_BYTES);
716
+ if (varIntScore > dwordScore + 2) {
717
+ payloadSize = vi.size;
718
+ reader.seek(eventStart + 1 + vi.bytesRead);
719
+ headerSize2 = 1 + vi.bytesRead;
720
+ } else {
721
+ payloadSize = 4;
722
+ headerSize2 = 1;
723
+ }
724
+ }
725
+ }
650
726
  } else {
651
727
  const sizeStart = reader.tell();
652
728
  payloadSize = reader.readVarInt();
@@ -662,16 +738,14 @@ function parseFlp(buffer) {
662
738
  payload: Buffer.from(payload)
663
739
  };
664
740
  events.push(event);
665
- if (eventId === EVENT_ID.PROJECT_FL_VERSION && !detectedVersion) {
741
+ if (eventId === EVENT_ID.PROJECT_FL_VERSION && flVersion === "0.0.0") {
666
742
  const candidate = payload.toString("ascii").replace(/\0/g, "").trim();
667
- const isSemverLike = /^\d+(\.\d+)+$/.test(candidate);
668
- if (isSemverLike) {
743
+ if (/^\d+(\.\d+)+$/.test(candidate)) {
669
744
  flVersion = candidate;
670
745
  const parts = flVersion.split(".").map((p) => parseInt(p, 10));
671
746
  const major = parts[0] ?? 0;
672
747
  const minor = parts[1] ?? 0;
673
748
  useUnicode = parts.length >= 2 && (major > 11 || major === 11 && minor >= 5);
674
- detectedVersion = true;
675
749
  }
676
750
  }
677
751
  }
@@ -738,7 +812,7 @@ function createEvent(id, payload) {
738
812
  function patchEvents(parsed, patcher) {
739
813
  const patchedEvents = parsed.events.map((event, index) => {
740
814
  const patched = patcher(event, index);
741
- if (patched.payload !== event.payload && patched.id >= TEXT) {
815
+ if (patched.payload !== event.payload && event.header.length > 1) {
742
816
  const headerWriter = new BinaryWriter();
743
817
  headerWriter.writeU8(patched.id);
744
818
  headerWriter.writeVarInt(patched.payload.length);
@@ -831,18 +905,33 @@ function createNumberPayload(value, kind) {
831
905
  }
832
906
 
833
907
  // src/api/ProjectApi.ts
908
+ function readBpm(parsed) {
909
+ const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
910
+ if (tempoEvent) {
911
+ return getEventNumber(tempoEvent) / 1e3;
912
+ }
913
+ const coarseEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_COARSE);
914
+ if (coarseEvent) {
915
+ let bpm = coarseEvent.payload.readUInt16LE(0);
916
+ const fineEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_FINE);
917
+ if (fineEvent) {
918
+ bpm += fineEvent.payload.readUInt16LE(0) / 1e3;
919
+ }
920
+ return bpm;
921
+ }
922
+ return null;
923
+ }
834
924
  function readProjectMeta(parsed) {
835
925
  const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);
836
926
  const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);
837
927
  const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);
838
928
  const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);
839
- const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
840
929
  return {
841
930
  name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,
842
931
  description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,
843
932
  artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,
844
933
  genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,
845
- bpm: tempoEvent ? getEventNumber(tempoEvent) / 1e3 : null
934
+ bpm: readBpm(parsed)
846
935
  };
847
936
  }
848
937
  function writeProjectMeta(parsed, meta) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts","../src/generated/events.generated.ts","../src/io/BinaryReader.ts","../src/io/BinaryWriter.ts","../src/parser/FlpParser.ts","../src/api/ProjectApi.ts"],"sourcesContent":["/**\n * ts-flp - A minimal TypeScript port of PyFLP for FL Studio .flp files\n *\n * This library provides conservative (non-destructive) parsing and patching\n * of FL Studio project files. It preserves all unknown data byte-for-byte.\n *\n * Features:\n * - Read and modify project metadata (name, description, artist, BPM)\n * - Read and modify timestamps (creation date, work time)\n * - List and remap sample paths\n * - List VST plugins (name and vendor)\n * - Conservative round-trip: unchanged files are byte-identical\n *\n * @example\n * ```typescript\n * import { parseFlp, serializeFlp, readProjectMeta, writeProjectMeta } from \"ts-flp\";\n * import * as fs from \"fs\";\n *\n * // Parse an FLP file\n * const buffer = fs.readFileSync(\"project.flp\");\n * const parsed = parseFlp(buffer);\n *\n * // Read metadata\n * const meta = readProjectMeta(parsed);\n * console.log(meta.name, meta.bpm);\n *\n * // Modify and save\n * const modified = writeProjectMeta(parsed, { bpm: 140 });\n * fs.writeFileSync(\"project_modified.flp\", serializeFlp(modified));\n * ```\n */\n\n// Re-export parser functions\nexport {\n createEvent, createNumberPayload, createTextPayload, findEvents,\n findFirstEvent, getEventNumber, getEventString, parseFlp, patchEvents, serializeFlp, type FlpEvent, type ParsedFlp\n} from \"./src/parser/FlpParser.js\";\n\n// Re-export API functions\nexport {\n getFlVersion,\n getPPQ,\n listPlugins,\n listSamples,\n readProjectMeta,\n readProjectTimeInfo,\n rewriteSamplePaths,\n writeProjectMeta,\n writeProjectTimeInfo,\n type PluginRef,\n type ProjectMeta,\n type ProjectTimeInfo,\n type SampleRef\n} from \"./src/api/ProjectApi.js\";\n\n// Re-export event constants\nexport { EVENT_ID, EVENT_KIND, getEventFixedSize, getEventKind } from \"./src/generated/events.generated.js\";\n\n// Re-export IO utilities for advanced usage\nexport { BinaryReader } from \"./src/io/BinaryReader.js\";\nexport { BinaryWriter, encodeVarInt, varIntSize } from \"./src/io/BinaryWriter.js\";\n\n","/**\r\n * Auto-generated event constants from PyFLP\r\n * FL Studio .flp file event IDs and types\r\n *\r\n * Event ID ranges:\r\n * - BYTE (0-63): 1 byte data\r\n * - WORD (64-127): 2 bytes data\r\n * - DWORD (128-191): 4 bytes data\r\n * - TEXT (192-207): Variable length strings (+ some IDs in DATA range)\r\n * - DATA (208+): Variable length binary data\r\n */\r\n\r\n// Base offsets for event ID ranges\r\nexport const BYTE = 0;\r\nexport const WORD = 64;\r\nexport const DWORD = 128;\r\nexport const TEXT = 192;\r\nexport const DATA = 208;\r\n\r\n// IDs for TEXT events that use UTF-16LE in newer FL versions\r\nexport const NEW_TEXT_IDS = [\r\n TEXT + 49, // ArrangementID.Name\r\n TEXT + 39, // DisplayGroupID.Name\r\n TEXT + 47, // TrackID.Name\r\n] as const;\r\n\r\n/**\r\n * Event IDs used by FL Studio project files\r\n * Based on PyFLP's event definitions\r\n */\r\nexport const EVENT_ID = {\r\n // === Project IDs (from project.py) ===\r\n PROJECT_LOOP_ACTIVE: 9,\r\n PROJECT_SHOW_INFO: 10,\r\n PROJECT__VOLUME: 12,\r\n PROJECT_PAN_LAW: 23,\r\n PROJECT_LICENSED: 28,\r\n PROJECT__TEMPO_COARSE: WORD + 2, // 66\r\n PROJECT_PITCH: WORD + 16, // 80\r\n PROJECT__TEMPO_FINE: WORD + 29, // 93\r\n PROJECT_CUR_GROUP_ID: DWORD + 18,\r\n PROJECT_TEMPO: DWORD + 28,\r\n PROJECT_FL_BUILD: DWORD + 31,\r\n PROJECT_TITLE: TEXT + 2, // 194\r\n PROJECT_COMMENTS: TEXT + 3, // 195\r\n PROJECT_URL: TEXT + 5, // 197\r\n PROJECT__RTF_COMMENTS: TEXT + 6, // 198\r\n PROJECT_FL_VERSION: TEXT + 7, // 199\r\n PROJECT_LICENSEE: TEXT + 8, // 200\r\n PROJECT_DATA_PATH: TEXT + 10, // 202\r\n PROJECT_GENRE: TEXT + 14, // 206\r\n PROJECT_ARTISTS: TEXT + 15, // 207\r\n PROJECT_TIMESTAMP: DATA + 29, // 237\r\n\r\n // === Channel IDs (from channel.py) ===\r\n CHANNEL_IS_ENABLED: 0,\r\n CHANNEL__VOL_BYTE: 2,\r\n CHANNEL__PAN_BYTE: 3,\r\n CHANNEL_ZIPPED: 15,\r\n CHANNEL_PING_PONG_LOOP: 20,\r\n CHANNEL_TYPE: 21,\r\n CHANNEL_ROUTED_TO: 22,\r\n CHANNEL_IS_LOCKED: 32,\r\n CHANNEL_NEW: WORD,\r\n CHANNEL_FREQ_TILT: WORD + 5, // 69\r\n CHANNEL_FX_FLAGS: WORD + 6, // 70\r\n CHANNEL_CUTOFF: WORD + 7, // 71\r\n CHANNEL__VOL_WORD: WORD + 8, // 72\r\n CHANNEL__PAN_WORD: WORD + 9, // 73\r\n CHANNEL_PREAMP: WORD + 10, // 74\r\n CHANNEL_FADE_OUT: WORD + 11, // 75\r\n CHANNEL_FADE_IN: WORD + 12, // 76\r\n CHANNEL_RESONANCE: WORD + 19, // 83\r\n CHANNEL_STEREO_DELAY: WORD + 21, // 85\r\n CHANNEL_POGO: WORD + 22, // 86\r\n CHANNEL_TIME_SHIFT: WORD + 25, // 89\r\n CHANNEL_CHILDREN: WORD + 30, // 94\r\n CHANNEL_SWING: WORD + 33, // 97\r\n CHANNEL_RING_MOD: DWORD + 3,\r\n CHANNEL_CUT_GROUP: DWORD + 4,\r\n CHANNEL_ROOT_NOTE: DWORD + 7,\r\n CHANNEL_DELAY_MOD_XY: DWORD + 10,\r\n CHANNEL_REVERB: DWORD + 11,\r\n CHANNEL__STRETCH_TIME: DWORD + 12,\r\n CHANNEL_FINE_TUNE: DWORD + 14,\r\n CHANNEL_SAMPLER_FLAGS: DWORD + 15,\r\n CHANNEL_LAYER_FLAGS: DWORD + 16,\r\n CHANNEL_GROUP_NUM: DWORD + 17,\r\n CHANNEL_AU_SAMPLE_RATE: DWORD + 25,\r\n CHANNEL__NAME: TEXT,\r\n CHANNEL_SAMPLE_PATH: TEXT + 4, // 196\r\n CHANNEL_DELAY: DATA + 1, // 209\r\n CHANNEL_PARAMETERS: DATA + 7, // 215\r\n CHANNEL_ENVELOPE_LFO: DATA + 10, // 218\r\n CHANNEL_LEVELS: DATA + 11, // 219\r\n CHANNEL_POLYPHONY: DATA + 13, // 221\r\n CHANNEL_TRACKING: DATA + 20, // 228\r\n CHANNEL_LEVEL_ADJUSTS: DATA + 21, // 229\r\n CHANNEL_AUTOMATION: DATA + 26, // 234\r\n\r\n // === Plugin IDs (from plugin.py) ===\r\n PLUGIN_COLOR: DWORD,\r\n PLUGIN_ICON: DWORD + 27,\r\n PLUGIN_INTERNAL_NAME: TEXT + 9, // 201\r\n PLUGIN_NAME: TEXT + 11, // 203\r\n PLUGIN_WRAPPER: DATA + 4, // 212\r\n PLUGIN_DATA: DATA + 5, // 213\r\n\r\n // === Display Group IDs ===\r\n DISPLAY_GROUP_NAME: TEXT + 39, // 231\r\n\r\n // === Rack IDs ===\r\n RACK_SWING: 11,\r\n RACK__FIT_TO_STEPS: 13,\r\n RACK_WINDOW_HEIGHT: DWORD + 5,\r\n\r\n // === Mixer IDs (from mixer.py) ===\r\n MIXER_SLOT_INDEX: WORD + 34, // 98\r\n MIXER_INSERT_ICON: WORD + 31, // 95\r\n MIXER_INSERT_OUTPUT: DWORD + 19,\r\n MIXER_INSERT_COLOR: DWORD + 21,\r\n MIXER_INSERT_INPUT: DWORD + 26,\r\n MIXER_INSERT_NAME: TEXT + 12, // 204\r\n MIXER_INSERT_ROUTING: DATA + 27, // 235\r\n MIXER_INSERT_FLAGS: DATA + 28, // 236\r\n} as const;\r\n\r\n/**\r\n * Event data type classification\r\n */\r\nexport type EventKind =\r\n | \"u8\"\r\n | \"i8\"\r\n | \"u16\"\r\n | \"i16\"\r\n | \"u32\"\r\n | \"i32\"\r\n | \"f32\"\r\n | \"text\"\r\n | \"data\"\r\n | \"unknown\";\r\n\r\n/**\r\n * Maps event IDs to their data type\r\n * Used for proper serialization/deserialization\r\n */\r\nexport const EVENT_KIND: Record<number, EventKind> = {\r\n // Project events\r\n [EVENT_ID.PROJECT_LOOP_ACTIVE]: \"u8\",\r\n [EVENT_ID.PROJECT_SHOW_INFO]: \"u8\",\r\n [EVENT_ID.PROJECT__VOLUME]: \"u8\",\r\n [EVENT_ID.PROJECT_PAN_LAW]: \"u8\",\r\n [EVENT_ID.PROJECT_LICENSED]: \"u8\",\r\n [EVENT_ID.PROJECT__TEMPO_COARSE]: \"u16\",\r\n [EVENT_ID.PROJECT_PITCH]: \"i16\",\r\n [EVENT_ID.PROJECT__TEMPO_FINE]: \"u16\",\r\n [EVENT_ID.PROJECT_CUR_GROUP_ID]: \"i32\",\r\n [EVENT_ID.PROJECT_TEMPO]: \"u32\",\r\n [EVENT_ID.PROJECT_FL_BUILD]: \"u32\",\r\n [EVENT_ID.PROJECT_TITLE]: \"text\",\r\n [EVENT_ID.PROJECT_COMMENTS]: \"text\",\r\n [EVENT_ID.PROJECT_URL]: \"text\",\r\n [EVENT_ID.PROJECT__RTF_COMMENTS]: \"text\",\r\n [EVENT_ID.PROJECT_FL_VERSION]: \"text\",\r\n [EVENT_ID.PROJECT_LICENSEE]: \"text\",\r\n [EVENT_ID.PROJECT_DATA_PATH]: \"text\",\r\n [EVENT_ID.PROJECT_GENRE]: \"text\",\r\n [EVENT_ID.PROJECT_ARTISTS]: \"text\",\r\n [EVENT_ID.PROJECT_TIMESTAMP]: \"data\",\r\n\r\n // Channel events\r\n [EVENT_ID.CHANNEL_IS_ENABLED]: \"u8\",\r\n [EVENT_ID.CHANNEL__VOL_BYTE]: \"u8\",\r\n [EVENT_ID.CHANNEL__PAN_BYTE]: \"u8\",\r\n [EVENT_ID.CHANNEL_ZIPPED]: \"u8\",\r\n [EVENT_ID.CHANNEL_PING_PONG_LOOP]: \"u8\",\r\n [EVENT_ID.CHANNEL_TYPE]: \"u8\",\r\n [EVENT_ID.CHANNEL_ROUTED_TO]: \"i8\",\r\n [EVENT_ID.CHANNEL_IS_LOCKED]: \"u8\",\r\n [EVENT_ID.CHANNEL_NEW]: \"u16\",\r\n [EVENT_ID.CHANNEL_FREQ_TILT]: \"u16\",\r\n [EVENT_ID.CHANNEL_FX_FLAGS]: \"u16\",\r\n [EVENT_ID.CHANNEL_CUTOFF]: \"u16\",\r\n [EVENT_ID.CHANNEL__VOL_WORD]: \"u16\",\r\n [EVENT_ID.CHANNEL__PAN_WORD]: \"u16\",\r\n [EVENT_ID.CHANNEL_PREAMP]: \"u16\",\r\n [EVENT_ID.CHANNEL_FADE_OUT]: \"u16\",\r\n [EVENT_ID.CHANNEL_FADE_IN]: \"u16\",\r\n [EVENT_ID.CHANNEL_RESONANCE]: \"u16\",\r\n [EVENT_ID.CHANNEL_STEREO_DELAY]: \"u16\",\r\n [EVENT_ID.CHANNEL_POGO]: \"u16\",\r\n [EVENT_ID.CHANNEL_TIME_SHIFT]: \"u16\",\r\n [EVENT_ID.CHANNEL_CHILDREN]: \"u16\",\r\n [EVENT_ID.CHANNEL_SWING]: \"u16\",\r\n [EVENT_ID.CHANNEL_RING_MOD]: \"u32\",\r\n [EVENT_ID.CHANNEL_CUT_GROUP]: \"u32\",\r\n [EVENT_ID.CHANNEL_ROOT_NOTE]: \"u32\",\r\n [EVENT_ID.CHANNEL_DELAY_MOD_XY]: \"u32\",\r\n [EVENT_ID.CHANNEL_REVERB]: \"u32\",\r\n [EVENT_ID.CHANNEL__STRETCH_TIME]: \"f32\",\r\n [EVENT_ID.CHANNEL_FINE_TUNE]: \"i32\",\r\n [EVENT_ID.CHANNEL_SAMPLER_FLAGS]: \"u32\",\r\n [EVENT_ID.CHANNEL_LAYER_FLAGS]: \"u32\",\r\n [EVENT_ID.CHANNEL_GROUP_NUM]: \"i32\",\r\n [EVENT_ID.CHANNEL_AU_SAMPLE_RATE]: \"u32\",\r\n [EVENT_ID.CHANNEL__NAME]: \"text\",\r\n [EVENT_ID.CHANNEL_SAMPLE_PATH]: \"text\",\r\n [EVENT_ID.CHANNEL_DELAY]: \"data\",\r\n [EVENT_ID.CHANNEL_PARAMETERS]: \"data\",\r\n [EVENT_ID.CHANNEL_ENVELOPE_LFO]: \"data\",\r\n [EVENT_ID.CHANNEL_LEVELS]: \"data\",\r\n [EVENT_ID.CHANNEL_POLYPHONY]: \"data\",\r\n [EVENT_ID.CHANNEL_TRACKING]: \"data\",\r\n [EVENT_ID.CHANNEL_LEVEL_ADJUSTS]: \"data\",\r\n [EVENT_ID.CHANNEL_AUTOMATION]: \"data\",\r\n\r\n // Plugin events\r\n [EVENT_ID.PLUGIN_COLOR]: \"u32\",\r\n [EVENT_ID.PLUGIN_ICON]: \"u32\",\r\n [EVENT_ID.PLUGIN_INTERNAL_NAME]: \"text\",\r\n [EVENT_ID.PLUGIN_NAME]: \"text\",\r\n [EVENT_ID.PLUGIN_WRAPPER]: \"data\",\r\n [EVENT_ID.PLUGIN_DATA]: \"data\",\r\n\r\n // Display Group events\r\n [EVENT_ID.DISPLAY_GROUP_NAME]: \"data\",\r\n\r\n // Rack events\r\n [EVENT_ID.RACK_SWING]: \"u8\",\r\n [EVENT_ID.RACK__FIT_TO_STEPS]: \"u8\",\r\n [EVENT_ID.RACK_WINDOW_HEIGHT]: \"u32\",\r\n\r\n // Mixer events\r\n [EVENT_ID.MIXER_SLOT_INDEX]: \"u16\",\r\n [EVENT_ID.MIXER_INSERT_ICON]: \"i16\",\r\n [EVENT_ID.MIXER_INSERT_OUTPUT]: \"i32\",\r\n [EVENT_ID.MIXER_INSERT_COLOR]: \"u32\",\r\n [EVENT_ID.MIXER_INSERT_INPUT]: \"i32\",\r\n [EVENT_ID.MIXER_INSERT_NAME]: \"text\",\r\n [EVENT_ID.MIXER_INSERT_ROUTING]: \"data\",\r\n [EVENT_ID.MIXER_INSERT_FLAGS]: \"data\",\r\n\r\n};\r\n\r\n/**\r\n * Determines the event kind based on ID\r\n * Falls back to range-based detection if not explicitly mapped\r\n */\r\nexport function getEventKind(eventId: number): EventKind {\r\n // Check explicit mapping first\r\n const mapped = EVENT_KIND[eventId];\r\n if (mapped !== undefined) {\r\n return mapped;\r\n }\r\n\r\n // Fall back to range-based detection\r\n if (eventId < WORD) {\r\n return \"u8\";\r\n } else if (eventId < DWORD) {\r\n return \"u16\";\r\n } else if (eventId < TEXT) {\r\n return \"u32\";\r\n } else if (eventId < DATA || NEW_TEXT_IDS.includes(eventId as 241 | 231 | 239)) {\r\n return \"text\";\r\n } else {\r\n return \"data\";\r\n }\r\n}\r\n\r\n/**\r\n * Returns the fixed size for BYTE/WORD/DWORD events\r\n * Returns -1 for variable-length events (TEXT/DATA)\r\n */\r\nexport function getEventFixedSize(eventId: number): number {\r\n if (eventId < WORD) {\r\n return 1;\r\n } else if (eventId < DWORD) {\r\n return 2;\r\n } else if (eventId < TEXT) {\r\n return 4;\r\n }\r\n return -1; // Variable length\r\n}\r\n","/**\n * Binary reader for FL Studio .flp files\n * Uses smart-buffer for binary reading and protobufjs for VarInt decoding\n */\n\nimport protobuf from \"protobufjs/minimal.js\";\nimport { SmartBuffer } from \"smart-buffer\";\n\nexport class BinaryReader {\n private sb: SmartBuffer;\n private source: Buffer;\n\n constructor(buffer: Buffer) {\n this.source = buffer;\n this.sb = SmartBuffer.fromBuffer(buffer);\n }\n\n /**\n * Returns current read position\n */\n tell(): number {\n return this.sb.readOffset;\n }\n\n /**\n * Seeks to an absolute position\n */\n seek(pos: number): void {\n this.sb.readOffset = pos;\n }\n\n /**\n * Seeks relative to current position\n */\n skip(offset: number): void {\n this.sb.readOffset += offset;\n }\n\n /**\n * Returns total buffer length\n */\n length(): number {\n return this.sb.length;\n }\n\n /**\n * Returns remaining bytes to read\n */\n remaining(): number {\n return this.sb.remaining();\n }\n\n /**\n * Returns true if there are more bytes to read\n */\n hasMore(): boolean {\n return this.sb.remaining() > 0;\n }\n\n /**\n * Reads an unsigned 8-bit integer\n */\n readU8(): number {\n return this.sb.readUInt8();\n }\n\n /**\n * Reads a signed 8-bit integer\n */\n readI8(): number {\n return this.sb.readInt8();\n }\n\n /**\n * Reads an unsigned 16-bit integer (little-endian)\n */\n readU16LE(): number {\n return this.sb.readUInt16LE();\n }\n\n /**\n * Reads a signed 16-bit integer (little-endian)\n */\n readI16LE(): number {\n return this.sb.readInt16LE();\n }\n\n /**\n * Reads an unsigned 32-bit integer (little-endian)\n */\n readU32LE(): number {\n return this.sb.readUInt32LE();\n }\n\n /**\n * Reads a signed 32-bit integer (little-endian)\n */\n readI32LE(): number {\n return this.sb.readInt32LE();\n }\n\n /**\n * Reads a 32-bit float (little-endian)\n */\n readF32LE(): number {\n return this.sb.readFloatLE();\n }\n\n /**\n * Reads a 64-bit float (little-endian)\n */\n readF64LE(): number {\n return this.sb.readDoubleLE();\n }\n\n /**\n * Reads raw bytes\n */\n readBytes(length: number): Buffer {\n return this.sb.readBuffer(length);\n }\n\n /**\n * Reads a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n readVarInt(): number {\n // Get remaining buffer from current position\n const remainingBuffer = this.source.subarray(this.sb.readOffset);\n const reader = protobuf.Reader.create(remainingBuffer);\n const value = reader.uint32();\n // Advance our position by how many bytes protobufjs consumed\n this.sb.readOffset += reader.pos;\n return value;\n }\n\n /**\n * Reads an ASCII string (null-terminated)\n */\n readAsciiString(length: number): string {\n const str = this.sb.readString(length, \"ascii\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Reads a UTF-16LE string (null-terminated)\n */\n readUtf16LEString(length: number): string {\n const bytes = this.sb.readBuffer(length);\n const str = bytes.toString(\"utf16le\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Peeks at bytes without advancing position\n */\n peek(length: number): Buffer {\n const pos = this.sb.readOffset;\n const data = this.sb.readBuffer(length);\n this.sb.readOffset = pos;\n return data;\n }\n\n /**\n * Peeks at a single byte without advancing position\n */\n peekU8(): number {\n const pos = this.sb.readOffset;\n const value = this.sb.readUInt8();\n this.sb.readOffset = pos;\n return value;\n }\n\n /**\n * Returns the internal buffer\n */\n toBuffer(): Buffer {\n return this.source;\n }\n}\n","/**\n * Binary writer for FL Studio .flp files\n * Uses smart-buffer for binary writing and protobufjs for VarInt encoding\n */\n\nimport { SmartBuffer } from \"smart-buffer\";\nimport protobuf from \"protobufjs/minimal.js\";\n\nexport class BinaryWriter {\n private sb: SmartBuffer;\n\n constructor() {\n this.sb = new SmartBuffer();\n }\n\n /**\n * Returns current write position (total bytes written)\n */\n tell(): number {\n return this.sb.writeOffset;\n }\n\n /**\n * Writes an unsigned 8-bit integer\n */\n writeU8(value: number): void {\n this.sb.writeUInt8(value & 0xff);\n }\n\n /**\n * Writes a signed 8-bit integer\n */\n writeI8(value: number): void {\n this.sb.writeInt8(value);\n }\n\n /**\n * Writes an unsigned 16-bit integer (little-endian)\n */\n writeU16LE(value: number): void {\n this.sb.writeUInt16LE(value & 0xffff);\n }\n\n /**\n * Writes a signed 16-bit integer (little-endian)\n */\n writeI16LE(value: number): void {\n this.sb.writeInt16LE(value);\n }\n\n /**\n * Writes an unsigned 32-bit integer (little-endian)\n */\n writeU32LE(value: number): void {\n this.sb.writeUInt32LE(value >>> 0);\n }\n\n /**\n * Writes a signed 32-bit integer (little-endian)\n */\n writeI32LE(value: number): void {\n this.sb.writeInt32LE(value);\n }\n\n /**\n * Writes a 32-bit float (little-endian)\n */\n writeF32LE(value: number): void {\n this.sb.writeFloatLE(value);\n }\n\n /**\n * Writes a 64-bit float (little-endian)\n */\n writeF64LE(value: number): void {\n this.sb.writeDoubleLE(value);\n }\n\n /**\n * Writes raw bytes\n */\n writeBytes(data: Buffer): void {\n this.sb.writeBuffer(data);\n }\n\n /**\n * Writes a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n writeVarInt(value: number): void {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n const encoded = writer.finish();\n this.sb.writeBuffer(Buffer.from(encoded));\n }\n\n /**\n * Writes an ASCII string (null-terminated)\n */\n writeAsciiString(str: string): void {\n this.sb.writeString(str + \"\\0\", \"ascii\");\n }\n\n /**\n * Writes a UTF-16LE string (null-terminated)\n */\n writeUtf16LEString(str: string): void {\n const buf = Buffer.from(str + \"\\0\", \"utf16le\");\n this.sb.writeBuffer(buf);\n }\n\n /**\n * Gets the final buffer\n */\n toBuffer(): Buffer {\n return this.sb.toBuffer();\n }\n}\n\n/**\n * Encodes a VarInt to a Buffer using protobufjs (standalone utility)\n */\nexport function encodeVarInt(value: number): Buffer {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n return Buffer.from(writer.finish());\n}\n\n/**\n * Calculates the byte size needed for a VarInt\n */\nexport function varIntSize(value: number): number {\n if (value < 0x80) return 1;\n if (value < 0x4000) return 2;\n if (value < 0x200000) return 3;\n if (value < 0x10000000) return 4;\n return 5;\n}\n","/**\n * FL Studio .flp file parser and serializer\n * Conservative patch approach: preserves all unknown data byte-for-byte\n */\n\nimport { DWORD, EVENT_ID, TEXT, WORD, getEventKind } from \"../generated/events.generated.js\";\nimport { BinaryReader } from \"../io/BinaryReader.js\";\nimport { BinaryWriter } from \"../io/BinaryWriter.js\";\n\n/**\n * Represents a single event in an FLP file\n * Stores raw bytes to enable conservative patching\n */\nexport interface FlpEvent {\n /** Event ID (0-255) */\n id: number;\n /** Data type classification */\n kind: \"u8\" | \"i8\" | \"u16\" | \"i16\" | \"u32\" | \"i32\" | \"f32\" | \"text\" | \"data\" | \"unknown\";\n /** Original header bytes (ID + size for variable-length events) */\n header: Buffer;\n /** Event payload data */\n payload: Buffer;\n}\n\n/**\n * Represents a parsed FLP file\n * Contains all data needed for conservative round-trip\n */\nexport interface ParsedFlp {\n /** FLhd chunk (header) - 14 bytes total */\n headerChunkBytes: Buffer;\n /** FLdt chunk header (magic + size placeholder) - 8 bytes */\n fldtHeaderBytes: Buffer;\n /** All events from the FLdt chunk */\n events: FlpEvent[];\n /** Any trailing bytes after events (should be empty) */\n trailingBytes: Buffer | undefined;\n /** Detected FL version for string encoding */\n flVersion: string;\n /** Whether to use UTF-16LE for strings (FL >= 11.5) */\n useUnicode: boolean;\n}\n\n/**\n * Header structure constants\n */\nconst FLP_HEADER_MAGIC = \"FLhd\";\nconst FLP_DATA_MAGIC = \"FLdt\";\nconst FLP_HEADER_SIZE = 6; // Expected header chunk size\n\n/**\n * Parses an FL Studio project file\n *\n * @param buffer - Raw file data\n * @returns Parsed FLP structure\n * @throws Error if file is corrupted or invalid\n */\nexport function parseFlp(buffer: Buffer): ParsedFlp {\n const reader = new BinaryReader(buffer);\n\n // Read FLhd header chunk\n const headerMagic = reader.readBytes(4).toString(\"ascii\");\n if (headerMagic !== FLP_HEADER_MAGIC) {\n throw new Error(`Invalid header magic: expected 'FLhd', got '${headerMagic}'`);\n }\n\n const headerSize = reader.readU32LE();\n if (headerSize !== FLP_HEADER_SIZE) {\n throw new Error(`Invalid header size: expected ${FLP_HEADER_SIZE}, got ${headerSize}`);\n }\n\n const format = reader.readI16LE();\n const channelCount = reader.readU16LE();\n const ppq = reader.readU16LE();\n\n // Validate format (0 = normal project)\n if (format < -1 || format > 0x50) {\n throw new Error(`Invalid file format: ${format}`);\n }\n\n // Store complete header chunk (14 bytes)\n const headerChunkBytes = buffer.subarray(0, 14);\n\n // Read FLdt data chunk header\n const dataMagic = reader.readBytes(4).toString(\"ascii\");\n if (dataMagic !== FLP_DATA_MAGIC) {\n throw new Error(`Invalid data magic: expected 'FLdt', got '${dataMagic}'`);\n }\n\n const eventsSize = reader.readU32LE();\n\n // Validate events size\n const expectedFileSize = 22 + eventsSize; // 14 (FLhd) + 8 (FLdt header) + events\n if (buffer.length !== expectedFileSize) {\n throw new Error(\n `Data chunk size mismatch: expected file size ${expectedFileSize}, got ${buffer.length}`\n );\n }\n\n // Store FLdt header (8 bytes)\n const fldtHeaderBytes = buffer.subarray(14, 22);\n\n // Parse events\n const events: FlpEvent[] = [];\n let flVersion = \"0.0.0\";\n let useUnicode = false;\n let detectedVersion = false;\n\n const eventsStart = reader.tell();\n const eventsEnd = eventsStart + eventsSize;\n\n while (reader.tell() < eventsEnd) {\n const eventStart = reader.tell();\n const eventId = reader.readU8();\n\n let payloadSize: number;\n let headerSize: number;\n\n // Determine payload size based on event ID range\n if (eventId < WORD) {\n // BYTE range: 1 byte payload\n payloadSize = 1;\n headerSize = 1; // Just the ID\n } else if (eventId < DWORD) {\n // WORD range: 2 bytes payload\n payloadSize = 2;\n headerSize = 1;\n } else if (eventId < TEXT) {\n // DWORD range: 4 bytes payload\n payloadSize = 4;\n headerSize = 1;\n } else {\n // TEXT/DATA range: VarInt-encoded size\n const sizeStart = reader.tell();\n payloadSize = reader.readVarInt();\n headerSize = 1 + (reader.tell() - sizeStart); // ID + VarInt bytes\n }\n\n // Read payload\n const payload = reader.readBytes(payloadSize);\n\n // Extract header from original buffer for byte-perfect round-trip\n const header = buffer.subarray(eventStart, eventStart + headerSize);\n\n // Determine event kind\n const kind = getEventKind(eventId);\n\n const event: FlpEvent = {\n id: eventId,\n kind,\n header: Buffer.from(header),\n payload: Buffer.from(payload),\n };\n\n events.push(event);\n\n // Detect FL version from FLVersion event\n if (eventId === EVENT_ID.PROJECT_FL_VERSION && !detectedVersion) {\n const candidate = payload.toString(\"ascii\").replace(/\\0/g, \"\").trim();\n const isSemverLike = /^\\d+(\\.\\d+)+$/.test(candidate);\n if (isSemverLike) {\n flVersion = candidate;\n const parts = flVersion.split(\".\").map((p) => parseInt(p, 10));\n const major = parts[0] ?? 0;\n const minor = parts[1] ?? 0;\n useUnicode = parts.length >= 2 && (major > 11 || (major === 11 && minor >= 5));\n detectedVersion = true;\n }\n }\n }\n\n // Check for trailing bytes\n let trailingBytes: Buffer | undefined;\n if (reader.remaining() > 0) {\n trailingBytes = reader.readBytes(reader.remaining());\n }\n\n return {\n headerChunkBytes: Buffer.from(headerChunkBytes),\n fldtHeaderBytes: Buffer.from(fldtHeaderBytes),\n events,\n trailingBytes,\n flVersion,\n useUnicode,\n };\n}\n\n/**\n * Serializes a parsed FLP back to a buffer\n * Recalculates FLdt chunk size if events have changed\n *\n * @param parsed - Parsed FLP structure\n * @returns Serialized buffer\n */\nexport function serializeFlp(parsed: ParsedFlp): Buffer {\n const writer = new BinaryWriter();\n\n // Write FLhd header chunk (preserved byte-for-byte)\n writer.writeBytes(parsed.headerChunkBytes);\n\n // Calculate total events size\n let eventsSize = 0;\n for (const event of parsed.events) {\n eventsSize += serializeEvent(event).length;\n }\n\n // Add trailing bytes if present\n if (parsed.trailingBytes) {\n eventsSize += parsed.trailingBytes.length;\n }\n\n // Write FLdt header with updated size\n writer.writeBytes(Buffer.from(FLP_DATA_MAGIC, \"ascii\"));\n writer.writeU32LE(eventsSize);\n\n // Write all events\n for (const event of parsed.events) {\n writer.writeBytes(serializeEvent(event));\n }\n\n // Write trailing bytes if present\n if (parsed.trailingBytes) {\n writer.writeBytes(parsed.trailingBytes);\n }\n\n return writer.toBuffer();\n}\n\n/**\n * Serializes a single event to bytes\n * Uses original header for unchanged events, recalculates for modified ones\n */\nfunction serializeEvent(event: FlpEvent): Buffer {\n const writer = new BinaryWriter();\n\n // Conservative mode: preserve the original header bytes whenever available.\n // This keeps unchanged files byte-identical, including exact VarInt encoding.\n if (event.header.length > 0) {\n writer.writeBytes(event.header);\n } else {\n writer.writeU8(event.id);\n if (event.id >= TEXT) {\n writer.writeVarInt(event.payload.length);\n }\n }\n\n // Write payload\n writer.writeBytes(event.payload);\n\n return writer.toBuffer();\n}\n\n/**\n * Creates a new event with the given ID and payload\n */\nexport function createEvent(id: number, payload: Buffer): FlpEvent {\n const kind = getEventKind(id);\n\n // Build header\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(id);\n\n if (id >= TEXT) {\n headerWriter.writeVarInt(payload.length);\n }\n\n return {\n id,\n kind,\n header: headerWriter.toBuffer(),\n payload: Buffer.from(payload),\n };\n}\n\n/**\n * Applies a patcher function to all events\n * Returns a new ParsedFlp with patched events\n *\n * @param parsed - Original parsed FLP\n * @param patcher - Function that transforms each event\n * @returns New ParsedFlp with patched events\n */\nexport function patchEvents(\n parsed: ParsedFlp,\n patcher: (event: FlpEvent, index: number) => FlpEvent\n): ParsedFlp {\n const patchedEvents = parsed.events.map((event, index) => {\n const patched = patcher(event, index);\n\n // If payload changed, recalculate header for TEXT/DATA events\n if (patched.payload !== event.payload && patched.id >= TEXT) {\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(patched.id);\n headerWriter.writeVarInt(patched.payload.length);\n return {\n ...patched,\n header: headerWriter.toBuffer(),\n };\n }\n\n return patched;\n });\n\n return {\n ...parsed,\n events: patchedEvents,\n };\n}\n\n/**\n * Finds all events with a specific ID\n */\nexport function findEvents(parsed: ParsedFlp, eventId: number): FlpEvent[] {\n return parsed.events.filter((e) => e.id === eventId);\n}\n\n/**\n * Finds the first event with a specific ID\n */\nexport function findFirstEvent(parsed: ParsedFlp, eventId: number): FlpEvent | undefined {\n return parsed.events.find((e) => e.id === eventId);\n}\n\n/**\n * Gets event payload as a string (handles encoding)\n */\nexport function getEventString(event: FlpEvent, useUnicode: boolean): string {\n if (event.kind !== \"text\") {\n throw new Error(`Event ${event.id} is not a text event`);\n }\n\n if (useUnicode) {\n const str = event.payload.toString(\"utf16le\");\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n } else {\n const str = event.payload.toString(\"ascii\");\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n}\n\n/**\n * Creates a text payload with proper encoding\n */\nexport function createTextPayload(text: string, useUnicode: boolean): Buffer {\n if (useUnicode) {\n return Buffer.from(text + \"\\0\", \"utf16le\");\n } else {\n return Buffer.from(text + \"\\0\", \"ascii\");\n }\n}\n\n/**\n * Gets event payload as a number\n */\nexport function getEventNumber(event: FlpEvent): number {\n const reader = new BinaryReader(event.payload);\n\n switch (event.kind) {\n case \"u8\":\n return reader.readU8();\n case \"i8\":\n return reader.readI8();\n case \"u16\":\n return reader.readU16LE();\n case \"i16\":\n return reader.readI16LE();\n case \"u32\":\n return reader.readU32LE();\n case \"i32\":\n return reader.readI32LE();\n case \"f32\":\n return reader.readF32LE();\n default:\n throw new Error(`Cannot get number from ${event.kind} event`);\n }\n}\n\n/**\n * Creates a numeric payload\n */\nexport function createNumberPayload(\n value: number,\n kind: \"u8\" | \"i8\" | \"u16\" | \"i16\" | \"u32\" | \"i32\" | \"f32\"\n): Buffer {\n const writer = new BinaryWriter();\n\n switch (kind) {\n case \"u8\":\n writer.writeU8(value);\n break;\n case \"i8\":\n writer.writeI8(value);\n break;\n case \"u16\":\n writer.writeU16LE(value);\n break;\n case \"i16\":\n writer.writeI16LE(value);\n break;\n case \"u32\":\n writer.writeU32LE(value);\n break;\n case \"i32\":\n writer.writeI32LE(value);\n break;\n case \"f32\":\n writer.writeF32LE(value);\n break;\n }\n\n return writer.toBuffer();\n}\n","/**\n * High-level API for reading and modifying FL Studio project files\n * Provides typed access to project metadata, samples, plugins, and time info\n */\n\nimport { EVENT_ID } from \"../generated/events.generated.js\";\nimport { BinaryReader } from \"../io/BinaryReader.js\";\nimport { BinaryWriter } from \"../io/BinaryWriter.js\";\nimport {\n createNumberPayload,\n createTextPayload,\n findFirstEvent,\n type FlpEvent,\n getEventNumber,\n getEventString,\n type ParsedFlp,\n patchEvents,\n} from \"../parser/FlpParser.js\";\n\n// ============================================================================\n// Project Metadata\n// ============================================================================\n\n/**\n * Project metadata (name, description, artist, genre, BPM)\n */\nexport interface ProjectMeta {\n name: string | null;\n description: string | null;\n artist: string | null;\n genre: string | null;\n bpm: number | null;\n}\n\n/**\n * Reads project metadata from a parsed FLP\n */\nexport function readProjectMeta(parsed: ParsedFlp): ProjectMeta {\n const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);\n const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);\n const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);\n const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);\n const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);\n\n return {\n name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,\n description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,\n artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,\n genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,\n bpm: tempoEvent ? getEventNumber(tempoEvent) / 1000 : null,\n };\n}\n\n/**\n * Writes project metadata to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n */\nexport function writeProjectMeta(parsed: ParsedFlp, meta: Partial<ProjectMeta>): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n // Patch title\n if (meta.name !== undefined && event.id === EVENT_ID.PROJECT_TITLE) {\n return {\n ...event,\n payload: createTextPayload(meta.name ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch comments/description\n if (meta.description !== undefined && event.id === EVENT_ID.PROJECT_COMMENTS) {\n return {\n ...event,\n payload: createTextPayload(meta.description ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch artists\n if (meta.artist !== undefined && event.id === EVENT_ID.PROJECT_ARTISTS) {\n return {\n ...event,\n payload: createTextPayload(meta.artist ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch genre\n if (meta.genre !== undefined && event.id === EVENT_ID.PROJECT_GENRE) {\n return {\n ...event,\n payload: createTextPayload(meta.genre ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch tempo (stored as BPM * 1000)\n if (meta.bpm && Math.abs(meta.bpm) > 0 && event.id === EVENT_ID.PROJECT_TEMPO) {\n return {\n ...event,\n payload: createNumberPayload(Math.round(Math.abs(meta.bpm) * 1000), \"u32\"),\n };\n }\n\n return event;\n });\n}\n\n// ============================================================================\n// Project Time Info\n// ============================================================================\n\n/**\n * Project creation date and work time\n */\nexport interface ProjectTimeInfo {\n creationDate: Date | null;\n workTimeSeconds: number | null;\n}\n\n// Delphi epoch: December 30, 1899\nconst DELPHI_EPOCH = new Date(1899, 11, 30);\n\n/**\n * Converts Delphi timestamp (days since epoch) to Date\n */\nfunction delphiToDate(days: number): Date {\n const ms = days * 24 * 60 * 60 * 1000;\n return new Date(DELPHI_EPOCH.getTime() + ms);\n}\n\n/**\n * Converts Date to Delphi timestamp (days since epoch)\n */\nfunction dateToDelphiDays(date: Date): number {\n const ms = date.getTime() - DELPHI_EPOCH.getTime();\n return ms / (24 * 60 * 60 * 1000);\n}\n\n/**\n * Reads project time info from a parsed FLP\n * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)\n */\nexport function readProjectTimeInfo(parsed: ParsedFlp): ProjectTimeInfo {\n const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);\n\n if (!timestampEvent || timestampEvent.payload.length < 16) {\n return {\n creationDate: null,\n workTimeSeconds: null,\n };\n }\n\n const reader = new BinaryReader(timestampEvent.payload);\n const createdOnDays = reader.readF64LE();\n const timeSpentDays = reader.readF64LE();\n\n return {\n creationDate: delphiToDate(createdOnDays),\n workTimeSeconds: timeSpentDays * 24 * 60 * 60, // Convert days to seconds\n };\n}\n\n/**\n * Writes project time info to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n * \n * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)\n */\nexport function writeProjectTimeInfo(\n parsed: ParsedFlp,\n info: Partial<ProjectTimeInfo>\n): ParsedFlp {\n // Read current values to preserve unmodified fields\n const current = readProjectTimeInfo(parsed);\n\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.PROJECT_TIMESTAMP) {\n return event;\n }\n\n const writer = new BinaryWriter();\n\n // Write creation date\n if (info.creationDate !== undefined && info.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(info.creationDate));\n } else if (current.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(current.creationDate));\n } else {\n writer.writeF64LE(0);\n }\n\n // Write work time\n if (info.workTimeSeconds !== undefined && info.workTimeSeconds !== null) {\n writer.writeF64LE(info.workTimeSeconds / (24 * 60 * 60)); // Convert seconds to days\n } else if (current.workTimeSeconds !== null) {\n writer.writeF64LE(current.workTimeSeconds / (24 * 60 * 60));\n } else {\n writer.writeF64LE(0);\n }\n\n return {\n ...event,\n payload: writer.toBuffer(),\n };\n });\n}\n\n// ============================================================================\n// Samples\n// ============================================================================\n\n/**\n * Reference to a sample in the project\n */\nexport interface SampleRef {\n /** Index of the event in the events array */\n eventIndex: number;\n /** File path of the sample */\n path: string;\n}\n\n/**\n * Lists all samples in the project\n */\nexport function listSamples(parsed: ParsedFlp): SampleRef[] {\n const samples: SampleRef[] = [];\n\n parsed.events.forEach((event: FlpEvent, index: number) => {\n if (event.id === EVENT_ID.CHANNEL_SAMPLE_PATH) {\n const path = getEventString(event, parsed.useUnicode);\n if (path && path.length > 0) {\n samples.push({\n eventIndex: index,\n path,\n });\n }\n }\n });\n\n return samples;\n}\n\n/**\n * Rewrites sample paths using a mapper function\n */\nexport function rewriteSamplePaths(\n parsed: ParsedFlp,\n mapper: (oldPath: string) => string\n): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.CHANNEL_SAMPLE_PATH) {\n return event;\n }\n\n const oldPath = getEventString(event, parsed.useUnicode);\n if (!oldPath || oldPath.length === 0) {\n return event;\n }\n\n const newPath = mapper(oldPath);\n if (newPath === oldPath) {\n return event;\n }\n\n return {\n ...event,\n payload: createTextPayload(newPath, parsed.useUnicode),\n };\n });\n}\n\n// ============================================================================\n// Plugins (VST)\n// ============================================================================\n\n/**\n * Reference to a plugin in the project\n */\nexport interface PluginRef {\n /** Plugin name (may be null if not available) */\n name: string | null;\n /** Plugin vendor/provider (may be null if not stored in file) */\n vendor: string | null;\n}\n\n/**\n * VST Plugin Event internal IDs (from plugin.py VSTPluginEvent)\n */\nconst VST_EVENT_ID = {\n MIDI: 1,\n FLAGS: 2,\n IO: 30,\n INPUTS: 31,\n OUTPUTS: 32,\n PLUGIN_INFO: 50,\n FOUR_CC: 51,\n GUID: 52,\n STATE: 53,\n NAME: 54,\n PLUGIN_PATH: 55,\n VENDOR: 56,\n} as const;\n\n/**\n * Parses a VST plugin data event to extract name and vendor\n * Based on VSTPluginEvent structure in PyFLP\n */\nfunction parseVstPluginData(payload: Buffer): { name: string | null; vendor: string | null } {\n if (payload.length < 4) {\n return { name: null, vendor: null };\n }\n\n const reader = new BinaryReader(payload);\n\n // First 4 bytes: wrapper marker.\n // Historically seen values include 8/10/11, but newer FL versions can use\n // other markers (e.g. 12). We read and ignore the exact value here because\n // this parser is only called for \"Fruity Wrapper\" plugin data.\n const typeMarker = reader.readU32LE();\n void typeMarker;\n\n let name: string | null = null;\n let vendor: string | null = null;\n\n // Parse sub-events\n while (reader.remaining() >= 12) {\n // At minimum: 4 (id) + 8 (size)\n const subEventId = reader.readU32LE();\n const dataSize = reader.readU32LE(); // Lower 32 bits of size\n const dataSizeHigh = reader.readU32LE(); // Upper 32 bits (usually 0)\n\n // Combine for 64-bit size (though in practice it's always small)\n const actualSize = dataSize + dataSizeHigh * 0x100000000;\n\n if (actualSize > reader.remaining()) {\n break;\n }\n\n const data = reader.readBytes(actualSize);\n\n // Extract name and vendor\n if (subEventId === VST_EVENT_ID.NAME && actualSize > 0) {\n name = data.toString(\"utf8\").replace(/\\0/g, \"\");\n } else if (subEventId === VST_EVENT_ID.VENDOR && actualSize > 0) {\n vendor = data.toString(\"utf8\").replace(/\\0/g, \"\");\n }\n }\n\n return { name, vendor };\n}\n\n/**\n * Lists all plugins (VST and native) in the project\n * Returns plugin name and vendor where available\n */\nexport function listPlugins(parsed: ParsedFlp): PluginRef[] {\n const plugins: PluginRef[] = [];\n const seenPlugins = new Set<string>();\n\n // Track current channel context\n let currentInternalName: string | null = null;\n let currentPluginName: string | null = null;\n\n for (const event of parsed.events) {\n // Track channel new events as boundaries\n if (event.id === EVENT_ID.CHANNEL_NEW) {\n // Reset context for new channel\n if (currentInternalName || currentPluginName) {\n // Save previous channel's plugin if it wasn't a VST (no plugin data)\n const key = `${currentInternalName ?? \"\"}:${currentPluginName ?? \"\"}`;\n if (!seenPlugins.has(key) && currentInternalName && currentInternalName !== \"Fruity Wrapper\") {\n seenPlugins.add(key);\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null, // Native plugins don't store vendor\n });\n }\n }\n currentInternalName = null;\n currentPluginName = null;\n }\n\n // Track internal name (identifies plugin type)\n if (event.id === EVENT_ID.PLUGIN_INTERNAL_NAME) {\n currentInternalName = getEventString(event, parsed.useUnicode);\n }\n\n // Track display name\n if (event.id === EVENT_ID.PLUGIN_NAME) {\n currentPluginName = getEventString(event, parsed.useUnicode);\n }\n\n // Parse VST plugin data\n if (event.id === EVENT_ID.PLUGIN_DATA && currentInternalName === \"Fruity Wrapper\") {\n const vstData = parseVstPluginData(event.payload);\n if (vstData.name) {\n const key = `vst:${vstData.name}:${vstData.vendor ?? \"\"}`;\n if (!seenPlugins.has(key)) {\n seenPlugins.add(key);\n plugins.push({\n name: vstData.name,\n vendor: vstData.vendor,\n });\n }\n }\n // Reset context after processing VST\n currentInternalName = null;\n currentPluginName = null;\n }\n }\n\n // Handle last channel if any\n if (currentInternalName && currentInternalName !== \"Fruity Wrapper\") {\n const key = `${currentInternalName}:${currentPluginName ?? \"\"}`;\n if (!seenPlugins.has(key)) {\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null,\n });\n }\n }\n\n return plugins;\n}\n\n// ============================================================================\n// FL Version\n// ============================================================================\n\n/**\n * Gets the FL Studio version string from the project\n */\nexport function getFlVersion(parsed: ParsedFlp): string {\n return parsed.flVersion;\n}\n\n/**\n * Gets the PPQ (Pulses Per Quarter note) from the header\n */\nexport function getPPQ(parsed: ParsedFlp): number {\n // PPQ is at offset 12-13 in the header (bytes 12-13 of the 14-byte FLhd chunk)\n const reader = new BinaryReader(parsed.headerChunkBytes);\n reader.seek(12);\n return reader.readU16LE();\n}\n"],"mappings":"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","names":["protobuf","import_smart_buffer","import_minimal","protobuf","headerSize"]}
1
+ {"version":3,"sources":["../index.ts","../src/generated/events.generated.ts","../src/io/BinaryReader.ts","../src/io/BinaryWriter.ts","../src/parser/FlpParser.ts","../src/api/ProjectApi.ts"],"sourcesContent":["/**\n * ts-flp - A minimal TypeScript port of PyFLP for FL Studio .flp files\n *\n * This library provides conservative (non-destructive) parsing and patching\n * of FL Studio project files. It preserves all unknown data byte-for-byte.\n *\n * Features:\n * - Read and modify project metadata (name, description, artist, BPM)\n * - Read and modify timestamps (creation date, work time)\n * - List and remap sample paths\n * - List VST plugins (name and vendor)\n * - Conservative round-trip: unchanged files are byte-identical\n *\n * @example\n * ```typescript\n * import { parseFlp, serializeFlp, readProjectMeta, writeProjectMeta } from \"ts-flp\";\n * import * as fs from \"fs\";\n *\n * // Parse an FLP file\n * const buffer = fs.readFileSync(\"project.flp\");\n * const parsed = parseFlp(buffer);\n *\n * // Read metadata\n * const meta = readProjectMeta(parsed);\n * console.log(meta.name, meta.bpm);\n *\n * // Modify and save\n * const modified = writeProjectMeta(parsed, { bpm: 140 });\n * fs.writeFileSync(\"project_modified.flp\", serializeFlp(modified));\n * ```\n */\n\n// Re-export parser functions\nexport {\n createEvent, createNumberPayload, createTextPayload, findEvents,\n findFirstEvent, getEventNumber, getEventString, parseFlp, patchEvents, serializeFlp, type FlpEvent, type ParsedFlp\n} from \"./src/parser/FlpParser.js\";\n\n// Re-export API functions\nexport {\n getFlVersion,\n getPPQ,\n listPlugins,\n listSamples,\n readProjectMeta,\n readProjectTimeInfo,\n rewriteSamplePaths,\n writeProjectMeta,\n writeProjectTimeInfo,\n type PluginRef,\n type ProjectMeta,\n type ProjectTimeInfo,\n type SampleRef\n} from \"./src/api/ProjectApi.js\";\n\n// Re-export event constants\nexport { EVENT_ID, EVENT_KIND, getEventFixedSize, getEventKind } from \"./src/generated/events.generated.js\";\n\n// Re-export IO utilities for advanced usage\nexport { BinaryReader } from \"./src/io/BinaryReader.js\";\nexport { BinaryWriter, encodeVarInt, varIntSize } from \"./src/io/BinaryWriter.js\";\n\n","/**\r\n * Auto-generated event constants from PyFLP\r\n * FL Studio .flp file event IDs and types\r\n *\r\n * Event ID ranges:\r\n * - BYTE (0-63): 1 byte data\r\n * - WORD (64-127): 2 bytes data\r\n * - DWORD (128-191): 4 bytes data\r\n * - TEXT (192-207): Variable length strings (+ some IDs in DATA range)\r\n * - DATA (208+): Variable length binary data\r\n */\r\n\r\n// Base offsets for event ID ranges\r\nexport const BYTE = 0;\r\nexport const WORD = 64;\r\nexport const DWORD = 128;\r\nexport const TEXT = 192;\r\nexport const DATA = 208;\r\n\r\n// IDs for TEXT events that use UTF-16LE in newer FL versions\r\nexport const NEW_TEXT_IDS = [\r\n TEXT + 49, // ArrangementID.Name\r\n TEXT + 39, // DisplayGroupID.Name\r\n TEXT + 47, // TrackID.Name\r\n] as const;\r\n\r\n/**\r\n * Event IDs used by FL Studio project files\r\n * Based on PyFLP's event definitions\r\n */\r\nexport const EVENT_ID = {\r\n // === Project IDs (from project.py) ===\r\n PROJECT_LOOP_ACTIVE: 9,\r\n PROJECT_SHOW_INFO: 10,\r\n PROJECT__VOLUME: 12,\r\n PROJECT_PAN_LAW: 23,\r\n PROJECT_LICENSED: 28,\r\n PROJECT__TEMPO_COARSE: WORD + 2, // 66\r\n PROJECT_PITCH: WORD + 16, // 80\r\n PROJECT__TEMPO_FINE: WORD + 29, // 93\r\n PROJECT_CUR_GROUP_ID: DWORD + 18,\r\n PROJECT_TEMPO: DWORD + 28,\r\n PROJECT_FL_BUILD: DWORD + 31,\r\n PROJECT_TITLE: TEXT + 2, // 194\r\n PROJECT_COMMENTS: TEXT + 3, // 195\r\n PROJECT_URL: TEXT + 5, // 197\r\n PROJECT__RTF_COMMENTS: TEXT + 6, // 198\r\n PROJECT_FL_VERSION: TEXT + 7, // 199\r\n PROJECT_LICENSEE: TEXT + 8, // 200\r\n PROJECT_DATA_PATH: TEXT + 10, // 202\r\n PROJECT_GENRE: TEXT + 14, // 206\r\n PROJECT_ARTISTS: TEXT + 15, // 207\r\n PROJECT_TIMESTAMP: DATA + 29, // 237\r\n\r\n // === Channel IDs (from channel.py) ===\r\n CHANNEL_IS_ENABLED: 0,\r\n CHANNEL__VOL_BYTE: 2,\r\n CHANNEL__PAN_BYTE: 3,\r\n CHANNEL_ZIPPED: 15,\r\n CHANNEL_PING_PONG_LOOP: 20,\r\n CHANNEL_TYPE: 21,\r\n CHANNEL_ROUTED_TO: 22,\r\n CHANNEL_IS_LOCKED: 32,\r\n CHANNEL_NEW: WORD,\r\n CHANNEL_FREQ_TILT: WORD + 5, // 69\r\n CHANNEL_FX_FLAGS: WORD + 6, // 70\r\n CHANNEL_CUTOFF: WORD + 7, // 71\r\n CHANNEL__VOL_WORD: WORD + 8, // 72\r\n CHANNEL__PAN_WORD: WORD + 9, // 73\r\n CHANNEL_PREAMP: WORD + 10, // 74\r\n CHANNEL_FADE_OUT: WORD + 11, // 75\r\n CHANNEL_FADE_IN: WORD + 12, // 76\r\n CHANNEL_RESONANCE: WORD + 19, // 83\r\n CHANNEL_STEREO_DELAY: WORD + 21, // 85\r\n CHANNEL_POGO: WORD + 22, // 86\r\n CHANNEL_TIME_SHIFT: WORD + 25, // 89\r\n CHANNEL_CHILDREN: WORD + 30, // 94\r\n CHANNEL_SWING: WORD + 33, // 97\r\n CHANNEL_RING_MOD: DWORD + 3,\r\n CHANNEL_CUT_GROUP: DWORD + 4,\r\n CHANNEL_ROOT_NOTE: DWORD + 7,\r\n CHANNEL_DELAY_MOD_XY: DWORD + 10,\r\n CHANNEL_REVERB: DWORD + 11,\r\n CHANNEL__STRETCH_TIME: DWORD + 12,\r\n CHANNEL_FINE_TUNE: DWORD + 14,\r\n CHANNEL_SAMPLER_FLAGS: DWORD + 15,\r\n CHANNEL_LAYER_FLAGS: DWORD + 16,\r\n CHANNEL_GROUP_NUM: DWORD + 17,\r\n CHANNEL_AU_SAMPLE_RATE: DWORD + 25,\r\n CHANNEL__NAME: TEXT,\r\n CHANNEL_SAMPLE_PATH: TEXT + 4, // 196\r\n CHANNEL_DELAY: DATA + 1, // 209\r\n CHANNEL_PARAMETERS: DATA + 7, // 215\r\n CHANNEL_ENVELOPE_LFO: DATA + 10, // 218\r\n CHANNEL_LEVELS: DATA + 11, // 219\r\n CHANNEL_POLYPHONY: DATA + 13, // 221\r\n CHANNEL_TRACKING: DATA + 20, // 228\r\n CHANNEL_LEVEL_ADJUSTS: DATA + 21, // 229\r\n CHANNEL_AUTOMATION: DATA + 26, // 234\r\n\r\n // === Plugin IDs (from plugin.py) ===\r\n PLUGIN_COLOR: DWORD,\r\n PLUGIN_ICON: DWORD + 27,\r\n PLUGIN_INTERNAL_NAME: TEXT + 9, // 201\r\n PLUGIN_NAME: TEXT + 11, // 203\r\n PLUGIN_WRAPPER: DATA + 4, // 212\r\n PLUGIN_DATA: DATA + 5, // 213\r\n\r\n // === Display Group IDs ===\r\n DISPLAY_GROUP_NAME: TEXT + 39, // 231\r\n\r\n // === Rack IDs ===\r\n RACK_SWING: 11,\r\n RACK__FIT_TO_STEPS: 13,\r\n RACK_WINDOW_HEIGHT: DWORD + 5,\r\n\r\n // === Mixer IDs (from mixer.py) ===\r\n MIXER_SLOT_INDEX: WORD + 34, // 98\r\n MIXER_INSERT_ICON: WORD + 31, // 95\r\n MIXER_INSERT_OUTPUT: DWORD + 19,\r\n MIXER_INSERT_COLOR: DWORD + 21,\r\n MIXER_INSERT_INPUT: DWORD + 26,\r\n MIXER_INSERT_NAME: TEXT + 12, // 204\r\n MIXER_INSERT_ROUTING: DATA + 27, // 235\r\n MIXER_INSERT_FLAGS: DATA + 28, // 236\r\n} as const;\r\n\r\n/**\r\n * Event data type classification\r\n */\r\nexport type EventKind =\r\n | 'u8'\r\n | 'i8'\r\n | 'u16'\r\n | 'i16'\r\n | 'u32'\r\n | 'i32'\r\n | 'f32'\r\n | 'text'\r\n | 'data'\r\n | 'unknown';\r\n\r\n/**\r\n * Maps event IDs to their data type\r\n * Used for proper serialization/deserialization\r\n */\r\nexport const EVENT_KIND: Record<number, EventKind> = {\r\n // Project events\r\n [EVENT_ID.PROJECT_LOOP_ACTIVE]: 'u8',\r\n [EVENT_ID.PROJECT_SHOW_INFO]: 'u8',\r\n [EVENT_ID.PROJECT__VOLUME]: 'u8',\r\n [EVENT_ID.PROJECT_PAN_LAW]: 'u8',\r\n [EVENT_ID.PROJECT_LICENSED]: 'u8',\r\n [EVENT_ID.PROJECT__TEMPO_COARSE]: 'u16',\r\n [EVENT_ID.PROJECT_PITCH]: 'i16',\r\n [EVENT_ID.PROJECT__TEMPO_FINE]: 'u16',\r\n [EVENT_ID.PROJECT_CUR_GROUP_ID]: 'i32',\r\n [EVENT_ID.PROJECT_TEMPO]: 'u32',\r\n [EVENT_ID.PROJECT_FL_BUILD]: 'u32',\r\n [EVENT_ID.PROJECT_TITLE]: 'text',\r\n [EVENT_ID.PROJECT_COMMENTS]: 'text',\r\n [EVENT_ID.PROJECT_URL]: 'text',\r\n [EVENT_ID.PROJECT__RTF_COMMENTS]: 'text',\r\n [EVENT_ID.PROJECT_FL_VERSION]: 'text',\r\n [EVENT_ID.PROJECT_LICENSEE]: 'text',\r\n [EVENT_ID.PROJECT_DATA_PATH]: 'text',\r\n [EVENT_ID.PROJECT_GENRE]: 'text',\r\n [EVENT_ID.PROJECT_ARTISTS]: 'text',\r\n [EVENT_ID.PROJECT_TIMESTAMP]: 'data',\r\n\r\n // Channel events\r\n [EVENT_ID.CHANNEL_IS_ENABLED]: 'u8',\r\n [EVENT_ID.CHANNEL__VOL_BYTE]: 'u8',\r\n [EVENT_ID.CHANNEL__PAN_BYTE]: 'u8',\r\n [EVENT_ID.CHANNEL_ZIPPED]: 'u8',\r\n [EVENT_ID.CHANNEL_PING_PONG_LOOP]: 'u8',\r\n [EVENT_ID.CHANNEL_TYPE]: 'u8',\r\n [EVENT_ID.CHANNEL_ROUTED_TO]: 'i8',\r\n [EVENT_ID.CHANNEL_IS_LOCKED]: 'u8',\r\n [EVENT_ID.CHANNEL_NEW]: 'u16',\r\n [EVENT_ID.CHANNEL_FREQ_TILT]: 'u16',\r\n [EVENT_ID.CHANNEL_FX_FLAGS]: 'u16',\r\n [EVENT_ID.CHANNEL_CUTOFF]: 'u16',\r\n [EVENT_ID.CHANNEL__VOL_WORD]: 'u16',\r\n [EVENT_ID.CHANNEL__PAN_WORD]: 'u16',\r\n [EVENT_ID.CHANNEL_PREAMP]: 'u16',\r\n [EVENT_ID.CHANNEL_FADE_OUT]: 'u16',\r\n [EVENT_ID.CHANNEL_FADE_IN]: 'u16',\r\n [EVENT_ID.CHANNEL_RESONANCE]: 'u16',\r\n [EVENT_ID.CHANNEL_STEREO_DELAY]: 'u16',\r\n [EVENT_ID.CHANNEL_POGO]: 'u16',\r\n [EVENT_ID.CHANNEL_TIME_SHIFT]: 'u16',\r\n [EVENT_ID.CHANNEL_CHILDREN]: 'u16',\r\n [EVENT_ID.CHANNEL_SWING]: 'u16',\r\n [EVENT_ID.CHANNEL_RING_MOD]: 'u32',\r\n [EVENT_ID.CHANNEL_CUT_GROUP]: 'u32',\r\n [EVENT_ID.CHANNEL_ROOT_NOTE]: 'u32',\r\n [EVENT_ID.CHANNEL_DELAY_MOD_XY]: 'u32',\r\n [EVENT_ID.CHANNEL_REVERB]: 'u32',\r\n [EVENT_ID.CHANNEL__STRETCH_TIME]: 'f32',\r\n [EVENT_ID.CHANNEL_FINE_TUNE]: 'i32',\r\n [EVENT_ID.CHANNEL_SAMPLER_FLAGS]: 'u32',\r\n [EVENT_ID.CHANNEL_LAYER_FLAGS]: 'u32',\r\n [EVENT_ID.CHANNEL_GROUP_NUM]: 'i32',\r\n [EVENT_ID.CHANNEL_AU_SAMPLE_RATE]: 'u32',\r\n [EVENT_ID.CHANNEL__NAME]: 'text',\r\n [EVENT_ID.CHANNEL_SAMPLE_PATH]: 'text',\r\n [EVENT_ID.CHANNEL_DELAY]: 'data',\r\n [EVENT_ID.CHANNEL_PARAMETERS]: 'data',\r\n [EVENT_ID.CHANNEL_ENVELOPE_LFO]: 'data',\r\n [EVENT_ID.CHANNEL_LEVELS]: 'data',\r\n [EVENT_ID.CHANNEL_POLYPHONY]: 'data',\r\n [EVENT_ID.CHANNEL_TRACKING]: 'data',\r\n [EVENT_ID.CHANNEL_LEVEL_ADJUSTS]: 'data',\r\n [EVENT_ID.CHANNEL_AUTOMATION]: 'data',\r\n\r\n // Plugin events\r\n [EVENT_ID.PLUGIN_COLOR]: 'u32',\r\n [EVENT_ID.PLUGIN_ICON]: 'u32',\r\n [EVENT_ID.PLUGIN_INTERNAL_NAME]: 'text',\r\n [EVENT_ID.PLUGIN_NAME]: 'text',\r\n [EVENT_ID.PLUGIN_WRAPPER]: 'data',\r\n [EVENT_ID.PLUGIN_DATA]: 'data',\r\n\r\n // Display Group events\r\n [EVENT_ID.DISPLAY_GROUP_NAME]: 'data',\r\n\r\n // Rack events\r\n [EVENT_ID.RACK_SWING]: 'u8',\r\n [EVENT_ID.RACK__FIT_TO_STEPS]: 'u8',\r\n [EVENT_ID.RACK_WINDOW_HEIGHT]: 'u32',\r\n\r\n // Mixer events\r\n [EVENT_ID.MIXER_SLOT_INDEX]: 'u16',\r\n [EVENT_ID.MIXER_INSERT_ICON]: 'i16',\r\n [EVENT_ID.MIXER_INSERT_OUTPUT]: 'i32',\r\n [EVENT_ID.MIXER_INSERT_COLOR]: 'u32',\r\n [EVENT_ID.MIXER_INSERT_INPUT]: 'i32',\r\n [EVENT_ID.MIXER_INSERT_NAME]: 'text',\r\n [EVENT_ID.MIXER_INSERT_ROUTING]: 'data',\r\n [EVENT_ID.MIXER_INSERT_FLAGS]: 'data',\r\n};\r\n\r\n/**\r\n * Determines the event kind based on ID\r\n * Falls back to range-based detection if not explicitly mapped\r\n */\r\nexport function getEventKind(eventId: number): EventKind {\r\n // Check explicit mapping first\r\n const mapped = EVENT_KIND[eventId];\r\n if (mapped !== undefined) {\r\n return mapped;\r\n }\r\n\r\n // Fall back to range-based detection\r\n if (eventId < WORD) {\r\n return 'u8';\r\n } else if (eventId < DWORD) {\r\n return 'u16';\r\n } else if (eventId < TEXT) {\r\n return 'u32';\r\n } else if (eventId < DATA || NEW_TEXT_IDS.includes(eventId as 241 | 231 | 239)) {\r\n return 'text';\r\n } else {\r\n return 'data';\r\n }\r\n}\r\n\r\n/**\r\n * Returns the fixed size for BYTE/WORD/DWORD events\r\n * Returns -1 for variable-length events (TEXT/DATA)\r\n */\r\nexport function getEventFixedSize(eventId: number): number {\r\n if (eventId < WORD) {\r\n return 1;\r\n } else if (eventId < DWORD) {\r\n return 2;\r\n } else if (eventId < TEXT) {\r\n return 4;\r\n }\r\n return -1; // Variable length\r\n}\r\n","/**\n * Binary reader for FL Studio .flp files\n * Uses smart-buffer for binary reading and protobufjs for VarInt decoding\n */\n\nimport protobuf from \"protobufjs/minimal.js\";\nimport { SmartBuffer } from \"smart-buffer\";\n\nexport class BinaryReader {\n private sb: SmartBuffer;\n private source: Buffer;\n\n constructor(buffer: Buffer) {\n this.source = buffer;\n this.sb = SmartBuffer.fromBuffer(buffer);\n }\n\n /**\n * Returns current read position\n */\n tell(): number {\n return this.sb.readOffset;\n }\n\n /**\n * Seeks to an absolute position\n */\n seek(pos: number): void {\n this.sb.readOffset = pos;\n }\n\n /**\n * Seeks relative to current position\n */\n skip(offset: number): void {\n this.sb.readOffset += offset;\n }\n\n /**\n * Returns total buffer length\n */\n length(): number {\n return this.sb.length;\n }\n\n /**\n * Returns remaining bytes to read\n */\n remaining(): number {\n return this.sb.remaining();\n }\n\n /**\n * Returns true if there are more bytes to read\n */\n hasMore(): boolean {\n return this.sb.remaining() > 0;\n }\n\n /**\n * Reads an unsigned 8-bit integer\n */\n readU8(): number {\n return this.sb.readUInt8();\n }\n\n /**\n * Reads a signed 8-bit integer\n */\n readI8(): number {\n return this.sb.readInt8();\n }\n\n /**\n * Reads an unsigned 16-bit integer (little-endian)\n */\n readU16LE(): number {\n return this.sb.readUInt16LE();\n }\n\n /**\n * Reads a signed 16-bit integer (little-endian)\n */\n readI16LE(): number {\n return this.sb.readInt16LE();\n }\n\n /**\n * Reads an unsigned 32-bit integer (little-endian)\n */\n readU32LE(): number {\n return this.sb.readUInt32LE();\n }\n\n /**\n * Reads a signed 32-bit integer (little-endian)\n */\n readI32LE(): number {\n return this.sb.readInt32LE();\n }\n\n /**\n * Reads a 32-bit float (little-endian)\n */\n readF32LE(): number {\n return this.sb.readFloatLE();\n }\n\n /**\n * Reads a 64-bit float (little-endian)\n */\n readF64LE(): number {\n return this.sb.readDoubleLE();\n }\n\n /**\n * Reads raw bytes\n */\n readBytes(length: number): Buffer {\n return this.sb.readBuffer(length);\n }\n\n /**\n * Reads a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n readVarInt(): number {\n // Get remaining buffer from current position\n const remainingBuffer = this.source.subarray(this.sb.readOffset);\n const reader = protobuf.Reader.create(remainingBuffer);\n const value = reader.uint32();\n // Advance our position by how many bytes protobufjs consumed\n this.sb.readOffset += reader.pos;\n return value;\n }\n\n /**\n * Reads an ASCII string (null-terminated)\n */\n readAsciiString(length: number): string {\n const str = this.sb.readString(length, \"ascii\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Reads a UTF-16LE string (null-terminated)\n */\n readUtf16LEString(length: number): string {\n const bytes = this.sb.readBuffer(length);\n const str = bytes.toString(\"utf16le\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Peeks at bytes without advancing position\n */\n peek(length: number): Buffer {\n const pos = this.sb.readOffset;\n const data = this.sb.readBuffer(length);\n this.sb.readOffset = pos;\n return data;\n }\n\n /**\n * Peeks at a single byte without advancing position\n */\n peekU8(): number {\n const pos = this.sb.readOffset;\n const value = this.sb.readUInt8();\n this.sb.readOffset = pos;\n return value;\n }\n\n /**\n * Returns the internal buffer\n */\n toBuffer(): Buffer {\n return this.source;\n }\n}\n","/**\n * Binary writer for FL Studio .flp files\n * Uses smart-buffer for binary writing and protobufjs for VarInt encoding\n */\n\nimport { SmartBuffer } from \"smart-buffer\";\nimport protobuf from \"protobufjs/minimal.js\";\n\nexport class BinaryWriter {\n private sb: SmartBuffer;\n\n constructor() {\n this.sb = new SmartBuffer();\n }\n\n /**\n * Returns current write position (total bytes written)\n */\n tell(): number {\n return this.sb.writeOffset;\n }\n\n /**\n * Writes an unsigned 8-bit integer\n */\n writeU8(value: number): void {\n this.sb.writeUInt8(value & 0xff);\n }\n\n /**\n * Writes a signed 8-bit integer\n */\n writeI8(value: number): void {\n this.sb.writeInt8(value);\n }\n\n /**\n * Writes an unsigned 16-bit integer (little-endian)\n */\n writeU16LE(value: number): void {\n this.sb.writeUInt16LE(value & 0xffff);\n }\n\n /**\n * Writes a signed 16-bit integer (little-endian)\n */\n writeI16LE(value: number): void {\n this.sb.writeInt16LE(value);\n }\n\n /**\n * Writes an unsigned 32-bit integer (little-endian)\n */\n writeU32LE(value: number): void {\n this.sb.writeUInt32LE(value >>> 0);\n }\n\n /**\n * Writes a signed 32-bit integer (little-endian)\n */\n writeI32LE(value: number): void {\n this.sb.writeInt32LE(value);\n }\n\n /**\n * Writes a 32-bit float (little-endian)\n */\n writeF32LE(value: number): void {\n this.sb.writeFloatLE(value);\n }\n\n /**\n * Writes a 64-bit float (little-endian)\n */\n writeF64LE(value: number): void {\n this.sb.writeDoubleLE(value);\n }\n\n /**\n * Writes raw bytes\n */\n writeBytes(data: Buffer): void {\n this.sb.writeBuffer(data);\n }\n\n /**\n * Writes a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n writeVarInt(value: number): void {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n const encoded = writer.finish();\n this.sb.writeBuffer(Buffer.from(encoded));\n }\n\n /**\n * Writes an ASCII string (null-terminated)\n */\n writeAsciiString(str: string): void {\n this.sb.writeString(str + \"\\0\", \"ascii\");\n }\n\n /**\n * Writes a UTF-16LE string (null-terminated)\n */\n writeUtf16LEString(str: string): void {\n const buf = Buffer.from(str + \"\\0\", \"utf16le\");\n this.sb.writeBuffer(buf);\n }\n\n /**\n * Gets the final buffer\n */\n toBuffer(): Buffer {\n return this.sb.toBuffer();\n }\n}\n\n/**\n * Encodes a VarInt to a Buffer using protobufjs (standalone utility)\n */\nexport function encodeVarInt(value: number): Buffer {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n return Buffer.from(writer.finish());\n}\n\n/**\n * Calculates the byte size needed for a VarInt\n */\nexport function varIntSize(value: number): number {\n if (value < 0x80) return 1;\n if (value < 0x4000) return 2;\n if (value < 0x200000) return 3;\n if (value < 0x10000000) return 4;\n return 5;\n}\n","/**\n * FL Studio .flp file parser and serializer\n * Conservative patch approach: preserves all unknown data byte-for-byte\n *\n * Uses look-ahead scoring to handle events in the DWORD range (128-191)\n * that may use either fixed 4-byte encoding or VarInt-encoded variable\n * length payloads. For each unknown event in this range, both\n * interpretations are scored by parsing ahead ~200 bytes and counting\n * how many TEXT/DATA events (with valid VarInt sizes) are reachable.\n * The interpretation producing more valid TEXT/DATA events wins.\n */\n\nimport {\n DWORD,\n EVENT_ID,\n EVENT_KIND,\n TEXT,\n WORD,\n getEventKind,\n} from '../generated/events.generated.js';\nimport { BinaryReader } from '../io/BinaryReader.js';\nimport { BinaryWriter } from '../io/BinaryWriter.js';\n\n/** Event IDs in the DWORD range (128-191) that are explicitly mapped in EVENT_KIND. */\nconst KNOWN_DWORD_IDS: ReadonlySet<number> = new Set(\n Object.keys(EVENT_KIND)\n .map(Number)\n .filter((id) => id >= DWORD && id < TEXT),\n);\n\n/**\n * Represents a single event in an FLP file\n * Stores raw bytes to enable conservative patching\n */\nexport interface FlpEvent {\n /** Event ID (0-255) */\n id: number;\n /** Data type classification */\n kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'text' | 'data' | 'unknown';\n /** Original header bytes (ID + size for variable-length events) */\n header: Buffer;\n /** Event payload data */\n payload: Buffer;\n}\n\n/**\n * Represents a parsed FLP file\n * Contains all data needed for conservative round-trip\n */\nexport interface ParsedFlp {\n /** FLhd chunk (header) - 14 bytes total */\n headerChunkBytes: Buffer;\n /** FLdt chunk header (magic + size placeholder) - 8 bytes */\n fldtHeaderBytes: Buffer;\n /** All events from the FLdt chunk */\n events: FlpEvent[];\n /** Any trailing bytes after events (should be empty) */\n trailingBytes: Buffer | undefined;\n /** Detected FL version for string encoding */\n flVersion: string;\n /** Whether to use UTF-16LE for strings (FL >= 11.5) */\n useUnicode: boolean;\n}\n\n/**\n * Header structure constants\n */\nconst FLP_HEADER_MAGIC = 'FLhd';\nconst FLP_DATA_MAGIC = 'FLdt';\nconst FLP_HEADER_SIZE = 6; // Expected header chunk size\nconst LOOKAHEAD_BYTES = 200;\nconst LOOKAHEAD_VARINT_MAX = 100_000;\n\n/**\n * Reads a VarInt directly from a raw buffer at the given offset.\n * Returns `{ size, bytesRead }` or `{ size: -1, bytesRead: 0 }` on failure.\n */\nfunction readVarIntRaw(\n buf: Buffer,\n off: number,\n end: number,\n): { size: number; bytesRead: number } {\n let shift = 0;\n let size = 0;\n const start = off;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (off >= end) return { size: -1, bytesRead: 0 };\n const byte = buf[off]!;\n off++;\n size |= (byte & 0x7f) << shift;\n shift += 7;\n if (!(byte & 0x80)) break;\n }\n return { size, bytesRead: off - start };\n}\n\n/**\n * Scores an alignment hypothesis by parsing ahead from `off` for up to\n * `bytesToCheck` bytes. TEXT/DATA events with valid VarInt sizes are\n * the strongest signal of correct alignment; runs of low-value unknown\n * BYTE events (typical UTF-16-LE misread) are penalised.\n */\nfunction scoreAlignment(buf: Buffer, off: number, end: number, bytesToCheck: number): number {\n const limit = Math.min(off + bytesToCheck, end);\n let textDataCount = 0;\n let consecutiveSmallByte = 0;\n let maxConsecutiveSmallByte = 0;\n\n while (off < limit) {\n const id = buf[off]!;\n\n if (id < WORD) {\n off += 2;\n if (id < 32 && !KNOWN_DWORD_IDS.has(id)) {\n consecutiveSmallByte++;\n maxConsecutiveSmallByte = Math.max(maxConsecutiveSmallByte, consecutiveSmallByte);\n } else {\n consecutiveSmallByte = 0;\n }\n } else if (id < DWORD) {\n off += 3;\n consecutiveSmallByte = 0;\n } else if (id < TEXT) {\n off += 5;\n consecutiveSmallByte = 0;\n } else {\n const vi = readVarIntRaw(buf, off + 1, end);\n if (vi.size >= 0 && vi.size < LOOKAHEAD_VARINT_MAX && off + 1 + vi.bytesRead + vi.size <= end) {\n textDataCount++;\n off = off + 1 + vi.bytesRead + vi.size;\n } else {\n return -100;\n }\n consecutiveSmallByte = 0;\n }\n }\n\n return textDataCount * 10 - maxConsecutiveSmallByte * 3;\n}\n\n/**\n * Parses an FL Studio project file\n *\n * @param buffer - Raw file data\n * @returns Parsed FLP structure\n * @throws Error if file is corrupted or invalid\n */\nexport function parseFlp(buffer: Buffer): ParsedFlp {\n const reader = new BinaryReader(buffer);\n\n // Read FLhd header chunk\n const headerMagic = reader.readBytes(4).toString('ascii');\n if (headerMagic !== FLP_HEADER_MAGIC) {\n throw new Error(`Invalid header magic: expected 'FLhd', got '${headerMagic}'`);\n }\n\n const headerSize = reader.readU32LE();\n if (headerSize !== FLP_HEADER_SIZE) {\n throw new Error(`Invalid header size: expected ${FLP_HEADER_SIZE}, got ${headerSize}`);\n }\n\n const format = reader.readI16LE();\n const channelCount = reader.readU16LE();\n const ppq = reader.readU16LE();\n\n // Validate format (0 = normal project)\n if (format < -1 || format > 0x50) {\n throw new Error(`Invalid file format: ${format}`);\n }\n\n // Store complete header chunk (14 bytes)\n const headerChunkBytes = buffer.subarray(0, 14);\n\n // Read FLdt data chunk header\n const dataMagic = reader.readBytes(4).toString('ascii');\n if (dataMagic !== FLP_DATA_MAGIC) {\n throw new Error(`Invalid data magic: expected 'FLdt', got '${dataMagic}'`);\n }\n\n const eventsSize = reader.readU32LE();\n\n // Validate events size\n const expectedFileSize = 22 + eventsSize; // 14 (FLhd) + 8 (FLdt header) + events\n if (buffer.length !== expectedFileSize) {\n throw new Error(\n `Data chunk size mismatch: expected file size ${expectedFileSize}, got ${buffer.length}`,\n );\n }\n\n // Store FLdt header (8 bytes)\n const fldtHeaderBytes = buffer.subarray(14, 22);\n\n // Parse events\n const events: FlpEvent[] = [];\n let flVersion = '0.0.0';\n let useUnicode = false;\n\n const eventsStart = reader.tell();\n const eventsEnd = eventsStart + eventsSize;\n\n while (reader.tell() < eventsEnd) {\n const eventStart = reader.tell();\n const eventId = reader.readU8();\n\n let payloadSize: number;\n let headerSize: number;\n\n if (eventId < WORD) {\n payloadSize = 1;\n headerSize = 1;\n } else if (eventId < DWORD) {\n payloadSize = 2;\n headerSize = 1;\n } else if (eventId < TEXT) {\n if (KNOWN_DWORD_IDS.has(eventId)) {\n payloadSize = 4;\n headerSize = 1;\n } else {\n // Unknown DWORD-range event: use look-ahead to decide encoding\n const afterDword = eventStart + 1 + 4;\n const vi = readVarIntRaw(buffer, eventStart + 1, eventsEnd);\n\n if (vi.size < 0 || vi.size > LOOKAHEAD_VARINT_MAX || eventStart + 1 + vi.bytesRead + vi.size > eventsEnd) {\n payloadSize = 4;\n headerSize = 1;\n } else if (vi.size === 3) {\n // VarInt(3) produces the same total footprint as DWORD; prefer DWORD\n payloadSize = 4;\n headerSize = 1;\n } else {\n const afterVarInt = eventStart + 1 + vi.bytesRead + vi.size;\n const dwordScore = scoreAlignment(buffer, afterDword, eventsEnd, LOOKAHEAD_BYTES);\n const varIntScore = scoreAlignment(buffer, afterVarInt, eventsEnd, LOOKAHEAD_BYTES);\n\n if (varIntScore > dwordScore + 2) {\n payloadSize = vi.size;\n reader.seek(eventStart + 1 + vi.bytesRead);\n headerSize = 1 + vi.bytesRead;\n } else {\n payloadSize = 4;\n headerSize = 1;\n }\n }\n }\n } else {\n // TEXT / DATA: always VarInt-encoded size\n const sizeStart = reader.tell();\n payloadSize = reader.readVarInt();\n headerSize = 1 + (reader.tell() - sizeStart);\n }\n\n const payload = reader.readBytes(payloadSize);\n const header = buffer.subarray(eventStart, eventStart + headerSize);\n const kind = getEventKind(eventId);\n\n const event: FlpEvent = {\n id: eventId,\n kind,\n header: Buffer.from(header),\n payload: Buffer.from(payload),\n };\n\n events.push(event);\n\n // Detect FL version from the first FLVersion event\n if (eventId === EVENT_ID.PROJECT_FL_VERSION && flVersion === '0.0.0') {\n const candidate = payload.toString('ascii').replace(/\\0/g, '').trim();\n if (/^\\d+(\\.\\d+)+$/.test(candidate)) {\n flVersion = candidate;\n const parts = flVersion.split('.').map((p) => parseInt(p, 10));\n const major = parts[0] ?? 0;\n const minor = parts[1] ?? 0;\n useUnicode = parts.length >= 2 && (major > 11 || (major === 11 && minor >= 5));\n }\n }\n }\n\n // Check for trailing bytes\n let trailingBytes: Buffer | undefined;\n if (reader.remaining() > 0) {\n trailingBytes = reader.readBytes(reader.remaining());\n }\n\n return {\n headerChunkBytes: Buffer.from(headerChunkBytes),\n fldtHeaderBytes: Buffer.from(fldtHeaderBytes),\n events,\n trailingBytes,\n flVersion,\n useUnicode,\n };\n}\n\n/**\n * Serializes a parsed FLP back to a buffer\n * Recalculates FLdt chunk size if events have changed\n *\n * @param parsed - Parsed FLP structure\n * @returns Serialized buffer\n */\nexport function serializeFlp(parsed: ParsedFlp): Buffer {\n const writer = new BinaryWriter();\n\n // Write FLhd header chunk (preserved byte-for-byte)\n writer.writeBytes(parsed.headerChunkBytes);\n\n // Calculate total events size\n let eventsSize = 0;\n for (const event of parsed.events) {\n eventsSize += serializeEvent(event).length;\n }\n\n // Add trailing bytes if present\n if (parsed.trailingBytes) {\n eventsSize += parsed.trailingBytes.length;\n }\n\n // Write FLdt header with updated size\n writer.writeBytes(Buffer.from(FLP_DATA_MAGIC, 'ascii'));\n writer.writeU32LE(eventsSize);\n\n // Write all events\n for (const event of parsed.events) {\n writer.writeBytes(serializeEvent(event));\n }\n\n // Write trailing bytes if present\n if (parsed.trailingBytes) {\n writer.writeBytes(parsed.trailingBytes);\n }\n\n return writer.toBuffer();\n}\n\n/**\n * Serializes a single event to bytes.\n *\n * For unchanged events the original header is emitted verbatim, ensuring a\n * byte-identical round-trip. For newly created events (empty header) the\n * header is rebuilt: TEXT/DATA events get a VarInt-encoded size prefix,\n * BYTE/WORD/DWORD events get only the 1-byte ID.\n */\nfunction serializeEvent(event: FlpEvent): Buffer {\n const writer = new BinaryWriter();\n\n if (event.header.length > 0) {\n writer.writeBytes(event.header);\n } else {\n writer.writeU8(event.id);\n if (event.id >= TEXT) {\n writer.writeVarInt(event.payload.length);\n }\n }\n\n writer.writeBytes(event.payload);\n\n return writer.toBuffer();\n}\n\n/**\n * Creates a new event with the given ID and payload.\n *\n * For TEXT/DATA events (id >= 192) the header includes a VarInt size prefix.\n * For BYTE/WORD/DWORD events the header is a single byte (the event ID).\n */\nexport function createEvent(id: number, payload: Buffer): FlpEvent {\n const kind = getEventKind(id);\n\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(id);\n\n if (id >= TEXT) {\n headerWriter.writeVarInt(payload.length);\n }\n\n return {\n id,\n kind,\n header: headerWriter.toBuffer(),\n payload: Buffer.from(payload),\n };\n}\n\n/**\n * Applies a patcher function to all events\n * Returns a new ParsedFlp with patched events\n *\n * @param parsed - Original parsed FLP\n * @param patcher - Function that transforms each event\n * @returns New ParsedFlp with patched events\n */\nexport function patchEvents(\n parsed: ParsedFlp,\n patcher: (event: FlpEvent, index: number) => FlpEvent,\n): ParsedFlp {\n const patchedEvents = parsed.events.map((event, index) => {\n const patched = patcher(event, index);\n\n // If payload changed and the original event used a VarInt-encoded header\n // (header > 1 byte), recalculate the header with the new size.\n if (patched.payload !== event.payload && event.header.length > 1) {\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(patched.id);\n headerWriter.writeVarInt(patched.payload.length);\n return {\n ...patched,\n header: headerWriter.toBuffer(),\n };\n }\n\n return patched;\n });\n\n return {\n ...parsed,\n events: patchedEvents,\n };\n}\n\n/**\n * Finds all events with a specific ID\n */\nexport function findEvents(parsed: ParsedFlp, eventId: number): FlpEvent[] {\n return parsed.events.filter((e) => e.id === eventId);\n}\n\n/**\n * Finds the first event with a specific ID\n */\nexport function findFirstEvent(parsed: ParsedFlp, eventId: number): FlpEvent | undefined {\n return parsed.events.find((e) => e.id === eventId);\n}\n\n/**\n * Gets event payload as a string (handles encoding)\n */\nexport function getEventString(event: FlpEvent, useUnicode: boolean): string {\n if (event.kind !== 'text') {\n throw new Error(`Event ${event.id} is not a text event`);\n }\n\n if (useUnicode) {\n const str = event.payload.toString('utf16le');\n const nullPos = str.indexOf('\\0');\n return nullPos === -1 ? str : str.substring(0, nullPos);\n } else {\n const str = event.payload.toString('ascii');\n const nullPos = str.indexOf('\\0');\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n}\n\n/**\n * Creates a text payload with proper encoding\n */\nexport function createTextPayload(text: string, useUnicode: boolean): Buffer {\n if (useUnicode) {\n return Buffer.from(text + '\\0', 'utf16le');\n } else {\n return Buffer.from(text + '\\0', 'ascii');\n }\n}\n\n/**\n * Gets event payload as a number\n */\nexport function getEventNumber(event: FlpEvent): number {\n const reader = new BinaryReader(event.payload);\n\n switch (event.kind) {\n case 'u8':\n return reader.readU8();\n case 'i8':\n return reader.readI8();\n case 'u16':\n return reader.readU16LE();\n case 'i16':\n return reader.readI16LE();\n case 'u32':\n return reader.readU32LE();\n case 'i32':\n return reader.readI32LE();\n case 'f32':\n return reader.readF32LE();\n default:\n throw new Error(`Cannot get number from ${event.kind} event`);\n }\n}\n\n/**\n * Creates a numeric payload\n */\nexport function createNumberPayload(\n value: number,\n kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32',\n): Buffer {\n const writer = new BinaryWriter();\n\n switch (kind) {\n case 'u8':\n writer.writeU8(value);\n break;\n case 'i8':\n writer.writeI8(value);\n break;\n case 'u16':\n writer.writeU16LE(value);\n break;\n case 'i16':\n writer.writeI16LE(value);\n break;\n case 'u32':\n writer.writeU32LE(value);\n break;\n case 'i32':\n writer.writeI32LE(value);\n break;\n case 'f32':\n writer.writeF32LE(value);\n break;\n }\n\n return writer.toBuffer();\n}\n","/**\n * High-level API for reading and modifying FL Studio project files\n * Provides typed access to project metadata, samples, plugins, and time info\n */\n\nimport { EVENT_ID } from '../generated/events.generated.js';\nimport { BinaryReader } from '../io/BinaryReader.js';\nimport { BinaryWriter } from '../io/BinaryWriter.js';\nimport {\n createNumberPayload,\n createTextPayload,\n findFirstEvent,\n type FlpEvent,\n getEventNumber,\n getEventString,\n type ParsedFlp,\n patchEvents,\n} from '../parser/FlpParser.js';\n\n// ============================================================================\n// Project Metadata\n// ============================================================================\n\n/**\n * Project metadata (name, description, artist, genre, BPM)\n */\nexport interface ProjectMeta {\n name: string | null;\n description: string | null;\n artist: string | null;\n genre: string | null;\n bpm: number | null;\n}\n\n/**\n * Reads the BPM value from the parsed FLP.\n *\n * Primary: PROJECT_TEMPO (DWORD+28) stores BPM × 1000 as u32.\n * Legacy fallback: _TEMPO_COARSE (WORD+2) + _TEMPO_FINE (WORD+29).\n */\nfunction readBpm(parsed: ParsedFlp): number | null {\n const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);\n if (tempoEvent) {\n return getEventNumber(tempoEvent) / 1000;\n }\n\n const coarseEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_COARSE);\n if (coarseEvent) {\n let bpm = coarseEvent.payload.readUInt16LE(0);\n const fineEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_FINE);\n if (fineEvent) {\n bpm += fineEvent.payload.readUInt16LE(0) / 1000;\n }\n return bpm;\n }\n\n return null;\n}\n\n/**\n * Reads project metadata from a parsed FLP\n */\nexport function readProjectMeta(parsed: ParsedFlp): ProjectMeta {\n const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);\n const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);\n const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);\n const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);\n\n return {\n name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,\n description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,\n artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,\n genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,\n bpm: readBpm(parsed),\n };\n}\n\n/**\n * Writes project metadata to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n */\nexport function writeProjectMeta(parsed: ParsedFlp, meta: Partial<ProjectMeta>): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n // Patch title\n if (meta.name !== undefined && event.id === EVENT_ID.PROJECT_TITLE) {\n return {\n ...event,\n payload: createTextPayload(meta.name ?? '', parsed.useUnicode),\n };\n }\n\n // Patch comments/description\n if (meta.description !== undefined && event.id === EVENT_ID.PROJECT_COMMENTS) {\n return {\n ...event,\n payload: createTextPayload(meta.description ?? '', parsed.useUnicode),\n };\n }\n\n // Patch artists\n if (meta.artist !== undefined && event.id === EVENT_ID.PROJECT_ARTISTS) {\n return {\n ...event,\n payload: createTextPayload(meta.artist ?? '', parsed.useUnicode),\n };\n }\n\n // Patch genre\n if (meta.genre !== undefined && event.id === EVENT_ID.PROJECT_GENRE) {\n return {\n ...event,\n payload: createTextPayload(meta.genre ?? '', parsed.useUnicode),\n };\n }\n\n // Patch tempo (stored as BPM * 1000)\n if (meta.bpm && Math.abs(meta.bpm) > 0 && event.id === EVENT_ID.PROJECT_TEMPO) {\n return {\n ...event,\n payload: createNumberPayload(Math.round(Math.abs(meta.bpm) * 1000), 'u32'),\n };\n }\n\n return event;\n });\n}\n\n// ============================================================================\n// Project Time Info\n// ============================================================================\n\n/**\n * Project creation date and work time\n */\nexport interface ProjectTimeInfo {\n creationDate: Date | null;\n workTimeSeconds: number | null;\n}\n\n// Delphi epoch: December 30, 1899\nconst DELPHI_EPOCH = new Date(1899, 11, 30);\n\n/**\n * Converts Delphi timestamp (days since epoch) to Date\n */\nfunction delphiToDate(days: number): Date {\n const ms = days * 24 * 60 * 60 * 1000;\n return new Date(DELPHI_EPOCH.getTime() + ms);\n}\n\n/**\n * Converts Date to Delphi timestamp (days since epoch)\n */\nfunction dateToDelphiDays(date: Date): number {\n const ms = date.getTime() - DELPHI_EPOCH.getTime();\n return ms / (24 * 60 * 60 * 1000);\n}\n\n/**\n * Reads project time info from a parsed FLP\n * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)\n */\nexport function readProjectTimeInfo(parsed: ParsedFlp): ProjectTimeInfo {\n const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);\n\n if (!timestampEvent || timestampEvent.payload.length < 16) {\n return {\n creationDate: null,\n workTimeSeconds: null,\n };\n }\n\n const reader = new BinaryReader(timestampEvent.payload);\n const createdOnDays = reader.readF64LE();\n const timeSpentDays = reader.readF64LE();\n\n return {\n creationDate: delphiToDate(createdOnDays),\n workTimeSeconds: timeSpentDays * 24 * 60 * 60, // Convert days to seconds\n };\n}\n\n/**\n * Writes project time info to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n *\n * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)\n */\nexport function writeProjectTimeInfo(parsed: ParsedFlp, info: Partial<ProjectTimeInfo>): ParsedFlp {\n // Read current values to preserve unmodified fields\n const current = readProjectTimeInfo(parsed);\n\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.PROJECT_TIMESTAMP) {\n return event;\n }\n\n const writer = new BinaryWriter();\n\n // Write creation date\n if (info.creationDate !== undefined && info.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(info.creationDate));\n } else if (current.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(current.creationDate));\n } else {\n writer.writeF64LE(0);\n }\n\n // Write work time\n if (info.workTimeSeconds !== undefined && info.workTimeSeconds !== null) {\n writer.writeF64LE(info.workTimeSeconds / (24 * 60 * 60)); // Convert seconds to days\n } else if (current.workTimeSeconds !== null) {\n writer.writeF64LE(current.workTimeSeconds / (24 * 60 * 60));\n } else {\n writer.writeF64LE(0);\n }\n\n return {\n ...event,\n payload: writer.toBuffer(),\n };\n });\n}\n\n// ============================================================================\n// Samples\n// ============================================================================\n\n/**\n * Reference to a sample in the project\n */\nexport interface SampleRef {\n /** Index of the event in the events array */\n eventIndex: number;\n /** File path of the sample */\n path: string;\n}\n\n/**\n * Lists all samples in the project\n */\nexport function listSamples(parsed: ParsedFlp): SampleRef[] {\n const samples: SampleRef[] = [];\n\n parsed.events.forEach((event: FlpEvent, index: number) => {\n if (event.id === EVENT_ID.CHANNEL_SAMPLE_PATH) {\n const path = getEventString(event, parsed.useUnicode);\n if (path && path.length > 0) {\n samples.push({\n eventIndex: index,\n path,\n });\n }\n }\n });\n\n return samples;\n}\n\n/**\n * Rewrites sample paths using a mapper function\n */\nexport function rewriteSamplePaths(\n parsed: ParsedFlp,\n mapper: (oldPath: string) => string,\n): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.CHANNEL_SAMPLE_PATH) {\n return event;\n }\n\n const oldPath = getEventString(event, parsed.useUnicode);\n if (!oldPath || oldPath.length === 0) {\n return event;\n }\n\n const newPath = mapper(oldPath);\n if (newPath === oldPath) {\n return event;\n }\n\n return {\n ...event,\n payload: createTextPayload(newPath, parsed.useUnicode),\n };\n });\n}\n\n// ============================================================================\n// Plugins (VST)\n// ============================================================================\n\n/**\n * Reference to a plugin in the project\n */\nexport interface PluginRef {\n /** Plugin name (may be null if not available) */\n name: string | null;\n /** Plugin vendor/provider (may be null if not stored in file) */\n vendor: string | null;\n}\n\n/**\n * VST Plugin Event internal IDs (from plugin.py VSTPluginEvent)\n */\nconst VST_EVENT_ID = {\n MIDI: 1,\n FLAGS: 2,\n IO: 30,\n INPUTS: 31,\n OUTPUTS: 32,\n PLUGIN_INFO: 50,\n FOUR_CC: 51,\n GUID: 52,\n STATE: 53,\n NAME: 54,\n PLUGIN_PATH: 55,\n VENDOR: 56,\n} as const;\n\n/**\n * Parses a VST plugin data event to extract name and vendor\n * Based on VSTPluginEvent structure in PyFLP\n */\nfunction parseVstPluginData(payload: Buffer): { name: string | null; vendor: string | null } {\n if (payload.length < 4) {\n return { name: null, vendor: null };\n }\n\n const reader = new BinaryReader(payload);\n\n // First 4 bytes: wrapper marker.\n // Historically seen values include 8/10/11, but newer FL versions can use\n // other markers (e.g. 12). We read and ignore the exact value here because\n // this parser is only called for \"Fruity Wrapper\" plugin data.\n const typeMarker = reader.readU32LE();\n void typeMarker;\n\n let name: string | null = null;\n let vendor: string | null = null;\n\n // Parse sub-events\n while (reader.remaining() >= 12) {\n // At minimum: 4 (id) + 8 (size)\n const subEventId = reader.readU32LE();\n const dataSize = reader.readU32LE(); // Lower 32 bits of size\n const dataSizeHigh = reader.readU32LE(); // Upper 32 bits (usually 0)\n\n // Combine for 64-bit size (though in practice it's always small)\n const actualSize = dataSize + dataSizeHigh * 0x100000000;\n\n if (actualSize > reader.remaining()) {\n break;\n }\n\n const data = reader.readBytes(actualSize);\n\n // Extract name and vendor\n if (subEventId === VST_EVENT_ID.NAME && actualSize > 0) {\n name = data.toString('utf8').replace(/\\0/g, '');\n } else if (subEventId === VST_EVENT_ID.VENDOR && actualSize > 0) {\n vendor = data.toString('utf8').replace(/\\0/g, '');\n }\n }\n\n return { name, vendor };\n}\n\n/**\n * Lists all plugins (VST and native) in the project\n * Returns plugin name and vendor where available\n */\nexport function listPlugins(parsed: ParsedFlp): PluginRef[] {\n const plugins: PluginRef[] = [];\n const seenPlugins = new Set<string>();\n\n // Track current channel context\n let currentInternalName: string | null = null;\n let currentPluginName: string | null = null;\n\n for (const event of parsed.events) {\n // Track channel new events as boundaries\n if (event.id === EVENT_ID.CHANNEL_NEW) {\n // Reset context for new channel\n if (currentInternalName || currentPluginName) {\n // Save previous channel's plugin if it wasn't a VST (no plugin data)\n const key = `${currentInternalName ?? ''}:${currentPluginName ?? ''}`;\n if (\n !seenPlugins.has(key) &&\n currentInternalName &&\n currentInternalName !== 'Fruity Wrapper'\n ) {\n seenPlugins.add(key);\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null, // Native plugins don't store vendor\n });\n }\n }\n currentInternalName = null;\n currentPluginName = null;\n }\n\n // Track internal name (identifies plugin type)\n if (event.id === EVENT_ID.PLUGIN_INTERNAL_NAME) {\n currentInternalName = getEventString(event, parsed.useUnicode);\n }\n\n // Track display name\n if (event.id === EVENT_ID.PLUGIN_NAME) {\n currentPluginName = getEventString(event, parsed.useUnicode);\n }\n\n // Parse VST plugin data\n if (event.id === EVENT_ID.PLUGIN_DATA && currentInternalName === 'Fruity Wrapper') {\n const vstData = parseVstPluginData(event.payload);\n if (vstData.name) {\n const key = `vst:${vstData.name}:${vstData.vendor ?? ''}`;\n if (!seenPlugins.has(key)) {\n seenPlugins.add(key);\n plugins.push({\n name: vstData.name,\n vendor: vstData.vendor,\n });\n }\n }\n // Reset context after processing VST\n currentInternalName = null;\n currentPluginName = null;\n }\n }\n\n // Handle last channel if any\n if (currentInternalName && currentInternalName !== 'Fruity Wrapper') {\n const key = `${currentInternalName}:${currentPluginName ?? ''}`;\n if (!seenPlugins.has(key)) {\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null,\n });\n }\n }\n\n return plugins;\n}\n\n// ============================================================================\n// FL Version\n// ============================================================================\n\n/**\n * Gets the FL Studio version string from the project\n */\nexport function getFlVersion(parsed: ParsedFlp): string {\n return parsed.flVersion;\n}\n\n/**\n * Gets the PPQ (Pulses Per Quarter note) from the header\n */\nexport function getPPQ(parsed: ParsedFlp): number {\n // PPQ is at offset 12-13 in the header (bytes 12-13 of the 14-byte FLhd chunk)\n const reader = new BinaryReader(parsed.headerChunkBytes);\n reader.seek(12);\n return reader.readU16LE();\n}\n"],"mappings":"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","names":["protobuf","import_smart_buffer","import_minimal","protobuf","headerSize"]}
package/dist/index.js CHANGED
@@ -533,9 +533,62 @@ function varIntSize(value) {
533
533
  }
534
534
 
535
535
  // src/parser/FlpParser.ts
536
+ var KNOWN_DWORD_IDS = new Set(
537
+ Object.keys(EVENT_KIND).map(Number).filter((id) => id >= DWORD && id < TEXT)
538
+ );
536
539
  var FLP_HEADER_MAGIC = "FLhd";
537
540
  var FLP_DATA_MAGIC = "FLdt";
538
541
  var FLP_HEADER_SIZE = 6;
542
+ var LOOKAHEAD_BYTES = 200;
543
+ var LOOKAHEAD_VARINT_MAX = 1e5;
544
+ function readVarIntRaw(buf, off, end) {
545
+ let shift = 0;
546
+ let size = 0;
547
+ const start = off;
548
+ while (true) {
549
+ if (off >= end) return { size: -1, bytesRead: 0 };
550
+ const byte = buf[off];
551
+ off++;
552
+ size |= (byte & 127) << shift;
553
+ shift += 7;
554
+ if (!(byte & 128)) break;
555
+ }
556
+ return { size, bytesRead: off - start };
557
+ }
558
+ function scoreAlignment(buf, off, end, bytesToCheck) {
559
+ const limit = Math.min(off + bytesToCheck, end);
560
+ let textDataCount = 0;
561
+ let consecutiveSmallByte = 0;
562
+ let maxConsecutiveSmallByte = 0;
563
+ while (off < limit) {
564
+ const id = buf[off];
565
+ if (id < WORD) {
566
+ off += 2;
567
+ if (id < 32 && !KNOWN_DWORD_IDS.has(id)) {
568
+ consecutiveSmallByte++;
569
+ maxConsecutiveSmallByte = Math.max(maxConsecutiveSmallByte, consecutiveSmallByte);
570
+ } else {
571
+ consecutiveSmallByte = 0;
572
+ }
573
+ } else if (id < DWORD) {
574
+ off += 3;
575
+ consecutiveSmallByte = 0;
576
+ } else if (id < TEXT) {
577
+ off += 5;
578
+ consecutiveSmallByte = 0;
579
+ } else {
580
+ const vi = readVarIntRaw(buf, off + 1, end);
581
+ if (vi.size >= 0 && vi.size < LOOKAHEAD_VARINT_MAX && off + 1 + vi.bytesRead + vi.size <= end) {
582
+ textDataCount++;
583
+ off = off + 1 + vi.bytesRead + vi.size;
584
+ } else {
585
+ return -100;
586
+ }
587
+ consecutiveSmallByte = 0;
588
+ }
589
+ }
590
+ return textDataCount * 10 - maxConsecutiveSmallByte * 3;
591
+ }
539
592
  function parseFlp(buffer) {
540
593
  const reader = new BinaryReader(buffer);
541
594
  const headerMagic = reader.readBytes(4).toString("ascii");
@@ -568,7 +621,6 @@ function parseFlp(buffer) {
568
621
  const events = [];
569
622
  let flVersion = "0.0.0";
570
623
  let useUnicode = false;
571
- let detectedVersion = false;
572
624
  const eventsStart = reader.tell();
573
625
  const eventsEnd = eventsStart + eventsSize;
574
626
  while (reader.tell() < eventsEnd) {
@@ -583,8 +635,32 @@ function parseFlp(buffer) {
583
635
  payloadSize = 2;
584
636
  headerSize2 = 1;
585
637
  } else if (eventId < TEXT) {
586
- payloadSize = 4;
587
- headerSize2 = 1;
638
+ if (KNOWN_DWORD_IDS.has(eventId)) {
639
+ payloadSize = 4;
640
+ headerSize2 = 1;
641
+ } else {
642
+ const afterDword = eventStart + 1 + 4;
643
+ const vi = readVarIntRaw(buffer, eventStart + 1, eventsEnd);
644
+ if (vi.size < 0 || vi.size > LOOKAHEAD_VARINT_MAX || eventStart + 1 + vi.bytesRead + vi.size > eventsEnd) {
645
+ payloadSize = 4;
646
+ headerSize2 = 1;
647
+ } else if (vi.size === 3) {
648
+ payloadSize = 4;
649
+ headerSize2 = 1;
650
+ } else {
651
+ const afterVarInt = eventStart + 1 + vi.bytesRead + vi.size;
652
+ const dwordScore = scoreAlignment(buffer, afterDword, eventsEnd, LOOKAHEAD_BYTES);
653
+ const varIntScore = scoreAlignment(buffer, afterVarInt, eventsEnd, LOOKAHEAD_BYTES);
654
+ if (varIntScore > dwordScore + 2) {
655
+ payloadSize = vi.size;
656
+ reader.seek(eventStart + 1 + vi.bytesRead);
657
+ headerSize2 = 1 + vi.bytesRead;
658
+ } else {
659
+ payloadSize = 4;
660
+ headerSize2 = 1;
661
+ }
662
+ }
663
+ }
588
664
  } else {
589
665
  const sizeStart = reader.tell();
590
666
  payloadSize = reader.readVarInt();
@@ -600,16 +676,14 @@ function parseFlp(buffer) {
600
676
  payload: Buffer.from(payload)
601
677
  };
602
678
  events.push(event);
603
- if (eventId === EVENT_ID.PROJECT_FL_VERSION && !detectedVersion) {
679
+ if (eventId === EVENT_ID.PROJECT_FL_VERSION && flVersion === "0.0.0") {
604
680
  const candidate = payload.toString("ascii").replace(/\0/g, "").trim();
605
- const isSemverLike = /^\d+(\.\d+)+$/.test(candidate);
606
- if (isSemverLike) {
681
+ if (/^\d+(\.\d+)+$/.test(candidate)) {
607
682
  flVersion = candidate;
608
683
  const parts = flVersion.split(".").map((p) => parseInt(p, 10));
609
684
  const major = parts[0] ?? 0;
610
685
  const minor = parts[1] ?? 0;
611
686
  useUnicode = parts.length >= 2 && (major > 11 || major === 11 && minor >= 5);
612
- detectedVersion = true;
613
687
  }
614
688
  }
615
689
  }
@@ -676,7 +750,7 @@ function createEvent(id, payload) {
676
750
  function patchEvents(parsed, patcher) {
677
751
  const patchedEvents = parsed.events.map((event, index) => {
678
752
  const patched = patcher(event, index);
679
- if (patched.payload !== event.payload && patched.id >= TEXT) {
753
+ if (patched.payload !== event.payload && event.header.length > 1) {
680
754
  const headerWriter = new BinaryWriter();
681
755
  headerWriter.writeU8(patched.id);
682
756
  headerWriter.writeVarInt(patched.payload.length);
@@ -769,18 +843,33 @@ function createNumberPayload(value, kind) {
769
843
  }
770
844
 
771
845
  // src/api/ProjectApi.ts
846
+ function readBpm(parsed) {
847
+ const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
848
+ if (tempoEvent) {
849
+ return getEventNumber(tempoEvent) / 1e3;
850
+ }
851
+ const coarseEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_COARSE);
852
+ if (coarseEvent) {
853
+ let bpm = coarseEvent.payload.readUInt16LE(0);
854
+ const fineEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_FINE);
855
+ if (fineEvent) {
856
+ bpm += fineEvent.payload.readUInt16LE(0) / 1e3;
857
+ }
858
+ return bpm;
859
+ }
860
+ return null;
861
+ }
772
862
  function readProjectMeta(parsed) {
773
863
  const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);
774
864
  const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);
775
865
  const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);
776
866
  const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);
777
- const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
778
867
  return {
779
868
  name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,
780
869
  description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,
781
870
  artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,
782
871
  genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,
783
- bpm: tempoEvent ? getEventNumber(tempoEvent) / 1e3 : null
872
+ bpm: readBpm(parsed)
784
873
  };
785
874
  }
786
875
  function writeProjectMeta(parsed, meta) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generated/events.generated.ts","../src/io/BinaryReader.ts","../src/io/BinaryWriter.ts","../src/parser/FlpParser.ts","../src/api/ProjectApi.ts"],"sourcesContent":["/**\r\n * Auto-generated event constants from PyFLP\r\n * FL Studio .flp file event IDs and types\r\n *\r\n * Event ID ranges:\r\n * - BYTE (0-63): 1 byte data\r\n * - WORD (64-127): 2 bytes data\r\n * - DWORD (128-191): 4 bytes data\r\n * - TEXT (192-207): Variable length strings (+ some IDs in DATA range)\r\n * - DATA (208+): Variable length binary data\r\n */\r\n\r\n// Base offsets for event ID ranges\r\nexport const BYTE = 0;\r\nexport const WORD = 64;\r\nexport const DWORD = 128;\r\nexport const TEXT = 192;\r\nexport const DATA = 208;\r\n\r\n// IDs for TEXT events that use UTF-16LE in newer FL versions\r\nexport const NEW_TEXT_IDS = [\r\n TEXT + 49, // ArrangementID.Name\r\n TEXT + 39, // DisplayGroupID.Name\r\n TEXT + 47, // TrackID.Name\r\n] as const;\r\n\r\n/**\r\n * Event IDs used by FL Studio project files\r\n * Based on PyFLP's event definitions\r\n */\r\nexport const EVENT_ID = {\r\n // === Project IDs (from project.py) ===\r\n PROJECT_LOOP_ACTIVE: 9,\r\n PROJECT_SHOW_INFO: 10,\r\n PROJECT__VOLUME: 12,\r\n PROJECT_PAN_LAW: 23,\r\n PROJECT_LICENSED: 28,\r\n PROJECT__TEMPO_COARSE: WORD + 2, // 66\r\n PROJECT_PITCH: WORD + 16, // 80\r\n PROJECT__TEMPO_FINE: WORD + 29, // 93\r\n PROJECT_CUR_GROUP_ID: DWORD + 18,\r\n PROJECT_TEMPO: DWORD + 28,\r\n PROJECT_FL_BUILD: DWORD + 31,\r\n PROJECT_TITLE: TEXT + 2, // 194\r\n PROJECT_COMMENTS: TEXT + 3, // 195\r\n PROJECT_URL: TEXT + 5, // 197\r\n PROJECT__RTF_COMMENTS: TEXT + 6, // 198\r\n PROJECT_FL_VERSION: TEXT + 7, // 199\r\n PROJECT_LICENSEE: TEXT + 8, // 200\r\n PROJECT_DATA_PATH: TEXT + 10, // 202\r\n PROJECT_GENRE: TEXT + 14, // 206\r\n PROJECT_ARTISTS: TEXT + 15, // 207\r\n PROJECT_TIMESTAMP: DATA + 29, // 237\r\n\r\n // === Channel IDs (from channel.py) ===\r\n CHANNEL_IS_ENABLED: 0,\r\n CHANNEL__VOL_BYTE: 2,\r\n CHANNEL__PAN_BYTE: 3,\r\n CHANNEL_ZIPPED: 15,\r\n CHANNEL_PING_PONG_LOOP: 20,\r\n CHANNEL_TYPE: 21,\r\n CHANNEL_ROUTED_TO: 22,\r\n CHANNEL_IS_LOCKED: 32,\r\n CHANNEL_NEW: WORD,\r\n CHANNEL_FREQ_TILT: WORD + 5, // 69\r\n CHANNEL_FX_FLAGS: WORD + 6, // 70\r\n CHANNEL_CUTOFF: WORD + 7, // 71\r\n CHANNEL__VOL_WORD: WORD + 8, // 72\r\n CHANNEL__PAN_WORD: WORD + 9, // 73\r\n CHANNEL_PREAMP: WORD + 10, // 74\r\n CHANNEL_FADE_OUT: WORD + 11, // 75\r\n CHANNEL_FADE_IN: WORD + 12, // 76\r\n CHANNEL_RESONANCE: WORD + 19, // 83\r\n CHANNEL_STEREO_DELAY: WORD + 21, // 85\r\n CHANNEL_POGO: WORD + 22, // 86\r\n CHANNEL_TIME_SHIFT: WORD + 25, // 89\r\n CHANNEL_CHILDREN: WORD + 30, // 94\r\n CHANNEL_SWING: WORD + 33, // 97\r\n CHANNEL_RING_MOD: DWORD + 3,\r\n CHANNEL_CUT_GROUP: DWORD + 4,\r\n CHANNEL_ROOT_NOTE: DWORD + 7,\r\n CHANNEL_DELAY_MOD_XY: DWORD + 10,\r\n CHANNEL_REVERB: DWORD + 11,\r\n CHANNEL__STRETCH_TIME: DWORD + 12,\r\n CHANNEL_FINE_TUNE: DWORD + 14,\r\n CHANNEL_SAMPLER_FLAGS: DWORD + 15,\r\n CHANNEL_LAYER_FLAGS: DWORD + 16,\r\n CHANNEL_GROUP_NUM: DWORD + 17,\r\n CHANNEL_AU_SAMPLE_RATE: DWORD + 25,\r\n CHANNEL__NAME: TEXT,\r\n CHANNEL_SAMPLE_PATH: TEXT + 4, // 196\r\n CHANNEL_DELAY: DATA + 1, // 209\r\n CHANNEL_PARAMETERS: DATA + 7, // 215\r\n CHANNEL_ENVELOPE_LFO: DATA + 10, // 218\r\n CHANNEL_LEVELS: DATA + 11, // 219\r\n CHANNEL_POLYPHONY: DATA + 13, // 221\r\n CHANNEL_TRACKING: DATA + 20, // 228\r\n CHANNEL_LEVEL_ADJUSTS: DATA + 21, // 229\r\n CHANNEL_AUTOMATION: DATA + 26, // 234\r\n\r\n // === Plugin IDs (from plugin.py) ===\r\n PLUGIN_COLOR: DWORD,\r\n PLUGIN_ICON: DWORD + 27,\r\n PLUGIN_INTERNAL_NAME: TEXT + 9, // 201\r\n PLUGIN_NAME: TEXT + 11, // 203\r\n PLUGIN_WRAPPER: DATA + 4, // 212\r\n PLUGIN_DATA: DATA + 5, // 213\r\n\r\n // === Display Group IDs ===\r\n DISPLAY_GROUP_NAME: TEXT + 39, // 231\r\n\r\n // === Rack IDs ===\r\n RACK_SWING: 11,\r\n RACK__FIT_TO_STEPS: 13,\r\n RACK_WINDOW_HEIGHT: DWORD + 5,\r\n\r\n // === Mixer IDs (from mixer.py) ===\r\n MIXER_SLOT_INDEX: WORD + 34, // 98\r\n MIXER_INSERT_ICON: WORD + 31, // 95\r\n MIXER_INSERT_OUTPUT: DWORD + 19,\r\n MIXER_INSERT_COLOR: DWORD + 21,\r\n MIXER_INSERT_INPUT: DWORD + 26,\r\n MIXER_INSERT_NAME: TEXT + 12, // 204\r\n MIXER_INSERT_ROUTING: DATA + 27, // 235\r\n MIXER_INSERT_FLAGS: DATA + 28, // 236\r\n} as const;\r\n\r\n/**\r\n * Event data type classification\r\n */\r\nexport type EventKind =\r\n | \"u8\"\r\n | \"i8\"\r\n | \"u16\"\r\n | \"i16\"\r\n | \"u32\"\r\n | \"i32\"\r\n | \"f32\"\r\n | \"text\"\r\n | \"data\"\r\n | \"unknown\";\r\n\r\n/**\r\n * Maps event IDs to their data type\r\n * Used for proper serialization/deserialization\r\n */\r\nexport const EVENT_KIND: Record<number, EventKind> = {\r\n // Project events\r\n [EVENT_ID.PROJECT_LOOP_ACTIVE]: \"u8\",\r\n [EVENT_ID.PROJECT_SHOW_INFO]: \"u8\",\r\n [EVENT_ID.PROJECT__VOLUME]: \"u8\",\r\n [EVENT_ID.PROJECT_PAN_LAW]: \"u8\",\r\n [EVENT_ID.PROJECT_LICENSED]: \"u8\",\r\n [EVENT_ID.PROJECT__TEMPO_COARSE]: \"u16\",\r\n [EVENT_ID.PROJECT_PITCH]: \"i16\",\r\n [EVENT_ID.PROJECT__TEMPO_FINE]: \"u16\",\r\n [EVENT_ID.PROJECT_CUR_GROUP_ID]: \"i32\",\r\n [EVENT_ID.PROJECT_TEMPO]: \"u32\",\r\n [EVENT_ID.PROJECT_FL_BUILD]: \"u32\",\r\n [EVENT_ID.PROJECT_TITLE]: \"text\",\r\n [EVENT_ID.PROJECT_COMMENTS]: \"text\",\r\n [EVENT_ID.PROJECT_URL]: \"text\",\r\n [EVENT_ID.PROJECT__RTF_COMMENTS]: \"text\",\r\n [EVENT_ID.PROJECT_FL_VERSION]: \"text\",\r\n [EVENT_ID.PROJECT_LICENSEE]: \"text\",\r\n [EVENT_ID.PROJECT_DATA_PATH]: \"text\",\r\n [EVENT_ID.PROJECT_GENRE]: \"text\",\r\n [EVENT_ID.PROJECT_ARTISTS]: \"text\",\r\n [EVENT_ID.PROJECT_TIMESTAMP]: \"data\",\r\n\r\n // Channel events\r\n [EVENT_ID.CHANNEL_IS_ENABLED]: \"u8\",\r\n [EVENT_ID.CHANNEL__VOL_BYTE]: \"u8\",\r\n [EVENT_ID.CHANNEL__PAN_BYTE]: \"u8\",\r\n [EVENT_ID.CHANNEL_ZIPPED]: \"u8\",\r\n [EVENT_ID.CHANNEL_PING_PONG_LOOP]: \"u8\",\r\n [EVENT_ID.CHANNEL_TYPE]: \"u8\",\r\n [EVENT_ID.CHANNEL_ROUTED_TO]: \"i8\",\r\n [EVENT_ID.CHANNEL_IS_LOCKED]: \"u8\",\r\n [EVENT_ID.CHANNEL_NEW]: \"u16\",\r\n [EVENT_ID.CHANNEL_FREQ_TILT]: \"u16\",\r\n [EVENT_ID.CHANNEL_FX_FLAGS]: \"u16\",\r\n [EVENT_ID.CHANNEL_CUTOFF]: \"u16\",\r\n [EVENT_ID.CHANNEL__VOL_WORD]: \"u16\",\r\n [EVENT_ID.CHANNEL__PAN_WORD]: \"u16\",\r\n [EVENT_ID.CHANNEL_PREAMP]: \"u16\",\r\n [EVENT_ID.CHANNEL_FADE_OUT]: \"u16\",\r\n [EVENT_ID.CHANNEL_FADE_IN]: \"u16\",\r\n [EVENT_ID.CHANNEL_RESONANCE]: \"u16\",\r\n [EVENT_ID.CHANNEL_STEREO_DELAY]: \"u16\",\r\n [EVENT_ID.CHANNEL_POGO]: \"u16\",\r\n [EVENT_ID.CHANNEL_TIME_SHIFT]: \"u16\",\r\n [EVENT_ID.CHANNEL_CHILDREN]: \"u16\",\r\n [EVENT_ID.CHANNEL_SWING]: \"u16\",\r\n [EVENT_ID.CHANNEL_RING_MOD]: \"u32\",\r\n [EVENT_ID.CHANNEL_CUT_GROUP]: \"u32\",\r\n [EVENT_ID.CHANNEL_ROOT_NOTE]: \"u32\",\r\n [EVENT_ID.CHANNEL_DELAY_MOD_XY]: \"u32\",\r\n [EVENT_ID.CHANNEL_REVERB]: \"u32\",\r\n [EVENT_ID.CHANNEL__STRETCH_TIME]: \"f32\",\r\n [EVENT_ID.CHANNEL_FINE_TUNE]: \"i32\",\r\n [EVENT_ID.CHANNEL_SAMPLER_FLAGS]: \"u32\",\r\n [EVENT_ID.CHANNEL_LAYER_FLAGS]: \"u32\",\r\n [EVENT_ID.CHANNEL_GROUP_NUM]: \"i32\",\r\n [EVENT_ID.CHANNEL_AU_SAMPLE_RATE]: \"u32\",\r\n [EVENT_ID.CHANNEL__NAME]: \"text\",\r\n [EVENT_ID.CHANNEL_SAMPLE_PATH]: \"text\",\r\n [EVENT_ID.CHANNEL_DELAY]: \"data\",\r\n [EVENT_ID.CHANNEL_PARAMETERS]: \"data\",\r\n [EVENT_ID.CHANNEL_ENVELOPE_LFO]: \"data\",\r\n [EVENT_ID.CHANNEL_LEVELS]: \"data\",\r\n [EVENT_ID.CHANNEL_POLYPHONY]: \"data\",\r\n [EVENT_ID.CHANNEL_TRACKING]: \"data\",\r\n [EVENT_ID.CHANNEL_LEVEL_ADJUSTS]: \"data\",\r\n [EVENT_ID.CHANNEL_AUTOMATION]: \"data\",\r\n\r\n // Plugin events\r\n [EVENT_ID.PLUGIN_COLOR]: \"u32\",\r\n [EVENT_ID.PLUGIN_ICON]: \"u32\",\r\n [EVENT_ID.PLUGIN_INTERNAL_NAME]: \"text\",\r\n [EVENT_ID.PLUGIN_NAME]: \"text\",\r\n [EVENT_ID.PLUGIN_WRAPPER]: \"data\",\r\n [EVENT_ID.PLUGIN_DATA]: \"data\",\r\n\r\n // Display Group events\r\n [EVENT_ID.DISPLAY_GROUP_NAME]: \"data\",\r\n\r\n // Rack events\r\n [EVENT_ID.RACK_SWING]: \"u8\",\r\n [EVENT_ID.RACK__FIT_TO_STEPS]: \"u8\",\r\n [EVENT_ID.RACK_WINDOW_HEIGHT]: \"u32\",\r\n\r\n // Mixer events\r\n [EVENT_ID.MIXER_SLOT_INDEX]: \"u16\",\r\n [EVENT_ID.MIXER_INSERT_ICON]: \"i16\",\r\n [EVENT_ID.MIXER_INSERT_OUTPUT]: \"i32\",\r\n [EVENT_ID.MIXER_INSERT_COLOR]: \"u32\",\r\n [EVENT_ID.MIXER_INSERT_INPUT]: \"i32\",\r\n [EVENT_ID.MIXER_INSERT_NAME]: \"text\",\r\n [EVENT_ID.MIXER_INSERT_ROUTING]: \"data\",\r\n [EVENT_ID.MIXER_INSERT_FLAGS]: \"data\",\r\n\r\n};\r\n\r\n/**\r\n * Determines the event kind based on ID\r\n * Falls back to range-based detection if not explicitly mapped\r\n */\r\nexport function getEventKind(eventId: number): EventKind {\r\n // Check explicit mapping first\r\n const mapped = EVENT_KIND[eventId];\r\n if (mapped !== undefined) {\r\n return mapped;\r\n }\r\n\r\n // Fall back to range-based detection\r\n if (eventId < WORD) {\r\n return \"u8\";\r\n } else if (eventId < DWORD) {\r\n return \"u16\";\r\n } else if (eventId < TEXT) {\r\n return \"u32\";\r\n } else if (eventId < DATA || NEW_TEXT_IDS.includes(eventId as 241 | 231 | 239)) {\r\n return \"text\";\r\n } else {\r\n return \"data\";\r\n }\r\n}\r\n\r\n/**\r\n * Returns the fixed size for BYTE/WORD/DWORD events\r\n * Returns -1 for variable-length events (TEXT/DATA)\r\n */\r\nexport function getEventFixedSize(eventId: number): number {\r\n if (eventId < WORD) {\r\n return 1;\r\n } else if (eventId < DWORD) {\r\n return 2;\r\n } else if (eventId < TEXT) {\r\n return 4;\r\n }\r\n return -1; // Variable length\r\n}\r\n","/**\n * Binary reader for FL Studio .flp files\n * Uses smart-buffer for binary reading and protobufjs for VarInt decoding\n */\n\nimport protobuf from \"protobufjs/minimal.js\";\nimport { SmartBuffer } from \"smart-buffer\";\n\nexport class BinaryReader {\n private sb: SmartBuffer;\n private source: Buffer;\n\n constructor(buffer: Buffer) {\n this.source = buffer;\n this.sb = SmartBuffer.fromBuffer(buffer);\n }\n\n /**\n * Returns current read position\n */\n tell(): number {\n return this.sb.readOffset;\n }\n\n /**\n * Seeks to an absolute position\n */\n seek(pos: number): void {\n this.sb.readOffset = pos;\n }\n\n /**\n * Seeks relative to current position\n */\n skip(offset: number): void {\n this.sb.readOffset += offset;\n }\n\n /**\n * Returns total buffer length\n */\n length(): number {\n return this.sb.length;\n }\n\n /**\n * Returns remaining bytes to read\n */\n remaining(): number {\n return this.sb.remaining();\n }\n\n /**\n * Returns true if there are more bytes to read\n */\n hasMore(): boolean {\n return this.sb.remaining() > 0;\n }\n\n /**\n * Reads an unsigned 8-bit integer\n */\n readU8(): number {\n return this.sb.readUInt8();\n }\n\n /**\n * Reads a signed 8-bit integer\n */\n readI8(): number {\n return this.sb.readInt8();\n }\n\n /**\n * Reads an unsigned 16-bit integer (little-endian)\n */\n readU16LE(): number {\n return this.sb.readUInt16LE();\n }\n\n /**\n * Reads a signed 16-bit integer (little-endian)\n */\n readI16LE(): number {\n return this.sb.readInt16LE();\n }\n\n /**\n * Reads an unsigned 32-bit integer (little-endian)\n */\n readU32LE(): number {\n return this.sb.readUInt32LE();\n }\n\n /**\n * Reads a signed 32-bit integer (little-endian)\n */\n readI32LE(): number {\n return this.sb.readInt32LE();\n }\n\n /**\n * Reads a 32-bit float (little-endian)\n */\n readF32LE(): number {\n return this.sb.readFloatLE();\n }\n\n /**\n * Reads a 64-bit float (little-endian)\n */\n readF64LE(): number {\n return this.sb.readDoubleLE();\n }\n\n /**\n * Reads raw bytes\n */\n readBytes(length: number): Buffer {\n return this.sb.readBuffer(length);\n }\n\n /**\n * Reads a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n readVarInt(): number {\n // Get remaining buffer from current position\n const remainingBuffer = this.source.subarray(this.sb.readOffset);\n const reader = protobuf.Reader.create(remainingBuffer);\n const value = reader.uint32();\n // Advance our position by how many bytes protobufjs consumed\n this.sb.readOffset += reader.pos;\n return value;\n }\n\n /**\n * Reads an ASCII string (null-terminated)\n */\n readAsciiString(length: number): string {\n const str = this.sb.readString(length, \"ascii\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Reads a UTF-16LE string (null-terminated)\n */\n readUtf16LEString(length: number): string {\n const bytes = this.sb.readBuffer(length);\n const str = bytes.toString(\"utf16le\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Peeks at bytes without advancing position\n */\n peek(length: number): Buffer {\n const pos = this.sb.readOffset;\n const data = this.sb.readBuffer(length);\n this.sb.readOffset = pos;\n return data;\n }\n\n /**\n * Peeks at a single byte without advancing position\n */\n peekU8(): number {\n const pos = this.sb.readOffset;\n const value = this.sb.readUInt8();\n this.sb.readOffset = pos;\n return value;\n }\n\n /**\n * Returns the internal buffer\n */\n toBuffer(): Buffer {\n return this.source;\n }\n}\n","/**\n * Binary writer for FL Studio .flp files\n * Uses smart-buffer for binary writing and protobufjs for VarInt encoding\n */\n\nimport { SmartBuffer } from \"smart-buffer\";\nimport protobuf from \"protobufjs/minimal.js\";\n\nexport class BinaryWriter {\n private sb: SmartBuffer;\n\n constructor() {\n this.sb = new SmartBuffer();\n }\n\n /**\n * Returns current write position (total bytes written)\n */\n tell(): number {\n return this.sb.writeOffset;\n }\n\n /**\n * Writes an unsigned 8-bit integer\n */\n writeU8(value: number): void {\n this.sb.writeUInt8(value & 0xff);\n }\n\n /**\n * Writes a signed 8-bit integer\n */\n writeI8(value: number): void {\n this.sb.writeInt8(value);\n }\n\n /**\n * Writes an unsigned 16-bit integer (little-endian)\n */\n writeU16LE(value: number): void {\n this.sb.writeUInt16LE(value & 0xffff);\n }\n\n /**\n * Writes a signed 16-bit integer (little-endian)\n */\n writeI16LE(value: number): void {\n this.sb.writeInt16LE(value);\n }\n\n /**\n * Writes an unsigned 32-bit integer (little-endian)\n */\n writeU32LE(value: number): void {\n this.sb.writeUInt32LE(value >>> 0);\n }\n\n /**\n * Writes a signed 32-bit integer (little-endian)\n */\n writeI32LE(value: number): void {\n this.sb.writeInt32LE(value);\n }\n\n /**\n * Writes a 32-bit float (little-endian)\n */\n writeF32LE(value: number): void {\n this.sb.writeFloatLE(value);\n }\n\n /**\n * Writes a 64-bit float (little-endian)\n */\n writeF64LE(value: number): void {\n this.sb.writeDoubleLE(value);\n }\n\n /**\n * Writes raw bytes\n */\n writeBytes(data: Buffer): void {\n this.sb.writeBuffer(data);\n }\n\n /**\n * Writes a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n writeVarInt(value: number): void {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n const encoded = writer.finish();\n this.sb.writeBuffer(Buffer.from(encoded));\n }\n\n /**\n * Writes an ASCII string (null-terminated)\n */\n writeAsciiString(str: string): void {\n this.sb.writeString(str + \"\\0\", \"ascii\");\n }\n\n /**\n * Writes a UTF-16LE string (null-terminated)\n */\n writeUtf16LEString(str: string): void {\n const buf = Buffer.from(str + \"\\0\", \"utf16le\");\n this.sb.writeBuffer(buf);\n }\n\n /**\n * Gets the final buffer\n */\n toBuffer(): Buffer {\n return this.sb.toBuffer();\n }\n}\n\n/**\n * Encodes a VarInt to a Buffer using protobufjs (standalone utility)\n */\nexport function encodeVarInt(value: number): Buffer {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n return Buffer.from(writer.finish());\n}\n\n/**\n * Calculates the byte size needed for a VarInt\n */\nexport function varIntSize(value: number): number {\n if (value < 0x80) return 1;\n if (value < 0x4000) return 2;\n if (value < 0x200000) return 3;\n if (value < 0x10000000) return 4;\n return 5;\n}\n","/**\n * FL Studio .flp file parser and serializer\n * Conservative patch approach: preserves all unknown data byte-for-byte\n */\n\nimport { DWORD, EVENT_ID, TEXT, WORD, getEventKind } from \"../generated/events.generated.js\";\nimport { BinaryReader } from \"../io/BinaryReader.js\";\nimport { BinaryWriter } from \"../io/BinaryWriter.js\";\n\n/**\n * Represents a single event in an FLP file\n * Stores raw bytes to enable conservative patching\n */\nexport interface FlpEvent {\n /** Event ID (0-255) */\n id: number;\n /** Data type classification */\n kind: \"u8\" | \"i8\" | \"u16\" | \"i16\" | \"u32\" | \"i32\" | \"f32\" | \"text\" | \"data\" | \"unknown\";\n /** Original header bytes (ID + size for variable-length events) */\n header: Buffer;\n /** Event payload data */\n payload: Buffer;\n}\n\n/**\n * Represents a parsed FLP file\n * Contains all data needed for conservative round-trip\n */\nexport interface ParsedFlp {\n /** FLhd chunk (header) - 14 bytes total */\n headerChunkBytes: Buffer;\n /** FLdt chunk header (magic + size placeholder) - 8 bytes */\n fldtHeaderBytes: Buffer;\n /** All events from the FLdt chunk */\n events: FlpEvent[];\n /** Any trailing bytes after events (should be empty) */\n trailingBytes: Buffer | undefined;\n /** Detected FL version for string encoding */\n flVersion: string;\n /** Whether to use UTF-16LE for strings (FL >= 11.5) */\n useUnicode: boolean;\n}\n\n/**\n * Header structure constants\n */\nconst FLP_HEADER_MAGIC = \"FLhd\";\nconst FLP_DATA_MAGIC = \"FLdt\";\nconst FLP_HEADER_SIZE = 6; // Expected header chunk size\n\n/**\n * Parses an FL Studio project file\n *\n * @param buffer - Raw file data\n * @returns Parsed FLP structure\n * @throws Error if file is corrupted or invalid\n */\nexport function parseFlp(buffer: Buffer): ParsedFlp {\n const reader = new BinaryReader(buffer);\n\n // Read FLhd header chunk\n const headerMagic = reader.readBytes(4).toString(\"ascii\");\n if (headerMagic !== FLP_HEADER_MAGIC) {\n throw new Error(`Invalid header magic: expected 'FLhd', got '${headerMagic}'`);\n }\n\n const headerSize = reader.readU32LE();\n if (headerSize !== FLP_HEADER_SIZE) {\n throw new Error(`Invalid header size: expected ${FLP_HEADER_SIZE}, got ${headerSize}`);\n }\n\n const format = reader.readI16LE();\n const channelCount = reader.readU16LE();\n const ppq = reader.readU16LE();\n\n // Validate format (0 = normal project)\n if (format < -1 || format > 0x50) {\n throw new Error(`Invalid file format: ${format}`);\n }\n\n // Store complete header chunk (14 bytes)\n const headerChunkBytes = buffer.subarray(0, 14);\n\n // Read FLdt data chunk header\n const dataMagic = reader.readBytes(4).toString(\"ascii\");\n if (dataMagic !== FLP_DATA_MAGIC) {\n throw new Error(`Invalid data magic: expected 'FLdt', got '${dataMagic}'`);\n }\n\n const eventsSize = reader.readU32LE();\n\n // Validate events size\n const expectedFileSize = 22 + eventsSize; // 14 (FLhd) + 8 (FLdt header) + events\n if (buffer.length !== expectedFileSize) {\n throw new Error(\n `Data chunk size mismatch: expected file size ${expectedFileSize}, got ${buffer.length}`\n );\n }\n\n // Store FLdt header (8 bytes)\n const fldtHeaderBytes = buffer.subarray(14, 22);\n\n // Parse events\n const events: FlpEvent[] = [];\n let flVersion = \"0.0.0\";\n let useUnicode = false;\n let detectedVersion = false;\n\n const eventsStart = reader.tell();\n const eventsEnd = eventsStart + eventsSize;\n\n while (reader.tell() < eventsEnd) {\n const eventStart = reader.tell();\n const eventId = reader.readU8();\n\n let payloadSize: number;\n let headerSize: number;\n\n // Determine payload size based on event ID range\n if (eventId < WORD) {\n // BYTE range: 1 byte payload\n payloadSize = 1;\n headerSize = 1; // Just the ID\n } else if (eventId < DWORD) {\n // WORD range: 2 bytes payload\n payloadSize = 2;\n headerSize = 1;\n } else if (eventId < TEXT) {\n // DWORD range: 4 bytes payload\n payloadSize = 4;\n headerSize = 1;\n } else {\n // TEXT/DATA range: VarInt-encoded size\n const sizeStart = reader.tell();\n payloadSize = reader.readVarInt();\n headerSize = 1 + (reader.tell() - sizeStart); // ID + VarInt bytes\n }\n\n // Read payload\n const payload = reader.readBytes(payloadSize);\n\n // Extract header from original buffer for byte-perfect round-trip\n const header = buffer.subarray(eventStart, eventStart + headerSize);\n\n // Determine event kind\n const kind = getEventKind(eventId);\n\n const event: FlpEvent = {\n id: eventId,\n kind,\n header: Buffer.from(header),\n payload: Buffer.from(payload),\n };\n\n events.push(event);\n\n // Detect FL version from FLVersion event\n if (eventId === EVENT_ID.PROJECT_FL_VERSION && !detectedVersion) {\n const candidate = payload.toString(\"ascii\").replace(/\\0/g, \"\").trim();\n const isSemverLike = /^\\d+(\\.\\d+)+$/.test(candidate);\n if (isSemverLike) {\n flVersion = candidate;\n const parts = flVersion.split(\".\").map((p) => parseInt(p, 10));\n const major = parts[0] ?? 0;\n const minor = parts[1] ?? 0;\n useUnicode = parts.length >= 2 && (major > 11 || (major === 11 && minor >= 5));\n detectedVersion = true;\n }\n }\n }\n\n // Check for trailing bytes\n let trailingBytes: Buffer | undefined;\n if (reader.remaining() > 0) {\n trailingBytes = reader.readBytes(reader.remaining());\n }\n\n return {\n headerChunkBytes: Buffer.from(headerChunkBytes),\n fldtHeaderBytes: Buffer.from(fldtHeaderBytes),\n events,\n trailingBytes,\n flVersion,\n useUnicode,\n };\n}\n\n/**\n * Serializes a parsed FLP back to a buffer\n * Recalculates FLdt chunk size if events have changed\n *\n * @param parsed - Parsed FLP structure\n * @returns Serialized buffer\n */\nexport function serializeFlp(parsed: ParsedFlp): Buffer {\n const writer = new BinaryWriter();\n\n // Write FLhd header chunk (preserved byte-for-byte)\n writer.writeBytes(parsed.headerChunkBytes);\n\n // Calculate total events size\n let eventsSize = 0;\n for (const event of parsed.events) {\n eventsSize += serializeEvent(event).length;\n }\n\n // Add trailing bytes if present\n if (parsed.trailingBytes) {\n eventsSize += parsed.trailingBytes.length;\n }\n\n // Write FLdt header with updated size\n writer.writeBytes(Buffer.from(FLP_DATA_MAGIC, \"ascii\"));\n writer.writeU32LE(eventsSize);\n\n // Write all events\n for (const event of parsed.events) {\n writer.writeBytes(serializeEvent(event));\n }\n\n // Write trailing bytes if present\n if (parsed.trailingBytes) {\n writer.writeBytes(parsed.trailingBytes);\n }\n\n return writer.toBuffer();\n}\n\n/**\n * Serializes a single event to bytes\n * Uses original header for unchanged events, recalculates for modified ones\n */\nfunction serializeEvent(event: FlpEvent): Buffer {\n const writer = new BinaryWriter();\n\n // Conservative mode: preserve the original header bytes whenever available.\n // This keeps unchanged files byte-identical, including exact VarInt encoding.\n if (event.header.length > 0) {\n writer.writeBytes(event.header);\n } else {\n writer.writeU8(event.id);\n if (event.id >= TEXT) {\n writer.writeVarInt(event.payload.length);\n }\n }\n\n // Write payload\n writer.writeBytes(event.payload);\n\n return writer.toBuffer();\n}\n\n/**\n * Creates a new event with the given ID and payload\n */\nexport function createEvent(id: number, payload: Buffer): FlpEvent {\n const kind = getEventKind(id);\n\n // Build header\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(id);\n\n if (id >= TEXT) {\n headerWriter.writeVarInt(payload.length);\n }\n\n return {\n id,\n kind,\n header: headerWriter.toBuffer(),\n payload: Buffer.from(payload),\n };\n}\n\n/**\n * Applies a patcher function to all events\n * Returns a new ParsedFlp with patched events\n *\n * @param parsed - Original parsed FLP\n * @param patcher - Function that transforms each event\n * @returns New ParsedFlp with patched events\n */\nexport function patchEvents(\n parsed: ParsedFlp,\n patcher: (event: FlpEvent, index: number) => FlpEvent\n): ParsedFlp {\n const patchedEvents = parsed.events.map((event, index) => {\n const patched = patcher(event, index);\n\n // If payload changed, recalculate header for TEXT/DATA events\n if (patched.payload !== event.payload && patched.id >= TEXT) {\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(patched.id);\n headerWriter.writeVarInt(patched.payload.length);\n return {\n ...patched,\n header: headerWriter.toBuffer(),\n };\n }\n\n return patched;\n });\n\n return {\n ...parsed,\n events: patchedEvents,\n };\n}\n\n/**\n * Finds all events with a specific ID\n */\nexport function findEvents(parsed: ParsedFlp, eventId: number): FlpEvent[] {\n return parsed.events.filter((e) => e.id === eventId);\n}\n\n/**\n * Finds the first event with a specific ID\n */\nexport function findFirstEvent(parsed: ParsedFlp, eventId: number): FlpEvent | undefined {\n return parsed.events.find((e) => e.id === eventId);\n}\n\n/**\n * Gets event payload as a string (handles encoding)\n */\nexport function getEventString(event: FlpEvent, useUnicode: boolean): string {\n if (event.kind !== \"text\") {\n throw new Error(`Event ${event.id} is not a text event`);\n }\n\n if (useUnicode) {\n const str = event.payload.toString(\"utf16le\");\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n } else {\n const str = event.payload.toString(\"ascii\");\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n}\n\n/**\n * Creates a text payload with proper encoding\n */\nexport function createTextPayload(text: string, useUnicode: boolean): Buffer {\n if (useUnicode) {\n return Buffer.from(text + \"\\0\", \"utf16le\");\n } else {\n return Buffer.from(text + \"\\0\", \"ascii\");\n }\n}\n\n/**\n * Gets event payload as a number\n */\nexport function getEventNumber(event: FlpEvent): number {\n const reader = new BinaryReader(event.payload);\n\n switch (event.kind) {\n case \"u8\":\n return reader.readU8();\n case \"i8\":\n return reader.readI8();\n case \"u16\":\n return reader.readU16LE();\n case \"i16\":\n return reader.readI16LE();\n case \"u32\":\n return reader.readU32LE();\n case \"i32\":\n return reader.readI32LE();\n case \"f32\":\n return reader.readF32LE();\n default:\n throw new Error(`Cannot get number from ${event.kind} event`);\n }\n}\n\n/**\n * Creates a numeric payload\n */\nexport function createNumberPayload(\n value: number,\n kind: \"u8\" | \"i8\" | \"u16\" | \"i16\" | \"u32\" | \"i32\" | \"f32\"\n): Buffer {\n const writer = new BinaryWriter();\n\n switch (kind) {\n case \"u8\":\n writer.writeU8(value);\n break;\n case \"i8\":\n writer.writeI8(value);\n break;\n case \"u16\":\n writer.writeU16LE(value);\n break;\n case \"i16\":\n writer.writeI16LE(value);\n break;\n case \"u32\":\n writer.writeU32LE(value);\n break;\n case \"i32\":\n writer.writeI32LE(value);\n break;\n case \"f32\":\n writer.writeF32LE(value);\n break;\n }\n\n return writer.toBuffer();\n}\n","/**\n * High-level API for reading and modifying FL Studio project files\n * Provides typed access to project metadata, samples, plugins, and time info\n */\n\nimport { EVENT_ID } from \"../generated/events.generated.js\";\nimport { BinaryReader } from \"../io/BinaryReader.js\";\nimport { BinaryWriter } from \"../io/BinaryWriter.js\";\nimport {\n createNumberPayload,\n createTextPayload,\n findFirstEvent,\n type FlpEvent,\n getEventNumber,\n getEventString,\n type ParsedFlp,\n patchEvents,\n} from \"../parser/FlpParser.js\";\n\n// ============================================================================\n// Project Metadata\n// ============================================================================\n\n/**\n * Project metadata (name, description, artist, genre, BPM)\n */\nexport interface ProjectMeta {\n name: string | null;\n description: string | null;\n artist: string | null;\n genre: string | null;\n bpm: number | null;\n}\n\n/**\n * Reads project metadata from a parsed FLP\n */\nexport function readProjectMeta(parsed: ParsedFlp): ProjectMeta {\n const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);\n const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);\n const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);\n const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);\n const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);\n\n return {\n name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,\n description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,\n artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,\n genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,\n bpm: tempoEvent ? getEventNumber(tempoEvent) / 1000 : null,\n };\n}\n\n/**\n * Writes project metadata to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n */\nexport function writeProjectMeta(parsed: ParsedFlp, meta: Partial<ProjectMeta>): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n // Patch title\n if (meta.name !== undefined && event.id === EVENT_ID.PROJECT_TITLE) {\n return {\n ...event,\n payload: createTextPayload(meta.name ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch comments/description\n if (meta.description !== undefined && event.id === EVENT_ID.PROJECT_COMMENTS) {\n return {\n ...event,\n payload: createTextPayload(meta.description ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch artists\n if (meta.artist !== undefined && event.id === EVENT_ID.PROJECT_ARTISTS) {\n return {\n ...event,\n payload: createTextPayload(meta.artist ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch genre\n if (meta.genre !== undefined && event.id === EVENT_ID.PROJECT_GENRE) {\n return {\n ...event,\n payload: createTextPayload(meta.genre ?? \"\", parsed.useUnicode),\n };\n }\n\n // Patch tempo (stored as BPM * 1000)\n if (meta.bpm && Math.abs(meta.bpm) > 0 && event.id === EVENT_ID.PROJECT_TEMPO) {\n return {\n ...event,\n payload: createNumberPayload(Math.round(Math.abs(meta.bpm) * 1000), \"u32\"),\n };\n }\n\n return event;\n });\n}\n\n// ============================================================================\n// Project Time Info\n// ============================================================================\n\n/**\n * Project creation date and work time\n */\nexport interface ProjectTimeInfo {\n creationDate: Date | null;\n workTimeSeconds: number | null;\n}\n\n// Delphi epoch: December 30, 1899\nconst DELPHI_EPOCH = new Date(1899, 11, 30);\n\n/**\n * Converts Delphi timestamp (days since epoch) to Date\n */\nfunction delphiToDate(days: number): Date {\n const ms = days * 24 * 60 * 60 * 1000;\n return new Date(DELPHI_EPOCH.getTime() + ms);\n}\n\n/**\n * Converts Date to Delphi timestamp (days since epoch)\n */\nfunction dateToDelphiDays(date: Date): number {\n const ms = date.getTime() - DELPHI_EPOCH.getTime();\n return ms / (24 * 60 * 60 * 1000);\n}\n\n/**\n * Reads project time info from a parsed FLP\n * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)\n */\nexport function readProjectTimeInfo(parsed: ParsedFlp): ProjectTimeInfo {\n const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);\n\n if (!timestampEvent || timestampEvent.payload.length < 16) {\n return {\n creationDate: null,\n workTimeSeconds: null,\n };\n }\n\n const reader = new BinaryReader(timestampEvent.payload);\n const createdOnDays = reader.readF64LE();\n const timeSpentDays = reader.readF64LE();\n\n return {\n creationDate: delphiToDate(createdOnDays),\n workTimeSeconds: timeSpentDays * 24 * 60 * 60, // Convert days to seconds\n };\n}\n\n/**\n * Writes project time info to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n * \n * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)\n */\nexport function writeProjectTimeInfo(\n parsed: ParsedFlp,\n info: Partial<ProjectTimeInfo>\n): ParsedFlp {\n // Read current values to preserve unmodified fields\n const current = readProjectTimeInfo(parsed);\n\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.PROJECT_TIMESTAMP) {\n return event;\n }\n\n const writer = new BinaryWriter();\n\n // Write creation date\n if (info.creationDate !== undefined && info.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(info.creationDate));\n } else if (current.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(current.creationDate));\n } else {\n writer.writeF64LE(0);\n }\n\n // Write work time\n if (info.workTimeSeconds !== undefined && info.workTimeSeconds !== null) {\n writer.writeF64LE(info.workTimeSeconds / (24 * 60 * 60)); // Convert seconds to days\n } else if (current.workTimeSeconds !== null) {\n writer.writeF64LE(current.workTimeSeconds / (24 * 60 * 60));\n } else {\n writer.writeF64LE(0);\n }\n\n return {\n ...event,\n payload: writer.toBuffer(),\n };\n });\n}\n\n// ============================================================================\n// Samples\n// ============================================================================\n\n/**\n * Reference to a sample in the project\n */\nexport interface SampleRef {\n /** Index of the event in the events array */\n eventIndex: number;\n /** File path of the sample */\n path: string;\n}\n\n/**\n * Lists all samples in the project\n */\nexport function listSamples(parsed: ParsedFlp): SampleRef[] {\n const samples: SampleRef[] = [];\n\n parsed.events.forEach((event: FlpEvent, index: number) => {\n if (event.id === EVENT_ID.CHANNEL_SAMPLE_PATH) {\n const path = getEventString(event, parsed.useUnicode);\n if (path && path.length > 0) {\n samples.push({\n eventIndex: index,\n path,\n });\n }\n }\n });\n\n return samples;\n}\n\n/**\n * Rewrites sample paths using a mapper function\n */\nexport function rewriteSamplePaths(\n parsed: ParsedFlp,\n mapper: (oldPath: string) => string\n): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.CHANNEL_SAMPLE_PATH) {\n return event;\n }\n\n const oldPath = getEventString(event, parsed.useUnicode);\n if (!oldPath || oldPath.length === 0) {\n return event;\n }\n\n const newPath = mapper(oldPath);\n if (newPath === oldPath) {\n return event;\n }\n\n return {\n ...event,\n payload: createTextPayload(newPath, parsed.useUnicode),\n };\n });\n}\n\n// ============================================================================\n// Plugins (VST)\n// ============================================================================\n\n/**\n * Reference to a plugin in the project\n */\nexport interface PluginRef {\n /** Plugin name (may be null if not available) */\n name: string | null;\n /** Plugin vendor/provider (may be null if not stored in file) */\n vendor: string | null;\n}\n\n/**\n * VST Plugin Event internal IDs (from plugin.py VSTPluginEvent)\n */\nconst VST_EVENT_ID = {\n MIDI: 1,\n FLAGS: 2,\n IO: 30,\n INPUTS: 31,\n OUTPUTS: 32,\n PLUGIN_INFO: 50,\n FOUR_CC: 51,\n GUID: 52,\n STATE: 53,\n NAME: 54,\n PLUGIN_PATH: 55,\n VENDOR: 56,\n} as const;\n\n/**\n * Parses a VST plugin data event to extract name and vendor\n * Based on VSTPluginEvent structure in PyFLP\n */\nfunction parseVstPluginData(payload: Buffer): { name: string | null; vendor: string | null } {\n if (payload.length < 4) {\n return { name: null, vendor: null };\n }\n\n const reader = new BinaryReader(payload);\n\n // First 4 bytes: wrapper marker.\n // Historically seen values include 8/10/11, but newer FL versions can use\n // other markers (e.g. 12). We read and ignore the exact value here because\n // this parser is only called for \"Fruity Wrapper\" plugin data.\n const typeMarker = reader.readU32LE();\n void typeMarker;\n\n let name: string | null = null;\n let vendor: string | null = null;\n\n // Parse sub-events\n while (reader.remaining() >= 12) {\n // At minimum: 4 (id) + 8 (size)\n const subEventId = reader.readU32LE();\n const dataSize = reader.readU32LE(); // Lower 32 bits of size\n const dataSizeHigh = reader.readU32LE(); // Upper 32 bits (usually 0)\n\n // Combine for 64-bit size (though in practice it's always small)\n const actualSize = dataSize + dataSizeHigh * 0x100000000;\n\n if (actualSize > reader.remaining()) {\n break;\n }\n\n const data = reader.readBytes(actualSize);\n\n // Extract name and vendor\n if (subEventId === VST_EVENT_ID.NAME && actualSize > 0) {\n name = data.toString(\"utf8\").replace(/\\0/g, \"\");\n } else if (subEventId === VST_EVENT_ID.VENDOR && actualSize > 0) {\n vendor = data.toString(\"utf8\").replace(/\\0/g, \"\");\n }\n }\n\n return { name, vendor };\n}\n\n/**\n * Lists all plugins (VST and native) in the project\n * Returns plugin name and vendor where available\n */\nexport function listPlugins(parsed: ParsedFlp): PluginRef[] {\n const plugins: PluginRef[] = [];\n const seenPlugins = new Set<string>();\n\n // Track current channel context\n let currentInternalName: string | null = null;\n let currentPluginName: string | null = null;\n\n for (const event of parsed.events) {\n // Track channel new events as boundaries\n if (event.id === EVENT_ID.CHANNEL_NEW) {\n // Reset context for new channel\n if (currentInternalName || currentPluginName) {\n // Save previous channel's plugin if it wasn't a VST (no plugin data)\n const key = `${currentInternalName ?? \"\"}:${currentPluginName ?? \"\"}`;\n if (!seenPlugins.has(key) && currentInternalName && currentInternalName !== \"Fruity Wrapper\") {\n seenPlugins.add(key);\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null, // Native plugins don't store vendor\n });\n }\n }\n currentInternalName = null;\n currentPluginName = null;\n }\n\n // Track internal name (identifies plugin type)\n if (event.id === EVENT_ID.PLUGIN_INTERNAL_NAME) {\n currentInternalName = getEventString(event, parsed.useUnicode);\n }\n\n // Track display name\n if (event.id === EVENT_ID.PLUGIN_NAME) {\n currentPluginName = getEventString(event, parsed.useUnicode);\n }\n\n // Parse VST plugin data\n if (event.id === EVENT_ID.PLUGIN_DATA && currentInternalName === \"Fruity Wrapper\") {\n const vstData = parseVstPluginData(event.payload);\n if (vstData.name) {\n const key = `vst:${vstData.name}:${vstData.vendor ?? \"\"}`;\n if (!seenPlugins.has(key)) {\n seenPlugins.add(key);\n plugins.push({\n name: vstData.name,\n vendor: vstData.vendor,\n });\n }\n }\n // Reset context after processing VST\n currentInternalName = null;\n currentPluginName = null;\n }\n }\n\n // Handle last channel if any\n if (currentInternalName && currentInternalName !== \"Fruity Wrapper\") {\n const key = `${currentInternalName}:${currentPluginName ?? \"\"}`;\n if (!seenPlugins.has(key)) {\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null,\n });\n }\n }\n\n return plugins;\n}\n\n// ============================================================================\n// FL Version\n// ============================================================================\n\n/**\n * Gets the FL Studio version string from the project\n */\nexport function getFlVersion(parsed: ParsedFlp): string {\n return parsed.flVersion;\n}\n\n/**\n * Gets the PPQ (Pulses Per Quarter note) from the header\n */\nexport function getPPQ(parsed: ParsedFlp): number {\n // PPQ is at offset 12-13 in the header (bytes 12-13 of the 14-byte FLhd chunk)\n const reader = new BinaryReader(parsed.headerChunkBytes);\n reader.seek(12);\n return reader.readU16LE();\n}\n"],"mappings":"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","names":["SmartBuffer","protobuf","headerSize"]}
1
+ {"version":3,"sources":["../src/generated/events.generated.ts","../src/io/BinaryReader.ts","../src/io/BinaryWriter.ts","../src/parser/FlpParser.ts","../src/api/ProjectApi.ts"],"sourcesContent":["/**\r\n * Auto-generated event constants from PyFLP\r\n * FL Studio .flp file event IDs and types\r\n *\r\n * Event ID ranges:\r\n * - BYTE (0-63): 1 byte data\r\n * - WORD (64-127): 2 bytes data\r\n * - DWORD (128-191): 4 bytes data\r\n * - TEXT (192-207): Variable length strings (+ some IDs in DATA range)\r\n * - DATA (208+): Variable length binary data\r\n */\r\n\r\n// Base offsets for event ID ranges\r\nexport const BYTE = 0;\r\nexport const WORD = 64;\r\nexport const DWORD = 128;\r\nexport const TEXT = 192;\r\nexport const DATA = 208;\r\n\r\n// IDs for TEXT events that use UTF-16LE in newer FL versions\r\nexport const NEW_TEXT_IDS = [\r\n TEXT + 49, // ArrangementID.Name\r\n TEXT + 39, // DisplayGroupID.Name\r\n TEXT + 47, // TrackID.Name\r\n] as const;\r\n\r\n/**\r\n * Event IDs used by FL Studio project files\r\n * Based on PyFLP's event definitions\r\n */\r\nexport const EVENT_ID = {\r\n // === Project IDs (from project.py) ===\r\n PROJECT_LOOP_ACTIVE: 9,\r\n PROJECT_SHOW_INFO: 10,\r\n PROJECT__VOLUME: 12,\r\n PROJECT_PAN_LAW: 23,\r\n PROJECT_LICENSED: 28,\r\n PROJECT__TEMPO_COARSE: WORD + 2, // 66\r\n PROJECT_PITCH: WORD + 16, // 80\r\n PROJECT__TEMPO_FINE: WORD + 29, // 93\r\n PROJECT_CUR_GROUP_ID: DWORD + 18,\r\n PROJECT_TEMPO: DWORD + 28,\r\n PROJECT_FL_BUILD: DWORD + 31,\r\n PROJECT_TITLE: TEXT + 2, // 194\r\n PROJECT_COMMENTS: TEXT + 3, // 195\r\n PROJECT_URL: TEXT + 5, // 197\r\n PROJECT__RTF_COMMENTS: TEXT + 6, // 198\r\n PROJECT_FL_VERSION: TEXT + 7, // 199\r\n PROJECT_LICENSEE: TEXT + 8, // 200\r\n PROJECT_DATA_PATH: TEXT + 10, // 202\r\n PROJECT_GENRE: TEXT + 14, // 206\r\n PROJECT_ARTISTS: TEXT + 15, // 207\r\n PROJECT_TIMESTAMP: DATA + 29, // 237\r\n\r\n // === Channel IDs (from channel.py) ===\r\n CHANNEL_IS_ENABLED: 0,\r\n CHANNEL__VOL_BYTE: 2,\r\n CHANNEL__PAN_BYTE: 3,\r\n CHANNEL_ZIPPED: 15,\r\n CHANNEL_PING_PONG_LOOP: 20,\r\n CHANNEL_TYPE: 21,\r\n CHANNEL_ROUTED_TO: 22,\r\n CHANNEL_IS_LOCKED: 32,\r\n CHANNEL_NEW: WORD,\r\n CHANNEL_FREQ_TILT: WORD + 5, // 69\r\n CHANNEL_FX_FLAGS: WORD + 6, // 70\r\n CHANNEL_CUTOFF: WORD + 7, // 71\r\n CHANNEL__VOL_WORD: WORD + 8, // 72\r\n CHANNEL__PAN_WORD: WORD + 9, // 73\r\n CHANNEL_PREAMP: WORD + 10, // 74\r\n CHANNEL_FADE_OUT: WORD + 11, // 75\r\n CHANNEL_FADE_IN: WORD + 12, // 76\r\n CHANNEL_RESONANCE: WORD + 19, // 83\r\n CHANNEL_STEREO_DELAY: WORD + 21, // 85\r\n CHANNEL_POGO: WORD + 22, // 86\r\n CHANNEL_TIME_SHIFT: WORD + 25, // 89\r\n CHANNEL_CHILDREN: WORD + 30, // 94\r\n CHANNEL_SWING: WORD + 33, // 97\r\n CHANNEL_RING_MOD: DWORD + 3,\r\n CHANNEL_CUT_GROUP: DWORD + 4,\r\n CHANNEL_ROOT_NOTE: DWORD + 7,\r\n CHANNEL_DELAY_MOD_XY: DWORD + 10,\r\n CHANNEL_REVERB: DWORD + 11,\r\n CHANNEL__STRETCH_TIME: DWORD + 12,\r\n CHANNEL_FINE_TUNE: DWORD + 14,\r\n CHANNEL_SAMPLER_FLAGS: DWORD + 15,\r\n CHANNEL_LAYER_FLAGS: DWORD + 16,\r\n CHANNEL_GROUP_NUM: DWORD + 17,\r\n CHANNEL_AU_SAMPLE_RATE: DWORD + 25,\r\n CHANNEL__NAME: TEXT,\r\n CHANNEL_SAMPLE_PATH: TEXT + 4, // 196\r\n CHANNEL_DELAY: DATA + 1, // 209\r\n CHANNEL_PARAMETERS: DATA + 7, // 215\r\n CHANNEL_ENVELOPE_LFO: DATA + 10, // 218\r\n CHANNEL_LEVELS: DATA + 11, // 219\r\n CHANNEL_POLYPHONY: DATA + 13, // 221\r\n CHANNEL_TRACKING: DATA + 20, // 228\r\n CHANNEL_LEVEL_ADJUSTS: DATA + 21, // 229\r\n CHANNEL_AUTOMATION: DATA + 26, // 234\r\n\r\n // === Plugin IDs (from plugin.py) ===\r\n PLUGIN_COLOR: DWORD,\r\n PLUGIN_ICON: DWORD + 27,\r\n PLUGIN_INTERNAL_NAME: TEXT + 9, // 201\r\n PLUGIN_NAME: TEXT + 11, // 203\r\n PLUGIN_WRAPPER: DATA + 4, // 212\r\n PLUGIN_DATA: DATA + 5, // 213\r\n\r\n // === Display Group IDs ===\r\n DISPLAY_GROUP_NAME: TEXT + 39, // 231\r\n\r\n // === Rack IDs ===\r\n RACK_SWING: 11,\r\n RACK__FIT_TO_STEPS: 13,\r\n RACK_WINDOW_HEIGHT: DWORD + 5,\r\n\r\n // === Mixer IDs (from mixer.py) ===\r\n MIXER_SLOT_INDEX: WORD + 34, // 98\r\n MIXER_INSERT_ICON: WORD + 31, // 95\r\n MIXER_INSERT_OUTPUT: DWORD + 19,\r\n MIXER_INSERT_COLOR: DWORD + 21,\r\n MIXER_INSERT_INPUT: DWORD + 26,\r\n MIXER_INSERT_NAME: TEXT + 12, // 204\r\n MIXER_INSERT_ROUTING: DATA + 27, // 235\r\n MIXER_INSERT_FLAGS: DATA + 28, // 236\r\n} as const;\r\n\r\n/**\r\n * Event data type classification\r\n */\r\nexport type EventKind =\r\n | 'u8'\r\n | 'i8'\r\n | 'u16'\r\n | 'i16'\r\n | 'u32'\r\n | 'i32'\r\n | 'f32'\r\n | 'text'\r\n | 'data'\r\n | 'unknown';\r\n\r\n/**\r\n * Maps event IDs to their data type\r\n * Used for proper serialization/deserialization\r\n */\r\nexport const EVENT_KIND: Record<number, EventKind> = {\r\n // Project events\r\n [EVENT_ID.PROJECT_LOOP_ACTIVE]: 'u8',\r\n [EVENT_ID.PROJECT_SHOW_INFO]: 'u8',\r\n [EVENT_ID.PROJECT__VOLUME]: 'u8',\r\n [EVENT_ID.PROJECT_PAN_LAW]: 'u8',\r\n [EVENT_ID.PROJECT_LICENSED]: 'u8',\r\n [EVENT_ID.PROJECT__TEMPO_COARSE]: 'u16',\r\n [EVENT_ID.PROJECT_PITCH]: 'i16',\r\n [EVENT_ID.PROJECT__TEMPO_FINE]: 'u16',\r\n [EVENT_ID.PROJECT_CUR_GROUP_ID]: 'i32',\r\n [EVENT_ID.PROJECT_TEMPO]: 'u32',\r\n [EVENT_ID.PROJECT_FL_BUILD]: 'u32',\r\n [EVENT_ID.PROJECT_TITLE]: 'text',\r\n [EVENT_ID.PROJECT_COMMENTS]: 'text',\r\n [EVENT_ID.PROJECT_URL]: 'text',\r\n [EVENT_ID.PROJECT__RTF_COMMENTS]: 'text',\r\n [EVENT_ID.PROJECT_FL_VERSION]: 'text',\r\n [EVENT_ID.PROJECT_LICENSEE]: 'text',\r\n [EVENT_ID.PROJECT_DATA_PATH]: 'text',\r\n [EVENT_ID.PROJECT_GENRE]: 'text',\r\n [EVENT_ID.PROJECT_ARTISTS]: 'text',\r\n [EVENT_ID.PROJECT_TIMESTAMP]: 'data',\r\n\r\n // Channel events\r\n [EVENT_ID.CHANNEL_IS_ENABLED]: 'u8',\r\n [EVENT_ID.CHANNEL__VOL_BYTE]: 'u8',\r\n [EVENT_ID.CHANNEL__PAN_BYTE]: 'u8',\r\n [EVENT_ID.CHANNEL_ZIPPED]: 'u8',\r\n [EVENT_ID.CHANNEL_PING_PONG_LOOP]: 'u8',\r\n [EVENT_ID.CHANNEL_TYPE]: 'u8',\r\n [EVENT_ID.CHANNEL_ROUTED_TO]: 'i8',\r\n [EVENT_ID.CHANNEL_IS_LOCKED]: 'u8',\r\n [EVENT_ID.CHANNEL_NEW]: 'u16',\r\n [EVENT_ID.CHANNEL_FREQ_TILT]: 'u16',\r\n [EVENT_ID.CHANNEL_FX_FLAGS]: 'u16',\r\n [EVENT_ID.CHANNEL_CUTOFF]: 'u16',\r\n [EVENT_ID.CHANNEL__VOL_WORD]: 'u16',\r\n [EVENT_ID.CHANNEL__PAN_WORD]: 'u16',\r\n [EVENT_ID.CHANNEL_PREAMP]: 'u16',\r\n [EVENT_ID.CHANNEL_FADE_OUT]: 'u16',\r\n [EVENT_ID.CHANNEL_FADE_IN]: 'u16',\r\n [EVENT_ID.CHANNEL_RESONANCE]: 'u16',\r\n [EVENT_ID.CHANNEL_STEREO_DELAY]: 'u16',\r\n [EVENT_ID.CHANNEL_POGO]: 'u16',\r\n [EVENT_ID.CHANNEL_TIME_SHIFT]: 'u16',\r\n [EVENT_ID.CHANNEL_CHILDREN]: 'u16',\r\n [EVENT_ID.CHANNEL_SWING]: 'u16',\r\n [EVENT_ID.CHANNEL_RING_MOD]: 'u32',\r\n [EVENT_ID.CHANNEL_CUT_GROUP]: 'u32',\r\n [EVENT_ID.CHANNEL_ROOT_NOTE]: 'u32',\r\n [EVENT_ID.CHANNEL_DELAY_MOD_XY]: 'u32',\r\n [EVENT_ID.CHANNEL_REVERB]: 'u32',\r\n [EVENT_ID.CHANNEL__STRETCH_TIME]: 'f32',\r\n [EVENT_ID.CHANNEL_FINE_TUNE]: 'i32',\r\n [EVENT_ID.CHANNEL_SAMPLER_FLAGS]: 'u32',\r\n [EVENT_ID.CHANNEL_LAYER_FLAGS]: 'u32',\r\n [EVENT_ID.CHANNEL_GROUP_NUM]: 'i32',\r\n [EVENT_ID.CHANNEL_AU_SAMPLE_RATE]: 'u32',\r\n [EVENT_ID.CHANNEL__NAME]: 'text',\r\n [EVENT_ID.CHANNEL_SAMPLE_PATH]: 'text',\r\n [EVENT_ID.CHANNEL_DELAY]: 'data',\r\n [EVENT_ID.CHANNEL_PARAMETERS]: 'data',\r\n [EVENT_ID.CHANNEL_ENVELOPE_LFO]: 'data',\r\n [EVENT_ID.CHANNEL_LEVELS]: 'data',\r\n [EVENT_ID.CHANNEL_POLYPHONY]: 'data',\r\n [EVENT_ID.CHANNEL_TRACKING]: 'data',\r\n [EVENT_ID.CHANNEL_LEVEL_ADJUSTS]: 'data',\r\n [EVENT_ID.CHANNEL_AUTOMATION]: 'data',\r\n\r\n // Plugin events\r\n [EVENT_ID.PLUGIN_COLOR]: 'u32',\r\n [EVENT_ID.PLUGIN_ICON]: 'u32',\r\n [EVENT_ID.PLUGIN_INTERNAL_NAME]: 'text',\r\n [EVENT_ID.PLUGIN_NAME]: 'text',\r\n [EVENT_ID.PLUGIN_WRAPPER]: 'data',\r\n [EVENT_ID.PLUGIN_DATA]: 'data',\r\n\r\n // Display Group events\r\n [EVENT_ID.DISPLAY_GROUP_NAME]: 'data',\r\n\r\n // Rack events\r\n [EVENT_ID.RACK_SWING]: 'u8',\r\n [EVENT_ID.RACK__FIT_TO_STEPS]: 'u8',\r\n [EVENT_ID.RACK_WINDOW_HEIGHT]: 'u32',\r\n\r\n // Mixer events\r\n [EVENT_ID.MIXER_SLOT_INDEX]: 'u16',\r\n [EVENT_ID.MIXER_INSERT_ICON]: 'i16',\r\n [EVENT_ID.MIXER_INSERT_OUTPUT]: 'i32',\r\n [EVENT_ID.MIXER_INSERT_COLOR]: 'u32',\r\n [EVENT_ID.MIXER_INSERT_INPUT]: 'i32',\r\n [EVENT_ID.MIXER_INSERT_NAME]: 'text',\r\n [EVENT_ID.MIXER_INSERT_ROUTING]: 'data',\r\n [EVENT_ID.MIXER_INSERT_FLAGS]: 'data',\r\n};\r\n\r\n/**\r\n * Determines the event kind based on ID\r\n * Falls back to range-based detection if not explicitly mapped\r\n */\r\nexport function getEventKind(eventId: number): EventKind {\r\n // Check explicit mapping first\r\n const mapped = EVENT_KIND[eventId];\r\n if (mapped !== undefined) {\r\n return mapped;\r\n }\r\n\r\n // Fall back to range-based detection\r\n if (eventId < WORD) {\r\n return 'u8';\r\n } else if (eventId < DWORD) {\r\n return 'u16';\r\n } else if (eventId < TEXT) {\r\n return 'u32';\r\n } else if (eventId < DATA || NEW_TEXT_IDS.includes(eventId as 241 | 231 | 239)) {\r\n return 'text';\r\n } else {\r\n return 'data';\r\n }\r\n}\r\n\r\n/**\r\n * Returns the fixed size for BYTE/WORD/DWORD events\r\n * Returns -1 for variable-length events (TEXT/DATA)\r\n */\r\nexport function getEventFixedSize(eventId: number): number {\r\n if (eventId < WORD) {\r\n return 1;\r\n } else if (eventId < DWORD) {\r\n return 2;\r\n } else if (eventId < TEXT) {\r\n return 4;\r\n }\r\n return -1; // Variable length\r\n}\r\n","/**\n * Binary reader for FL Studio .flp files\n * Uses smart-buffer for binary reading and protobufjs for VarInt decoding\n */\n\nimport protobuf from \"protobufjs/minimal.js\";\nimport { SmartBuffer } from \"smart-buffer\";\n\nexport class BinaryReader {\n private sb: SmartBuffer;\n private source: Buffer;\n\n constructor(buffer: Buffer) {\n this.source = buffer;\n this.sb = SmartBuffer.fromBuffer(buffer);\n }\n\n /**\n * Returns current read position\n */\n tell(): number {\n return this.sb.readOffset;\n }\n\n /**\n * Seeks to an absolute position\n */\n seek(pos: number): void {\n this.sb.readOffset = pos;\n }\n\n /**\n * Seeks relative to current position\n */\n skip(offset: number): void {\n this.sb.readOffset += offset;\n }\n\n /**\n * Returns total buffer length\n */\n length(): number {\n return this.sb.length;\n }\n\n /**\n * Returns remaining bytes to read\n */\n remaining(): number {\n return this.sb.remaining();\n }\n\n /**\n * Returns true if there are more bytes to read\n */\n hasMore(): boolean {\n return this.sb.remaining() > 0;\n }\n\n /**\n * Reads an unsigned 8-bit integer\n */\n readU8(): number {\n return this.sb.readUInt8();\n }\n\n /**\n * Reads a signed 8-bit integer\n */\n readI8(): number {\n return this.sb.readInt8();\n }\n\n /**\n * Reads an unsigned 16-bit integer (little-endian)\n */\n readU16LE(): number {\n return this.sb.readUInt16LE();\n }\n\n /**\n * Reads a signed 16-bit integer (little-endian)\n */\n readI16LE(): number {\n return this.sb.readInt16LE();\n }\n\n /**\n * Reads an unsigned 32-bit integer (little-endian)\n */\n readU32LE(): number {\n return this.sb.readUInt32LE();\n }\n\n /**\n * Reads a signed 32-bit integer (little-endian)\n */\n readI32LE(): number {\n return this.sb.readInt32LE();\n }\n\n /**\n * Reads a 32-bit float (little-endian)\n */\n readF32LE(): number {\n return this.sb.readFloatLE();\n }\n\n /**\n * Reads a 64-bit float (little-endian)\n */\n readF64LE(): number {\n return this.sb.readDoubleLE();\n }\n\n /**\n * Reads raw bytes\n */\n readBytes(length: number): Buffer {\n return this.sb.readBuffer(length);\n }\n\n /**\n * Reads a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n readVarInt(): number {\n // Get remaining buffer from current position\n const remainingBuffer = this.source.subarray(this.sb.readOffset);\n const reader = protobuf.Reader.create(remainingBuffer);\n const value = reader.uint32();\n // Advance our position by how many bytes protobufjs consumed\n this.sb.readOffset += reader.pos;\n return value;\n }\n\n /**\n * Reads an ASCII string (null-terminated)\n */\n readAsciiString(length: number): string {\n const str = this.sb.readString(length, \"ascii\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Reads a UTF-16LE string (null-terminated)\n */\n readUtf16LEString(length: number): string {\n const bytes = this.sb.readBuffer(length);\n const str = bytes.toString(\"utf16le\");\n // Remove null terminator if present\n const nullPos = str.indexOf(\"\\0\");\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n\n /**\n * Peeks at bytes without advancing position\n */\n peek(length: number): Buffer {\n const pos = this.sb.readOffset;\n const data = this.sb.readBuffer(length);\n this.sb.readOffset = pos;\n return data;\n }\n\n /**\n * Peeks at a single byte without advancing position\n */\n peekU8(): number {\n const pos = this.sb.readOffset;\n const value = this.sb.readUInt8();\n this.sb.readOffset = pos;\n return value;\n }\n\n /**\n * Returns the internal buffer\n */\n toBuffer(): Buffer {\n return this.source;\n }\n}\n","/**\n * Binary writer for FL Studio .flp files\n * Uses smart-buffer for binary writing and protobufjs for VarInt encoding\n */\n\nimport { SmartBuffer } from \"smart-buffer\";\nimport protobuf from \"protobufjs/minimal.js\";\n\nexport class BinaryWriter {\n private sb: SmartBuffer;\n\n constructor() {\n this.sb = new SmartBuffer();\n }\n\n /**\n * Returns current write position (total bytes written)\n */\n tell(): number {\n return this.sb.writeOffset;\n }\n\n /**\n * Writes an unsigned 8-bit integer\n */\n writeU8(value: number): void {\n this.sb.writeUInt8(value & 0xff);\n }\n\n /**\n * Writes a signed 8-bit integer\n */\n writeI8(value: number): void {\n this.sb.writeInt8(value);\n }\n\n /**\n * Writes an unsigned 16-bit integer (little-endian)\n */\n writeU16LE(value: number): void {\n this.sb.writeUInt16LE(value & 0xffff);\n }\n\n /**\n * Writes a signed 16-bit integer (little-endian)\n */\n writeI16LE(value: number): void {\n this.sb.writeInt16LE(value);\n }\n\n /**\n * Writes an unsigned 32-bit integer (little-endian)\n */\n writeU32LE(value: number): void {\n this.sb.writeUInt32LE(value >>> 0);\n }\n\n /**\n * Writes a signed 32-bit integer (little-endian)\n */\n writeI32LE(value: number): void {\n this.sb.writeInt32LE(value);\n }\n\n /**\n * Writes a 32-bit float (little-endian)\n */\n writeF32LE(value: number): void {\n this.sb.writeFloatLE(value);\n }\n\n /**\n * Writes a 64-bit float (little-endian)\n */\n writeF64LE(value: number): void {\n this.sb.writeDoubleLE(value);\n }\n\n /**\n * Writes raw bytes\n */\n writeBytes(data: Buffer): void {\n this.sb.writeBuffer(data);\n }\n\n /**\n * Writes a VarInt (variable-length integer) using protobufjs\n * FL Studio uses this encoding for TEXT and DATA event sizes\n */\n writeVarInt(value: number): void {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n const encoded = writer.finish();\n this.sb.writeBuffer(Buffer.from(encoded));\n }\n\n /**\n * Writes an ASCII string (null-terminated)\n */\n writeAsciiString(str: string): void {\n this.sb.writeString(str + \"\\0\", \"ascii\");\n }\n\n /**\n * Writes a UTF-16LE string (null-terminated)\n */\n writeUtf16LEString(str: string): void {\n const buf = Buffer.from(str + \"\\0\", \"utf16le\");\n this.sb.writeBuffer(buf);\n }\n\n /**\n * Gets the final buffer\n */\n toBuffer(): Buffer {\n return this.sb.toBuffer();\n }\n}\n\n/**\n * Encodes a VarInt to a Buffer using protobufjs (standalone utility)\n */\nexport function encodeVarInt(value: number): Buffer {\n const writer = protobuf.Writer.create();\n writer.uint32(value);\n return Buffer.from(writer.finish());\n}\n\n/**\n * Calculates the byte size needed for a VarInt\n */\nexport function varIntSize(value: number): number {\n if (value < 0x80) return 1;\n if (value < 0x4000) return 2;\n if (value < 0x200000) return 3;\n if (value < 0x10000000) return 4;\n return 5;\n}\n","/**\n * FL Studio .flp file parser and serializer\n * Conservative patch approach: preserves all unknown data byte-for-byte\n *\n * Uses look-ahead scoring to handle events in the DWORD range (128-191)\n * that may use either fixed 4-byte encoding or VarInt-encoded variable\n * length payloads. For each unknown event in this range, both\n * interpretations are scored by parsing ahead ~200 bytes and counting\n * how many TEXT/DATA events (with valid VarInt sizes) are reachable.\n * The interpretation producing more valid TEXT/DATA events wins.\n */\n\nimport {\n DWORD,\n EVENT_ID,\n EVENT_KIND,\n TEXT,\n WORD,\n getEventKind,\n} from '../generated/events.generated.js';\nimport { BinaryReader } from '../io/BinaryReader.js';\nimport { BinaryWriter } from '../io/BinaryWriter.js';\n\n/** Event IDs in the DWORD range (128-191) that are explicitly mapped in EVENT_KIND. */\nconst KNOWN_DWORD_IDS: ReadonlySet<number> = new Set(\n Object.keys(EVENT_KIND)\n .map(Number)\n .filter((id) => id >= DWORD && id < TEXT),\n);\n\n/**\n * Represents a single event in an FLP file\n * Stores raw bytes to enable conservative patching\n */\nexport interface FlpEvent {\n /** Event ID (0-255) */\n id: number;\n /** Data type classification */\n kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'text' | 'data' | 'unknown';\n /** Original header bytes (ID + size for variable-length events) */\n header: Buffer;\n /** Event payload data */\n payload: Buffer;\n}\n\n/**\n * Represents a parsed FLP file\n * Contains all data needed for conservative round-trip\n */\nexport interface ParsedFlp {\n /** FLhd chunk (header) - 14 bytes total */\n headerChunkBytes: Buffer;\n /** FLdt chunk header (magic + size placeholder) - 8 bytes */\n fldtHeaderBytes: Buffer;\n /** All events from the FLdt chunk */\n events: FlpEvent[];\n /** Any trailing bytes after events (should be empty) */\n trailingBytes: Buffer | undefined;\n /** Detected FL version for string encoding */\n flVersion: string;\n /** Whether to use UTF-16LE for strings (FL >= 11.5) */\n useUnicode: boolean;\n}\n\n/**\n * Header structure constants\n */\nconst FLP_HEADER_MAGIC = 'FLhd';\nconst FLP_DATA_MAGIC = 'FLdt';\nconst FLP_HEADER_SIZE = 6; // Expected header chunk size\nconst LOOKAHEAD_BYTES = 200;\nconst LOOKAHEAD_VARINT_MAX = 100_000;\n\n/**\n * Reads a VarInt directly from a raw buffer at the given offset.\n * Returns `{ size, bytesRead }` or `{ size: -1, bytesRead: 0 }` on failure.\n */\nfunction readVarIntRaw(\n buf: Buffer,\n off: number,\n end: number,\n): { size: number; bytesRead: number } {\n let shift = 0;\n let size = 0;\n const start = off;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (off >= end) return { size: -1, bytesRead: 0 };\n const byte = buf[off]!;\n off++;\n size |= (byte & 0x7f) << shift;\n shift += 7;\n if (!(byte & 0x80)) break;\n }\n return { size, bytesRead: off - start };\n}\n\n/**\n * Scores an alignment hypothesis by parsing ahead from `off` for up to\n * `bytesToCheck` bytes. TEXT/DATA events with valid VarInt sizes are\n * the strongest signal of correct alignment; runs of low-value unknown\n * BYTE events (typical UTF-16-LE misread) are penalised.\n */\nfunction scoreAlignment(buf: Buffer, off: number, end: number, bytesToCheck: number): number {\n const limit = Math.min(off + bytesToCheck, end);\n let textDataCount = 0;\n let consecutiveSmallByte = 0;\n let maxConsecutiveSmallByte = 0;\n\n while (off < limit) {\n const id = buf[off]!;\n\n if (id < WORD) {\n off += 2;\n if (id < 32 && !KNOWN_DWORD_IDS.has(id)) {\n consecutiveSmallByte++;\n maxConsecutiveSmallByte = Math.max(maxConsecutiveSmallByte, consecutiveSmallByte);\n } else {\n consecutiveSmallByte = 0;\n }\n } else if (id < DWORD) {\n off += 3;\n consecutiveSmallByte = 0;\n } else if (id < TEXT) {\n off += 5;\n consecutiveSmallByte = 0;\n } else {\n const vi = readVarIntRaw(buf, off + 1, end);\n if (vi.size >= 0 && vi.size < LOOKAHEAD_VARINT_MAX && off + 1 + vi.bytesRead + vi.size <= end) {\n textDataCount++;\n off = off + 1 + vi.bytesRead + vi.size;\n } else {\n return -100;\n }\n consecutiveSmallByte = 0;\n }\n }\n\n return textDataCount * 10 - maxConsecutiveSmallByte * 3;\n}\n\n/**\n * Parses an FL Studio project file\n *\n * @param buffer - Raw file data\n * @returns Parsed FLP structure\n * @throws Error if file is corrupted or invalid\n */\nexport function parseFlp(buffer: Buffer): ParsedFlp {\n const reader = new BinaryReader(buffer);\n\n // Read FLhd header chunk\n const headerMagic = reader.readBytes(4).toString('ascii');\n if (headerMagic !== FLP_HEADER_MAGIC) {\n throw new Error(`Invalid header magic: expected 'FLhd', got '${headerMagic}'`);\n }\n\n const headerSize = reader.readU32LE();\n if (headerSize !== FLP_HEADER_SIZE) {\n throw new Error(`Invalid header size: expected ${FLP_HEADER_SIZE}, got ${headerSize}`);\n }\n\n const format = reader.readI16LE();\n const channelCount = reader.readU16LE();\n const ppq = reader.readU16LE();\n\n // Validate format (0 = normal project)\n if (format < -1 || format > 0x50) {\n throw new Error(`Invalid file format: ${format}`);\n }\n\n // Store complete header chunk (14 bytes)\n const headerChunkBytes = buffer.subarray(0, 14);\n\n // Read FLdt data chunk header\n const dataMagic = reader.readBytes(4).toString('ascii');\n if (dataMagic !== FLP_DATA_MAGIC) {\n throw new Error(`Invalid data magic: expected 'FLdt', got '${dataMagic}'`);\n }\n\n const eventsSize = reader.readU32LE();\n\n // Validate events size\n const expectedFileSize = 22 + eventsSize; // 14 (FLhd) + 8 (FLdt header) + events\n if (buffer.length !== expectedFileSize) {\n throw new Error(\n `Data chunk size mismatch: expected file size ${expectedFileSize}, got ${buffer.length}`,\n );\n }\n\n // Store FLdt header (8 bytes)\n const fldtHeaderBytes = buffer.subarray(14, 22);\n\n // Parse events\n const events: FlpEvent[] = [];\n let flVersion = '0.0.0';\n let useUnicode = false;\n\n const eventsStart = reader.tell();\n const eventsEnd = eventsStart + eventsSize;\n\n while (reader.tell() < eventsEnd) {\n const eventStart = reader.tell();\n const eventId = reader.readU8();\n\n let payloadSize: number;\n let headerSize: number;\n\n if (eventId < WORD) {\n payloadSize = 1;\n headerSize = 1;\n } else if (eventId < DWORD) {\n payloadSize = 2;\n headerSize = 1;\n } else if (eventId < TEXT) {\n if (KNOWN_DWORD_IDS.has(eventId)) {\n payloadSize = 4;\n headerSize = 1;\n } else {\n // Unknown DWORD-range event: use look-ahead to decide encoding\n const afterDword = eventStart + 1 + 4;\n const vi = readVarIntRaw(buffer, eventStart + 1, eventsEnd);\n\n if (vi.size < 0 || vi.size > LOOKAHEAD_VARINT_MAX || eventStart + 1 + vi.bytesRead + vi.size > eventsEnd) {\n payloadSize = 4;\n headerSize = 1;\n } else if (vi.size === 3) {\n // VarInt(3) produces the same total footprint as DWORD; prefer DWORD\n payloadSize = 4;\n headerSize = 1;\n } else {\n const afterVarInt = eventStart + 1 + vi.bytesRead + vi.size;\n const dwordScore = scoreAlignment(buffer, afterDword, eventsEnd, LOOKAHEAD_BYTES);\n const varIntScore = scoreAlignment(buffer, afterVarInt, eventsEnd, LOOKAHEAD_BYTES);\n\n if (varIntScore > dwordScore + 2) {\n payloadSize = vi.size;\n reader.seek(eventStart + 1 + vi.bytesRead);\n headerSize = 1 + vi.bytesRead;\n } else {\n payloadSize = 4;\n headerSize = 1;\n }\n }\n }\n } else {\n // TEXT / DATA: always VarInt-encoded size\n const sizeStart = reader.tell();\n payloadSize = reader.readVarInt();\n headerSize = 1 + (reader.tell() - sizeStart);\n }\n\n const payload = reader.readBytes(payloadSize);\n const header = buffer.subarray(eventStart, eventStart + headerSize);\n const kind = getEventKind(eventId);\n\n const event: FlpEvent = {\n id: eventId,\n kind,\n header: Buffer.from(header),\n payload: Buffer.from(payload),\n };\n\n events.push(event);\n\n // Detect FL version from the first FLVersion event\n if (eventId === EVENT_ID.PROJECT_FL_VERSION && flVersion === '0.0.0') {\n const candidate = payload.toString('ascii').replace(/\\0/g, '').trim();\n if (/^\\d+(\\.\\d+)+$/.test(candidate)) {\n flVersion = candidate;\n const parts = flVersion.split('.').map((p) => parseInt(p, 10));\n const major = parts[0] ?? 0;\n const minor = parts[1] ?? 0;\n useUnicode = parts.length >= 2 && (major > 11 || (major === 11 && minor >= 5));\n }\n }\n }\n\n // Check for trailing bytes\n let trailingBytes: Buffer | undefined;\n if (reader.remaining() > 0) {\n trailingBytes = reader.readBytes(reader.remaining());\n }\n\n return {\n headerChunkBytes: Buffer.from(headerChunkBytes),\n fldtHeaderBytes: Buffer.from(fldtHeaderBytes),\n events,\n trailingBytes,\n flVersion,\n useUnicode,\n };\n}\n\n/**\n * Serializes a parsed FLP back to a buffer\n * Recalculates FLdt chunk size if events have changed\n *\n * @param parsed - Parsed FLP structure\n * @returns Serialized buffer\n */\nexport function serializeFlp(parsed: ParsedFlp): Buffer {\n const writer = new BinaryWriter();\n\n // Write FLhd header chunk (preserved byte-for-byte)\n writer.writeBytes(parsed.headerChunkBytes);\n\n // Calculate total events size\n let eventsSize = 0;\n for (const event of parsed.events) {\n eventsSize += serializeEvent(event).length;\n }\n\n // Add trailing bytes if present\n if (parsed.trailingBytes) {\n eventsSize += parsed.trailingBytes.length;\n }\n\n // Write FLdt header with updated size\n writer.writeBytes(Buffer.from(FLP_DATA_MAGIC, 'ascii'));\n writer.writeU32LE(eventsSize);\n\n // Write all events\n for (const event of parsed.events) {\n writer.writeBytes(serializeEvent(event));\n }\n\n // Write trailing bytes if present\n if (parsed.trailingBytes) {\n writer.writeBytes(parsed.trailingBytes);\n }\n\n return writer.toBuffer();\n}\n\n/**\n * Serializes a single event to bytes.\n *\n * For unchanged events the original header is emitted verbatim, ensuring a\n * byte-identical round-trip. For newly created events (empty header) the\n * header is rebuilt: TEXT/DATA events get a VarInt-encoded size prefix,\n * BYTE/WORD/DWORD events get only the 1-byte ID.\n */\nfunction serializeEvent(event: FlpEvent): Buffer {\n const writer = new BinaryWriter();\n\n if (event.header.length > 0) {\n writer.writeBytes(event.header);\n } else {\n writer.writeU8(event.id);\n if (event.id >= TEXT) {\n writer.writeVarInt(event.payload.length);\n }\n }\n\n writer.writeBytes(event.payload);\n\n return writer.toBuffer();\n}\n\n/**\n * Creates a new event with the given ID and payload.\n *\n * For TEXT/DATA events (id >= 192) the header includes a VarInt size prefix.\n * For BYTE/WORD/DWORD events the header is a single byte (the event ID).\n */\nexport function createEvent(id: number, payload: Buffer): FlpEvent {\n const kind = getEventKind(id);\n\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(id);\n\n if (id >= TEXT) {\n headerWriter.writeVarInt(payload.length);\n }\n\n return {\n id,\n kind,\n header: headerWriter.toBuffer(),\n payload: Buffer.from(payload),\n };\n}\n\n/**\n * Applies a patcher function to all events\n * Returns a new ParsedFlp with patched events\n *\n * @param parsed - Original parsed FLP\n * @param patcher - Function that transforms each event\n * @returns New ParsedFlp with patched events\n */\nexport function patchEvents(\n parsed: ParsedFlp,\n patcher: (event: FlpEvent, index: number) => FlpEvent,\n): ParsedFlp {\n const patchedEvents = parsed.events.map((event, index) => {\n const patched = patcher(event, index);\n\n // If payload changed and the original event used a VarInt-encoded header\n // (header > 1 byte), recalculate the header with the new size.\n if (patched.payload !== event.payload && event.header.length > 1) {\n const headerWriter = new BinaryWriter();\n headerWriter.writeU8(patched.id);\n headerWriter.writeVarInt(patched.payload.length);\n return {\n ...patched,\n header: headerWriter.toBuffer(),\n };\n }\n\n return patched;\n });\n\n return {\n ...parsed,\n events: patchedEvents,\n };\n}\n\n/**\n * Finds all events with a specific ID\n */\nexport function findEvents(parsed: ParsedFlp, eventId: number): FlpEvent[] {\n return parsed.events.filter((e) => e.id === eventId);\n}\n\n/**\n * Finds the first event with a specific ID\n */\nexport function findFirstEvent(parsed: ParsedFlp, eventId: number): FlpEvent | undefined {\n return parsed.events.find((e) => e.id === eventId);\n}\n\n/**\n * Gets event payload as a string (handles encoding)\n */\nexport function getEventString(event: FlpEvent, useUnicode: boolean): string {\n if (event.kind !== 'text') {\n throw new Error(`Event ${event.id} is not a text event`);\n }\n\n if (useUnicode) {\n const str = event.payload.toString('utf16le');\n const nullPos = str.indexOf('\\0');\n return nullPos === -1 ? str : str.substring(0, nullPos);\n } else {\n const str = event.payload.toString('ascii');\n const nullPos = str.indexOf('\\0');\n return nullPos === -1 ? str : str.substring(0, nullPos);\n }\n}\n\n/**\n * Creates a text payload with proper encoding\n */\nexport function createTextPayload(text: string, useUnicode: boolean): Buffer {\n if (useUnicode) {\n return Buffer.from(text + '\\0', 'utf16le');\n } else {\n return Buffer.from(text + '\\0', 'ascii');\n }\n}\n\n/**\n * Gets event payload as a number\n */\nexport function getEventNumber(event: FlpEvent): number {\n const reader = new BinaryReader(event.payload);\n\n switch (event.kind) {\n case 'u8':\n return reader.readU8();\n case 'i8':\n return reader.readI8();\n case 'u16':\n return reader.readU16LE();\n case 'i16':\n return reader.readI16LE();\n case 'u32':\n return reader.readU32LE();\n case 'i32':\n return reader.readI32LE();\n case 'f32':\n return reader.readF32LE();\n default:\n throw new Error(`Cannot get number from ${event.kind} event`);\n }\n}\n\n/**\n * Creates a numeric payload\n */\nexport function createNumberPayload(\n value: number,\n kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32',\n): Buffer {\n const writer = new BinaryWriter();\n\n switch (kind) {\n case 'u8':\n writer.writeU8(value);\n break;\n case 'i8':\n writer.writeI8(value);\n break;\n case 'u16':\n writer.writeU16LE(value);\n break;\n case 'i16':\n writer.writeI16LE(value);\n break;\n case 'u32':\n writer.writeU32LE(value);\n break;\n case 'i32':\n writer.writeI32LE(value);\n break;\n case 'f32':\n writer.writeF32LE(value);\n break;\n }\n\n return writer.toBuffer();\n}\n","/**\n * High-level API for reading and modifying FL Studio project files\n * Provides typed access to project metadata, samples, plugins, and time info\n */\n\nimport { EVENT_ID } from '../generated/events.generated.js';\nimport { BinaryReader } from '../io/BinaryReader.js';\nimport { BinaryWriter } from '../io/BinaryWriter.js';\nimport {\n createNumberPayload,\n createTextPayload,\n findFirstEvent,\n type FlpEvent,\n getEventNumber,\n getEventString,\n type ParsedFlp,\n patchEvents,\n} from '../parser/FlpParser.js';\n\n// ============================================================================\n// Project Metadata\n// ============================================================================\n\n/**\n * Project metadata (name, description, artist, genre, BPM)\n */\nexport interface ProjectMeta {\n name: string | null;\n description: string | null;\n artist: string | null;\n genre: string | null;\n bpm: number | null;\n}\n\n/**\n * Reads the BPM value from the parsed FLP.\n *\n * Primary: PROJECT_TEMPO (DWORD+28) stores BPM × 1000 as u32.\n * Legacy fallback: _TEMPO_COARSE (WORD+2) + _TEMPO_FINE (WORD+29).\n */\nfunction readBpm(parsed: ParsedFlp): number | null {\n const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);\n if (tempoEvent) {\n return getEventNumber(tempoEvent) / 1000;\n }\n\n const coarseEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_COARSE);\n if (coarseEvent) {\n let bpm = coarseEvent.payload.readUInt16LE(0);\n const fineEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_FINE);\n if (fineEvent) {\n bpm += fineEvent.payload.readUInt16LE(0) / 1000;\n }\n return bpm;\n }\n\n return null;\n}\n\n/**\n * Reads project metadata from a parsed FLP\n */\nexport function readProjectMeta(parsed: ParsedFlp): ProjectMeta {\n const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);\n const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);\n const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);\n const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);\n\n return {\n name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,\n description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,\n artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,\n genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,\n bpm: readBpm(parsed),\n };\n}\n\n/**\n * Writes project metadata to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n */\nexport function writeProjectMeta(parsed: ParsedFlp, meta: Partial<ProjectMeta>): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n // Patch title\n if (meta.name !== undefined && event.id === EVENT_ID.PROJECT_TITLE) {\n return {\n ...event,\n payload: createTextPayload(meta.name ?? '', parsed.useUnicode),\n };\n }\n\n // Patch comments/description\n if (meta.description !== undefined && event.id === EVENT_ID.PROJECT_COMMENTS) {\n return {\n ...event,\n payload: createTextPayload(meta.description ?? '', parsed.useUnicode),\n };\n }\n\n // Patch artists\n if (meta.artist !== undefined && event.id === EVENT_ID.PROJECT_ARTISTS) {\n return {\n ...event,\n payload: createTextPayload(meta.artist ?? '', parsed.useUnicode),\n };\n }\n\n // Patch genre\n if (meta.genre !== undefined && event.id === EVENT_ID.PROJECT_GENRE) {\n return {\n ...event,\n payload: createTextPayload(meta.genre ?? '', parsed.useUnicode),\n };\n }\n\n // Patch tempo (stored as BPM * 1000)\n if (meta.bpm && Math.abs(meta.bpm) > 0 && event.id === EVENT_ID.PROJECT_TEMPO) {\n return {\n ...event,\n payload: createNumberPayload(Math.round(Math.abs(meta.bpm) * 1000), 'u32'),\n };\n }\n\n return event;\n });\n}\n\n// ============================================================================\n// Project Time Info\n// ============================================================================\n\n/**\n * Project creation date and work time\n */\nexport interface ProjectTimeInfo {\n creationDate: Date | null;\n workTimeSeconds: number | null;\n}\n\n// Delphi epoch: December 30, 1899\nconst DELPHI_EPOCH = new Date(1899, 11, 30);\n\n/**\n * Converts Delphi timestamp (days since epoch) to Date\n */\nfunction delphiToDate(days: number): Date {\n const ms = days * 24 * 60 * 60 * 1000;\n return new Date(DELPHI_EPOCH.getTime() + ms);\n}\n\n/**\n * Converts Date to Delphi timestamp (days since epoch)\n */\nfunction dateToDelphiDays(date: Date): number {\n const ms = date.getTime() - DELPHI_EPOCH.getTime();\n return ms / (24 * 60 * 60 * 1000);\n}\n\n/**\n * Reads project time info from a parsed FLP\n * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)\n */\nexport function readProjectTimeInfo(parsed: ParsedFlp): ProjectTimeInfo {\n const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);\n\n if (!timestampEvent || timestampEvent.payload.length < 16) {\n return {\n creationDate: null,\n workTimeSeconds: null,\n };\n }\n\n const reader = new BinaryReader(timestampEvent.payload);\n const createdOnDays = reader.readF64LE();\n const timeSpentDays = reader.readF64LE();\n\n return {\n creationDate: delphiToDate(createdOnDays),\n workTimeSeconds: timeSpentDays * 24 * 60 * 60, // Convert days to seconds\n };\n}\n\n/**\n * Writes project time info to a parsed FLP\n * Only modifies fields that are provided (non-undefined)\n *\n * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)\n */\nexport function writeProjectTimeInfo(parsed: ParsedFlp, info: Partial<ProjectTimeInfo>): ParsedFlp {\n // Read current values to preserve unmodified fields\n const current = readProjectTimeInfo(parsed);\n\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.PROJECT_TIMESTAMP) {\n return event;\n }\n\n const writer = new BinaryWriter();\n\n // Write creation date\n if (info.creationDate !== undefined && info.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(info.creationDate));\n } else if (current.creationDate !== null) {\n writer.writeF64LE(dateToDelphiDays(current.creationDate));\n } else {\n writer.writeF64LE(0);\n }\n\n // Write work time\n if (info.workTimeSeconds !== undefined && info.workTimeSeconds !== null) {\n writer.writeF64LE(info.workTimeSeconds / (24 * 60 * 60)); // Convert seconds to days\n } else if (current.workTimeSeconds !== null) {\n writer.writeF64LE(current.workTimeSeconds / (24 * 60 * 60));\n } else {\n writer.writeF64LE(0);\n }\n\n return {\n ...event,\n payload: writer.toBuffer(),\n };\n });\n}\n\n// ============================================================================\n// Samples\n// ============================================================================\n\n/**\n * Reference to a sample in the project\n */\nexport interface SampleRef {\n /** Index of the event in the events array */\n eventIndex: number;\n /** File path of the sample */\n path: string;\n}\n\n/**\n * Lists all samples in the project\n */\nexport function listSamples(parsed: ParsedFlp): SampleRef[] {\n const samples: SampleRef[] = [];\n\n parsed.events.forEach((event: FlpEvent, index: number) => {\n if (event.id === EVENT_ID.CHANNEL_SAMPLE_PATH) {\n const path = getEventString(event, parsed.useUnicode);\n if (path && path.length > 0) {\n samples.push({\n eventIndex: index,\n path,\n });\n }\n }\n });\n\n return samples;\n}\n\n/**\n * Rewrites sample paths using a mapper function\n */\nexport function rewriteSamplePaths(\n parsed: ParsedFlp,\n mapper: (oldPath: string) => string,\n): ParsedFlp {\n return patchEvents(parsed, (event: FlpEvent) => {\n if (event.id !== EVENT_ID.CHANNEL_SAMPLE_PATH) {\n return event;\n }\n\n const oldPath = getEventString(event, parsed.useUnicode);\n if (!oldPath || oldPath.length === 0) {\n return event;\n }\n\n const newPath = mapper(oldPath);\n if (newPath === oldPath) {\n return event;\n }\n\n return {\n ...event,\n payload: createTextPayload(newPath, parsed.useUnicode),\n };\n });\n}\n\n// ============================================================================\n// Plugins (VST)\n// ============================================================================\n\n/**\n * Reference to a plugin in the project\n */\nexport interface PluginRef {\n /** Plugin name (may be null if not available) */\n name: string | null;\n /** Plugin vendor/provider (may be null if not stored in file) */\n vendor: string | null;\n}\n\n/**\n * VST Plugin Event internal IDs (from plugin.py VSTPluginEvent)\n */\nconst VST_EVENT_ID = {\n MIDI: 1,\n FLAGS: 2,\n IO: 30,\n INPUTS: 31,\n OUTPUTS: 32,\n PLUGIN_INFO: 50,\n FOUR_CC: 51,\n GUID: 52,\n STATE: 53,\n NAME: 54,\n PLUGIN_PATH: 55,\n VENDOR: 56,\n} as const;\n\n/**\n * Parses a VST plugin data event to extract name and vendor\n * Based on VSTPluginEvent structure in PyFLP\n */\nfunction parseVstPluginData(payload: Buffer): { name: string | null; vendor: string | null } {\n if (payload.length < 4) {\n return { name: null, vendor: null };\n }\n\n const reader = new BinaryReader(payload);\n\n // First 4 bytes: wrapper marker.\n // Historically seen values include 8/10/11, but newer FL versions can use\n // other markers (e.g. 12). We read and ignore the exact value here because\n // this parser is only called for \"Fruity Wrapper\" plugin data.\n const typeMarker = reader.readU32LE();\n void typeMarker;\n\n let name: string | null = null;\n let vendor: string | null = null;\n\n // Parse sub-events\n while (reader.remaining() >= 12) {\n // At minimum: 4 (id) + 8 (size)\n const subEventId = reader.readU32LE();\n const dataSize = reader.readU32LE(); // Lower 32 bits of size\n const dataSizeHigh = reader.readU32LE(); // Upper 32 bits (usually 0)\n\n // Combine for 64-bit size (though in practice it's always small)\n const actualSize = dataSize + dataSizeHigh * 0x100000000;\n\n if (actualSize > reader.remaining()) {\n break;\n }\n\n const data = reader.readBytes(actualSize);\n\n // Extract name and vendor\n if (subEventId === VST_EVENT_ID.NAME && actualSize > 0) {\n name = data.toString('utf8').replace(/\\0/g, '');\n } else if (subEventId === VST_EVENT_ID.VENDOR && actualSize > 0) {\n vendor = data.toString('utf8').replace(/\\0/g, '');\n }\n }\n\n return { name, vendor };\n}\n\n/**\n * Lists all plugins (VST and native) in the project\n * Returns plugin name and vendor where available\n */\nexport function listPlugins(parsed: ParsedFlp): PluginRef[] {\n const plugins: PluginRef[] = [];\n const seenPlugins = new Set<string>();\n\n // Track current channel context\n let currentInternalName: string | null = null;\n let currentPluginName: string | null = null;\n\n for (const event of parsed.events) {\n // Track channel new events as boundaries\n if (event.id === EVENT_ID.CHANNEL_NEW) {\n // Reset context for new channel\n if (currentInternalName || currentPluginName) {\n // Save previous channel's plugin if it wasn't a VST (no plugin data)\n const key = `${currentInternalName ?? ''}:${currentPluginName ?? ''}`;\n if (\n !seenPlugins.has(key) &&\n currentInternalName &&\n currentInternalName !== 'Fruity Wrapper'\n ) {\n seenPlugins.add(key);\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null, // Native plugins don't store vendor\n });\n }\n }\n currentInternalName = null;\n currentPluginName = null;\n }\n\n // Track internal name (identifies plugin type)\n if (event.id === EVENT_ID.PLUGIN_INTERNAL_NAME) {\n currentInternalName = getEventString(event, parsed.useUnicode);\n }\n\n // Track display name\n if (event.id === EVENT_ID.PLUGIN_NAME) {\n currentPluginName = getEventString(event, parsed.useUnicode);\n }\n\n // Parse VST plugin data\n if (event.id === EVENT_ID.PLUGIN_DATA && currentInternalName === 'Fruity Wrapper') {\n const vstData = parseVstPluginData(event.payload);\n if (vstData.name) {\n const key = `vst:${vstData.name}:${vstData.vendor ?? ''}`;\n if (!seenPlugins.has(key)) {\n seenPlugins.add(key);\n plugins.push({\n name: vstData.name,\n vendor: vstData.vendor,\n });\n }\n }\n // Reset context after processing VST\n currentInternalName = null;\n currentPluginName = null;\n }\n }\n\n // Handle last channel if any\n if (currentInternalName && currentInternalName !== 'Fruity Wrapper') {\n const key = `${currentInternalName}:${currentPluginName ?? ''}`;\n if (!seenPlugins.has(key)) {\n plugins.push({\n name: currentPluginName || currentInternalName,\n vendor: null,\n });\n }\n }\n\n return plugins;\n}\n\n// ============================================================================\n// FL Version\n// ============================================================================\n\n/**\n * Gets the FL Studio version string from the project\n */\nexport function getFlVersion(parsed: ParsedFlp): string {\n return parsed.flVersion;\n}\n\n/**\n * Gets the PPQ (Pulses Per Quarter note) from the header\n */\nexport function getPPQ(parsed: ParsedFlp): number {\n // PPQ is at offset 12-13 in the header (bytes 12-13 of the 14-byte FLhd chunk)\n const reader = new BinaryReader(parsed.headerChunkBytes);\n reader.seek(12);\n return reader.readU16LE();\n}\n"],"mappings":"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","names":["SmartBuffer","protobuf","headerSize"]}
@@ -2,7 +2,7 @@
2
2
  * High-level API for reading and modifying FL Studio project files
3
3
  * Provides typed access to project metadata, samples, plugins, and time info
4
4
  */
5
- import { type ParsedFlp } from "../parser/FlpParser.js";
5
+ import { type ParsedFlp } from '../parser/FlpParser.js';
6
6
  /**
7
7
  * Project metadata (name, description, artist, genre, BPM)
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectApi.d.ts","sourceRoot":"","sources":["../../../src/api/ProjectApi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAOL,KAAK,SAAS,EAEf,MAAM,wBAAwB,CAAC;AAMhC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CAc9D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,CA4CzF;AAMD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAqBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,eAAe,CAkBtE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,GAC7B,SAAS,CAkCX;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAgB1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAClC,SAAS,CAqBX;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAoED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAoE1D;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAKhD"}
1
+ {"version":3,"file":"ProjectApi.d.ts","sourceRoot":"","sources":["../../../src/api/ProjectApi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAOL,KAAK,SAAS,EAEf,MAAM,wBAAwB,CAAC;AAMhC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AA2BD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CAa9D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,CA4CzF;AAMD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAqBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,eAAe,CAkBtE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,CAkCjG;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAgB1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAClC,SAAS,CAqBX;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAoED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAwE1D;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAKhD"}
@@ -107,7 +107,7 @@ export declare const EVENT_ID: {
107
107
  /**
108
108
  * Event data type classification
109
109
  */
110
- export type EventKind = "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32" | "text" | "data" | "unknown";
110
+ export type EventKind = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'text' | 'data' | 'unknown';
111
111
  /**
112
112
  * Maps event IDs to their data type
113
113
  * Used for proper serialization/deserialization
@@ -1 +1 @@
1
- {"version":3,"file":"events.generated.d.ts","sourceRoot":"","sources":["../../../src/generated/events.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,eAAO,MAAM,IAAI,IAAI,CAAC;AACtB,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,KAAK,MAAM,CAAC;AACzB,eAAO,MAAM,IAAI,MAAM,CAAC;AACxB,eAAO,MAAM,IAAI,MAAM,CAAC;AAGxB,eAAO,MAAM,YAAY,mCAIf,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+FX,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,SAAS,GACjB,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,MAAM,GACN,MAAM,GACN,SAAS,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAgGhD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAmBvD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASzD"}
1
+ {"version":3,"file":"events.generated.d.ts","sourceRoot":"","sources":["../../../src/generated/events.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,eAAO,MAAM,IAAI,IAAI,CAAC;AACtB,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,KAAK,MAAM,CAAC;AACzB,eAAO,MAAM,IAAI,MAAM,CAAC;AACxB,eAAO,MAAM,IAAI,MAAM,CAAC;AAGxB,eAAO,MAAM,YAAY,mCAIf,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+FX,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,SAAS,GACjB,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,MAAM,GACN,MAAM,GACN,SAAS,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CA+FhD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAmBvD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASzD"}
@@ -1,6 +1,13 @@
1
1
  /**
2
2
  * FL Studio .flp file parser and serializer
3
3
  * Conservative patch approach: preserves all unknown data byte-for-byte
4
+ *
5
+ * Uses look-ahead scoring to handle events in the DWORD range (128-191)
6
+ * that may use either fixed 4-byte encoding or VarInt-encoded variable
7
+ * length payloads. For each unknown event in this range, both
8
+ * interpretations are scored by parsing ahead ~200 bytes and counting
9
+ * how many TEXT/DATA events (with valid VarInt sizes) are reachable.
10
+ * The interpretation producing more valid TEXT/DATA events wins.
4
11
  */
5
12
  /**
6
13
  * Represents a single event in an FLP file
@@ -10,7 +17,7 @@ export interface FlpEvent {
10
17
  /** Event ID (0-255) */
11
18
  id: number;
12
19
  /** Data type classification */
13
- kind: "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32" | "text" | "data" | "unknown";
20
+ kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'text' | 'data' | 'unknown';
14
21
  /** Original header bytes (ID + size for variable-length events) */
15
22
  header: Buffer;
16
23
  /** Event payload data */
@@ -51,7 +58,10 @@ export declare function parseFlp(buffer: Buffer): ParsedFlp;
51
58
  */
52
59
  export declare function serializeFlp(parsed: ParsedFlp): Buffer;
53
60
  /**
54
- * Creates a new event with the given ID and payload
61
+ * Creates a new event with the given ID and payload.
62
+ *
63
+ * For TEXT/DATA events (id >= 192) the header includes a VarInt size prefix.
64
+ * For BYTE/WORD/DWORD events the header is a single byte (the event ID).
55
65
  */
56
66
  export declare function createEvent(id: number, payload: Buffer): FlpEvent;
57
67
  /**
@@ -86,5 +96,5 @@ export declare function getEventNumber(event: FlpEvent): number;
86
96
  /**
87
97
  * Creates a numeric payload
88
98
  */
89
- export declare function createNumberPayload(value: number, kind: "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32"): Buffer;
99
+ export declare function createNumberPayload(value: number, kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32'): Buffer;
90
100
  //# sourceMappingURL=FlpParser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FlpParser.d.ts","sourceRoot":"","sources":["../../../src/parser/FlpParser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,uBAAuB;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACxF,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,wDAAwD;IACxD,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAgIlD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAgCtD;AA0BD;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAiBjE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,QAAQ,GACpD,SAAS,CAsBX;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAEzE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAEvF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAc3E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAM3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAqBtD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GACxD,MAAM,CA4BR"}
1
+ {"version":3,"file":"FlpParser.d.ts","sourceRoot":"","sources":["../../../src/parser/FlpParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoBH;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,uBAAuB;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACxF,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,wDAAwD;IACxD,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;CACrB;AA+ED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAgJlD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAgCtD;AA2BD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAgBjE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,QAAQ,GACpD,SAAS,CAuBX;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAEzE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAEvF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAc3E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAM3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAqBtD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GACxD,MAAM,CA4BR"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dump-all-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dump-all-info.d.ts","sourceRoot":"","sources":["../../test/dump-all-info.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-flp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Minimalist TypeScript library for parsing and modifying FL Studio project files (.flp)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "typecheck": "tsc --noEmit",
28
28
  "test": "vitest run",
29
29
  "test:watch": "vitest",
30
+ "test:dump-info": "node --loader ts-node/esm test/dump-all-info.ts",
30
31
  "lint": "eslint .",
31
32
  "format": "prettier -w .",
32
33
  "commit": "git add . && git commit -m",