mlua-pulse 0.1.0

Lua-friendly music composition and audio export bindings built on tunes and mlua
Documentation
use crate::composition::{PulseMidiClip, PulseSong};
use crate::error::{PulseError, PulseResult};
use crate::export::{
    apply_midi_export_options, export_mixer_flac_with_options, export_mixer_wav_with_options,
    export_wav_with_options, path_to_string, prepare_output_path, ExportOptions,
};
use std::path::Path;
use tunes::track::Mixer;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ExportFormat {
    Wav,
    Flac,
    Midi,
}

/// Returns the file extensions supported by `export`.
pub fn export_formats() -> &'static [&'static str] {
    &["wav", "flac", "mid", "midi"]
}

/// A MIDI file imported into a `tunes` mixer.
#[derive(Debug, Clone)]
pub struct PulseImportedMidi {
    mixer: Mixer,
}

impl PulseImportedMidi {
    /// Creates an imported MIDI wrapper from a `tunes` mixer.
    pub fn new(mixer: Mixer) -> Self {
        Self { mixer }
    }

    pub(crate) fn cloned_mixer(&self) -> Mixer {
        self.mixer.clone()
    }

    /// Returns this imported MIDI file as an arrangeable song or phrase clip.
    pub fn as_clip(&self) -> PulseMidiClip {
        PulseMidiClip::new(self.mixer.clone())
    }

    /// Exports the imported MIDI mixer based on the output file extension.
    pub fn export(&self, path: impl AsRef<Path>) -> PulseResult<()> {
        self.export_with_options(path, ExportOptions::new())
    }

    /// Exports the imported MIDI mixer based on the output file extension with options.
    pub fn export_with_options(
        &self,
        path: impl AsRef<Path>,
        options: ExportOptions,
    ) -> PulseResult<()> {
        match export_format(path.as_ref())? {
            ExportFormat::Wav => self.export_wav_with_options(path, options),
            ExportFormat::Flac => self.export_flac_with_options(path, options),
            ExportFormat::Midi => self.export_midi_with_options(path, options),
        }
    }

    /// Exports the imported MIDI mixer to WAV.
    pub fn export_wav(&self, path: impl AsRef<Path>) -> PulseResult<()> {
        self.export_wav_with_options(path, ExportOptions::new())
    }

    /// Exports the imported MIDI mixer to WAV with explicit export options.
    pub fn export_wav_with_options(
        &self,
        path: impl AsRef<Path>,
        options: ExportOptions,
    ) -> PulseResult<()> {
        let mut mixer = self.mixer.clone();
        export_mixer_wav_with_options(&mut mixer, path, options)
    }

    /// Exports the imported MIDI mixer to FLAC.
    pub fn export_flac(&self, path: impl AsRef<Path>) -> PulseResult<()> {
        self.export_flac_with_options(path, ExportOptions::new())
    }

    /// Exports the imported MIDI mixer to FLAC with explicit export options.
    pub fn export_flac_with_options(
        &self,
        path: impl AsRef<Path>,
        options: ExportOptions,
    ) -> PulseResult<()> {
        let mut mixer = self.mixer.clone();
        export_mixer_flac_with_options(&mut mixer, path, options)
    }

    /// Exports the imported MIDI mixer to a Standard MIDI File.
    pub fn export_midi(&self, path: impl AsRef<Path>) -> PulseResult<()> {
        self.export_midi_with_options(path, ExportOptions::new())
    }

    /// Exports the imported MIDI mixer to a Standard MIDI File with explicit export options.
    pub fn export_midi_with_options(
        &self,
        path: impl AsRef<Path>,
        options: ExportOptions,
    ) -> PulseResult<()> {
        let path_text = prepare_output_path(path.as_ref())?;
        let mut mixer = self.mixer.clone();
        apply_midi_export_options(&mut mixer, options);
        mixer
            .export_midi(&path_text)
            .map_err(|error| PulseError::MidiExportFailed {
                message: error.to_string(),
            })
    }
}

/// Exports a song to FLAC through `tunes::engine::AudioEngine::export_flac`.
pub fn export_flac(song: &PulseSong, path: impl AsRef<Path>) -> PulseResult<()> {
    export_flac_with_options(song, path, ExportOptions::new())
}

/// Exports a song to FLAC through `tunes::engine::AudioEngine::export_flac` with options.
pub fn export_flac_with_options(
    song: &PulseSong,
    path: impl AsRef<Path>,
    options: ExportOptions,
) -> PulseResult<()> {
    let mut mixer = song.to_mixer()?;
    export_mixer_flac_with_options(&mut mixer, path, options)
}

/// Exports a song to Standard MIDI File through `tunes::track::Mixer::export_midi`.
pub fn export_midi(song: &PulseSong, path: impl AsRef<Path>) -> PulseResult<()> {
    export_midi_with_options(song, path, ExportOptions::new())
}

/// Exports a song to Standard MIDI File with explicit export options.
pub fn export_midi_with_options(
    song: &PulseSong,
    path: impl AsRef<Path>,
    options: ExportOptions,
) -> PulseResult<()> {
    let path_text = prepare_output_path(path.as_ref())?;
    let mut mixer = song.to_arranged_mixer()?;
    apply_midi_export_options(&mut mixer, options);
    mixer
        .export_midi(&path_text)
        .map_err(|error| PulseError::MidiExportFailed {
            message: error.to_string(),
        })
}

/// Imports a Standard MIDI File through `tunes::track::Mixer::import_midi`.
pub fn import_midi(path: impl AsRef<Path>) -> PulseResult<PulseImportedMidi> {
    let path_text = path_to_string(path.as_ref())?;
    let mixer = Mixer::import_midi(&path_text).map_err(|error| PulseError::MidiImportFailed {
        message: error.to_string(),
    })?;
    Ok(PulseImportedMidi::new(mixer))
}

/// Exports a song based on the output file extension.
pub fn export(song: &PulseSong, path: impl AsRef<Path>) -> PulseResult<()> {
    export_with_options(song, path, ExportOptions::new())
}

/// Exports a song based on the output file extension with explicit export options.
pub fn export_with_options(
    song: &PulseSong,
    path: impl AsRef<Path>,
    options: ExportOptions,
) -> PulseResult<()> {
    match export_format(path.as_ref())? {
        ExportFormat::Wav => export_wav_with_options(song, path, options),
        ExportFormat::Flac => export_flac_with_options(song, path, options),
        ExportFormat::Midi => export_midi_with_options(song, path, options),
    }
}

fn export_format(path: &Path) -> PulseResult<ExportFormat> {
    let Some(extension) = path.extension().and_then(|value| value.to_str()) else {
        return Err(PulseError::UnsupportedExportFormat {
            format: "missing".to_string(),
        });
    };

    match extension.to_ascii_lowercase().as_str() {
        "wav" => Ok(ExportFormat::Wav),
        "flac" => Ok(ExportFormat::Flac),
        "mid" | "midi" => Ok(ExportFormat::Midi),
        format => Err(PulseError::UnsupportedExportFormat {
            format: format.to_string(),
        }),
    }
}