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::launchkey::error::{LaunchkeyError, LaunchkeyInitError, MidiSendError};
14use crate::midi::to_hex::ToHexString;
15
16/// Marker trait for state-dependent behavior in [`LaunchkeyManager`].
17pub trait LaunchKeyState {}
18
19/// Marker type representing the Launchkey being in DAW mode.
20pub struct DAWMode;
21impl LaunchKeyState for DAWMode {}
22
23/// Marker type representing the Launchkey being in standalone mode.
24pub struct StandaloneMode;
25impl LaunchKeyState for StandaloneMode {}
26
27/// A stateful manager for sending MIDI commands to a Launchkey device.
28/// It is generic over a state (['DAWMode'] or ['StandaloneMode']) to enforce correct usage.
29pub struct LaunchkeyManager<S: LaunchKeyState + 'static> {
30    conn_out: Rc<RefCell<midir::MidiOutputConnection>>,
31    sku: LaunchKeySku,
32    state: PhantomData<S>,
33}
34
35impl<S: LaunchKeyState> LaunchkeyManager<S> {
36    /// Enables DAW mode on the Launchkey device by sending a MIDI message.
37    fn _enable_daw_mode(&mut self) -> Result<(), MidiSendError> {
38        println!("Enabling DAW Mode");
39        self._send_bytes(&ENABLE_DAW_MODE)
40    }
41
42    /// Disables DAW mode on the Launchkey device by sending a MIDI message.
43    fn _disable_daw_mode(&mut self) -> Result<(), MidiSendError> {
44        println!("Disabling DAW Mode");
45        self._send_bytes(&DISABLE_DAW_MODE)
46    }
47
48    /// Sends a MIDI command to the Launchkey.
49    fn _send_command(&mut self, command: LaunchkeyCommand) -> Result<(), MidiSendError> {
50        match command {
51            LaunchkeyCommand::SetPadMode(ref pad_mode) => {
52                match pad_mode {
53                    PadMode::Drum => self._send_command(LaunchkeyCommand::SetDrumDAWMode(false)),
54                    PadMode::DrumDAW => self._send_command(LaunchkeyCommand::SetDrumDAWMode(true)),
55                    _ => Ok(()),
56                }?;
57                self._send_bytes(&command.as_bytes(&self.sku))
58            }
59            command => self._send_bytes(&command.as_bytes(&self.sku)),
60        }
61    }
62
63    /// Sends raw byte command to the Launchkey.
64    fn _send_bytes(&mut self, bytes: &[u8]) -> Result<(), MidiSendError> {
65        println!("Sending MIDI message: {}", &bytes.to_hex_string());
66        self.conn_out.borrow_mut().send(bytes)?;
67        Ok(())
68    }
69}
70
71impl LaunchkeyManager<StandaloneMode> {
72    /// Creates a new ['LaunchkeyManager'] and connects to the specified output port.
73    pub fn new(
74        midi_out: MidiOutput,
75        port: &MidiOutputPort,
76        sku: LaunchKeySku,
77    ) -> Result<Self, LaunchkeyInitError> {
78        let conn_out = midi_out
79            .connect(port, "launchkey-manager")
80            .map_err(LaunchkeyInitError::MidiConnectError)?;
81        Ok(Self {
82            conn_out: Rc::new(RefCell::new(conn_out)),
83            sku,
84            state: PhantomData,
85        })
86    }
87
88    /// Provides a default ['LaunchkeyManager'] instance.
89    pub fn default() -> Result<Self, LaunchkeyInitError> {
90        // Create MIDI output instance
91        let midi_out = MidiOutput::new("Custom DAW")?;
92
93        let ports = midi_out.ports();
94        if ports.is_empty() {
95            return Err(LaunchkeyInitError::DeviceNotFound("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(|| LaunchkeyInitError::DeviceNotFound("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<(), MidiSendError> {
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<(), MidiSendError> {
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>, LaunchkeyError> {
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>, LaunchkeyError> {
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<(), LaunchkeyError> {
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<(), MidiSendError> {
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<(), MidiSendError> {
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}