ee30ca097e
UnifiedMidiEvent (trackId, channel, command, pitch clamp 0-127, velocity normalize float->int 1-127, sourceType, timestampNs) + UnifiedMidiRouter: engineRegistry Map<trackId, engine> (route theo track), dispatchMidiEvent, activeVoiceTracker key trackId_channel_pitch (dem note-on/off dung - het stuck notes, note-off cuoi moi tat voice), panicAllNotesOff. Chua noi vao app (T5-T7). Test node: pitch clamp, velocity normalize, tracker, panic clear - xanh.
58 lines
3.1 KiB
JavaScript
58 lines
3.1 KiB
JavaScript
// T4 test: node tests/unified_midi_router.test.js
|
|
// Router chưa nối app — test độc lập qua window stub.
|
|
'use strict';
|
|
global.window = {};
|
|
require('../app/static/js/services/unifiedMidiRouter.js');
|
|
const R = global.window.SonicUnifiedMidiRouter;
|
|
const assert = require('assert');
|
|
|
|
const calls = [];
|
|
function makeEngine() {
|
|
return {
|
|
playNote(pitch, vel, dur) { calls.push(['on', pitch, vel, dur]); },
|
|
noteOff(pitch) { calls.push(['off', pitch]); }
|
|
};
|
|
}
|
|
|
|
// 1) pitch clamp
|
|
const r = new R.UnifiedMidiRouter();
|
|
const eng = makeEngine();
|
|
r.registerEngine('t1', eng);
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 0, command: R.COMMAND_NOTE_ON, pitch: 200, velocity: 1, sourceType: 'TEST' });
|
|
assert.deepStrictEqual(calls[0], ['on', 127, 127, 500], 'pitch clamp 200->127');
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 0, command: R.COMMAND_NOTE_OFF, pitch: -3 });
|
|
assert.strictEqual(calls.length, 1, 'note-off khong co voice -> khong goi engine (pitch clamp khong crash)');
|
|
|
|
// 2) velocity normalize float->int (100/127 -> 100, 0.5 -> 64)
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 1, command: R.COMMAND_NOTE_ON, pitch: 60, velocity: 100 / 127 });
|
|
assert.deepStrictEqual(calls[1], ['on', 60, 100, 500], 'velocity 100/127 -> 100');
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 1, command: R.COMMAND_NOTE_ON, pitch: 61, velocity: 0.5 });
|
|
assert.deepStrictEqual(calls[2], ['on', 61, 64, 500], 'velocity 0.5 -> 64');
|
|
|
|
// 3) tracker đếm đúng: 2 note-on trùng key -> 1 playNote; off cuối mới tắt
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 2, command: R.COMMAND_NOTE_ON, pitch: 64, velocity: 100 });
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 2, command: R.COMMAND_NOTE_ON, pitch: 64, velocity: 100 });
|
|
assert.strictEqual(r.activeVoiceCount('t1', 2, 64), 2, '2 note-on count=2');
|
|
const onCalls = calls.filter(c => c[0] === 'on' && c[1] === 64).length;
|
|
assert.strictEqual(onCalls, 1, 'trung key chi play 1 lan');
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 2, command: R.COMMAND_NOTE_OFF, pitch: 64 });
|
|
assert.strictEqual(r.activeVoiceCount('t1', 2, 64), 1, '1 off -> count=1, chua noteOff');
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 2, command: R.COMMAND_NOTE_OFF, pitch: 64 });
|
|
assert.strictEqual(r.activeVoiceCount('t1', 2, 64), 0, 'off cuoi -> count=0');
|
|
assert.deepStrictEqual(calls[calls.length - 1], ['off', 64], 'off cuoi moi noteOff');
|
|
|
|
// 4) 2 track cùng channel+pitch đếm riêng (không lệch voice)
|
|
const eng2 = makeEngine();
|
|
r.registerEngine('t2', eng2);
|
|
r.dispatchMidiEvent({ trackId: 't2', channel: 2, command: R.COMMAND_NOTE_ON, pitch: 64, velocity: 90 });
|
|
assert.strictEqual(r.activeVoiceCount('t2', 2, 64), 1);
|
|
assert.strictEqual(r.activeVoiceCount('t1', 2, 64), 0);
|
|
|
|
// 5) panic clear: note đang giữ -> noteOff + tracker rỗng
|
|
r.dispatchMidiEvent({ trackId: 't1', channel: 3, command: R.COMMAND_NOTE_ON, pitch: 72, velocity: 80 });
|
|
r.panicAllNotesOff();
|
|
assert.strictEqual(r.activeVoiceCount('t1', 3, 72), 0, 'panic xoa tracker');
|
|
assert.deepStrictEqual(calls[calls.length - 1], ['off', 72], 'panic gui noteOff');
|
|
|
|
console.log('unified_midi_router: ALL TESTS PASSED');
|