ts-flp 1.0.1 → 1.0.3

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