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"
|
derive-new = "0.7.0"
|
||||||
naan = "0.1.32"
|
naan = "0.1.32"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
tune = "0.35.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["play", "save"]
|
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:
|
vars:
|
||||||
# name of the variable
|
# name of the variable
|
||||||
v: 1 # initial value of the variable
|
v: 1 # initial value of the variable
|
||||||
|
kbm:
|
||||||
|
Greek: poc/Ancient Greek.kbm
|
||||||
channels:
|
channels:
|
||||||
melody:
|
melody:
|
||||||
instr: sine
|
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 instrument::Instrument;
|
||||||
pub(super) use score::Atom;
|
pub(super) use score::Atom;
|
||||||
use score::Atoms;
|
use score::Atoms;
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde::{de::Visitor, Deserialize};
|
use serde::{de::Visitor, Deserialize};
|
||||||
|
use tune::scala::{Kbm, Scl};
|
||||||
|
|
||||||
|
mod de;
|
||||||
mod instrument;
|
mod instrument;
|
||||||
mod score;
|
mod score;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Wrapper, From)]
|
#[derive(Debug, PartialEq, Wrapper, From)]
|
||||||
pub struct Expression(Instruction);
|
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)]
|
#[derive(new, Deserialize)]
|
||||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||||
pub(super) struct Channel {
|
pub(super) struct Channel {
|
||||||
instr: String,
|
instr: String,
|
||||||
score: Atoms,
|
score: Atoms,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Default)]
|
||||||
#[cfg_attr(debug_assertions, derive(new, Debug, Serialize))]
|
#[cfg_attr(debug_assertions, derive(new, Debug))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[serde(default)]
|
||||||
pub(super) struct BngFile {
|
pub(super) struct BngFile {
|
||||||
instruments: HashMap<String, Instrument>,
|
instruments: HashMap<String, Instrument>,
|
||||||
channels: HashMap<String, Channel>,
|
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;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
use super::Expression as Instruction;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use derived_deref::Deref;
|
use derived_deref::Deref;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use super::Expression as Instruction;
|
|
||||||
|
|
||||||
#[derive(Deref, new, Deserialize)]
|
#[derive(Deref, new, Deserialize)]
|
||||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||||
pub struct Instrument {
|
pub struct Instrument {
|
||||||
#[target]
|
#[target]
|
||||||
expr: Instruction,
|
expr: Instruction,
|
||||||
|
|
|
@ -15,8 +15,6 @@ use nom::{
|
||||||
multi::many0,
|
multi::many0,
|
||||||
sequence::{preceded, terminated},
|
sequence::{preceded, terminated},
|
||||||
};
|
};
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde::{
|
use serde::{
|
||||||
de::{self as serde_de, Visitor},
|
de::{self as serde_de, Visitor},
|
||||||
Deserialize,
|
Deserialize,
|
||||||
|
@ -31,7 +29,7 @@ pub use de::*;
|
||||||
use super::Expression as Instruction;
|
use super::Expression as Instruction;
|
||||||
|
|
||||||
#[derive(Deref, From, Default, Clone)]
|
#[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>);
|
pub struct Atoms(Vec<Atom>);
|
||||||
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
#[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)]
|
#[derive(Clone, ModifierParser)]
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
||||||
pub enum Modifier {
|
pub enum Modifier {
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -28,26 +28,3 @@ fn main() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
Ok(())
|
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