// 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');