mseq_core 0.1.6

Library for developing MIDI Sequencers.
Documentation
use crate::Conductor;
use crate::Instruction;
use crate::MidiController;
use crate::MidiMessage;
use crate::MidiOut;
use crate::bpm::Bpm;
use alloc::collections::vec_deque::VecDeque;
use alloc::{vec, vec::Vec};

const DEFAULT_BPM: u8 = 120;

/// An object of type [`Context`] is passed to the user’s [`Conductor`] at each clock tick
/// via the [`Conductor::update`] method. It provides a high-level interface to send
/// system MIDI messages and modify system parameters.
///
/// The user can set MIDI system parameters (e.g., [`Context::set_bpm`]) or send system messages
/// (e.g., [`Context::start`]) using the provided methods.
///
/// In addition to sending the corresponding MIDI system messages, these methods also update
/// the internal logic of the sequencer to reflect the change.
pub struct Context {
    /// Field used to send MIDI Channel Messages.
    bpm: Bpm,
    step: u32,
    running: bool,
    on_pause: bool,
    pause: bool,
    sys_instructions: Vec<Instruction>,
}

/// Inputs queue to process.
pub type InputQueue = VecDeque<
    MidiMessage, // Midi message
>;

impl Default for Context {
    fn default() -> Self {
        Self {
            bpm: Bpm::new(DEFAULT_BPM),
            step: 0,
            running: true,
            on_pause: true,
            pause: false,
            sys_instructions: vec![],
        }
    }
}

impl Context {
    /// Sets the BPM (Beats per minute) of the sequencer.
    pub fn set_bpm(&mut self, bpm: u8) {
        self.bpm.set_bpm(bpm);
    }

    /// Gets the current BPM of the sequencer.
    pub fn get_bpm(&self) -> u8 {
        self.bpm.get_bpm()
    }

    /// Gets the current period (in microsec) of the sequencer.
    /// A period represents the amount of time between each MIDI clock messages.
    pub fn get_period_us(&self) -> u64 {
        self.bpm.get_period_us()
    }

    /// Stops and exit the sequencer.
    pub fn quit(&mut self) {
        self.running = false
    }

    /// Pauses the sequencer and send a MIDI stop message.
    pub fn pause(&mut self) {
        self.on_pause = true;
        self.pause = true;
        self.sys_instructions.push(Instruction::StopAllNotes);
        self.sys_instructions.push(Instruction::Stop);
    }

    /// Resumes the sequencer and send a MIDI continue message.
    pub fn resume(&mut self) {
        self.on_pause = false;
        self.sys_instructions.push(Instruction::Continue);
    }

    /// Starts the sequencer and send a MIDI start message. The current step is set to 0.
    pub fn start(&mut self) {
        self.step = 0;
        self.on_pause = false;
        self.sys_instructions.push(Instruction::Start);
    }

    /// Retrieves the current MIDI step.
    /// - 96 steps make a bar
    /// - 24 steps make a whole note
    /// - 12 steps make a half note
    /// - 6 steps make a quarter note
    pub fn get_step(&self) -> u32 {
        self.step
    }

    /// MIDI logic called at the initialization.
    /// This function is not intended to be called directly by users.  
    /// `init` is used internally to enable code reuse across platforms.
    pub fn init(
        &mut self,
        conductor: &mut impl Conductor,
        controller: &mut MidiController<impl MidiOut>,
    ) {
        conductor
            .init(self)
            .into_iter()
            .for_each(|instruction| controller.execute(instruction));
    }

    /// MIDI logic called before the clock tick.
    /// This function is not intended to be called directly by users.  
    /// `process_pre_tick` is used internally to enable code reuse across platforms.
    pub fn process_pre_tick(
        &mut self,
        conductor: &mut impl Conductor,
        controller: &mut MidiController<impl MidiOut>,
    ) {
        core::mem::take(&mut self.sys_instructions)
            .into_iter()
            .for_each(|instruction| controller.execute(instruction));

        if self.on_pause {
            conductor.update(self);
        } else {
            conductor
                .update(self)
                .into_iter()
                .for_each(|instruction| controller.execute(instruction));
        };
    }

    /// MIDI logic called after the clock tick.
    /// This function is not intended to be called directly by users.  
    /// `process_post_tick` is used internally to enable code reuse across platforms.
    pub fn process_post_tick(&mut self, controller: &mut MidiController<impl MidiOut>) {
        controller.send_clock();
        if !self.on_pause {
            self.step += 1;
            controller.update(self.step);
        } else if self.pause {
            self.pause = false;
        }
    }

    /// Returns `true` if the sequencer is currently running, `false` otherwise.
    pub fn is_running(&self) -> bool {
        self.running
    }

    /// Returns `true` if the sequencer is currently paused, `false` otherwise.
    pub fn is_paused(&self) -> bool {
        self.on_pause
    }

    /// Internal MIDI input handler.
    ///
    /// This function is not intended to be called directly by users.  
    /// Instead, users should implement [`Conductor::handle_input`] for their custom input handler logic.
    ///
    /// `handle_input` is used internally to enable code reuse across platforms and unify MIDI input processing.
    pub fn handle_input(
        &mut self,
        conductor: &mut impl Conductor,
        controller: &mut MidiController<impl MidiOut>,
        input_queue: &mut InputQueue,
    ) {
        if self.is_paused() {
            input_queue
                .drain(..)
                .flat_map(|message| conductor.handle_input(message, self))
                .for_each(drop);
        } else {
            input_queue
                .drain(..)
                .flat_map(|message| conductor.handle_input(message, self))
                .for_each(|instruction| controller.execute(instruction));
        }
    }
}