Skip to content

Commit

Permalink
Doc and README done
Browse files Browse the repository at this point in the history
  • Loading branch information
jeudine committed Oct 10, 2024
1 parent d640731 commit 7456ae6
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 40 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 9 additions & 5 deletions src/acid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<AcidTrig>, root: Note, channel_id: u8, name: &str) -> Self {
if pattern.is_empty() {
return DeteTrack::new(0, vec![], root, channel_id, name);
Expand Down Expand Up @@ -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<P: AsRef<Path>>(
filename: P,
root: Note,
Expand Down
4 changes: 2 additions & 2 deletions src/arp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MidiNote>,
Expand All @@ -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

Expand Down
16 changes: 9 additions & 7 deletions src/conductor.rs
Original file line number Diff line number Diff line change
@@ -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<impl MidiConnection>);
/// 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<impl MidiConnection>);
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl<T: MidiConnection> Context<T> {
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
Expand Down
46 changes: 23 additions & 23 deletions src/midi_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -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<T: MidiConnection> {
/// 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<u32, Vec<NotePlay>>,

/// Every note currently being played triggered by start_note.
// Every note currently being played triggered by start_note.
start_note_set: HashSet<NotePlay>,

/// Notes to play at the next update call
// Notes to play at the next update call
notes_to_play: Vec<NotePlay>,

conn: T,
Expand All @@ -88,14 +88,14 @@ impl<T: MidiConnection> MidiController<T> {
}
}

/// 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;
Expand All @@ -109,8 +109,8 @@ impl<T: MidiConnection> MidiController<T> {
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,
Expand All @@ -120,9 +120,8 @@ impl<T: MidiConnection> MidiController<T> {
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,
Expand All @@ -135,7 +134,8 @@ impl<T: MidiConnection> MidiController<T> {
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}");
Expand Down

0 comments on commit 7456ae6

Please sign in to comment.