7c22ae1f02
- remove diagnostic canaries from audio hot path (crackle fix) and stuck C5 note raw-MIDI test (Vst2Instrument::init noteOff) - split native bridge into daw_vst_bridge (VSTi) + fx_vst_bridge (VST FX) - VST3: processData prepare before setActive, zero input buses, accept both .vstpreset and bridge-state blob in RenderJob presets - native_render/vst_engine: bridge-exe discovery + render_fx_chain + /fx-gui - delete old install/ engine binaries (install via sidecar+resources now) - add BUILD_LAYOUT.md, build_selfcheck, tools/ diagnostics
60 lines
2.9 KiB
JavaScript
60 lines
2.9 KiB
JavaScript
// tools/test_midi_polyphony.js — runnable check cho Phase A (dedup/legato/polyphony).
|
|
// Đọc app.jsx, extract 2 hàm thuần, eval, assert spec §II.1 + §II.5.
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const src = fs.readFileSync(path.join(__dirname, '..', 'app', 'static', 'js', 'app.jsx'), 'utf8');
|
|
|
|
const start = src.indexOf('const _cleanNativeNotes = (notes) => {');
|
|
const end = src.indexOf('const _nativeTrackKey', start);
|
|
if (start < 0 || end < 0) { console.error('FAIL: khong tim thay 2 ham'); process.exit(1); }
|
|
const code = src.slice(start, end) +
|
|
';globalThis._cleanNativeNotes=_cleanNativeNotes;' +
|
|
'globalThis._scaleNativePolyphony=_scaleNativePolyphony;';
|
|
// eslint-disable-next-line no-eval
|
|
(0, eval)(code); // indirect eval: const khai báo ở global scope
|
|
|
|
let fails = 0;
|
|
const assert = (cond, msg) => { if (!cond) { fails++; console.error('FAIL: ' + msg); } else { console.log('ok: ' + msg); } };
|
|
const near = (a, b, eps) => Math.abs(a - b) < (eps || 1e-9);
|
|
|
|
// 1. 8-note chord cùng lúc → tổng peak ≈ 0.3*8/sqrt(8) ≤ 1.0
|
|
const chord = [];
|
|
for (let i = 0; i < 8; i++) chord.push({ pitch: 60 + i, start_beat: 0, duration_beats: 2, velocity: 0.3 });
|
|
const sc = _scaleNativePolyphony(_cleanNativeNotes(chord));
|
|
const sum = sc.reduce((a, n) => a + n.velocity, 0);
|
|
assert(sc.length === 8, 'chord giu 8 note');
|
|
assert(near(sum, 8 * 0.3 / Math.sqrt(8)), 'tong peak ' + sum.toFixed(4) + ' ~= ' + (8 * 0.3 / Math.sqrt(8)).toFixed(4));
|
|
assert(sum <= 1.0, 'tong peak <= 1.0');
|
|
|
|
// 2. Solo → giữ nguyên velocity
|
|
const solo = _scaleNativePolyphony([{ pitch: 60, start_beat: 0, duration_beats: 1, velocity: 0.8 }]);
|
|
assert(near(solo[0].velocity, 0.8), 'solo velocity giu nguyen 0.8');
|
|
|
|
// 3. Dedup: cùng pitch + start_beat → gộp, velocity max
|
|
const dup = _cleanNativeNotes([
|
|
{ pitch: 60, start_beat: 1, duration_beats: 2, velocity: 0.5 },
|
|
{ pitch: 60, start_beat: 1, duration_beats: 3, velocity: 0.7 },
|
|
]);
|
|
assert(dup.length === 1, 'dedup gop 2 note');
|
|
assert(near(dup[0].velocity, 0.7), 'dedup giu velocity cao 0.7');
|
|
assert(near(dup[0].duration_beats, 3), 'dedup giu duration dai nhat');
|
|
|
|
// 4. Legato: note trước cắt 0.01 beats trước note sau cùng pitch
|
|
const leg = _cleanNativeNotes([
|
|
{ pitch: 60, start_beat: 0, duration_beats: 4, velocity: 0.8 },
|
|
{ pitch: 60, start_beat: 2, duration_beats: 1, velocity: 0.8 },
|
|
]);
|
|
assert(leg.length === 2, 'legato giu 2 note');
|
|
assert(near(leg[0].duration_beats, 2 - 0.01, 1e-6), 'note dau cat con ' + leg[0].duration_beats);
|
|
assert(near(leg[1].duration_beats, 1), 'note sau giu nguyen');
|
|
|
|
// 5. Nối tiếp khác pitch → không cắt
|
|
const seq = _cleanNativeNotes([
|
|
{ pitch: 60, start_beat: 0, duration_beats: 4, velocity: 0.8 },
|
|
{ pitch: 64, start_beat: 2, duration_beats: 1, velocity: 0.8 },
|
|
]);
|
|
assert(near(seq[0].duration_beats, 4), 'khac pitch khong bi cat');
|
|
|
|
console.log(fails ? 'FAILED: ' + fails : 'ALL PASS');
|
|
process.exit(fails ? 1 : 0);
|