bliplab 0.0.3

Tool to combine several BLIP channels in a song
Documentation
#![doc = "../README.md"]

use std::{borrow::Cow, collections::HashMap, path::PathBuf};

use bliplib::compiler::Expression;
use derive_new::new;
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};

#[cfg(feature = "example-skip-defaults")]
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
    t == &T::default()
}

/// Entry point to a BLIPlab song. Made of BLIP channels.
#[derive(new, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Song<'a> {
    #[serde(default)]
    #[cfg_attr(
        feature = "example-skip-defaults",
        serde(skip_serializing_if = "is_default")
    )]
    global_variables: Globals,
    #[new(into_iter = "(Cow<'a, str>, Channel<'a>)")]
    channels: HashMap<Cow<'a, str>, Channel<'a>>,
}

/// Global variables used by the compiler for rendering every sample.
/// They are made global for the sake of consistency but can be modified.
#[derive(new, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Globals {
    #[new(into)]
    absolute_length: char,
    #[new(into)]
    note_index: char,
}

impl Default for Globals {
    fn default() -> Self {
        Self::new('L', 'n')
    }
}

/// BLIP channel. Contains a song for just one instrument.
#[serde_as]
#[derive(new, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Channel<'a> {
    input: PathOrString<'a>,
    #[new(into_iter = "Cow<'a, str>")]
    notes: Vec<Cow<'a, str>>,
    #[serde_as(as = "DisplayFromStr")]
    instrument: Expression,
    #[new(into_iter = "(char, f64)")]
    #[serde(default)]
    #[cfg_attr(
        feature = "example-skip-defaults",
        serde(skip_serializing_if = "is_default")
    )]
    variables: HashMap<char, f64>,
    #[new(into_iter = "(char, Cow<'a, str>)")]
    #[serde(default)]
    #[cfg_attr(
        feature = "example-skip-defaults",
        serde(skip_serializing_if = "is_default")
    )]
    macros: HashMap<char, Cow<'a, str>>,
    #[new(into_iter = "(Cow<'a, str>, VariableChange)")]
    #[serde(default = "default_slope")]
    slopes: HashMap<Cow<'a, str>, VariableChange>,
}

fn default_slope<'a>() -> HashMap<Cow<'a, str>, VariableChange> {
    [(
        "L".into(),
        VariableChange::new(
            'L',
            "2^(2-log(2, l))*(60/T)"
                .parse()
                .expect("oops, my default length expression doesn't work"),
        ),
    )]
    .into()
}

/// Same as [bliplib::compiler::VariableChange] but with named fields and serde support.
/// Both types are compatible with [From].
#[serde_as]
#[derive(new, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VariableChange {
    target: char,
    #[serde_as(as = "DisplayFromStr")]
    change: Expression,
}

impl From<bliplib::compiler::VariableChange> for VariableChange {
    fn from(value: bliplib::compiler::VariableChange) -> Self {
        Self {
            target: value.0,
            change: value.1,
        }
    }
}

impl From<VariableChange> for bliplib::compiler::VariableChange {
    fn from(value: VariableChange) -> Self {
        Self(value.target, value.change)
    }
}

/// BLIP [Channel] MML input, either a file or a string.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[serde(deny_unknown_fields)]
pub enum PathOrString<'a> {
    /// Read the MML from a BLIP file.
    Path(PathBuf),
    /// Read the MML from the specified string.
    String(Cow<'a, str>),
}