From 5a49d2012b5f968c81d7dfa80b808498e0b0cf30 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Mon, 27 Jul 2026 11:02:45 +0700 Subject: [PATCH] feat: sustain (CC64), modulation (CC1), pitch bend for MIDI keyboard - Add controllerChange forwarder for all CC messages (cmd 0xB) - Add pitchBend method to SonicSF + MIDI handler (cmd 0xE) - CCs routed to _synthInstance.controllerChange(ch, cc, val) - Pitch bend routed to _synthInstance.pitchBend(ch, 14bit-val) --- app/static/js/app.jsx | 59 +++++++++++++++-------- app/static/js/services/soundfontPlayer.js | 7 +++ 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index e543b08..2315a7a 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -6753,25 +6753,26 @@ const App = () => { const pitch = msg.data[1]; const velocity = msg.data[2]; if (cmd === 0x9 && velocity > 0) { - lastMidiNoteRef.current = { pitch, velocity, startTime: performance.now(), length: 0 }; - setLastMidiNote({ pitch, velocity, length: 0, time: Date.now() }); - activeMidiPitchesRef.current.add(pitch); - setActiveMidiPitches(new Set(activeMidiPitchesRef.current)); - { if (window.SonicSF) { - 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); - } 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); - } - } - } } - } else if (cmd === 0x8 || (cmd === 0x9 && velocity === 0)) { + lastMidiNoteRef.current = { pitch, velocity, startTime: performance.now(), length: 0 }; + setLastMidiNote({ pitch, velocity, length: 0, time: Date.now() }); + activeMidiPitchesRef.current.add(pitch); + setActiveMidiPitches(new Set(activeMidiPitchesRef.current)); + { if (window.SonicSF) { + 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); + } 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); + } + } + } } + } else if (cmd === 0x8 || (cmd === 0x9 && velocity === 0)) { activeMidiPitchesRef.current.delete(pitch); setActiveMidiPitches(new Set(activeMidiPitchesRef.current)); const current = lastMidiNoteRef.current; @@ -6780,6 +6781,26 @@ const App = () => { lastMidiNoteRef.current = { ...current, length: lenSec }; setLastMidiNote(prev => prev && prev.pitch === pitch ? { ...prev, length: lenSec, time: Date.now() } : prev); } + } + } + + // ── Sustain (CC64), Modulation (CC1), Pitch Bend ── + const midiCh = msg.data[0] & 0x0F; + if (cmd === 0xB) { + // Controller Change: forward all CCs to SpessaSynth + const cc = msg.data[1]; + const val = msg.data[2]; + if (window.SonicSF && window.SonicSF.controllerChange) { + window.SonicSF.controllerChange(midiCh, cc, val); + } + } else if (cmd === 0xE) { + // Pitch Bend: 14-bit value (LSB + MSB) + const lsb = msg.data[1]; + const msb = msg.data[2]; + const bendVal = (msb << 7) | lsb; + if (window.SonicSF && window.SonicSF.pitchBend) { + window.SonicSF.pitchBend(midiCh, bendVal); + } } // Forward to active MIDI recorders diff --git a/app/static/js/services/soundfontPlayer.js b/app/static/js/services/soundfontPlayer.js index dd7feb6..8f51be7 100644 --- a/app/static/js/services/soundfontPlayer.js +++ b/app/static/js/services/soundfontPlayer.js @@ -154,6 +154,13 @@ return { ..._channels[channel] }; }, + pitchBend: function (channel, value) { + if (channel < 0 || channel > 15) return; + if (_initialized && _synthInstance) { + try { _synthInstance.pitchBend(channel, value); } catch (e) {} + } + }, + playNote: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) { if (_initialized && _synthInstance) { this._playNoteSpessa(note, velocity, durationMs, startTime, program, channel, synthEngine);