@@ -41,6 +41,7 @@ pub struct Midi {
4141 pub activity_log : Vec < crate :: ActivityEvent > ,
4242 pub intended_dir : CrossfadeState ,
4343 pub last_applied_dir : CrossfadeState ,
44+ pub device_fader_values : std:: collections:: HashMap < ( String , usize ) , f32 > ,
4445}
4546
4647impl Midi {
@@ -70,6 +71,7 @@ impl Midi {
7071 blink_state : false ,
7172 intended_dir : CrossfadeState :: Inactive ,
7273 last_applied_dir : CrossfadeState :: Inactive ,
74+ device_fader_values : std:: collections:: HashMap :: new ( ) ,
7375 }
7476 }
7577
@@ -589,70 +591,35 @@ pub async fn execute_mapping(
589591 crate :: controller:: LogicalAction :: FaderMove { index } => {
590592 let mut final_v = 0.0 ;
591593 let mut update = false ;
594+ let mut requires_pickup = false ;
592595
593596 if let crate :: controller:: Trigger :: MidiCc { mode, .. } = & mapping. trigger {
594597 match mode {
595598 crate :: controller:: MidiCcTriggerMode :: Absolute => {
596- // let value = ((d2 as i16) << 7) | (d1 as i16); // Wait, d1/d2 logic for FaderMove assumed 14-bit pitchbend source!
597- // If source is CC (7-bit), d2 is value.
598- // But if source is PitchWheel, d1/d2 is 14-bit.
599- // Logic below was hardcoded for PitchWheel?
600- // "let value = ((d2 as i16) << 7) | (d1 as i16);"
601-
602- // We need to differentiate source trigger type for value extraction!
603- // But execute_mapping just gets d1/d2.
604-
605- // FIX:
606- let f_val = if d1 == 0xB0 || ( d1 & 0xF0 ) == 0xB0 {
607- // Ah d1 passed to execute_mapping is msg[1], d2 is msg[2]
608- // msg[0] is not passed?
609- // Wait, call site:
610- // let (d1, d2) = (msg[1], msg[2]);
611- // execute_mapping(..., d1, d2, ...)
612- // We don't know if it was PitchWheel or CC inside execute_mapping easily without checking trigger type again.
613-
614- ( d2 as f32 ) / 127.0
615- } else {
616- // Assume PitchWheel default 14-bit
617- let value = ( ( d2 as i16 ) << 7 ) | ( d1 as i16 ) ;
618- ( value as f32 ) / 16383.0
619- } ;
620- final_v = f_val;
599+ // Inherited logic: assumes 14-bit if not caught by the broken B0 check (which is always false here)
600+ // effectively: value = (d2 << 7) | d1.
601+ // For CC: d2 is value, d1 is CC number.
602+ let value = ( ( d2 as i16 ) << 7 ) | ( d1 as i16 ) ;
603+ final_v = ( value as f32 ) / 16383.0 ;
621604 update = true ;
605+ requires_pickup = true ;
622606 }
623607 crate :: controller:: MidiCcTriggerMode :: RelativeStandard => {
624- // < 64 = +d2, >= 64 = -(d2-64) ?
625- // Usually 1-63 is +, 65-127 is - (Two's complement 7-bit signed?)
626- // Or 65 = -1, 127 = -63?
627- // Let's implement generic Signed 7-bit:
628- // 0-63 positive, 64-127 negative (offset 128? or just >64 is neg?)
629-
630- // Relative 1 (Behringer Manual says 1..7, 65..71)
631- // Actually let's assume RelativeBehringer covers the specifics.
632- // Standard often 65 = +1? No 1=+1.
633-
634- // Let's implement logic:
635608 let delta: i32 = if d2 < 64 {
636609 d2 as i32
637610 } else {
638611 ( d2 as i32 ) - 128
639- } ; // 7-bit signed
640-
612+ } ;
641613 let m = midi. lock ( ) . unwrap ( ) ;
642614 if * index < m. fader_levels . len ( ) {
643615 let current = m. fader_levels [ * index] ;
644- let new_val = ( current + ( delta as f32 * 0.01 ) ) . clamp ( 0.0 , 1.0 ) ; // 1% per tick?
616+ let new_val = ( current + ( delta as f32 * 0.01 ) ) . clamp ( 0.0 , 1.0 ) ;
645617 final_v = new_val;
646618 update = true ;
619+ requires_pickup = false ;
647620 }
648621 }
649622 crate :: controller:: MidiCcTriggerMode :: RelativeBehringer => {
650- // CW: 1..7 (speed) -> +1..+7
651- // CCW: 127..121? -> -1..-7 ?
652- // Current issue: User reports "drops immediately".
653- // This implies our previous logic (65 = -1) was being fed 127, resulting in -63.
654- // So device sends 127 for -1.
655- // We will use standard 7-bit signed logic (High values = small negatives).
656623 let delta: i32 = if d2 >= 64 {
657624 ( d2 as i32 ) - 128
658625 } else {
@@ -661,24 +628,61 @@ pub async fn execute_mapping(
661628 let m = midi. lock ( ) . unwrap ( ) ;
662629 if * index < m. fader_levels . len ( ) {
663630 let current = m. fader_levels [ * index] ;
664- let new_val = ( current + ( delta as f32 * 0.005 ) ) . clamp ( 0.0 , 1.0 ) ; // 0.5% per tick base
631+ let new_val = ( current + ( delta as f32 * 0.005 ) ) . clamp ( 0.0 , 1.0 ) ;
665632 final_v = new_val;
666633 update = true ;
634+ requires_pickup = false ;
667635 }
668636 }
669637 }
670638 } else {
671- // Not MIDI CC Trigger (PitchWheel or Note)
672- // Original logic for PitchWheel/Fader
673- // Assuming PitchWheel for FaderMove usually
639+ // PitchWheel or others: Treat as 14-bit Absolute
674640 let value = ( ( d2 as i16 ) << 7 ) | ( d1 as i16 ) ;
675641 final_v = ( value as f32 ) / 16383.0 ;
676642 update = true ;
643+ requires_pickup = true ;
677644 }
678645
679646 if update {
680647 let mut m = midi. lock ( ) . unwrap ( ) ;
681- if * index < m. fader_levels . len ( ) {
648+
649+ let allow_update = if requires_pickup && * index < m. fader_levels . len ( ) {
650+ let map_key = ( device_name. to_string ( ) , mapping_idx) ;
651+ let last_phys_val = m. device_fader_values . get ( & map_key) . cloned ( ) ;
652+ let eos_val = m. fader_levels [ * index] ;
653+
654+ // Always update Physical State so we can track movement
655+ m. device_fader_values . insert ( map_key, final_v) ;
656+
657+ if let Some ( prev) = last_phys_val {
658+ let tolerance = 0.05 ; // 5% latch window
659+ // If we were previously synced, allow small drifts
660+ let was_latched = ( prev - eos_val) . abs ( ) < tolerance;
661+
662+ // Check crossover
663+ let crossed_up = prev <= eos_val && final_v >= eos_val;
664+ let crossed_down = prev >= eos_val && final_v <= eos_val;
665+
666+ // User requirement: Wait until value crossed EOS value
667+ if was_latched || crossed_up || crossed_down {
668+ true
669+ } else {
670+ false // Blocking until crossover
671+ }
672+ } else {
673+ // No history: Block until we establish crossover context?
674+ // Or Check proximity?
675+ if ( final_v - eos_val) . abs ( ) < 0.1 {
676+ true
677+ } else {
678+ false
679+ }
680+ }
681+ } else {
682+ true
683+ } ;
684+
685+ if allow_update && * index < m. fader_levels . len ( ) {
682686 m. fader_levels [ * index] = final_v;
683687 cached_fader_value = Some ( final_v) ;
684688 m. activity_log . push ( crate :: ActivityEvent {
@@ -726,10 +730,9 @@ pub async fn execute_mapping(
726730 if let Some ( val) = cached_fader_value {
727731 Some ( rosc:: OscType :: Float ( val) )
728732 } else {
729- // Fallback to recalculating for absolute/pitchwheel if somehow not cached (shouldn't happen for FaderMove)
730- let value = ( ( d2 as i16 ) << 7 ) | ( d1 as i16 ) ;
731- let f_val = ( value as f32 ) / 16383.0 ;
732- Some ( rosc:: OscType :: Float ( f_val) )
733+ // If no cached value, it means the update was blocked by pickup logic or didn't run.
734+ // In either case, we should NOT send an OSC update.
735+ None
733736 }
734737 } else {
735738 None
0 commit comments