ts-flp 1.0.4 → 1.0.6
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 +21 -674
- package/README.md +12 -1
- package/dist/index.cjs +174 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +174 -78
- package/dist/index.js.map +1 -1
- package/dist/src/api/ProjectApi.d.ts +1 -1
- package/dist/src/api/ProjectApi.d.ts.map +1 -1
- package/dist/src/generated/events.generated.d.ts +1 -1
- package/dist/src/generated/events.generated.d.ts.map +1 -1
- package/dist/src/parser/FlpParser.d.ts +9 -3
- package/dist/src/parser/FlpParser.d.ts.map +1 -1
- package/package.json +8 -5
- package/dist/test/flparser.test.d.ts +0 -6
- package/dist/test/flparser.test.d.ts.map +0 -1
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -160,6 +160,14 @@ interface PluginRef {
|
|
|
160
160
|
| `getFlVersion(parsed)` | Get the FL Studio version string |
|
|
161
161
|
| `getPPQ(parsed)` | Get the PPQ (Pulses Per Quarter note) |
|
|
162
162
|
|
|
163
|
+
### Write behavior
|
|
164
|
+
|
|
165
|
+
- `undefined` leaves the corresponding value unchanged.
|
|
166
|
+
- `null` clears text metadata, leaves BPM unchanged, and resets time values to zero.
|
|
167
|
+
- Missing metadata and timestamp events are created in the project preamble.
|
|
168
|
+
- BPM values are validated against the limits supported by the detected FL Studio version.
|
|
169
|
+
- Invalid dates, negative work times, and non-finite numeric values throw an error.
|
|
170
|
+
|
|
163
171
|
## Constraints and limitations
|
|
164
172
|
|
|
165
173
|
### What this library does
|
|
@@ -197,6 +205,9 @@ src/
|
|
|
197
205
|
# Install dependencies
|
|
198
206
|
pnpm install
|
|
199
207
|
|
|
208
|
+
# Add at least one private .flp file directly to test_projs/
|
|
209
|
+
# These fixtures are ignored by Git and are required by the integration tests.
|
|
210
|
+
|
|
200
211
|
# Type checking
|
|
201
212
|
pnpm run typecheck
|
|
202
213
|
|
|
@@ -215,7 +226,7 @@ pnpm run build
|
|
|
215
226
|
|
|
216
227
|
## License
|
|
217
228
|
|
|
218
|
-
[
|
|
229
|
+
[MIT](LICENSE)
|
|
219
230
|
|
|
220
231
|
---
|
|
221
232
|
|
package/dist/index.cjs
CHANGED
|
@@ -65,6 +65,9 @@ var WORD = 64;
|
|
|
65
65
|
var DWORD = 128;
|
|
66
66
|
var TEXT = 192;
|
|
67
67
|
var DATA = 208;
|
|
68
|
+
var FIXED_SIZE_OVERRIDES = {
|
|
69
|
+
172: 3
|
|
70
|
+
};
|
|
68
71
|
var NEW_TEXT_IDS = [
|
|
69
72
|
TEXT + 49,
|
|
70
73
|
// ArrangementID.Name
|
|
@@ -211,6 +214,7 @@ var EVENT_ID = {
|
|
|
211
214
|
// 236
|
|
212
215
|
};
|
|
213
216
|
var EVENT_KIND = {
|
|
217
|
+
[172]: "unknown",
|
|
214
218
|
// Project events
|
|
215
219
|
[EVENT_ID.PROJECT_LOOP_ACTIVE]: "u8",
|
|
216
220
|
[EVENT_ID.PROJECT_SHOW_INFO]: "u8",
|
|
@@ -319,6 +323,10 @@ function getEventKind(eventId) {
|
|
|
319
323
|
}
|
|
320
324
|
}
|
|
321
325
|
function getEventFixedSize(eventId) {
|
|
326
|
+
const override = FIXED_SIZE_OVERRIDES[eventId];
|
|
327
|
+
if (override !== void 0) {
|
|
328
|
+
return override;
|
|
329
|
+
}
|
|
322
330
|
if (eventId < WORD) {
|
|
323
331
|
return 1;
|
|
324
332
|
} else if (eventId < DWORD) {
|
|
@@ -609,8 +617,8 @@ function parseFlp(buffer) {
|
|
|
609
617
|
throw new Error(`Invalid header size: expected ${FLP_HEADER_SIZE}, got ${headerSize}`);
|
|
610
618
|
}
|
|
611
619
|
const format = reader.readI16LE();
|
|
612
|
-
|
|
613
|
-
|
|
620
|
+
reader.readU16LE();
|
|
621
|
+
reader.readU16LE();
|
|
614
622
|
if (format < -1 || format > 80) {
|
|
615
623
|
throw new Error(`Invalid file format: ${format}`);
|
|
616
624
|
}
|
|
@@ -630,7 +638,6 @@ function parseFlp(buffer) {
|
|
|
630
638
|
const events = [];
|
|
631
639
|
let flVersion = "0.0.0";
|
|
632
640
|
let useUnicode = false;
|
|
633
|
-
let detectedVersion = false;
|
|
634
641
|
const eventsStart = reader.tell();
|
|
635
642
|
const eventsEnd = eventsStart + eventsSize;
|
|
636
643
|
while (reader.tell() < eventsEnd) {
|
|
@@ -638,14 +645,9 @@ function parseFlp(buffer) {
|
|
|
638
645
|
const eventId = reader.readU8();
|
|
639
646
|
let payloadSize;
|
|
640
647
|
let headerSize2;
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
} else if (eventId < DWORD) {
|
|
645
|
-
payloadSize = 2;
|
|
646
|
-
headerSize2 = 1;
|
|
647
|
-
} else if (eventId < TEXT) {
|
|
648
|
-
payloadSize = 4;
|
|
648
|
+
const fixedSize = getEventFixedSize(eventId);
|
|
649
|
+
if (fixedSize >= 0) {
|
|
650
|
+
payloadSize = fixedSize;
|
|
649
651
|
headerSize2 = 1;
|
|
650
652
|
} else {
|
|
651
653
|
const sizeStart = reader.tell();
|
|
@@ -662,16 +664,14 @@ function parseFlp(buffer) {
|
|
|
662
664
|
payload: Buffer.from(payload)
|
|
663
665
|
};
|
|
664
666
|
events.push(event);
|
|
665
|
-
if (eventId === EVENT_ID.PROJECT_FL_VERSION &&
|
|
667
|
+
if (eventId === EVENT_ID.PROJECT_FL_VERSION && flVersion === "0.0.0") {
|
|
666
668
|
const candidate = payload.toString("ascii").replace(/\0/g, "").trim();
|
|
667
|
-
|
|
668
|
-
if (isSemverLike) {
|
|
669
|
+
if (/^\d+(\.\d+)+$/.test(candidate)) {
|
|
669
670
|
flVersion = candidate;
|
|
670
671
|
const parts = flVersion.split(".").map((p) => parseInt(p, 10));
|
|
671
672
|
const major = parts[0] ?? 0;
|
|
672
673
|
const minor = parts[1] ?? 0;
|
|
673
674
|
useUnicode = parts.length >= 2 && (major > 11 || major === 11 && minor >= 5);
|
|
674
|
-
detectedVersion = true;
|
|
675
675
|
}
|
|
676
676
|
}
|
|
677
677
|
}
|
|
@@ -738,7 +738,7 @@ function createEvent(id, payload) {
|
|
|
738
738
|
function patchEvents(parsed, patcher) {
|
|
739
739
|
const patchedEvents = parsed.events.map((event, index) => {
|
|
740
740
|
const patched = patcher(event, index);
|
|
741
|
-
if (patched.payload !== event.payload &&
|
|
741
|
+
if (patched.payload !== event.payload && event.header.length > 1) {
|
|
742
742
|
const headerWriter = new BinaryWriter();
|
|
743
743
|
headerWriter.writeU8(patched.id);
|
|
744
744
|
headerWriter.writeVarInt(patched.payload.length);
|
|
@@ -831,62 +831,166 @@ function createNumberPayload(value, kind) {
|
|
|
831
831
|
}
|
|
832
832
|
|
|
833
833
|
// src/api/ProjectApi.ts
|
|
834
|
+
var PROJECT_EVENT_ORDER = [
|
|
835
|
+
EVENT_ID.PROJECT_TEMPO,
|
|
836
|
+
EVENT_ID.PROJECT_TITLE,
|
|
837
|
+
EVENT_ID.PROJECT_GENRE,
|
|
838
|
+
EVENT_ID.PROJECT_ARTISTS,
|
|
839
|
+
EVENT_ID.PROJECT_COMMENTS,
|
|
840
|
+
EVENT_ID.PROJECT_TIMESTAMP
|
|
841
|
+
];
|
|
842
|
+
function insertProjectEvent(parsed, event) {
|
|
843
|
+
const orderIndex = PROJECT_EVENT_ORDER.indexOf(event.id);
|
|
844
|
+
let insertionIndex = -1;
|
|
845
|
+
if (orderIndex >= 0) {
|
|
846
|
+
const laterIds = new Set(PROJECT_EVENT_ORDER.slice(orderIndex + 1));
|
|
847
|
+
insertionIndex = parsed.events.findIndex((candidate) => laterIds.has(candidate.id));
|
|
848
|
+
if (insertionIndex < 0) {
|
|
849
|
+
const previousIds = new Set(PROJECT_EVENT_ORDER.slice(0, orderIndex + 1));
|
|
850
|
+
for (let index = parsed.events.length - 1; index >= 0; index--) {
|
|
851
|
+
if (previousIds.has(parsed.events[index].id)) {
|
|
852
|
+
insertionIndex = index + 1;
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (insertionIndex < 0) {
|
|
859
|
+
insertionIndex = parsed.events.findIndex((candidate) => candidate.id === EVENT_ID.CHANNEL_NEW);
|
|
860
|
+
}
|
|
861
|
+
if (insertionIndex < 0) {
|
|
862
|
+
const versionIndex = parsed.events.findIndex(
|
|
863
|
+
(candidate) => candidate.id === EVENT_ID.PROJECT_FL_VERSION
|
|
864
|
+
);
|
|
865
|
+
insertionIndex = versionIndex >= 0 ? versionIndex + 1 : parsed.events.length;
|
|
866
|
+
}
|
|
867
|
+
const events = [...parsed.events];
|
|
868
|
+
events.splice(insertionIndex, 0, event);
|
|
869
|
+
return { ...parsed, events };
|
|
870
|
+
}
|
|
871
|
+
function replaceEventPayload(parsed, target, payload) {
|
|
872
|
+
return patchEvents(parsed, (event) => event === target ? { ...event, payload } : event);
|
|
873
|
+
}
|
|
874
|
+
function readBpm(parsed) {
|
|
875
|
+
const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
|
|
876
|
+
if (tempoEvent) {
|
|
877
|
+
return getEventNumber(tempoEvent) / 1e3;
|
|
878
|
+
}
|
|
879
|
+
const coarseEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_COARSE);
|
|
880
|
+
if (coarseEvent) {
|
|
881
|
+
let bpm = coarseEvent.payload.readUInt16LE(0);
|
|
882
|
+
const fineEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_FINE);
|
|
883
|
+
if (fineEvent) {
|
|
884
|
+
bpm += fineEvent.payload.readUInt16LE(0) / 1e3;
|
|
885
|
+
}
|
|
886
|
+
return bpm;
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
834
890
|
function readProjectMeta(parsed) {
|
|
835
891
|
const titleEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TITLE);
|
|
836
892
|
const commentsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_COMMENTS);
|
|
837
893
|
const artistsEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_ARTISTS);
|
|
838
894
|
const genreEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_GENRE);
|
|
839
|
-
const tempoEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
|
|
840
895
|
return {
|
|
841
896
|
name: titleEvent ? getEventString(titleEvent, parsed.useUnicode) : null,
|
|
842
897
|
description: commentsEvent ? getEventString(commentsEvent, parsed.useUnicode) : null,
|
|
843
898
|
artist: artistsEvent ? getEventString(artistsEvent, parsed.useUnicode) : null,
|
|
844
899
|
genre: genreEvent ? getEventString(genreEvent, parsed.useUnicode) : null,
|
|
845
|
-
bpm:
|
|
900
|
+
bpm: readBpm(parsed)
|
|
846
901
|
};
|
|
847
902
|
}
|
|
903
|
+
function compareVersion(version, expected) {
|
|
904
|
+
const actual = version.split(".").map((part) => Number.parseInt(part, 10));
|
|
905
|
+
const length = Math.max(actual.length, expected.length);
|
|
906
|
+
for (let index = 0; index < length; index++) {
|
|
907
|
+
const difference = (actual[index] ?? 0) - (expected[index] ?? 0);
|
|
908
|
+
if (difference !== 0) return Math.sign(difference);
|
|
909
|
+
}
|
|
910
|
+
return 0;
|
|
911
|
+
}
|
|
912
|
+
function validateBpm(parsed, bpm) {
|
|
913
|
+
if (!Number.isFinite(bpm)) {
|
|
914
|
+
throw new RangeError("BPM must be a finite number");
|
|
915
|
+
}
|
|
916
|
+
const legacyHighTempo = compareVersion(parsed.flVersion, [1, 4, 2]) >= 0 && compareVersion(parsed.flVersion, [11]) < 0;
|
|
917
|
+
const maximum = legacyHighTempo ? 999 : 522;
|
|
918
|
+
if (bpm < 10 || bpm > maximum) {
|
|
919
|
+
throw new RangeError(`BPM must be between 10 and ${maximum} for FL Studio ${parsed.flVersion}`);
|
|
920
|
+
}
|
|
921
|
+
if (parsed.flVersion !== "0.0.0" && compareVersion(parsed.flVersion, [3, 4]) < 0 && !Number.isInteger(bpm)) {
|
|
922
|
+
throw new TypeError(`FL Studio ${parsed.flVersion} does not support decimal BPM values`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function writeTextProjectEvent(parsed, eventId, value) {
|
|
926
|
+
if (value === void 0) return parsed;
|
|
927
|
+
const payload = createTextPayload(value ?? "", parsed.useUnicode);
|
|
928
|
+
const existing = findFirstEvent(parsed, eventId);
|
|
929
|
+
return existing ? replaceEventPayload(parsed, existing, payload) : insertProjectEvent(parsed, createEvent(eventId, payload));
|
|
930
|
+
}
|
|
931
|
+
function writeProjectBpm(parsed, bpm) {
|
|
932
|
+
if (bpm === void 0 || bpm === null) return parsed;
|
|
933
|
+
validateBpm(parsed, bpm);
|
|
934
|
+
const scaledBpm = Math.round(bpm * 1e3);
|
|
935
|
+
const coarseBpm = Math.floor(scaledBpm / 1e3);
|
|
936
|
+
const fineBpm = scaledBpm % 1e3;
|
|
937
|
+
const modernEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TEMPO);
|
|
938
|
+
const coarseEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_COARSE);
|
|
939
|
+
const fineEvent = findFirstEvent(parsed, EVENT_ID.PROJECT__TEMPO_FINE);
|
|
940
|
+
let result = parsed;
|
|
941
|
+
if (modernEvent) {
|
|
942
|
+
result = replaceEventPayload(result, modernEvent, createNumberPayload(scaledBpm, "u32"));
|
|
943
|
+
}
|
|
944
|
+
if (coarseEvent) {
|
|
945
|
+
result = replaceEventPayload(result, coarseEvent, createNumberPayload(coarseBpm, "u16"));
|
|
946
|
+
}
|
|
947
|
+
if (fineEvent) {
|
|
948
|
+
result = replaceEventPayload(result, fineEvent, createNumberPayload(fineBpm, "u16"));
|
|
949
|
+
}
|
|
950
|
+
if (!modernEvent && !coarseEvent && !fineEvent) {
|
|
951
|
+
result = insertProjectEvent(
|
|
952
|
+
result,
|
|
953
|
+
createEvent(EVENT_ID.PROJECT_TEMPO, createNumberPayload(scaledBpm, "u32"))
|
|
954
|
+
);
|
|
955
|
+
} else if (!modernEvent && coarseEvent && !fineEvent && fineBpm > 0) {
|
|
956
|
+
const updatedCoarse = findFirstEvent(result, EVENT_ID.PROJECT__TEMPO_COARSE);
|
|
957
|
+
const index = result.events.indexOf(updatedCoarse) + 1;
|
|
958
|
+
const events = [...result.events];
|
|
959
|
+
events.splice(
|
|
960
|
+
index,
|
|
961
|
+
0,
|
|
962
|
+
createEvent(EVENT_ID.PROJECT__TEMPO_FINE, createNumberPayload(fineBpm, "u16"))
|
|
963
|
+
);
|
|
964
|
+
result = { ...result, events };
|
|
965
|
+
} else if (!modernEvent && !coarseEvent && fineEvent) {
|
|
966
|
+
const updatedFine = findFirstEvent(result, EVENT_ID.PROJECT__TEMPO_FINE);
|
|
967
|
+
const index = result.events.indexOf(updatedFine);
|
|
968
|
+
const events = [...result.events];
|
|
969
|
+
events.splice(
|
|
970
|
+
index,
|
|
971
|
+
0,
|
|
972
|
+
createEvent(EVENT_ID.PROJECT__TEMPO_COARSE, createNumberPayload(coarseBpm, "u16"))
|
|
973
|
+
);
|
|
974
|
+
result = { ...result, events };
|
|
975
|
+
}
|
|
976
|
+
return result;
|
|
977
|
+
}
|
|
848
978
|
function writeProjectMeta(parsed, meta) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
});
|
|
979
|
+
let result = parsed;
|
|
980
|
+
result = writeProjectBpm(result, meta.bpm);
|
|
981
|
+
result = writeTextProjectEvent(result, EVENT_ID.PROJECT_TITLE, meta.name);
|
|
982
|
+
result = writeTextProjectEvent(result, EVENT_ID.PROJECT_GENRE, meta.genre);
|
|
983
|
+
result = writeTextProjectEvent(result, EVENT_ID.PROJECT_ARTISTS, meta.artist);
|
|
984
|
+
result = writeTextProjectEvent(result, EVENT_ID.PROJECT_COMMENTS, meta.description);
|
|
985
|
+
return result;
|
|
882
986
|
}
|
|
883
|
-
var
|
|
987
|
+
var DELPHI_EPOCH_MS = Date.UTC(1899, 11, 30);
|
|
884
988
|
function delphiToDate(days) {
|
|
885
989
|
const ms = days * 24 * 60 * 60 * 1e3;
|
|
886
|
-
return new Date(
|
|
990
|
+
return new Date(DELPHI_EPOCH_MS + ms);
|
|
887
991
|
}
|
|
888
992
|
function dateToDelphiDays(date) {
|
|
889
|
-
const ms = date.getTime() -
|
|
993
|
+
const ms = date.getTime() - DELPHI_EPOCH_MS;
|
|
890
994
|
return ms / (24 * 60 * 60 * 1e3);
|
|
891
995
|
}
|
|
892
996
|
function readProjectTimeInfo(parsed) {
|
|
@@ -907,31 +1011,23 @@ function readProjectTimeInfo(parsed) {
|
|
|
907
1011
|
};
|
|
908
1012
|
}
|
|
909
1013
|
function writeProjectTimeInfo(parsed, info) {
|
|
1014
|
+
if (info.creationDate === void 0 && info.workTimeSeconds === void 0) {
|
|
1015
|
+
return parsed;
|
|
1016
|
+
}
|
|
910
1017
|
const current = readProjectTimeInfo(parsed);
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
});
|
|
1018
|
+
if (info.creationDate !== void 0 && info.creationDate !== null && Number.isNaN(info.creationDate.getTime())) {
|
|
1019
|
+
throw new RangeError("creationDate must be a valid Date");
|
|
1020
|
+
}
|
|
1021
|
+
if (info.workTimeSeconds !== void 0 && info.workTimeSeconds !== null && (!Number.isFinite(info.workTimeSeconds) || info.workTimeSeconds < 0)) {
|
|
1022
|
+
throw new RangeError("workTimeSeconds must be a finite, non-negative number");
|
|
1023
|
+
}
|
|
1024
|
+
const creationDateDays = info.creationDate === null ? 0 : info.creationDate !== void 0 ? dateToDelphiDays(info.creationDate) : current.creationDate !== null ? dateToDelphiDays(current.creationDate) : 0;
|
|
1025
|
+
const workTimeDays = info.workTimeSeconds === null ? 0 : info.workTimeSeconds !== void 0 ? info.workTimeSeconds / (24 * 60 * 60) : current.workTimeSeconds !== null ? current.workTimeSeconds / (24 * 60 * 60) : 0;
|
|
1026
|
+
const writer = new BinaryWriter();
|
|
1027
|
+
writer.writeF64LE(creationDateDays);
|
|
1028
|
+
writer.writeF64LE(workTimeDays);
|
|
1029
|
+
const timestampEvent = findFirstEvent(parsed, EVENT_ID.PROJECT_TIMESTAMP);
|
|
1030
|
+
return timestampEvent ? replaceEventPayload(parsed, timestampEvent, writer.toBuffer()) : insertProjectEvent(parsed, createEvent(EVENT_ID.PROJECT_TIMESTAMP, writer.toBuffer()));
|
|
935
1031
|
}
|
|
936
1032
|
function listSamples(parsed) {
|
|
937
1033
|
const samples = [];
|