T7: hardware MIDI keyboard route qua UnifiedMidiRouter (3 nguon 1 router)
This commit is contained in:
+26
-3
@@ -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,7 +15802,12 @@ 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);
|
||||||
setActiveMidiPitches(new Set(activeMidiPitchesRef.current));
|
setActiveMidiPitches(new Set(activeMidiPitchesRef.current));
|
||||||
@@ -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
|
||||||
// kẹt âm VSTi (giống keybed ảo).
|
// kẹt âm VSTi (giống 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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&¤t.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&¤t.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 () {
|
||||||
|
|||||||
Reference in New Issue
Block a user