@jeffy-g/live-midi-types 0.10.0 → 0.11.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 +208 -106
- package/{ESMFEventType.d.ts → dist/ESMFEventType.d.ts} +26 -40
- package/dist/ESMFEventType.js +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/midi-clip.d.ts +6 -4
- package/dist/midi10-smf-io.d.ts +12 -0
- package/dist/midi10-smf-io.js +12 -0
- package/dist/midi10-smf-types.d.ts +40 -25
- package/dist/midi10-smf-types.js +0 -1
- package/dist/project.d.ts +4 -2
- package/dist/smf-parser.d.ts +22 -0
- package/dist/smf-parser.js +287 -0
- package/dist/smf-writer.d.ts +53 -0
- package/dist/smf-writer.js +473 -0
- package/package.json +9 -8
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Original Source: midi-file (https://github.com/carter-thaxton/midi-file/blob/master/lib/midi-writer.js)
|
|
3
|
+
* Original License: MIT License
|
|
4
|
+
*
|
|
5
|
+
* This file is a reimplementation of the original midi-writer.js with performance optimizations.
|
|
6
|
+
* The core logic is preserved, but the Writer class is replaced with a more efficient
|
|
7
|
+
* implementation that pre-calculates the buffer size and writes directly to a Uint8Array.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2025 jeffy-g
|
|
10
|
+
* Released under the MIT license.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @file src/smf-writer.ts
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @import {
|
|
17
|
+
* MidiData,
|
|
18
|
+
* MidiEvent,
|
|
19
|
+
* MidiHeader,
|
|
20
|
+
* MidiChannelEvent,
|
|
21
|
+
* MidiWriteOption,
|
|
22
|
+
* } from "./midi10-smf-types.ts";
|
|
23
|
+
*/
|
|
24
|
+
/** cache object */
|
|
25
|
+
const encoder = new TextEncoder();
|
|
26
|
+
/**
|
|
27
|
+
* A highly efficient writer that pre-allocates a buffer and writes data directly
|
|
28
|
+
* into it using offsets. This avoids the massive overhead of Array.push/concat.
|
|
29
|
+
*
|
|
30
|
+
* @see {@link https://sites.google.com/site/yyagisite/material/smfspec?authuser=0 SMF (Standard MIDI Files) の構造}
|
|
31
|
+
*/
|
|
32
|
+
class FastWriter {
|
|
33
|
+
/**
|
|
34
|
+
* @param {number} size
|
|
35
|
+
*/
|
|
36
|
+
constructor(size) {
|
|
37
|
+
this.writeInt8 = this.writeUInt8;
|
|
38
|
+
this.buffer = new Uint8Array(size);
|
|
39
|
+
this.view = new DataView(this.buffer.buffer);
|
|
40
|
+
this.offset = 0;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @param {number} value
|
|
44
|
+
*/
|
|
45
|
+
writeUInt8(value) {
|
|
46
|
+
this.view.setUint8(this.offset, value);
|
|
47
|
+
this.offset += 1;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @param {number} value
|
|
51
|
+
*/
|
|
52
|
+
writeUInt16(value) {
|
|
53
|
+
this.view.setUint16(this.offset, value, false);
|
|
54
|
+
this.offset += 2;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* @param {number} value
|
|
58
|
+
*/
|
|
59
|
+
writeUInt24(value) {
|
|
60
|
+
const view = this.view,
|
|
61
|
+
offset = this.offset;
|
|
62
|
+
view.setUint8(offset, (value >> 16) & 0xff);
|
|
63
|
+
view.setUint8(offset + 1, (value >> 8) & 0xff);
|
|
64
|
+
view.setUint8(offset + 2, value & 0xff);
|
|
65
|
+
this.offset += 3;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* @param {number} value
|
|
69
|
+
*/
|
|
70
|
+
writeUInt32(value) {
|
|
71
|
+
this.view.setUint32(this.offset, value, false);
|
|
72
|
+
this.offset += 4;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @param {ArrayLike<number>} data
|
|
76
|
+
*/
|
|
77
|
+
writeBytes(data) {
|
|
78
|
+
this.buffer.set(data, this.offset);
|
|
79
|
+
this.offset += data.length;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} str
|
|
83
|
+
*/
|
|
84
|
+
writeString(str) {
|
|
85
|
+
this.writeBytes(encoder.encode(str));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @param {number} value
|
|
89
|
+
*/
|
|
90
|
+
writeVarInt(value) {
|
|
91
|
+
const buf = this.buffer;
|
|
92
|
+
const offset = this.offset;
|
|
93
|
+
if (value <= 0x7f) {
|
|
94
|
+
buf[offset] = value;
|
|
95
|
+
this.offset = offset + 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let v = value;
|
|
99
|
+
const bytes = [];
|
|
100
|
+
bytes.push(v & 0x7f);
|
|
101
|
+
v >>>= 7;
|
|
102
|
+
while (v) {
|
|
103
|
+
bytes.push((v & 0x7f) | 0x80);
|
|
104
|
+
v >>>= 7;
|
|
105
|
+
}
|
|
106
|
+
v = bytes.length;
|
|
107
|
+
this.offset = offset + v;
|
|
108
|
+
let i = 0;
|
|
109
|
+
while (v--) {
|
|
110
|
+
buf[offset + v] = bytes[i++];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} id
|
|
115
|
+
* @param {Uint8Array} data
|
|
116
|
+
*/
|
|
117
|
+
writeChunk(id, data) {
|
|
118
|
+
this.writeString(id);
|
|
119
|
+
this.writeUInt32(data.length);
|
|
120
|
+
this.writeBytes(data);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* @param {number} msPerBeat microsecondsPerBeat
|
|
124
|
+
*/
|
|
125
|
+
writeSetTempo(msPerBeat) {
|
|
126
|
+
var view = this.view;
|
|
127
|
+
var offset = this.offset;
|
|
128
|
+
view.setUint8(offset, 0xff);
|
|
129
|
+
view.setUint8(offset + 1, 0x51);
|
|
130
|
+
view.setUint8(offset + 2, 3);
|
|
131
|
+
view.setUint8(offset + 3, (msPerBeat >> 16) & 0xff);
|
|
132
|
+
view.setUint8(offset + 4, (msPerBeat >> 8) & 0xff);
|
|
133
|
+
view.setUint8(offset + 5, msPerBeat & 0xff);
|
|
134
|
+
this.offset = offset + 6;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* @param {number} type
|
|
139
|
+
* @throws {Error} Unrecognized event type
|
|
140
|
+
*/
|
|
141
|
+
const throwInvalidType = (type) => {
|
|
142
|
+
throw new Error(`Unrecognized event type: ${type}`);
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Returns the number of bytes required to encode a non-negative integer
|
|
146
|
+
* as a MIDI 1.0 Variable-Length Quantity (VLQ).
|
|
147
|
+
*
|
|
148
|
+
* VLQ encoding uses 7 bits of payload per byte; the MSB of each byte
|
|
149
|
+
* is a continuation flag (1 = more bytes follow, 0 = final byte).
|
|
150
|
+
* The MIDI 1.0 spec caps values at 4 bytes → max encodable value is
|
|
151
|
+
* `0x0FFF_FFFF` (268,435,455 / 28 bits).
|
|
152
|
+
*
|
|
153
|
+
* ```
|
|
154
|
+
* value range encoded bytes
|
|
155
|
+
* ─────────────────────────── ─────────────
|
|
156
|
+
* 0x0000_0000 – 0x0000_007F 1
|
|
157
|
+
* 0x0000_0080 – 0x0000_3FFF 2
|
|
158
|
+
* 0x0000_4000 – 0x001F_FFFF 3
|
|
159
|
+
* 0x0020_0000 – 0x0FFF_FFFF 4
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* @see MIDI 1.0 Detailed Specification §2 — "Variable-Length Quantities"
|
|
163
|
+
*
|
|
164
|
+
* @param {number} value - A non-negative integer to encode as VLQ.
|
|
165
|
+
* @returns Byte count needed to hold the VLQ-encoded value (1–4).
|
|
166
|
+
* @throws {RangeError} If `value` is negative or exceeds `0x0FFF_FFFF`.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* getVarIntSize(0x0000_0000); // → 1 (MIDI delta-time of 0)
|
|
170
|
+
* getVarIntSize(0x0000_007F); // → 1
|
|
171
|
+
* getVarIntSize(0x0000_0080); // → 2
|
|
172
|
+
* getVarIntSize(0x0FFF_FFFF); // → 4 (spec maximum)
|
|
173
|
+
*/
|
|
174
|
+
export function getVarIntSize(value) {
|
|
175
|
+
if (value < 0) throw new RangeError(`VLQ value must be ≥ 0 (got ${value})`);
|
|
176
|
+
if (value <= 127) return 1;
|
|
177
|
+
if (value <= 16383) return 2;
|
|
178
|
+
if (value <= 2097151) return 3;
|
|
179
|
+
if (value <= 268435455) return 4;
|
|
180
|
+
throw new RangeError(`VLQ value 0x${value.toString(16)} exceeds MIDI 1.0 maximum (0x0FFF_FFFF)`);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* @template GType
|
|
184
|
+
* @typedef {GType & { bytes: Uint8Array }} TInternalTextEvent
|
|
185
|
+
*/
|
|
186
|
+
/** @type {(event: MidiEvent) => number} */
|
|
187
|
+
// @ts-expect- error `useByte9ForNoteOff` is unused...
|
|
188
|
+
function calculateEventSize(event /* , useByte9ForNoteOff?: boolean */) {
|
|
189
|
+
let size = getVarIntSize(event.deltaTime);
|
|
190
|
+
/** @type {number} */
|
|
191
|
+
let temp;
|
|
192
|
+
switch (event.type) {
|
|
193
|
+
case 0 /* ESMFEventType.sequenceNumber */:
|
|
194
|
+
size += 5;
|
|
195
|
+
break;
|
|
196
|
+
case 1 /* ESMFEventType.text */:
|
|
197
|
+
case 2 /* ESMFEventType.copyrightNotice */:
|
|
198
|
+
case 3 /* ESMFEventType.trackName */:
|
|
199
|
+
case 4 /* ESMFEventType.instrumentName */:
|
|
200
|
+
case 5 /* ESMFEventType.lyrics */:
|
|
201
|
+
case 6 /* ESMFEventType.marker */:
|
|
202
|
+
case 7 /* ESMFEventType.cuePoint */: {
|
|
203
|
+
const u8data = encoder.encode(event.text);
|
|
204
|
+
const u8dataLen = u8data.length;
|
|
205
|
+
size += 2 + getVarIntSize(u8dataLen) + u8dataLen;
|
|
206
|
+
/** @type {TInternalTextEvent<typeof event>} */ (event).bytes = u8data;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 8 /* ESMFEventType.noteOff */:
|
|
210
|
+
case 9 /* ESMFEventType.noteOn */:
|
|
211
|
+
case 10 /* ESMFEventType.noteAftertouch */:
|
|
212
|
+
case 11 /* ESMFEventType.controller */:
|
|
213
|
+
case 14 /* ESMFEventType.pitchBend */:
|
|
214
|
+
size += 3 - +!!event.running;
|
|
215
|
+
break;
|
|
216
|
+
case 12 /* ESMFEventType.programChange */:
|
|
217
|
+
case 13 /* ESMFEventType.channelAftertouch */:
|
|
218
|
+
size += 2 - +!!event.running;
|
|
219
|
+
break;
|
|
220
|
+
case 32 /* ESMFEventType.channelPrefix */:
|
|
221
|
+
case 33 /* ESMFEventType.portPrefix */:
|
|
222
|
+
size += 4;
|
|
223
|
+
break;
|
|
224
|
+
case 47 /* ESMFEventType.endOfTrack */:
|
|
225
|
+
size += 3;
|
|
226
|
+
break;
|
|
227
|
+
case 81 /* ESMFEventType.setTempo */:
|
|
228
|
+
size += 6;
|
|
229
|
+
break;
|
|
230
|
+
case 84 /* ESMFEventType.smpteOffset */:
|
|
231
|
+
size += 8;
|
|
232
|
+
break;
|
|
233
|
+
case 88 /* ESMFEventType.timeSignature */:
|
|
234
|
+
size += 7;
|
|
235
|
+
break;
|
|
236
|
+
case 89 /* ESMFEventType.keySignature */:
|
|
237
|
+
size += 5;
|
|
238
|
+
break;
|
|
239
|
+
case -1 /* ESMFEventType.unknownMeta */:
|
|
240
|
+
case 127 /* ESMFEventType.sequencerSpecific */:
|
|
241
|
+
size += 2 + getVarIntSize((temp = event.data.length)) + temp;
|
|
242
|
+
break;
|
|
243
|
+
case 240 /* ESMFEventType.sysEx */:
|
|
244
|
+
case 247 /* ESMFEventType.endSysEx */:
|
|
245
|
+
size += 1 + getVarIntSize((temp = event.data.length)) + temp;
|
|
246
|
+
break;
|
|
247
|
+
default:
|
|
248
|
+
throwInvalidType(/** @type {MidiEvent} */ (event).type);
|
|
249
|
+
}
|
|
250
|
+
return size;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* @param {MidiEvent[]} track
|
|
254
|
+
*/
|
|
255
|
+
function calculateTrackSize(track) {
|
|
256
|
+
let size = 8;
|
|
257
|
+
const trackLen = track.length;
|
|
258
|
+
for (let idx = 0; idx < trackLen;) {
|
|
259
|
+
size += calculateEventSize(track[idx++]);
|
|
260
|
+
}
|
|
261
|
+
return size;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* @param {MidiData} data
|
|
265
|
+
*/
|
|
266
|
+
function calculateMidiSize(data) {
|
|
267
|
+
let size = 14;
|
|
268
|
+
const trackEvents = data.tracks;
|
|
269
|
+
for (let idx = 0, trackEventsLen = trackEvents.length; idx < trackEventsLen;) {
|
|
270
|
+
size += calculateTrackSize(trackEvents[idx++]);
|
|
271
|
+
}
|
|
272
|
+
return size;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* @param {FastWriter} w
|
|
276
|
+
* @param {MidiHeader} header
|
|
277
|
+
* @param {number} numTracks
|
|
278
|
+
*/
|
|
279
|
+
function writeHeader(w, header, numTracks) {
|
|
280
|
+
const format = header.format ?? 1;
|
|
281
|
+
let timeDivision = 128;
|
|
282
|
+
if (header.timeDivision) {
|
|
283
|
+
timeDivision = header.timeDivision;
|
|
284
|
+
} else if (header.ticksPerFrame && header.framesPerSecond) {
|
|
285
|
+
timeDivision = (-(header.framesPerSecond & 0xff) << 8) | (header.ticksPerFrame & 0xff);
|
|
286
|
+
} else if (header.ticksPerBeat) {
|
|
287
|
+
timeDivision = header.ticksPerBeat & 0x7fff;
|
|
288
|
+
}
|
|
289
|
+
const h = new FastWriter(6);
|
|
290
|
+
h.writeUInt16(format);
|
|
291
|
+
h.writeUInt16(numTracks);
|
|
292
|
+
h.writeUInt16(timeDivision);
|
|
293
|
+
w.writeChunk("MThd", h.buffer);
|
|
294
|
+
}
|
|
295
|
+
const FRAME_RATES = { 24: 0x00, 25: 0x20, 29.97: 0x40, 30: 0x60 };
|
|
296
|
+
/** @type {(w: FastWriter, event: MidiEvent, lastEventTypeByte: number | undefined, useByte9ForNoteOff?: boolean) => number | undefined} */
|
|
297
|
+
function writeEvent(w, event, lastEventTypeByte, useByte9ForNoteOff) {
|
|
298
|
+
/** @type {number=} */
|
|
299
|
+
let eventTypeByte;
|
|
300
|
+
w.writeVarInt(event.deltaTime);
|
|
301
|
+
switch (event.type) {
|
|
302
|
+
case 0 /* ESMFEventType.sequenceNumber */:
|
|
303
|
+
w.writeUInt8(0xff);
|
|
304
|
+
w.writeUInt8(0x00);
|
|
305
|
+
w.writeVarInt(2);
|
|
306
|
+
w.writeUInt16(event.number);
|
|
307
|
+
break;
|
|
308
|
+
case 1 /* ESMFEventType.text */:
|
|
309
|
+
case 2 /* ESMFEventType.copyrightNotice */:
|
|
310
|
+
case 3 /* ESMFEventType.trackName */:
|
|
311
|
+
case 4 /* ESMFEventType.instrumentName */:
|
|
312
|
+
case 5 /* ESMFEventType.lyrics */:
|
|
313
|
+
case 6 /* ESMFEventType.marker */:
|
|
314
|
+
case 7 /* ESMFEventType.cuePoint */: {
|
|
315
|
+
w.writeUInt8(0xff);
|
|
316
|
+
w.writeUInt8(event.type);
|
|
317
|
+
const textBytes = /** @type {TInternalTextEvent<typeof event>} */ (event).bytes;
|
|
318
|
+
w.writeVarInt(textBytes.length);
|
|
319
|
+
w.writeBytes(textBytes);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case 8 /* ESMFEventType.noteOff */:
|
|
323
|
+
const noteMSB = (useByte9ForNoteOff !== false && event.byte9) || (useByte9ForNoteOff && event.velocity === 0) ? 0x90 : 0x80;
|
|
324
|
+
eventTypeByte = noteMSB | event.channel;
|
|
325
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
326
|
+
w.writeUInt8(event.noteNumber);
|
|
327
|
+
w.writeUInt8(event.velocity);
|
|
328
|
+
break;
|
|
329
|
+
case 9 /* ESMFEventType.noteOn */:
|
|
330
|
+
eventTypeByte = 0x90 | event.channel;
|
|
331
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
332
|
+
w.writeUInt8(event.noteNumber);
|
|
333
|
+
w.writeUInt8(event.velocity);
|
|
334
|
+
break;
|
|
335
|
+
case 10 /* ESMFEventType.noteAftertouch */:
|
|
336
|
+
eventTypeByte = 0xa0 | event.channel;
|
|
337
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
338
|
+
w.writeUInt8(event.noteNumber);
|
|
339
|
+
w.writeUInt8(event.amount);
|
|
340
|
+
break;
|
|
341
|
+
case 11 /* ESMFEventType.controller */:
|
|
342
|
+
eventTypeByte = 0xb0 | event.channel;
|
|
343
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
344
|
+
w.writeUInt8(event.controllerType);
|
|
345
|
+
w.writeUInt8(event.value);
|
|
346
|
+
break;
|
|
347
|
+
case 12 /* ESMFEventType.programChange */:
|
|
348
|
+
eventTypeByte = 0xc0 | event.channel;
|
|
349
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
350
|
+
w.writeUInt8(event.programNumber);
|
|
351
|
+
break;
|
|
352
|
+
case 13 /* ESMFEventType.channelAftertouch */:
|
|
353
|
+
eventTypeByte = 0xd0 | event.channel;
|
|
354
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
355
|
+
w.writeUInt8(event.amount);
|
|
356
|
+
break;
|
|
357
|
+
case 14 /* ESMFEventType.pitchBend */:
|
|
358
|
+
eventTypeByte = 0xe0 | event.channel;
|
|
359
|
+
if (eventTypeByte !== lastEventTypeByte) w.writeUInt8(eventTypeByte);
|
|
360
|
+
const value14 = 0x2000 + event.value;
|
|
361
|
+
w.writeUInt8(value14 & 0x7f);
|
|
362
|
+
w.writeUInt8((value14 >> 7) & 0x7f);
|
|
363
|
+
break;
|
|
364
|
+
case 32 /* ESMFEventType.channelPrefix */:
|
|
365
|
+
w.writeUInt8(0xff);
|
|
366
|
+
w.writeUInt8(0x20);
|
|
367
|
+
w.writeVarInt(1);
|
|
368
|
+
w.writeUInt8(event.channel);
|
|
369
|
+
break;
|
|
370
|
+
case 33 /* ESMFEventType.portPrefix */:
|
|
371
|
+
w.writeUInt8(0xff);
|
|
372
|
+
w.writeUInt8(0x21);
|
|
373
|
+
w.writeVarInt(1);
|
|
374
|
+
w.writeUInt8(event.port);
|
|
375
|
+
break;
|
|
376
|
+
case 47 /* ESMFEventType.endOfTrack */:
|
|
377
|
+
w.writeUInt8(0xff);
|
|
378
|
+
w.writeUInt8(0x2f);
|
|
379
|
+
w.writeVarInt(0);
|
|
380
|
+
break;
|
|
381
|
+
case 81 /* ESMFEventType.setTempo */:
|
|
382
|
+
w.writeSetTempo(event.microsecondsPerBeat);
|
|
383
|
+
break;
|
|
384
|
+
case 84 /* ESMFEventType.smpteOffset */:
|
|
385
|
+
w.writeUInt8(0xff);
|
|
386
|
+
w.writeUInt8(0x54);
|
|
387
|
+
w.writeVarInt(5);
|
|
388
|
+
const hourByte = (event.hour & 0x1f) | FRAME_RATES[event.frameRate];
|
|
389
|
+
w.writeUInt8(hourByte);
|
|
390
|
+
w.writeUInt8(event.min);
|
|
391
|
+
w.writeUInt8(event.sec);
|
|
392
|
+
w.writeUInt8(event.frame);
|
|
393
|
+
w.writeUInt8(event.subFrame);
|
|
394
|
+
break;
|
|
395
|
+
case 88 /* ESMFEventType.timeSignature */:
|
|
396
|
+
w.writeUInt8(0xff);
|
|
397
|
+
w.writeUInt8(0x58);
|
|
398
|
+
w.writeVarInt(4);
|
|
399
|
+
w.writeUInt8(event.numerator);
|
|
400
|
+
const denominator = Math.floor(Math.log(event.denominator) / Math.LN2) & 0xff;
|
|
401
|
+
w.writeUInt8(denominator);
|
|
402
|
+
w.writeUInt8(event.metronome);
|
|
403
|
+
w.writeUInt8(event.thirtyseconds || 8);
|
|
404
|
+
break;
|
|
405
|
+
case 89 /* ESMFEventType.keySignature */:
|
|
406
|
+
w.writeUInt8(0xff);
|
|
407
|
+
w.writeUInt8(0x59);
|
|
408
|
+
w.writeVarInt(2);
|
|
409
|
+
w.writeInt8(event.key);
|
|
410
|
+
w.writeUInt8(event.scale);
|
|
411
|
+
break;
|
|
412
|
+
case 127 /* ESMFEventType.sequencerSpecific */:
|
|
413
|
+
w.writeUInt8(0xff);
|
|
414
|
+
w.writeUInt8(0x7f);
|
|
415
|
+
w.writeVarInt(event.data.length);
|
|
416
|
+
w.writeBytes(event.data);
|
|
417
|
+
break;
|
|
418
|
+
case -1 /* ESMFEventType.unknownMeta */:
|
|
419
|
+
if (event.metatypeByte != null) {
|
|
420
|
+
w.writeUInt8(0xff);
|
|
421
|
+
w.writeUInt8(event.metatypeByte);
|
|
422
|
+
w.writeVarInt(event.data.length);
|
|
423
|
+
w.writeBytes(event.data);
|
|
424
|
+
}
|
|
425
|
+
break;
|
|
426
|
+
case 240 /* ESMFEventType.sysEx */:
|
|
427
|
+
w.writeUInt8(0xf0);
|
|
428
|
+
w.writeVarInt(event.data.length);
|
|
429
|
+
w.writeBytes(event.data);
|
|
430
|
+
break;
|
|
431
|
+
case 247 /* ESMFEventType.endSysEx */:
|
|
432
|
+
w.writeUInt8(0xf7);
|
|
433
|
+
w.writeVarInt(event.data.length);
|
|
434
|
+
w.writeBytes(event.data);
|
|
435
|
+
break;
|
|
436
|
+
default:
|
|
437
|
+
throwInvalidType(/** @type {MidiEvent} */ (event).type);
|
|
438
|
+
}
|
|
439
|
+
return eventTypeByte;
|
|
440
|
+
}
|
|
441
|
+
/** @type {(writer: FastWriter, track: MidiEvent[], opts: MidiWriteOption) => void} */
|
|
442
|
+
function writeTrack(writer, track, opts) {
|
|
443
|
+
writer.writeString("MTrk");
|
|
444
|
+
writer.writeUInt32(calculateTrackSize(track) - 8);
|
|
445
|
+
let eventTypeByte;
|
|
446
|
+
for (let idx = 0, L = track.length; idx < L;) {
|
|
447
|
+
const midiEvent = track[idx++];
|
|
448
|
+
if (opts.running === false || (!opts.running && !(/** @type {MidiChannelEvent<unknown>} */ (midiEvent).running))) {
|
|
449
|
+
eventTypeByte = undefined;
|
|
450
|
+
}
|
|
451
|
+
eventTypeByte = writeEvent(writer, midiEvent, eventTypeByte, opts.useByte9ForNoteOff);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Writes MIDI data to a Uint8Array.
|
|
456
|
+
* @param {MidiData} data The MIDI data to write.
|
|
457
|
+
* @param {MidiWriteOption=} opts Options for writing the MIDI file.
|
|
458
|
+
* @returns A Uint8Array containing the MIDI file data.
|
|
459
|
+
*/
|
|
460
|
+
export function writeMidi(data, opts = {}) {
|
|
461
|
+
if (typeof data !== "object") {
|
|
462
|
+
throw new Error("Invalid MIDI data");
|
|
463
|
+
}
|
|
464
|
+
const header = data.header || {};
|
|
465
|
+
const tracks = data.tracks || [];
|
|
466
|
+
const totalSize = calculateMidiSize(data);
|
|
467
|
+
const w = new FastWriter(totalSize);
|
|
468
|
+
writeHeader(w, header, tracks.length);
|
|
469
|
+
for (const track of tracks) {
|
|
470
|
+
writeTrack(w, track, opts);
|
|
471
|
+
}
|
|
472
|
+
return w.buffer;
|
|
473
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffy-g/live-midi-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Common TypeScript types for Ableton Live MIDI clip and event data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,18 +16,19 @@
|
|
|
16
16
|
"require": "./dist/midi10-smf-types.js",
|
|
17
17
|
"types": "./dist/midi10-smf-types.d.ts"
|
|
18
18
|
},
|
|
19
|
-
"./smf-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
19
|
+
"./midi10-smf-io": {
|
|
20
|
+
"types": "./dist/midi10-smf-io.d.ts",
|
|
21
|
+
"import": "./dist/midi10-smf-io.js",
|
|
22
|
+
"default": "./dist/midi10-smf-io.js"
|
|
23
23
|
},
|
|
24
24
|
"./ESMFEventType": {
|
|
25
|
-
"types": "./ESMFEventType.d.ts"
|
|
25
|
+
"types": "./dist/ESMFEventType.d.ts",
|
|
26
|
+
"import": "./dist/smf-event-type.js",
|
|
27
|
+
"default": "./dist/smf-event-type.js"
|
|
26
28
|
}
|
|
27
29
|
},
|
|
28
30
|
"files": [
|
|
29
|
-
"dist"
|
|
30
|
-
"ESMFEventType.d.ts"
|
|
31
|
+
"dist"
|
|
31
32
|
],
|
|
32
33
|
"author": "jeffy-g <hirotom1107@gmail.com>",
|
|
33
34
|
"license": "MIT",
|