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