Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 5 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@

A suite of tools used to read, modify, and manage MIDI-related systems

### NOTE: The main branch is in development. Stable versions are on their own branches.


## Overview

`midix` provides users with human readable MIDI structures without invariant states. That is, the midi 1.0 specification has been strongly typed such that programatic commands built with this crate are not invariant.
Expand All @@ -17,12 +14,13 @@ calling [`Reader::read_event`](crate::prelude::Reader::read_event) will yield a

Additionally, `midix` provides the user with [`LiveEvent::from_bytes`](crate::events::LiveEvent), which will parse events from a live MIDI source.

You may also make your own MIDI representation using the provided structs. A significant portion of
this library lives within the `bevy` feature. See details below on usage with the bevy engine.
You may also make your own MIDI representation using the provided structs.

## Goal
`midix` is NOT designed to be as fast as possible. It is designed for a user to navigate the MIDI format to read and write to. Instead of working directly with bytes, use language to define what your MIDI is supposed to do.

TODO: Benches

## Getting Started

MIDI can be interpreted in two main ways: through `LiveEvent`s and regular file `Events`.
Expand Down Expand Up @@ -83,37 +81,17 @@ assert_eq!(velocity.byte(), 96);


## Semantic Versioning and Support
`midix` will adhere to semantic versioning. This means that I've opted to use major versions, even if this crate does not consider itself feature complete (you might get a midix `v29.3.1` someday)
`midix` will adhere to semantic versioning. I've opted to use major versions.

The current MSRV is rust `1.87`

## Acknowledgments

This crate was originally forked from [`bevy_midi`](https://github.com/BlackPhlox/bevy_midi). Please check them out if this crate doesn't suit your needs!

## MIDIx feature roadmap
- `no_std`
- Streamer (midir ext)
- Interfaces between `MidiSource` and `Midi` (some representable MIDI type, like a file, events, etc.)
- MIDI writers
- Streamer (async timed stream event via midir)
- MidiFile

## General feature schedule
The SUPPORT.md file denotes the length of time major revisions are supported.

When the major version of the crate is incremented, new features for the previous version(s)
will likely be neglected. If you need a non-breaking feature for an older version before the end
of its maintenence period, please let me know!

## Feature roadmap
- `no_std`
- Streamer (midir ext)
- Interfaces between `MidiSource` and `Midi` (some representable MIDI type, like a file, events, etc.)
- MIDI writers
- Streamer (async timed stream event via midir)
- MidiFile

## Acknowledgments
A lot of the documentation is copied directly from
[this documentation](http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html).
Expand All @@ -126,23 +104,9 @@ EMail: david@csw2.co.uk
Web: http://www.csw2.co.uk
```

Inspired by/copied from

### `midix`
## Inspired by/copied from

inspired by [`midly`](https://github.com/kovaxis/midly)
and [`quick-xml`](https://github.com/tafia/quick-xml).

If you are in need of a MIDI writer, I highly
recommend using `midly`, as this `midix` does not yet
support file writing.

Thanks to these mainters and contributors for inspiration!

### `bevy` feature

Forked originally from [`bevy_midi`](https://github.com/BlackPhlox/bevy_midi). Huge thank you for the examples and docs!

### hidden `synth` feature

Forked originally from [rustysynth](https://github.com/sinshu/rustysynth).
2 changes: 1 addition & 1 deletion src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::message::{ChannelVoiceMessage, VoiceEvent};

/// Identifies a channel for MIDI.
///
/// To get this channel from a `u8`, use [`Channel::try_from_primitive`].
/// To get this channel from a `u8`, use [`Channel::try_from_byte`].
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
#[cfg_attr(
feature = "bevy",
Expand Down
7 changes: 2 additions & 5 deletions src/file/builder.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use alloc::vec::Vec;

use crate::{
prelude::*,
reader::{ReadError, ReaderErrorKind},
};
use crate::{prelude::*, reader::ReaderErrorKind};

use super::MidiFile;

Expand Down Expand Up @@ -109,7 +106,7 @@ impl<'a> MidiFileBuilder<'a> {
self.unknown_chunks.push(data);
Ok(())
}
EOF => Err(ReaderErrorKind::ReadError(ReadError::OutOfBounds)),
EOF => Err(ReaderErrorKind::OutOfBounds),
}
}
pub fn build(self) -> Result<MidiFile<'a>, FileError> {
Expand Down
2 changes: 1 addition & 1 deletion src/file/timed_event_iter.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::prelude::*;

/// An iterator returned from [`ParsedMidiFile::into_events`].
/// An iterator returned from [`MidiFile::into_events`].
pub enum OptTimedEventIterator<'a> {
/// No tracks in the file
None,
Expand Down
2 changes: 1 addition & 1 deletion src/file_repr/chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ not fall into th
## [`RawHeaderChunk`]
This chunk type contains meta information about the MIDI file, such as
- [`RawFormat`](crate::prelude::RawFormat), which identifies how tracks should be played, and the claimed track count
- [`Timing`](crate::prelude::Timing), which defines how delta-seconds are to be interpreted
- [`Timing`], which defines how delta-seconds are to be interpreted

## [`]

Expand Down
7 changes: 2 additions & 5 deletions src/message/channel/voice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
channel::Channel,
events::FromLiveEventBytes,
message::VoiceEvent,
reader::{MidiSource, ReadError, ReadResult, Reader, ReaderError, ReaderErrorKind, inv_data},
reader::{MidiSource, ReadResult, Reader, ReaderError, ReaderErrorKind, inv_data},
};

/// Represents a MIDI voice message.
Expand Down Expand Up @@ -192,13 +192,10 @@ impl FromLiveEventBytes for ChannelVoiceMessage {
velocity: Velocity::new(data.get_byte(1).ok_or(ParseError::MissingData)?)?,
},
0xB => {
// TODO: really need to unify this
let mut temp = Reader::from_byte_slice(data);
let c = Controller::read(&mut temp).map_err(|e| match e.kind {
ReaderErrorKind::ParseError(p) => p,
ReaderErrorKind::ReadError(p) => match p {
ReadError::OutOfBounds => ParseError::MissingData,
},
ReaderErrorKind::OutOfBounds => ParseError::MissingData,
})?;
VoiceEvent::ControlChange(c)
}
Expand Down
4 changes: 2 additions & 2 deletions src/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl Note {
self.0.0
}
}
/// Efficiently make a note.
/// Create a note the easy way.
///
///
/// ## Example
Expand Down Expand Up @@ -259,7 +259,7 @@ pub enum Key {
B,
}
impl Key {
/// Returns an array beginning with [`Note::C`] to [`Note::B`]
/// Returns an array beginning with [`Key::C`] to [`Key::B`]
pub fn all() -> [Key; 12] {
use Key::*;
[C, CSharp, D, DSharp, E, F, FSharp, G, GSharp, A, ASharp, B]
Expand Down
46 changes: 15 additions & 31 deletions src/reader/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{ChunkError, ParseError};
use thiserror::Error;

#[doc = r#"
A set of errors that can occur while reading something into a midi representation
A set of errors that can occur while reading data into the midi representation
"#]
#[derive(Debug, Error)]
#[error("Reading at Position {position}, {kind}")]
Expand All @@ -18,9 +18,9 @@ pub enum ReaderErrorKind {
/// Parsing errors
#[error("Parsing {0}")]
ParseError(#[from] ParseError),
/// Errors unrelated to parsing (out of bounds)
#[error("Reading {0}")]
ReadError(#[from] ReadError),
/// Reading out of bounds.
#[error("Read out of bounds!")]
OutOfBounds,
}

impl ReaderErrorKind {
Expand All @@ -29,25 +29,22 @@ impl ReaderErrorKind {
}
}

/// Errors reading from some [`MidiSource`](crate::prelude::MidiSource)
#[derive(Debug, Error)]
pub enum ReadError {
/// Read out of bounds
#[error("Read out of bounds!")]
OutOfBounds,
}

impl ReaderError {
/// Create a reader error from a position and kind
pub const fn new(position: usize, kind: ReaderErrorKind) -> Self {
Self { position, kind }
}
/// True if out of bounds or unexpected end of file
pub const fn is_out_of_bounds(&self) -> bool {
matches!(
self.kind,
ReaderErrorKind::ReadError(ReadError::OutOfBounds)
)
matches!(self.kind, ReaderErrorKind::OutOfBounds)
}
/// Returns the error kind of the reader.
pub fn error_kind(&self) -> &ReaderErrorKind {
&self.kind
}
/// Returns the position where the read error occurred.
pub fn position(&self) -> usize {
self.position
}

/// Create a new invalid data error
Expand All @@ -62,31 +59,18 @@ impl ReaderError {
pub const fn oob(position: usize) -> Self {
Self {
position,
kind: ReaderErrorKind::ReadError(ReadError::OutOfBounds),
kind: ReaderErrorKind::OutOfBounds,
}
}
}

/// A result type that is either `T` or an [`io::Error`].
/// The Read Result type (see [`ReaderError`])
///
/// This may change in a future release if `midix`
/// should support `no-std` environments.
pub type ReadResult<T> = Result<T, ReaderError>;

// pub(crate) fn unexp_eof() -> ReaderError {
// io::Error::new(ErrorKind::UnexpectedEof, "Read past the end of the file").into()
// }

pub(crate) fn inv_data<R>(reader: &mut Reader<R>, v: impl Into<ParseError>) -> ReaderError {
reader.set_last_error_offset(reader.buffer_position());
ReaderError::parse_error(reader.buffer_position(), v.into())
}
// #[allow(dead_code)]
// pub(crate) fn inv_input<R>(reader: &mut Reader<R>, v: impl fmt::Display) -> ReaderError {
// reader.set_last_error_offset(reader.buffer_position());
// io::Error::new(
// ErrorKind::InvalidInput,
// format!("Cursor at {}: {}", reader.buffer_position(), v),
// )
// .into()
// }
Loading