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,33 @@ 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, midiState.lastMidiTimeFader , midiState.recentFader , g_midiPickupWaitingForPickup );
1445+ }
1446+
1447+ if ( pSettings && pSettings->bMIDIPickupMode )
1448+ {
1449+ // Initialize pickup state on first use
1450+ if ( !g_midiPickupInitialized[iChannelIdx] )
1451+ {
1452+ g_midiPickupInitialized[iChannelIdx] = true ;
1453+ g_midiPickupWaitingForPickup[iChannelIdx] = true ;
1454+ g_midiPickupStates[iChannelIdx].recentFader .clear ();
1455+ }
1456+
1457+ auto & pickup = g_midiPickupStates[iChannelIdx].recentFader ;
1458+ int current = vecpChanFader[static_cast <size_t > ( iChannelIdx )]->GetFaderLevel ();
1459+ bool waiting = g_midiPickupWaitingForPickup[iChannelIdx];
1460+ waiting = midiPickupTryApply ( iValue, current, MIDI_PICKUP_TOLERANCE, pickup, waiting );
1461+ g_midiPickupWaitingForPickup[iChannelIdx] = waiting;
1462+
1463+ if ( waiting )
1464+ return ; // Don't apply the value yet, still waiting for pickup
1465+ }
1466+
13511467 vecpChanFader[static_cast <size_t > ( iChannelIdx )]->SetFaderLevel ( iValue );
13521468 }
13531469 }
@@ -1360,6 +1476,33 @@ void CAudioMixerBoard::SetPanValue ( const int iChannelIdx, const int iValue )
13601476 {
13611477 if ( vecpChanFader[static_cast <size_t > ( iChannelIdx )]->IsVisible () )
13621478 {
1479+ // Check for MIDI pickup mode
1480+ if ( pSettings && pSettings->bMIDIPickupMode && g_midiPickupInitialized[iChannelIdx] )
1481+ {
1482+ auto & midiState = g_midiPickupStates[iChannelIdx];
1483+ midiPickupInactivityCheck ( iChannelIdx, midiState.lastMidiTimePan , midiState.recentPan , g_midiPickupWaitingForPickup );
1484+ }
1485+
1486+ if ( pSettings && pSettings->bMIDIPickupMode )
1487+ {
1488+ // Initialize pickup state on first use
1489+ if ( !g_midiPickupInitialized[iChannelIdx] )
1490+ {
1491+ g_midiPickupInitialized[iChannelIdx] = true ;
1492+ g_midiPickupWaitingForPickup[iChannelIdx] = true ;
1493+ g_midiPickupStates[iChannelIdx].recentPan .clear ();
1494+ }
1495+
1496+ auto & pickup = g_midiPickupStates[iChannelIdx].recentPan ;
1497+ int current = vecpChanFader[static_cast <size_t > ( iChannelIdx )]->GetPanValue ();
1498+ bool waiting = g_midiPickupWaitingForPickup[iChannelIdx];
1499+ waiting = midiPickupTryApply ( iValue, current, MIDI_PICKUP_TOLERANCE, pickup, waiting );
1500+ g_midiPickupWaitingForPickup[iChannelIdx] = waiting;
1501+
1502+ if ( waiting )
1503+ return ; // Don't apply the value yet, still waiting for pickup
1504+ }
1505+
13631506 vecpChanFader[static_cast <size_t > ( iChannelIdx )]->SetPanValue ( iValue );
13641507 }
13651508 }
@@ -1403,6 +1546,13 @@ void CAudioMixerBoard::SetAllFaderLevelsToNewClientLevel()
14031546 // update flag to make sure the group values are all set to the
14041547 // same fader level now
14051548 vecpChanFader[i]->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX, true );
1549+
1550+ // Reset MIDI pickup state so pickup mode works with the new value
1551+ if ( g_midiPickupInitialized[i] )
1552+ {
1553+ g_midiPickupWaitingForPickup[i] = true ;
1554+ g_midiPickupStates[i].recentFader .clear ();
1555+ }
14061556 }
14071557 }
14081558}
@@ -1552,6 +1702,13 @@ void CAudioMixerBoard::AutoAdjustAllFaderLevels()
15521702
15531703 // set fader level
15541704 vecpChanFader[i]->SetFaderLevel ( newFaderLevel, true );
1705+
1706+ // Reset MIDI pickup state so pickup mode works with the auto-adjusted value
1707+ if ( g_midiPickupInitialized[i] )
1708+ {
1709+ g_midiPickupWaitingForPickup[i] = true ;
1710+ g_midiPickupStates[i].recentFader .clear ();
1711+ }
15551712 }
15561713 }
15571714 }
@@ -1565,6 +1722,16 @@ void CAudioMixerBoard::SetMIDICtrlUsed ( const bool bMIDICtrlUsed )
15651722 {
15661723 vecpChanFader[i]->SetMIDICtrlUsed ( bMIDICtrlUsed );
15671724 }
1725+
1726+ // Reset MIDI pickup state when toggling MIDI control to
1727+ // ensure pickup mode works correctly when re-enabling MIDI
1728+ for ( size_t i = 0 ; i < MAX_NUM_CHANNELS; i++ )
1729+ {
1730+ g_midiPickupInitialized[i] = false ;
1731+ g_midiPickupWaitingForPickup[i] = false ;
1732+ g_midiPickupStates[i].recentFader .clear ();
1733+ g_midiPickupStates[i].recentPan .clear ();
1734+ }
15681735}
15691736
15701737void CAudioMixerBoard::StoreAllFaderSettings ()
@@ -1601,6 +1768,16 @@ void CAudioMixerBoard::LoadAllFaderSettings()
16011768 vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo );
16021769 vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute );
16031770 vecpChanFader[i]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader!
1771+
1772+ // Reset MIDI pickup state for this channel so pickup mode works with restored values
1773+ // Without this, if MIDI values arrived before settings were loaded, pickup might have
1774+ // already occurred at the wrong value, causing jumps when the controller is moved
1775+ if ( g_midiPickupInitialized[i] )
1776+ {
1777+ g_midiPickupWaitingForPickup[i] = true ;
1778+ g_midiPickupStates[i].recentFader .clear ();
1779+ g_midiPickupStates[i].recentPan .clear ();
1780+ }
16041781 }
16051782 }
16061783}
0 commit comments