Skip to content

Commit 80db04c

Browse files
committed
fix: refine crossfade logic and improve cue progress detection
1 parent 4e3d737 commit 80db04c

File tree

3 files changed

+60
-59
lines changed

3 files changed

+60
-59
lines changed

src/lib.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ pub fn parse_cue_number(text: &str) -> Option<f32> {
103103
.and_then(|s| s.parse::<f32>().ok())
104104
}
105105

106+
pub fn parse_cue_progress(text: &str) -> Option<f32> {
107+
// Expected format: "... 75%" or "... 100%"
108+
// Text ends with '%' usually.
109+
// Let's split by whitespace, take the last element, remove '%', parse as f32, div by 100.
110+
if let Some(last) = text.split_whitespace().last() {
111+
if last.ends_with('%') {
112+
let num_str = &last[..last.len() - 1]; // remove %
113+
if let Ok(pct) = num_str.parse::<f32>() {
114+
return Some(pct / 100.0);
115+
}
116+
}
117+
}
118+
None
119+
}
120+
106121
// Actually, looking at the original code in osc.rs:276:
107122
// if current_state == Inactive { intended = Go } else { /* do nothing to intended */ }
108123
// So if we return Option<CrossfadeState>, we can signify "change" vs "no change".
@@ -117,7 +132,10 @@ pub fn resolve_crossfade_direction(
117132
} else if new_cue < old_cue {
118133
Some(CrossfadeState::GoBack)
119134
} else if current_state == CrossfadeState::Inactive {
120-
Some(CrossfadeState::Go)
135+
// Was: Some(CrossfadeState::Go)
136+
// Changing to None to prevent false 'Go' detection when EOS re-broadcasts active cue
137+
// during a GoBack or other transitions.
138+
None
121139
} else {
122140
None
123141
}

src/midi.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub struct Midi {
3939
pub needs_sync: bool,
4040
pub blink_state: bool,
4141
pub activity_log: Vec<crate::ActivityEvent>,
42-
pub intended_dir: CrossfadeState,
42+
pub intended_dir: Option<CrossfadeState>,
4343
pub last_applied_dir: CrossfadeState,
4444
pub device_fader_values: std::collections::HashMap<(String, usize), f32>,
4545
pub log_sender: tokio::sync::mpsc::Sender<crate::LogEntry>,
@@ -73,7 +73,7 @@ impl Midi {
7373
needs_sync: true,
7474
activity_log: Vec::new(),
7575
blink_state: false,
76-
intended_dir: CrossfadeState::Inactive,
76+
intended_dir: None,
7777
last_applied_dir: CrossfadeState::Inactive,
7878
device_fader_values: std::collections::HashMap::new(),
7979
log_sender,

src/osc.rs

Lines changed: 39 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -273,48 +273,7 @@ impl OscServer {
273273
}
274274

275275
// INDEPENDENT CHECK: Cue Events (Update State even if mapped)
276-
if msg.addr == "/eos/out/event/cue/1/0/stop" {
277-
let mut m = midi.lock().unwrap();
278-
if m.crossfade_state == CrossfadeState::Go
279-
|| m.crossfade_state == CrossfadeState::GoBack
280-
{
281-
m.crossfade_state = CrossfadeState::Pause;
282-
let profiles_to_process: Vec<(String, crate::controller::ControllerProfile)> =
283-
m.device_profiles
284-
.iter()
285-
.map(|(k, v)| (k.clone(), v.clone()))
286-
.collect();
287-
for (d_name, p) in profiles_to_process {
288-
if let Some((note, chan, _)) =
289-
p.get_midi_output_for_action(crate::controller::LogicalAction::Stop)
290-
{
291-
m.enqueue(&d_name, vec![0x90 | chan, note, 127]);
292-
}
293-
}
294-
}
295-
} else if msg.addr == "/eos/out/event/cue/1/0/resume" {
296-
let mut m = midi.lock().unwrap();
297-
if m.crossfade_state == CrossfadeState::Pause {
298-
m.crossfade_state = CrossfadeState::Go;
299-
let profiles_to_process: Vec<(String, crate::controller::ControllerProfile)> =
300-
m.device_profiles
301-
.iter()
302-
.map(|(k, v)| (k.clone(), v.clone()))
303-
.collect();
304-
for (d_name, p) in profiles_to_process {
305-
if let Some((s_note, s_chan, _)) =
306-
p.get_midi_output_for_action(crate::controller::LogicalAction::Stop)
307-
{
308-
m.enqueue(&d_name, vec![0x90 | s_chan, s_note, 0]);
309-
}
310-
if let Some((g_note, g_chan, _)) =
311-
p.get_midi_output_for_action(crate::controller::LogicalAction::Go)
312-
{
313-
m.enqueue(&d_name, vec![0x90 | g_chan, g_note, 127]);
314-
}
315-
}
316-
}
317-
} else if msg.addr == "/eos/out/active/cue/text" {
276+
if msg.addr == "/eos/out/active/cue/text" {
318277
if let Some(OscType::String(text)) = msg.args.first() {
319278
// Extract the cue number part
320279
if let Some(new_cue_num) = crate::parse_cue_number(text) {
@@ -327,10 +286,32 @@ impl OscServer {
327286
new_cue_num,
328287
current_state,
329288
) {
330-
m.intended_dir = direction;
289+
m.intended_dir = Some(direction);
290+
291+
// IMMEDIATE TRIGGER CHECK
292+
// If the text contains progress (e.g. "7%"), we should try to apply state NOW
293+
// because the float packet might have been lost or arrived earlier (when dir was None).
294+
if let Some(p) = crate::parse_cue_progress(text) {
295+
if p < 1.0 {
296+
let last = m.last_applied_dir;
297+
let current = m.crossfade_state;
298+
299+
if direction != last {
300+
m.set_crossfade_state(direction, false);
301+
m.last_applied_dir = direction;
302+
} else if current == CrossfadeState::Inactive {
303+
m.set_crossfade_state(direction, false);
304+
m.last_applied_dir = direction;
305+
}
306+
}
307+
}
308+
} else {
309+
// no log
331310
}
332311

333312
m.current_cue = Some(new_cue_num);
313+
} else {
314+
// no log
334315
}
335316
}
336317
} else if msg.addr == "/eos/out/ping" {
@@ -380,22 +361,24 @@ impl OscServer {
380361

381362
if *progress < 1.0 {
382363
// Activate the intended direction if we are not already in it (or if overriding something other than Pause)
383-
let intended = m.intended_dir;
384-
let last = m.last_applied_dir;
385-
let current = m.crossfade_state;
386-
387-
// Only apply change if direction is new OR if we are Inactive
388-
// Respect Pause: If Paused, do not switch unless the intended direction actually changed (e.g. Go -> Back)
389-
if intended != last {
390-
m.set_crossfade_state(intended, false);
391-
m.last_applied_dir = intended;
392-
} else if current == CrossfadeState::Inactive {
393-
// Starting a fade from Inactive
394-
m.set_crossfade_state(intended, false);
395-
m.last_applied_dir = intended;
364+
if let Some(intended) = m.intended_dir {
365+
let last = m.last_applied_dir;
366+
let current = m.crossfade_state;
367+
368+
// Only apply change if direction is new OR if we are Inactive
369+
// Respect Pause: If Paused, do not switch unless the intended direction actually changed (e.g. Go -> Back)
370+
if intended != last {
371+
m.set_crossfade_state(intended, false);
372+
m.last_applied_dir = intended;
373+
} else if current == CrossfadeState::Inactive {
374+
// Starting a fade from Inactive
375+
m.set_crossfade_state(intended, false);
376+
m.last_applied_dir = intended;
377+
}
396378
}
397379
} else {
398380
m.set_crossfade_state(CrossfadeState::Inactive, false);
381+
m.intended_dir = None;
399382
}
400383
}
401384
}

0 commit comments

Comments
 (0)