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}