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 +1102 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1038 -40
- package/dist/index.js.map +1 -1
- package/dist/src/api/ProjectApi.d.ts.map +1 -1
- package/dist/src/io/BinaryReader.d.ts +1 -0
- package/dist/src/io/BinaryReader.d.ts.map +1 -1
- package/dist/src/parser/FlpParser.d.ts.map +1 -1
- package/package.json +6 -3
- package/dist/src/api/ProjectApi.js +0 -326
- package/dist/src/api/ProjectApi.js.map +0 -1
- package/dist/src/generated/events.generated.js +0 -257
- package/dist/src/generated/events.generated.js.map +0 -1
- package/dist/src/io/BinaryReader.js +0 -159
- package/dist/src/io/BinaryReader.js.map +0 -1
- package/dist/src/io/BinaryWriter.js +0 -124
- package/dist/src/io/BinaryWriter.js.map +0 -1
- package/dist/src/parser/FlpParser.js +0 -313
- package/dist/src/parser/FlpParser.js.map +0 -1
- package/dist/test/flparser.test.js +0 -345
- package/dist/test/flparser.test.js.map +0 -1
- package/dist/vitest.config.js +0 -10
- package/dist/vitest.config.js.map +0 -1
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
|