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/LICENSE +674 -0
- package/README.md +226 -0
- package/dist/src/api/ProjectApi.d.ts +83 -0
- package/dist/src/api/ProjectApi.d.ts.map +1 -0
- package/dist/src/api/ProjectApi.js +326 -0
- package/dist/src/api/ProjectApi.js.map +1 -0
- package/dist/src/generated/events.generated.d.ts +126 -0
- package/dist/src/generated/events.generated.d.ts.map +1 -0
- package/dist/src/generated/events.generated.js +257 -0
- package/dist/src/generated/events.generated.js.map +1 -0
- package/dist/src/index.d.ts +37 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +41 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/io/BinaryReader.d.ts +94 -0
- package/dist/src/io/BinaryReader.d.ts.map +1 -0
- package/dist/src/io/BinaryReader.js +159 -0
- package/dist/src/io/BinaryReader.js.map +1 -0
- package/dist/src/io/BinaryWriter.d.ts +74 -0
- package/dist/src/io/BinaryWriter.d.ts.map +1 -0
- package/dist/src/io/BinaryWriter.js +124 -0
- package/dist/src/io/BinaryWriter.js.map +1 -0
- package/dist/src/parser/FlpParser.d.ts +90 -0
- package/dist/src/parser/FlpParser.d.ts.map +1 -0
- package/dist/src/parser/FlpParser.js +313 -0
- package/dist/src/parser/FlpParser.js.map +1 -0
- package/package.json +73 -0
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":"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"}
|