From c344decea4755322583fccdb6f917196f987c51f Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 18 Feb 2026 17:58:52 +0530 Subject: [PATCH] fix(ui): persist VST plugin selection across page reloads - Use FTUESave(true) instead of TrackSaveAssignments() to properly persist VST assignments to the profile file - Always call VSTLoad() when modal opens if VST not already loaded, removing unreliable hasVstAssignment() check - Pass correct trackIndex to JKSessionPluginModal for multi-track support - Add trackIndex prop to track data in useMixerHelper for VST operations Co-Authored-By: Claude Opus 4.5 --- .../src/components/client/JKSessionMyTrack.js | 3 +- .../components/client/JKSessionPluginModal.js | 43 +++++++++++++++---- jam-ui/src/hooks/useMixerHelper.js | 16 ++++++- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/jam-ui/src/components/client/JKSessionMyTrack.js b/jam-ui/src/components/client/JKSessionMyTrack.js index 86fc3477b..01a59ee0c 100644 --- a/jam-ui/src/components/client/JKSessionMyTrack.js +++ b/jam-ui/src/components/client/JKSessionMyTrack.js @@ -21,6 +21,7 @@ import pluginIcon from '../../assets/img/client/plugin.svg'; const JKSessionMyTrack = ({ track, + trackIndex = 0, // 0-based index for native client VST operations mixers, hasMixer, name, @@ -314,7 +315,7 @@ const JKSessionMyTrack = ({ setShowPluginModal(false)} - trackNumber={ASSIGNMENT.TRACK1} + trackNumber={trackIndex + 1} /> )} diff --git a/jam-ui/src/components/client/JKSessionPluginModal.js b/jam-ui/src/components/client/JKSessionPluginModal.js index 1e3ee478e..ee6d0d569 100644 --- a/jam-ui/src/components/client/JKSessionPluginModal.js +++ b/jam-ui/src/components/client/JKSessionPluginModal.js @@ -10,7 +10,6 @@ import { Label, Input, Spinner, - Alert, } from 'reactstrap'; import { useJamClient } from '../../context/JamClientContext'; import JKSessionPluginFoldersModal from './JKSessionPluginFoldersModal'; @@ -34,18 +33,34 @@ const JKSessionPluginModal = ({ isOpen, toggle, trackNumber = 1 }) => { const loadAvailablePlugins = async () => { try { setIsLoading(true); + + // Load the audio profile configuration first to restore saved VST assignments + const profileName = await jamClient.LastUsedProfileName(); + if (profileName) { + await jamClient.FTUELoadAudioConfiguration(profileName); + } + + // Always try to load VST assignments from profile after loading profile config + const isVstLoaded = await jamClient.IsVstLoaded(); + if (!isVstLoaded) { + await jamClient.VSTLoad(); + // Give the native client time to load the VST + await new Promise(resolve => setTimeout(resolve, 500)); + } + const pluginsResponse = await jamClient.VSTListVsts(); const plugins = pluginsResponse?.vsts || []; setAvailablePlugins(plugins); - console.log('loadAvailablePlugins Available plugins:', plugins); // Load current track assignments const assignmentsResponse = await jamClient.VSTListTrackAssignments(); - console.log('loadAvailablePlugins Current track assignments:', assignmentsResponse); const assignments = assignmentsResponse?.vsts || []; - const currentAssignment = assignments.find(assignment => assignment.track === trackNumber - 1); + + const targetTrackIndex = trackNumber - 1; + const currentAssignment = assignments.find(assignment => assignment.track === targetTrackIndex); + if (currentAssignment) { - setSelectedPlugin(currentAssignment.id || currentAssignment.file || ''); + setSelectedPlugin(currentAssignment.file || ''); } else { setSelectedPlugin(''); } @@ -60,7 +75,6 @@ const JKSessionPluginModal = ({ isOpen, toggle, trackNumber = 1 }) => { const handlePluginChange = async (event) => { const pluginFile = event.target.value; - setSelectedPlugin(pluginFile); const selectedPluginObj = availablePlugins.find(plugin => @@ -69,8 +83,22 @@ const JKSessionPluginModal = ({ isOpen, toggle, trackNumber = 1 }) => { if (pluginFile && selectedPluginObj) { try { - console.log('handlePluginChange Selected plugin object:', selectedPluginObj, 'for track number: ', trackNumber - 1); + // Establish profile context first + const profileName = await jamClient.LastUsedProfileName(); + if (profileName) { + await jamClient.FTUELoadAudioConfiguration(profileName); + } + + // Set the VST assignment await jamClient.VSTSetTrackAssignment(selectedPluginObj, trackNumber - 1); + + // Save the entire profile to persist VST assignment + // FTUESave(true) persists the profile to disk + try { + await jamClient.FTUESave(true); + } catch (saveError) { + console.warn('FTUESave failed:', saveError); + } } catch (error) { console.error('Failed to assign VST plugin:', error); } @@ -90,7 +118,6 @@ const JKSessionPluginModal = ({ isOpen, toggle, trackNumber = 1 }) => { setLoadingText('scanning'); setIsLoading(true); await jamClient.VSTScan("window.ConfigureTracksStore.onVstScanComplete"); - // Callback after scan completes setIsLoading(false); setLoadingText(''); loadAvailablePlugins(); diff --git a/jam-ui/src/hooks/useMixerHelper.js b/jam-ui/src/hooks/useMixerHelper.js index 210302128..652dff432 100644 --- a/jam-ui/src/hooks/useMixerHelper.js +++ b/jam-ui/src/hooks/useMixerHelper.js @@ -524,15 +524,25 @@ const useMixerHelper = () => { const name = participant.user.name; // Get VST track assignments + // First ensure VST is loaded from persistent storage (needed after page reload) let vstTrackAssignments = { vsts: [] }; try { + const isVstLoaded = jamClient.IsVstLoaded(); + const hasVstAssignment = jamClient.hasVstAssignment(); + if (hasVstAssignment && !isVstLoaded) { + // Load VST from persistent storage - this is async but we proceed anyway + // The UI will update on next render cycle after VST loads + jamClient.VSTLoad(); + } const assignments = jamClient.VSTListTrackAssignments(); vstTrackAssignments = assignments || { vsts: [] }; } catch (error) { console.warn("Failed to get VST track assignments:", error); } - for (const track of participant.tracks || []) { + const participantTracks = participant.tracks || []; + for (let trackIndex = 0; trackIndex < participantTracks.length; trackIndex++) { + const track = participantTracks[trackIndex]; // Get mixers for BOTH modes to support independent Audio Mix (personal) and Session Mix (master) controls const masterMixerData = findMixerForTrack(participant.client_id, track, true, MIX_MODES.MASTER); const personalMixerData = findMixerForTrack(participant.client_id, track, true, MIX_MODES.PERSONAL); @@ -543,8 +553,9 @@ const useMixerHelper = () => { const trackName = name; // Check if this track has a VST plugin assigned + // Native client uses 0-based track indices for VST assignments const hasVst = vstTrackAssignments.vsts && Array.isArray(vstTrackAssignments.vsts) - ? vstTrackAssignments.vsts.some(vst => vst.track === track.client_track_id) + ? vstTrackAssignments.vsts.some(vst => vst.track === trackIndex) : false; tracks.push({ @@ -552,6 +563,7 @@ const useMixerHelper = () => { ...track, hasVst }, + trackIndex, // 0-based index for native client VST operations mixerFinder: [participant.client_id, track, true], mixers: mixerData, // Store both master and personal mixers for independent control