ts-flp 1.0.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.
package/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # Typescript FLP Library
2
+
3
+ A minimalist TypeScript library for reading and modifying FL Studio project files (`.flp`).
4
+
5
+ Designed for **non-destructive** modifications ("conservative patch").
6
+
7
+ ## Features
8
+
9
+ - **Project metadata**: Read and modify name, description, artist, genre, and BPM
10
+ - **Time information**: Read and modify creation date and work time
11
+ - **Samples**: List sample paths and batch rewrite paths
12
+ - **VST Plugins**: List plugins with name and vendor (when available)
13
+ - **Stable round-trip**: Untargeted data remains byte-for-byte identical
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install ts-flp
19
+ # or
20
+ pnpm add ts-flp
21
+ # or
22
+ yarn add ts-flp
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Reading a project
28
+
29
+ ```typescript
30
+ import { readFileSync, writeFileSync } from 'fs';
31
+ import { parseFlp, serializeFlp, readProjectMeta, listSamples, listPlugins } from 'ts-flp';
32
+
33
+ // Load and parse the .flp file
34
+ const buffer = readFileSync('my-project.flp');
35
+ const parsed = parseFlp(buffer);
36
+
37
+ // Read metadata
38
+ const meta = readProjectMeta(parsed);
39
+ console.log(`Project: ${meta.name}`);
40
+ console.log(`Artist: ${meta.artist}`);
41
+ console.log(`Genre: ${meta.genre}`);
42
+ console.log(`BPM: ${meta.bpm}`);
43
+
44
+ // List samples
45
+ const samples = listSamples(parsed);
46
+ for (const sample of samples) {
47
+ console.log(`Sample: ${sample.path}`);
48
+ }
49
+
50
+ // List plugins
51
+ const plugins = listPlugins(parsed);
52
+ for (const plugin of plugins) {
53
+ console.log(`Plugin: ${plugin.name} (${plugin.vendor ?? 'N/A'})`);
54
+ }
55
+ ```
56
+
57
+ ### Modifying metadata
58
+
59
+ ```typescript
60
+ import { readFileSync, writeFileSync } from 'fs';
61
+ import { parseFlp, serializeFlp, writeProjectMeta } from 'ts-flp';
62
+
63
+ const buffer = readFileSync('my-project.flp');
64
+ let parsed = parseFlp(buffer);
65
+
66
+ // Modify metadata
67
+ parsed = writeProjectMeta(parsed, {
68
+ name: 'New Title',
69
+ artist: 'My Name',
70
+ genre: 'Electronic',
71
+ bpm: 140,
72
+ });
73
+
74
+ // Save
75
+ writeFileSync('my-project-modified.flp', serializeFlp(parsed));
76
+ ```
77
+
78
+ ### Rewriting sample paths
79
+
80
+ ```typescript
81
+ import { readFileSync, writeFileSync } from 'fs';
82
+ import { parseFlp, serializeFlp, rewriteSamplePaths } from 'ts-flp';
83
+
84
+ const buffer = readFileSync('my-project.flp');
85
+ let parsed = parseFlp(buffer);
86
+
87
+ // Remap paths (e.g., disk migration)
88
+ parsed = rewriteSamplePaths(parsed, (oldPath) => {
89
+ return oldPath.replace('D:\\Samples\\', 'E:\\NewSamples\\');
90
+ });
91
+
92
+ writeFileSync('my-project-migrated.flp', serializeFlp(parsed));
93
+ ```
94
+
95
+ ### Modifying time information
96
+
97
+ ```typescript
98
+ import { readFileSync, writeFileSync } from 'fs';
99
+ import { parseFlp, serializeFlp, readProjectTimeInfo, writeProjectTimeInfo } from 'ts-flp';
100
+
101
+ const buffer = readFileSync('my-project.flp');
102
+ let parsed = parseFlp(buffer);
103
+
104
+ // Read time info
105
+ const timeInfo = readProjectTimeInfo(parsed);
106
+ console.log(`Created on: ${timeInfo.creationDate}`);
107
+ console.log(`Work time: ${timeInfo.workTimeSeconds} seconds`);
108
+
109
+ // Modify creation date
110
+ parsed = writeProjectTimeInfo(parsed, {
111
+ creationDate: new Date('2024-01-01'),
112
+ workTimeSeconds: 3600, // 1 hour
113
+ });
114
+
115
+ writeFileSync('my-project-modified.flp', serializeFlp(parsed));
116
+ ```
117
+
118
+ ## API
119
+
120
+ ### Main types
121
+
122
+ ```typescript
123
+ interface ProjectMeta {
124
+ name: string | null;
125
+ description: string | null;
126
+ artist: string | null;
127
+ genre: string | null;
128
+ bpm: number | null;
129
+ }
130
+
131
+ interface ProjectTimeInfo {
132
+ creationDate: Date | null;
133
+ workTimeSeconds: number | null;
134
+ }
135
+
136
+ interface SampleRef {
137
+ eventIndex: number;
138
+ path: string;
139
+ }
140
+
141
+ interface PluginRef {
142
+ name: string | null;
143
+ vendor: string | null;
144
+ }
145
+ ```
146
+
147
+ ### Functions
148
+
149
+ | Function | Description |
150
+ | ------------------------------------ | -------------------------------------------------- |
151
+ | `parseFlp(buffer)` | Parse a `.flp` buffer into a `ParsedFlp` structure |
152
+ | `serializeFlp(parsed)` | Serialize a `ParsedFlp` structure to buffer |
153
+ | `readProjectMeta(parsed)` | Read project metadata |
154
+ | `writeProjectMeta(parsed, meta)` | Modify project metadata |
155
+ | `readProjectTimeInfo(parsed)` | Read time information |
156
+ | `writeProjectTimeInfo(parsed, info)` | Modify time information |
157
+ | `listSamples(parsed)` | List all samples in the project |
158
+ | `rewriteSamplePaths(parsed, mapper)` | Batch rewrite sample paths |
159
+ | `listPlugins(parsed)` | List all plugins (VST and native) |
160
+ | `getFlVersion(parsed)` | Get the FL Studio version string |
161
+ | `getPPQ(parsed)` | Get the PPQ (Pulses Per Quarter note) |
162
+
163
+ ## Constraints and limitations
164
+
165
+ ### What this library does
166
+
167
+ - **Non-destructive** read and write of `.flp` files
168
+ - Byte-for-byte preservation of unmodified data
169
+ - Compatibility with FL Studio 11.5+ (UTF-16LE strings)
170
+
171
+ ### What this library does NOT do
172
+
173
+ - Full editor for patterns, mixer, playlist, automation
174
+ - Create `.flp` files from scratch
175
+ - Guarantee VST `vendor` availability (returns `null` if not serialized)
176
+ - Support compressed (zipped) `.flp` files
177
+
178
+ ## Architecture
179
+
180
+ ```
181
+ src/
182
+ generated/
183
+ events.generated.ts # Event constants
184
+ io/
185
+ BinaryReader.ts # Binary reading
186
+ BinaryWriter.ts # Binary writing
187
+ parser/
188
+ FlpParser.ts # .flp Parser/Serializer
189
+ api/
190
+ ProjectApi.ts # High-level API
191
+ index.ts # Entry point
192
+ ```
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ # Install dependencies
198
+ pnpm install
199
+
200
+ # Type checking
201
+ pnpm run typecheck
202
+
203
+ # Tests
204
+ pnpm run test
205
+
206
+ # Build
207
+ pnpm run build
208
+ ```
209
+
210
+ ## Credits
211
+
212
+ - [PyFLP](https://github.com/demberto/PyFLP) - Reference implementation for FL Studio file format
213
+ - [smart-buffer](https://github.com/JoshGlazebrook/smart-buffer) - Binary read/write
214
+ - [protobufjs](https://github.com/protobufjs/protobuf.js) - VarInt encoding
215
+
216
+ ## License
217
+
218
+ [GPL-3.0](LICENSE)
219
+
220
+ ---
221
+
222
+ ## Acknowledgements
223
+
224
+ A huge thank you to **[@demberto](https://github.com/demberto)** ([PyFLP](https://github.com/demberto/PyFLP)), **[@monadgroup](https://github.com/monadgroup)** ([FLParser](https://github.com/monadgroup/FLParser)), and **FLPEdit** for their incredible reverse-engineering work on the FL Studio `.flp` format.
225
+
226
+ This project would never have been possible without them.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * High-level API for reading and modifying FL Studio project files
3
+ * Provides typed access to project metadata, samples, plugins, and time info
4
+ */
5
+ import { type ParsedFlp } from "../parser/FlpParser.js";
6
+ /**
7
+ * Project metadata (name, description, artist, genre, BPM)
8
+ */
9
+ export interface ProjectMeta {
10
+ name: string | null;
11
+ description: string | null;
12
+ artist: string | null;
13
+ genre: string | null;
14
+ bpm: number | null;
15
+ }
16
+ /**
17
+ * Reads project metadata from a parsed FLP
18
+ */
19
+ export declare function readProjectMeta(parsed: ParsedFlp): ProjectMeta;
20
+ /**
21
+ * Writes project metadata to a parsed FLP
22
+ * Only modifies fields that are provided (non-undefined)
23
+ */
24
+ export declare function writeProjectMeta(parsed: ParsedFlp, meta: Partial<ProjectMeta>): ParsedFlp;
25
+ /**
26
+ * Project creation date and work time
27
+ */
28
+ export interface ProjectTimeInfo {
29
+ creationDate: Date | null;
30
+ workTimeSeconds: number | null;
31
+ }
32
+ /**
33
+ * Reads project time info from a parsed FLP
34
+ * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)
35
+ */
36
+ export declare function readProjectTimeInfo(parsed: ParsedFlp): ProjectTimeInfo;
37
+ /**
38
+ * Writes project time info to a parsed FLP
39
+ * Only modifies fields that are provided (non-undefined)
40
+ *
41
+ * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)
42
+ */
43
+ export declare function writeProjectTimeInfo(parsed: ParsedFlp, info: Partial<ProjectTimeInfo>): ParsedFlp;
44
+ /**
45
+ * Reference to a sample in the project
46
+ */
47
+ export interface SampleRef {
48
+ /** Index of the event in the events array */
49
+ eventIndex: number;
50
+ /** File path of the sample */
51
+ path: string;
52
+ }
53
+ /**
54
+ * Lists all samples in the project
55
+ */
56
+ export declare function listSamples(parsed: ParsedFlp): SampleRef[];
57
+ /**
58
+ * Rewrites sample paths using a mapper function
59
+ */
60
+ export declare function rewriteSamplePaths(parsed: ParsedFlp, mapper: (oldPath: string) => string): ParsedFlp;
61
+ /**
62
+ * Reference to a plugin in the project
63
+ */
64
+ export interface PluginRef {
65
+ /** Plugin name (may be null if not available) */
66
+ name: string | null;
67
+ /** Plugin vendor/provider (may be null if not stored in file) */
68
+ vendor: string | null;
69
+ }
70
+ /**
71
+ * Lists all plugins (VST and native) in the project
72
+ * Returns plugin name and vendor where available
73
+ */
74
+ export declare function listPlugins(parsed: ParsedFlp): PluginRef[];
75
+ /**
76
+ * Gets the FL Studio version string from the project
77
+ */
78
+ export declare function getFlVersion(parsed: ParsedFlp): string;
79
+ /**
80
+ * Gets the PPQ (Pulses Per Quarter note) from the header
81
+ */
82
+ export declare function getPPQ(parsed: ParsedFlp): number;
83
+ //# sourceMappingURL=ProjectApi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectApi.d.ts","sourceRoot":"","sources":["../../../src/api/ProjectApi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAOL,KAAK,SAAS,EAEf,MAAM,wBAAwB,CAAC;AAMhC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CAc9D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,CA4CzF;AAMD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAqBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,eAAe,CAkBtE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,GAC7B,SAAS,CAkCX;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAgB1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAClC,SAAS,CAqBX;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAqED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAoE1D;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAKhD"}
@@ -0,0 +1,326 @@
1
+ /**
2
+ * High-level API for reading and modifying FL Studio project files
3
+ * Provides typed access to project metadata, samples, plugins, and time info
4
+ */
5
+ import { EVENT_ID } from "../generated/events.generated.js";
6
+ import { BinaryReader } from "../io/BinaryReader.js";
7
+ import { BinaryWriter } from "../io/BinaryWriter.js";
8
+ import { createNumberPayload, createTextPayload, findFirstEvent, getEventNumber, getEventString, patchEvents, } from "../parser/FlpParser.js";
9
+ /**
10
+ * Reads project metadata from a parsed FLP
11
+ */
12
+ export function readProjectMeta(parsed) {
13
+ const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);
14
+ const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);
15
+ const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);
16
+ const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);
17
+ const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
18
+ return {
19
+ name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,
20
+ description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,
21
+ artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,
22
+ genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,
23
+ bpm: tempoEvent ? getEventNumber(tempoEvent) / 1000 : null,
24
+ };
25
+ }
26
+ /**
27
+ * Writes project metadata to a parsed FLP
28
+ * Only modifies fields that are provided (non-undefined)
29
+ */
30
+ export function writeProjectMeta(parsed, meta) {
31
+ return patchEvents(parsed, (event) => {
32
+ // Patch title
33
+ if (meta.name !== undefined && event.id === EVENT_ID.PROJECT_TITLE) {
34
+ return {
35
+ ...event,
36
+ payload: createTextPayload(meta.name ?? "", parsed.useUnicode),
37
+ };
38
+ }
39
+ // Patch comments/description
40
+ if (meta.description !== undefined && event.id === EVENT_ID.PROJECT_COMMENTS) {
41
+ return {
42
+ ...event,
43
+ payload: createTextPayload(meta.description ?? "", parsed.useUnicode),
44
+ };
45
+ }
46
+ // Patch artists
47
+ if (meta.artist !== undefined && event.id === EVENT_ID.PROJECT_ARTISTS) {
48
+ return {
49
+ ...event,
50
+ payload: createTextPayload(meta.artist ?? "", parsed.useUnicode),
51
+ };
52
+ }
53
+ // Patch genre
54
+ if (meta.genre !== undefined && event.id === EVENT_ID.PROJECT_GENRE) {
55
+ return {
56
+ ...event,
57
+ payload: createTextPayload(meta.genre ?? "", parsed.useUnicode),
58
+ };
59
+ }
60
+ // Patch tempo (stored as BPM * 1000)
61
+ if (meta.bpm && Math.abs(meta.bpm) > 0 && event.id === EVENT_ID.PROJECT_TEMPO) {
62
+ return {
63
+ ...event,
64
+ payload: createNumberPayload(Math.round(Math.abs(meta.bpm) * 1000), "u32"),
65
+ };
66
+ }
67
+ return event;
68
+ });
69
+ }
70
+ // Delphi epoch: December 30, 1899
71
+ const DELPHI_EPOCH = new Date(1899, 11, 30);
72
+ /**
73
+ * Converts Delphi timestamp (days since epoch) to Date
74
+ */
75
+ function delphiToDate(days) {
76
+ const ms = days * 24 * 60 * 60 * 1000;
77
+ return new Date(DELPHI_EPOCH.getTime() + ms);
78
+ }
79
+ /**
80
+ * Converts Date to Delphi timestamp (days since epoch)
81
+ */
82
+ function dateToDelphiDays(date) {
83
+ const ms = date.getTime() - DELPHI_EPOCH.getTime();
84
+ return ms / (24 * 60 * 60 * 1000);
85
+ }
86
+ /**
87
+ * Reads project time info from a parsed FLP
88
+ * Timestamp event contains: Float64 created_on (days), Float64 time_spent (days)
89
+ */
90
+ export function readProjectTimeInfo(parsed) {
91
+ const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);
92
+ if (!timestampEvent || timestampEvent.payload.length < 16) {
93
+ return {
94
+ creationDate: null,
95
+ workTimeSeconds: null,
96
+ };
97
+ }
98
+ const reader = new BinaryReader(timestampEvent.payload);
99
+ const createdOnDays = reader.readF64LE();
100
+ const timeSpentDays = reader.readF64LE();
101
+ return {
102
+ creationDate: delphiToDate(createdOnDays),
103
+ workTimeSeconds: timeSpentDays * 24 * 60 * 60, // Convert days to seconds
104
+ };
105
+ }
106
+ /**
107
+ * Writes project time info to a parsed FLP
108
+ * Only modifies fields that are provided (non-undefined)
109
+ *
110
+ * If `creationDate` or `workTimeSeconds` is set to `null`, the final value will be set on `0` (Delphi epoch)
111
+ */
112
+ export function writeProjectTimeInfo(parsed, info) {
113
+ // Read current values to preserve unmodified fields
114
+ const current = readProjectTimeInfo(parsed);
115
+ return patchEvents(parsed, (event) => {
116
+ if (event.id !== EVENT_ID.PROJECT_TIMESTAMP) {
117
+ return event;
118
+ }
119
+ const writer = new BinaryWriter();
120
+ // Write creation date
121
+ if (info.creationDate !== undefined && info.creationDate !== null) {
122
+ writer.writeF64LE(dateToDelphiDays(info.creationDate));
123
+ }
124
+ else if (current.creationDate !== null) {
125
+ writer.writeF64LE(dateToDelphiDays(current.creationDate));
126
+ }
127
+ else {
128
+ writer.writeF64LE(0);
129
+ }
130
+ // Write work time
131
+ if (info.workTimeSeconds !== undefined && info.workTimeSeconds !== null) {
132
+ writer.writeF64LE(info.workTimeSeconds / (24 * 60 * 60)); // Convert seconds to days
133
+ }
134
+ else if (current.workTimeSeconds !== null) {
135
+ writer.writeF64LE(current.workTimeSeconds / (24 * 60 * 60));
136
+ }
137
+ else {
138
+ writer.writeF64LE(0);
139
+ }
140
+ return {
141
+ ...event,
142
+ payload: writer.toBuffer(),
143
+ };
144
+ });
145
+ }
146
+ /**
147
+ * Lists all samples in the project
148
+ */
149
+ export function listSamples(parsed) {
150
+ const samples = [];
151
+ parsed.events.forEach((event, index) => {
152
+ if (event.id === EVENT_ID.CHANNEL_SAMPLE_PATH) {
153
+ const path = getEventString(event, parsed.useUnicode);
154
+ if (path && path.length > 0) {
155
+ samples.push({
156
+ eventIndex: index,
157
+ path,
158
+ });
159
+ }
160
+ }
161
+ });
162
+ return samples;
163
+ }
164
+ /**
165
+ * Rewrites sample paths using a mapper function
166
+ */
167
+ export function rewriteSamplePaths(parsed, mapper) {
168
+ return patchEvents(parsed, (event) => {
169
+ if (event.id !== EVENT_ID.CHANNEL_SAMPLE_PATH) {
170
+ return event;
171
+ }
172
+ const oldPath = getEventString(event, parsed.useUnicode);
173
+ if (!oldPath || oldPath.length === 0) {
174
+ return event;
175
+ }
176
+ const newPath = mapper(oldPath);
177
+ if (newPath === oldPath) {
178
+ return event;
179
+ }
180
+ return {
181
+ ...event,
182
+ payload: createTextPayload(newPath, parsed.useUnicode),
183
+ };
184
+ });
185
+ }
186
+ /**
187
+ * VST Plugin Event internal IDs (from plugin.py VSTPluginEvent)
188
+ */
189
+ const VST_EVENT_ID = {
190
+ MIDI: 1,
191
+ FLAGS: 2,
192
+ IO: 30,
193
+ INPUTS: 31,
194
+ OUTPUTS: 32,
195
+ PLUGIN_INFO: 50,
196
+ FOUR_CC: 51,
197
+ GUID: 52,
198
+ STATE: 53,
199
+ NAME: 54,
200
+ PLUGIN_PATH: 55,
201
+ VENDOR: 56,
202
+ };
203
+ /**
204
+ * Parses a VST plugin data event to extract name and vendor
205
+ * Based on VSTPluginEvent structure in PyFLP
206
+ */
207
+ function parseVstPluginData(payload) {
208
+ if (payload.length < 4) {
209
+ return { name: null, vendor: null };
210
+ }
211
+ const reader = new BinaryReader(payload);
212
+ // First 4 bytes: type marker (8, 10, or 11 for VSTs)
213
+ const typeMarker = reader.readU32LE();
214
+ // Skip if not a VST plugin marker
215
+ if (typeMarker !== 8 && typeMarker !== 10 && typeMarker !== 11) {
216
+ return { name: null, vendor: null };
217
+ }
218
+ let name = null;
219
+ let vendor = null;
220
+ // Parse sub-events
221
+ while (reader.remaining() >= 12) {
222
+ // At minimum: 4 (id) + 8 (size)
223
+ const subEventId = reader.readU32LE();
224
+ const dataSize = reader.readU32LE(); // Lower 32 bits of size
225
+ const dataSizeHigh = reader.readU32LE(); // Upper 32 bits (usually 0)
226
+ // Combine for 64-bit size (though in practice it's always small)
227
+ const actualSize = dataSize + dataSizeHigh * 0x100000000;
228
+ if (actualSize > reader.remaining()) {
229
+ break;
230
+ }
231
+ const data = reader.readBytes(actualSize);
232
+ // Extract name and vendor
233
+ if (subEventId === VST_EVENT_ID.NAME && actualSize > 0) {
234
+ name = data.toString("utf8").replace(/\0/g, "");
235
+ }
236
+ else if (subEventId === VST_EVENT_ID.VENDOR && actualSize > 0) {
237
+ vendor = data.toString("utf8").replace(/\0/g, "");
238
+ }
239
+ }
240
+ return { name, vendor };
241
+ }
242
+ /**
243
+ * Lists all plugins (VST and native) in the project
244
+ * Returns plugin name and vendor where available
245
+ */
246
+ export function listPlugins(parsed) {
247
+ const plugins = [];
248
+ const seenPlugins = new Set();
249
+ // Track current channel context
250
+ let currentInternalName = null;
251
+ let currentPluginName = null;
252
+ for (const event of parsed.events) {
253
+ // Track channel new events as boundaries
254
+ if (event.id === EVENT_ID.CHANNEL_NEW) {
255
+ // Reset context for new channel
256
+ if (currentInternalName || currentPluginName) {
257
+ // Save previous channel's plugin if it wasn't a VST (no plugin data)
258
+ const key = `${currentInternalName ?? ""}:${currentPluginName ?? ""}`;
259
+ if (!seenPlugins.has(key) && currentInternalName && currentInternalName !== "Fruity Wrapper") {
260
+ seenPlugins.add(key);
261
+ plugins.push({
262
+ name: currentPluginName || currentInternalName,
263
+ vendor: null, // Native plugins don't store vendor
264
+ });
265
+ }
266
+ }
267
+ currentInternalName = null;
268
+ currentPluginName = null;
269
+ }
270
+ // Track internal name (identifies plugin type)
271
+ if (event.id === EVENT_ID.PLUGIN_INTERNAL_NAME) {
272
+ currentInternalName = getEventString(event, parsed.useUnicode);
273
+ }
274
+ // Track display name
275
+ if (event.id === EVENT_ID.PLUGIN_NAME) {
276
+ currentPluginName = getEventString(event, parsed.useUnicode);
277
+ }
278
+ // Parse VST plugin data
279
+ if (event.id === EVENT_ID.PLUGIN_DATA && currentInternalName === "Fruity Wrapper") {
280
+ const vstData = parseVstPluginData(event.payload);
281
+ if (vstData.name) {
282
+ const key = `vst:${vstData.name}:${vstData.vendor ?? ""}`;
283
+ if (!seenPlugins.has(key)) {
284
+ seenPlugins.add(key);
285
+ plugins.push({
286
+ name: vstData.name,
287
+ vendor: vstData.vendor,
288
+ });
289
+ }
290
+ }
291
+ // Reset context after processing VST
292
+ currentInternalName = null;
293
+ currentPluginName = null;
294
+ }
295
+ }
296
+ // Handle last channel if any
297
+ if (currentInternalName && currentInternalName !== "Fruity Wrapper") {
298
+ const key = `${currentInternalName}:${currentPluginName ?? ""}`;
299
+ if (!seenPlugins.has(key)) {
300
+ plugins.push({
301
+ name: currentPluginName || currentInternalName,
302
+ vendor: null,
303
+ });
304
+ }
305
+ }
306
+ return plugins;
307
+ }
308
+ // ============================================================================
309
+ // FL Version
310
+ // ============================================================================
311
+ /**
312
+ * Gets the FL Studio version string from the project
313
+ */
314
+ export function getFlVersion(parsed) {
315
+ return parsed.flVersion;
316
+ }
317
+ /**
318
+ * Gets the PPQ (Pulses Per Quarter note) from the header
319
+ */
320
+ export function getPPQ(parsed) {
321
+ // PPQ is at offset 12-13 in the header (bytes 12-13 of the 14-byte FLhd chunk)
322
+ const reader = new BinaryReader(parsed.headerChunkBytes);
323
+ reader.seek(12);
324
+ return reader.readU16LE();
325
+ }
326
+ //# sourceMappingURL=ProjectApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectApi.js","sourceRoot":"","sources":["../../../src/api/ProjectApi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EAEd,cAAc,EACd,cAAc,EAEd,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAiBhC;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAClE,MAAM,aAAa,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACxE,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAElE,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QACvE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QACpF,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QAC7E,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QACxE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI;KAC3D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,IAA0B;IAC5E,OAAO,WAAW,CAAC,MAAM,EAAE,CAAC,KAAe,EAAE,EAAE;QAC7C,cAAc;QACd,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,aAAa,EAAE,CAAC;YACnE,OAAO;gBACL,GAAG,KAAK;gBACR,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC;aAC/D,CAAC;QACJ,CAAC;QAED,6BAA6B;QAC7B,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,gBAAgB,EAAE,CAAC;YAC7E,OAAO;gBACL,GAAG,KAAK;gBACR,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC;aACtE,CAAC;QACJ,CAAC;QAED,gBAAgB;QAChB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,eAAe,EAAE,CAAC;YACvE,OAAO;gBACL,GAAG,KAAK;gBACR,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC;aACjE,CAAC;QACJ,CAAC;QAED,cAAc;QACd,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,aAAa,EAAE,CAAC;YACpE,OAAO;gBACL,GAAG,KAAK;gBACR,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC;aAChE,CAAC;QACJ,CAAC;QAED,qCAAqC;QACrC,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC9E,OAAO;gBACL,GAAG,KAAK;gBACR,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC;aAC3E,CAAC;QACJ,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAcD,kCAAkC;AAClC,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAE5C;;GAEG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACtC,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,IAAU;IAClC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;IACnD,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAE1E,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC1D,OAAO;YACL,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,IAAI;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IACxD,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAEzC,OAAO;QACL,YAAY,EAAE,YAAY,CAAC,aAAa,CAAC;QACzC,eAAe,EAAE,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,0BAA0B;KAC1E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,IAA8B;IAE9B,oDAAoD;IACpD,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5C,OAAO,WAAW,CAAC,MAAM,EAAE,CAAC,KAAe,EAAE,EAAE;QAC7C,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YAC5C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAElC,sBAAsB;QACtB,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAClE,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QACzD,CAAC;aAAM,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YACzC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;QAED,kBAAkB;QAClB,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YACxE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,0BAA0B;QACtF,CAAC;aAAM,IAAI,OAAO,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAC5C,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,eAAe,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;QAED,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE;SAC3B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAgBD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,MAAM,OAAO,GAAgB,EAAE,CAAC;IAEhC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAe,EAAE,KAAa,EAAE,EAAE;QACvD,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YAC9C,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YACtD,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5B,OAAO,CAAC,IAAI,CAAC;oBACX,UAAU,EAAE,KAAK;oBACjB,IAAI;iBACL,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAiB,EACjB,MAAmC;IAEnC,OAAO,WAAW,CAAC,MAAM,EAAE,CAAC,KAAe,EAAE,EAAE;QAC7C,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YAC9C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QACzD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC;SACvD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAgBD;;GAEG;AACH,MAAM,YAAY,GAAG;IACnB,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,EAAE,EAAE,EAAE;IACN,MAAM,EAAE,EAAE;IACV,OAAO,EAAE,EAAE;IACX,WAAW,EAAE,EAAE;IACf,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,WAAW,EAAE,EAAE;IACf,MAAM,EAAE,EAAE;CACF,CAAC;AAEX;;;GAGG;AACH,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;IAEzC,qDAAqD;IACrD,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAEtC,kCAAkC;IAClC,IAAI,UAAU,KAAK,CAAC,IAAI,UAAU,KAAK,EAAE,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,MAAM,GAAkB,IAAI,CAAC;IAEjC,mBAAmB;IACnB,OAAO,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;QAChC,gCAAgC;QAChC,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,wBAAwB;QAC7D,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,4BAA4B;QAErE,iEAAiE;QACjE,MAAM,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,WAAW,CAAC;QAEzD,IAAI,UAAU,GAAG,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;YACpC,MAAM;QACR,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAE1C,0BAA0B;QAC1B,IAAI,UAAU,KAAK,YAAY,CAAC,IAAI,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACvD,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAClD,CAAC;aAAM,IAAI,UAAU,KAAK,YAAY,CAAC,MAAM,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YAChE,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,gCAAgC;IAChC,IAAI,mBAAmB,GAAkB,IAAI,CAAC;IAC9C,IAAI,iBAAiB,GAAkB,IAAI,CAAC;IAE5C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,yCAAyC;QACzC,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;YACtC,gCAAgC;YAChC,IAAI,mBAAmB,IAAI,iBAAiB,EAAE,CAAC;gBAC7C,qEAAqE;gBACrE,MAAM,GAAG,GAAG,GAAG,mBAAmB,IAAI,EAAE,IAAI,iBAAiB,IAAI,EAAE,EAAE,CAAC;gBACtE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,mBAAmB,IAAI,mBAAmB,KAAK,gBAAgB,EAAE,CAAC;oBAC7F,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACrB,OAAO,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,iBAAiB,IAAI,mBAAmB;wBAC9C,MAAM,EAAE,IAAI,EAAE,oCAAoC;qBACnD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,mBAAmB,GAAG,IAAI,CAAC;YAC3B,iBAAiB,GAAG,IAAI,CAAC;QAC3B,CAAC;QAED,+CAA+C;QAC/C,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,oBAAoB,EAAE,CAAC;YAC/C,mBAAmB,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QACjE,CAAC;QAED,qBAAqB;QACrB,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;YACtC,iBAAiB,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QAC/D,CAAC;QAED,wBAAwB;QACxB,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,WAAW,IAAI,mBAAmB,KAAK,gBAAgB,EAAE,CAAC;YAClF,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,MAAM,GAAG,GAAG,OAAO,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBAC1D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC1B,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACrB,OAAO,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,OAAO,CAAC,IAAI;wBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;qBACvB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,qCAAqC;YACrC,mBAAmB,GAAG,IAAI,CAAC;YAC3B,iBAAiB,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,IAAI,mBAAmB,IAAI,mBAAmB,KAAK,gBAAgB,EAAE,CAAC;QACpE,MAAM,GAAG,GAAG,GAAG,mBAAmB,IAAI,iBAAiB,IAAI,EAAE,EAAE,CAAC;QAChE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,iBAAiB,IAAI,mBAAmB;gBAC9C,MAAM,EAAE,IAAI;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+EAA+E;AAC/E,aAAa;AACb,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,MAAiB;IACtC,+EAA+E;IAC/E,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACzD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,MAAM,CAAC,SAAS,EAAE,CAAC;AAC5B,CAAC"}