use super::file::MidiFile;
use super::message::{MetaEvent, MidiMessage};
use super::track::MidiTrack;
use super::MidiFormat;
use crate::core::{Duration, Fraction, Note, Pitch};
use crate::stream::{Measure, Part, Score};
pub struct ScoreToMidi {
ticks_per_quarter: u16,
}
impl ScoreToMidi {
pub fn new() -> Self {
Self {
ticks_per_quarter: 480,
}
}
pub fn with_ticks_per_quarter(mut self, tpq: u16) -> Self {
self.ticks_per_quarter = tpq;
self
}
pub fn convert(&self, score: &Score) -> MidiFile {
let mut midi = MidiFile::with_format(MidiFormat::MultiTrack, self.ticks_per_quarter);
let tempo_track = midi.add_track();
tempo_track.set_name("Tempo");
if let Some(tempo) = score.tempo() {
tempo_track.add_tempo(0, tempo.bpm());
} else {
tempo_track.add_tempo(0, 120.0);
}
if let Some(ts) = score.time_signature() {
tempo_track.add_time_signature(0, ts.numerator(), ts.denominator());
}
if let Some(ks) = score.key_signature() {
tempo_track.add_key_signature(0, ks.sharps(), ks.is_minor());
}
tempo_track.add_end_of_track();
for (i, part) in score.parts().iter().enumerate() {
let track = midi.add_track();
track.set_name(part.name().unwrap_or(&format!("Part {}", i + 1)));
if let Some(instrument) = part.instrument() {
track.add_program_change(0, i as u8, instrument.midi_program());
}
self.convert_part(part, track, i as u8);
track.add_end_of_track();
}
midi.link_note_events();
midi
}
fn convert_part(&self, part: &Part, track: &mut MidiTrack, channel: u8) {
let mut current_tick: u64 = 0;
for measure in part.measures() {
self.convert_measure(measure, track, channel, &mut current_tick);
}
}
fn convert_measure(
&self,
measure: &Measure,
track: &mut MidiTrack,
channel: u8,
current_tick: &mut u64,
) {
let measure_start = *current_tick;
for (offset, element) in measure.elements() {
let element_tick = measure_start + self.fraction_to_ticks(*offset);
match element {
crate::stream::MusicElement::Note(note) => {
let duration_ticks = self.fraction_to_ticks(note.quarter_length());
track.add_note(
element_tick,
duration_ticks,
channel,
note.midi(),
note.volume().velocity,
);
}
crate::stream::MusicElement::Chord(chord) => {
let duration_ticks = self.fraction_to_ticks(chord.quarter_length());
for note in chord.notes() {
track.add_note(
element_tick,
duration_ticks,
channel,
note.midi(),
note.volume().velocity,
);
}
}
crate::stream::MusicElement::Rest(_) => {
}
}
}
*current_tick += self.fraction_to_ticks(measure.duration());
}
fn fraction_to_ticks(&self, fraction: Fraction) -> u64 {
let ticks = fraction * Fraction::from(self.ticks_per_quarter as i64);
(*ticks.numer() / *ticks.denom()) as u64
}
}
impl Default for ScoreToMidi {
fn default() -> Self {
Self::new()
}
}
pub struct MidiToScore {
quantize_ticks: Option<u64>,
separate_voices: bool,
}
impl MidiToScore {
pub fn new() -> Self {
Self {
quantize_ticks: None,
separate_voices: false,
}
}
pub fn with_quantization(mut self, ticks: u64) -> Self {
self.quantize_ticks = Some(ticks);
self
}
pub fn with_voice_separation(mut self, enabled: bool) -> Self {
self.separate_voices = enabled;
self
}
pub fn convert(&self, midi: &MidiFile) -> Score {
let mut score = Score::new();
let tpq = midi.ticks_per_quarter();
if let Some(track) = midi.tracks().first() {
for event in track.events() {
match event.message() {
MidiMessage::Meta(MetaEvent::Tempo(us)) => {
let bpm = 60_000_000.0 / *us as f64;
score.set_tempo(crate::notation::Tempo::new(bpm));
}
MidiMessage::Meta(MetaEvent::TimeSignature {
numerator,
denominator_power,
..
}) => {
let denom = 1 << denominator_power;
score.set_time_signature(crate::notation::TimeSignature::new(
*numerator, denom,
));
}
MidiMessage::Meta(MetaEvent::KeySignature {
sharps_flats,
minor,
}) => {
score.set_key_signature(crate::notation::KeySignature::new(
*sharps_flats,
*minor,
));
}
_ => {}
}
}
}
let start_track = if midi.format() == MidiFormat::MultiTrack && midi.num_tracks() > 1 {
1
} else {
0
};
for track in midi.tracks().iter().skip(start_track) {
let part = self.convert_track(track, tpq);
score.add_part(part);
}
score
}
fn convert_track(&self, track: &MidiTrack, tpq: u16) -> Part {
let mut part = Part::new();
if let Some(name) = track.name() {
part.set_name(name);
}
let mut notes: Vec<(u64, u64, u8, u8)> = Vec::new();
for event in track.events() {
if event.is_note_on() {
if let (Some(key), Some(vel), Some(duration)) =
(event.key(), event.velocity(), event.tick_duration(track.events()))
{
notes.push((event.tick(), duration, key, vel));
}
}
}
notes.sort_by_key(|(start, _, _, _)| *start);
let beats_per_measure = 4; let ticks_per_measure = tpq as u64 * beats_per_measure;
let mut current_measure = Measure::new(1);
let mut measure_start_tick: u64 = 0;
let mut measure_number = 1;
for (start, duration, key, velocity) in notes {
while start >= measure_start_tick + ticks_per_measure {
part.add_measure(current_measure);
measure_number += 1;
current_measure = Measure::new(measure_number);
measure_start_tick += ticks_per_measure;
}
let pitch = Pitch::from_midi(key);
let quarter_length = Fraction::new(duration as i64, tpq as i64);
let duration_obj = Duration::from_quarter_length(quarter_length);
let mut note = Note::new(pitch, duration_obj);
note.set_velocity(velocity);
let offset_ticks = start - measure_start_tick;
let offset = Fraction::new(offset_ticks as i64, tpq as i64);
current_measure.insert(offset, crate::stream::MusicElement::Note(note));
}
part.add_measure(current_measure);
part
}
}
impl Default for MidiToScore {
fn default() -> Self {
Self::new()
}
}
impl From<&Score> for MidiFile {
fn from(score: &Score) -> MidiFile {
ScoreToMidi::new().convert(score)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Duration, Note, Pitch, Step};
use crate::stream::Score;
#[test]
fn test_score_to_midi() {
let mut score = Score::new();
let mut part = Part::new();
part.set_name("Piano");
let mut measure = Measure::new(1);
let note = Note::new(
Pitch::from_parts(Step::C, Some(4), None),
Duration::quarter(),
);
measure.append(crate::stream::MusicElement::Note(note));
part.add_measure(measure);
score.add_part(part);
let midi = ScoreToMidi::new().convert(&score);
assert_eq!(midi.num_tracks(), 2); assert_eq!(midi.track(1).unwrap().name(), Some("Piano"));
}
#[test]
fn test_fraction_to_ticks() {
let converter = ScoreToMidi::new().with_ticks_per_quarter(480);
assert_eq!(
converter.fraction_to_ticks(Fraction::new(1, 1)),
480
);
assert_eq!(
converter.fraction_to_ticks(Fraction::new(2, 1)),
960
);
assert_eq!(
converter.fraction_to_ticks(Fraction::new(1, 2)),
240
);
}
}