diff --git a/README.md b/README.md index b3f08a0..65bdc63 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,24 @@ # MSeq -[![CI/CD](https://github.com/MF-Room/mseq/actions/workflows/rust.yml/badge.svg)](https://github.com/MF-Room/mseq/actions/workflows/rust.yml) +[![doc](https://docs.rs/mseq/badge.svg)](https://docs.rs/mseq) [![crates.io](https://img.shields.io/crates/v/mseq.svg)](https://crates.io/crates/mseq) [![CI/CD](https://github.com/MF-Room/mseq/actions/workflows/rust.yml/badge.svg)](https://github.com/MF-Room/mseq/actions/workflows/rust.yml) Library for developing MIDI Sequencers. -*WIP* +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +mseq = "0.1" +``` +To start using `mseq`, create a struct that implements the `Conductor` trait. + +You can then add tracks to your sequencer by adding fields (to your struct that implements the +`Conductor` trait) of type `DeteTrack` or more generally fields that implement the trait `Track`. + +Once this is done, you can play your track in the `Conductor::update` function of your struct that +implements the `Conductor` trait. To do so, call the method `MidiController::play_track` (of +the `Context::midi`) with the track you want to play as a parameter. + +You can find some examples in the [`examples`](https://github.com/MF-Room/mseq/tree/main/examples) directory. diff --git a/src/acid.rs b/src/acid.rs index 9edbaa8..8f6e8d7 100644 --- a/src/acid.rs +++ b/src/acid.rs @@ -14,7 +14,7 @@ pub enum Timing { /// Trig used to create acid tracks with [`DeteTrack::new_acid`]. Each Trig represents one /// sixteenth step. `AcidTrig` provides a similar interface to the original [`Roland TB-303`] with -/// some slight modification. +/// some slight modifications. /// /// [`Roland TB-303`]: https://en.wikipedia.org/wiki/Roland_TB-303 #[derive(Debug, serde::Deserialize)] @@ -31,7 +31,9 @@ pub struct AcidTrig { use Timing::*; impl DeteTrack { - /// Create a new acid track from a pattern, its root note, the midi channel, and a name. + /// Create a new acid track following the trigs in `pattern`. + /// The `root` note is used for transposition. The track will be played on the MIDI channel + /// with `channel_id`. pub fn new_acid(pattern: Vec, root: Note, channel_id: u8, name: &str) -> Self { if pattern.is_empty() { return DeteTrack::new(0, vec![], root, channel_id, name); @@ -96,9 +98,11 @@ impl DeteTrack { DeteTrack::new(6 * pattern.len() as u32, notes, root, channel_id, name) } - /// Load an acid track from a csv file. Refer to `examples/res/acid_0.csv` for an example file. - /// Provide the root note of the track to allow for transposition. channel_id is the midi - /// channel where this track will be played when passed to the MidiController. + /// Load an acid track from a csv file (`filename`). Refer to this + /// [`example`] for an example file. The `root` note is used for transposition. The track + /// will be played on the MIDI channel with `channel_id`. + /// + /// [`example`]: https://github.com/MF-Room/mseq/tree/main/examples/res/acid_0.csv pub fn load_acid_from_file>( filename: P, root: Note, diff --git a/src/arp.rs b/src/arp.rs index 654d5f4..101b914 100644 --- a/src/arp.rs +++ b/src/arp.rs @@ -15,7 +15,7 @@ pub enum ArpDiv { impl DeteTrack { /// Create a new arpeggiator track following the notes in `pattern` with the `div` time division. - /// The `root` note is used for transposition. The track will be played on the MIDI channel + /// The `root` note is used for transposition. The track will be played on the MIDI channel /// with `channel_id`. pub fn new_arp( pattern: Vec, @@ -41,7 +41,7 @@ impl DeteTrack { /// Load an arpeggiator track from a csv file (`filename`) and a time division (`div`). Refer to this /// [`example`] for an example file. The `root` note is used for transposition. The track - /// will be played on the MIDI channel with `channel_id`. + /// will be played on the MIDI channel with `channel_id`. /// /// [`example`]: https://github.com/MF-Room/mseq/tree/main/examples/res/arp_0.csv diff --git a/src/conductor.rs b/src/conductor.rs index 5d49745..24e1dc9 100644 --- a/src/conductor.rs +++ b/src/conductor.rs @@ -1,15 +1,17 @@ use crate::{Context, MidiConnection}; -/// The Conductor trait is the trait that the client has to implement to be able to use mseq. The -/// client has to implement init and update, then pass the Conductor to the main entry point -/// (`mseq::run`). Refer to the examples in `examples/` for complete examples. +/// The Conductor trait is the trait that the user has to implement to be able to use mseq. The +/// user has to implement [`Conductor::init`] and [`Conductor::update`], then pass the Conductor to the main entry point +/// [`crate::run`]. Refer to these [`examples`] for complete implementation examples. +/// +/// [`examples`]: https://github.com/MF-Room/mseq/tree/main/examples pub trait Conductor { - /// This function will be called only once when the Conductor is passed to the main entry point. + /// This function will be called only once at the start when the Conductor is passed to [`crate::run`] (the main entry point). fn init(&mut self, context: &mut Context); - /// This function will be called at every midi clock. All the notes sent to the MidiController - /// (`context.midi_controller`) will be played at the next midi clock. + /// This function will be called at every midi clock cycle. All the notes sent to the [`Context::midi`] + /// will be played at the beginning of the next midi clock cycle. /// /// __Warning: if this function takes too long, the midi clock might be late. Be careful not to - /// do any intensive computation, call sleep(), or wait for a lock inside this function.__ + /// do any intensive computation, ot block the thread.__ fn update(&mut self, context: &mut Context); } diff --git a/src/lib.rs b/src/lib.rs index b0675ce..d93b544 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,7 +100,7 @@ impl Context { self.midi.start(); } - /// Retrieve the current midi step. + /// Retrieve the current MIDI step. /// - 96 steps make a bar /// - 24 steps make a whole note /// - 12 steps make a half note diff --git a/src/midi_controller.rs b/src/midi_controller.rs index 7b12a68..292d569 100644 --- a/src/midi_controller.rs +++ b/src/midi_controller.rs @@ -7,31 +7,32 @@ use std::hash::Hash; const MAX_MIDI_CHANNEL: u8 = 16; -/// MidiNote describes a note that can be sent through midi. +/// Note that can be sent through a MIDI message. #[derive(Default, Clone, Copy, serde::Deserialize, PartialEq, Eq, Debug)] pub struct MidiNote { - /// The musical note (A to G) + /// The chromatic note (A to G) pub note: Note, /// The octave of the note pub octave: u8, - /// The note velocity (0 to 127) + /// The velocity of the note (0 to 127) pub vel: u8, } impl MidiNote { - /// Construct a new MidiNote + /// Construct a new [`MidiNote`] pub fn new(note: Note, octave: u8, vel: u8) -> Self { Self { note, octave, vel } } - /// Convert a midi note value to a MidiNote. + /// Convert a MIDI note value into a [`MidiNote`]. pub fn from_midi_value(midi_value: u8, vel: u8) -> Self { let octave = midi_value / 12; let note = Note::from(midi_value % 12); Self::new(note, octave, vel) } - /// Transpose the MidiNote. transpose corresponds to the number of semitones to add to the note. + /// Transpose the [`MidiNote`]. + /// The `transpose` parameter corresponds to the number of semitones to add to the note. pub fn transpose(&self, transpose: i8) -> Self { let (note, octave) = self.note.add_semitone(self.octave, transpose); Self { @@ -41,8 +42,8 @@ impl MidiNote { } } - /// Retrieve the midi value of the MidiNote, which can be sent through a midi connection. - pub fn midi_value(&self) -> u8 { + // Retrieve the MIDI value of the MidiNote, which can be sent through a MIDI message. + fn midi_value(&self) -> u8 { u8::from(self.note) + 12 * self.octave } } @@ -60,18 +61,17 @@ impl Hash for NotePlay { } } -/// The midi controller is responsible for handling the interface between the client and the midi connection. +/// The [`MidiController`] provides a MIDI interface to the user. pub struct MidiController { - /// Current midi step step: u32, - /// Every note currently being played triggered by play_note. The key is the step at which to stop the note. + // Every note currently being played triggered by play_note. The key is the step at which to stop the note. play_note_set: HashMap>, - /// Every note currently being played triggered by start_note. + // Every note currently being played triggered by start_note. start_note_set: HashSet, - /// Notes to play at the next update call + // Notes to play at the next update call notes_to_play: Vec, conn: T, @@ -88,14 +88,14 @@ impl MidiController { } } - /// Request the midi controller to play a track. This has to be called at every step when the - /// client wants the track to be played. + /// Request the [`MidiController`] to play `track`. This method has to be called at every MIDI step + /// the user wants the track to be played. pub fn play_track(&mut self, track: &mut impl Track) { track.play_step(self.step, self); } - /// Request the midi controller to play a note at the next midi clock. Specify the length of the - /// note and the midi channel on which to send the note. + /// Request the MIDI controller to play a note at the current MIDI step. Specify the length (`len`) of the + /// note and the MIDI channel id (`channel_id`) on which to send the note. pub fn play_note(&mut self, midi_note: MidiNote, len: u32, channel_id: u8) { if len == 0 { return; @@ -109,8 +109,8 @@ impl MidiController { self.stop_note_at_step(note_play, self.step + len); } - /// Request the midi controller to start playing a note. Specify the midi channel. The note will - /// not stop until stop_note is called with the same note and midi channel. + /// Request the MIDI controller to start playing a note. Specify the MIDI channel id (`channel_id`). The note will + /// not stop until [`MidiController::stop_note`] is called with the same note, ocatve and MIDI channel id. pub fn start_note(&mut self, midi_note: MidiNote, channel_id: u8) { let note_play = NotePlay { midi_note, @@ -120,9 +120,8 @@ impl MidiController { self.start_note_set.insert(note_play); } - /// Request the midi controller to stop playing a note that was started by start_note. The note - /// will stop only if the midi note and midi channel are identical to what was used in - /// start_note + /// Request the MIDI controller to stop playing a note that was started by [`MidiController::start_note`]. The note + /// will stop only if the note, ocatave and MIDI channel are identical to what was used in [`MidiController::start_note`]. pub fn stop_note(&mut self, midi_note: MidiNote, channel_id: u8) { let note_play = NotePlay { midi_note, @@ -135,7 +134,8 @@ impl MidiController { self.play_note_set.entry(step).or_default().push(note_play); } - /// Send midi Control Change (CC) signal. + /// Send MIDI Control Change (CC) message. You can use [`crate::param_value`] to convert a + /// float into a integer. pub fn send_cc(&mut self, channel_id: u8, parameter: u8, value: u8) { if let Err(e) = self.conn.send_cc(channel_id, parameter, value) { error!("MIDI: {e}");