mseq_core/
context.rs

1use crate::Conductor;
2use crate::Instruction;
3use crate::MidiController;
4use crate::MidiMessage;
5use crate::MidiOut;
6use crate::bpm::Bpm;
7use alloc::collections::vec_deque::VecDeque;
8use alloc::{vec, vec::Vec};
9
10const DEFAULT_BPM: u8 = 120;
11
12/// An object of type [`Context`] is passed to the user’s [`Conductor`] at each clock tick
13/// via the [`Conductor::update`] method. It provides a high-level interface to send
14/// system MIDI messages and modify system parameters.
15///
16/// The user can set MIDI system parameters (e.g., [`Context::set_bpm`]) or send system messages
17/// (e.g., [`Context::start`]) using the provided methods.
18///
19/// In addition to sending the corresponding MIDI system messages, these methods also update
20/// the internal logic of the sequencer to reflect the change.
21pub struct Context {
22    /// Field used to send MIDI Channel Messages.
23    bpm: Bpm,
24    step: u32,
25    running: bool,
26    on_pause: bool,
27    pause: bool,
28    sys_instructions: Vec<Instruction>,
29}
30
31/// Inputs queue to process.
32pub type InputQueue = VecDeque<
33    MidiMessage, // Midi message
34>;
35
36impl Default for Context {
37    fn default() -> Self {
38        Self {
39            bpm: Bpm::new(DEFAULT_BPM),
40            step: 0,
41            running: true,
42            on_pause: true,
43            pause: false,
44            sys_instructions: vec![],
45        }
46    }
47}
48
49impl Context {
50    /// Sets the BPM (Beats per minute) of the sequencer.
51    pub fn set_bpm(&mut self, bpm: u8) {
52        self.bpm.set_bpm(bpm);
53    }
54
55    /// Gets the current BPM of the sequencer.
56    pub fn get_bpm(&self) -> u8 {
57        self.bpm.get_bpm()
58    }
59
60    /// Gets the current period (in microsec) of the sequencer.
61    /// A period represents the amount of time between each MIDI clock messages.
62    pub fn get_period_us(&self) -> u64 {
63        self.bpm.get_period_us()
64    }
65
66    /// Stops and exit the sequencer.
67    pub fn quit(&mut self) {
68        self.running = false
69    }
70
71    /// Pauses the sequencer and send a MIDI stop message.
72    pub fn pause(&mut self) {
73        self.on_pause = true;
74        self.pause = true;
75        self.sys_instructions.push(Instruction::StopAllNotes);
76        self.sys_instructions.push(Instruction::Stop);
77    }
78
79    /// Resumes the sequencer and send a MIDI continue message.
80    pub fn resume(&mut self) {
81        self.on_pause = false;
82        self.sys_instructions.push(Instruction::Continue);
83    }
84
85    /// Starts the sequencer and send a MIDI start message. The current step is set to 0.
86    pub fn start(&mut self) {
87        self.step = 0;
88        self.on_pause = false;
89        self.sys_instructions.push(Instruction::Start);
90    }
91
92    /// Retrieves the current MIDI step.
93    /// - 96 steps make a bar
94    /// - 24 steps make a whole note
95    /// - 12 steps make a half note
96    /// - 6 steps make a quarter note
97    pub fn get_step(&self) -> u32 {
98        self.step
99    }
100
101    /// MIDI logic called at the initialization.
102    /// This function is not intended to be called directly by users.  
103    /// `init` is used internally to enable code reuse across platforms.
104    pub fn init(
105        &mut self,
106        conductor: &mut impl Conductor,
107        controller: &mut MidiController<impl MidiOut>,
108    ) {
109        conductor
110            .init(self)
111            .into_iter()
112            .for_each(|instruction| controller.execute(instruction));
113    }
114
115    /// MIDI logic called before the clock tick.
116    /// This function is not intended to be called directly by users.  
117    /// `process_pre_tick` is used internally to enable code reuse across platforms.
118    pub fn process_pre_tick(
119        &mut self,
120        conductor: &mut impl Conductor,
121        controller: &mut MidiController<impl MidiOut>,
122    ) {
123        core::mem::take(&mut self.sys_instructions)
124            .into_iter()
125            .for_each(|instruction| controller.execute(instruction));
126
127        if self.on_pause {
128            conductor.update(self);
129        } else {
130            conductor
131                .update(self)
132                .into_iter()
133                .for_each(|instruction| controller.execute(instruction));
134        };
135    }
136
137    /// MIDI logic called after the clock tick.
138    /// This function is not intended to be called directly by users.  
139    /// `process_post_tick` is used internally to enable code reuse across platforms.
140    pub fn process_post_tick(&mut self, controller: &mut MidiController<impl MidiOut>) {
141        controller.send_clock();
142        if !self.on_pause {
143            self.step += 1;
144            controller.update(self.step);
145        } else if self.pause {
146            self.pause = false;
147        }
148    }
149
150    /// Returns `true` if the sequencer is currently running, `false` otherwise.
151    pub fn is_running(&self) -> bool {
152        self.running
153    }
154
155    /// Internal MIDI input handler.
156    ///
157    /// This function is not intended to be called directly by users.  
158    /// Instead, users should implement [`Conductor::handle_input`] for their custom input handler logic.
159    ///
160    /// `handle_input` is used internally to enable code reuse across platforms and unify MIDI input processing.
161    pub fn handle_input(
162        &mut self,
163        conductor: &mut impl Conductor,
164        controller: &mut MidiController<impl MidiOut>,
165        input_queue: &mut InputQueue,
166    ) {
167        input_queue
168            .drain(..)
169            .flat_map(|message| conductor.handle_input(message, self))
170            .for_each(|instruction| controller.execute(instruction));
171    }
172}