@modplayjs/fmt-med 0.0.0-stage → 0.2.1
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 +39 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/instr.d.ts +69 -0
- package/dist/instr.d.ts.map +1 -0
- package/dist/instr.js +453 -0
- package/dist/instr.js.map +1 -0
- package/dist/med2.d.ts +12 -0
- package/dist/med2.d.ts.map +1 -0
- package/dist/med2.js +240 -0
- package/dist/med2.js.map +1 -0
- package/dist/med3.d.ts +8 -0
- package/dist/med3.d.ts.map +1 -0
- package/dist/med3.js +464 -0
- package/dist/med3.js.map +1 -0
- package/dist/med4.d.ts +5 -0
- package/dist/med4.d.ts.map +1 -0
- package/dist/med4.js +813 -0
- package/dist/med4.js.map +1 -0
- package/dist/mmd1.d.ts +11 -0
- package/dist/mmd1.d.ts.map +1 -0
- package/dist/mmd1.js +496 -0
- package/dist/mmd1.js.map +1 -0
- package/dist/mmd3.d.ts +10 -0
- package/dist/mmd3.d.ts.map +1 -0
- package/dist/mmd3.js +440 -0
- package/dist/mmd3.js.map +1 -0
- package/dist/mmdCommon.d.ts +98 -0
- package/dist/mmdCommon.d.ts.map +1 -0
- package/dist/mmdCommon.js +395 -0
- package/dist/mmdCommon.js.map +1 -0
- package/dist/readevent.d.ts +6 -0
- package/dist/readevent.d.ts.map +1 -0
- package/dist/readevent.js +168 -0
- package/dist/readevent.js.map +1 -0
- package/package.json +47 -4
package/dist/med4.js
ADDED
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
// Copyright (c) 2026 Bitti09 — modplayjs contributors
|
|
3
|
+
// Ported from: libxmp src/loaders/med4_load.c (med4_test :46-54,
|
|
4
|
+
// fix_effect :56-108, nibble stream :110-169, med4_load :497-992).
|
|
5
|
+
// MED 2.10 MED4 — bitmasked nibble-stream patterns, synth/hybrid
|
|
6
|
+
// instruments (MSH0 chunks), and an IFF-style MEDV/ANNO tail.
|
|
7
|
+
import { ParseError, Quirk, ReadEventType, SampleFlags, } from '@modplayjs/core';
|
|
8
|
+
import { FLAG_INSTRSATT, MED_TIME_FACTOR, MED_VER_210, mmdConvertTempo, } from './mmdCommon.js';
|
|
9
|
+
const FX_VOLSLIDE = 0x0a;
|
|
10
|
+
const FX_BREAK = 0x0d;
|
|
11
|
+
const FX_EXTENDED = 0x0e;
|
|
12
|
+
const FX_S3M_BPM = 0xab;
|
|
13
|
+
const FX_SPEED = 0x0f;
|
|
14
|
+
const FX_MED_RETRIG = 0x93;
|
|
15
|
+
const EX_RETRIG = 0x9;
|
|
16
|
+
const EX_DELAY = 0xd;
|
|
17
|
+
/** MAGIC_MED4 = "MED\x04". */
|
|
18
|
+
export function med4Test(bytes) {
|
|
19
|
+
if (bytes.length < 4)
|
|
20
|
+
return false;
|
|
21
|
+
return bytes[0] === 0x4d && bytes[1] === 0x45 && bytes[2] === 0x44 && bytes[3] === 0x04;
|
|
22
|
+
}
|
|
23
|
+
function makeSub(vol, sid) {
|
|
24
|
+
return {
|
|
25
|
+
vol, gvl: 0x40, pan: -1, xpo: 0, fin: 0, vwf: 0, vde: 0,
|
|
26
|
+
vra: 0, vsw: 0, sid, rvv: 0, nna: 0, dct: 0, dca: 0, ifc: 0, ifr: 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function makeInstrument(name) {
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
volume: 0x40,
|
|
33
|
+
nsm: 0,
|
|
34
|
+
rls: 0,
|
|
35
|
+
map: new Array(121).fill(0),
|
|
36
|
+
mapXpo: new Array(121).fill(0),
|
|
37
|
+
sub: [],
|
|
38
|
+
aei: { flags: 0, npt: 0, scl: 0, sus: 0, sue: 0, lps: 0, lpe: 0, x: [], y: [] },
|
|
39
|
+
fei: { flags: 0, npt: 0, scl: 0, sus: 0, sue: 0, lps: 0, lpe: 0, x: [], y: [] },
|
|
40
|
+
pei: { flags: 0, npt: 0, scl: 0, sus: 0, sue: 0, lps: 0, lpe: 0, x: [], y: [] },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** fix_effect (med4_load.c:56-108). */
|
|
44
|
+
function fixEffect(e, hexvol) {
|
|
45
|
+
switch (e.fxt) {
|
|
46
|
+
case 0x00: // arpeggio
|
|
47
|
+
case 0x01: // slide up
|
|
48
|
+
case 0x02: // slide down
|
|
49
|
+
case 0x03: // portamento
|
|
50
|
+
case 0x04: // vibrato?
|
|
51
|
+
break;
|
|
52
|
+
case 0x09: // set speed (3.00+)
|
|
53
|
+
if (e.fxp >= 0x01 && e.fxp <= 0x20) {
|
|
54
|
+
e.fxt = FX_SPEED;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
e.fxt = 0;
|
|
58
|
+
e.fxp = 0;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case 0x0c: // set volume (BCD)
|
|
62
|
+
if (!hexvol) {
|
|
63
|
+
e.fxp = ((((e.fxp >> 4) & 0x0f) * 10) + (e.fxp & 0x0f)) & 0xff;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case 0x0d: // volume slides
|
|
67
|
+
e.fxt = FX_VOLSLIDE;
|
|
68
|
+
break;
|
|
69
|
+
case 0x0f: // tempo/break
|
|
70
|
+
if (e.fxp === 0) {
|
|
71
|
+
e.fxt = FX_BREAK;
|
|
72
|
+
}
|
|
73
|
+
else if (e.fxp === 0xff) {
|
|
74
|
+
e.fxp = 0;
|
|
75
|
+
e.fxt = 0;
|
|
76
|
+
e.vol = 1;
|
|
77
|
+
}
|
|
78
|
+
else if (e.fxp === 0xf1) {
|
|
79
|
+
// Retrigger once on tick 3.
|
|
80
|
+
e.fxt = FX_EXTENDED;
|
|
81
|
+
e.fxp = (EX_RETRIG << 4) | 3;
|
|
82
|
+
}
|
|
83
|
+
else if (e.fxp === 0xf2) {
|
|
84
|
+
// Delay until tick 3.
|
|
85
|
+
e.fxt = FX_EXTENDED;
|
|
86
|
+
e.fxp = (EX_DELAY << 4) | 3;
|
|
87
|
+
}
|
|
88
|
+
else if (e.fxp === 0xf3) {
|
|
89
|
+
// Retrigger every 2 ticks (TODO: buggy).
|
|
90
|
+
e.fxt = FX_MED_RETRIG;
|
|
91
|
+
e.fxp = 0x02;
|
|
92
|
+
}
|
|
93
|
+
else if (e.fxp <= 0xf0) {
|
|
94
|
+
e.fxt = FX_S3M_BPM;
|
|
95
|
+
e.fxp = mmdConvertTempo(e.fxp, 0, 0);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
e.fxp = 0;
|
|
99
|
+
e.fxt = 0;
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
e.fxp = 0;
|
|
104
|
+
e.fxt = 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Nibble stream (med4_load.c:110-169). */
|
|
108
|
+
class Stream {
|
|
109
|
+
bytes;
|
|
110
|
+
hasNibble = false;
|
|
111
|
+
value = 0;
|
|
112
|
+
pos;
|
|
113
|
+
constructor(bytes, pos) {
|
|
114
|
+
this.bytes = bytes;
|
|
115
|
+
this.pos = pos;
|
|
116
|
+
}
|
|
117
|
+
read4() {
|
|
118
|
+
this.hasNibble = !this.hasNibble;
|
|
119
|
+
if (!this.hasNibble) {
|
|
120
|
+
return this.value & 0x0f;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.value = this.bytes[this.pos++] ?? 0;
|
|
124
|
+
return this.value >> 4;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
read8() {
|
|
128
|
+
const a = this.read4();
|
|
129
|
+
const b = this.read4();
|
|
130
|
+
return (a << 4) | b;
|
|
131
|
+
}
|
|
132
|
+
read12() {
|
|
133
|
+
const a = this.read4();
|
|
134
|
+
const b = this.read4();
|
|
135
|
+
const c = this.read4();
|
|
136
|
+
return (a << 8) | (b << 4) | c;
|
|
137
|
+
}
|
|
138
|
+
read16() {
|
|
139
|
+
const a = this.read4();
|
|
140
|
+
const b = this.read4();
|
|
141
|
+
const c = this.read4();
|
|
142
|
+
const d = this.read4();
|
|
143
|
+
return ((a << 12) | (b << 8) | (c << 4) | d) & 0xffff;
|
|
144
|
+
}
|
|
145
|
+
readAligned16(bits) {
|
|
146
|
+
if (bits <= 4)
|
|
147
|
+
return this.read4() << 12;
|
|
148
|
+
if (bits <= 8)
|
|
149
|
+
return this.read8() << 8;
|
|
150
|
+
if (bits <= 12)
|
|
151
|
+
return this.read12() << 4;
|
|
152
|
+
return this.read16();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export function med4Load(bytes, ctx) {
|
|
156
|
+
let pos = 4; // skip magic
|
|
157
|
+
let commentText = '';
|
|
158
|
+
// Instrument-loaders read through `curBytes`/`curDvL` so the external
|
|
159
|
+
// instrument path can redirect them at a sibling file's bytes
|
|
160
|
+
// (med4_load_external_instrument opens a separate hio handle).
|
|
161
|
+
const dvL = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
162
|
+
let curBytes = bytes;
|
|
163
|
+
let curDvL = dvL;
|
|
164
|
+
const u8 = () => bytes[pos++] ?? 0;
|
|
165
|
+
const s8 = () => ((u8() << 24) >> 24);
|
|
166
|
+
const u16 = () => { const v = (bytes[pos] << 8) | bytes[pos + 1]; pos += 2; return v; };
|
|
167
|
+
const u32 = () => {
|
|
168
|
+
const v = ((bytes[pos] << 24) | (bytes[pos + 1] << 16) |
|
|
169
|
+
(bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;
|
|
170
|
+
pos += 4;
|
|
171
|
+
return v;
|
|
172
|
+
};
|
|
173
|
+
const seek = (p) => { pos = p; };
|
|
174
|
+
const skip = (n) => { pos += n; };
|
|
175
|
+
let vermaj = 2;
|
|
176
|
+
let vermin = 10;
|
|
177
|
+
// Check if we have a MEDV chunk at the end of the file
|
|
178
|
+
// (med4_load.c:521-540).
|
|
179
|
+
{
|
|
180
|
+
const save = pos;
|
|
181
|
+
if (bytes.length > 2000) {
|
|
182
|
+
const tailPos = bytes.length - 1024;
|
|
183
|
+
const buf = bytes.subarray(tailPos);
|
|
184
|
+
for (let i = 0; i < 1013; i++) {
|
|
185
|
+
if (buf[i] === 0x4d && buf[i + 1] === 0x45 && buf[i + 2] === 0x44 &&
|
|
186
|
+
buf[i + 3] === 0x56 && buf[i + 4] === 0 && buf[i + 5] === 0 &&
|
|
187
|
+
buf[i + 6] === 0 && buf[i + 7] === 4) {
|
|
188
|
+
vermaj = buf[i + 10];
|
|
189
|
+
vermin = buf[i + 11];
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
pos = save;
|
|
195
|
+
}
|
|
196
|
+
const trackerType = `MED ${vermaj}.${String(vermin).padStart(2, '0')} MED4`;
|
|
197
|
+
const m0 = u8();
|
|
198
|
+
// Instrument mask (med4_load.c:546-562): variable-length big-endian mask
|
|
199
|
+
// collected into a 64-bit value, left-aligned. Uses BigInt so >4-byte
|
|
200
|
+
// masks keep C's semantics.
|
|
201
|
+
let maskHi = 0;
|
|
202
|
+
let mask = 0;
|
|
203
|
+
{
|
|
204
|
+
const bytesArr = [];
|
|
205
|
+
let m = m0;
|
|
206
|
+
for (let i = 0; m !== 0 && i < 8; i++, m = (m << 1) & 0xff) {
|
|
207
|
+
if (m & 0x80)
|
|
208
|
+
bytesArr.push(u8());
|
|
209
|
+
}
|
|
210
|
+
if (bytesArr.length > 0) {
|
|
211
|
+
let big = 0n;
|
|
212
|
+
for (const b of bytesArr)
|
|
213
|
+
big = (big << 8n) | BigInt(b);
|
|
214
|
+
big <<= BigInt(8 * (8 - bytesArr.length));
|
|
215
|
+
maskHi = Number((big >> 32n) & 0xffffffffn);
|
|
216
|
+
mask = Number(big & 0xffffffffn);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Read instrument names in temporary space (med4_load.c:564-607).
|
|
220
|
+
const tempInst = [];
|
|
221
|
+
for (let i = 0; i < 64; i++)
|
|
222
|
+
tempInst.push({ name: '', loop_start: 0, loop_end: 0, volume: 0, transpose: 0 });
|
|
223
|
+
let numIns = 0;
|
|
224
|
+
{
|
|
225
|
+
// 64-bit mask test: C's `if ((int64)mask > 0) continue;` — a set bit in
|
|
226
|
+
// the top byte of the sign position means the shifted value is negative
|
|
227
|
+
// (keep iterating); a clear sign bit with nonzero value is positive
|
|
228
|
+
// (skip). Emulate with BigInt.
|
|
229
|
+
const m64base = (BigInt(maskHi) << 32n) | BigInt(mask >>> 0);
|
|
230
|
+
for (let i = 0; i < 64; i++) {
|
|
231
|
+
// C: (mask <<= 1) each iteration, 64-bit wraparound; test
|
|
232
|
+
// (int64)mask > 0 → skip. BigInt shifts must wrap at 64 bits.
|
|
233
|
+
const m64 = ((m64base << BigInt(i)) & 0xffffffffffffffffn);
|
|
234
|
+
if (m64 === 0n)
|
|
235
|
+
break;
|
|
236
|
+
const asS64 = m64 >= (1n << 63n) ? m64 - (1n << 64n) : m64;
|
|
237
|
+
if (asS64 > 0n)
|
|
238
|
+
continue;
|
|
239
|
+
numIns = i + 1;
|
|
240
|
+
const t = tempInst[i];
|
|
241
|
+
// Read flags.
|
|
242
|
+
const c = u8();
|
|
243
|
+
// Read instrument name.
|
|
244
|
+
const size = u8();
|
|
245
|
+
const nameBuf = bytes.subarray(pos, pos + size);
|
|
246
|
+
pos += size;
|
|
247
|
+
let s = '';
|
|
248
|
+
for (let j = 0; j < nameBuf.length; j++) {
|
|
249
|
+
const ch = nameBuf[j];
|
|
250
|
+
if (ch === 0)
|
|
251
|
+
break;
|
|
252
|
+
s += ch > 127 || ch < 0x20 || ch === 0x7f ? '.' : String.fromCharCode(ch);
|
|
253
|
+
}
|
|
254
|
+
t.name = s;
|
|
255
|
+
t.volume = 0x40;
|
|
256
|
+
let loopLen = 0;
|
|
257
|
+
if ((c & 0x01) === 0)
|
|
258
|
+
t.loop_start = u16() << 1;
|
|
259
|
+
if ((c & 0x02) === 0)
|
|
260
|
+
loopLen = u16() << 1;
|
|
261
|
+
if ((c & 0x04) === 0)
|
|
262
|
+
u8(); // ? Tanko2 (MED 3.00 demo)
|
|
263
|
+
if ((c & 0x08) === 0)
|
|
264
|
+
u8(); // Tim Newsham's "span"
|
|
265
|
+
if ((c & 0x30) === 0)
|
|
266
|
+
t.volume = u8();
|
|
267
|
+
if ((c & 0x40) === 0)
|
|
268
|
+
t.transpose = s8();
|
|
269
|
+
t.loop_end = t.loop_start + loopLen;
|
|
270
|
+
// libxmp_copy_adjust(temp_inst[i].name, buf, 32) (med4_load.c:606 →
|
|
271
|
+
// common.c:237-252) does memset(s, 0, n + 1) with n = 32 on a
|
|
272
|
+
// char name[32] field — 1 byte spills into the int loop_start that
|
|
273
|
+
// follows it in struct temp_inst, zeroing its low byte. This is a
|
|
274
|
+
// real struct-overlap behavior in libxmp; replicate it so loop
|
|
275
|
+
// starts match byte-for-byte (march of wonders: 2882 → 2816).
|
|
276
|
+
// Note: C computes loop_end BEFORE this call, so loop_end keeps the
|
|
277
|
+
// unmasked value (lpe=7574 while lps=2816 in march of wonders).
|
|
278
|
+
t.loop_start &= ~0xff;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const pat = u16();
|
|
282
|
+
const len = u16();
|
|
283
|
+
if (pos > bytes.length)
|
|
284
|
+
throw new ParseError('MED4: truncated header');
|
|
285
|
+
if (pat > 256 || len > 255)
|
|
286
|
+
throw new ParseError('MED4: invalid pattern count or song length');
|
|
287
|
+
// Order list.
|
|
288
|
+
const xxo = [];
|
|
289
|
+
for (let i = 0; i < len; i++)
|
|
290
|
+
xxo.push(u8());
|
|
291
|
+
// From MED V3.00 docs: primary tempo 1-240; 1-10 are Tracker-compatible.
|
|
292
|
+
const tempo = u16();
|
|
293
|
+
const transp = s8();
|
|
294
|
+
const flags = s8();
|
|
295
|
+
let speed = u16();
|
|
296
|
+
const bpm = mmdConvertTempo(tempo, 0, 0);
|
|
297
|
+
const timeFactor = MED_TIME_FACTOR;
|
|
298
|
+
let quirks = Quirk.RTONCE; /* FF1 */
|
|
299
|
+
let hexvol = 0;
|
|
300
|
+
if ((~flags & 0x20) !== 0)
|
|
301
|
+
quirks |= Quirk.VSALL | Quirk.PBALL; // sliding
|
|
302
|
+
if ((flags & 0x10) !== 0)
|
|
303
|
+
hexvol = 1; // dec/hex volumes
|
|
304
|
+
// This is just a guess... (med4_load.c:645-648)
|
|
305
|
+
if (vermaj === 2)
|
|
306
|
+
speed = (flags & 0x20) !== 0 ? 5 : 6;
|
|
307
|
+
pos += 20;
|
|
308
|
+
const trkvol = [];
|
|
309
|
+
for (let i = 0; i < 16; i++)
|
|
310
|
+
trkvol.push(u8());
|
|
311
|
+
u8(); // master vol
|
|
312
|
+
// Play transpose rides in every instrument (med4_load.c:658-660).
|
|
313
|
+
for (let i = 0; i < 64; i++)
|
|
314
|
+
tempInst[i].transpose += transp;
|
|
315
|
+
// Scan patterns to determine number of channels (med4_load.c:661-683).
|
|
316
|
+
let chn = 0;
|
|
317
|
+
const scanPos = pos;
|
|
318
|
+
for (let i = 0; i < pat; i++) {
|
|
319
|
+
const size = u8(); // pattern control block
|
|
320
|
+
const c = u8();
|
|
321
|
+
if (c > chn)
|
|
322
|
+
chn = c;
|
|
323
|
+
u8(); // skip number of rows
|
|
324
|
+
const plen = u16();
|
|
325
|
+
skip(size + plen - 4);
|
|
326
|
+
}
|
|
327
|
+
// Sanity check.
|
|
328
|
+
if (chn > 16)
|
|
329
|
+
throw new ParseError('MED4: invalid channel count');
|
|
330
|
+
// Load and convert patterns (med4_load.c:692-825).
|
|
331
|
+
const patterns = [];
|
|
332
|
+
pos = scanPos;
|
|
333
|
+
for (let i = 0; i < pat; i++) {
|
|
334
|
+
const size = u8(); // pattern control block
|
|
335
|
+
// C captures `pos = hio_tell(f)` AFTER the size byte (med4_load.c:709):
|
|
336
|
+
// the block-relative offsets (events at pos+size, next block at
|
|
337
|
+
// pos+size+plen) are relative to this point.
|
|
338
|
+
const blockPos = pos;
|
|
339
|
+
const blockChn = u8();
|
|
340
|
+
if (blockChn > chn)
|
|
341
|
+
throw new ParseError(`MED4: channel count mismatch at pattern ${i}`);
|
|
342
|
+
const rows = u8() + 1;
|
|
343
|
+
const plen = u16();
|
|
344
|
+
// Read control byte (med4_load.c:725-734).
|
|
345
|
+
const ctl = [0, 0, 0, 0];
|
|
346
|
+
for (let j = 0; j < 4; j++) {
|
|
347
|
+
if (rows > j * 64)
|
|
348
|
+
ctl[j] = u8();
|
|
349
|
+
else
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
// Initialize masks (med4_load.c:739-763).
|
|
353
|
+
const linemask = [];
|
|
354
|
+
const fxmask = [];
|
|
355
|
+
let numMasks = 0;
|
|
356
|
+
for (let y = 0; y < 8; y++) {
|
|
357
|
+
linemask.push(0);
|
|
358
|
+
fxmask.push(0);
|
|
359
|
+
}
|
|
360
|
+
for (let y = 0; y < 8; y++) {
|
|
361
|
+
if (rows > y * 32) {
|
|
362
|
+
const c = ctl[Math.trunc(y / 2)];
|
|
363
|
+
const s = 4 * (y % 2);
|
|
364
|
+
linemask[y] = (c & (0x80 >> s)) !== 0 ? 0xffffffff
|
|
365
|
+
: (c & (0x40 >> s)) !== 0 ? 0 : u32();
|
|
366
|
+
fxmask[y] = (c & (0x20 >> s)) !== 0 ? 0xffffffff
|
|
367
|
+
: (c & (0x10 >> s)) !== 0 ? 0 : u32();
|
|
368
|
+
numMasks++;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const eventGrid = [];
|
|
375
|
+
for (let k = 0; k < chn; k++) {
|
|
376
|
+
const evs = [];
|
|
377
|
+
for (let j = 0; j < rows; j++)
|
|
378
|
+
evs.push({ note: 0, ins: 0, vol: 0, fxt: 0, fxp: 0, f2t: 0, f2p: 0 });
|
|
379
|
+
eventGrid.push(evs);
|
|
380
|
+
}
|
|
381
|
+
seek(blockPos + size);
|
|
382
|
+
const stream = new Stream(bytes, pos);
|
|
383
|
+
for (let y = 0; y < numMasks; y++) {
|
|
384
|
+
for (let j = 0; j < 32; j++) {
|
|
385
|
+
const line = y * 32 + j;
|
|
386
|
+
if (line >= rows)
|
|
387
|
+
break;
|
|
388
|
+
if ((linemask[y] & 0x80000000) !== 0) {
|
|
389
|
+
let chmsk = stream.readAligned16(blockChn);
|
|
390
|
+
for (let k = 0; k < blockChn; k++, chmsk = (chmsk << 1) & 0xffff) {
|
|
391
|
+
if ((chmsk & 0x8000) !== 0) {
|
|
392
|
+
const x = stream.read12();
|
|
393
|
+
const e = eventGrid[k][line];
|
|
394
|
+
e.note = x >> 4;
|
|
395
|
+
if (e.note)
|
|
396
|
+
e.note += 48;
|
|
397
|
+
e.ins = x & 0x0f;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if ((fxmask[y] & 0x80000000) !== 0) {
|
|
402
|
+
let chmsk = stream.readAligned16(blockChn);
|
|
403
|
+
for (let k = 0; k < blockChn; k++, chmsk = (chmsk << 1) & 0xffff) {
|
|
404
|
+
if ((chmsk & 0x8000) !== 0) {
|
|
405
|
+
const x = stream.read12();
|
|
406
|
+
const e = eventGrid[k][line];
|
|
407
|
+
e.fxt = x >> 8;
|
|
408
|
+
e.fxp = x & 0xff;
|
|
409
|
+
fixEffect(e, hexvol);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
linemask[y] = (linemask[y] << 1) >>> 0;
|
|
414
|
+
fxmask[y] = (fxmask[y] << 1) >>> 0;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
seek(blockPos + size + plen);
|
|
418
|
+
const tracksArr = [];
|
|
419
|
+
for (let k = 0; k < chn; k++)
|
|
420
|
+
tracksArr.push({ rows, event: eventGrid[k] });
|
|
421
|
+
patterns.push({ rows, tracks: tracksArr });
|
|
422
|
+
}
|
|
423
|
+
// Load samples (med4_load.c:827-948). Song files reference external
|
|
424
|
+
// instrument files (not supported in the browser preview — samples stay
|
|
425
|
+
// empty); module files carry internal sample/synth chunks inline.
|
|
426
|
+
const instruments = [];
|
|
427
|
+
for (let i = 0; i < numIns; i++)
|
|
428
|
+
instruments.push(makeInstrument(tempInst[i].name));
|
|
429
|
+
const samples = [];
|
|
430
|
+
let smpIdx = 0;
|
|
431
|
+
const sampleCheck = () => {
|
|
432
|
+
// Nothing to do: the JS port grows the samples array on demand.
|
|
433
|
+
};
|
|
434
|
+
/** med4_load_sampled_instrument (med4_load.c:190-247). */
|
|
435
|
+
const loadSampled = (i, length, at) => {
|
|
436
|
+
const xxi = instruments[i];
|
|
437
|
+
xxi.nsm = 1;
|
|
438
|
+
xxi.sub = [makeSub(tempInst[i].volume, smpIdx)];
|
|
439
|
+
const sub = xxi.sub[0];
|
|
440
|
+
sub.vol = tempInst[i].volume;
|
|
441
|
+
sub.xpo = tempInst[i].transpose;
|
|
442
|
+
sub.sid = smpIdx;
|
|
443
|
+
sampleCheck();
|
|
444
|
+
const s = {
|
|
445
|
+
name: '',
|
|
446
|
+
data: new Uint8Array(0),
|
|
447
|
+
length,
|
|
448
|
+
loopStart: tempInst[i].loop_start,
|
|
449
|
+
loopEnd: tempInst[i].loop_end,
|
|
450
|
+
sustainStart: 0,
|
|
451
|
+
sustainEnd: 0,
|
|
452
|
+
finetune: 0,
|
|
453
|
+
volume: sub.vol,
|
|
454
|
+
flags: tempInst[i].loop_end > 2 ? SampleFlags.LOOP : 0,
|
|
455
|
+
c5spd: 8287,
|
|
456
|
+
};
|
|
457
|
+
const is16 = false;
|
|
458
|
+
const bytelen = s.length * (is16 ? 2 : 1);
|
|
459
|
+
const take = Math.min(bytelen, Math.max(0, curBytes.length - at.pos));
|
|
460
|
+
s.data = curBytes.subarray(at.pos, at.pos + take);
|
|
461
|
+
at.pos += bytelen;
|
|
462
|
+
samples[smpIdx] = s;
|
|
463
|
+
// Limit range to 3 octaves (see MED.El toro) (med4_load.c:232-243).
|
|
464
|
+
for (let j = 0; j < 9; j++) {
|
|
465
|
+
for (let k = 0; k < 12; k++) {
|
|
466
|
+
let xpo = 0;
|
|
467
|
+
if (j < 4)
|
|
468
|
+
xpo = 12 * (4 - j);
|
|
469
|
+
else if (j > 6)
|
|
470
|
+
xpo = -12 * (j - 6);
|
|
471
|
+
xxi.mapXpo[12 * j + k] = xpo;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
smpIdx++;
|
|
475
|
+
};
|
|
476
|
+
/** med4_load_synth_instrument (med4_load.c:252-418). */
|
|
477
|
+
const loadSynth = (i, type, at) => {
|
|
478
|
+
const startPos = at.pos;
|
|
479
|
+
// MSH0 check.
|
|
480
|
+
if (startPos + 22 > curBytes.length)
|
|
481
|
+
throw new ParseError('MED4: truncated synth');
|
|
482
|
+
const magic = curDvL.getUint32(startPos, false);
|
|
483
|
+
if (magic !== 0x4d534800) { // 'MSH\0'
|
|
484
|
+
throw new ParseError(`MED4: invalid MSH0 at instrument ${i}`);
|
|
485
|
+
}
|
|
486
|
+
let p = startPos + 6; // skip magic + type2 (checked below)
|
|
487
|
+
const type2 = (curDvL.getInt16(startPos + 4, false));
|
|
488
|
+
if (type2 !== -1 && type2 !== -2) {
|
|
489
|
+
throw new ParseError(`MED4: invalid type ${type2} on synth/hybrid ${i}`);
|
|
490
|
+
}
|
|
491
|
+
void type;
|
|
492
|
+
p = startPos + 6;
|
|
493
|
+
p += 4; // ? - 0000 twice
|
|
494
|
+
const synthRep = curDvL.getUint16(p, false);
|
|
495
|
+
p += 2;
|
|
496
|
+
const synthReplen = curDvL.getUint16(p, false);
|
|
497
|
+
p += 2;
|
|
498
|
+
const voltbllen = curDvL.getUint16(p, false);
|
|
499
|
+
p += 2;
|
|
500
|
+
const wftbllen = curDvL.getUint16(p, false);
|
|
501
|
+
p += 2;
|
|
502
|
+
const volspeed = bytes[p];
|
|
503
|
+
p += 1;
|
|
504
|
+
const wfspeed = bytes[p];
|
|
505
|
+
p += 1;
|
|
506
|
+
const wforms = curDvL.getUint16(p, false);
|
|
507
|
+
p += 2;
|
|
508
|
+
void synthRep;
|
|
509
|
+
void synthReplen;
|
|
510
|
+
if (wforms === 0xffff)
|
|
511
|
+
return;
|
|
512
|
+
// Sanity check.
|
|
513
|
+
if (voltbllen > 128 || wftbllen > 128 || wforms > 64) {
|
|
514
|
+
throw new ParseError(`MED4: invalid synth tables at instrument ${i}`);
|
|
515
|
+
}
|
|
516
|
+
const voltblPos = p;
|
|
517
|
+
p += voltbllen;
|
|
518
|
+
const wftblPos = p;
|
|
519
|
+
p += wftbllen;
|
|
520
|
+
const wf = [];
|
|
521
|
+
for (let j = 0; j < wforms; j++) {
|
|
522
|
+
wf.push(curDvL.getUint32(p, false));
|
|
523
|
+
p += 4;
|
|
524
|
+
}
|
|
525
|
+
const xxi = instruments[i];
|
|
526
|
+
xxi.nsm = wforms;
|
|
527
|
+
xxi.sub = [];
|
|
528
|
+
for (let j = 0; j < wforms; j++)
|
|
529
|
+
xxi.sub.push(makeSub(0, 0));
|
|
530
|
+
xxi.extras = {
|
|
531
|
+
vts: volspeed, wts: wfspeed, vtlen: voltbllen, wtlen: wftbllen,
|
|
532
|
+
hold: 0, decay: 0, default_pitch: 0, finetune: 0,
|
|
533
|
+
suppress_midi_off: 0, long_repeat: 0, long_replen: 0,
|
|
534
|
+
volTable: curBytes.slice(voltblPos, voltblPos + voltbllen),
|
|
535
|
+
wavTable: curBytes.slice(wftblPos, wftblPos + wftbllen),
|
|
536
|
+
};
|
|
537
|
+
// MED_MODULE_EXTRAS(*m)->tracker_version = MED_VER_210;
|
|
538
|
+
sampleCheck();
|
|
539
|
+
let j = 0;
|
|
540
|
+
if (type === -2 && wforms > 0) { // Hybrid (med4_load.c:340-378)
|
|
541
|
+
const sPos = startPos + wf[0];
|
|
542
|
+
const length = curDvL.getUint32(sPos, false);
|
|
543
|
+
if (curDvL.getUint16(sPos + 4, false) !== 0) {
|
|
544
|
+
throw new ParseError(`MED4: hybrid ${i} has non-sample at pos 0`);
|
|
545
|
+
}
|
|
546
|
+
const sub = xxi.sub[0];
|
|
547
|
+
sub.vol = tempInst[i].volume;
|
|
548
|
+
sub.xpo = tempInst[i].transpose;
|
|
549
|
+
sub.sid = smpIdx;
|
|
550
|
+
const s = {
|
|
551
|
+
name: '',
|
|
552
|
+
data: new Uint8Array(0),
|
|
553
|
+
length,
|
|
554
|
+
loopStart: tempInst[i].loop_start,
|
|
555
|
+
loopEnd: tempInst[i].loop_end,
|
|
556
|
+
sustainStart: 0,
|
|
557
|
+
sustainEnd: 0,
|
|
558
|
+
finetune: 0,
|
|
559
|
+
volume: sub.vol,
|
|
560
|
+
flags: tempInst[i].loop_end > 2 ? SampleFlags.LOOP : 0,
|
|
561
|
+
c5spd: 8287,
|
|
562
|
+
};
|
|
563
|
+
const is16 = false;
|
|
564
|
+
const bytelen = s.length * (is16 ? 2 : 1);
|
|
565
|
+
const take = Math.min(bytelen, Math.max(0, curBytes.length - (sPos + 6)));
|
|
566
|
+
s.data = curBytes.subarray(sPos + 6, sPos + 6 + take);
|
|
567
|
+
samples[smpIdx] = s;
|
|
568
|
+
smpIdx++;
|
|
569
|
+
j++;
|
|
570
|
+
}
|
|
571
|
+
for (; j < wforms; j++) {
|
|
572
|
+
const sub = xxi.sub[j];
|
|
573
|
+
sub.vol = 64;
|
|
574
|
+
sub.xpo = -24;
|
|
575
|
+
sub.sid = smpIdx;
|
|
576
|
+
const wPos = startPos + wf[j];
|
|
577
|
+
const wlen = curDvL.getUint16(wPos, false) * 2;
|
|
578
|
+
const s = {
|
|
579
|
+
name: '',
|
|
580
|
+
data: new Uint8Array(0),
|
|
581
|
+
length: wlen,
|
|
582
|
+
loopStart: 0,
|
|
583
|
+
loopEnd: wlen,
|
|
584
|
+
sustainStart: 0,
|
|
585
|
+
sustainEnd: 0,
|
|
586
|
+
finetune: 0,
|
|
587
|
+
volume: sub.vol,
|
|
588
|
+
flags: SampleFlags.LOOP,
|
|
589
|
+
c5spd: 8287,
|
|
590
|
+
};
|
|
591
|
+
const take = Math.min(wlen, Math.max(0, curBytes.length - (wPos + 2)));
|
|
592
|
+
s.data = curBytes.subarray(wPos + 2, wPos + 2 + take);
|
|
593
|
+
samples[smpIdx] = s;
|
|
594
|
+
smpIdx++;
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
/** med4_load_instrument (med4_load.c:420-446). */
|
|
598
|
+
const loadInstrument = (i, length, type, at) => {
|
|
599
|
+
if (type === 0) { // Sampled
|
|
600
|
+
loadSampled(i, length, at);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (type === -1 || type === -2) { // Synthetic or Hybrid
|
|
604
|
+
const sPos = at.pos;
|
|
605
|
+
loadSynth(i, type, at);
|
|
606
|
+
at.pos = sPos + length;
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
// Skip unknown instrument type.
|
|
610
|
+
at.pos += length;
|
|
611
|
+
};
|
|
612
|
+
if ((~flags & FLAG_INSTRSATT) !== 0) {
|
|
613
|
+
// Song file (med4_load.c:835-864 → med4_load_external_instrument
|
|
614
|
+
// :448-495): each instrument's data lives in a sibling file named
|
|
615
|
+
// after the instrument, resolved via the harness's externalInstrument
|
|
616
|
+
// hook. A file starting with 'MSH\0' + type -1/-2 is an external
|
|
617
|
+
// synth/hybrid; anything else is a raw sample.
|
|
618
|
+
for (let i = 0; i < numIns; i++) {
|
|
619
|
+
instruments[i].name = tempInst[i].name;
|
|
620
|
+
instruments[i].nsm = 0;
|
|
621
|
+
// No placeholder sample: C sets mod->smp = mod->ins and then shrinks
|
|
622
|
+
// it to smp_idx (med4_load.c:839, 857-861) — instruments with no
|
|
623
|
+
// external file contribute NO sample (load_med4_invalid_iff.med).
|
|
624
|
+
}
|
|
625
|
+
// Load externals (med4_load.c:844-856 → med4_load_external_instrument
|
|
626
|
+
// :448-495): the C code opens each sibling file as a separate hio
|
|
627
|
+
// handle and runs the synth/sample loaders against it, growing the
|
|
628
|
+
// sample array on demand. We swap curBytes/curDvL to the external
|
|
629
|
+
// buffer for the duration of each load.
|
|
630
|
+
for (let i = 0; i < numIns; i++) {
|
|
631
|
+
const inst = instruments[i];
|
|
632
|
+
const data = ctx.externalInstrument ? ctx.externalInstrument(inst.name) : null;
|
|
633
|
+
if (!data || data.length === 0)
|
|
634
|
+
continue;
|
|
635
|
+
const extLen = data.length;
|
|
636
|
+
const edv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
637
|
+
const savedBytes = curBytes;
|
|
638
|
+
const savedDvL = curDvL;
|
|
639
|
+
curBytes = data;
|
|
640
|
+
curDvL = edv;
|
|
641
|
+
try {
|
|
642
|
+
if (extLen >= 6) {
|
|
643
|
+
const magic = edv.getUint32(0, false);
|
|
644
|
+
const type = edv.getInt16(4, false);
|
|
645
|
+
if (magic === 0x4d534800 && (type === -1 || type === -2)) { // 'MSH\0'
|
|
646
|
+
// External synth/hybrid: whole file is the synth chunk.
|
|
647
|
+
const at = { pos: 0 };
|
|
648
|
+
loadSynth(i, type, at);
|
|
649
|
+
curBytes = savedBytes;
|
|
650
|
+
curDvL = savedDvL;
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Raw sampled external: whole file is the sample data
|
|
655
|
+
// (med4_load_sampled_instrument with length = hio_size).
|
|
656
|
+
loadSampled(i, extLen, { pos: 0 });
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
curBytes = savedBytes;
|
|
660
|
+
curDvL = savedDvL;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
// Sanity check (med4_load.c:619): pat > 256 or len > XMP_MAX_MOD_LENGTH.
|
|
666
|
+
if (pat > 256 || len > 255)
|
|
667
|
+
throw new ParseError('MED4: invalid pattern count or song length');
|
|
668
|
+
// Internal samples (med4_load.c:866-944).
|
|
669
|
+
const maskLo = u32();
|
|
670
|
+
if (maskLo === 0x4d454456) { // 'MEDV' — module with no samples
|
|
671
|
+
throw new ParseError('MED4: invalid module MED4 with no samples');
|
|
672
|
+
}
|
|
673
|
+
const maskHiLo = u32();
|
|
674
|
+
// 64-bit mask, shifted left by 1 (no instrument #0).
|
|
675
|
+
const fullMask = ((((BigInt(maskLo >>> 0) << 32n) | BigInt(maskHiLo >>> 0)) << 1n) & 0xffffffffffffffffn);
|
|
676
|
+
const posSave = pos;
|
|
677
|
+
// Faithful C count loop (med4_load.c:885-909): per entry read
|
|
678
|
+
// {u32 len, i16 type}; samples +1; synths seek +20, add u16 wforms and
|
|
679
|
+
// shrink len by 22; then seek len from the current position.
|
|
680
|
+
let numSmp = 0;
|
|
681
|
+
{
|
|
682
|
+
numSmp = 0;
|
|
683
|
+
let m = fullMask;
|
|
684
|
+
let p = posSave;
|
|
685
|
+
for (let i = 0; m !== 0n && i < 64; i++, m = (m << 1n) & 0xffffffffffffffffn) {
|
|
686
|
+
const asS64 = m >= (1n << 63n) ? m - (1n << 64n) : m;
|
|
687
|
+
if (asS64 > 0n)
|
|
688
|
+
continue;
|
|
689
|
+
// hio reads at EOF return 0/short and set an error; the C loop then
|
|
690
|
+
// walks zero-length entries. Our buffer has no such slack — stop
|
|
691
|
+
// when the 6-byte header no longer fits (matches C's net behavior
|
|
692
|
+
// of loading only the real entries).
|
|
693
|
+
if (p + 6 > bytes.length)
|
|
694
|
+
break;
|
|
695
|
+
let _len = dvL.getInt32(p, false);
|
|
696
|
+
const _type = dvL.getInt16(p + 4, false);
|
|
697
|
+
p += 6;
|
|
698
|
+
if (_type === 0) {
|
|
699
|
+
numSmp++;
|
|
700
|
+
}
|
|
701
|
+
else if (_type === -1 || _type === -2) {
|
|
702
|
+
if (_len < 22) {
|
|
703
|
+
throw new ParseError(`MED4: invalid synth ${i} length`);
|
|
704
|
+
}
|
|
705
|
+
p += 20;
|
|
706
|
+
numSmp += dvL.getUint16(p, false);
|
|
707
|
+
p += 2;
|
|
708
|
+
_len -= 22;
|
|
709
|
+
}
|
|
710
|
+
if (_len < 0 || _len > bytes.length) {
|
|
711
|
+
throw new ParseError(`MED4: invalid sample ${i} length`);
|
|
712
|
+
}
|
|
713
|
+
p += _len;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
pos = posSave;
|
|
717
|
+
let m = fullMask;
|
|
718
|
+
const at = { pos };
|
|
719
|
+
for (let i = 0; m !== 0n && i < numIns; i++, m = (m << 1n) & 0xffffffffffffffffn) {
|
|
720
|
+
const asS64 = m >= (1n << 63n) ? m - (1n << 64n) : m;
|
|
721
|
+
if (asS64 > 0n)
|
|
722
|
+
continue;
|
|
723
|
+
const length = dvL.getInt32(at.pos, false);
|
|
724
|
+
const type = dvL.getInt16(at.pos + 4, false);
|
|
725
|
+
at.pos += 6;
|
|
726
|
+
loadInstrument(i, length, type, at);
|
|
727
|
+
}
|
|
728
|
+
pos = at.pos;
|
|
729
|
+
}
|
|
730
|
+
for (const raw of samples) {
|
|
731
|
+
if (raw)
|
|
732
|
+
ctx.addSample(raw);
|
|
733
|
+
}
|
|
734
|
+
// IFF-like section (med4_load.c:950-987).
|
|
735
|
+
{
|
|
736
|
+
let p = pos;
|
|
737
|
+
for (;;) {
|
|
738
|
+
if (p + 8 > bytes.length)
|
|
739
|
+
break;
|
|
740
|
+
const id = dvL.getInt32(p, false);
|
|
741
|
+
const size = dvL.getInt32(p + 4, false);
|
|
742
|
+
if (id <= 0 || size <= 0)
|
|
743
|
+
break;
|
|
744
|
+
p += 8;
|
|
745
|
+
if (id === 0x4d454456) { // 'MEDV'
|
|
746
|
+
const ver = dvL.getUint32(p, false);
|
|
747
|
+
vermaj = (ver & 0xff00) >> 8;
|
|
748
|
+
vermin = ver & 0xff;
|
|
749
|
+
}
|
|
750
|
+
else if (id === 0x414e4e4f) { // 'ANNO'
|
|
751
|
+
// Annotation: raw bytes → 1:1 charcodes via latin-1 mapping. Using
|
|
752
|
+
// Buffer-style latin-1 decode keeps each byte as one U+00xx char so
|
|
753
|
+
// the dump round-trips byte-for-byte like C's raw buffer.
|
|
754
|
+
const readLen = Math.min(size, 1023);
|
|
755
|
+
let c = '';
|
|
756
|
+
for (let i = 0; i < readLen && p + i < bytes.length; i++) {
|
|
757
|
+
const ch = bytes[p + i];
|
|
758
|
+
if (ch === 0)
|
|
759
|
+
break;
|
|
760
|
+
c += String.fromCharCode(ch); // latin-1: byte === code point
|
|
761
|
+
}
|
|
762
|
+
commentText = c;
|
|
763
|
+
}
|
|
764
|
+
// 'HLD C' hold & decay: skipped.
|
|
765
|
+
p += size;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const channels = [];
|
|
769
|
+
for (let i = 0; i < chn; i++) {
|
|
770
|
+
// med4_load never touches channel pan — the prologue LRLR default
|
|
771
|
+
// (load_helpers.c:334) applies. C integer division: (i+1)/2 truncates.
|
|
772
|
+
const pattern = Math.floor((i + 1) / 2) % 2 * 0xff;
|
|
773
|
+
channels.push({ pan: Math.min(255, Math.max(0, 0x80 + (pattern - 0x80))), vol: trkvol[i] ?? 0x40, flg: 0 });
|
|
774
|
+
}
|
|
775
|
+
const mod = {
|
|
776
|
+
title: '',
|
|
777
|
+
format: 'med',
|
|
778
|
+
comment: commentText,
|
|
779
|
+
chn,
|
|
780
|
+
pat,
|
|
781
|
+
ins: numIns,
|
|
782
|
+
len,
|
|
783
|
+
restart: 0,
|
|
784
|
+
xxo,
|
|
785
|
+
channels,
|
|
786
|
+
patterns,
|
|
787
|
+
instruments,
|
|
788
|
+
samples: samples.map((s) => s ?? { name: '', data: new Uint8Array(0), length: 0, loopStart: 0, loopEnd: 0, sustainStart: 0, sustainEnd: 0, finetune: 0, volume: 0, flags: 0, c5spd: 8363 }),
|
|
789
|
+
num_sequences: 0,
|
|
790
|
+
sequences: [],
|
|
791
|
+
speed,
|
|
792
|
+
bpm,
|
|
793
|
+
volbase: 0x40,
|
|
794
|
+
gvolbase: 0x40,
|
|
795
|
+
gvol: 0x40,
|
|
796
|
+
quirks,
|
|
797
|
+
flowMode: 0,
|
|
798
|
+
readEventType: ReadEventType.MED,
|
|
799
|
+
periodType: 0,
|
|
800
|
+
defpan: 0x80,
|
|
801
|
+
time_factor: timeFactor,
|
|
802
|
+
rrate: 250,
|
|
803
|
+
c4rate: 8287,
|
|
804
|
+
compare_vblank: false,
|
|
805
|
+
tracker: trackerType,
|
|
806
|
+
extras: { kind: 'med', trackerVersion: (vermaj << 8) | vermin },
|
|
807
|
+
};
|
|
808
|
+
void ctx.sampleRate;
|
|
809
|
+
void ctx.outputRate;
|
|
810
|
+
void MED_VER_210;
|
|
811
|
+
return mod;
|
|
812
|
+
}
|
|
813
|
+
//# sourceMappingURL=med4.js.map
|