@modplayjs/fmt-med 0.0.0-stage → 0.2.2

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 CHANGED
@@ -1,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # @modplayjs/fmt-med
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **MED / OctaMED (.med) format plugin** for `@modplayjs/core`, ported 1:1
4
+ from libxmp's MED loader family:
5
+
6
+ - MMD0/MMD1 (`mmd1_load.c`) — MED/OctaMED tracker modes
7
+ - MMD2/MMD3 (`mmd3_load.c`) — OctaMED Soundstudio
8
+ - MMDCommon (`mmd_common.c`) — shared instrument/sample decoding incl.
9
+ the MED synth/hybrid instruments (MED V1–V5, IFFOCT/IFF5OCT wavelists)
10
+ - MED2/MED3/MED4 (`.med` 5/6/8-channel song variants, `med2/3/4_load.c`)
11
+
12
+ Playback includes the MED synth extras (arpeggios, wave/PSS commands,
13
+ hold/decay) through the shared extras engine.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ npm install @modplayjs/fmt-med
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { CorePlayer } from '@modplayjs/core';
25
+ import { plugin as medPlugin } from '@modplayjs/fmt-med';
26
+
27
+ const core = new CorePlayer();
28
+ core.registries.registerFormat(medPlugin);
29
+ core.loadModule(bytes); // .med / .mmd0-.mmd3 / .med2-.med4
30
+ ```
31
+
32
+ ## Also exports
33
+
34
+ Plugins: `plugin` (MMD0/MMD1), `mmd3Plugin`, `med2Plugin`, `med3Plugin`,
35
+ `med4Plugin` — register each format you want to accept. Direct loaders:
36
+ `mmd1Test/Load`, `mmd3Test/Load`, `med2Test/Load`, `med3Test/Load`,
37
+ `med4Test/Load`, `mmdReadTitle`, plus `readEventMed` for the shared MED
38
+ event reader.
39
+
40
+ Part of the [modplayjs monorepo](../../README.md). BSD-3-Clause.
@@ -0,0 +1,19 @@
1
+ import type { FormatPlugin } from '@modplayjs/core';
2
+ export { mmdConvertTempo, mmdXlatFx, mmdSetBpm, mmdTrackerVersion, MED_TIME_FACTOR, FLAG_INSTRSATT, type MedExpData, type MedInstrExt, type MedSample, } from './mmdCommon.js';
3
+ export { mmd1Test, mmd1Load, mmdReadTitle } from './mmd1.js';
4
+ export { mmd3Test, mmd3Load } from './mmd3.js';
5
+ export { med2Test, med2Load } from './med2.js';
6
+ export { med3Test, med3Load } from './med3.js';
7
+ export { med4Test, med4Load } from './med4.js';
8
+ export { readEventMed } from './readevent.js';
9
+ /** MMD0/MMD1/MMDC (MED 2.10 / OctaMED). */
10
+ export declare const plugin: FormatPlugin;
11
+ /** MMD2/MMD3 (OctaMED / OctaMED Soundstudio). */
12
+ export declare const mmd3Plugin: FormatPlugin;
13
+ /** MED 1.12 MED2. */
14
+ export declare const med2Plugin: FormatPlugin;
15
+ /** MED 2.00 MED3. */
16
+ export declare const med3Plugin: FormatPlugin;
17
+ /** MED 2.10 MED4. */
18
+ export declare const med4Plugin: FormatPlugin;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAQ,YAAY,EAAuB,MAAM,iBAAiB,CAAC;AAQ/E,OAAO,EACL,eAAe,EACf,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAoB9C,2CAA2C;AAC3C,eAAO,MAAM,MAAM,cAAwC,CAAC;AAC5D,iDAAiD;AACjD,eAAO,MAAM,UAAU,cAAyC,CAAC;AACjE,qBAAqB;AACrB,eAAO,MAAM,UAAU,cAAyC,CAAC;AACjE,qBAAqB;AACrB,eAAO,MAAM,UAAU,cAAyC,CAAC;AACjE,qBAAqB;AACrB,eAAO,MAAM,UAAU,cAAyC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause
2
+ // Copyright (c) 2026 Bitti09 — modplayjs contributors
3
+ // Project-original code.
4
+ // @modplayjs/fmt-med — MED/OctaMED loaders (MMD0/1/C, MMD2/3, MED2/3/4)
5
+ // + event reader. Port of reference/libxmp src/loaders/mmd_common.c,
6
+ // mmd1_load.c, mmd3_load.c, med2_load.c, med3_load.c, med4_load.c.
7
+ import { mmd1Test, mmd1Load } from './mmd1.js';
8
+ import { mmd3Test, mmd3Load } from './mmd3.js';
9
+ import { med2Test, med2Load } from './med2.js';
10
+ import { med3Test, med3Load } from './med3.js';
11
+ import { med4Test, med4Load } from './med4.js';
12
+ import { readEventMed } from './readevent.js';
13
+ export { mmdConvertTempo, mmdXlatFx, mmdSetBpm, mmdTrackerVersion, MED_TIME_FACTOR, FLAG_INSTRSATT, } from './mmdCommon.js';
14
+ export { mmd1Test, mmd1Load, mmdReadTitle } from './mmd1.js';
15
+ export { mmd3Test, mmd3Load } from './mmd3.js';
16
+ export { med2Test, med2Load } from './med2.js';
17
+ export { med3Test, med3Load } from './med3.js';
18
+ export { med4Test, med4Load } from './med4.js';
19
+ export { readEventMed } from './readevent.js';
20
+ function makePlugin(name, test, load) {
21
+ return {
22
+ name,
23
+ test,
24
+ load,
25
+ readEvent(core, chn, row) {
26
+ const mod = core.module;
27
+ const e = core.readEventScratch(chn) ?? core.readEventAt(mod.xxo[core.ctx.p.ord] ?? 0, chn, row);
28
+ readEventMed(core, e, chn);
29
+ },
30
+ };
31
+ }
32
+ /** MMD0/MMD1/MMDC (MED 2.10 / OctaMED). */
33
+ export const plugin = makePlugin('med', mmd1Test, mmd1Load);
34
+ /** MMD2/MMD3 (OctaMED / OctaMED Soundstudio). */
35
+ export const mmd3Plugin = makePlugin('mmd3', mmd3Test, mmd3Load);
36
+ /** MED 1.12 MED2. */
37
+ export const med2Plugin = makePlugin('med2', med2Test, med2Load);
38
+ /** MED 2.00 MED3. */
39
+ export const med3Plugin = makePlugin('med3', med3Test, med3Load);
40
+ /** MED 2.10 MED4. */
41
+ export const med4Plugin = makePlugin('med4', med4Test, med4Load);
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,sDAAsD;AACtD,yBAAyB;AACzB,wEAAwE;AACxE,qEAAqE;AACrE,mEAAmE;AAGnE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,eAAe,EACf,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,GAIf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,SAAS,UAAU,CACjB,IAAY,EACZ,IAAoC,EACpC,IAAqD;IAErD,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,SAAS,CAAC,IAAU,EAAE,GAAW,EAAE,GAAW;YAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAoB,CAAC;YACtC,MAAM,CAAC,GACL,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACzF,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,2CAA2C;AAC3C,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC5D,iDAAiD;AACjD,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACjE,qBAAqB;AACrB,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACjE,qBAAqB;AACrB,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACjE,qBAAqB;AACrB,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { Instrument, RawSample } from '@modplayjs/core';
2
+ import { type MedExpData, type MedInstrExt, type MedSample } from './mmdCommon.js';
3
+ /** InstrHdr view (med.h:159-166). */
4
+ export interface InstrHdr {
5
+ length: number;
6
+ type: number;
7
+ }
8
+ /** Everything the instrument loaders need from the calling loader. */
9
+ export interface InstrumentLoadState {
10
+ bytes: Uint8Array;
11
+ start: number;
12
+ /** Current byte cursor (advances as samples are consumed). */
13
+ pos: number;
14
+ ver: number;
15
+ samples: RawSample[];
16
+ instruments: Instrument[];
17
+ expdata: MedExpData;
18
+ expSmp: MedInstrExt[];
19
+ song: MedSample[];
20
+ }
21
+ /** SynthInstr (med.h:175-190) + the absolute file positions of the tables. */
22
+ export interface SynthInstr {
23
+ defaultdecay: number;
24
+ rep: number;
25
+ replen: number;
26
+ voltbllen: number;
27
+ wftbllen: number;
28
+ volspeed: number;
29
+ wfspeed: number;
30
+ wforms: number;
31
+ wf: number[];
32
+ voltblPos?: number;
33
+ wftblPos?: number;
34
+ }
35
+ export declare function emptySynth(): SynthInstr;
36
+ /** Big-endian cursor (shared with mmd1's Reader; declared here so the
37
+ * instrument loader can seek the raw bytes). */
38
+ export declare class Reader {
39
+ readonly bytes: Uint8Array;
40
+ pos: number;
41
+ constructor(bytes: Uint8Array, start: number);
42
+ get length(): number;
43
+ u8(): number;
44
+ s8(): number;
45
+ u16(): number;
46
+ s16(): number;
47
+ u32(): number;
48
+ raw(n: number): Uint8Array;
49
+ seek(p: number): void;
50
+ skip(n: number): void;
51
+ eof(): boolean;
52
+ }
53
+ /** mmd_load_instrument_common (mmd_common.c:413-482). */
54
+ export declare function instrumentInfo(st: InstrumentLoadState, instr: InstrHdr | null, e: MedInstrExt, sample: MedSample): {
55
+ length: number;
56
+ rep: number;
57
+ replen: number;
58
+ sampletrans: number;
59
+ synthtrans: number;
60
+ flg: number;
61
+ enable: number;
62
+ };
63
+ /** libxmp_load_sample equivalent over the byte buffer. */
64
+ export declare function loadSampleInto(st: InstrumentLoadState & Partial<{
65
+ r: Reader;
66
+ }>, smpIdx: number, bigend: number): void;
67
+ /** mmd_load_instrument (mmd_common.c:912-990). Returns the next smp_idx. */
68
+ export declare function mmdLoadInstrument(st: InstrumentLoadState, i: number, smpIdx: number): number;
69
+ //# sourceMappingURL=instr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instr.d.ts","sourceRoot":"","sources":["../src/instr.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,UAAU,EAEV,SAAS,EACV,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAExB,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,UAAU,IAAI,UAAU,CAKvC;AAED;iDACiD;AACjD,qBAAa,MAAM;IAEL,QAAQ,CAAC,KAAK,EAAE,UAAU;IADtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAqB,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAuB;IAC5E,IAAI,MAAM,IAAI,MAAM,CAA8B;IAClD,EAAE,IAAI,MAAM,CAAwC;IACpD,EAAE,IAAI,MAAM,CAAoC;IAChD,GAAG,IAAI,MAAM,CAIZ;IACD,GAAG,IAAI,MAAM,CAAqC;IAClD,GAAG,IAAI,MAAM,CAMZ;IACD,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAIzB;IACD,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAkB;IACvC,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAmB;IACxC,GAAG,IAAI,OAAO,CAA0C;CACzD;AASD,yDAAyD;AACzD,wBAAgB,cAAc,CAC5B,EAAE,EAAE,mBAAmB,EACvB,KAAK,EAAE,QAAQ,GAAG,IAAI,EACtB,CAAC,EAAE,WAAW,EACd,MAAM,EAAE,SAAS,GAChB;IACD,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CACtE,CAiDA;AA+BD,0DAA0D;AAC1D,wBAAgB,cAAc,CAC5B,EAAE,EAAE,mBAAmB,GAAG,OAAO,CAAC;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAC/E,IAAI,CAcN;AA6UD,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,mBAAmB,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GACjD,MAAM,CAgCR"}
package/dist/instr.js ADDED
@@ -0,0 +1,453 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause
2
+ // Copyright (c) 2026 Bitti09 — modplayjs contributors
3
+ // Ported from: libxmp src/loaders/mmd_common.c (mmd_load_instrument and the
4
+ // per-type loaders, :400-990). Shared by the MMD0/1 and MMD2/3 loaders.
5
+ import { C4_NTSC_RATE, ParseError, SampleFlags, } from '@modplayjs/core';
6
+ import { MMD_NUM_OCT, S_16, STEREO, } from './mmdCommon.js';
7
+ export function emptySynth() {
8
+ return {
9
+ defaultdecay: 0, rep: 0, replen: 0, voltbllen: 0, wftbllen: 0,
10
+ volspeed: 0, wfspeed: 0, wforms: 0, wf: [],
11
+ };
12
+ }
13
+ /** Big-endian cursor (shared with mmd1's Reader; declared here so the
14
+ * instrument loader can seek the raw bytes). */
15
+ export class Reader {
16
+ bytes;
17
+ pos;
18
+ constructor(bytes, start) {
19
+ this.bytes = bytes;
20
+ this.pos = start;
21
+ }
22
+ get length() { return this.bytes.length; }
23
+ u8() { return this.bytes[this.pos++] ?? 0; }
24
+ s8() { return (this.u8() << 24) >> 24; }
25
+ u16() {
26
+ const v = (this.bytes[this.pos] << 8) | this.bytes[this.pos + 1];
27
+ this.pos += 2;
28
+ return v;
29
+ }
30
+ s16() { return (this.u16() << 16) >> 16; }
31
+ u32() {
32
+ const v = ((this.bytes[this.pos] << 24) | (this.bytes[this.pos + 1] << 16) |
33
+ (this.bytes[this.pos + 2] << 8) | this.bytes[this.pos + 3]) >>> 0;
34
+ this.pos += 4;
35
+ return v;
36
+ }
37
+ raw(n) {
38
+ const s = this.bytes.subarray(this.pos, this.pos + n);
39
+ this.pos += n;
40
+ return s;
41
+ }
42
+ seek(p) { this.pos = p; }
43
+ skip(n) { this.pos += n; }
44
+ eof() { return this.pos >= this.bytes.length; }
45
+ }
46
+ function makeSub() {
47
+ return {
48
+ vol: 0, gvl: 0x40, pan: -1, xpo: 0, fin: 0, vwf: 0, vde: 0,
49
+ vra: 0, vsw: 0, sid: 0, rvv: 0, nna: 0, dct: 0, dca: 0, ifc: 0, ifr: 0,
50
+ };
51
+ }
52
+ /** mmd_load_instrument_common (mmd_common.c:413-482). */
53
+ export function instrumentInfo(st, instr, e, sample) {
54
+ const ver = st.ver;
55
+ let flg = 0;
56
+ let enable = 1;
57
+ if (ver >= 2 && st.expdata.s_ext_entrsz >= 8) { // MMD2+ instrument flags
58
+ if (e.instr_flags & 0x01 /* SSFLG_LOOP */)
59
+ flg |= SampleFlags.LOOP;
60
+ if (e.instr_flags & 0x08 /* SSFLG_PINGPONG */)
61
+ flg |= SampleFlags.BIDIR;
62
+ if (e.instr_flags & 0x04 /* SSFLG_DISABLED */)
63
+ enable = 0;
64
+ }
65
+ else {
66
+ if (sample.replen > 1)
67
+ flg |= SampleFlags.LOOP;
68
+ }
69
+ let sampletrans = 36 + sample.strans;
70
+ const synthtrans = 12 + sample.strans;
71
+ let length = 0;
72
+ let rep = 0;
73
+ let replen = 0;
74
+ if (instr) {
75
+ const sampleType = instr.type & ~(S_16 | 0x18 /* MD16 */ | STEREO);
76
+ if ((ver >= 3 && sampleType === 0) || sampleType === 7) {
77
+ // Mix mode transposes samples down two octaves. ExtSamples (7)
78
+ // are transposed regardless.
79
+ sampletrans -= 24;
80
+ }
81
+ length = instr.length;
82
+ if (ver >= 3 && st.expdata.s_ext_entrsz >= 18) {
83
+ rep = e.long_repeat;
84
+ replen = e.long_replen;
85
+ }
86
+ else {
87
+ rep = sample.rep << 1;
88
+ replen = sample.replen << 1;
89
+ }
90
+ if (instr.type & S_16) {
91
+ flg |= SampleFlags.BITS16;
92
+ // Length is (bytes / channels) but the loop is measured in frames.
93
+ length >>= 1;
94
+ }
95
+ if (instr.type & STEREO) {
96
+ flg |= SampleFlags.STEREO;
97
+ }
98
+ }
99
+ return { length, rep, replen, sampletrans, synthtrans, flg, enable };
100
+ }
101
+ /** mmd_set_default_pitch_note (mmd_common.c:492-504). */
102
+ function mmdSetDefaultPitchNote(st, i, e, ver) {
103
+ if (ver >= 3) {
104
+ let note = 53; // MMD3_DEFAULT_NOTE
105
+ if (e.default_pitch)
106
+ note = e.default_pitch - 1;
107
+ if (note >= 0 && note < 121) {
108
+ st.instruments[i].mapXpo[12] = note;
109
+ }
110
+ }
111
+ }
112
+ /** mmd_alloc_tables (mmd_common.c:506-526). */
113
+ function mmdAllocTables(st, i, synth) {
114
+ const ie = st.instruments[i].extras;
115
+ if (!ie)
116
+ return;
117
+ ie.volTable = st.bytes.slice(st.start + (synth.voltblPos ?? 0), st.start + (synth.voltblPos ?? 0) + synth.voltbllen);
118
+ ie.wavTable = st.bytes.slice(st.start + (synth.wftblPos ?? 0), st.start + (synth.wftblPos ?? 0) + synth.wftbllen);
119
+ }
120
+ /** libxmp_load_sample equivalent over the byte buffer. */
121
+ export function loadSampleInto(st, smpIdx, bigend) {
122
+ const s = st.samples[smpIdx];
123
+ const is16 = (s.flags & SampleFlags.BITS16) !== 0;
124
+ const stereo = (s.flags & SampleFlags.STEREO) !== 0;
125
+ const bytelen = s.length * (is16 ? 2 : 1) * (stereo ? 2 : 1);
126
+ // The C loader reads through the SAME hio handle the struct header came
127
+ // from. Keep the reader cursor (r) authoritative when present so the
128
+ // instrument header reads and the sample data share one position.
129
+ const cur = 'r' in st && st.r ? st.r : st;
130
+ const take = Math.min(bytelen, Math.max(0, st.bytes.length - cur.pos));
131
+ s.data = st.bytes.subarray(cur.pos, cur.pos + take);
132
+ if (bigend)
133
+ s.flags |= 1 << 11; // DecodeFlag.BIGEND
134
+ cur.pos += bytelen; // hio_read advances by requested count
135
+ if (cur !== st)
136
+ st.pos = cur.pos;
137
+ }
138
+ /** Read a SynthInstr body (mmd_common.c:548-558 / 663-673). */
139
+ function readSynthInstr(r, pos) {
140
+ r.seek(pos);
141
+ const synth = emptySynth();
142
+ synth.defaultdecay = r.u8();
143
+ r.skip(3);
144
+ synth.rep = r.u16();
145
+ synth.replen = r.u16();
146
+ synth.voltbllen = r.u16();
147
+ synth.wftbllen = r.u16();
148
+ synth.volspeed = r.u8();
149
+ synth.wfspeed = r.u8();
150
+ synth.wforms = r.u16();
151
+ synth.voltblPos = r.pos;
152
+ r.skip(synth.voltbllen);
153
+ synth.wftblPos = r.pos;
154
+ r.skip(synth.wftbllen);
155
+ for (let j = 0; j < synth.wforms; j++)
156
+ synth.wf.push(r.u32());
157
+ return synth;
158
+ }
159
+ function synthExtras(synth) {
160
+ return {
161
+ vts: synth.volspeed, wts: synth.wfspeed,
162
+ vtlen: synth.voltbllen, wtlen: synth.wftbllen,
163
+ hold: 0, decay: 0, default_pitch: 0, finetune: 0,
164
+ suppress_midi_off: 0, long_repeat: 0, long_replen: 0,
165
+ volTable: null, wavTable: null,
166
+ };
167
+ }
168
+ function holdDecayExtras(e) {
169
+ return {
170
+ vts: 0, wts: 0, vtlen: 0, wtlen: 0,
171
+ hold: e.hold, decay: e.decay, default_pitch: e.default_pitch,
172
+ finetune: e.finetune, suppress_midi_off: e.suppress_midi_off,
173
+ long_repeat: 0, long_replen: 0,
174
+ volTable: null, wavTable: null,
175
+ };
176
+ }
177
+ /** mmd_load_hybrid_instrument (mmd_common.c:528-646). */
178
+ function loadHybridInstrument(st, i, smpIdx0, e, sample) {
179
+ const r = st.r;
180
+ let smpIdx = smpIdx0;
181
+ const pos = r.pos;
182
+ const synth = readSynthInstr(r, pos);
183
+ if (synth.voltbllen > 128 || synth.wftbllen > 128 ||
184
+ synth.wforms < 1 || synth.wforms > 64) {
185
+ throw new ParseError(`MED: invalid synth tables at instrument ${i}`);
186
+ }
187
+ // Sample header at wf[0]; hybrids don't carry IFFOCT/ext samples.
188
+ r.seek(pos - 6 + synth.wf[0]);
189
+ const length = r.u32();
190
+ const type = r.s16();
191
+ if (type !== 0) {
192
+ throw new ParseError(`MED: unsupported sample type ${type} for hybrid`);
193
+ }
194
+ const xxi = st.instruments[i];
195
+ xxi.nsm = synth.wforms;
196
+ xxi.sub = [];
197
+ for (let j = 0; j < synth.wforms; j++)
198
+ xxi.sub.push(makeSub());
199
+ const ie = synthExtras(synth);
200
+ xxi.extras = ie;
201
+ const info = instrumentInfo(st, { length, type }, e, sample);
202
+ mmdSetDefaultPitchNote(st, i, e, st.ver);
203
+ const fin = (((e.finetune << 4) & 0xff) << 24) >> 24;
204
+ // sub 0 = the sample.
205
+ {
206
+ const sub = xxi.sub[0];
207
+ sub.vol = info.enable ? sample.svol : 0;
208
+ sub.xpo = info.sampletrans;
209
+ sub.sid = smpIdx;
210
+ sub.fin = fin;
211
+ st.samples[smpIdx] = {
212
+ name: '',
213
+ data: new Uint8Array(0),
214
+ length: info.length,
215
+ loopStart: info.rep,
216
+ loopEnd: info.rep + info.replen,
217
+ sustainStart: 0,
218
+ sustainEnd: 0,
219
+ finetune: fin,
220
+ volume: sub.vol,
221
+ flags: info.flg,
222
+ c5spd: C4_NTSC_RATE,
223
+ };
224
+ loadSampleInto(st, smpIdx, 0);
225
+ smpIdx++;
226
+ }
227
+ for (let j = 1; j < synth.wforms; j++) {
228
+ const sub = xxi.sub[j];
229
+ sub.vol = info.enable ? 64 : 0;
230
+ sub.xpo = info.synthtrans;
231
+ sub.sid = smpIdx;
232
+ sub.fin = fin;
233
+ r.seek(pos - 6 + synth.wf[j]);
234
+ const len = r.u16() * 2;
235
+ st.samples[smpIdx] = {
236
+ name: '',
237
+ data: new Uint8Array(0),
238
+ length: len,
239
+ loopStart: 0,
240
+ loopEnd: len,
241
+ sustainStart: 0,
242
+ sustainEnd: 0,
243
+ finetune: fin,
244
+ volume: sub.vol,
245
+ flags: SampleFlags.LOOP,
246
+ c5spd: C4_NTSC_RATE,
247
+ };
248
+ loadSampleInto(st, smpIdx, 0);
249
+ smpIdx++;
250
+ }
251
+ mmdAllocTables(st, i, synth);
252
+ return smpIdx;
253
+ }
254
+ /** mmd_load_synth_instrument (mmd_common.c:648-739). Returns smpIdx
255
+ * unchanged when wforms == 0xffff (empty synth). */
256
+ function loadSynthInstrument(st, i, smpIdx, e, sample) {
257
+ const r = st.r;
258
+ const pos = r.pos;
259
+ const info = instrumentInfo(st, null, e, sample);
260
+ mmdSetDefaultPitchNote(st, i, e, st.ver);
261
+ const synth = readSynthInstr(r, pos);
262
+ if (synth.wforms === 0xffff) {
263
+ st.instruments[i].nsm = 0;
264
+ return { smpIdx, wforms: 0 };
265
+ }
266
+ if (synth.voltbllen > 128 || synth.wftbllen > 128 || synth.wforms > 64) {
267
+ throw new ParseError(`MED: invalid synth tables at instrument ${i}`);
268
+ }
269
+ const xxi = st.instruments[i];
270
+ xxi.nsm = synth.wforms;
271
+ xxi.sub = [];
272
+ for (let j = 0; j < synth.wforms; j++)
273
+ xxi.sub.push(makeSub());
274
+ const ie = synthExtras(synth);
275
+ xxi.extras = ie;
276
+ const fin = (((e.finetune << 4) & 0xff) << 24) >> 24;
277
+ for (let j = 0; j < synth.wforms; j++) {
278
+ const sub = xxi.sub[j];
279
+ sub.vol = info.enable ? 64 : 0;
280
+ sub.xpo = info.synthtrans;
281
+ sub.sid = smpIdx;
282
+ sub.fin = fin;
283
+ r.seek(pos - 6 + synth.wf[j]);
284
+ const len = r.u16() * 2;
285
+ st.samples[smpIdx] = {
286
+ name: '',
287
+ data: new Uint8Array(0),
288
+ length: len,
289
+ loopStart: 0,
290
+ loopEnd: len,
291
+ sustainStart: 0,
292
+ sustainEnd: 0,
293
+ finetune: fin,
294
+ volume: sub.vol,
295
+ flags: SampleFlags.LOOP,
296
+ c5spd: C4_NTSC_RATE,
297
+ };
298
+ loadSampleInto(st, smpIdx, 0);
299
+ smpIdx++;
300
+ }
301
+ mmdAllocTables(st, i, synth);
302
+ return { smpIdx, wforms: synth.wforms };
303
+ }
304
+ /** iffoct_insmap / iffoct_xpomap (mmd_common.c:814-830). */
305
+ const IFFOCT_INSMAP = [
306
+ [1, 1, 1, 0, 0, 0, 0, 0, 0],
307
+ [2, 2, 2, 2, 2, 2, 1, 1, 0],
308
+ [3, 3, 3, 2, 2, 2, 1, 1, 0],
309
+ [4, 4, 4, 3, 2, 2, 1, 1, 0],
310
+ [5, 5, 5, 5, 4, 3, 2, 1, 0],
311
+ [6, 6, 6, 6, 5, 4, 3, 2, 1],
312
+ ];
313
+ const IFFOCT_XPOMAP = [
314
+ [12, 12, 12, 0, 0, 0, 0, 0, 0],
315
+ [12, 12, 12, 12, 12, 12, 0, 0, -12],
316
+ [12, 12, 12, 0, 0, 0, -12, -12, -24],
317
+ [24, 24, 24, 12, 0, 0, -12, -24, -36],
318
+ [12, 12, 12, 12, 0, -12, -24, -36, -48],
319
+ [12, 12, 12, 12, 0, -12, -24, -36, -48],
320
+ ];
321
+ /** mmd_load_iffoct_instrument (mmd_common.c:832-907). */
322
+ function loadIffoctInstrument(st, i, smpIdx0, instr, numOct, e, sample) {
323
+ let smpIdx = smpIdx0;
324
+ if (numOct < 2 || numOct > 7) {
325
+ throw new ParseError(`MED: invalid octave count at instrument ${i}`);
326
+ }
327
+ if ((instr.length | 0) < 0) {
328
+ throw new ParseError(`MED: absurd IFFOCT instrument ${i}`);
329
+ }
330
+ const xxi = st.instruments[i];
331
+ xxi.nsm = numOct;
332
+ xxi.sub = [];
333
+ for (let j = 0; j < numOct; j++)
334
+ xxi.sub.push(makeSub());
335
+ xxi.extras = holdDecayExtras(e);
336
+ // Base octave size.
337
+ let size = Math.trunc(instr.length / ((1 << numOct) - 1));
338
+ const info = instrumentInfo(st, instr, e, sample);
339
+ const fin = (((e.finetune << 4) & 0xff) << 24) >> 24;
340
+ for (let j = 0; j < numOct; j++) {
341
+ const sub = xxi.sub[j];
342
+ sub.vol = info.enable ? sample.svol : 0;
343
+ sub.xpo = info.sampletrans - 12;
344
+ sub.sid = smpIdx;
345
+ sub.fin = fin;
346
+ st.samples[smpIdx] = {
347
+ name: '',
348
+ data: new Uint8Array(0),
349
+ length: size,
350
+ loopStart: info.rep,
351
+ loopEnd: info.rep + info.replen,
352
+ sustainStart: 0,
353
+ sustainEnd: 0,
354
+ finetune: fin,
355
+ volume: sub.vol,
356
+ flags: info.flg,
357
+ c5spd: C4_NTSC_RATE,
358
+ };
359
+ loadSampleInto(st, smpIdx, 1); // SAMPLE_FLAG_BIGEND
360
+ smpIdx++;
361
+ size <<= 1;
362
+ info.rep <<= 1;
363
+ info.replen <<= 1;
364
+ }
365
+ // Instrument mapping.
366
+ for (let j = 0; j < 9; j++) {
367
+ for (let k = 0; k < 12; k++) {
368
+ xxi.map[12 * j + k] = IFFOCT_INSMAP[numOct - 2][j];
369
+ xxi.mapXpo[12 * j + k] = IFFOCT_XPOMAP[numOct - 2][j];
370
+ }
371
+ }
372
+ return smpIdx;
373
+ }
374
+ /** mmd_load_sampled_instrument (mmd_common.c:741-812). */
375
+ function loadSampledInstrument(st, i, smpIdx, instr, e, sample) {
376
+ const xxi = st.instruments[i];
377
+ xxi.nsm = 1;
378
+ xxi.sub = [makeSub()];
379
+ xxi.extras = holdDecayExtras(e);
380
+ const info = instrumentInfo(st, instr, e, sample);
381
+ mmdSetDefaultPitchNote(st, i, e, st.ver);
382
+ const sub = xxi.sub[0];
383
+ sub.vol = info.enable ? sample.svol : 0;
384
+ sub.xpo = info.sampletrans;
385
+ sub.sid = smpIdx;
386
+ sub.fin = (((e.finetune << 4) & 0xff) << 24) >> 24;
387
+ st.samples[smpIdx] = {
388
+ name: '',
389
+ data: new Uint8Array(0),
390
+ length: info.length,
391
+ loopStart: info.rep,
392
+ loopEnd: info.rep + info.replen,
393
+ sustainStart: 0,
394
+ sustainEnd: 0,
395
+ finetune: sub.fin,
396
+ volume: sub.vol,
397
+ flags: info.flg,
398
+ c5spd: C4_NTSC_RATE,
399
+ };
400
+ // Restrict sampled instruments to a 3-octave range except for MMD3.
401
+ // ExtSamples have two extra octaves.
402
+ if (st.ver < 3) {
403
+ const octaves = (instr.type & 7) === 7 ? 5 : 3;
404
+ for (let j = 0; j < 9; j++) {
405
+ for (let k = 0; k < 12; k++) {
406
+ let xpo = 0;
407
+ if (j < 1)
408
+ xpo = 12 * (1 - j);
409
+ else if (j > octaves)
410
+ xpo = -12 * (j - octaves);
411
+ xxi.mapXpo[12 * j + k] = xpo;
412
+ }
413
+ }
414
+ }
415
+ loadSampleInto(st, smpIdx, 1); // SAMPLE_FLAG_BIGEND
416
+ return smpIdx + 1;
417
+ }
418
+ /** mmd_load_instrument (mmd_common.c:912-990). Returns the next smp_idx. */
419
+ export function mmdLoadInstrument(st, i, smpIdx) {
420
+ const r = new Reader(st.bytes, st.pos);
421
+ const cst = { ...st, r };
422
+ const length = r.u32();
423
+ const type = r.s16();
424
+ const sampleType = type & ~(S_16 | 0x18 | STEREO);
425
+ const e = st.expSmp[i] ?? {
426
+ hold: 0, decay: 0, suppress_midi_off: 0, finetune: 0,
427
+ default_pitch: 0, instr_flags: 0, long_repeat: 0, long_replen: 0,
428
+ };
429
+ const sample = st.song[i] ?? {
430
+ rep: 0, replen: 0, midich: 0, midipreset: 0, svol: 0, strans: 0,
431
+ };
432
+ let out;
433
+ if (type === -2) { // Hybrid
434
+ out = loadHybridInstrument(cst, i, smpIdx, e, sample);
435
+ }
436
+ else if (type === -1) { // Synthetic
437
+ const res = loadSynthInstrument(cst, i, smpIdx, e, sample);
438
+ out = res.smpIdx;
439
+ }
440
+ else if (type >= 1 && type <= 6) { // IFFOCT
441
+ const oct = MMD_NUM_OCT[type - 1];
442
+ out = loadIffoctInstrument(cst, i, smpIdx, { length, type }, oct, e, sample);
443
+ }
444
+ else if (sampleType === 0 || sampleType === 7) { // Sample
445
+ out = loadSampledInstrument(cst, i, smpIdx, { length, type }, e, sample);
446
+ }
447
+ else {
448
+ throw new ParseError(`MED: invalid instrument type ${type}`);
449
+ }
450
+ st.pos = r.pos;
451
+ return out;
452
+ }
453
+ //# sourceMappingURL=instr.js.map