From e6439c2d9d8de2f6c8c4005bb6437c7717090b39 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Mon, 27 Jul 2026 11:06:35 +0700 Subject: [PATCH] fix: MIDI NoteOff stops note immediately (60000ms duration + stopNote) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NoteOn: durationMs=60000 (1 phút, giữ cho đến khi NoteOff) - NoteOff: stopNote(ch, pitch) gọi synthInstance.noteOff() ngay - MIDI channel (msg.data[0] & 0x0F) truyền đúng vào playNote - CC (sustain/modulation) + pitch bend forward qua SpessaSynth --- app/static/js/app.jsx | 25 ++++++++++++++--------- app/static/js/services/soundfontPlayer.js | 7 +++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 2315a7a..d030321 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -6761,26 +6761,31 @@ const App = () => { const ch = msg.data[0] & 0x0F; const arSub = subTabsRef && subTabsRef.current && activeTabRef && subTabsRef.current.find(s => s.id === activeTabRef.current && s.type === 'PIANO_ROLL' && s.isArmed); if (arSub) { - window.SonicSF.playNote(pitch, velocity, 500, undefined, arSub.instrumentProgram, null, undefined, arSub.synth_engine); + window.SonicSF.playNote(pitch, velocity, 60000, undefined, arSub.instrumentProgram, null, ch, arSub.synth_engine); } else { const armedTrack = activeTracksRef.current ? activeTracksRef.current.find(t => t.isArmed) : null; if (armedTrack) { const prog = armedTrack.instrumentProgram; const se = armedTrack.synth_engine; const dest = activeTrackNodesRef.current[armedTrack.id]?.gainNode || null; - window.SonicSF.playNote(pitch, velocity, 500, undefined, prog, dest, undefined, se); + window.SonicSF.playNote(pitch, velocity, 60000, undefined, prog, dest, ch, se); } } } } } else if (cmd === 0x8 || (cmd === 0x9 && velocity === 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); - } + 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 immediately via SpessaSynth + if (window.SonicSF && window.SonicSF.stopNote) { + const ch = msg.data[0] & 0x0F; + window.SonicSF.stopNote(ch, pitch); + } } } diff --git a/app/static/js/services/soundfontPlayer.js b/app/static/js/services/soundfontPlayer.js index 8f51be7..11c7799 100644 --- a/app/static/js/services/soundfontPlayer.js +++ b/app/static/js/services/soundfontPlayer.js @@ -161,6 +161,13 @@ } }, + stopNote: function (channel, pitch) { + if (channel < 0 || channel > 15) return; + if (_initialized && _synthInstance) { + try { _synthInstance.noteOff(channel, pitch); } catch (e) {} + } + }, + playNote: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) { if (_initialized && _synthInstance) { this._playNoteSpessa(note, velocity, durationMs, startTime, program, channel, synthEngine);