use crate::*; pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; } pub trait HasTrackScroll: HasTracks { fn track_scroll (&self) -> usize; } pub trait HasMidiClip { fn clip (&self) -> Option>>; } pub trait HasClipsSize { fn clips_size (&self) -> &Measure; } pub trait HasClock: Send + Sync { fn clock (&self) -> &Clock; fn clock_mut (&mut self) -> &mut Clock; } pub trait HasDevices { fn devices (&self) -> &Vec; fn devices_mut (&mut self) -> &mut Vec; } pub trait HasSelection: Has { fn selection (&self) -> &Selection { self.get() } fn selection_mut (&mut self) -> &mut Selection { self.get_mut() } } pub trait HasWidth { const MIN_WIDTH: usize; /// Increment track width. fn width_inc (&mut self); /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. fn width_dec (&mut self); } pub trait HasMidiBuffers { fn note_buf_mut (&mut self) -> &mut Vec; fn midi_buf_mut (&mut self) -> &mut Vec>>; } pub trait HasSequencer { fn sequencer (&self) -> &Sequencer; fn sequencer_mut (&mut self) -> &mut Sequencer; } pub trait HasScene: Has> + Send + Sync { fn scene (&self) -> Option<&Scene> { Has::>::get(self).as_ref() } fn scene_mut (&mut self) -> &mut Option { Has::>::get_mut(self) } } pub trait HasScenes: Has> + Send + Sync { fn scenes (&self) -> &Vec { Has::>::get(self) } fn scenes_mut (&mut self) -> &mut Vec { Has::>::get_mut(self) } /// Generate the default name for a new scene fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() } fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } } /// ``` /// struct TestEditorHost(Option); /// has!(Option: |self: TestEditorHost|self.0); /// let mut host = TestEditorHost(Some(MidiEditor::default())); /// let _ = host.editor(); /// let _ = host.editor_mut(); /// let _ = host.is_editing(); /// let _ = host.editor_w(); /// let _ = host.editor_h(); /// ``` pub trait HasEditor: Has> { fn editor (&self) -> Option<&MidiEditor> { self.get().as_ref() } fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.get_mut().as_mut() } fn is_editing (&self) -> bool { self.editor().is_some() } fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize } fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize } } pub trait HasClips { fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; fn add_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); self.clips_mut().push(clip.clone()); (self.clips().len() - 1, clip) } } /// Trait for thing that may receive MIDI. pub trait HasMidiIns { fn midi_ins (&self) -> &Vec; fn midi_ins_mut (&mut self) -> &mut Vec; /// Collect MIDI input from app ports (TODO preallocate large buffers) fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> { self.midi_ins().iter() .map(|port|port.port().iter(scope) .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) .collect::>()) .collect::>() } fn midi_ins_with_sizes <'a> (&'a self) -> impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_ins().iter().enumerate().map(move|(i, input)|{ let height = 1 + input.connections().len(); let data = (i, input.port_name(), input.connections(), y, y + height); y += height; data }) } } /// Trait for thing that may output MIDI. pub trait HasMidiOuts { fn midi_outs (&self) -> &Vec; fn midi_outs_mut (&mut self) -> &mut Vec; fn midi_outs_with_sizes <'a> (&'a self) -> impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_outs().iter().enumerate().map(move|(i, output)|{ let height = 1 + output.connections().len(); let data = (i, output.port_name(), output.connections(), y, y + height); y += height; data }) } fn midi_outs_emit (&mut self, scope: &ProcessScope) { for port in self.midi_outs_mut().iter_mut() { port.buffer_emit(scope) } } } pub trait HasTracks: Has> + Send + Sync { fn tracks (&self) -> &Vec { Has::>::get(self) } fn tracks_mut (&mut self) -> &mut Vec { Has::>::get_mut(self) } /// Run audio callbacks for every track and every device fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control { for track in self.tracks_mut().iter_mut() { if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { return Control::Quit } for device in track.devices.iter_mut() { if Control::Quit == DeviceAudio(device).process(client, scope) { return Control::Quit } } } Control::Continue } fn track_longest_name (&self) -> usize { self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) } /// Stop all playing clips fn tracks_stop_all (&mut self) { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } } /// Stop all playing clips fn tracks_launch (&mut self, clips: Option>>>>) { if let Some(clips) = clips { for (clip, track) in clips.iter().zip(self.tracks_mut()) { track.sequencer.enqueue_next(clip.as_ref()); } } else { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } } } /// Spacing between tracks. const TRACK_SPACING: usize = 0; } pub trait HasTrack { fn track (&self) -> Option<&Track>; fn track_mut (&mut self) -> Option<&mut Track>; #[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { self.track().map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) } #[cfg(feature = "port")] fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content + '_ { self.track().map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) } #[cfg(feature = "port")] fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content { self.track().map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) } #[cfg(feature = "port")] fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content { self.track().map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) } } pub trait HasPlayClip: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; fn play_clip (&self) -> &Option<(Moment, Option>>)>; fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn next_clip (&self) -> &Option<(Moment, Option>>)>; fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); return Some(elapsed) } None } fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { if let Some((started, Some(clip))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip let times = (elapsed as usize / length) as f64; let elapsed = (elapsed as usize % length) as f64; return Some((times, elapsed)) } None } fn enqueue_next (&mut self, clip: Option<&Arc>>) { *self.next_clip_mut() = Some((self.clock().next_launch_instant(), clip.cloned())); *self.reset_mut() = true; } fn play_status (&self) -> impl Content { let (name, color): (Arc, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone(), color) } else { ("".into(), Tui::g(64).into()) }; let time: String = self.pulses_since_start_looped() .map(|(times, time)|format!("{:>3}x {:>}", times+1.0, self.clock().timebase.format_beats_1(time))) .unwrap_or_else(||String::from(" ")).into(); FieldV(color, "Now:", format!("{} {}", time, name)) } fn next_status (&self) -> impl Content { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); let mut color = ItemTheme::G[64]; let clock = self.clock(); if let Some((t, Some(clip))) = self.next_clip() { let clip = clip.read().unwrap(); name = clip.name.clone(); color = clip.color.clone(); time = { let target = t.pulse.get(); let current = clock.playhead.pulse.get(); if target > current { let remaining = target - current; format!("-{:>}", clock.timebase.format_beats_1(remaining)) } else { String::new() } }.into() } else if let Some((t, Some(clip))) = self.play_clip() { let clip = clip.read().unwrap(); if clip.looped { name = clip.name.clone(); color = clip.color.clone(); let target = t.pulse.get() + clip.length as f64; let current = clock.playhead.pulse.get(); if target > current { time = format!("-{:>}", clock.timebase.format_beats_0(target - current)).into() } } else { name = "Stop".to_string().into(); } }; FieldV(color, "Next:", format!("{} {}", time, name)) } } pub trait MidiMonitor: HasMidiIns + HasMidiBuffers { fn notes_in (&self) -> &Arc>; fn monitoring (&self) -> bool; fn monitoring_mut (&mut self) -> &mut bool; fn toggle_monitor (&mut self) { *self.monitoring_mut() = !self.monitoring(); } fn monitor (&mut self, scope: &ProcessScope) { } } pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip { fn recording (&self) -> bool; fn recording_mut (&mut self) -> &mut bool; fn toggle_record (&mut self) { *self.recording_mut() = !self.recording(); } fn overdub (&self) -> bool; fn overdub_mut (&mut self) -> &mut bool; fn toggle_overdub (&mut self) { *self.overdub_mut() = !self.overdub(); } fn record_clip ( &mut self, scope: &ProcessScope, started: Moment, clip: &Option>>, ) { if let Some(clip) = clip { let sample0 = scope.last_frame_time() as usize; let start = started.sample.get() as usize; let _recording = self.recording(); let timebase = self.clock().timebase().clone(); let quant = self.clock().quant.get(); let mut clip = clip.write().unwrap(); let length = clip.length; for input in self.midi_ins_mut().iter() { for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { if let LiveEvent::Midi { message, .. } = event { clip.record_event({ let sample = (sample0 + sample - start) as f64; let pulse = timebase.samples_to_pulse(sample); let quantized = (pulse / quant).round() * quant; quantized as usize % length }, message); } } } } } fn record_next (&mut self) { // TODO switch to next clip and record into it } } pub trait MidiViewer: Measured + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); fn redraw (&self); fn clip (&self) -> &Option>>; fn clip_mut (&mut self) -> &mut Option>>; fn set_clip (&mut self, clip: Option<&Arc>>) { *self.clip_mut() = clip.cloned(); self.redraw(); } /// Make sure cursor is within note range fn autoscroll (&self) { let note_pos = self.get_note_pos().min(127); let note_lo = self.get_note_lo(); let note_hi = self.get_note_hi(); if note_pos < note_lo { self.note_lo().set(note_pos); } else if note_pos > note_hi { self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi)); } } /// Make sure time range is within display fn autozoom (&self) { if self.time_lock().get() { let time_len = self.get_time_len(); let time_axis = self.get_time_axis(); let time_zoom = self.get_time_zoom(); loop { let time_zoom = self.time_zoom().get(); let time_area = time_axis * time_zoom; if time_area > time_len { let next_time_zoom = note_duration_prev(time_zoom); if next_time_zoom <= 1 { break } let next_time_area = time_axis * next_time_zoom; if next_time_area >= time_len { self.time_zoom().set(next_time_zoom); } else { break } } else if time_area < time_len { let prev_time_zoom = note_duration_next(time_zoom); if prev_time_zoom > 384 { break } let prev_time_area = time_axis * prev_time_zoom; if prev_time_area <= time_len { self.time_zoom().set(prev_time_zoom); } else { break } } } if time_zoom != self.time_zoom().get() { self.redraw() } } //while time_len.div_ceil(time_zoom) > time_axis { //println!("\r{time_len} {time_zoom} {time_axis}"); //time_zoom = Note::next(time_zoom); //} //self.time_zoom().set(time_zoom); } } pub trait AddScene: HasScenes + HasTracks { /// Add multiple scenes fn scenes_add (&mut self, n: usize) -> Usually<()> { let scene_color_1 = ItemColor::random(); let scene_color_2 = ItemColor::random(); for i in 0..n { let _ = self.scene_add(None, Some( scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() ))?; } Ok(()) } /// Add a scene fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<(usize, &mut Scene)> { let scene = Scene { name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), clips: vec![None;self.tracks().len()], color: color.unwrap_or_else(ItemTheme::random), }; self.scenes_mut().push(scene); let index = self.scenes().len() - 1; Ok((index, &mut self.scenes_mut()[index])) } } pub trait ClipsView: TracksView + ScenesView { fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { self.clips_size().of(Fill::XY(Bsp::a( Fill::XY(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), Thunk::new(|to: &mut TuiOut|for ( track_index, track, _, _ ) in self.tracks_with_sizes() { to.place(&Fixed::X(track.width as u16, Fill::Y(self.view_track_clips(track_index, track)))) })))) } fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Thunk::new(move|to: &mut TuiOut|for ( scene_index, scene, .. ) in self.scenes_with_sizes() { let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (format!(" ⏹ {}", &clip.name).into(), clip.color) } else { (" ⏹ -- ".into(), ItemTheme::G[32]) }; let fg = theme.lightest.rgb; let mut outline = theme.base.rgb; let bg = if self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) { outline = theme.lighter.rgb; theme.light.rgb } else if self.selection().track() == Some(track_index) || self.selection().scene() == Some(scene_index) { outline = theme.darkest.rgb; theme.base.rgb } else { theme.dark.rgb }; let w = if self.selection().track() == Some(track_index) && let Some(editor) = self.editor () { (editor.measure_width() as usize).max(24).max(track.width) } else { track.width } as u16; let y = if self.selection().scene() == Some(scene_index) && let Some(editor) = self.editor () { (editor.measure_height() as usize).max(12) } else { Self::H_SCENE as usize } as u16; to.place(&Fixed::XY(w, y, Bsp::b( Fill::XY(Outer(true, Style::default().fg(outline))), Fill::XY(Bsp::b( Bsp::b( Tui::fg_bg(outline, bg, Fill::XY("")), Fill::XY(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), ), Fill::XY(When::new(self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(), self.editor()))))))); }) } } pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll + Measured { fn tracks_width_available (&self) -> u16 { (self.measure_width() as u16).saturating_sub(40) } /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { let _editor_width = self.editor().map(|e|e.measure_width()); let _active_track = self.selection().track(); let mut x = 0; self.tracks().iter().enumerate().map_while(move |(index, track)|{ let width = track.width.max(8); if x + width < self.clips_size().w() as usize { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) } else { None } }) } fn view_track_names (&self, theme: ItemTheme) -> impl Content { let track_count = self.tracks().len(); let scene_count = self.scenes().len(); let selected = self.selection(); let button = Bsp::s( button_3("t", "rack ", format!("{}{track_count}", selected.track() .map(|track|format!("{track}/")).unwrap_or_default()), false), button_3("s", "cene ", format!("{}{scene_count}", selected.scene() .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); let button_2 = Bsp::s( button_2("T", "+", false), button_2("S", "+", false)); view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb, Fixed::Y(2, Thunk::new(|to: &mut TuiOut|{ for (index, track, x1, _x2) in self.tracks_with_sizes() { to.place(&Push::X(x1 as u16, Fixed::X(track_width(index, track), Tui::bg(if selected.track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb }, Bsp::s(Fill::X(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) ))), ""))) ));}})))) } fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Content { view_track_row_section(theme, Bsp::s(Fill::X(Align::w(button_2("o", "utput", false))), Thunk::new(|to: &mut TuiOut|for port in self.midi_outs().iter() { to.place(&Fill::X(Align::w(port.port_name()))); })), button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Thunk::new(|to: &mut TuiOut|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&Fixed::X(track_width(index, track), Align::nw(Fill::Y(Map::south(1, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), Fixed::Y(1, Tui::bg(track.color.dark.rgb, Fill::X(Align::w( format!("·o{index:02} {}", port.port_name())))))))))));}})))) } fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 0u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } let content = Thunk::new(move|to: &mut TuiOut|for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&Fixed::XY(track_width(index, track), h + 1, Align::nw(Bsp::s( Tui::bg(track.color.base.rgb, Fill::X(Align::w(row!( Either::new(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), Either::new(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), Either::new(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), )))), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, Fill::X(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); }); view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), Tui::bg(theme.darker.rgb, Align::w(content))) } } pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { /// Default scene height. const H_SCENE: usize = 2; /// Default editor height. const H_EDITOR: usize = 15; fn h_scenes (&self) -> u16; fn w_side (&self) -> u16; fn w_mid (&self) -> u16; fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { let mut y = 0; self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let height = if self.selection().scene() == Some(s) && self.editor().is_some() { 8 } else { Self::H_SCENE }; if y + height <= self.clips_size().h() as usize { let data = (s, scene, y, y + height); y += height; Some(data) } else { None } }) } fn view_scenes_names (&self) -> impl Content { Fixed::X(20, Thunk::new(|to: &mut TuiOut|for (index, scene, ..) in self.scenes_with_sizes() { to.place(&self.view_scene_name(index, scene)); })) } fn view_scene_name <'a> (&'a self, index: usize, scene: &'a Scene) -> impl Content + 'a { let h = if self.selection().scene() == Some(index) && let Some(_editor) = self.editor() { 7 } else { Self::H_SCENE as u16 }; let bg = if self.selection().scene() == Some(index) { scene.color.light.rgb } else { scene.color.base.rgb }; let a = Fill::X(Align::w(Bsp::e(format!("·s{index:02} "), Tui::fg(Tui::g(255), Tui::bold(true, &scene.name))))); let b = When::new(self.selection().scene() == Some(index) && self.is_editing(), Fill::XY(Align::nw(Bsp::s( self.editor().as_ref().map(|e|e.clip_status()), self.editor().as_ref().map(|e|e.edit_status()))))); Fixed::XY(20, h, Tui::bg(bg, Align::nw(Bsp::s(a, b)))) } } /// May create new MIDI input ports. pub trait AddMidiIn { fn midi_in_add (&mut self) -> Usually<()>; } /// May create new MIDI output ports. pub trait AddMidiOut { fn midi_out_add (&mut self) -> Usually<()>; } pub trait RegisterPorts: HasJack<'static> { /// Register a MIDI input port. fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; /// Register a MIDI output port. fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; /// Register an audio input port. fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; /// Register an audio output port. fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; } pub trait JackPort: HasJack<'static> { type Port: PortSpec + Default; type Pair: PortSpec + Default; fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) -> Usually where Self: Sized; fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { jack.with_client(|c|c.register_port::(name.as_ref(), Default::default())) .map_err(|e|e.into()) } fn port_name (&self) -> &Arc; fn connections (&self) -> &[Connect]; fn port (&self) -> &Port; fn port_mut (&mut self) -> &mut Port; fn into_port (self) -> Port where Self: Sized; fn close (self) -> Usually<()> where Self: Sized { let jack = self.jack().clone(); Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) } fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { self.with_client(|c|c.ports(re_name, re_type, flags)) } fn port_by_id (&self, id: u32) -> Option> { self.with_client(|c|c.port_by_id(id)) } fn port_by_name (&self, name: impl AsRef) -> Option> { self.with_client(|c|c.port_by_name(name.as_ref())) } fn connect_to_matching <'k> (&'k self) -> Usually<()> { for connect in self.connections().iter() { //panic!("{connect:?}"); let status = match &connect.name { Exact(name) => self.connect_exact(name), RegExp(re) => self.connect_regexp(re, connect.scope), }?; *connect.status.write().unwrap() = status; } Ok(()) } fn connect_exact <'k> (&'k self, name: &str) -> Usually, Arc, ConnectStatus)>> { self.with_client(move|c|{ let mut status = vec![]; for port in c.ports(None, None, PortFlags::empty()).iter() { if port.as_str() == &*name { if let Some(port) = c.port_by_name(port.as_str()) { let port_status = self.connect_to_unowned(&port)?; let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected { break } } } } Ok(status) }) } fn connect_regexp <'k> ( &'k self, re: &str, scope: ConnectScope ) -> Usually, Arc, ConnectStatus)>> { self.with_client(move|c|{ let mut status = vec![]; let ports = c.ports(Some(&re), None, PortFlags::empty()); for port in ports.iter() { if let Some(port) = c.port_by_name(port.as_str()) { let port_status = self.connect_to_unowned(&port)?; let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected && scope == One { break } } } Ok(status) }) } /** Connect to a matching port by name. */ fn connect_to_name (&self, name: impl AsRef) -> Usually { self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { self.connect_to_unowned(port) } else { Ok(Missing) }) } /** Connect to a matching port by reference. */ fn connect_to_unowned (&self, port: &Port) -> Usually { self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { Connected } else if let Ok(_) = c.connect_ports(port, self.port()) { Connected } else { Mismatch })) } /** Connect to an owned matching port by reference. */ fn connect_to_owned (&self, port: &Port) -> Usually { self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { Connected } else if let Ok(_) = c.connect_ports(port, self.port()) { Connected } else { Mismatch })) } }