2323\******************************************************************************/
2424
2525#include " audiomixerboard.h"
26+ #include < chrono>
27+ #include < deque>
28+
29+ namespace
30+ {
31+ // Per-channel MIDI pickup state
32+ struct MidiPickupState
33+ {
34+ std::deque<int > recentFader;
35+ std::deque<int > recentPan;
36+ std::chrono::steady_clock::time_point lastMidiTimeFader;
37+ std::chrono::steady_clock::time_point lastMidiTimePan;
38+ };
39+
40+ std::vector<MidiPickupState> g_midiPickupStates ( MAX_NUM_CHANNELS );
41+ std::vector<bool > g_midiPickupInitialized ( MAX_NUM_CHANNELS, false );
42+ std::vector<bool > g_midiPickupWaitingForPickup ( MAX_NUM_CHANNELS, false );
43+
44+ // Check for inactivity and reset pickup state if needed
45+ static void midiPickupInactivityCheck ( int iChannelIdx,
46+ std::chrono::steady_clock::time_point& lastMidiTime,
47+ std::deque<int >& pickupBuffer,
48+ std::vector<bool >& waitingFlag )
49+ {
50+ auto now = std::chrono::steady_clock::now ();
51+ if ( lastMidiTime.time_since_epoch ().count () > 0 )
52+ {
53+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds> ( now - lastMidiTime ).count ();
54+ if ( elapsed > MIDI_PICKUP_INACTIVITY_TIMEOUT_MS )
55+ {
56+ // Reset pickup state after inactivity
57+ waitingFlag[iChannelIdx] = true ;
58+ pickupBuffer.clear ();
59+ }
60+ }
61+ lastMidiTime = now;
62+ }
63+
64+ // Determine if MIDI value should be applied based on pickup logic
65+ template <typename T>
66+ static bool midiPickupShouldApply ( int midiValue, int currentValue, int tolerance, const std::deque<T>& recentMidiValues )
67+ {
68+ // Accept if within tolerance
69+ if ( std::abs ( midiValue - currentValue ) <= tolerance )
70+ return true ;
71+
72+ // If buffer has at least 2 values, check if we're crossing the current value
73+ // Handles the case where rapid movement causes MIDI values to "skip over" the software value
74+ if ( recentMidiValues.size () >= 2 )
75+ {
76+ int prevMidi = recentMidiValues.back ();
77+ // Check if current and previous MIDI values bracket the software value
78+ if ( ( prevMidi <= currentValue && midiValue >= currentValue ) || ( prevMidi >= currentValue && midiValue <= currentValue ) )
79+ return true ;
80+ }
81+
82+ return false ;
83+ }
84+
85+ // Try to apply MIDI value with pickup logic
86+ template <typename T>
87+ static bool midiPickupTryApply ( int midiValue, int currentValue, int tolerance, std::deque<T>& pickupBuffer, bool waitingForPickup )
88+ {
89+ if ( waitingForPickup )
90+ {
91+ // Create temp buffer with new value to test pickup logic
92+ std::deque<int > tempPickup = pickupBuffer;
93+ if ( tempPickup.size () >= MIDI_PICKUP_HISTORY )
94+ tempPickup.pop_front ();
95+ tempPickup.push_back ( midiValue );
96+
97+ if ( !midiPickupShouldApply ( midiValue, currentValue, tolerance, tempPickup ) )
98+ return true ; // Still waiting for pickup
99+
100+ // Picked up. Stop waiting
101+ waitingForPickup = false ;
102+ }
103+
104+ // Update the pickup buffer
105+ if ( pickupBuffer.size () >= MIDI_PICKUP_HISTORY )
106+ pickupBuffer.pop_front ();
107+ pickupBuffer.push_back ( midiValue );
108+
109+ return waitingForPickup;
110+ }
111+ } // namespace
26112
27113/* *****************************************************************************\
28114* CChanneFader *
@@ -1334,6 +1420,9 @@ void CAudioMixerBoard::ApplyNewConClientList ( CVector<CChannelInfo>& vecChanInf
13341420 }
13351421 Mutex.unlock (); // release mutex
13361422
1423+ // Ensure MIDI state is applied to faders during the connection process
1424+ SetMIDICtrlUsed ( pSettings->bUseMIDIController );
1425+
13371426 // sort the channels according to the selected sorting type
13381427 ChangeFaderOrder ( eChSortType );
13391428
@@ -1348,6 +1437,36 @@ void CAudioMixerBoard::SetFaderLevel ( const int iChannelIdx, const int iValue )
13481437 {
13491438 if ( vecpChanFader[static_cast <size_t > ( iChannelIdx )]->IsVisible () )
13501439 {
1440+ // Check for MIDI pickup mode
1441+ if ( pSettings && pSettings->bMIDIPickupMode && g_midiPickupInitialized[iChannelIdx] )
1442+ {
1443+ auto & midiState = g_midiPickupStates[iChannelIdx];
1444+ midiPickupInactivityCheck ( iChannelIdx,
1445+ midiState.lastMidiTimeFader ,
1446+ midiState.recentFader ,
1447+ g_midiPickupWaitingForPickup );
1448+ }
1449+
1450+ if ( pSettings && pSettings->bMIDIPickupMode )
1451+ {
1452+ // Initialize pickup state on first use
1453+ if ( !g_midiPickupInitialized[iChannelIdx] )
1454+ {
1455+ g_midiPickupInitialized[iChannelIdx] = true ;
1456+ g_midiPickupWaitingForPickup[iChannelIdx] = true ;
1457+ g_midiPickupStates[iChannelIdx].recentFader .clear ();
1458+ }
1459+
1460+ auto & pickup = g_midiPickupStates[iChannelIdx].recentFader ;
1461+ int current = vecpChanFader[static_cast <size_t > ( iChannelIdx )]->GetFaderLevel ();
1462+ bool waiting = g_midiPickupWaitingForPickup[iChannelIdx];
1463+ waiting = midiPickupTryApply ( iValue, current, MIDI_PICKUP_TOLERANCE, pickup, waiting );
1464+ g_midiPickupWaitingForPickup[iChannelIdx] = waiting;
1465+
1466+ if ( waiting )
1467+ return ; // Don't apply the value yet, still waiting for pickup
1468+ }
1469+
13511470 vecpChanFader[static_cast <size_t > ( iChannelIdx )]->SetFaderLevel ( iValue );
13521471 }
13531472 }
@@ -1360,6 +1479,36 @@ void CAudioMixerBoard::SetPanValue ( const int iChannelIdx, const int iValue )
13601479 {
13611480 if ( vecpChanFader[static_cast <size_t > ( iChannelIdx )]->IsVisible () )
13621481 {
1482+ // Check for MIDI pickup mode
1483+ if ( pSettings && pSettings->bMIDIPickupMode && g_midiPickupInitialized[iChannelIdx] )
1484+ {
1485+ auto & midiState = g_midiPickupStates[iChannelIdx];
1486+ midiPickupInactivityCheck ( iChannelIdx,
1487+ midiState.lastMidiTimePan ,
1488+ midiState.recentPan ,
1489+ g_midiPickupWaitingForPickup );
1490+ }
1491+
1492+ if ( pSettings && pSettings->bMIDIPickupMode )
1493+ {
1494+ // Initialize pickup state on first use
1495+ if ( !g_midiPickupInitialized[iChannelIdx] )
1496+ {
1497+ g_midiPickupInitialized[iChannelIdx] = true ;
1498+ g_midiPickupWaitingForPickup[iChannelIdx] = true ;
1499+ g_midiPickupStates[iChannelIdx].recentPan .clear ();
1500+ }
1501+
1502+ auto & pickup = g_midiPickupStates[iChannelIdx].recentPan ;
1503+ int current = vecpChanFader[static_cast <size_t > ( iChannelIdx )]->GetPanValue ();
1504+ bool waiting = g_midiPickupWaitingForPickup[iChannelIdx];
1505+ waiting = midiPickupTryApply ( iValue, current, MIDI_PICKUP_TOLERANCE, pickup, waiting );
1506+ g_midiPickupWaitingForPickup[iChannelIdx] = waiting;
1507+
1508+ if ( waiting )
1509+ return ; // Don't apply the value yet, still waiting for pickup
1510+ }
1511+
13631512 vecpChanFader[static_cast <size_t > ( iChannelIdx )]->SetPanValue ( iValue );
13641513 }
13651514 }
@@ -1403,6 +1552,13 @@ void CAudioMixerBoard::SetAllFaderLevelsToNewClientLevel()
14031552 // update flag to make sure the group values are all set to the
14041553 // same fader level now
14051554 vecpChanFader[i]->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX, true );
1555+
1556+ // Reset MIDI pickup state so pickup mode works with the new value
1557+ if ( g_midiPickupInitialized[i] )
1558+ {
1559+ g_midiPickupWaitingForPickup[i] = true ;
1560+ g_midiPickupStates[i].recentFader .clear ();
1561+ }
14061562 }
14071563 }
14081564}
@@ -1552,6 +1708,13 @@ void CAudioMixerBoard::AutoAdjustAllFaderLevels()
15521708
15531709 // set fader level
15541710 vecpChanFader[i]->SetFaderLevel ( newFaderLevel, true );
1711+
1712+ // Reset MIDI pickup state so pickup mode works with the auto-adjusted value
1713+ if ( g_midiPickupInitialized[i] )
1714+ {
1715+ g_midiPickupWaitingForPickup[i] = true ;
1716+ g_midiPickupStates[i].recentFader .clear ();
1717+ }
15551718 }
15561719 }
15571720 }
@@ -1565,6 +1728,16 @@ void CAudioMixerBoard::SetMIDICtrlUsed ( const bool bMIDICtrlUsed )
15651728 {
15661729 vecpChanFader[i]->SetMIDICtrlUsed ( bMIDICtrlUsed );
15671730 }
1731+
1732+ // Reset MIDI pickup state when toggling MIDI control to
1733+ // ensure pickup mode works correctly when re-enabling MIDI
1734+ for ( size_t i = 0 ; i < MAX_NUM_CHANNELS; i++ )
1735+ {
1736+ g_midiPickupInitialized[i] = false ;
1737+ g_midiPickupWaitingForPickup[i] = false ;
1738+ g_midiPickupStates[i].recentFader .clear ();
1739+ g_midiPickupStates[i].recentPan .clear ();
1740+ }
15681741}
15691742
15701743void CAudioMixerBoard::StoreAllFaderSettings ()
@@ -1601,6 +1774,16 @@ void CAudioMixerBoard::LoadAllFaderSettings()
16011774 vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo );
16021775 vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute );
16031776 vecpChanFader[i]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader!
1777+
1778+ // Reset MIDI pickup state for this channel so pickup mode works with restored values
1779+ // Without this, if MIDI values arrived before settings were loaded, pickup might have
1780+ // already occurred at the wrong value, causing jumps when the controller is moved
1781+ if ( g_midiPickupInitialized[i] )
1782+ {
1783+ g_midiPickupWaitingForPickup[i] = true ;
1784+ g_midiPickupStates[i].recentFader .clear ();
1785+ g_midiPickupStates[i].recentPan .clear ();
1786+ }
16041787 }
16051788 }
16061789}
0 commit comments