ts-flp 1.0.5 → 1.1.0

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.
@@ -0,0 +1,489 @@
1
+ /**
2
+ * FL Studio .flp file parser and serializer
3
+ * Conservative patch approach: preserves all unknown data byte-for-byte
4
+ *
5
+ * Event payload sizes follow the deterministic FL Studio event ranges,
6
+ * including explicit overrides for known format exceptions.
7
+ */
8
+ /**
9
+ * Represents a single event in an FLP file
10
+ * Stores raw bytes to enable conservative patching
11
+ */
12
+ interface FlpEvent {
13
+ /** Event ID (0-255) */
14
+ id: number;
15
+ /** Data type classification */
16
+ kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'text' | 'data' | 'unknown';
17
+ /** Original header bytes (ID + size for variable-length events) */
18
+ header: Buffer;
19
+ /** Event payload data */
20
+ payload: Buffer;
21
+ }
22
+ /**
23
+ * Represents a parsed FLP file
24
+ * Contains all data needed for conservative round-trip
25
+ */
26
+ interface ParsedFlp {
27
+ /** FLhd chunk (header) - 14 bytes total */
28
+ headerChunkBytes: Buffer;
29
+ /** FLdt chunk header (magic + size placeholder) - 8 bytes */
30
+ fldtHeaderBytes: Buffer;
31
+ /** All events from the FLdt chunk */
32
+ events: FlpEvent[];
33
+ /** Any trailing bytes after events (should be empty) */
34
+ trailingBytes: Buffer | undefined;
35
+ /** Detected FL version for string encoding */
36
+ flVersion: string;
37
+ /** Whether to use UTF-16LE for strings (FL >= 11.5) */
38
+ useUnicode: boolean;
39
+ }
40
+ /**
41
+ * Parses an FL Studio project file
42
+ *
43
+ * @param buffer - Raw file data
44
+ * @returns Parsed FLP structure
45
+ * @throws Error if file is corrupted or invalid
46
+ */
47
+ declare function parseFlp(buffer: Buffer): ParsedFlp;
48
+ /**
49
+ * Serializes a parsed FLP back to a buffer
50
+ * Recalculates FLdt chunk size if events have changed
51
+ *
52
+ * @param parsed - Parsed FLP structure
53
+ * @returns Serialized buffer
54
+ */
55
+ declare function serializeFlp(parsed: ParsedFlp): Buffer;
56
+ /**
57
+ * Creates a new event with the given ID and payload.
58
+ *
59
+ * For TEXT/DATA events (id >= 192) the header includes a VarInt size prefix.
60
+ * For BYTE/WORD/DWORD events the header is a single byte (the event ID).
61
+ */
62
+ declare function createEvent(id: number, payload: Buffer): FlpEvent;
63
+ /**
64
+ * Applies a patcher function to all events
65
+ * Returns a new ParsedFlp with patched events
66
+ *
67
+ * @param parsed - Original parsed FLP
68
+ * @param patcher - Function that transforms each event
69
+ * @returns New ParsedFlp with patched events
70
+ */
71
+ declare function patchEvents(parsed: ParsedFlp, patcher: (event: FlpEvent, index: number) => FlpEvent): ParsedFlp;
72
+ /**
73
+ * Finds all events with a specific ID
74
+ */
75
+ declare function findEvents(parsed: ParsedFlp, eventId: number): FlpEvent[];
76
+ /**
77
+ * Finds the first event with a specific ID
78
+ */
79
+ declare function findFirstEvent(parsed: ParsedFlp, eventId: number): FlpEvent | undefined;
80
+ /**
81
+ * Gets event payload as a string (handles encoding)
82
+ */
83
+ declare function getEventString(event: FlpEvent, useUnicode: boolean): string;
84
+ /**
85
+ * Creates a text payload with proper encoding
86
+ */
87
+ declare function createTextPayload(text: string, useUnicode: boolean): Buffer;
88
+ /**
89
+ * Gets event payload as a number
90
+ */
91
+ declare function getEventNumber(event: FlpEvent): number;
92
+ /**
93
+ * Creates a numeric payload
94
+ */
95
+ declare function createNumberPayload(value: number, kind: 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32'): Buffer;
96
+
97
+ /**
98
+ * High-level API for reading and modifying FL Studio project files
99
+ * Provides typed access to project metadata, samples, plugins, and time info
100
+ */
101
+
102
+ /**
103
+ * Project metadata (name, description, artist, genre, BPM)
104
+ */
105
+ interface ProjectMeta {
106
+ name: string | null;
107
+ description: string | null;
108
+ artist: string | null;
109
+ genre: string | null;
110
+ bpm: number | null;
111
+ }
112
+ /**
113
+ * Reads project metadata from a parsed FLP
114
+ */
115
+ declare function readProjectMeta(parsed: ParsedFlp): ProjectMeta;
116
+ /**
117
+ * Writes project metadata to a parsed FLP
118
+ * Only modifies fields that are provided (non-undefined)
119
+ */
120
+ declare function writeProjectMeta(parsed: ParsedFlp, meta: Partial<ProjectMeta>): ParsedFlp;
121
+ /**
122
+ * Project creation date and work time
123
+ */
124
+ interface ProjectTimeInfo {
125
+ creationDate: Date | null;
126
+ workTimeSeconds: number | null;
127
+ }
128
+ /**
129
+ * Reads project time info from a parsed FLP
130
+ * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)
131
+ */
132
+ declare function readProjectTimeInfo(parsed: ParsedFlp): ProjectTimeInfo;
133
+ /**
134
+ * Writes project time info to a parsed FLP
135
+ * Only modifies fields that are provided (non-undefined)
136
+ *
137
+ * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)
138
+ */
139
+ declare function writeProjectTimeInfo(parsed: ParsedFlp, info: Partial<ProjectTimeInfo>): ParsedFlp;
140
+ /**
141
+ * Reference to a sample in the project
142
+ */
143
+ interface SampleRef {
144
+ /** Index of the event in the events array */
145
+ eventIndex: number;
146
+ /** File path of the sample */
147
+ path: string;
148
+ }
149
+ /**
150
+ * Lists all samples in the project
151
+ */
152
+ declare function listSamples(parsed: ParsedFlp): SampleRef[];
153
+ /**
154
+ * Rewrites sample paths using a mapper function
155
+ */
156
+ declare function rewriteSamplePaths(parsed: ParsedFlp, mapper: (oldPath: string) => string): ParsedFlp;
157
+ /**
158
+ * Reference to a plugin in the project
159
+ */
160
+ interface PluginRef {
161
+ /** Plugin name (may be null if not available) */
162
+ name: string | null;
163
+ /** Plugin vendor/provider (may be null if not stored in file) */
164
+ vendor: string | null;
165
+ }
166
+ /**
167
+ * Lists all plugins (VST and native) in the project
168
+ * Returns plugin name and vendor where available
169
+ */
170
+ declare function listPlugins(parsed: ParsedFlp): PluginRef[];
171
+ /**
172
+ * Gets the FL Studio version string from the project
173
+ */
174
+ declare function getFlVersion(parsed: ParsedFlp): string;
175
+ /**
176
+ * Gets the PPQ (Pulses Per Quarter note) from the header
177
+ */
178
+ declare function getPPQ(parsed: ParsedFlp): number;
179
+
180
+ /**
181
+ * Event IDs used by FL Studio project files
182
+ * Based on PyFLP's event definitions
183
+ */
184
+ declare const EVENT_ID: {
185
+ readonly PROJECT_LOOP_ACTIVE: 9;
186
+ readonly PROJECT_SHOW_INFO: 10;
187
+ readonly PROJECT__VOLUME: 12;
188
+ readonly PROJECT_PAN_LAW: 23;
189
+ readonly PROJECT_LICENSED: 28;
190
+ readonly PROJECT__TEMPO_COARSE: number;
191
+ readonly PROJECT_PITCH: number;
192
+ readonly PROJECT__TEMPO_FINE: number;
193
+ readonly PROJECT_CUR_GROUP_ID: number;
194
+ readonly PROJECT_TEMPO: number;
195
+ readonly PROJECT_FL_BUILD: number;
196
+ readonly PROJECT_TITLE: number;
197
+ readonly PROJECT_COMMENTS: number;
198
+ readonly PROJECT_URL: number;
199
+ readonly PROJECT__RTF_COMMENTS: number;
200
+ readonly PROJECT_FL_VERSION: number;
201
+ readonly PROJECT_LICENSEE: number;
202
+ readonly PROJECT_DATA_PATH: number;
203
+ readonly PROJECT_GENRE: number;
204
+ readonly PROJECT_ARTISTS: number;
205
+ readonly PROJECT_TIMESTAMP: number;
206
+ readonly CHANNEL_IS_ENABLED: 0;
207
+ readonly CHANNEL__VOL_BYTE: 2;
208
+ readonly CHANNEL__PAN_BYTE: 3;
209
+ readonly CHANNEL_ZIPPED: 15;
210
+ readonly CHANNEL_PING_PONG_LOOP: 20;
211
+ readonly CHANNEL_TYPE: 21;
212
+ readonly CHANNEL_ROUTED_TO: 22;
213
+ readonly CHANNEL_IS_LOCKED: 32;
214
+ readonly CHANNEL_NEW: 64;
215
+ readonly CHANNEL_FREQ_TILT: number;
216
+ readonly CHANNEL_FX_FLAGS: number;
217
+ readonly CHANNEL_CUTOFF: number;
218
+ readonly CHANNEL__VOL_WORD: number;
219
+ readonly CHANNEL__PAN_WORD: number;
220
+ readonly CHANNEL_PREAMP: number;
221
+ readonly CHANNEL_FADE_OUT: number;
222
+ readonly CHANNEL_FADE_IN: number;
223
+ readonly CHANNEL_RESONANCE: number;
224
+ readonly CHANNEL_STEREO_DELAY: number;
225
+ readonly CHANNEL_POGO: number;
226
+ readonly CHANNEL_TIME_SHIFT: number;
227
+ readonly CHANNEL_CHILDREN: number;
228
+ readonly CHANNEL_SWING: number;
229
+ readonly CHANNEL_RING_MOD: number;
230
+ readonly CHANNEL_CUT_GROUP: number;
231
+ readonly CHANNEL_ROOT_NOTE: number;
232
+ readonly CHANNEL_DELAY_MOD_XY: number;
233
+ readonly CHANNEL_REVERB: number;
234
+ readonly CHANNEL__STRETCH_TIME: number;
235
+ readonly CHANNEL_FINE_TUNE: number;
236
+ readonly CHANNEL_SAMPLER_FLAGS: number;
237
+ readonly CHANNEL_LAYER_FLAGS: number;
238
+ readonly CHANNEL_GROUP_NUM: number;
239
+ readonly CHANNEL_AU_SAMPLE_RATE: number;
240
+ readonly CHANNEL__NAME: 192;
241
+ readonly CHANNEL_SAMPLE_PATH: number;
242
+ readonly CHANNEL_DELAY: number;
243
+ readonly CHANNEL_PARAMETERS: number;
244
+ readonly CHANNEL_ENVELOPE_LFO: number;
245
+ readonly CHANNEL_LEVELS: number;
246
+ readonly CHANNEL_POLYPHONY: number;
247
+ readonly CHANNEL_TRACKING: number;
248
+ readonly CHANNEL_LEVEL_ADJUSTS: number;
249
+ readonly CHANNEL_AUTOMATION: number;
250
+ readonly PLUGIN_COLOR: 128;
251
+ readonly PLUGIN_ICON: number;
252
+ readonly PLUGIN_INTERNAL_NAME: number;
253
+ readonly PLUGIN_NAME: number;
254
+ readonly PLUGIN_WRAPPER: number;
255
+ readonly PLUGIN_DATA: number;
256
+ readonly DISPLAY_GROUP_NAME: number;
257
+ readonly RACK_SWING: 11;
258
+ readonly RACK__FIT_TO_STEPS: 13;
259
+ readonly RACK_WINDOW_HEIGHT: number;
260
+ readonly MIXER_SLOT_INDEX: number;
261
+ readonly MIXER_INSERT_ICON: number;
262
+ readonly MIXER_INSERT_OUTPUT: number;
263
+ readonly MIXER_INSERT_COLOR: number;
264
+ readonly MIXER_INSERT_INPUT: number;
265
+ readonly MIXER_INSERT_NAME: number;
266
+ readonly MIXER_INSERT_ROUTING: number;
267
+ readonly MIXER_INSERT_FLAGS: number;
268
+ };
269
+ /**
270
+ * Event data type classification
271
+ */
272
+ type EventKind = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'text' | 'data' | 'unknown';
273
+ /**
274
+ * Maps event IDs to their data type
275
+ * Used for proper serialization/deserialization
276
+ */
277
+ declare const EVENT_KIND: Record<number, EventKind>;
278
+ /**
279
+ * Determines the event kind based on ID
280
+ * Falls back to range-based detection if not explicitly mapped
281
+ */
282
+ declare function getEventKind(eventId: number): EventKind;
283
+ /**
284
+ * Returns the fixed size for BYTE/WORD/DWORD events
285
+ * Returns -1 for variable-length events (TEXT/DATA)
286
+ */
287
+ declare function getEventFixedSize(eventId: number): number;
288
+
289
+ /**
290
+ * Binary reader for FL Studio .flp files
291
+ * Uses smart-buffer for binary reading and protobufjs for VarInt decoding
292
+ */
293
+ declare class BinaryReader {
294
+ private sb;
295
+ private source;
296
+ constructor(buffer: Buffer);
297
+ /**
298
+ * Returns current read position
299
+ */
300
+ tell(): number;
301
+ /**
302
+ * Seeks to an absolute position
303
+ */
304
+ seek(pos: number): void;
305
+ /**
306
+ * Seeks relative to current position
307
+ */
308
+ skip(offset: number): void;
309
+ /**
310
+ * Returns total buffer length
311
+ */
312
+ length(): number;
313
+ /**
314
+ * Returns remaining bytes to read
315
+ */
316
+ remaining(): number;
317
+ /**
318
+ * Returns true if there are more bytes to read
319
+ */
320
+ hasMore(): boolean;
321
+ /**
322
+ * Reads an unsigned 8-bit integer
323
+ */
324
+ readU8(): number;
325
+ /**
326
+ * Reads a signed 8-bit integer
327
+ */
328
+ readI8(): number;
329
+ /**
330
+ * Reads an unsigned 16-bit integer (little-endian)
331
+ */
332
+ readU16LE(): number;
333
+ /**
334
+ * Reads a signed 16-bit integer (little-endian)
335
+ */
336
+ readI16LE(): number;
337
+ /**
338
+ * Reads an unsigned 32-bit integer (little-endian)
339
+ */
340
+ readU32LE(): number;
341
+ /**
342
+ * Reads a signed 32-bit integer (little-endian)
343
+ */
344
+ readI32LE(): number;
345
+ /**
346
+ * Reads a 32-bit float (little-endian)
347
+ */
348
+ readF32LE(): number;
349
+ /**
350
+ * Reads a 64-bit float (little-endian)
351
+ */
352
+ readF64LE(): number;
353
+ /**
354
+ * Reads raw bytes
355
+ */
356
+ readBytes(length: number): Buffer;
357
+ /**
358
+ * Reads a VarInt (variable-length integer) using protobufjs
359
+ * FL Studio uses this encoding for TEXT and DATA event sizes
360
+ */
361
+ readVarInt(): number;
362
+ /**
363
+ * Reads an ASCII string (null-terminated)
364
+ */
365
+ readAsciiString(length: number): string;
366
+ /**
367
+ * Reads a UTF-16LE string (null-terminated)
368
+ */
369
+ readUtf16LEString(length: number): string;
370
+ /**
371
+ * Peeks at bytes without advancing position
372
+ */
373
+ peek(length: number): Buffer;
374
+ /**
375
+ * Peeks at a single byte without advancing position
376
+ */
377
+ peekU8(): number;
378
+ /**
379
+ * Returns the internal buffer
380
+ */
381
+ toBuffer(): Buffer;
382
+ }
383
+
384
+ /**
385
+ * Binary writer for FL Studio .flp files
386
+ * Uses smart-buffer for binary writing and protobufjs for VarInt encoding
387
+ */
388
+ declare class BinaryWriter {
389
+ private sb;
390
+ constructor();
391
+ /**
392
+ * Returns current write position (total bytes written)
393
+ */
394
+ tell(): number;
395
+ /**
396
+ * Writes an unsigned 8-bit integer
397
+ */
398
+ writeU8(value: number): void;
399
+ /**
400
+ * Writes a signed 8-bit integer
401
+ */
402
+ writeI8(value: number): void;
403
+ /**
404
+ * Writes an unsigned 16-bit integer (little-endian)
405
+ */
406
+ writeU16LE(value: number): void;
407
+ /**
408
+ * Writes a signed 16-bit integer (little-endian)
409
+ */
410
+ writeI16LE(value: number): void;
411
+ /**
412
+ * Writes an unsigned 32-bit integer (little-endian)
413
+ */
414
+ writeU32LE(value: number): void;
415
+ /**
416
+ * Writes a signed 32-bit integer (little-endian)
417
+ */
418
+ writeI32LE(value: number): void;
419
+ /**
420
+ * Writes a 32-bit float (little-endian)
421
+ */
422
+ writeF32LE(value: number): void;
423
+ /**
424
+ * Writes a 64-bit float (little-endian)
425
+ */
426
+ writeF64LE(value: number): void;
427
+ /**
428
+ * Writes raw bytes
429
+ */
430
+ writeBytes(data: Buffer): void;
431
+ /**
432
+ * Writes a VarInt (variable-length integer) using protobufjs
433
+ * FL Studio uses this encoding for TEXT and DATA event sizes
434
+ */
435
+ writeVarInt(value: number): void;
436
+ /**
437
+ * Writes an ASCII string (null-terminated)
438
+ */
439
+ writeAsciiString(str: string): void;
440
+ /**
441
+ * Writes a UTF-16LE string (null-terminated)
442
+ */
443
+ writeUtf16LEString(str: string): void;
444
+ /**
445
+ * Gets the final buffer
446
+ */
447
+ toBuffer(): Buffer;
448
+ }
449
+ /**
450
+ * Encodes a VarInt to a Buffer using protobufjs (standalone utility)
451
+ */
452
+ declare function encodeVarInt(value: number): Buffer;
453
+ /**
454
+ * Calculates the byte size needed for a VarInt
455
+ */
456
+ declare function varIntSize(value: number): number;
457
+
458
+ interface SampleInspectionOptions {
459
+ mode?: 'resolve' | 'check';
460
+ projectPath?: string;
461
+ projectRoot?: string;
462
+ /** Explicit roots for DAW-specific relative reference types. */
463
+ relativeRoots?: Readonly<Record<string, string>>;
464
+ /** Recursive discovery only; basename matches never establish media identity. */
465
+ searchRoots?: readonly string[];
466
+ signal?: AbortSignal;
467
+ concurrency?: number;
468
+ maxSearchEntries?: number;
469
+ }
470
+ type SampleStatus = 'unchecked' | 'found' | 'missing' | 'unresolved' | 'ambiguous' | 'inaccessible';
471
+ interface SampleCandidate {
472
+ path: string;
473
+ source: 'declared' | 'relative' | 'search';
474
+ confidence: 'exact' | 'name-only';
475
+ }
476
+ interface SampleInspection {
477
+ referenceIndex: number;
478
+ recordedPath: string;
479
+ pathType: 'posix' | 'windows' | 'relative' | 'daw' | 'unknown';
480
+ relativePath: string | null;
481
+ candidates: SampleCandidate[];
482
+ resolvedPath: string | null;
483
+ status: SampleStatus;
484
+ reason: string | null;
485
+ }
486
+
487
+ declare function inspectSamples(parsed: ParsedFlp, options?: SampleInspectionOptions): Promise<SampleInspection[]>;
488
+
489
+ export { BinaryReader, BinaryWriter, EVENT_ID, EVENT_KIND, type FlpEvent, type ParsedFlp, type PluginRef, type ProjectMeta, type ProjectTimeInfo, type SampleCandidate, type SampleInspection, type SampleInspectionOptions, type SampleRef, type SampleStatus, createEvent, createNumberPayload, createTextPayload, encodeVarInt, findEvents, findFirstEvent, getEventFixedSize, getEventKind, getEventNumber, getEventString, getFlVersion, getPPQ, inspectSamples, listPlugins, listSamples, parseFlp, patchEvents, readProjectMeta, readProjectTimeInfo, rewriteSamplePaths, serializeFlp, varIntSize, writeProjectMeta, writeProjectTimeInfo };
package/dist/index.d.ts CHANGED
@@ -34,4 +34,6 @@ export { getFlVersion, getPPQ, listPlugins, listSamples, readProjectMeta, readPr
34
34
  export { EVENT_ID, EVENT_KIND, getEventFixedSize, getEventKind } from "./src/generated/events.generated.js";
35
35
  export { BinaryReader } from "./src/io/BinaryReader.js";
36
36
  export { BinaryWriter, encodeVarInt, varIntSize } from "./src/io/BinaryWriter.js";
37
+ export { inspectSamples } from './src/api/inspectSamples.js';
38
+ export type { SampleInspectionOptions, SampleInspection, SampleCandidate, SampleStatus } from './src/samples/inspectReferences.js';
37
39
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EACL,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,UAAU,EAC/D,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EACnH,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,YAAY,EACZ,MAAM,EACN,WAAW,EACX,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,EACf,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAG5G,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EACL,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,UAAU,EAC/D,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EACnH,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,YAAY,EACZ,MAAM,EACN,WAAW,EACX,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,EACf,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAG5G,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC"}