ts-flp 1.0.2 → 1.0.4

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 ADDED
@@ -0,0 +1,1102 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BinaryReader: () => BinaryReader,
34
+ BinaryWriter: () => BinaryWriter,
35
+ EVENT_ID: () => EVENT_ID,
36
+ EVENT_KIND: () => EVENT_KIND,
37
+ createEvent: () => createEvent,
38
+ createNumberPayload: () => createNumberPayload,
39
+ createTextPayload: () => createTextPayload,
40
+ encodeVarInt: () => encodeVarInt,
41
+ findEvents: () => findEvents,
42
+ findFirstEvent: () => findFirstEvent,
43
+ getEventFixedSize: () => getEventFixedSize,
44
+ getEventKind: () => getEventKind,
45
+ getEventNumber: () => getEventNumber,
46
+ getEventString: () => getEventString,
47
+ getFlVersion: () => getFlVersion,
48
+ getPPQ: () => getPPQ,
49
+ listPlugins: () => listPlugins,
50
+ listSamples: () => listSamples,
51
+ parseFlp: () => parseFlp,
52
+ patchEvents: () => patchEvents,
53
+ readProjectMeta: () => readProjectMeta,
54
+ readProjectTimeInfo: () => readProjectTimeInfo,
55
+ rewriteSamplePaths: () => rewriteSamplePaths,
56
+ serializeFlp: () => serializeFlp,
57
+ varIntSize: () => varIntSize,
58
+ writeProjectMeta: () => writeProjectMeta,
59
+ writeProjectTimeInfo: () => writeProjectTimeInfo
60
+ });
61
+ module.exports = __toCommonJS(index_exports);
62
+
63
+ // src/generated/events.generated.ts
64
+ var WORD = 64;
65
+ var DWORD = 128;
66
+ var TEXT = 192;
67
+ var DATA = 208;
68
+ var NEW_TEXT_IDS = [
69
+ TEXT + 49,
70
+ // ArrangementID.Name
71
+ TEXT + 39,
72
+ // DisplayGroupID.Name
73
+ TEXT + 47
74
+ // TrackID.Name
75
+ ];
76
+ var EVENT_ID = {
77
+ // === Project IDs (from project.py) ===
78
+ PROJECT_LOOP_ACTIVE: 9,
79
+ PROJECT_SHOW_INFO: 10,
80
+ PROJECT__VOLUME: 12,
81
+ PROJECT_PAN_LAW: 23,
82
+ PROJECT_LICENSED: 28,
83
+ PROJECT__TEMPO_COARSE: WORD + 2,
84
+ // 66
85
+ PROJECT_PITCH: WORD + 16,
86
+ // 80
87
+ PROJECT__TEMPO_FINE: WORD + 29,
88
+ // 93
89
+ PROJECT_CUR_GROUP_ID: DWORD + 18,
90
+ PROJECT_TEMPO: DWORD + 28,
91
+ PROJECT_FL_BUILD: DWORD + 31,
92
+ PROJECT_TITLE: TEXT + 2,
93
+ // 194
94
+ PROJECT_COMMENTS: TEXT + 3,
95
+ // 195
96
+ PROJECT_URL: TEXT + 5,
97
+ // 197
98
+ PROJECT__RTF_COMMENTS: TEXT + 6,
99
+ // 198
100
+ PROJECT_FL_VERSION: TEXT + 7,
101
+ // 199
102
+ PROJECT_LICENSEE: TEXT + 8,
103
+ // 200
104
+ PROJECT_DATA_PATH: TEXT + 10,
105
+ // 202
106
+ PROJECT_GENRE: TEXT + 14,
107
+ // 206
108
+ PROJECT_ARTISTS: TEXT + 15,
109
+ // 207
110
+ PROJECT_TIMESTAMP: DATA + 29,
111
+ // 237
112
+ // === Channel IDs (from channel.py) ===
113
+ CHANNEL_IS_ENABLED: 0,
114
+ CHANNEL__VOL_BYTE: 2,
115
+ CHANNEL__PAN_BYTE: 3,
116
+ CHANNEL_ZIPPED: 15,
117
+ CHANNEL_PING_PONG_LOOP: 20,
118
+ CHANNEL_TYPE: 21,
119
+ CHANNEL_ROUTED_TO: 22,
120
+ CHANNEL_IS_LOCKED: 32,
121
+ CHANNEL_NEW: WORD,
122
+ CHANNEL_FREQ_TILT: WORD + 5,
123
+ // 69
124
+ CHANNEL_FX_FLAGS: WORD + 6,
125
+ // 70
126
+ CHANNEL_CUTOFF: WORD + 7,
127
+ // 71
128
+ CHANNEL__VOL_WORD: WORD + 8,
129
+ // 72
130
+ CHANNEL__PAN_WORD: WORD + 9,
131
+ // 73
132
+ CHANNEL_PREAMP: WORD + 10,
133
+ // 74
134
+ CHANNEL_FADE_OUT: WORD + 11,
135
+ // 75
136
+ CHANNEL_FADE_IN: WORD + 12,
137
+ // 76
138
+ CHANNEL_RESONANCE: WORD + 19,
139
+ // 83
140
+ CHANNEL_STEREO_DELAY: WORD + 21,
141
+ // 85
142
+ CHANNEL_POGO: WORD + 22,
143
+ // 86
144
+ CHANNEL_TIME_SHIFT: WORD + 25,
145
+ // 89
146
+ CHANNEL_CHILDREN: WORD + 30,
147
+ // 94
148
+ CHANNEL_SWING: WORD + 33,
149
+ // 97
150
+ CHANNEL_RING_MOD: DWORD + 3,
151
+ CHANNEL_CUT_GROUP: DWORD + 4,
152
+ CHANNEL_ROOT_NOTE: DWORD + 7,
153
+ CHANNEL_DELAY_MOD_XY: DWORD + 10,
154
+ CHANNEL_REVERB: DWORD + 11,
155
+ CHANNEL__STRETCH_TIME: DWORD + 12,
156
+ CHANNEL_FINE_TUNE: DWORD + 14,
157
+ CHANNEL_SAMPLER_FLAGS: DWORD + 15,
158
+ CHANNEL_LAYER_FLAGS: DWORD + 16,
159
+ CHANNEL_GROUP_NUM: DWORD + 17,
160
+ CHANNEL_AU_SAMPLE_RATE: DWORD + 25,
161
+ CHANNEL__NAME: TEXT,
162
+ CHANNEL_SAMPLE_PATH: TEXT + 4,
163
+ // 196
164
+ CHANNEL_DELAY: DATA + 1,
165
+ // 209
166
+ CHANNEL_PARAMETERS: DATA + 7,
167
+ // 215
168
+ CHANNEL_ENVELOPE_LFO: DATA + 10,
169
+ // 218
170
+ CHANNEL_LEVELS: DATA + 11,
171
+ // 219
172
+ CHANNEL_POLYPHONY: DATA + 13,
173
+ // 221
174
+ CHANNEL_TRACKING: DATA + 20,
175
+ // 228
176
+ CHANNEL_LEVEL_ADJUSTS: DATA + 21,
177
+ // 229
178
+ CHANNEL_AUTOMATION: DATA + 26,
179
+ // 234
180
+ // === Plugin IDs (from plugin.py) ===
181
+ PLUGIN_COLOR: DWORD,
182
+ PLUGIN_ICON: DWORD + 27,
183
+ PLUGIN_INTERNAL_NAME: TEXT + 9,
184
+ // 201
185
+ PLUGIN_NAME: TEXT + 11,
186
+ // 203
187
+ PLUGIN_WRAPPER: DATA + 4,
188
+ // 212
189
+ PLUGIN_DATA: DATA + 5,
190
+ // 213
191
+ // === Display Group IDs ===
192
+ DISPLAY_GROUP_NAME: TEXT + 39,
193
+ // 231
194
+ // === Rack IDs ===
195
+ RACK_SWING: 11,
196
+ RACK__FIT_TO_STEPS: 13,
197
+ RACK_WINDOW_HEIGHT: DWORD + 5,
198
+ // === Mixer IDs (from mixer.py) ===
199
+ MIXER_SLOT_INDEX: WORD + 34,
200
+ // 98
201
+ MIXER_INSERT_ICON: WORD + 31,
202
+ // 95
203
+ MIXER_INSERT_OUTPUT: DWORD + 19,
204
+ MIXER_INSERT_COLOR: DWORD + 21,
205
+ MIXER_INSERT_INPUT: DWORD + 26,
206
+ MIXER_INSERT_NAME: TEXT + 12,
207
+ // 204
208
+ MIXER_INSERT_ROUTING: DATA + 27,
209
+ // 235
210
+ MIXER_INSERT_FLAGS: DATA + 28
211
+ // 236
212
+ };
213
+ var EVENT_KIND = {
214
+ // Project events
215
+ [EVENT_ID.PROJECT_LOOP_ACTIVE]: "u8",
216
+ [EVENT_ID.PROJECT_SHOW_INFO]: "u8",
217
+ [EVENT_ID.PROJECT__VOLUME]: "u8",
218
+ [EVENT_ID.PROJECT_PAN_LAW]: "u8",
219
+ [EVENT_ID.PROJECT_LICENSED]: "u8",
220
+ [EVENT_ID.PROJECT__TEMPO_COARSE]: "u16",
221
+ [EVENT_ID.PROJECT_PITCH]: "i16",
222
+ [EVENT_ID.PROJECT__TEMPO_FINE]: "u16",
223
+ [EVENT_ID.PROJECT_CUR_GROUP_ID]: "i32",
224
+ [EVENT_ID.PROJECT_TEMPO]: "u32",
225
+ [EVENT_ID.PROJECT_FL_BUILD]: "u32",
226
+ [EVENT_ID.PROJECT_TITLE]: "text",
227
+ [EVENT_ID.PROJECT_COMMENTS]: "text",
228
+ [EVENT_ID.PROJECT_URL]: "text",
229
+ [EVENT_ID.PROJECT__RTF_COMMENTS]: "text",
230
+ [EVENT_ID.PROJECT_FL_VERSION]: "text",
231
+ [EVENT_ID.PROJECT_LICENSEE]: "text",
232
+ [EVENT_ID.PROJECT_DATA_PATH]: "text",
233
+ [EVENT_ID.PROJECT_GENRE]: "text",
234
+ [EVENT_ID.PROJECT_ARTISTS]: "text",
235
+ [EVENT_ID.PROJECT_TIMESTAMP]: "data",
236
+ // Channel events
237
+ [EVENT_ID.CHANNEL_IS_ENABLED]: "u8",
238
+ [EVENT_ID.CHANNEL__VOL_BYTE]: "u8",
239
+ [EVENT_ID.CHANNEL__PAN_BYTE]: "u8",
240
+ [EVENT_ID.CHANNEL_ZIPPED]: "u8",
241
+ [EVENT_ID.CHANNEL_PING_PONG_LOOP]: "u8",
242
+ [EVENT_ID.CHANNEL_TYPE]: "u8",
243
+ [EVENT_ID.CHANNEL_ROUTED_TO]: "i8",
244
+ [EVENT_ID.CHANNEL_IS_LOCKED]: "u8",
245
+ [EVENT_ID.CHANNEL_NEW]: "u16",
246
+ [EVENT_ID.CHANNEL_FREQ_TILT]: "u16",
247
+ [EVENT_ID.CHANNEL_FX_FLAGS]: "u16",
248
+ [EVENT_ID.CHANNEL_CUTOFF]: "u16",
249
+ [EVENT_ID.CHANNEL__VOL_WORD]: "u16",
250
+ [EVENT_ID.CHANNEL__PAN_WORD]: "u16",
251
+ [EVENT_ID.CHANNEL_PREAMP]: "u16",
252
+ [EVENT_ID.CHANNEL_FADE_OUT]: "u16",
253
+ [EVENT_ID.CHANNEL_FADE_IN]: "u16",
254
+ [EVENT_ID.CHANNEL_RESONANCE]: "u16",
255
+ [EVENT_ID.CHANNEL_STEREO_DELAY]: "u16",
256
+ [EVENT_ID.CHANNEL_POGO]: "u16",
257
+ [EVENT_ID.CHANNEL_TIME_SHIFT]: "u16",
258
+ [EVENT_ID.CHANNEL_CHILDREN]: "u16",
259
+ [EVENT_ID.CHANNEL_SWING]: "u16",
260
+ [EVENT_ID.CHANNEL_RING_MOD]: "u32",
261
+ [EVENT_ID.CHANNEL_CUT_GROUP]: "u32",
262
+ [EVENT_ID.CHANNEL_ROOT_NOTE]: "u32",
263
+ [EVENT_ID.CHANNEL_DELAY_MOD_XY]: "u32",
264
+ [EVENT_ID.CHANNEL_REVERB]: "u32",
265
+ [EVENT_ID.CHANNEL__STRETCH_TIME]: "f32",
266
+ [EVENT_ID.CHANNEL_FINE_TUNE]: "i32",
267
+ [EVENT_ID.CHANNEL_SAMPLER_FLAGS]: "u32",
268
+ [EVENT_ID.CHANNEL_LAYER_FLAGS]: "u32",
269
+ [EVENT_ID.CHANNEL_GROUP_NUM]: "i32",
270
+ [EVENT_ID.CHANNEL_AU_SAMPLE_RATE]: "u32",
271
+ [EVENT_ID.CHANNEL__NAME]: "text",
272
+ [EVENT_ID.CHANNEL_SAMPLE_PATH]: "text",
273
+ [EVENT_ID.CHANNEL_DELAY]: "data",
274
+ [EVENT_ID.CHANNEL_PARAMETERS]: "data",
275
+ [EVENT_ID.CHANNEL_ENVELOPE_LFO]: "data",
276
+ [EVENT_ID.CHANNEL_LEVELS]: "data",
277
+ [EVENT_ID.CHANNEL_POLYPHONY]: "data",
278
+ [EVENT_ID.CHANNEL_TRACKING]: "data",
279
+ [EVENT_ID.CHANNEL_LEVEL_ADJUSTS]: "data",
280
+ [EVENT_ID.CHANNEL_AUTOMATION]: "data",
281
+ // Plugin events
282
+ [EVENT_ID.PLUGIN_COLOR]: "u32",
283
+ [EVENT_ID.PLUGIN_ICON]: "u32",
284
+ [EVENT_ID.PLUGIN_INTERNAL_NAME]: "text",
285
+ [EVENT_ID.PLUGIN_NAME]: "text",
286
+ [EVENT_ID.PLUGIN_WRAPPER]: "data",
287
+ [EVENT_ID.PLUGIN_DATA]: "data",
288
+ // Display Group events
289
+ [EVENT_ID.DISPLAY_GROUP_NAME]: "data",
290
+ // Rack events
291
+ [EVENT_ID.RACK_SWING]: "u8",
292
+ [EVENT_ID.RACK__FIT_TO_STEPS]: "u8",
293
+ [EVENT_ID.RACK_WINDOW_HEIGHT]: "u32",
294
+ // Mixer events
295
+ [EVENT_ID.MIXER_SLOT_INDEX]: "u16",
296
+ [EVENT_ID.MIXER_INSERT_ICON]: "i16",
297
+ [EVENT_ID.MIXER_INSERT_OUTPUT]: "i32",
298
+ [EVENT_ID.MIXER_INSERT_COLOR]: "u32",
299
+ [EVENT_ID.MIXER_INSERT_INPUT]: "i32",
300
+ [EVENT_ID.MIXER_INSERT_NAME]: "text",
301
+ [EVENT_ID.MIXER_INSERT_ROUTING]: "data",
302
+ [EVENT_ID.MIXER_INSERT_FLAGS]: "data"
303
+ };
304
+ function getEventKind(eventId) {
305
+ const mapped = EVENT_KIND[eventId];
306
+ if (mapped !== void 0) {
307
+ return mapped;
308
+ }
309
+ if (eventId < WORD) {
310
+ return "u8";
311
+ } else if (eventId < DWORD) {
312
+ return "u16";
313
+ } else if (eventId < TEXT) {
314
+ return "u32";
315
+ } else if (eventId < DATA || NEW_TEXT_IDS.includes(eventId)) {
316
+ return "text";
317
+ } else {
318
+ return "data";
319
+ }
320
+ }
321
+ function getEventFixedSize(eventId) {
322
+ if (eventId < WORD) {
323
+ return 1;
324
+ } else if (eventId < DWORD) {
325
+ return 2;
326
+ } else if (eventId < TEXT) {
327
+ return 4;
328
+ }
329
+ return -1;
330
+ }
331
+
332
+ // src/io/BinaryReader.ts
333
+ var import_minimal = __toESM(require("protobufjs/minimal.js"), 1);
334
+ var import_smart_buffer = require("smart-buffer");
335
+ var BinaryReader = class {
336
+ sb;
337
+ source;
338
+ constructor(buffer) {
339
+ this.source = buffer;
340
+ this.sb = import_smart_buffer.SmartBuffer.fromBuffer(buffer);
341
+ }
342
+ /**
343
+ * Returns current read position
344
+ */
345
+ tell() {
346
+ return this.sb.readOffset;
347
+ }
348
+ /**
349
+ * Seeks to an absolute position
350
+ */
351
+ seek(pos) {
352
+ this.sb.readOffset = pos;
353
+ }
354
+ /**
355
+ * Seeks relative to current position
356
+ */
357
+ skip(offset) {
358
+ this.sb.readOffset += offset;
359
+ }
360
+ /**
361
+ * Returns total buffer length
362
+ */
363
+ length() {
364
+ return this.sb.length;
365
+ }
366
+ /**
367
+ * Returns remaining bytes to read
368
+ */
369
+ remaining() {
370
+ return this.sb.remaining();
371
+ }
372
+ /**
373
+ * Returns true if there are more bytes to read
374
+ */
375
+ hasMore() {
376
+ return this.sb.remaining() > 0;
377
+ }
378
+ /**
379
+ * Reads an unsigned 8-bit integer
380
+ */
381
+ readU8() {
382
+ return this.sb.readUInt8();
383
+ }
384
+ /**
385
+ * Reads a signed 8-bit integer
386
+ */
387
+ readI8() {
388
+ return this.sb.readInt8();
389
+ }
390
+ /**
391
+ * Reads an unsigned 16-bit integer (little-endian)
392
+ */
393
+ readU16LE() {
394
+ return this.sb.readUInt16LE();
395
+ }
396
+ /**
397
+ * Reads a signed 16-bit integer (little-endian)
398
+ */
399
+ readI16LE() {
400
+ return this.sb.readInt16LE();
401
+ }
402
+ /**
403
+ * Reads an unsigned 32-bit integer (little-endian)
404
+ */
405
+ readU32LE() {
406
+ return this.sb.readUInt32LE();
407
+ }
408
+ /**
409
+ * Reads a signed 32-bit integer (little-endian)
410
+ */
411
+ readI32LE() {
412
+ return this.sb.readInt32LE();
413
+ }
414
+ /**
415
+ * Reads a 32-bit float (little-endian)
416
+ */
417
+ readF32LE() {
418
+ return this.sb.readFloatLE();
419
+ }
420
+ /**
421
+ * Reads a 64-bit float (little-endian)
422
+ */
423
+ readF64LE() {
424
+ return this.sb.readDoubleLE();
425
+ }
426
+ /**
427
+ * Reads raw bytes
428
+ */
429
+ readBytes(length) {
430
+ return this.sb.readBuffer(length);
431
+ }
432
+ /**
433
+ * Reads a VarInt (variable-length integer) using protobufjs
434
+ * FL Studio uses this encoding for TEXT and DATA event sizes
435
+ */
436
+ readVarInt() {
437
+ const remainingBuffer = this.source.subarray(this.sb.readOffset);
438
+ const reader = import_minimal.default.Reader.create(remainingBuffer);
439
+ const value = reader.uint32();
440
+ this.sb.readOffset += reader.pos;
441
+ return value;
442
+ }
443
+ /**
444
+ * Reads an ASCII string (null-terminated)
445
+ */
446
+ readAsciiString(length) {
447
+ const str = this.sb.readString(length, "ascii");
448
+ const nullPos = str.indexOf("\0");
449
+ return nullPos === -1 ? str : str.substring(0, nullPos);
450
+ }
451
+ /**
452
+ * Reads a UTF-16LE string (null-terminated)
453
+ */
454
+ readUtf16LEString(length) {
455
+ const bytes = this.sb.readBuffer(length);
456
+ const str = bytes.toString("utf16le");
457
+ const nullPos = str.indexOf("\0");
458
+ return nullPos === -1 ? str : str.substring(0, nullPos);
459
+ }
460
+ /**
461
+ * Peeks at bytes without advancing position
462
+ */
463
+ peek(length) {
464
+ const pos = this.sb.readOffset;
465
+ const data = this.sb.readBuffer(length);
466
+ this.sb.readOffset = pos;
467
+ return data;
468
+ }
469
+ /**
470
+ * Peeks at a single byte without advancing position
471
+ */
472
+ peekU8() {
473
+ const pos = this.sb.readOffset;
474
+ const value = this.sb.readUInt8();
475
+ this.sb.readOffset = pos;
476
+ return value;
477
+ }
478
+ /**
479
+ * Returns the internal buffer
480
+ */
481
+ toBuffer() {
482
+ return this.source;
483
+ }
484
+ };
485
+
486
+ // src/io/BinaryWriter.ts
487
+ var import_smart_buffer2 = require("smart-buffer");
488
+ var import_minimal2 = __toESM(require("protobufjs/minimal.js"), 1);
489
+ var BinaryWriter = class {
490
+ sb;
491
+ constructor() {
492
+ this.sb = new import_smart_buffer2.SmartBuffer();
493
+ }
494
+ /**
495
+ * Returns current write position (total bytes written)
496
+ */
497
+ tell() {
498
+ return this.sb.writeOffset;
499
+ }
500
+ /**
501
+ * Writes an unsigned 8-bit integer
502
+ */
503
+ writeU8(value) {
504
+ this.sb.writeUInt8(value & 255);
505
+ }
506
+ /**
507
+ * Writes a signed 8-bit integer
508
+ */
509
+ writeI8(value) {
510
+ this.sb.writeInt8(value);
511
+ }
512
+ /**
513
+ * Writes an unsigned 16-bit integer (little-endian)
514
+ */
515
+ writeU16LE(value) {
516
+ this.sb.writeUInt16LE(value & 65535);
517
+ }
518
+ /**
519
+ * Writes a signed 16-bit integer (little-endian)
520
+ */
521
+ writeI16LE(value) {
522
+ this.sb.writeInt16LE(value);
523
+ }
524
+ /**
525
+ * Writes an unsigned 32-bit integer (little-endian)
526
+ */
527
+ writeU32LE(value) {
528
+ this.sb.writeUInt32LE(value >>> 0);
529
+ }
530
+ /**
531
+ * Writes a signed 32-bit integer (little-endian)
532
+ */
533
+ writeI32LE(value) {
534
+ this.sb.writeInt32LE(value);
535
+ }
536
+ /**
537
+ * Writes a 32-bit float (little-endian)
538
+ */
539
+ writeF32LE(value) {
540
+ this.sb.writeFloatLE(value);
541
+ }
542
+ /**
543
+ * Writes a 64-bit float (little-endian)
544
+ */
545
+ writeF64LE(value) {
546
+ this.sb.writeDoubleLE(value);
547
+ }
548
+ /**
549
+ * Writes raw bytes
550
+ */
551
+ writeBytes(data) {
552
+ this.sb.writeBuffer(data);
553
+ }
554
+ /**
555
+ * Writes a VarInt (variable-length integer) using protobufjs
556
+ * FL Studio uses this encoding for TEXT and DATA event sizes
557
+ */
558
+ writeVarInt(value) {
559
+ const writer = import_minimal2.default.Writer.create();
560
+ writer.uint32(value);
561
+ const encoded = writer.finish();
562
+ this.sb.writeBuffer(Buffer.from(encoded));
563
+ }
564
+ /**
565
+ * Writes an ASCII string (null-terminated)
566
+ */
567
+ writeAsciiString(str) {
568
+ this.sb.writeString(str + "\0", "ascii");
569
+ }
570
+ /**
571
+ * Writes a UTF-16LE string (null-terminated)
572
+ */
573
+ writeUtf16LEString(str) {
574
+ const buf = Buffer.from(str + "\0", "utf16le");
575
+ this.sb.writeBuffer(buf);
576
+ }
577
+ /**
578
+ * Gets the final buffer
579
+ */
580
+ toBuffer() {
581
+ return this.sb.toBuffer();
582
+ }
583
+ };
584
+ function encodeVarInt(value) {
585
+ const writer = import_minimal2.default.Writer.create();
586
+ writer.uint32(value);
587
+ return Buffer.from(writer.finish());
588
+ }
589
+ function varIntSize(value) {
590
+ if (value < 128) return 1;
591
+ if (value < 16384) return 2;
592
+ if (value < 2097152) return 3;
593
+ if (value < 268435456) return 4;
594
+ return 5;
595
+ }
596
+
597
+ // src/parser/FlpParser.ts
598
+ var FLP_HEADER_MAGIC = "FLhd";
599
+ var FLP_DATA_MAGIC = "FLdt";
600
+ var FLP_HEADER_SIZE = 6;
601
+ function parseFlp(buffer) {
602
+ const reader = new BinaryReader(buffer);
603
+ const headerMagic = reader.readBytes(4).toString("ascii");
604
+ if (headerMagic !== FLP_HEADER_MAGIC) {
605
+ throw new Error(`Invalid header magic: expected 'FLhd', got '${headerMagic}'`);
606
+ }
607
+ const headerSize = reader.readU32LE();
608
+ if (headerSize !== FLP_HEADER_SIZE) {
609
+ throw new Error(`Invalid header size: expected ${FLP_HEADER_SIZE}, got ${headerSize}`);
610
+ }
611
+ const format = reader.readI16LE();
612
+ const channelCount = reader.readU16LE();
613
+ const ppq = reader.readU16LE();
614
+ if (format < -1 || format > 80) {
615
+ throw new Error(`Invalid file format: ${format}`);
616
+ }
617
+ const headerChunkBytes = buffer.subarray(0, 14);
618
+ const dataMagic = reader.readBytes(4).toString("ascii");
619
+ if (dataMagic !== FLP_DATA_MAGIC) {
620
+ throw new Error(`Invalid data magic: expected 'FLdt', got '${dataMagic}'`);
621
+ }
622
+ const eventsSize = reader.readU32LE();
623
+ const expectedFileSize = 22 + eventsSize;
624
+ if (buffer.length !== expectedFileSize) {
625
+ throw new Error(
626
+ `Data chunk size mismatch: expected file size ${expectedFileSize}, got ${buffer.length}`
627
+ );
628
+ }
629
+ const fldtHeaderBytes = buffer.subarray(14, 22);
630
+ const events = [];
631
+ let flVersion = "0.0.0";
632
+ let useUnicode = false;
633
+ let detectedVersion = false;
634
+ const eventsStart = reader.tell();
635
+ const eventsEnd = eventsStart + eventsSize;
636
+ while (reader.tell() < eventsEnd) {
637
+ const eventStart = reader.tell();
638
+ const eventId = reader.readU8();
639
+ let payloadSize;
640
+ let headerSize2;
641
+ if (eventId < WORD) {
642
+ payloadSize = 1;
643
+ headerSize2 = 1;
644
+ } else if (eventId < DWORD) {
645
+ payloadSize = 2;
646
+ headerSize2 = 1;
647
+ } else if (eventId < TEXT) {
648
+ payloadSize = 4;
649
+ headerSize2 = 1;
650
+ } else {
651
+ const sizeStart = reader.tell();
652
+ payloadSize = reader.readVarInt();
653
+ headerSize2 = 1 + (reader.tell() - sizeStart);
654
+ }
655
+ const payload = reader.readBytes(payloadSize);
656
+ const header = buffer.subarray(eventStart, eventStart + headerSize2);
657
+ const kind = getEventKind(eventId);
658
+ const event = {
659
+ id: eventId,
660
+ kind,
661
+ header: Buffer.from(header),
662
+ payload: Buffer.from(payload)
663
+ };
664
+ events.push(event);
665
+ if (eventId === EVENT_ID.PROJECT_FL_VERSION && !detectedVersion) {
666
+ const candidate = payload.toString("ascii").replace(/\0/g, "").trim();
667
+ const isSemverLike = /^\d+(\.\d+)+$/.test(candidate);
668
+ if (isSemverLike) {
669
+ flVersion = candidate;
670
+ const parts = flVersion.split(".").map((p) => parseInt(p, 10));
671
+ const major = parts[0] ?? 0;
672
+ const minor = parts[1] ?? 0;
673
+ useUnicode = parts.length >= 2 && (major > 11 || major === 11 && minor >= 5);
674
+ detectedVersion = true;
675
+ }
676
+ }
677
+ }
678
+ let trailingBytes;
679
+ if (reader.remaining() > 0) {
680
+ trailingBytes = reader.readBytes(reader.remaining());
681
+ }
682
+ return {
683
+ headerChunkBytes: Buffer.from(headerChunkBytes),
684
+ fldtHeaderBytes: Buffer.from(fldtHeaderBytes),
685
+ events,
686
+ trailingBytes,
687
+ flVersion,
688
+ useUnicode
689
+ };
690
+ }
691
+ function serializeFlp(parsed) {
692
+ const writer = new BinaryWriter();
693
+ writer.writeBytes(parsed.headerChunkBytes);
694
+ let eventsSize = 0;
695
+ for (const event of parsed.events) {
696
+ eventsSize += serializeEvent(event).length;
697
+ }
698
+ if (parsed.trailingBytes) {
699
+ eventsSize += parsed.trailingBytes.length;
700
+ }
701
+ writer.writeBytes(Buffer.from(FLP_DATA_MAGIC, "ascii"));
702
+ writer.writeU32LE(eventsSize);
703
+ for (const event of parsed.events) {
704
+ writer.writeBytes(serializeEvent(event));
705
+ }
706
+ if (parsed.trailingBytes) {
707
+ writer.writeBytes(parsed.trailingBytes);
708
+ }
709
+ return writer.toBuffer();
710
+ }
711
+ function serializeEvent(event) {
712
+ const writer = new BinaryWriter();
713
+ if (event.header.length > 0) {
714
+ writer.writeBytes(event.header);
715
+ } else {
716
+ writer.writeU8(event.id);
717
+ if (event.id >= TEXT) {
718
+ writer.writeVarInt(event.payload.length);
719
+ }
720
+ }
721
+ writer.writeBytes(event.payload);
722
+ return writer.toBuffer();
723
+ }
724
+ function createEvent(id, payload) {
725
+ const kind = getEventKind(id);
726
+ const headerWriter = new BinaryWriter();
727
+ headerWriter.writeU8(id);
728
+ if (id >= TEXT) {
729
+ headerWriter.writeVarInt(payload.length);
730
+ }
731
+ return {
732
+ id,
733
+ kind,
734
+ header: headerWriter.toBuffer(),
735
+ payload: Buffer.from(payload)
736
+ };
737
+ }
738
+ function patchEvents(parsed, patcher) {
739
+ const patchedEvents = parsed.events.map((event, index) => {
740
+ const patched = patcher(event, index);
741
+ if (patched.payload !== event.payload && patched.id >= TEXT) {
742
+ const headerWriter = new BinaryWriter();
743
+ headerWriter.writeU8(patched.id);
744
+ headerWriter.writeVarInt(patched.payload.length);
745
+ return {
746
+ ...patched,
747
+ header: headerWriter.toBuffer()
748
+ };
749
+ }
750
+ return patched;
751
+ });
752
+ return {
753
+ ...parsed,
754
+ events: patchedEvents
755
+ };
756
+ }
757
+ function findEvents(parsed, eventId) {
758
+ return parsed.events.filter((e) => e.id === eventId);
759
+ }
760
+ function findFirstEvent(parsed, eventId) {
761
+ return parsed.events.find((e) => e.id === eventId);
762
+ }
763
+ function getEventString(event, useUnicode) {
764
+ if (event.kind !== "text") {
765
+ throw new Error(`Event ${event.id} is not a text event`);
766
+ }
767
+ if (useUnicode) {
768
+ const str = event.payload.toString("utf16le");
769
+ const nullPos = str.indexOf("\0");
770
+ return nullPos === -1 ? str : str.substring(0, nullPos);
771
+ } else {
772
+ const str = event.payload.toString("ascii");
773
+ const nullPos = str.indexOf("\0");
774
+ return nullPos === -1 ? str : str.substring(0, nullPos);
775
+ }
776
+ }
777
+ function createTextPayload(text, useUnicode) {
778
+ if (useUnicode) {
779
+ return Buffer.from(text + "\0", "utf16le");
780
+ } else {
781
+ return Buffer.from(text + "\0", "ascii");
782
+ }
783
+ }
784
+ function getEventNumber(event) {
785
+ const reader = new BinaryReader(event.payload);
786
+ switch (event.kind) {
787
+ case "u8":
788
+ return reader.readU8();
789
+ case "i8":
790
+ return reader.readI8();
791
+ case "u16":
792
+ return reader.readU16LE();
793
+ case "i16":
794
+ return reader.readI16LE();
795
+ case "u32":
796
+ return reader.readU32LE();
797
+ case "i32":
798
+ return reader.readI32LE();
799
+ case "f32":
800
+ return reader.readF32LE();
801
+ default:
802
+ throw new Error(`Cannot get number from ${event.kind} event`);
803
+ }
804
+ }
805
+ function createNumberPayload(value, kind) {
806
+ const writer = new BinaryWriter();
807
+ switch (kind) {
808
+ case "u8":
809
+ writer.writeU8(value);
810
+ break;
811
+ case "i8":
812
+ writer.writeI8(value);
813
+ break;
814
+ case "u16":
815
+ writer.writeU16LE(value);
816
+ break;
817
+ case "i16":
818
+ writer.writeI16LE(value);
819
+ break;
820
+ case "u32":
821
+ writer.writeU32LE(value);
822
+ break;
823
+ case "i32":
824
+ writer.writeI32LE(value);
825
+ break;
826
+ case "f32":
827
+ writer.writeF32LE(value);
828
+ break;
829
+ }
830
+ return writer.toBuffer();
831
+ }
832
+
833
+ // src/api/ProjectApi.ts
834
+ function readProjectMeta(parsed) {
835
+ const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);
836
+ const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);
837
+ const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);
838
+ const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);
839
+ const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
840
+ return {
841
+ name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,
842
+ description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,
843
+ artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,
844
+ genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,
845
+ bpm: tempoEvent ? getEventNumber(tempoEvent) / 1e3 : null
846
+ };
847
+ }
848
+ function writeProjectMeta(parsed, meta) {
849
+ return patchEvents(parsed, (event) => {
850
+ if (meta.name !== void 0 && event.id === EVENT_ID.PROJECT_TITLE) {
851
+ return {
852
+ ...event,
853
+ payload: createTextPayload(meta.name ?? "", parsed.useUnicode)
854
+ };
855
+ }
856
+ if (meta.description !== void 0 && event.id === EVENT_ID.PROJECT_COMMENTS) {
857
+ return {
858
+ ...event,
859
+ payload: createTextPayload(meta.description ?? "", parsed.useUnicode)
860
+ };
861
+ }
862
+ if (meta.artist !== void 0 && event.id === EVENT_ID.PROJECT_ARTISTS) {
863
+ return {
864
+ ...event,
865
+ payload: createTextPayload(meta.artist ?? "", parsed.useUnicode)
866
+ };
867
+ }
868
+ if (meta.genre !== void 0 && event.id === EVENT_ID.PROJECT_GENRE) {
869
+ return {
870
+ ...event,
871
+ payload: createTextPayload(meta.genre ?? "", parsed.useUnicode)
872
+ };
873
+ }
874
+ if (meta.bpm && Math.abs(meta.bpm) > 0 && event.id === EVENT_ID.PROJECT_TEMPO) {
875
+ return {
876
+ ...event,
877
+ payload: createNumberPayload(Math.round(Math.abs(meta.bpm) * 1e3), "u32")
878
+ };
879
+ }
880
+ return event;
881
+ });
882
+ }
883
+ var DELPHI_EPOCH = new Date(1899, 11, 30);
884
+ function delphiToDate(days) {
885
+ const ms = days * 24 * 60 * 60 * 1e3;
886
+ return new Date(DELPHI_EPOCH.getTime() + ms);
887
+ }
888
+ function dateToDelphiDays(date) {
889
+ const ms = date.getTime() - DELPHI_EPOCH.getTime();
890
+ return ms / (24 * 60 * 60 * 1e3);
891
+ }
892
+ function readProjectTimeInfo(parsed) {
893
+ const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);
894
+ if (!timestampEvent || timestampEvent.payload.length < 16) {
895
+ return {
896
+ creationDate: null,
897
+ workTimeSeconds: null
898
+ };
899
+ }
900
+ const reader = new BinaryReader(timestampEvent.payload);
901
+ const createdOnDays = reader.readF64LE();
902
+ const timeSpentDays = reader.readF64LE();
903
+ return {
904
+ creationDate: delphiToDate(createdOnDays),
905
+ workTimeSeconds: timeSpentDays * 24 * 60 * 60
906
+ // Convert days to seconds
907
+ };
908
+ }
909
+ function writeProjectTimeInfo(parsed, info) {
910
+ const current = readProjectTimeInfo(parsed);
911
+ return patchEvents(parsed, (event) => {
912
+ if (event.id !== EVENT_ID.PROJECT_TIMESTAMP) {
913
+ return event;
914
+ }
915
+ const writer = new BinaryWriter();
916
+ if (info.creationDate !== void 0 && info.creationDate !== null) {
917
+ writer.writeF64LE(dateToDelphiDays(info.creationDate));
918
+ } else if (current.creationDate !== null) {
919
+ writer.writeF64LE(dateToDelphiDays(current.creationDate));
920
+ } else {
921
+ writer.writeF64LE(0);
922
+ }
923
+ if (info.workTimeSeconds !== void 0 && info.workTimeSeconds !== null) {
924
+ writer.writeF64LE(info.workTimeSeconds / (24 * 60 * 60));
925
+ } else if (current.workTimeSeconds !== null) {
926
+ writer.writeF64LE(current.workTimeSeconds / (24 * 60 * 60));
927
+ } else {
928
+ writer.writeF64LE(0);
929
+ }
930
+ return {
931
+ ...event,
932
+ payload: writer.toBuffer()
933
+ };
934
+ });
935
+ }
936
+ function listSamples(parsed) {
937
+ const samples = [];
938
+ parsed.events.forEach((event, index) => {
939
+ if (event.id === EVENT_ID.CHANNEL_SAMPLE_PATH) {
940
+ const path = getEventString(event, parsed.useUnicode);
941
+ if (path && path.length > 0) {
942
+ samples.push({
943
+ eventIndex: index,
944
+ path
945
+ });
946
+ }
947
+ }
948
+ });
949
+ return samples;
950
+ }
951
+ function rewriteSamplePaths(parsed, mapper) {
952
+ return patchEvents(parsed, (event) => {
953
+ if (event.id !== EVENT_ID.CHANNEL_SAMPLE_PATH) {
954
+ return event;
955
+ }
956
+ const oldPath = getEventString(event, parsed.useUnicode);
957
+ if (!oldPath || oldPath.length === 0) {
958
+ return event;
959
+ }
960
+ const newPath = mapper(oldPath);
961
+ if (newPath === oldPath) {
962
+ return event;
963
+ }
964
+ return {
965
+ ...event,
966
+ payload: createTextPayload(newPath, parsed.useUnicode)
967
+ };
968
+ });
969
+ }
970
+ var VST_EVENT_ID = {
971
+ MIDI: 1,
972
+ FLAGS: 2,
973
+ IO: 30,
974
+ INPUTS: 31,
975
+ OUTPUTS: 32,
976
+ PLUGIN_INFO: 50,
977
+ FOUR_CC: 51,
978
+ GUID: 52,
979
+ STATE: 53,
980
+ NAME: 54,
981
+ PLUGIN_PATH: 55,
982
+ VENDOR: 56
983
+ };
984
+ function parseVstPluginData(payload) {
985
+ if (payload.length < 4) {
986
+ return { name: null, vendor: null };
987
+ }
988
+ const reader = new BinaryReader(payload);
989
+ const typeMarker = reader.readU32LE();
990
+ void typeMarker;
991
+ let name = null;
992
+ let vendor = null;
993
+ while (reader.remaining() >= 12) {
994
+ const subEventId = reader.readU32LE();
995
+ const dataSize = reader.readU32LE();
996
+ const dataSizeHigh = reader.readU32LE();
997
+ const actualSize = dataSize + dataSizeHigh * 4294967296;
998
+ if (actualSize > reader.remaining()) {
999
+ break;
1000
+ }
1001
+ const data = reader.readBytes(actualSize);
1002
+ if (subEventId === VST_EVENT_ID.NAME && actualSize > 0) {
1003
+ name = data.toString("utf8").replace(/\0/g, "");
1004
+ } else if (subEventId === VST_EVENT_ID.VENDOR && actualSize > 0) {
1005
+ vendor = data.toString("utf8").replace(/\0/g, "");
1006
+ }
1007
+ }
1008
+ return { name, vendor };
1009
+ }
1010
+ function listPlugins(parsed) {
1011
+ const plugins = [];
1012
+ const seenPlugins = /* @__PURE__ */ new Set();
1013
+ let currentInternalName = null;
1014
+ let currentPluginName = null;
1015
+ for (const event of parsed.events) {
1016
+ if (event.id === EVENT_ID.CHANNEL_NEW) {
1017
+ if (currentInternalName || currentPluginName) {
1018
+ const key = `${currentInternalName ?? ""}:${currentPluginName ?? ""}`;
1019
+ if (!seenPlugins.has(key) && currentInternalName && currentInternalName !== "Fruity Wrapper") {
1020
+ seenPlugins.add(key);
1021
+ plugins.push({
1022
+ name: currentPluginName || currentInternalName,
1023
+ vendor: null
1024
+ // Native plugins don't store vendor
1025
+ });
1026
+ }
1027
+ }
1028
+ currentInternalName = null;
1029
+ currentPluginName = null;
1030
+ }
1031
+ if (event.id === EVENT_ID.PLUGIN_INTERNAL_NAME) {
1032
+ currentInternalName = getEventString(event, parsed.useUnicode);
1033
+ }
1034
+ if (event.id === EVENT_ID.PLUGIN_NAME) {
1035
+ currentPluginName = getEventString(event, parsed.useUnicode);
1036
+ }
1037
+ if (event.id === EVENT_ID.PLUGIN_DATA && currentInternalName === "Fruity Wrapper") {
1038
+ const vstData = parseVstPluginData(event.payload);
1039
+ if (vstData.name) {
1040
+ const key = `vst:${vstData.name}:${vstData.vendor ?? ""}`;
1041
+ if (!seenPlugins.has(key)) {
1042
+ seenPlugins.add(key);
1043
+ plugins.push({
1044
+ name: vstData.name,
1045
+ vendor: vstData.vendor
1046
+ });
1047
+ }
1048
+ }
1049
+ currentInternalName = null;
1050
+ currentPluginName = null;
1051
+ }
1052
+ }
1053
+ if (currentInternalName && currentInternalName !== "Fruity Wrapper") {
1054
+ const key = `${currentInternalName}:${currentPluginName ?? ""}`;
1055
+ if (!seenPlugins.has(key)) {
1056
+ plugins.push({
1057
+ name: currentPluginName || currentInternalName,
1058
+ vendor: null
1059
+ });
1060
+ }
1061
+ }
1062
+ return plugins;
1063
+ }
1064
+ function getFlVersion(parsed) {
1065
+ return parsed.flVersion;
1066
+ }
1067
+ function getPPQ(parsed) {
1068
+ const reader = new BinaryReader(parsed.headerChunkBytes);
1069
+ reader.seek(12);
1070
+ return reader.readU16LE();
1071
+ }
1072
+ // Annotate the CommonJS export names for ESM import in node:
1073
+ 0 && (module.exports = {
1074
+ BinaryReader,
1075
+ BinaryWriter,
1076
+ EVENT_ID,
1077
+ EVENT_KIND,
1078
+ createEvent,
1079
+ createNumberPayload,
1080
+ createTextPayload,
1081
+ encodeVarInt,
1082
+ findEvents,
1083
+ findFirstEvent,
1084
+ getEventFixedSize,
1085
+ getEventKind,
1086
+ getEventNumber,
1087
+ getEventString,
1088
+ getFlVersion,
1089
+ getPPQ,
1090
+ listPlugins,
1091
+ listSamples,
1092
+ parseFlp,
1093
+ patchEvents,
1094
+ readProjectMeta,
1095
+ readProjectTimeInfo,
1096
+ rewriteSamplePaths,
1097
+ serializeFlp,
1098
+ varIntSize,
1099
+ writeProjectMeta,
1100
+ writeProjectTimeInfo
1101
+ });
1102
+ //# sourceMappingURL=index.cjs.map