launchkey_sdk/launchkey/
manager.rs

1use crate::launchkey::bitmap::LaunchkeyBitmap;
2use crate::launchkey::commands::LaunchkeyCommand;
3use crate::launchkey::constants::{LaunchKeySku, DISABLE_DAW_MODE, ENABLE_DAW_MODE};
4use crate::launchkey::modes::encoder_mode::EncoderMode;
5use crate::launchkey::modes::fader_mode::FaderMode;
6use crate::launchkey::modes::pad_mode::PadMode;
7use crate::launchkey::surface::display::{Arrangement, GlobalDisplayTarget};
8use midir::{MidiOutput, MidiOutputPort};
9use std::any::TypeId;
10use std::cell::RefCell;
11use std::marker::PhantomData;
12use std::rc::Rc;
13use log::{debug, error, info, log};
14use crate::launchkey::error::{LaunchkeyError, LaunchkeyInitError, MidiSendError};
15use crate::midi::to_hex::ToHexString;
16
17/// Marker trait for state-dependent behavior in [`LaunchkeyManager`].
18pub trait LaunchKeyState {}
19
20/// Marker type representing the Launchkey being in DAW mode.
21pub struct DAWMode;
22impl LaunchKeyState for DAWMode {}
23
24/// Marker type representing the Launchkey being in standalone mode.
25pub struct StandaloneMode;
26impl LaunchKeyState for StandaloneMode {}
27
28/// A stateful manager for sending MIDI commands to a Launchkey device.
29/// It is generic over a state (['DAWMode'] or ['StandaloneMode']) to enforce correct usage.
30pub struct LaunchkeyManager<S: LaunchKeyState + 'static> {
31    conn_out: Rc<RefCell<midir::MidiOutputConnection>>,
32    sku: LaunchKeySku,
33    state: PhantomData<S>,
34}
35
36impl<S: LaunchKeyState> LaunchkeyManager<S> {
37    /// Sends a MIDI command to the Launchkey.
38    fn _send_command(&mut self, command: LaunchkeyCommand) -> Result<(), MidiSendError> {
39        match command {
40            LaunchkeyCommand::SetPadMode(ref pad_mode) => {
41                match pad_mode {
42                    PadMode::Drum => self._send_command(LaunchkeyCommand::SetDrumDAWMode(false)),
43                    PadMode::DrumDAW => self._send_command(LaunchkeyCommand::SetDrumDAWMode(true)),
44                    _ => Ok(()),
45                }?;
46                self._send_bytes(&command.as_bytes(&self.sku))
47            }
48            command => self._send_bytes(&command.as_bytes(&self.sku)),
49        }
50    }
51
52    /// Sends raw byte command to the Launchkey.
53    fn _send_bytes(&mut self, bytes: &[u8]) -> Result<(), MidiSendError> {
54        debug!("Sending MIDI message: {}", &bytes.to_hex_string());
55        self.conn_out.borrow_mut().send(bytes)?;
56        Ok(())
57    }
58}
59
60impl LaunchkeyManager<StandaloneMode> {
61    /// Creates a new ['LaunchkeyManager'] and connects to the specified output port.
62    pub fn new(
63        midi_out: MidiOutput,
64        port: &MidiOutputPort,
65        sku: LaunchKeySku,
66    ) -> Result<Self, LaunchkeyInitError> {
67        let conn_out = midi_out
68            .connect(port, "launchkey-manager")
69            .map_err(LaunchkeyInitError::MidiConnectError)?;
70        Ok(Self {
71            conn_out: Rc::new(RefCell::new(conn_out)),
72            sku,
73            state: PhantomData,
74        })
75    }
76
77    /// Provides a default ['LaunchkeyManager'] instance.
78    pub fn default() -> Result<Self, LaunchkeyInitError> {
79        // Create MIDI output instance
80        let midi_out = MidiOutput::new("Custom DAW")?;
81
82        let ports = midi_out.ports();
83        if ports.is_empty() {
84            return Err(LaunchkeyInitError::DeviceNotFound("No MIDI output ports found".to_string()));
85        }
86
87        // Find the desired output port (e.g., "MIDIOUT2")
88        info!("Looking for 'MIDIOUT2' among available MIDI ports...");
89        let out_port = ports
90            .iter()
91            .find(|port| {
92                midi_out
93                    .port_name(port)
94                    .unwrap_or_default()
95                    .contains("MIDIOUT2")
96            })
97            .ok_or_else(|| LaunchkeyInitError::DeviceNotFound("Could not find MIDIOUT2 port".to_string()))?;
98
99        Self::new(midi_out, out_port, LaunchKeySku::Mini)
100    }
101
102    /// Sends a command to set the global screen text in ['StandaloneMode'].
103    pub fn set_screen_text_global(
104        &mut self,
105        target: GlobalDisplayTarget,
106        arrangement: Arrangement,
107    ) -> Result<(), MidiSendError> {
108        self._send_command(LaunchkeyCommand::SetScreenTextGlobal {
109            target,
110            arrangement,
111        })
112    }
113
114    /// Sends a command to display a screen bitmap in ['StandaloneMode'].
115    pub fn send_screen_bitmap(
116        &mut self,
117        target: GlobalDisplayTarget,
118        bitmap: LaunchkeyBitmap,
119    ) -> Result<(), MidiSendError> {
120        self._send_command(LaunchkeyCommand::SendScreenBitmap {
121            target,
122            bitmap: Box::new(bitmap),
123        })
124    }
125
126    /// Enables DAW Mode and transitions the manager to ['DAWMode'].
127    pub fn into_daw_mode(mut self) -> Result<LaunchkeyManager<DAWMode>, LaunchkeyError> {
128        self._send_bytes(&ENABLE_DAW_MODE)?;
129        info!("Transitioned to DAW mode");
130        Ok(LaunchkeyManager {
131            conn_out: self.conn_out.clone(),
132            sku: self.sku.clone(),
133            state: PhantomData,
134        })
135    }
136}
137
138impl LaunchkeyManager<DAWMode> {
139    /// Disables DAW Mode and transitions the manager to ['StandaloneMode'].
140    pub fn into_standalone_mode(
141        mut self,
142    ) -> Result<LaunchkeyManager<StandaloneMode>, LaunchkeyError> {
143        self._send_bytes(&DISABLE_DAW_MODE)?;
144        info!("Transitioned to Standalone mode");
145        Ok(LaunchkeyManager {
146            conn_out: self.conn_out.clone(),
147            sku: self.sku.clone(),
148            state: PhantomData,
149        })
150    }
151
152    /// Sets the default mode for the Pads, Encoders and Faders on the Launchkey.
153    pub fn setup_default_element_modes(&mut self) -> Result<(), LaunchkeyError> {
154        self.send_command(LaunchkeyCommand::SetPadMode(PadMode::DAW))?;
155        self.send_command(LaunchkeyCommand::SetEncoderMode(EncoderMode::Plugin))?;
156        self.send_command(LaunchkeyCommand::SetFaderMode(FaderMode::Volume))?;
157
158        Ok(())
159    }
160
161    /// Sends a MIDI command to the Launchkey.
162    pub fn send_command(&mut self, command: LaunchkeyCommand) -> Result<(), MidiSendError> {
163        debug!("Dispatching command: {:?}", command);
164        self._send_command(command)
165    }
166
167    /// Sends multiple MIDI commands to the Launchkey.
168    pub fn send_commands(&mut self, commands: &[LaunchkeyCommand]) -> Result<(), MidiSendError> {
169        debug!("Sending {} MIDI commands to Launchkey", commands.len());
170        for command in commands {
171            self._send_command((*command).clone())?;
172        }
173        Ok(()) // Return Ok if all commands are successfully sent
174    }
175}
176
177impl<S: LaunchKeyState + 'static> Drop for LaunchkeyManager<S> {
178    fn drop(&mut self) {
179        // Check if the current type is DawMode
180        if TypeId::of::<S>() == TypeId::of::<DAWMode>() {
181            if let Err(err) = self._send_bytes(&DISABLE_DAW_MODE) {
182                error!("Failed to disable DAW mode during cleanup: {}", err);
183            }
184        }
185    }
186}