diff --git a/Cargo.toml b/Cargo.toml index 60ed4b0..896c159 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ thiserror = "1.0.64" derive-new = "0.7.0" naan = "0.1.32" lazy_static = "1.5.0" +tune = "0.35.0" [features] default = ["play", "save"] diff --git a/poc/Ancient Greek Didymus Chromatic.scl b/poc/Ancient Greek Didymus Chromatic.scl new file mode 100755 index 0000000..c43c603 --- /dev/null +++ b/poc/Ancient Greek Didymus Chromatic.scl @@ -0,0 +1,12 @@ +! C:\Audio\Tunings\MTS Packs\World Scales Pack\scl\Ancient Greek Didymus Chromatic.scl +! +Didymus' Chromatic + 7 +! + 16/15 + 10/9 + 4/3 + 3/2 + 8/5 + 5/3 + 2/1 diff --git a/poc/Ancient Greek.kbm b/poc/Ancient Greek.kbm new file mode 100755 index 0000000..23c3f4c --- /dev/null +++ b/poc/Ancient Greek.kbm @@ -0,0 +1,28 @@ +! Ancient Greek scales on E (white notes) +! Size of map: +12 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry in the mapping is mapped to: +64 +! Reference note for which frequency is given: +64 +! Frequency to tune the above note to (floating point e.g. 440.0): +329.627557 +! Scale degree to consider as formal octave: +7 +! Mapping. +0 +1 +1 +2 +2 +3 +3 +4 +5 +5 +6 +6 diff --git a/poc/complete.yml b/poc/complete.yml new file mode 100644 index 0000000..e53bf17 --- /dev/null +++ b/poc/complete.yml @@ -0,0 +1,57 @@ +# fixed +instruments: + # instrument name + sine: + # fixed + expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds) + square: + expr: v*abs(sin(2*PI*f*t)) + # fixed + vars: + # name of the variable + v: 1 # initial value of the variable +scl: + Didymus: poc/Ancient Greek Didymus Chromatic.scl +kbm: + Greek: poc/Ancient Greek.kbm +channels: + melody: + instr: sine + score: + notes: cCdDefFgGaAb + sheet: | + aabc. + 'ab°A + +d+d+d--- + /ff/f\\ + ab>c< + comment?: ;, + start here: ':' + slope: {MODIFIER EXPR score} + note modifier prefix: n + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t + loop: () + loop with count: (COUNT score) + tuple: [] + modifier: ! + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t, diff --git a/poc/nokbm.yml b/poc/nokbm.yml new file mode 100644 index 0000000..3b8165a --- /dev/null +++ b/poc/nokbm.yml @@ -0,0 +1,55 @@ +# fixed +instruments: + # instrument name + sine: + # fixed + expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds) + square: + expr: v*abs(sin(2*PI*f*t)) + # fixed + vars: + # name of the variable + v: 1 # initial value of the variable +scl: + Greek: poc/Ancient Greek Didymus Chromatic.scl +channels: + melody: + instr: sine + score: + notes: cCdDefFgGaAb + sheet: | + aabc. + 'ab°A + +d+d+d--- + /ff/f\\ + ab>c< + comment?: ;, + start here: ':' + slope: {MODIFIER EXPR score} + note modifier prefix: n + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t + loop: () + loop with count: (COUNT score) + tuple: [] + modifier: ! + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t, diff --git a/poc/poc.yml b/poc/noscl.yml similarity index 97% rename from poc/poc.yml rename to poc/noscl.yml index 87f09d9..2bee6f6 100644 --- a/poc/poc.yml +++ b/poc/noscl.yml @@ -10,6 +10,8 @@ instruments: vars: # name of the variable v: 1 # initial value of the variable +kbm: + Greek: poc/Ancient Greek.kbm channels: melody: instr: sine diff --git a/poc/nothing.yml b/poc/nothing.yml new file mode 100644 index 0000000..24916a4 --- /dev/null +++ b/poc/nothing.yml @@ -0,0 +1,49 @@ +# fixed +instruments: + # instrument name + sine: + # fixed + expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds) + square: + expr: v*abs(sin(2*PI*f*t)) +channels: + melody: + instr: sine + score: + notes: cCdDefFgGaAb + sheet: | + aabc. + 'ab°A + +d+d+d--- + /ff/f\\ + ab>c< + comment?: ;, + start here: ':' + slope: {MODIFIER EXPR score} + note modifier prefix: n + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t + loop: () + loop with count: (COUNT score) + tuple: [] + modifier: ! + volume modifier prefix: v + octave modifier prefix: o + length modifier prefix: l + tempo modifier prefix: t, diff --git a/src/bng.rs b/src/bng.rs index d3a2a8d..31cb5ec 100644 --- a/src/bng.rs +++ b/src/bng.rs @@ -7,75 +7,37 @@ use fasteval::{Compiler, Instruction}; pub(super) use instrument::Instrument; pub(super) use score::Atom; use score::Atoms; -#[cfg(debug_assertions)] -use serde::Serialize; use serde::{de::Visitor, Deserialize}; +use tune::scala::{Kbm, Scl}; +mod de; mod instrument; mod score; #[derive(Debug, PartialEq, Wrapper, From)] pub struct Expression(Instruction); -#[cfg(debug_assertions)] -impl Serialize for Expression { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let expr_str = String::new(); - serializer.serialize_str(&format!("{:#?}", self.0)) - } -} - -impl<'de> Deserialize<'de> for Expression { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(ExpressionVisitor) - } -} - -pub struct ExpressionVisitor; - -impl<'de> Visitor<'de> for ExpressionVisitor { - type Value = Expression; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter - .write_str("a math expression following fasteval syntax (https://docs.rs/fasteval)") - } - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - v.parse().map_err(serde::de::Error::custom) - } -} - -impl FromStr for Expression { - type Err = fasteval::Error; - fn from_str(s: &str) -> Result { - let parser = fasteval::Parser::new(); - let mut slab = fasteval::Slab::new(); - Ok(parser - .parse(s, &mut slab.ps)? - .from(&slab.ps) - .compile(&slab.ps, &mut slab.cs) - .into()) - } -} - #[derive(new, Deserialize)] -#[cfg_attr(debug_assertions, derive(Serialize, Debug))] +#[cfg_attr(debug_assertions, derive(Debug))] pub(super) struct Channel { instr: String, score: Atoms, } -#[derive(Deserialize)] -#[cfg_attr(debug_assertions, derive(new, Debug, Serialize))] +#[derive(Deserialize, Default)] +#[cfg_attr(debug_assertions, derive(new, Debug))] +#[serde(deny_unknown_fields)] +#[serde(default)] pub(super) struct BngFile { instruments: HashMap, channels: HashMap, + #[serde(rename = "scl")] + scales: HashMap, + #[serde(rename = "kbm")] + keyboard_mappings: HashMap, } + +#[derive(Debug, Wrapper, From)] +struct Scale(Scl); +#[derive(Debug, Wrapper, From)] +struct KeyboardMapping(Kbm); diff --git a/src/bng/de.rs b/src/bng/de.rs new file mode 100644 index 0000000..29844ff --- /dev/null +++ b/src/bng/de.rs @@ -0,0 +1,63 @@ +use std::{fmt::Display, marker::PhantomData, str::FromStr}; + +use fasteval::Compiler; +use serde::{de::Visitor, Deserialize}; + +use super::{Expression, KeyboardMapping, Scale}; + +mod from_str; + +struct FromStrVisitor<'a, V> { + expecting: &'a str, + phantom: PhantomData, +} + +impl<'a, V> FromStrVisitor<'a, V> { + fn new(expecting: &'a str) -> Self { + FromStrVisitor { + expecting, + phantom: PhantomData, + } + } +} + +impl<'de, 'a, V> Visitor<'de> for FromStrVisitor<'a, V> +where + V: FromStr, + ::Err: Display, +{ + type Value = V; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(self.expecting) + } + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + v.parse::().map_err(serde::de::Error::custom) + } +} + +macro_rules! deserialize_fromstr_expect { + ($type:ty, $expect:literal) => { + impl<'de> serde::de::Deserialize<'de> for $type { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(FromStrVisitor::new($expect)) + } + } + }; +} + +deserialize_fromstr_expect!( + Expression, + "a math expression following fasteval syntax (https://docs.rs/fasteval)" +); + +deserialize_fromstr_expect!(Scale, "a .scl file containing a Scala scale"); +deserialize_fromstr_expect!( + KeyboardMapping, + "a .kbm file containing a Scala keyboard mapping" +); diff --git a/src/bng/de/from_str.rs b/src/bng/de/from_str.rs new file mode 100644 index 0000000..f2e9996 --- /dev/null +++ b/src/bng/de/from_str.rs @@ -0,0 +1,53 @@ +use std::{fs::File, io, str::FromStr}; + +use amplify::{From, Wrapper}; +use fasteval::Compiler; +use thiserror::Error; +use tune::scala::{Kbm, KbmImportError, Scl, SclImportError}; + +use crate::bng::{KeyboardMapping, Scale}; + +use super::super::Expression; + +impl FromStr for Expression { + type Err = fasteval::Error; + fn from_str(s: &str) -> Result { + let parser = fasteval::Parser::new(); + let mut slab = fasteval::Slab::new(); + Ok(parser + .parse(s, &mut slab.ps)? + .from(&slab.ps) + .compile(&slab.ps, &mut slab.cs) + .into()) + } +} + +#[derive(Debug, Error)] +#[error("{0:?}")] +pub struct ScalaImportError(E); + +impl FromStr for Scale { + type Err = ScalaImportError; + fn from_str(s: &str) -> Result { + Scl::import( + File::open(s) + .map_err(Into::into) + .map_err(ScalaImportError)?, + ) + .map(Scale::from) + .map_err(ScalaImportError) + } +} + +impl FromStr for KeyboardMapping { + type Err = ScalaImportError; + fn from_str(s: &str) -> Result { + Kbm::import( + File::open(s) + .map_err(Into::into) + .map_err(ScalaImportError)?, + ) + .map(KeyboardMapping::from) + .map_err(ScalaImportError) + } +} diff --git a/src/bng/instrument.rs b/src/bng/instrument.rs index dce3256..6dc9cc8 100644 --- a/src/bng/instrument.rs +++ b/src/bng/instrument.rs @@ -1,15 +1,13 @@ use std::collections::HashMap; +#[cfg(debug_assertions)] +use super::Expression as Instruction; use derive_new::new; use derived_deref::Deref; use serde::Deserialize; -#[cfg(debug_assertions)] -use serde::Serialize; - -use super::Expression as Instruction; #[derive(Deref, new, Deserialize)] -#[cfg_attr(debug_assertions, derive(Serialize, Debug))] +#[cfg_attr(debug_assertions, derive(Debug))] pub struct Instrument { #[target] expr: Instruction, diff --git a/src/bng/score.rs b/src/bng/score.rs index 38b7609..71ffa77 100644 --- a/src/bng/score.rs +++ b/src/bng/score.rs @@ -15,8 +15,6 @@ use nom::{ multi::many0, sequence::{preceded, terminated}, }; -#[cfg(debug_assertions)] -use serde::Serialize; use serde::{ de::{self as serde_de, Visitor}, Deserialize, @@ -31,7 +29,7 @@ pub use de::*; use super::Expression as Instruction; #[derive(Deref, From, Default, Clone)] -#[cfg_attr(debug_assertions, derive(Serialize, Debug, PartialEq))] +#[cfg_attr(debug_assertions, derive(Debug, PartialEq))] pub struct Atoms(Vec); #[cfg_attr(debug_assertions, derive(Debug, PartialEq))] @@ -61,16 +59,6 @@ impl Clone for Atom { } } -#[cfg(debug_assertions)] -impl Serialize for Atom { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str("atom") - } -} - #[derive(Clone, ModifierParser)] #[cfg_attr(debug_assertions, derive(Debug, PartialEq))] pub enum Modifier { diff --git a/src/main.rs b/src/main.rs index 256e7f8..2f50c07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,26 +28,3 @@ fn main() -> Result<(), Error> { } Ok(()) } - -#[cfg(debug_assertions)] -fn bngfile_generator() -> BngFile { - BngFile::new( - HashMap::from([ - ( - "sine".to_string(), - Instrument::new("sin(2*PI*f*t)".parse().unwrap(), None), - ), - ( - "square".to_string(), - Instrument::new( - "v*abs(sin(2*PI*f*t))".parse().unwrap(), - Some(HashMap::from([("v".to_string(), 1f64)])), - ), - ), - ]), - HashMap::::from([( - "melody".to_string(), - Channel::new("sine".to_string(), vec![].into()), - )]), - ) -}