T7: hardware MIDI keyboard route qua UnifiedMidiRouter (3 nguon 1 router)

This commit is contained in:
2026-08-11 15:57:12 +07:00
parent 9dcc37e037
commit 9fc5498999
3 changed files with 84 additions and 10 deletions
+26 -3
View File
@@ -15738,6 +15738,9 @@ const App = () => {
var allTracks = activeTracksRef.current || []; var allTracks = activeTracksRef.current || [];
var arSubs = subTabsRef && subTabsRef.current ? subTabsRef.current.filter(function(s) { return s.type === 'PIANO_ROLL' && s.isArmed; }) : []; var arSubs = subTabsRef && subTabsRef.current ? subTabsRef.current.filter(function(s) { return s.type === 'PIANO_ROLL' && s.isArmed; }) : [];
var armedTracks = allTracks.filter(function(t) { return t.isArmed; }); var armedTracks = allTracks.filter(function(t) { return t.isArmed; });
// T7: UnifiedMidiRouter gom targets, dispatch 1 lan sau 2 vong.
var hwRouter = _getMidiRouter();
var hwTargets = [];
// Piano Roll arming has priority: route to each armed sub-tab's parent track // Piano Roll arming has priority: route to each armed sub-tab's parent track
if (arSubs.length > 0) { if (arSubs.length > 0) {
arSubs.forEach(function(as) { arSubs.forEach(function(as) {
@@ -15753,7 +15756,9 @@ const App = () => {
try { try {
heldMidiNotesRef.current[as.trackId] = (heldMidiNotesRef.current[as.trackId] || 0) + 1; heldMidiNotesRef.current[as.trackId] = (heldMidiNotesRef.current[as.trackId] || 0) + 1;
} catch (err) { } } catch (err) { }
if (isStandaloneSf() && isSfTrackEngine(asSe) && !shouldRouteCarla(asSe)) { if (hwRouter && asTrk && asTrk.id) {
hwTargets.push({ trackId: asTrk.id, ch: asCh, engine: new window.TrackInstrument(asTrk.id, _makeCtx(asCh, asProg, asSe, null)) });
} else if (isStandaloneSf() && isSfTrackEngine(asSe) && !shouldRouteCarla(asSe)) {
if (!_routeNoteOn(asTrk && asTrk.id, _makeCtx(asCh, asProg, asSe, null), pitch, scaledVel / 127, 60000, 'HARDWARE_KEYBOARD')) { if (!_routeNoteOn(asTrk && asTrk.id, _makeCtx(asCh, asProg, asSe, null), pitch, scaledVel / 127, 60000, 'HARDWARE_KEYBOARD')) {
window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, asProg, null, asCh, asSe); window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, asProg, null, asCh, asSe);
} }
@@ -15780,7 +15785,9 @@ const App = () => {
try { try {
heldMidiNotesRef.current[at.id] = (heldMidiNotesRef.current[at.id] || 0) + 1; heldMidiNotesRef.current[at.id] = (heldMidiNotesRef.current[at.id] || 0) + 1;
} catch (err) { } } catch (err) { }
if (isStandaloneSf() && isSfTrackEngine(atSe) && !shouldRouteCarla(atSe)) { if (hwRouter && at && at.id) {
hwTargets.push({ trackId: at.id, ch: atCh, engine: new window.TrackInstrument(at.id, _makeCtx(atCh, atProg, atSe, atDest)) });
} else if (isStandaloneSf() && isSfTrackEngine(atSe) && !shouldRouteCarla(atSe)) {
if (!_routeNoteOn(at && at.id, _makeCtx(atCh, atProg, atSe, atDest), pitch, scaledVel / 127, 60000, 'HARDWARE_KEYBOARD')) { if (!_routeNoteOn(at && at.id, _makeCtx(atCh, atProg, atSe, atDest), pitch, scaledVel / 127, 60000, 'HARDWARE_KEYBOARD')) {
window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, atProg, atDest, atCh, atSe); window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, atProg, atDest, atCh, atSe);
} }
@@ -15795,6 +15802,11 @@ const App = () => {
try { window.SonicCarlaMidi.noteOn(atCh, pitch, scaledVel); } catch (e) {} try { window.SonicCarlaMidi.noteOn(atCh, pitch, scaledVel); } catch (e) {}
} }
}); });
// T7: dispatch 1 lan qua router - keybed/scheduler/hardware
// chung 1 voice tracker (chong stuck note khi doi engine).
if (hwRouter && hwTargets.length) {
hwRouter.handleHardwareKeyboardMessage(msg.data, hwTargets, []);
}
} }
} else if (cmd === 0x8 || (cmd === 0x9 && rawVel === 0)) { } else if (cmd === 0x8 || (cmd === 0x9 && rawVel === 0)) {
activeMidiPitchesRef.current.delete(pitch); activeMidiPitchesRef.current.delete(pitch);
@@ -15809,13 +15821,19 @@ const App = () => {
// when ARM is toggled off while a key is held // when ARM is toggled off while a key is held
if (window.SonicSF && window.SonicSF.stopNote) { if (window.SonicSF && window.SonicSF.stopNote) {
var stopTracks = activeTracksRef.current || []; var stopTracks = activeTracksRef.current || [];
var hwRouterOff = _getMidiRouter();
var hwStopTargets = [];
stopTracks.forEach(function(st) { stopTracks.forEach(function(st) {
// Only stop channels that actually carry this track's notes // Only stop channels that actually carry this track's notes
// an index-based fallback could hit another track's dedicated // an index-based fallback could hit another track's dedicated
// channel and kill its sound. // channel and kill its sound.
if (!st.synth_engine && st.midiChannel === undefined) return; if (!st.synth_engine && st.midiChannel === undefined) return;
var stCh = assignTrackMidiChannel(st, stopTracks); var stCh = assignTrackMidiChannel(st, stopTracks);
if (isStandaloneSf()) _routeNoteOff(st.id, { ch: stCh }, pitch, 'HARDWARE_KEYBOARD'); if (hwRouterOff && st.id) {
hwStopTargets.push({ trackId: st.id, ch: stCh });
} else if (isStandaloneSf()) {
_routeNoteOff(st.id, { ch: stCh }, pitch, 'HARDWARE_KEYBOARD');
}
window.SonicSF.stopNote(stCh, pitch); window.SonicSF.stopNote(stCh, pitch);
// MIDI Keyboard Carla bridge: note-off khi th phím tránh // MIDI Keyboard Carla bridge: note-off khi th phím tránh
// kt âm VSTi (ging keybed o). // kt âm VSTi (ging keybed o).
@@ -15833,6 +15851,11 @@ const App = () => {
} }
} catch (err) { } } catch (err) { }
}); });
// T7: note-off dispatch qua router - engine voice giu tu note-on;
// SonicSF.stopNote o tren van giu belt-and-suspenders.
if (hwRouterOff && hwStopTargets.length) {
hwRouterOff.handleHardwareKeyboardMessage(msg.data, [], hwStopTargets);
}
} }
} }
+12 -7
View File
@@ -817,27 +817,32 @@ const scaledVel=Math.min(127,Math.max(1,Math.round(rawVel)));if(cmd===0x9&&rawVe
// đây chỉ keybed ảo gọi __ensureMasteringRouting). // đây chỉ keybed ảo gọi __ensureMasteringRouting).
try{if(window.__ensureMasteringRouting)window.__ensureMasteringRouting();}catch(er){}// Route MIDI input to ALL armed tracks on their dedicated channels try{if(window.__ensureMasteringRouting)window.__ensureMasteringRouting();}catch(er){}// Route MIDI input to ALL armed tracks on their dedicated channels
// Do NOT use raw MIDI hardware channel (msg.data[0] & 0x0F) // Do NOT use raw MIDI hardware channel (msg.data[0] & 0x0F)
if(window.SonicSF){var allTracks=activeTracksRef.current||[];var arSubs=subTabsRef&&subTabsRef.current?subTabsRef.current.filter(function(s){return s.type==='PIANO_ROLL'&&s.isArmed;}):[];var armedTracks=allTracks.filter(function(t){return t.isArmed;});// Piano Roll arming has priority: route to each armed sub-tab's parent track if(window.SonicSF){var allTracks=activeTracksRef.current||[];var arSubs=subTabsRef&&subTabsRef.current?subTabsRef.current.filter(function(s){return s.type==='PIANO_ROLL'&&s.isArmed;}):[];var armedTracks=allTracks.filter(function(t){return t.isArmed;});// T7: UnifiedMidiRouter gom targets, dispatch 1 lan sau 2 vong.
var hwRouter=_getMidiRouter();var hwTargets=[];// Piano Roll arming has priority: route to each armed sub-tab's parent track
if(arSubs.length>0){arSubs.forEach(function(as){var asTrk=allTracks.find(function(t){return t.id===as.trackId;});var asCh=asTrk?assignTrackMidiChannel(asTrk,allTracks):0;var asProg=as.instrumentProgram;var asSe=as.synth_engine;if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(as.trackId,scaledVel);}// Giữ VU theo trường độ: tăng counter note đang giữ if(arSubs.length>0){arSubs.forEach(function(as){var asTrk=allTracks.find(function(t){return t.id===as.trackId;});var asCh=asTrk?assignTrackMidiChannel(asTrk,allTracks):0;var asProg=as.instrumentProgram;var asSe=as.synth_engine;if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(as.trackId,scaledVel);}// Giữ VU theo trường độ: tăng counter note đang giữ
// (tick giữ peak cho tới khi note-off) // (tick giữ peak cho tới khi note-off)
try{heldMidiNotesRef.current[as.trackId]=(heldMidiNotesRef.current[as.trackId]||0)+1;}catch(err){}if(isStandaloneSf()&&isSfTrackEngine(asSe)&&!shouldRouteCarla(asSe)){if(!_routeNoteOn(asTrk&&asTrk.id,_makeCtx(asCh,asProg,asSe,null),pitch,scaledVel/127,60000,'HARDWARE_KEYBOARD')){window.SonicSF.playNote(pitch,scaledVel,60000,undefined,asProg,null,asCh,asSe);}}else{window.SonicSF.playNote(pitch,scaledVel,60000,undefined,asProg,null,asCh,asSe);}// MIDI Keyboard → Carla bridge (piano roll sub-tab ARM): try{heldMidiNotesRef.current[as.trackId]=(heldMidiNotesRef.current[as.trackId]||0)+1;}catch(err){}if(hwRouter&&asTrk&&asTrk.id){hwTargets.push({trackId:asTrk.id,ch:asCh,engine:new window.TrackInstrument(asTrk.id,_makeCtx(asCh,asProg,asSe,null))});}else if(isStandaloneSf()&&isSfTrackEngine(asSe)&&!shouldRouteCarla(asSe)){if(!_routeNoteOn(asTrk&&asTrk.id,_makeCtx(asCh,asProg,asSe,null),pitch,scaledVel/127,60000,'HARDWARE_KEYBOARD')){window.SonicSF.playNote(pitch,scaledVel,60000,undefined,asProg,null,asCh,asSe);}}else{window.SonicSF.playNote(pitch,scaledVel,60000,undefined,asProg,null,asCh,asSe);}// MIDI Keyboard → Carla bridge (piano roll sub-tab ARM):
// track VSTi của sub-tab phát VSTi realtime giống keybed. // track VSTi của sub-tab phát VSTi realtime giống keybed.
if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoute(asSe,true)){try{window.SonicCarlaMidi.noteOn(asCh,pitch,scaledVel);}catch(e){}}});}// Route to ALL armed tracks (not just the first one) if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoute(asSe,true)){try{window.SonicCarlaMidi.noteOn(asCh,pitch,scaledVel);}catch(e){}}});}// Route to ALL armed tracks (not just the first one)
armedTracks.forEach(function(at){var atCh=assignTrackMidiChannel(at,allTracks);var atProg=at.instrumentProgram;var atSe=at.synth_engine;var atDest=activeTrackNodesRef.current[at.id]?.gainNode||null;if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(at.id,scaledVel);}// Giữ VU theo trường độ: tăng counter note đang giữ armedTracks.forEach(function(at){var atCh=assignTrackMidiChannel(at,allTracks);var atProg=at.instrumentProgram;var atSe=at.synth_engine;var atDest=activeTrackNodesRef.current[at.id]?.gainNode||null;if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(at.id,scaledVel);}// Giữ VU theo trường độ: tăng counter note đang giữ
try{heldMidiNotesRef.current[at.id]=(heldMidiNotesRef.current[at.id]||0)+1;}catch(err){}if(isStandaloneSf()&&isSfTrackEngine(atSe)&&!shouldRouteCarla(atSe)){if(!_routeNoteOn(at&&at.id,_makeCtx(atCh,atProg,atSe,atDest),pitch,scaledVel/127,60000,'HARDWARE_KEYBOARD')){window.SonicSF.playNote(pitch,scaledVel,60000,undefined,atProg,atDest,atCh,atSe);}}else{window.SonicSF.playNote(pitch,scaledVel,60000,undefined,atProg,atDest,atCh,atSe);}// MIDI Keyboard → Carla bridge (track VSTi + ARM + Carla try{heldMidiNotesRef.current[at.id]=(heldMidiNotesRef.current[at.id]||0)+1;}catch(err){}if(hwRouter&&at&&at.id){hwTargets.push({trackId:at.id,ch:atCh,engine:new window.TrackInstrument(at.id,_makeCtx(atCh,atProg,atSe,atDest))});}else if(isStandaloneSf()&&isSfTrackEngine(atSe)&&!shouldRouteCarla(atSe)){if(!_routeNoteOn(at&&at.id,_makeCtx(atCh,atProg,atSe,atDest),pitch,scaledVel/127,60000,'HARDWARE_KEYBOARD')){window.SonicSF.playNote(pitch,scaledVel,60000,undefined,atProg,atDest,atCh,atSe);}}else{window.SonicSF.playNote(pitch,scaledVel,60000,undefined,atProg,atDest,atCh,atSe);}// MIDI Keyboard → Carla bridge (track VSTi + ARM + Carla
// local): phát VSTi realtime — giống hệt keybed ảo. Trước // local): phát VSTi realtime — giống hệt keybed ảo. Trước
// đây chỉ keybed gọi SonicCarlaMidi; keyboard hardware bị // đây chỉ keybed gọi SonicCarlaMidi; keyboard hardware bị
// bỏ sót → track VSTi không kêu khi bấm đàn thật. // bỏ sót → track VSTi không kêu khi bấm đàn thật.
if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoute(atSe,at.isArmed)){try{window.SonicCarlaMidi.noteOn(atCh,pitch,scaledVel);}catch(e){}}});}}else if(cmd===0x8||cmd===0x9&&rawVel===0){activeMidiPitchesRef.current.delete(pitch);setActiveMidiPitches(new Set(activeMidiPitchesRef.current));const current=lastMidiNoteRef.current;if(current&&current.pitch===pitch){const lenSec=(performance.now()-current.startTime)/1000;lastMidiNoteRef.current={...current,length:lenSec};setLastMidiNote(prev=>prev&&prev.pitch===pitch?{...prev,length:lenSec,time:Date.now()}:prev);}// Stop the note on ALL tracks (not just armed) to prevent stuck notes if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoute(atSe,at.isArmed)){try{window.SonicCarlaMidi.noteOn(atCh,pitch,scaledVel);}catch(e){}}});// T7: dispatch 1 lan qua router - keybed/scheduler/hardware
// chung 1 voice tracker (chong stuck note khi doi engine).
if(hwRouter&&hwTargets.length){hwRouter.handleHardwareKeyboardMessage(msg.data,hwTargets,[]);}}}else if(cmd===0x8||cmd===0x9&&rawVel===0){activeMidiPitchesRef.current.delete(pitch);setActiveMidiPitches(new Set(activeMidiPitchesRef.current));const current=lastMidiNoteRef.current;if(current&&current.pitch===pitch){const lenSec=(performance.now()-current.startTime)/1000;lastMidiNoteRef.current={...current,length:lenSec};setLastMidiNote(prev=>prev&&prev.pitch===pitch?{...prev,length:lenSec,time:Date.now()}:prev);}// Stop the note on ALL tracks (not just armed) to prevent stuck notes
// when ARM is toggled off while a key is held // when ARM is toggled off while a key is held
if(window.SonicSF&&window.SonicSF.stopNote){var stopTracks=activeTracksRef.current||[];stopTracks.forEach(function(st){// Only stop channels that actually carry this track's notes — if(window.SonicSF&&window.SonicSF.stopNote){var stopTracks=activeTracksRef.current||[];var hwRouterOff=_getMidiRouter();var hwStopTargets=[];stopTracks.forEach(function(st){// Only stop channels that actually carry this track's notes —
// an index-based fallback could hit another track's dedicated // an index-based fallback could hit another track's dedicated
// channel and kill its sound. // channel and kill its sound.
if(!st.synth_engine&&st.midiChannel===undefined)return;var stCh=assignTrackMidiChannel(st,stopTracks);if(isStandaloneSf())_routeNoteOff(st.id,{ch:stCh},pitch,'HARDWARE_KEYBOARD');window.SonicSF.stopNote(stCh,pitch);// MIDI Keyboard → Carla bridge: note-off khi thả phím — tránh if(!st.synth_engine&&st.midiChannel===undefined)return;var stCh=assignTrackMidiChannel(st,stopTracks);if(hwRouterOff&&st.id){hwStopTargets.push({trackId:st.id,ch:stCh});}else if(isStandaloneSf()){_routeNoteOff(st.id,{ch:stCh},pitch,'HARDWARE_KEYBOARD');}window.SonicSF.stopNote(stCh,pitch);// MIDI Keyboard → Carla bridge: note-off khi thả phím — tránh
// kẹt âm VSTi (giống keybed ảo). // kẹt âm VSTi (giống keybed ảo).
if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoute(st.synth_engine,st.isArmed)){try{window.SonicCarlaMidi.noteOff(stCh,pitch);}catch(e){}}// Giảm counter note đang giữ — hết note → VU được phép decay/tắt if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoute(st.synth_engine,st.isArmed)){try{window.SonicCarlaMidi.noteOff(stCh,pitch);}catch(e){}}// Giảm counter note đang giữ — hết note → VU được phép decay/tắt
// (tick sẽ thấy counter = 0 và không còn giữ peak nữa) // (tick sẽ thấy counter = 0 và không còn giữ peak nữa)
try{if(heldMidiNotesRef.current[st.id]!==undefined){heldMidiNotesRef.current[st.id]=Math.max(0,heldMidiNotesRef.current[st.id]-1);if(heldMidiNotesRef.current[st.id]===0){delete heldMidiNotesRef.current[st.id];}}}catch(err){}});}}// ── Sustain (CC64), Modulation (CC1), Pitch Bend ── try{if(heldMidiNotesRef.current[st.id]!==undefined){heldMidiNotesRef.current[st.id]=Math.max(0,heldMidiNotesRef.current[st.id]-1);if(heldMidiNotesRef.current[st.id]===0){delete heldMidiNotesRef.current[st.id];}}}catch(err){}});// T7: note-off dispatch qua router - engine voice giu tu note-on;
// SonicSF.stopNote o tren van giu belt-and-suspenders.
if(hwRouterOff&&hwStopTargets.length){hwRouterOff.handleHardwareKeyboardMessage(msg.data,[],hwStopTargets);}}}// ── Sustain (CC64), Modulation (CC1), Pitch Bend ──
const midiCh=msg.data[0]&0x0F;if(cmd===0xB){// Controller Change: forward to ALL armed tracks' dedicated channels const midiCh=msg.data[0]&0x0F;if(cmd===0xB){// Controller Change: forward to ALL armed tracks' dedicated channels
const cc=msg.data[1];const val=msg.data[2];if(window.SonicSF&&window.SonicSF.controllerChange){var ccTracks=activeTracksRef.current||[];var hasArmed=ccTracks.some(function(t){return t.isArmed;});if(hasArmed){ccTracks.forEach(function(ct){if(!ct.isArmed)return;var ctCh=assignTrackMidiChannel(ct,ccTracks);window.SonicSF.controllerChange(ctCh,cc,val);});}else{window.SonicSF.controllerChange(midiCh,cc,val);}}}else if(cmd===0xE){// Pitch Bend: forward to ALL armed tracks' dedicated channels const cc=msg.data[1];const val=msg.data[2];if(window.SonicSF&&window.SonicSF.controllerChange){var ccTracks=activeTracksRef.current||[];var hasArmed=ccTracks.some(function(t){return t.isArmed;});if(hasArmed){ccTracks.forEach(function(ct){if(!ct.isArmed)return;var ctCh=assignTrackMidiChannel(ct,ccTracks);window.SonicSF.controllerChange(ctCh,cc,val);});}else{window.SonicSF.controllerChange(midiCh,cc,val);}}}else if(cmd===0xE){// Pitch Bend: forward to ALL armed tracks' dedicated channels
const lsb=msg.data[1];const msb=msg.data[2];const bendVal=msb<<7|lsb;if(window.SonicSF&&window.SonicSF.pitchBend){var pbTracks=activeTracksRef.current||[];var hasArmedPB=pbTracks.some(function(t){return t.isArmed;});if(hasArmedPB){pbTracks.forEach(function(pt){if(!pt.isArmed)return;var ptCh=assignTrackMidiChannel(pt,pbTracks);window.SonicSF.pitchBend(ptCh,bendVal);});}else{window.SonicSF.pitchBend(midiCh,bendVal);}}}// Forward to active MIDI recorders const lsb=msg.data[1];const msb=msg.data[2];const bendVal=msb<<7|lsb;if(window.SonicSF&&window.SonicSF.pitchBend){var pbTracks=activeTracksRef.current||[];var hasArmedPB=pbTracks.some(function(t){return t.isArmed;});if(hasArmedPB){pbTracks.forEach(function(pt){if(!pt.isArmed)return;var ptCh=assignTrackMidiChannel(pt,pbTracks);window.SonicSF.pitchBend(ptCh,bendVal);});}else{window.SonicSF.pitchBend(midiCh,bendVal);}}}// Forward to active MIDI recorders
@@ -99,6 +99,52 @@ window.SonicUnifiedMidiRouter = window.SonicUnifiedMidiRouter || {};
return false; // CC / unknown return false; // CC / unknown
}; };
// Hardware MIDI keyboard message → dispatch tới targets đã gom sẵn (armed
// tracks). app.jsx thu thập noteOnTargets [{trackId, ch, engine}] ở note-on
// và noteOffTargets [{trackId, ch}] ở note-off, gọi 1 lần mỗi message.
// parse giống handler cũ (cmd 4-bit, pitch clamp, velocity 1-127) rồi đẩy
// qua dispatchMidiEvent → 3 nguồn (keybed/scheduler/hardware) chung router.
UnifiedMidiRouter.prototype.handleHardwareKeyboardMessage = function (msg, noteOnTargets, noteOffTargets) {
var cmd = Number(msg[0]) >> 4;
var pitch = clampPitch(msg[1]);
var rawVel = Number(msg[2]) || 0;
var velocity = Math.min(127, Math.max(1, Math.round(rawVel)));
var noteOn = (cmd === 0x9 && rawVel > 0);
var noteOff = (cmd === 0x8 || (cmd === 0x9 && rawVel === 0));
if (noteOn && noteOnTargets) {
noteOnTargets.forEach(function (t) {
if (!t || t.trackId === undefined || t.trackId === null) return;
if (t.engine) this.registerEngine(t.trackId, t.engine);
this.dispatchMidiEvent({
trackId: t.trackId,
command: COMMAND_NOTE_ON,
channel: t.ch,
pitch: pitch,
velocity: velocity / 127,
durationMs: 60000,
source: 'HARDWARE_KEYBOARD'
});
}, this);
}
if (noteOff && noteOffTargets) {
noteOffTargets.forEach(function (t) {
if (!t || t.trackId === undefined || t.trackId === null) return;
this.dispatchMidiEvent({
trackId: t.trackId,
command: COMMAND_NOTE_OFF,
channel: t.ch,
pitch: pitch,
source: 'HARDWARE_KEYBOARD'
});
}, this);
}
console.log('[MidiRouter] hardware ->', { noteOn: noteOn, noteOff: noteOff, pitch: pitch, velocity: velocity, targets: (noteOnTargets ? noteOnTargets.length : 0) + '/' + (noteOffTargets ? noteOffTargets.length : 0) });
return { noteOn: noteOn, noteOff: noteOff, pitch: pitch, velocity: velocity };
};
// All Notes Off: quét voice tracker, gửi note-off cho engine sở hữu từng // All Notes Off: quét voice tracker, gửi note-off cho engine sở hữu từng
// voice, xóa tracker. Gọi khi dừng playback / đổi instrument giữa chừng. // voice, xóa tracker. Gọi khi dừng playback / đổi instrument giữa chừng.
UnifiedMidiRouter.prototype.panicAllNotesOff = function () { UnifiedMidiRouter.prototype.panicAllNotesOff = function () {