#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
use core::{num::NonZeroU8, time::Duration};
pub mod midi;
mod tests;
use midi::{InvalidMidiNote, Note, NoteState};
pub mod util {
#[derive(Debug)]
pub struct OutOfBounds<const MAX: u8>(u8);
impl<const MAX: u8> OutOfBounds<MAX> {
pub const MAX: u8 = MAX;
pub(crate) const fn new(value: u8) -> Self {
Self(value)
}
pub const fn value(&self) -> u8 {
self.0
}
}
impl<const MAX: u8> core::fmt::Display for OutOfBounds<MAX> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Value {} is larger than maximum {}.", self.0, Self::MAX)
}
}
#[cfg(feature = "std")]
impl<const MAX: u8> std::error::Error for OutOfBounds<MAX> {}
}
#[derive(Debug, Clone)]
pub struct Config {
pub notes: core::ops::RangeInclusive<u8>,
pub step: NonZeroU8,
pub velocity_levels: NonZeroU8,
pub round_robins: NonZeroU8,
pub length: Duration,
pub gap: Duration,
}
impl Default for Config {
fn default() -> Self {
Self {
notes: 0..=127,
step: NonZeroU8::new(1).unwrap(),
velocity_levels: NonZeroU8::new(1).unwrap(),
round_robins: NonZeroU8::new(1).unwrap(),
length: Duration::from_millis(500),
gap: Duration::from_millis(500),
}
}
}
#[derive(Debug)]
pub struct Sequencer {
length: usize,
gap: usize,
pitch: u8,
pitch_step: u8,
final_pitch: u8,
velocity: u8,
velocity_step: u8,
round_robin: u8,
round_robin_count: u8,
samples_remaining: usize,
next_status: NoteState,
}
impl Sequencer {
pub fn new(config: Config, sample_rate: u32) -> Result<Self, SequencerError> {
let Config {
notes,
step,
velocity_levels,
round_robins,
length,
gap,
} = config;
let pitch = midi::Pitch::new(*notes.start())
.map_err(SequencerError::StartNote)?
.note_number();
let final_pitch = midi::Pitch::new(*notes.end())
.map_err(SequencerError::EndNote)?
.note_number();
let velocity_levels = velocity_levels.get();
let velocity_step = (128 + velocity_levels / 2) / velocity_levels;
if velocity_step == 0 {
return Err(SequencerError::VelocityLevels(velocity_levels));
}
Ok(Self {
length: ((length * sample_rate).as_millis() / 1_000) as usize,
gap: ((gap * sample_rate).as_millis() / 1_000) as usize,
pitch,
pitch_step: step.get(),
final_pitch,
velocity: 127,
velocity_step,
round_robin: 0,
round_robin_count: round_robins.get(),
samples_remaining: 0,
next_status: NoteState::On,
})
}
pub fn advance(&mut self, num_frames: usize) -> AdvanceResult {
match self.samples_remaining.checked_sub(num_frames) {
None => {
let result = AdvanceResult::Event {
position: core::mem::take(&mut self.samples_remaining),
note: Note {
pitch: self.pitch,
velocity: self.velocity,
state: self.next_status,
},
};
match self.next_status {
NoteState::On if self.pitch > self.final_pitch => {
return AdvanceResult::SequenceComplete;
}
NoteState::On => {
self.samples_remaining = self.length;
self.next_status = NoteState::Off;
}
NoteState::Off => {
self.samples_remaining = self.gap;
self.next_status = NoteState::On;
self.round_robin += 1;
if self.round_robin == self.round_robin_count {
self.round_robin = 0;
if let Some(next_velocity) =
self.velocity.checked_sub(self.velocity_step)
{
self.velocity = next_velocity;
} else {
self.velocity = 127;
self.pitch += self.pitch_step;
}
}
}
}
result
}
Some(further) => {
self.samples_remaining = further;
AdvanceResult::NoEventsInFrame
}
}
}
}
impl IntoIterator for Sequencer {
type Item = (usize, Note);
type IntoIter = SequencerIntoIter;
fn into_iter(self) -> Self::IntoIter {
SequencerIntoIter {
sequencer: self,
position: 0,
}
}
}
pub struct SequencerIntoIter {
sequencer: Sequencer,
position: usize,
}
impl Iterator for SequencerIntoIter {
type Item = (usize, Note);
fn next(&mut self) -> Option<Self::Item> {
match self.sequencer.advance(usize::MAX) {
AdvanceResult::SequenceComplete => None,
AdvanceResult::Event { position, note } => {
self.position = self.position.wrapping_add(position);
Some((self.position, note))
}
AdvanceResult::NoEventsInFrame => {
unreachable!(
"A {} with length {} samples was produced",
match self.sequencer.next_status {
NoteState::On => "gap",
NoteState::Off => "note",
},
usize::MAX
)
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum AdvanceResult {
NoEventsInFrame,
Event {
position: usize,
note: Note,
},
SequenceComplete,
}
#[derive(Debug)]
pub enum SequencerError {
StartNote(InvalidMidiNote),
EndNote(InvalidMidiNote),
VelocityLevels(u8),
}
impl core::fmt::Display for SequencerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SequencerError::StartNote(e) => write!(f, "Invalid start of note range: {e}"),
SequencerError::EndNote(e) => write!(f, "Invalid end of note range: {e}"),
SequencerError::VelocityLevels(n) => {
write!(f, "Maximum 128 possible velocity layers, specified {n}")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SequencerError {}