382 lines
12 KiB
JavaScript
382 lines
12 KiB
JavaScript
import { createSlice, createSelector } from '@reduxjs/toolkit';
|
|
|
|
const initialState = {
|
|
// Core mixer collections
|
|
chatMixer: null,
|
|
broadcastMixer: null,
|
|
recordingMixer: null,
|
|
|
|
// Mixer arrays by type (populated by groupMixersByType)
|
|
recordingTrackMixers: [],
|
|
backingTrackMixers: [],
|
|
jamTrackMixers: [],
|
|
metronomeTrackMixers: [],
|
|
adhocTrackMixers: [],
|
|
|
|
// Master and personal mixer arrays (source data from jamClient)
|
|
masterMixers: [],
|
|
personalMixers: [],
|
|
|
|
// Lookup tables (computed by organizeMixers reducer)
|
|
allMixers: {}, // Format: { 'M123': mixer, 'P456': mixer }
|
|
mixersByResourceId: {}, // Format: { 'rid': { master: mixer, personal: mixer } }
|
|
mixersByTrackId: {}, // Format: { 'trackId': { master: mixer, personal: mixer } }
|
|
|
|
// Simulated/derived mixers for category controls
|
|
simulatedMusicCategoryMixers: {
|
|
PERSONAL: null,
|
|
MASTER: null
|
|
},
|
|
simulatedChatCategoryMixers: {
|
|
PERSONAL: null,
|
|
MASTER: null
|
|
},
|
|
|
|
// Metronome state
|
|
metronome: null, // Current metronome mixer object
|
|
metronomeSettings: {
|
|
tempo: 120,
|
|
sound: "Beep",
|
|
cricket: false
|
|
},
|
|
|
|
// Media summary (open/closed states for media types)
|
|
// This tracks what media is currently open in the session
|
|
mediaSummary: {
|
|
mediaOpen: false,
|
|
backingTrackOpen: false,
|
|
jamTrackOpen: false,
|
|
recordingOpen: false,
|
|
metronomeOpen: false,
|
|
isOpener: false,
|
|
userNeedsMediaControls: false,
|
|
jamTrack: null
|
|
},
|
|
|
|
// Peer state (for managing missing audio users)
|
|
noAudioUsers: {},
|
|
clientsWithAudioOverride: {},
|
|
missingMixerPeers: {},
|
|
checkingMissingPeers: {},
|
|
|
|
// Ready state (indicates mixers have been organized and are ready to use)
|
|
isReady: false,
|
|
|
|
// Loading/errors
|
|
loading: false,
|
|
error: null
|
|
};
|
|
|
|
export const mixersSlice = createSlice({
|
|
name: 'mixers',
|
|
initialState,
|
|
reducers: {
|
|
// Initialize from jamClient.SessionGetAllControlState
|
|
setMasterMixers: (state, action) => {
|
|
state.masterMixers = action.payload;
|
|
},
|
|
|
|
setPersonalMixers: (state, action) => {
|
|
state.personalMixers = action.payload;
|
|
},
|
|
|
|
// Organize mixers - builds lookup tables (matches useMixerHelper organizeMixers logic)
|
|
// This should be called after master/personal mixers are set
|
|
organizeMixers: (state) => {
|
|
const newAllMixers = {};
|
|
const newMixersByResourceId = {};
|
|
const newMixersByTrackId = {};
|
|
|
|
// Process master mixers
|
|
for (const masterMixer of state.masterMixers) {
|
|
newAllMixers['M' + masterMixer.id] = masterMixer;
|
|
|
|
const mixerPair = {};
|
|
newMixersByResourceId[masterMixer.rid] = mixerPair;
|
|
newMixersByTrackId[masterMixer.id] = mixerPair;
|
|
mixerPair.master = masterMixer;
|
|
}
|
|
|
|
// Process personal mixers
|
|
for (const personalMixer of state.personalMixers) {
|
|
newAllMixers['P' + personalMixer.id] = personalMixer;
|
|
|
|
let mixerPair = newMixersByResourceId[personalMixer.rid];
|
|
if (!mixerPair) {
|
|
// Create new pair if no master exists (e.g., MonitorGroup)
|
|
mixerPair = {};
|
|
newMixersByResourceId[personalMixer.rid] = mixerPair;
|
|
}
|
|
newMixersByTrackId[personalMixer.id] = mixerPair;
|
|
mixerPair.personal = personalMixer;
|
|
}
|
|
|
|
// Update state
|
|
state.allMixers = { ...state.allMixers, ...newAllMixers };
|
|
state.mixersByResourceId = { ...state.mixersByResourceId, ...newMixersByResourceId };
|
|
state.mixersByTrackId = { ...state.mixersByTrackId, ...newMixersByTrackId };
|
|
state.isReady = true;
|
|
},
|
|
|
|
// Group mixers by type - categorizes mixers into recording, backing, jam, metronome, adhoc
|
|
// This complex logic will be implemented in useMixerHelper Redux version
|
|
// For now, just accept the categorized arrays
|
|
setRecordingTrackMixers: (state, action) => {
|
|
state.recordingTrackMixers = action.payload;
|
|
},
|
|
|
|
setBackingTrackMixers: (state, action) => {
|
|
state.backingTrackMixers = action.payload;
|
|
},
|
|
|
|
setJamTrackMixers: (state, action) => {
|
|
state.jamTrackMixers = action.payload;
|
|
},
|
|
|
|
setMetronomeTrackMixers: (state, action) => {
|
|
state.metronomeTrackMixers = action.payload;
|
|
},
|
|
|
|
setAdhocTrackMixers: (state, action) => {
|
|
state.adhocTrackMixers = action.payload;
|
|
},
|
|
|
|
// Update individual mixer (for real-time updates from jamClient)
|
|
updateMixer: (state, action) => {
|
|
const { mixerId, mode, updates } = action.payload;
|
|
const key = (mode ? 'M' : 'P') + mixerId;
|
|
|
|
if (state.allMixers[key]) {
|
|
state.allMixers[key] = { ...state.allMixers[key], ...updates };
|
|
|
|
// Also update in master/personal arrays
|
|
if (mode) {
|
|
const index = state.masterMixers.findIndex(m => m.id === mixerId);
|
|
if (index !== -1) {
|
|
state.masterMixers[index] = { ...state.masterMixers[index], ...updates };
|
|
}
|
|
} else {
|
|
const index = state.personalMixers.findIndex(m => m.id === mixerId);
|
|
if (index !== -1) {
|
|
state.personalMixers[index] = { ...state.personalMixers[index], ...updates };
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Metronome
|
|
setMetronome: (state, action) => {
|
|
state.metronome = action.payload;
|
|
state.mediaSummary.metronomeOpen = !!action.payload;
|
|
},
|
|
|
|
setMetronomeSettings: (state, action) => {
|
|
state.metronomeSettings = { ...state.metronomeSettings, ...action.payload };
|
|
},
|
|
|
|
// Media summary updates
|
|
updateMediaSummary: (state, action) => {
|
|
state.mediaSummary = { ...state.mediaSummary, ...action.payload };
|
|
},
|
|
|
|
setMediaSummary: (state, action) => {
|
|
state.mediaSummary = action.payload;
|
|
},
|
|
|
|
// Chat/broadcast mixers
|
|
setChatMixer: (state, action) => {
|
|
state.chatMixer = action.payload;
|
|
},
|
|
|
|
setBroadcastMixer: (state, action) => {
|
|
state.broadcastMixer = action.payload;
|
|
},
|
|
|
|
setRecordingMixer: (state, action) => {
|
|
state.recordingMixer = action.payload;
|
|
},
|
|
|
|
// Simulated mixers (for category controls)
|
|
setSimulatedMusicCategoryMixers: (state, action) => {
|
|
state.simulatedMusicCategoryMixers = action.payload;
|
|
},
|
|
|
|
setSimulatedChatCategoryMixers: (state, action) => {
|
|
state.simulatedChatCategoryMixers = action.payload;
|
|
},
|
|
|
|
// Peer management
|
|
addNoAudioUser: (state, action) => {
|
|
state.noAudioUsers[action.payload] = true;
|
|
},
|
|
|
|
removeNoAudioUser: (state, action) => {
|
|
delete state.noAudioUsers[action.payload];
|
|
},
|
|
|
|
setNoAudioUsers: (state, action) => {
|
|
state.noAudioUsers = action.payload;
|
|
},
|
|
|
|
setClientsWithAudioOverride: (state, action) => {
|
|
state.clientsWithAudioOverride = action.payload;
|
|
},
|
|
|
|
setMissingMixerPeers: (state, action) => {
|
|
state.missingMixerPeers = action.payload;
|
|
},
|
|
|
|
setCheckingMissingPeers: (state, action) => {
|
|
state.checkingMissingPeers = action.payload;
|
|
},
|
|
|
|
// Clear on session end
|
|
clearMixers: (state) => {
|
|
return { ...initialState };
|
|
}
|
|
}
|
|
});
|
|
|
|
export const {
|
|
setMasterMixers,
|
|
setPersonalMixers,
|
|
organizeMixers,
|
|
setRecordingTrackMixers,
|
|
setBackingTrackMixers,
|
|
setJamTrackMixers,
|
|
setMetronomeTrackMixers,
|
|
setAdhocTrackMixers,
|
|
updateMixer,
|
|
setMetronome,
|
|
setMetronomeSettings,
|
|
updateMediaSummary,
|
|
setMediaSummary,
|
|
setChatMixer,
|
|
setBroadcastMixer,
|
|
setRecordingMixer,
|
|
setSimulatedMusicCategoryMixers,
|
|
setSimulatedChatCategoryMixers,
|
|
addNoAudioUser,
|
|
removeNoAudioUser,
|
|
setNoAudioUsers,
|
|
setClientsWithAudioOverride,
|
|
setMissingMixerPeers,
|
|
setCheckingMissingPeers,
|
|
clearMixers
|
|
} = mixersSlice.actions;
|
|
|
|
export default mixersSlice.reducer;
|
|
|
|
// Selectors
|
|
export const selectAllMixers = (state) => state.mixers.allMixers;
|
|
export const selectMasterMixers = (state) => state.mixers.masterMixers;
|
|
export const selectPersonalMixers = (state) => state.mixers.personalMixers;
|
|
export const selectMixersByResourceId = (state) => state.mixers.mixersByResourceId;
|
|
export const selectMixersByTrackId = (state) => state.mixers.mixersByTrackId;
|
|
|
|
export const selectChatMixer = (state) => state.mixers.chatMixer;
|
|
export const selectBroadcastMixer = (state) => state.mixers.broadcastMixer;
|
|
export const selectRecordingMixer = (state) => state.mixers.recordingMixer;
|
|
|
|
export const selectMetronome = (state) => state.mixers.metronome;
|
|
export const selectMetronomeSettings = (state) => state.mixers.metronomeSettings;
|
|
export const selectMediaSummary = (state) => state.mixers.mediaSummary;
|
|
|
|
export const selectMixersReady = (state) => state.mixers.isReady;
|
|
|
|
export const selectBackingTrackMixers = (state) => state.mixers.backingTrackMixers;
|
|
export const selectJamTrackMixers = (state) => state.mixers.jamTrackMixers;
|
|
export const selectRecordingTrackMixers = (state) => state.mixers.recordingTrackMixers;
|
|
export const selectMetronomeTrackMixers = (state) => state.mixers.metronomeTrackMixers;
|
|
export const selectAdhocTrackMixers = (state) => state.mixers.adhocTrackMixers;
|
|
|
|
export const selectSimulatedMusicCategoryMixers = (state) => state.mixers.simulatedMusicCategoryMixers;
|
|
export const selectSimulatedChatCategoryMixers = (state) => state.mixers.simulatedChatCategoryMixers;
|
|
|
|
export const selectNoAudioUsers = (state) => state.mixers.noAudioUsers;
|
|
export const selectClientsWithAudioOverride = (state) => state.mixers.clientsWithAudioOverride;
|
|
export const selectMissingMixerPeers = (state) => state.mixers.missingMixerPeers;
|
|
export const selectCheckingMissingPeers = (state) => state.mixers.checkingMissingPeers;
|
|
|
|
// Computed selector for getting a specific mixer by ID and mode
|
|
export const selectMixer = (mixerId, mode) => (state) => {
|
|
const key = (mode ? 'M' : 'P') + mixerId;
|
|
return state.mixers.allMixers[key];
|
|
};
|
|
|
|
// Computed selector for getting mixer pair by resource ID
|
|
export const selectMixerPairByResourceId = (resourceId) => (state) => {
|
|
return state.mixers.mixersByResourceId[resourceId];
|
|
};
|
|
|
|
// Computed selector for getting mixer pair by track ID
|
|
export const selectMixerPairByTrackId = (trackId) => (state) => {
|
|
return state.mixers.mixersByTrackId[trackId];
|
|
};
|
|
|
|
// Composed memoized selectors for useMixerHelper optimization
|
|
// These group related data that's typically used together
|
|
|
|
export const selectCoreMixerState = createSelector(
|
|
[selectChatMixer, selectBroadcastMixer, selectRecordingMixer],
|
|
(chatMixer, broadcastMixer, recordingMixer) => ({
|
|
chatMixer,
|
|
broadcastMixer,
|
|
recordingMixer
|
|
})
|
|
);
|
|
|
|
export const selectTrackMixerState = createSelector(
|
|
[
|
|
selectRecordingTrackMixers,
|
|
selectBackingTrackMixers,
|
|
selectJamTrackMixers,
|
|
selectMetronomeTrackMixers,
|
|
selectAdhocTrackMixers
|
|
],
|
|
(recordingTrackMixers, backingTrackMixers, jamTrackMixers, metronomeTrackMixers, adhocTrackMixers) => ({
|
|
recordingTrackMixers,
|
|
backingTrackMixers,
|
|
jamTrackMixers,
|
|
metronomeTrackMixers,
|
|
adhocTrackMixers
|
|
})
|
|
);
|
|
|
|
export const selectMixerLookupTables = createSelector(
|
|
[selectAllMixers, selectMixersByResourceId, selectMixersByTrackId],
|
|
(allMixers, mixersByResourceId, mixersByTrackId) => ({
|
|
allMixers,
|
|
mixersByResourceId,
|
|
mixersByTrackId
|
|
})
|
|
);
|
|
|
|
export const selectMasterPersonalMixers = createSelector(
|
|
[selectMasterMixers, selectPersonalMixers],
|
|
(masterMixers, personalMixers) => ({
|
|
masterMixers,
|
|
personalMixers
|
|
})
|
|
);
|
|
|
|
export const selectMixerMetadata = createSelector(
|
|
[selectMetronome, selectMetronomeSettings, selectMediaSummary, selectNoAudioUsers, selectClientsWithAudioOverride, selectMixersReady],
|
|
(metronome, metronomeSettings, mediaSummary, noAudioUsers, clientsWithAudioOverride, isReady) => ({
|
|
metronome,
|
|
metronomeSettings,
|
|
mediaSummary,
|
|
noAudioUsers,
|
|
clientsWithAudioOverride,
|
|
isReady
|
|
})
|
|
);
|
|
|
|
export const selectSimulatedCategoryMixers = createSelector(
|
|
[selectSimulatedMusicCategoryMixers, selectSimulatedChatCategoryMixers],
|
|
(simulatedMusicCategoryMixers, simulatedChatCategoryMixers) => ({
|
|
simulatedMusicCategoryMixers,
|
|
simulatedChatCategoryMixers
|
|
})
|
|
);
|