add Scl / Kbm parsing
This commit is contained in:
parent
219d3e88fb
commit
36b0fe394c
13 changed files with 340 additions and 95 deletions
|
@ -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"]
|
||||
|
|
12
poc/Ancient Greek Didymus Chromatic.scl
Executable file
12
poc/Ancient Greek Didymus Chromatic.scl
Executable 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
28
poc/Ancient Greek.kbm
Executable 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
57
poc/complete.yml
Normal 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
55
poc/nokbm.yml
Normal 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,
|
|
@ -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
49
poc/nothing.yml
Normal 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,
|
70
src/bng.rs
70
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<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
63
src/bng/de.rs
Normal 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
53
src/bng/de/from_str.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
23
src/main.rs
23
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::<String, Channel>::from([(
|
||||
"melody".to_string(),
|
||||
Channel::new("sine".to_string(), vec![].into()),
|
||||
)]),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue