use crate::errors::{Error, Result};
use crate::processor::Processor;
use std::fs;
use std::io::Write;
use std::path::Path;
#[cfg(not(any(feature = "midi", feature = "audio")))]
compile_error!("Enable at least one of the `midi` or `audio` features to use `Track`.");
#[cfg(feature = "audio")]
use crate::audio::AudioTrack;
#[cfg(feature = "midi")]
use crate::midi::{MidiEvent, MidiTrack};
#[derive(Debug, Clone, PartialEq)]
pub enum Track {
#[cfg(feature = "midi")]
Midi(MidiTrack),
#[cfg(feature = "audio")]
Audio(AudioTrack),
}
impl Track {
#[cfg(feature = "midi")]
pub fn from_midi<P: AsRef<Path>>(path: P) -> Result<Self> {
let contents = fs::read_to_string(path)?;
let mut events = Vec::new();
for (idx, raw_line) in contents.lines().enumerate() {
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<_> = line.split_whitespace().collect();
if parts.len() != 2 {
return Err(Error::Parse(format!(
"line {}: expected 'note velocity', got '{line}'",
idx + 1
)));
}
let note = parts[0].parse::<u8>().map_err(|_| {
Error::Parse(format!(
"line {}: invalid MIDI note '{}'",
idx + 1,
parts[0]
))
})?;
let velocity = parts[1].parse::<u8>().map_err(|_| {
Error::Parse(format!(
"line {}: invalid velocity '{}'",
idx + 1,
parts[1]
))
})?;
if note > 127 {
return Err(Error::Parse(format!(
"line {}: MIDI note must be <= 127",
idx + 1
)));
}
if velocity > 127 {
return Err(Error::Parse(format!(
"line {}: velocity must be <= 127",
idx + 1
)));
}
events.push(MidiEvent::new(note, velocity));
}
Ok(Track::Midi(MidiTrack::new(events)))
}
#[cfg(feature = "audio")]
pub fn from_audio(samples: Vec<f32>, sample_rate: u32) -> Self {
Track::Audio(AudioTrack::new(samples, sample_rate))
}
pub fn apply<P: Processor>(&mut self, processor: &P) {
match self {
#[cfg(feature = "midi")]
Track::Midi(track) => processor.process_midi(track),
#[cfg(feature = "audio")]
Track::Audio(track) => processor.process_audio(track),
}
}
pub fn export<P: AsRef<Path>>(&self, path: P) -> Result<()> {
match self {
#[cfg(feature = "midi")]
Track::Midi(track) => export_midi(track, path),
#[cfg(feature = "audio")]
Track::Audio(track) => export_audio(track, path),
}
}
#[cfg(feature = "midi")]
pub fn as_midi(&self) -> Option<&MidiTrack> {
if let Track::Midi(track) = self {
Some(track)
} else {
None
}
}
#[cfg(feature = "midi")]
pub fn as_midi_mut(&mut self) -> Option<&mut MidiTrack> {
if let Track::Midi(track) = self {
Some(track)
} else {
None
}
}
#[cfg(feature = "audio")]
pub fn as_audio(&self) -> Option<&AudioTrack> {
if let Track::Audio(track) = self {
Some(track)
} else {
None
}
}
#[cfg(feature = "audio")]
pub fn as_audio_mut(&mut self) -> Option<&mut AudioTrack> {
if let Track::Audio(track) = self {
Some(track)
} else {
None
}
}
}
#[cfg(feature = "midi")]
fn export_midi<P: AsRef<Path>>(track: &MidiTrack, path: P) -> Result<()> {
let mut file = fs::File::create(path)?;
for event in &track.events {
writeln!(file, "{} {}", event.note, event.velocity)?;
}
Ok(())
}
#[cfg(feature = "audio")]
fn export_audio<P: AsRef<Path>>(track: &AudioTrack, path: P) -> Result<()> {
let mut file = fs::File::create(path)?;
writeln!(file, "sample_rate {}", track.sample_rate)?;
for sample in &track.samples {
writeln!(file, "{sample}")?;
}
Ok(())
}