add Scl / Kbm parsing

This commit is contained in:
Ponj 2024-11-13 16:18:56 -05:00
parent 219d3e88fb
commit 36b0fe394c
Signed by: p6nj
GPG key ID: 6FED68D87C479A59
13 changed files with 340 additions and 95 deletions

View file

@ -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"]

View file

@ -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

28
poc/Ancient Greek.kbm Executable file
View file

@ -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

57
poc/complete.yml Normal file
View file

@ -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<ba
;this is a comment (or lyrics whatever),
C:CC
(3deff)
(deff)
[ffe]
{l 1-cos((PI*x)/2),acced}
abbc!o5cc!v15feed!l4fedd!t60Gdd
; syntax :
rest: .
pizz.:
volume: +-
length: /\
octave: ><
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,

55
poc/nokbm.yml Normal file
View file

@ -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<ba
;this is a comment (or lyrics whatever),
C:CC
(3deff)
(deff)
[ffe]
{l 1-cos((PI*x)/2),acced}
abbc!o5cc!v15feed!l4fedd!t60Gdd
; syntax :
rest: .
pizz.:
volume: +-
length: /\
octave: ><
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,

View file

@ -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

49
poc/nothing.yml Normal file
View file

@ -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<ba
;this is a comment (or lyrics whatever),
C:CC
(3deff)
(deff)
[ffe]
{l 1-cos((PI*x)/2),acced}
abbc!o5cc!v15feed!l4fedd!t60Gdd
; syntax :
rest: .
pizz.:
volume: +-
length: /\
octave: ><
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,

View file

@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let expr_str = String::new();
serializer.serialize_str(&format!("{:#?}", self.0))
}
}
impl<'de> Deserialize<'de> for Expression {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: &str) -> Result<Self::Value, E>
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<Self, Self::Err> {
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<String, Instrument>,
channels: HashMap<String, Channel>,
#[serde(rename = "scl")]
scales: HashMap<String, Scale>,
#[serde(rename = "kbm")]
keyboard_mappings: HashMap<String, KeyboardMapping>,
}
#[derive(Debug, Wrapper, From)]
struct Scale(Scl);
#[derive(Debug, Wrapper, From)]
struct KeyboardMapping(Kbm);

63
src/bng/de.rs Normal file
View file

@ -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<V>,
}
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,
<V as 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<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse::<V>().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<D>(deserializer: D) -> Result<Self, D::Error>
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"
);

53
src/bng/de/from_str.rs Normal file
View file

@ -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<Self, Self::Err> {
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>(E);
impl FromStr for Scale {
type Err = ScalaImportError<SclImportError>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<KbmImportError>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Kbm::import(
File::open(s)
.map_err(Into::into)
.map_err(ScalaImportError)?,
)
.map(KeyboardMapping::from)
.map_err(ScalaImportError)
}
}

View file

@ -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,

View file

@ -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<Atom>);
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
@ -61,16 +59,6 @@ impl Clone for Atom {
}
}
#[cfg(debug_assertions)]
impl Serialize for Atom {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str("atom")
}
}
#[derive(Clone, ModifierParser)]
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
pub enum Modifier {

View file

@ -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::<String, Channel>::from([(
"melody".to_string(),
Channel::new("sine".to_string(), vec![].into()),
)]),
)
}