@map-audio/pam-mcp-server 1.25.0 → 1.28.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 +32 -10
- package/dist/audioAnalysis.d.ts +1 -1
- package/dist/audioAnalysis.js +3 -4
- package/dist/audioAnalysis.js.map +1 -1
- package/dist/bridge.d.ts +5 -1
- package/dist/bridge.js +48 -48
- package/dist/bridge.js.map +1 -1
- package/dist/daemon.d.ts +32 -0
- package/dist/daemon.js +871 -0
- package/dist/daemon.js.map +7 -0
- package/dist/drumSynth.d.ts +12 -0
- package/dist/drumSynth.js +15 -3
- package/dist/drumSynth.js.map +1 -1
- package/dist/drumSynthEngines.json +9 -1
- package/dist/{fgemma-format.d.ts → functiongemma-format.d.ts} +25 -7
- package/dist/{fgemma-format.js → functiongemma-format.js} +34 -11
- package/dist/functiongemma-format.js.map +1 -0
- package/dist/functiongemma-grammar.d.ts +14 -0
- package/dist/functiongemma-grammar.js +140 -0
- package/dist/functiongemma-grammar.js.map +1 -0
- package/dist/functiongemma-params.d.ts +33 -0
- package/dist/functiongemma-params.js +128 -0
- package/dist/functiongemma-params.js.map +1 -0
- package/dist/{fgemma-runner.d.ts → functiongemma-runner.d.ts} +8 -5
- package/dist/{fgemma-runner.js → functiongemma-runner.js} +107 -25
- package/dist/functiongemma-runner.js.map +1 -0
- package/dist/generated/kvWriteOptions.d.ts +20 -0
- package/dist/generated/kvWriteOptions.js +45 -0
- package/dist/generated/kvWriteOptions.js.map +1 -0
- package/dist/generated/variationSnapshot.js +0 -19
- package/dist/generated/variationSnapshot.js.map +1 -1
- package/dist/generativePlayerPresets.json +9 -0
- package/dist/generativePlayers.json +15 -1
- package/dist/launcher.d.ts +2 -0
- package/dist/launcher.js +36 -0
- package/dist/launcher.js.map +1 -0
- package/dist/manifest-mfx.json +12944 -311
- package/dist/manifest-msq.json +11446 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +12 -27
- package/dist/manifest.js.map +1 -1
- package/dist/manifest.json +37047 -6487
- package/dist/mfxCombos.d.ts +2 -0
- package/dist/mfxCombos.js +179 -56
- package/dist/mfxCombos.js.map +1 -1
- package/dist/midiFile.d.ts +20 -6
- package/dist/midiFile.js +22 -14
- package/dist/midiFile.js.map +1 -1
- package/dist/mixAssist.d.ts +35 -0
- package/dist/mixAssist.js +250 -21
- package/dist/mixAssist.js.map +1 -1
- package/dist/patternLanes.d.ts +40 -0
- package/dist/patternLanes.js +108 -0
- package/dist/patternLanes.js.map +1 -0
- package/dist/players.d.ts +22 -0
- package/dist/players.js +70 -0
- package/dist/players.js.map +1 -1
- package/dist/presetSave.d.ts +3 -0
- package/dist/presetSave.js +13 -0
- package/dist/presetSave.js.map +1 -1
- package/dist/productProfile.d.ts +4 -4
- package/dist/productProfile.js +10 -1
- package/dist/productProfile.js.map +1 -1
- package/dist/qwen-format.d.ts +85 -0
- package/dist/qwen-format.js +355 -0
- package/dist/qwen-format.js.map +1 -0
- package/dist/registration.mjs +41 -0
- package/dist/server.js +2112 -470
- package/dist/server.js.map +1 -1
- package/dist/targetDenylist.json +20 -0
- package/dist/variationSessionKeys.json +7 -5
- package/package.json +13 -7
- package/dist/fgemma-format.js.map +0 -1
- package/dist/fgemma-runner.js.map +0 -1
- package/dist/manifest-cell1.json +0 -12968
package/dist/midiFile.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Standard MIDI File (type 1) writer for export_midi — pure Node over the
|
|
2
|
-
//
|
|
1
|
+
// Standard MIDI File (type 1) writer for export_midi — pure Node over the events PAM renders
|
|
2
|
+
// (notes and CC out), with a drawn-grid tiling fallback for builds that predate that route.
|
|
3
3
|
export const MIDI_PPQ = 480;
|
|
4
4
|
/** Must match syncUtils::patternSpeedMultipliers (modules/dsp-core/SyncUtils.h). */
|
|
5
5
|
export const PATTERN_SPEED_MULTIPLIERS = [
|
|
6
6
|
1 / 32, 1 / 16, 1 / 12, 1 / 8, 1 / 6, 1 / 5, 1 / 4, 1 / 3, 1 / 2, 1,
|
|
7
|
-
|
|
7
|
+
4 / 3, 1.5, 5 / 3, 2, 3, 4, 5, 6, 8, 12,
|
|
8
8
|
];
|
|
9
9
|
export const patternSpeedMultiplier = (index) => {
|
|
10
10
|
const i = Math.round(Number(index));
|
|
@@ -61,27 +61,34 @@ function chunk(type, body) {
|
|
|
61
61
|
header.writeUInt32BE(body.length, 4);
|
|
62
62
|
return Buffer.concat([header, Buffer.from(body)]);
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
/** Quarter-note beats in one bar of `meter`. */
|
|
65
|
+
export const beatsPerBar = ({ numerator, denominator }) => (numerator * 4) / denominator;
|
|
66
|
+
function metaTrack(bpm, name, meter) {
|
|
65
67
|
const usPerQuarter = Math.round(60_000_000 / Math.max(1, bpm));
|
|
66
68
|
const nameBytes = [...Buffer.from(name, "utf8")];
|
|
67
69
|
const body = [
|
|
68
70
|
...varLen(0), 0xff, 0x03, ...varLen(nameBytes.length), ...nameBytes,
|
|
69
71
|
...varLen(0), 0xff, 0x51, 0x03, (usPerQuarter >> 16) & 0xff, (usPerQuarter >> 8) & 0xff, usPerQuarter & 0xff,
|
|
70
|
-
...varLen(0), 0xff, 0x58, 0x04,
|
|
72
|
+
...varLen(0), 0xff, 0x58, 0x04, meter.numerator, Math.round(Math.log2(meter.denominator)), 24, 8,
|
|
71
73
|
...varLen(0), 0xff, 0x2f, 0x00,
|
|
72
74
|
];
|
|
73
75
|
return chunk("MTrk", body);
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
const channelBits = (channel) => Math.max(0, Math.min(15, Math.round(channel) - 1));
|
|
78
|
+
const dataByte = (value) => Math.max(0, Math.min(127, Math.round(value)));
|
|
79
|
+
function eventTrack({ name, events, cc = [] }, lengthBeats) {
|
|
76
80
|
const messages = [];
|
|
81
|
+
// At one tick: note-offs (a retrigger is not swallowed), then controllers (a lane lock lands
|
|
82
|
+
// before its note), then note-ons, as PAM's MIDI out orders them.
|
|
77
83
|
for (const e of events) {
|
|
78
|
-
const status =
|
|
84
|
+
const status = channelBits(e.channel);
|
|
79
85
|
const on = Math.round(e.startBeats * MIDI_PPQ);
|
|
80
86
|
const off = Math.max(on + 1, Math.round((e.startBeats + e.durationBeats) * MIDI_PPQ));
|
|
81
|
-
// Note-offs sort before note-ons at the same tick so a retrigger is not swallowed.
|
|
82
87
|
messages.push({ tick: off, order: 0, bytes: [0x80 | status, e.note, 0] });
|
|
83
|
-
messages.push({ tick: on, order:
|
|
88
|
+
messages.push({ tick: on, order: 2, bytes: [0x90 | status, e.note, e.velocity] });
|
|
84
89
|
}
|
|
90
|
+
for (const e of cc)
|
|
91
|
+
messages.push({ tick: Math.max(0, Math.round(e.beats * MIDI_PPQ)), order: 1, bytes: [0xb0 | channelBits(e.channel), dataByte(e.controller), dataByte(e.value)] });
|
|
85
92
|
messages.sort((a, b) => a.tick - b.tick || a.order - b.order);
|
|
86
93
|
const nameBytes = [...Buffer.from(name, "utf8")];
|
|
87
94
|
const body = [...varLen(0), 0xff, 0x03, ...varLen(nameBytes.length), ...nameBytes];
|
|
@@ -90,19 +97,20 @@ function noteTrack(name, events) {
|
|
|
90
97
|
body.push(...varLen(m.tick - last), ...m.bytes);
|
|
91
98
|
last = m.tick;
|
|
92
99
|
}
|
|
93
|
-
|
|
100
|
+
// End of Track at the export length, so a re-import loops at it rather than at the last note-off.
|
|
101
|
+
body.push(...varLen(Math.max(0, Math.round(lengthBeats * MIDI_PPQ) - last)), 0xff, 0x2f, 0x00);
|
|
94
102
|
return chunk("MTrk", body);
|
|
95
103
|
}
|
|
96
|
-
/** A type-1 SMF: one tempo track plus one named track per entry. */
|
|
97
|
-
export function buildMidiFile(bpm, tracks, title = "PAM") {
|
|
104
|
+
/** A type-1 SMF: one tempo and meter track plus one named track per entry. */
|
|
105
|
+
export function buildMidiFile(bpm, tracks, title = "PAM", meter = { numerator: 4, denominator: 4 }, lengthBeats = 0) {
|
|
98
106
|
const header = Buffer.alloc(6);
|
|
99
107
|
header.writeUInt16BE(1, 0);
|
|
100
108
|
header.writeUInt16BE(tracks.length + 1, 2);
|
|
101
109
|
header.writeUInt16BE(MIDI_PPQ, 4);
|
|
102
110
|
return Buffer.concat([
|
|
103
111
|
chunk("MThd", [...header]),
|
|
104
|
-
metaTrack(bpm, title),
|
|
105
|
-
...tracks.map((track) =>
|
|
112
|
+
metaTrack(bpm, title, meter),
|
|
113
|
+
...tracks.map((track) => eventTrack(track, lengthBeats)),
|
|
106
114
|
]);
|
|
107
115
|
}
|
|
108
116
|
//# sourceMappingURL=midiFile.js.map
|
package/dist/midiFile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midiFile.js","sourceRoot":"","sources":["../src/midiFile.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"midiFile.js","sourceRoot":"","sources":["../src/midiFile.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,4FAA4F;AAE5F,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAC;AAE5B,oFAAoF;AACpF,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACnE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;CAC/B,CAAC;AAEX,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAc,EAAU,EAAE;IAC/D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACpC,OAAO,yBAAyB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5H,CAAC,CAAC;AA8BF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAiB,EACjB,aAAsB,EACtB,UAAmB,EACnB,UAAkB,EAClB,OAAe;IAEf,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACrE,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC;IACnF,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1D,yEAAyE;IACzE,MAAM,GAAG,GAAG,QAA0B,CAAC;IACvC,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,MAAM,aAAa,GAAG,WAAW,GAAG,CAAC,IAAI,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,UAAU,GAAG,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,IAAI,GAAG,UAAU,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;YACzC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC;gBAAE,SAAS;YACrG,MAAM,UAAU,GAAG,SAAS,GAAG,IAAI,GAAG,QAAQ,CAAC;YAC/C,IAAI,UAAU,IAAI,UAAU;gBAAE,SAAS;YACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;YACrC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;YACjH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;YACtG,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;QACxH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACzB,CAAC,KAAK,CAAC,CAAC;IACR,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACb,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACjC,CAAC,KAAK,CAAC,CAAC;IACV,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,KAAK,CAAC,IAAY,EAAE,IAAc;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAOD,gDAAgD;AAChD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,SAAS,EAAE,WAAW,EAAS,EAAU,EAAE,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;AAExG,SAAS,SAAS,CAAC,GAAW,EAAE,IAAY,EAAE,KAAY;IACxD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG;QACX,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,GAAG,SAAS;QACnE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,YAAY,GAAG,IAAI;QAC5G,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QAChG,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;KAC/B,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAC5F,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAElF,SAAS,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAa,EAAE,WAAmB;IAE3E,MAAM,QAAQ,GAAU,EAAE,CAAC;IAC3B,6FAA6F;IAC7F,kEAAkE;IAClE,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,GAAG,QAAQ,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;QACtF,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE;QAChB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;IACpK,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC;IACnF,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAChD,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IAChB,CAAC;IACD,kGAAkG;IAClG,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/F,OAAO,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAC3B,GAAW,EACX,MAAmB,EACnB,KAAK,GAAG,KAAK,EACb,QAAe,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAC/C,WAAW,GAAG,CAAC;IAEf,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;QAC1B,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC;QAC5B,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;KACzD,CAAC,CAAC;AACL,CAAC"}
|
package/dist/mixAssist.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface CellStemAnalysis {
|
|
|
14
14
|
onsetDensity: number;
|
|
15
15
|
seconds: number;
|
|
16
16
|
barkDb: number[];
|
|
17
|
+
coactivity?: Record<number, number>;
|
|
17
18
|
}
|
|
18
19
|
export interface MixAnalysis {
|
|
19
20
|
epoch: number;
|
|
@@ -35,6 +36,8 @@ export interface CellContext {
|
|
|
35
36
|
eqMidGain: number;
|
|
36
37
|
eqHighGain: number;
|
|
37
38
|
eqEnabled: boolean;
|
|
39
|
+
eqLowMidFreq?: number;
|
|
40
|
+
eqMidHighFreq?: number;
|
|
38
41
|
compressorEnabled: boolean;
|
|
39
42
|
compressorThreshold: number;
|
|
40
43
|
compressorRatioAmount: number;
|
|
@@ -106,6 +109,7 @@ export declare const MAX_EQ_CUT_DB = 3;
|
|
|
106
109
|
export declare const MAX_EQ_LOW_CUT_DB = 1.5;
|
|
107
110
|
export declare const MAX_EQ_CUTS = 2;
|
|
108
111
|
export declare const EQ_EPSILON_DB = 0.3;
|
|
112
|
+
export declare const MIN_EQ_CUT_SCORE = 0.2;
|
|
109
113
|
export declare const CREST_ENGAGE_DB = 14;
|
|
110
114
|
export declare const COMP_RATIO_MIN = 1.5;
|
|
111
115
|
export declare const COMP_RATIO_MAX = 4;
|
|
@@ -113,16 +117,37 @@ export declare const COMP_EPSILON = 0.05;
|
|
|
113
117
|
export declare const PERCUSSIVITY_FULL_ONSET_DENSITY = 8;
|
|
114
118
|
export declare const MIX_PARAM_DEFAULTS: Record<string, number>;
|
|
115
119
|
export declare function mixParamDefault(key: string): number;
|
|
120
|
+
export type RbjBiquadType = 'peaking' | 'lowshelf' | 'highshelf' | 'highpass' | 'lowpass' | 'notch';
|
|
121
|
+
export declare const gainToDb: (gain: number) => number;
|
|
122
|
+
/** |H(e^jw)| for one normalized RBJ biquad section. */
|
|
123
|
+
export declare function rbjBiquadMagnitude(frequencyHz: number, sampleRate: number, type: RbjBiquadType, centerHz: number, gainDb: number, q: number): number;
|
|
124
|
+
export declare const EQ_DEFAULT_LOW_MID_HZ = 880;
|
|
125
|
+
export declare const EQ_DEFAULT_MID_HZ = 2100;
|
|
126
|
+
export declare const EQ_DEFAULT_MID_HIGH_HZ = 5000;
|
|
127
|
+
export declare const EQ_LOW_MID_MIN_HZ = 80;
|
|
128
|
+
export declare const EQ_LOW_MID_MAX_HZ = 2000;
|
|
129
|
+
export declare const EQ_MID_HIGH_MIN_HZ = 2000;
|
|
130
|
+
export declare const EQ_MID_HIGH_MAX_HZ = 12000;
|
|
131
|
+
export declare const EQ_SAMPLE_RATE = 48000;
|
|
132
|
+
export declare const EQ_SHELF_Q = 0.5;
|
|
133
|
+
export declare const EQ_MID_Q = 0.7071;
|
|
134
|
+
export declare function eqMidFrequencyHz(lowMidHz: number, midHighHz: number): number;
|
|
135
|
+
/** Exact magnitude response of the native three cascaded Biquads. */
|
|
136
|
+
export declare function eqResponseDb(hz: number, lowGainDb: number, midGainDb: number, highGainDb: number, lowMidHz?: number, midHighHz?: number, sampleRate?: number): number;
|
|
116
137
|
export declare const BARK_BAND_COUNT = 25;
|
|
117
138
|
export declare const BARK_BAND_EDGES_HZ: readonly number[];
|
|
118
139
|
export declare function bandForFrequency(hz: number): number;
|
|
119
140
|
export declare function barkBandCenterHz(band: number): number;
|
|
120
141
|
export declare const dbToPower: (db: number) => number;
|
|
142
|
+
export declare const BARK_K_WEIGHT: readonly number[];
|
|
143
|
+
export declare function weightedBandPower(barkDb: readonly number[], band: number): number;
|
|
121
144
|
export declare function barkCentroidHz(barkDb: readonly number[]): number;
|
|
122
145
|
export declare function lowFraction(barkDb: readonly number[], belowHz?: number): number;
|
|
123
146
|
export declare function maskingOverlap(aDb: readonly number[], bDb: readonly number[]): number;
|
|
124
147
|
export declare function spectralShapeOverlap(aDb: readonly number[], bDb: readonly number[]): number;
|
|
125
148
|
export declare function maskingMatrix(barkDbByCell: readonly (readonly number[])[]): number[][];
|
|
149
|
+
export declare function coactivity(cell: CellStemAnalysis, otherCellId: number): number;
|
|
150
|
+
export declare function effectiveMaskingMatrix(cells: readonly CellStemAnalysis[]): number[][];
|
|
126
151
|
export type OverlapRegion = 'low' | 'mid' | 'high';
|
|
127
152
|
export declare function dominantOverlapRegion(aDb: readonly number[], bDb: readonly number[]): OverlapRegion;
|
|
128
153
|
export declare function parseMixAnalysisPayload(raw: unknown): MixAnalysis | null;
|
|
@@ -158,4 +183,14 @@ export declare function classifyCellRole(a: CellStemAnalysis): CellRole;
|
|
|
158
183
|
export declare function normalizeMovesToBus(deltasDb: number[], lufs: number[]): number[];
|
|
159
184
|
export declare function normalizeMovesToBusBounded(rawDeltasDb: number[], lufs: number[], currentVolumes: number[]): number[];
|
|
160
185
|
export declare function maskerWeight(pLufs: number, qLufs: number): number;
|
|
186
|
+
type EqKnob = 'eqLowGain' | 'eqMidGain' | 'eqHighGain';
|
|
187
|
+
export interface EqCutPlan {
|
|
188
|
+
key: EqKnob;
|
|
189
|
+
region: OverlapRegion;
|
|
190
|
+
label: string;
|
|
191
|
+
centerHz: number;
|
|
192
|
+
score: number;
|
|
193
|
+
}
|
|
194
|
+
export declare function planEqCut(maskedDb: readonly number[], maskerDb: readonly number[], lowMidHz?: number, midHighHz?: number): EqCutPlan | null;
|
|
161
195
|
export declare function suggestMix(m: MixMeasurement, options: MixAssistOptions, current?: CurrentFlatState): MixSuggestion;
|
|
196
|
+
export {};
|
package/dist/mixAssist.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GENERATED FILE — do not edit by hand.
|
|
2
2
|
// Vendored from shared/mixAssist/ (server rootDir cannot import outside src/;
|
|
3
|
-
// the
|
|
3
|
+
// the functiongemma-format.ts precedent). Regenerate with:
|
|
4
4
|
// node scripts/sync-mixassist.mjs
|
|
5
5
|
// Parity is pinned by interface/test/mixAssistServerParity.test.ts.
|
|
6
6
|
/* eslint-disable */
|
|
@@ -85,6 +85,8 @@ export const MAX_EQ_CUT_DB = 3; // Hafezi bound
|
|
|
85
85
|
export const MAX_EQ_LOW_CUT_DB = 1.5; // preserve low-end weight on non-bass maskers
|
|
86
86
|
export const MAX_EQ_CUTS = 2;
|
|
87
87
|
export const EQ_EPSILON_DB = 0.3;
|
|
88
|
+
// planEqCut score below which no EQ3 knob reaches the conflict usefully.
|
|
89
|
+
export const MIN_EQ_CUT_SCORE = 0.2;
|
|
88
90
|
// --- dynamics (comp) stage ---
|
|
89
91
|
export const CREST_ENGAGE_DB = 14; // plr above this makes a cell comp-eligible
|
|
90
92
|
export const COMP_RATIO_MIN = 1.5;
|
|
@@ -111,6 +113,120 @@ export function mixParamDefault(key) {
|
|
|
111
113
|
const suffix = key.replace(/^cell\d+_/, '');
|
|
112
114
|
return MIX_PARAM_DEFAULTS[suffix] ?? 0;
|
|
113
115
|
}
|
|
116
|
+
export const gainToDb = (gain) => 20 * Math.log10(Math.max(gain, 1e-9));
|
|
117
|
+
/** |H(e^jw)| for one normalized RBJ biquad section. */
|
|
118
|
+
export function rbjBiquadMagnitude(frequencyHz, sampleRate, type, centerHz, gainDb, q) {
|
|
119
|
+
const w = (2 * Math.PI * Math.min(Math.max(frequencyHz, 1e-6), sampleRate * 0.4999)) / sampleRate;
|
|
120
|
+
const w0 = (2 * Math.PI * Math.min(Math.max(centerHz, 1e-3), sampleRate * 0.4999)) / sampleRate;
|
|
121
|
+
const cos0 = Math.cos(w0);
|
|
122
|
+
const sin0 = Math.sin(w0);
|
|
123
|
+
const A = Math.pow(10, (gainDb || 0) / 40);
|
|
124
|
+
const alpha0 = sin0 / (2 * Math.max(1e-6, q));
|
|
125
|
+
let b0 = 1;
|
|
126
|
+
let b1 = 0;
|
|
127
|
+
let b2 = 0;
|
|
128
|
+
let a0 = 1;
|
|
129
|
+
let a1 = 0;
|
|
130
|
+
let a2 = 0;
|
|
131
|
+
switch (type) {
|
|
132
|
+
case 'peaking':
|
|
133
|
+
b0 = 1 + alpha0 * A;
|
|
134
|
+
b1 = -2 * cos0;
|
|
135
|
+
b2 = 1 - alpha0 * A;
|
|
136
|
+
a0 = 1 + alpha0 / A;
|
|
137
|
+
a1 = -2 * cos0;
|
|
138
|
+
a2 = 1 - alpha0 / A;
|
|
139
|
+
break;
|
|
140
|
+
case 'lowshelf': {
|
|
141
|
+
const sqrtA = Math.sqrt(A);
|
|
142
|
+
const two = 2 * sqrtA * alpha0;
|
|
143
|
+
b0 = A * (A + 1 - (A - 1) * cos0 + two);
|
|
144
|
+
b1 = 2 * A * (A - 1 - (A + 1) * cos0);
|
|
145
|
+
b2 = A * (A + 1 - (A - 1) * cos0 - two);
|
|
146
|
+
a0 = A + 1 + (A - 1) * cos0 + two;
|
|
147
|
+
a1 = -2 * (A - 1 + (A + 1) * cos0);
|
|
148
|
+
a2 = A + 1 + (A - 1) * cos0 - two;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'highshelf': {
|
|
152
|
+
const sqrtA = Math.sqrt(A);
|
|
153
|
+
const two = 2 * sqrtA * alpha0;
|
|
154
|
+
b0 = A * (A + 1 + (A - 1) * cos0 + two);
|
|
155
|
+
b1 = -2 * A * (A - 1 + (A + 1) * cos0);
|
|
156
|
+
b2 = A * (A + 1 + (A - 1) * cos0 - two);
|
|
157
|
+
a0 = A + 1 - (A - 1) * cos0 + two;
|
|
158
|
+
a1 = 2 * (A - 1 - (A + 1) * cos0);
|
|
159
|
+
a2 = A + 1 - (A - 1) * cos0 - two;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'lowpass':
|
|
163
|
+
b0 = (1 - cos0) / 2;
|
|
164
|
+
b1 = 1 - cos0;
|
|
165
|
+
b2 = (1 - cos0) / 2;
|
|
166
|
+
a0 = 1 + alpha0;
|
|
167
|
+
a1 = -2 * cos0;
|
|
168
|
+
a2 = 1 - alpha0;
|
|
169
|
+
break;
|
|
170
|
+
case 'highpass':
|
|
171
|
+
b0 = (1 + cos0) / 2;
|
|
172
|
+
b1 = -(1 + cos0);
|
|
173
|
+
b2 = (1 + cos0) / 2;
|
|
174
|
+
a0 = 1 + alpha0;
|
|
175
|
+
a1 = -2 * cos0;
|
|
176
|
+
a2 = 1 - alpha0;
|
|
177
|
+
break;
|
|
178
|
+
case 'notch':
|
|
179
|
+
b0 = 1;
|
|
180
|
+
b1 = -2 * cos0;
|
|
181
|
+
b2 = 1;
|
|
182
|
+
a0 = 1 + alpha0;
|
|
183
|
+
a1 = -2 * cos0;
|
|
184
|
+
a2 = 1 - alpha0;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
const bz0 = b0 / a0;
|
|
188
|
+
const bz1 = b1 / a0;
|
|
189
|
+
const bz2 = b2 / a0;
|
|
190
|
+
const az1 = a1 / a0;
|
|
191
|
+
const az2 = a2 / a0;
|
|
192
|
+
const cosw = Math.cos(w);
|
|
193
|
+
const sinw = Math.sin(w);
|
|
194
|
+
const cos2w = Math.cos(2 * w);
|
|
195
|
+
const sin2w = Math.sin(2 * w);
|
|
196
|
+
const numerator = (bz0 + bz1 * cosw + bz2 * cos2w) ** 2 + (bz1 * sinw + bz2 * sin2w) ** 2;
|
|
197
|
+
const denominator = (1 + az1 * cosw + az2 * cos2w) ** 2 + (az1 * sinw + az2 * sin2w) ** 2;
|
|
198
|
+
return Math.sqrt(numerator / Math.max(denominator, 1e-24));
|
|
199
|
+
}
|
|
200
|
+
// ---------------- EQ3 (the per-cell mixer EQ) ----------------
|
|
201
|
+
// Exact twins of modules/eq/EQ.h. The old engine was fixed at these frequencies;
|
|
202
|
+
// they remain the parameter defaults so old presets keep their sound.
|
|
203
|
+
export const EQ_DEFAULT_LOW_MID_HZ = 880;
|
|
204
|
+
export const EQ_DEFAULT_MID_HZ = 2100;
|
|
205
|
+
export const EQ_DEFAULT_MID_HIGH_HZ = 5000;
|
|
206
|
+
export const EQ_LOW_MID_MIN_HZ = 80;
|
|
207
|
+
export const EQ_LOW_MID_MAX_HZ = 2000;
|
|
208
|
+
export const EQ_MID_HIGH_MIN_HZ = 2000;
|
|
209
|
+
export const EQ_MID_HIGH_MAX_HZ = 12000;
|
|
210
|
+
export const EQ_SAMPLE_RATE = 48000;
|
|
211
|
+
export const EQ_SHELF_Q = 0.5;
|
|
212
|
+
export const EQ_MID_Q = 0.7071;
|
|
213
|
+
const clampHz = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
214
|
+
export function eqMidFrequencyHz(lowMidHz, midHighHz) {
|
|
215
|
+
const low = clampHz(lowMidHz, EQ_LOW_MID_MIN_HZ, EQ_LOW_MID_MAX_HZ);
|
|
216
|
+
const high = clampHz(midHighHz, EQ_MID_HIGH_MIN_HZ, EQ_MID_HIGH_MAX_HZ);
|
|
217
|
+
if (low === EQ_DEFAULT_LOW_MID_HZ && high === EQ_DEFAULT_MID_HIGH_HZ)
|
|
218
|
+
return EQ_DEFAULT_MID_HZ;
|
|
219
|
+
return EQ_DEFAULT_MID_HZ * Math.sqrt((low / EQ_DEFAULT_LOW_MID_HZ) * (high / EQ_DEFAULT_MID_HIGH_HZ));
|
|
220
|
+
}
|
|
221
|
+
/** Exact magnitude response of the native three cascaded Biquads. */
|
|
222
|
+
export function eqResponseDb(hz, lowGainDb, midGainDb, highGainDb, lowMidHz = EQ_DEFAULT_LOW_MID_HZ, midHighHz = EQ_DEFAULT_MID_HIGH_HZ, sampleRate = EQ_SAMPLE_RATE) {
|
|
223
|
+
const low = clampHz(lowMidHz, EQ_LOW_MID_MIN_HZ, EQ_LOW_MID_MAX_HZ);
|
|
224
|
+
const high = clampHz(midHighHz, EQ_MID_HIGH_MIN_HZ, EQ_MID_HIGH_MAX_HZ);
|
|
225
|
+
const magnitude = rbjBiquadMagnitude(hz, sampleRate, 'lowshelf', low, lowGainDb, EQ_SHELF_Q) *
|
|
226
|
+
rbjBiquadMagnitude(hz, sampleRate, 'peaking', eqMidFrequencyHz(low, high), midGainDb, EQ_MID_Q) *
|
|
227
|
+
rbjBiquadMagnitude(hz, sampleRate, 'highshelf', high, highGainDb, EQ_SHELF_Q);
|
|
228
|
+
return gainToDb(magnitude);
|
|
229
|
+
}
|
|
114
230
|
// ===== shared/mixAssist/bark.ts =====
|
|
115
231
|
// Bark band model — the TS mirror of modules/analysis/BarkScale.h. The 26
|
|
116
232
|
// edge literals MUST match the C++ table verbatim; parity is pinned by
|
|
@@ -137,6 +253,33 @@ export function barkBandCenterHz(band) {
|
|
|
137
253
|
return Math.sqrt(BARK_BAND_EDGES_HZ[b] * BARK_BAND_EDGES_HZ[b + 1]);
|
|
138
254
|
}
|
|
139
255
|
export const dbToPower = (db) => Math.pow(10, db / 10);
|
|
256
|
+
// BS.1770 K-weighting power gain per Bark band (48 kHz pre-filter + RLB
|
|
257
|
+
// high-pass), averaged over the band in log frequency. Masking decisions weigh
|
|
258
|
+
// band energy in the same domain the LUFS readings use, so raw sub-bass power
|
|
259
|
+
// cannot outvote what the ear hears in the mids.
|
|
260
|
+
function kWeightPowerAt(hz) {
|
|
261
|
+
const w = (2 * Math.PI * hz) / 48000;
|
|
262
|
+
const section = (b0, b1, b2, a1, a2) => {
|
|
263
|
+
const num = (b0 + b1 * Math.cos(w) + b2 * Math.cos(2 * w)) ** 2 + (b1 * Math.sin(w) + b2 * Math.sin(2 * w)) ** 2;
|
|
264
|
+
const den = (1 + a1 * Math.cos(w) + a2 * Math.cos(2 * w)) ** 2 + (a1 * Math.sin(w) + a2 * Math.sin(2 * w)) ** 2;
|
|
265
|
+
return num / den;
|
|
266
|
+
};
|
|
267
|
+
return (section(1.53512485958697, -2.69169618940638, 1.19839281085285, -1.69065929318241, 0.73248077421585) *
|
|
268
|
+
section(1, -2, 1, -1.99004745483398, 0.99007225036621));
|
|
269
|
+
}
|
|
270
|
+
export const BARK_K_WEIGHT = Array.from({ length: BARK_BAND_COUNT }, (_, b) => {
|
|
271
|
+
const lo = Math.log(BARK_BAND_EDGES_HZ[b]);
|
|
272
|
+
const hi = Math.log(BARK_BAND_EDGES_HZ[b + 1]);
|
|
273
|
+
const points = 16;
|
|
274
|
+
let sum = 0;
|
|
275
|
+
for (let i = 0; i < points; i++)
|
|
276
|
+
sum += kWeightPowerAt(Math.exp(lo + ((i + 0.5) / points) * (hi - lo)));
|
|
277
|
+
return sum / points;
|
|
278
|
+
});
|
|
279
|
+
// Band power as the loudness meter hears it (K-weighted), linear.
|
|
280
|
+
export function weightedBandPower(barkDb, band) {
|
|
281
|
+
return dbToPower(barkDb[band]) * BARK_K_WEIGHT[band];
|
|
282
|
+
}
|
|
140
283
|
// Power-weighted spectral centroid over band centers, Hz. 0 when silent.
|
|
141
284
|
export function barkCentroidHz(barkDb) {
|
|
142
285
|
let power = 0;
|
|
@@ -148,7 +291,8 @@ export function barkCentroidHz(barkDb) {
|
|
|
148
291
|
}
|
|
149
292
|
return power > 0 ? weighted / power : 0;
|
|
150
293
|
}
|
|
151
|
-
// Fraction of LINEAR power in bands wholly below `belowHz`. 0
|
|
294
|
+
// Fraction of LINEAR power in bands wholly below `belowHz`. 0 only at zero power: the analyzer's
|
|
295
|
+
// -120 dB silence floor (`kBarkFloorDb`) reads 2/25.
|
|
152
296
|
export function lowFraction(barkDb, belowHz = 200) {
|
|
153
297
|
let total = 0;
|
|
154
298
|
let low = 0;
|
|
@@ -164,7 +308,7 @@ export function lowFraction(barkDb, belowHz = 200) {
|
|
|
164
308
|
// Cross-cell Bark-band masking overlap (AutoMix M1). Deliberately simple:
|
|
165
309
|
// textbook objective masking models track perceived masking poorly (Hafezi &
|
|
166
310
|
// Reiss, JAES 2015) — a bounded band-overlap fraction works better as a rule
|
|
167
|
-
// input than a psychoacoustic model.
|
|
311
|
+
// input than a psychoacoustic model. Band energy is K-weighted (bark.ts).
|
|
168
312
|
// Fraction of A's energy that B overlaps: sum_b min(Pa, Pb) / sum_b Pa.
|
|
169
313
|
// Asymmetric (a quiet cell under a loud one reads ~1; the loud one reads
|
|
170
314
|
// less). 0 when A is silent.
|
|
@@ -173,8 +317,8 @@ export function maskingOverlap(aDb, bDb) {
|
|
|
173
317
|
let shared = 0;
|
|
174
318
|
const n = Math.min(aDb.length, bDb.length, BARK_BAND_COUNT);
|
|
175
319
|
for (let b = 0; b < n; b++) {
|
|
176
|
-
const pa =
|
|
177
|
-
const pb =
|
|
320
|
+
const pa = weightedBandPower(aDb, b);
|
|
321
|
+
const pb = weightedBandPower(bDb, b);
|
|
178
322
|
sumA += pa;
|
|
179
323
|
shared += Math.min(pa, pb);
|
|
180
324
|
}
|
|
@@ -197,14 +341,14 @@ export function spectralShapeOverlap(aDb, bDb) {
|
|
|
197
341
|
let sumA = 0;
|
|
198
342
|
let sumB = 0;
|
|
199
343
|
for (let b = 0; b < n; b++) {
|
|
200
|
-
sumA +=
|
|
201
|
-
sumB +=
|
|
344
|
+
sumA += weightedBandPower(aDb, b);
|
|
345
|
+
sumB += weightedBandPower(bDb, b);
|
|
202
346
|
}
|
|
203
347
|
if (sumA <= 0 || sumB <= 0)
|
|
204
348
|
return 0;
|
|
205
349
|
let shared = 0;
|
|
206
350
|
for (let b = 0; b < n; b++) {
|
|
207
|
-
shared += Math.min(
|
|
351
|
+
shared += Math.min(weightedBandPower(aDb, b) / sumA, weightedBandPower(bDb, b) / sumB);
|
|
208
352
|
}
|
|
209
353
|
return shared;
|
|
210
354
|
}
|
|
@@ -221,16 +365,34 @@ export function maskingMatrix(barkDbByCell) {
|
|
|
221
365
|
}
|
|
222
366
|
return m;
|
|
223
367
|
}
|
|
368
|
+
// Fraction of `cell`'s sounding frames in which `otherCellId` also sounds.
|
|
369
|
+
// Payloads without the map read 1, the old always-simultaneous assumption.
|
|
370
|
+
export function coactivity(cell, otherCellId) {
|
|
371
|
+
const v = cell.coactivity?.[otherCellId];
|
|
372
|
+
return v === undefined ? 1 : Math.min(1, Math.max(0, v));
|
|
373
|
+
}
|
|
374
|
+
// maskingMatrix gated by co-activity: two parts that never sound together do
|
|
375
|
+
// not mask each other, however alike their long-term spectra are.
|
|
376
|
+
export function effectiveMaskingMatrix(cells) {
|
|
377
|
+
const m = maskingMatrix(cells.map((c) => c.barkDb));
|
|
378
|
+
for (let i = 0; i < cells.length; i++) {
|
|
379
|
+
for (let j = 0; j < cells.length; j++) {
|
|
380
|
+
if (i !== j)
|
|
381
|
+
m[i][j] *= coactivity(cells[i], cells[j].cellId);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return m;
|
|
385
|
+
}
|
|
224
386
|
// Region carrying the largest shared power between two stems, split at
|
|
225
|
-
// 300 Hz / 5 kHz
|
|
226
|
-
// knob
|
|
387
|
+
// 300 Hz / 5 kHz by log-center. PAN reads it to keep bass-range conflicts
|
|
388
|
+
// centred; EQ cuts pick their knob from the real EQ3 curve instead.
|
|
227
389
|
export function dominantOverlapRegion(aDb, bDb) {
|
|
228
390
|
const shared = { low: 0, mid: 0, high: 0 };
|
|
229
391
|
const n = Math.min(aDb.length, bDb.length, BARK_BAND_COUNT);
|
|
230
392
|
for (let b = 0; b < n; b++) {
|
|
231
393
|
const center = barkBandCenterHz(b);
|
|
232
394
|
const region = center < 300 ? 'low' : center >= 5000 ? 'high' : 'mid';
|
|
233
|
-
shared[region] += Math.min(
|
|
395
|
+
shared[region] += Math.min(weightedBandPower(aDb, b), weightedBandPower(bDb, b));
|
|
234
396
|
}
|
|
235
397
|
if (shared.low >= shared.mid && shared.low >= shared.high)
|
|
236
398
|
return 'low';
|
|
@@ -240,6 +402,18 @@ export function dominantOverlapRegion(aDb, bDb) {
|
|
|
240
402
|
}
|
|
241
403
|
// ===== shared/mixAssist/parse.ts =====
|
|
242
404
|
const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
405
|
+
function parseCoactivity(raw) {
|
|
406
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
407
|
+
return undefined;
|
|
408
|
+
const out = {};
|
|
409
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
410
|
+
const id = Number(key);
|
|
411
|
+
const v = num(value);
|
|
412
|
+
if (Number.isInteger(id) && v !== undefined)
|
|
413
|
+
out[id] = Math.min(1, Math.max(0, v));
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
243
417
|
function parseStem(raw) {
|
|
244
418
|
// Number.isFinite per band: one NaN/Infinity entry would silently poison the
|
|
245
419
|
// masking matrix (dbToPower propagates it through every overlap sum).
|
|
@@ -263,6 +437,7 @@ function parseStem(raw) {
|
|
|
263
437
|
onsetDensity: num(raw.onsetDensity) ?? 0,
|
|
264
438
|
seconds: num(raw.secs) ?? 0,
|
|
265
439
|
barkDb: bark,
|
|
440
|
+
coactivity: parseCoactivity(raw.coact),
|
|
266
441
|
};
|
|
267
442
|
}
|
|
268
443
|
// Maps the native analyzeMixStems payload (short keys, sentinel fields
|
|
@@ -353,6 +528,8 @@ export function buildMixMeasurementFromState(analysis, flatState, mods = {}, opt
|
|
|
353
528
|
eqMidGain: numOr(k('eqMidGain'), 0),
|
|
354
529
|
eqHighGain: numOr(k('eqHighGain'), 0),
|
|
355
530
|
eqEnabled: numOr(k('eqEnabled'), 0) > 0.5,
|
|
531
|
+
eqLowMidFreq: numOr(k('eqLowMidFreq'), EQ_DEFAULT_LOW_MID_HZ),
|
|
532
|
+
eqMidHighFreq: numOr(k('eqMidHighFreq'), EQ_DEFAULT_MID_HIGH_HZ),
|
|
356
533
|
compressorEnabled: numOr(k('compressorEnabled'), 0) > 0.5,
|
|
357
534
|
compressorThreshold: numOr(k('compressorThreshold'), 0),
|
|
358
535
|
compressorRatioAmount: numOr(k('compressorRatioAmount'), 2),
|
|
@@ -474,6 +651,53 @@ export function normalizeMovesToBusBounded(rawDeltasDb, lufs, currentVolumes) {
|
|
|
474
651
|
export function maskerWeight(pLufs, qLufs) {
|
|
475
652
|
return 1 / (1 + Math.pow(10, (pLufs - qLufs) / 10));
|
|
476
653
|
}
|
|
654
|
+
const EQ_KNOBS = [
|
|
655
|
+
{ key: 'eqLowGain', region: 'low', label: 'low shelf' },
|
|
656
|
+
{ key: 'eqMidGain', region: 'mid', label: 'mid band' },
|
|
657
|
+
{ key: 'eqHighGain', region: 'high', label: 'high shelf' },
|
|
658
|
+
];
|
|
659
|
+
// Picks the masker's EQ3 knob from its REAL curve at this cell's crossovers.
|
|
660
|
+
// coverage = how hard a probe cut hits the shared (conflict) energy, collateral =
|
|
661
|
+
// how hard it hits the masker overall; score = coverage * min(1, coverage/collateral).
|
|
662
|
+
export function planEqCut(maskedDb, maskerDb, lowMidHz = EQ_DEFAULT_LOW_MID_HZ, midHighHz = EQ_DEFAULT_MID_HIGH_HZ) {
|
|
663
|
+
const n = Math.min(maskedDb.length, maskerDb.length, BARK_BAND_COUNT);
|
|
664
|
+
const shared = [];
|
|
665
|
+
const own = [];
|
|
666
|
+
let sharedSum = 0;
|
|
667
|
+
let ownSum = 0;
|
|
668
|
+
let logCenter = 0;
|
|
669
|
+
for (let b = 0; b < n; b++) {
|
|
670
|
+
const pk = weightedBandPower(maskerDb, b);
|
|
671
|
+
const s = Math.min(weightedBandPower(maskedDb, b), pk);
|
|
672
|
+
shared.push(s);
|
|
673
|
+
own.push(pk);
|
|
674
|
+
sharedSum += s;
|
|
675
|
+
ownSum += pk;
|
|
676
|
+
logCenter += s * Math.log(barkBandCenterHz(b));
|
|
677
|
+
}
|
|
678
|
+
if (sharedSum <= 0 || ownSum <= 0)
|
|
679
|
+
return null;
|
|
680
|
+
const probeDb = -MAX_EQ_CUT_DB;
|
|
681
|
+
const probeRemoval = 1 - Math.pow(10, probeDb / 10);
|
|
682
|
+
let best = null;
|
|
683
|
+
for (const knob of EQ_KNOBS) {
|
|
684
|
+
let hitShared = 0;
|
|
685
|
+
let hitOwn = 0;
|
|
686
|
+
for (let b = 0; b < n; b++) {
|
|
687
|
+
const responseDb = eqResponseDb(barkBandCenterHz(b), knob.key === 'eqLowGain' ? probeDb : 0, knob.key === 'eqMidGain' ? probeDb : 0, knob.key === 'eqHighGain' ? probeDb : 0, lowMidHz, midHighHz);
|
|
688
|
+
const removal = 1 - Math.pow(10, responseDb / 10);
|
|
689
|
+
hitShared += shared[b] * removal;
|
|
690
|
+
hitOwn += own[b] * removal;
|
|
691
|
+
}
|
|
692
|
+
const coverage = hitShared / (sharedSum * probeRemoval);
|
|
693
|
+
const collateral = hitOwn / (ownSum * probeRemoval);
|
|
694
|
+
const score = coverage * Math.min(1, coverage / Math.max(collateral, 1e-9));
|
|
695
|
+
if (!best || score > best.score) {
|
|
696
|
+
best = { key: knob.key, region: knob.region, label: knob.label, centerHz: Math.exp(logCenter / sharedSum), score };
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return best;
|
|
700
|
+
}
|
|
477
701
|
export function suggestMix(m, options, current = {}) {
|
|
478
702
|
const moves = [];
|
|
479
703
|
const guardsHit = [];
|
|
@@ -524,14 +748,15 @@ export function suggestMix(m, options, current = {}) {
|
|
|
524
748
|
// feeding it into effectiveLufs made the quietest cell take the deepest
|
|
525
749
|
// penalty and the largest compensating lift. spectralShapeOverlap answers
|
|
526
750
|
// only "do they collide", and maskerWeight scales that by the level gap.
|
|
527
|
-
const matrix =
|
|
751
|
+
const matrix = effectiveMaskingMatrix(participants.map((p) => p.cell.analysis));
|
|
528
752
|
for (const p of participants) {
|
|
529
753
|
let masked = 0;
|
|
530
754
|
for (const q of participants) {
|
|
531
755
|
if (q.index === p.index)
|
|
532
756
|
continue;
|
|
533
757
|
const shape = spectralShapeOverlap(p.cell.analysis.barkDb, q.cell.analysis.barkDb);
|
|
534
|
-
|
|
758
|
+
const together = coactivity(p.cell.analysis, q.cell.context.cellId);
|
|
759
|
+
masked = Math.max(masked, shape * maskerWeight(p.lufs, q.lufs) * together);
|
|
535
760
|
}
|
|
536
761
|
p.maskedFraction = masked;
|
|
537
762
|
p.effectiveLufs = p.lufs - MASK_PENALTY_DB_PER_UNIT * masked;
|
|
@@ -646,23 +871,24 @@ export function suggestMix(m, options, current = {}) {
|
|
|
646
871
|
guardsHit.push(`cell ${id} mixer EQ is modulated — no masking cut`);
|
|
647
872
|
continue;
|
|
648
873
|
}
|
|
649
|
-
const
|
|
874
|
+
const plan = planEqCut(c.masked.cell.analysis.barkDb, c.masker.cell.analysis.barkDb, maskerCtx.eqLowMidFreq, maskerCtx.eqMidHighFreq);
|
|
875
|
+
if (!plan || plan.score < MIN_EQ_CUT_SCORE) {
|
|
876
|
+
guardsHit.push(`cell ${id} EQ cannot reach the conflict with cell ${c.masked.cell.context.cellId}`);
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
650
879
|
// Never carve the fundamental of a low-dominant source. Kick and bass
|
|
651
880
|
// conflicts belong to balance or sidechain, not a broad static low cut.
|
|
652
|
-
|
|
653
|
-
// spill masks another cell.
|
|
654
|
-
if (region === 'low' && c.masker.lowDominant) {
|
|
881
|
+
if (plan.region === 'low' && c.masker.lowDominant) {
|
|
655
882
|
guardsHit.push(`cell ${id} is low-dominant — its low band is left alone`);
|
|
656
883
|
continue;
|
|
657
884
|
}
|
|
658
|
-
const
|
|
659
|
-
const
|
|
660
|
-
const maxCutDb = region === 'low' ? MAX_EQ_LOW_CUT_DB : MAX_EQ_CUT_DB;
|
|
885
|
+
const key = `cell${id}_${plan.key}`;
|
|
886
|
+
const maxCutDb = plan.region === 'low' ? MAX_EQ_LOW_CUT_DB : MAX_EQ_CUT_DB;
|
|
661
887
|
const cutDb = Math.min(maxCutDb, 6 * c.overlap - 2);
|
|
662
888
|
if (cutDb <= EQ_EPSILON_DB)
|
|
663
889
|
continue;
|
|
664
890
|
const value = round1(cur(key) - cutDb);
|
|
665
|
-
propose(id, key, value, `cell ${id} covers cell ${c.masked.cell.context.cellId}
|
|
891
|
+
propose(id, key, value, `cell ${id} covers cell ${c.masked.cell.context.cellId} around ${formatHz(plan.centerHz)} — cut ${round1(cutDb)} dB on the ${plan.label}`, EQ_EPSILON_DB);
|
|
666
892
|
if (flat[key] !== undefined && !maskerCtx.eqEnabled) {
|
|
667
893
|
flat[`cell${id}_eqEnabled`] = 1;
|
|
668
894
|
guardsHit.push(`cell ${id} mixer EQ enabled for the cut`);
|
|
@@ -734,4 +960,7 @@ export function suggestMix(m, options, current = {}) {
|
|
|
734
960
|
function cellId(p) {
|
|
735
961
|
return p.cell.context.cellId;
|
|
736
962
|
}
|
|
963
|
+
function formatHz(hz) {
|
|
964
|
+
return hz >= 1000 ? `${round1(hz / 1000)} kHz` : `${Math.round(hz / 10) * 10} Hz`;
|
|
965
|
+
}
|
|
737
966
|
//# sourceMappingURL=mixAssist.js.map
|