split cli, cli utilities

This commit is contained in:
Breval Ferrari 2025-05-31 11:36:18 +02:00
parent 1074adb9e7
commit c0e3478ae0
Signed by: breval
GPG key ID: 5310EC237FE283D1
3 changed files with 453 additions and 413 deletions

View file

@ -20,6 +20,7 @@ derive_builder = "0.20"
derive_wrapper = "0.1"
fasteval = "0.2"
flacenc = { version = "0.4", optional = true }
getset = "0.1.5"
hound = { version = "3.5", optional = true }
lazy_static = "1.5"
mp3lame-encoder = { version = "0.2", features = ["std"], optional = true }

445
src/cli/cli.rs Normal file
View file

@ -0,0 +1,445 @@
#![allow(dead_code)]
use std::{
error::Error,
fmt::Debug,
fs::File,
io::{self, Cursor, Read, stdin},
ops::Not,
str::{Bytes, FromStr},
};
use anyhow::anyhow;
use bliplib::compiler::Expression;
use clap::Parser;
use derive_wrapper::AsRef;
use getset::Getters;
use hound::SampleFormat;
use mp3lame_encoder::{Bitrate, Quality};
use nom::{
Finish, Parser as P,
branch::alt,
bytes::complete::tag,
character::complete::{char, u16, usize},
combinator::{rest, success, value},
error::ErrorKind,
sequence::preceded,
};
use nom_locate::{LocatedSpan, position};
use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr};
use thiserror::Error;
const DEFAULT_INSTRUMENT: &str = "sin(2*pi()*(442+442*((n+1)/N))*t)";
const DEFAULT_LENGTH: &str = "2^(2-log(2, l))*(60/T)";
#[derive(Parser)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[command(version, author, about)]
pub(super) enum Cli {
/// Play a song
Play(PlayOpts),
/// Export a song to an audio file or stdout
Export(ExportOpts),
/// Memo menu for examples and general help about syntax and supported audio formats
#[command(subcommand)]
Memo(Memo),
}
#[derive(Parser, Clone, Getters)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[getset(get)]
pub(super) struct PlayOpts {
/// Use this sheet music [default: stdin]
#[command(flatten)]
input: InputGroup,
/// Set available notes ("a,b,c" for example)
#[arg(short, long)]
notes: Vec<String>,
/// Set the signal expression (instrument) used to generate music samples
#[arg(short, long, default_value = DEFAULT_INSTRUMENT)]
instrument: Expression,
/// Set the expression used to generate note lengths in seconds
#[arg(short, long, default_value = DEFAULT_LENGTH)]
length: Expression,
/// Add a variable named VARIABLE (a single letter) and set its initial value to VALUE
#[arg(short, long = "variable", value_name = "VARIABLE=VALUE", value_parser = parse_key_val::<'=', Letter, f64>)]
#[getset(skip)]
variables: Vec<(Letter, f64)>,
/// Add a macro named NAME (single character, not alphanumeric) which expands to EXPANSION when called in sheet music
#[arg(short, long = "macro", value_name = "NAME:EXPANSION", value_parser = parse_key_val::<':', NotALetter, String>)]
#[getset(skip)]
macros: Vec<(NotALetter, String)>,
/// Add a slope expression named NAME which mutates the VARIABLE with the result of EXPR each frame
#[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::<LetterString, Letter, Expression>)]
#[getset(skip)]
slopes: Vec<(String, (LetterString, Expression))>,
}
impl PlayOpts {
pub(super) fn variables(&self) -> impl Iterator<Item = (&char, &f64)> {
self.variables.iter().map(|(c, f)| (c.as_ref(), f))
}
pub(super) fn macros(&self) -> impl Iterator<Item = (&char, &String)> {
self.macros.iter().map(|(c, s)| (c.as_ref(), s))
}
pub(super) fn slopes(&self) -> impl Iterator<Item = (&String, (&String, &Expression))> {
self.slopes.iter().map(|(id, (s, e))| (id, (s.as_ref(), e)))
}
}
impl InputGroup {
pub(super) fn get<'a>(&'a self) -> Box<dyn Read> {
self.input
.as_ref()
.map(|i| Box::new(i.clone().0) as Box<dyn Read>)
.or(self
.sheet_music_string
.as_ref()
.map(|s| Box::new(Cursor::new(s.clone())) as Box<dyn Read>))
.unwrap_or(Box::new(stdin()))
}
}
#[derive(Clone, Copy, AsRef)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct Letter(char);
#[derive(Debug, Error)]
enum LetterError {
#[error("the character '{0}' should be a letter")]
Char(char),
#[error("no characters found")]
Empty,
}
impl FromStr for Letter {
type Err = LetterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let c = s.chars().next().ok_or(Self::Err::Empty)?;
c.is_alphabetic()
.then_some(c)
.map(Self)
.ok_or(Self::Err::Char(c))
}
}
#[derive(Clone, Copy, AsRef)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct NotALetter(char);
#[derive(Debug, Error)]
enum NotALetterError {
#[error("the character '{0}' should not be a letter")]
Char(char),
#[error("no characters found")]
Empty,
}
impl FromStr for NotALetter {
type Err = NotALetterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let c = s.chars().next().ok_or(Self::Err::Empty)?;
c.is_alphabetic()
.not()
.then_some(c)
.map(Self)
.ok_or(Self::Err::Char(c))
}
}
#[derive(Clone, AsRef)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct LetterString(String);
#[derive(Debug, Error)]
enum LetterStringError {
#[error("the string \"{0}\" should be only letters")]
String(String),
#[error("no characters found")]
Empty,
}
impl FromStr for LetterString {
type Err = LetterStringError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.is_empty()
.not()
.then_some(
s.chars()
.all(|c| c.is_alphabetic())
.then_some(s)
.map(str::to_string)
.map(Self)
.ok_or(Self::Err::String(s.to_string())),
)
.ok_or(Self::Err::Empty)?
}
}
/// Parse a single key-value pair
///
/// From https://github.com/clap-rs/clap/blob/6b12a81bafe7b9d013b06981f520ab4c70da5510/examples/typed-derive.rs
fn parse_key_val<const SEP: char, T, U>(
s: &str,
) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
where
T: std::str::FromStr,
T::Err: Error + Send + Sync + 'static,
U: std::str::FromStr,
U::Err: Error + Send + Sync + 'static,
{
let pos = s
.find(SEP)
.ok_or_else(|| format!("invalid KEY{SEP}value: no `{SEP}` found in `{s}`"))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}
fn parse_key_tuple<T, U1, U2>(
s: &str,
) -> Result<(T, (U1, U2)), Box<dyn Error + Send + Sync + 'static>>
where
T: std::str::FromStr,
T::Err: Error + Send + Sync + 'static,
U1: std::str::FromStr,
U1::Err: Error + Send + Sync + 'static,
U2: std::str::FromStr,
U2::Err: Error + Send + Sync + 'static,
{
let pos = s
.find(':')
.ok_or_else(|| format!("invalid KEY:value, no `:` found in `{s}`"))?;
Ok((
s[..pos].parse()?,
parse_key_val::<'=', _, _>(&s[pos + 1..])?,
))
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[group(required = false, multiple = false)]
pub(super) struct InputGroup {
/// Set the path to your sheet music file [default: stdin]
input: Option<ClonableFile>,
/// Use this sheet music instead of reading from a file or stdin
#[arg(short = 'c')]
sheet_music_string: Option<String>,
}
#[derive(Debug)]
struct ClonableFile(File);
impl Clone for ClonableFile {
fn clone(&self) -> Self {
Self(self.0.try_clone().expect("cloning file handle"))
}
}
impl FromStr for ClonableFile {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
File::open(s).map(Self)
}
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub(super) struct ExportOpts {
#[command(flatten)]
playopts: PlayOpts,
/// Audio format to use
#[cfg_attr(
feature = "mp3",
arg(default_value = "mp3 --bitrate 128 --quality best")
)]
#[cfg_attr(
not(feature = "mp3"),
cfg_attr(feature = "raw", arg(default_value = "raw mulaw"))
)]
#[arg(short, long, value_parser = audio_format_parser)]
format: AudioFormat,
/// Output file [default: stdout]
output: Option<ClonableFile>,
}
#[derive(Clone, EnumDiscriminants)]
#[strum_discriminants(derive(EnumString, IntoStaticStr), strum(serialize_all = "lowercase"))]
enum AudioFormat {
Mp3 {
bitrate: Bitrate,
quality: Quality,
},
Wav {
bps: u16,
sample_format: SampleFormat,
},
Flac {
bps: usize,
},
Raw(RawAudioFormat),
}
#[cfg(debug_assertions)]
impl Debug for AudioFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Raw(r) => f.debug_tuple("Raw").field(r).finish(),
_ => self.discriminant().fmt(f),
}
}
}
impl Default for AudioFormat {
fn default() -> Self {
AudioFormat::Raw(Default::default())
}
}
fn audio_format_parser(input: &str) -> Result<AudioFormat, anyhow::Error> {
fn mp3<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
preceded(
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Mp3.into(),
),
u16.or(success(320))
.and(
alt((
value(Quality::Best, tag("b")),
value(Quality::SecondBest, tag("sb")),
value(Quality::NearBest, tag("nb")),
value(Quality::VeryNice, tag("v")),
value(Quality::Nice, tag("n")),
value(Quality::Good, tag("g")),
value(Quality::Decent, tag("d")),
value(Quality::Ok, tag("o")),
value(Quality::SecondWorst, tag("sw")),
value(Quality::Worst, tag("w")),
))
.or(success(Quality::Good)),
)
.and(position)
.map_res(|((b, q), p)| {
Ok(AudioFormat::Mp3 {
bitrate: match b {
8 => Bitrate::Kbps8,
16 => Bitrate::Kbps16,
24 => Bitrate::Kbps24,
32 => Bitrate::Kbps32,
40 => Bitrate::Kbps40,
48 => Bitrate::Kbps48,
64 => Bitrate::Kbps64,
80 => Bitrate::Kbps80,
96 => Bitrate::Kbps96,
112 => Bitrate::Kbps112,
128 => Bitrate::Kbps128,
160 => Bitrate::Kbps160,
192 => Bitrate::Kbps192,
224 => Bitrate::Kbps224,
256 => Bitrate::Kbps256,
320 => Bitrate::Kbps320,
_ => return Err(nom::error::Error::new(p, ErrorKind::Verify)),
},
quality: q,
})
}),
)
}
fn wav<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
preceded(
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Wav.into(),
),
u16.or(success(320))
.and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Int)))
.map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }),
)
}
fn flac<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
preceded(
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Flac.into(),
),
usize
.or(success(320000))
.map(|bps| AudioFormat::Flac { bps }),
)
}
fn parser<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
alt((
mp3::<'a>(),
wav::<'a>(),
flac::<'a>(),
rest.map_res(|r: LocatedSpan<&'a str>| {
Ok::<AudioFormat, nom::error::Error<LocatedSpan<&'a str>>>(AudioFormat::Raw(
RawAudioFormat::try_from(*r)
.map_err(|_| nom::error::Error::new(r, ErrorKind::Verify))?,
))
}),
))
}
parser()
.parse_complete(LocatedSpan::new(input))
.finish()
.map(|(_, o)| o)
.map_err(|e| anyhow!("{e:?}"))
}
#[derive(Parser, Clone, EnumString, Default)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[strum(ascii_case_insensitive)]
enum RawAudioFormat {
ALaw,
F32Be,
F32Le,
F64Be,
F64Le,
#[default]
MuLaw,
S8,
S16Be,
S16Le,
S24Be,
S24Le,
S32Be,
S32Le,
U8,
U16Be,
U16Le,
U24Be,
U24Le,
U32Be,
U32Le,
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub(super) enum Memo {
Syntax,
#[command(subcommand)]
Example(Example),
Formats,
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub(super) enum Example {
List,
N { id: u8 },
}

View file

@ -1,419 +1,13 @@
#![allow(dead_code)]
use std::{
error::Error,
fmt::Debug,
fs::File,
io::{self},
ops::Not,
str::FromStr,
};
use anyhow::anyhow;
use bliplib::compiler::Expression;
mod cli;
use clap::Parser;
use derive_wrapper::AsRef;
use hound::SampleFormat;
use mp3lame_encoder::{Bitrate, Quality};
use nom::{
Finish, Parser as P,
branch::alt,
bytes::complete::tag,
character::complete::{char, u16, usize},
combinator::{rest, success, value},
error::ErrorKind,
sequence::preceded,
};
use nom_locate::{LocatedSpan, position};
use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr};
use thiserror::Error;
const DEFAULT_INSTRUMENT: &str = "sin(2*pi()*(442+442*((n+1)/N))*t)";
const DEFAULT_LENGTH: &str = "2^(2-log(2, l))*(60/T)";
use cli::Cli;
fn main() {
let cli = Cli::parse();
#[cfg(debug_assertions)]
dbg!(cli);
}
#[derive(Parser)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[command(version, author, about)]
enum Cli {
/// Play a song
Play(PlayOpts),
/// Export a song to an audio file or stdout
Export(ExportOpts),
/// Memo menu for examples and general help about syntax and supported audio formats
#[command(subcommand)]
Memo(Memo),
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct PlayOpts {
/// Use this sheet music [default: stdin]
#[command(flatten)]
input: InputGroup,
/// Set available notes ("a,b,c" for example)
#[arg(short, long)]
notes: Vec<String>,
/// Set the signal expression (instrument) used to generate music samples
#[arg(short, long, default_value = DEFAULT_INSTRUMENT)]
instrument: Expression,
/// Set the expression used to generate note lengths in seconds
#[arg(short, long, default_value = DEFAULT_LENGTH)]
length: Expression,
/// Add a variable named VARIABLE (a single letter) and set its initial value to VALUE
#[arg(short, long = "variable", value_name = "VARIABLE=VALUE", value_parser = parse_key_val::<'=', Letter, f64>)]
variables: Vec<(Letter, f64)>,
/// Add a macro named NAME (single character, not alphanumeric) which expands to EXPANSION when called in sheet music
#[arg(short, long = "macro", value_name = "NAME:EXPANSION", value_parser = parse_key_val::<':', NotALetter, String>)]
macros: Vec<(NotALetter, String)>,
/// Add a slope expression named NAME which mutates the VARIABLE with the result of EXPR each frame
#[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::<LetterString, Letter, Expression>)]
slopes: Vec<(String, (LetterString, Expression))>,
}
#[derive(Clone, Copy, AsRef)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct Letter(char);
#[derive(Debug, Error)]
enum LetterError {
#[error("the character '{0}' should be a letter")]
Char(char),
#[error("no characters found")]
Empty,
}
impl FromStr for Letter {
type Err = LetterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let c = s.chars().next().ok_or(Self::Err::Empty)?;
c.is_alphabetic()
.then_some(c)
.map(Self)
.ok_or(Self::Err::Char(c))
use Cli::*;
match cli {
Play(play_opts) => todo!(),
Export(export_opts) => todo!(),
Memo(memo) => todo!(),
}
}
#[derive(Clone, Copy, AsRef)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct NotALetter(char);
#[derive(Debug, Error)]
enum NotALetterError {
#[error("the character '{0}' should not be a letter")]
Char(char),
#[error("no characters found")]
Empty,
}
impl FromStr for NotALetter {
type Err = NotALetterError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let c = s.chars().next().ok_or(Self::Err::Empty)?;
c.is_alphabetic()
.not()
.then_some(c)
.map(Self)
.ok_or(Self::Err::Char(c))
}
}
#[derive(Clone, AsRef)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct LetterString(String);
#[derive(Debug, Error)]
enum LetterStringError {
#[error("the string \"{0}\" should be only letters")]
String(String),
#[error("no characters found")]
Empty,
}
impl FromStr for LetterString {
type Err = LetterStringError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.is_empty()
.not()
.then_some(
s.chars()
.all(|c| c.is_alphabetic())
.then_some(s)
.map(str::to_string)
.map(Self)
.ok_or(Self::Err::String(s.to_string())),
)
.ok_or(Self::Err::Empty)?
}
}
/// Parse a single key-value pair
///
/// From https://github.com/clap-rs/clap/blob/6b12a81bafe7b9d013b06981f520ab4c70da5510/examples/typed-derive.rs
fn parse_key_val<const SEP: char, T, U>(
s: &str,
) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
where
T: std::str::FromStr,
T::Err: Error + Send + Sync + 'static,
U: std::str::FromStr,
U::Err: Error + Send + Sync + 'static,
{
let pos = s
.find(SEP)
.ok_or_else(|| format!("invalid KEY{SEP}value: no `{SEP}` found in `{s}`"))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}
fn parse_key_tuple<T, U1, U2>(
s: &str,
) -> Result<(T, (U1, U2)), Box<dyn Error + Send + Sync + 'static>>
where
T: std::str::FromStr,
T::Err: Error + Send + Sync + 'static,
U1: std::str::FromStr,
U1::Err: Error + Send + Sync + 'static,
U2: std::str::FromStr,
U2::Err: Error + Send + Sync + 'static,
{
let pos = s
.find(':')
.ok_or_else(|| format!("invalid KEY:value, no `:` found in `{s}`"))?;
Ok((
s[..pos].parse()?,
parse_key_val::<'=', _, _>(&s[pos + 1..])?,
))
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[group(required = false, multiple = false)]
struct InputGroup {
/// Set the path to your sheet music file [default: stdin]
input: Option<ClonableFile>,
/// Use this sheet music instead of reading from a file or stdin
#[arg(short = 'c')]
sheet_music_string: Option<String>,
}
#[derive(Debug)]
struct ClonableFile(File);
impl Clone for ClonableFile {
fn clone(&self) -> Self {
Self(self.0.try_clone().expect("cloning file handle"))
}
}
impl FromStr for ClonableFile {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
File::open(s).map(Self)
}
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
struct ExportOpts {
#[command(flatten)]
playopts: PlayOpts,
/// Audio format to use
#[cfg_attr(
feature = "mp3",
arg(default_value = "mp3 --bitrate 128 --quality best")
)]
#[cfg_attr(
not(feature = "mp3"),
cfg_attr(feature = "raw", arg(default_value = "raw mulaw"))
)]
#[arg(short, long, value_parser = audio_format_parser)]
format: AudioFormat,
/// Output file [default: stdout]
output: Option<ClonableFile>,
}
#[derive(Clone, EnumDiscriminants)]
#[strum_discriminants(derive(EnumString, IntoStaticStr), strum(serialize_all = "lowercase"))]
enum AudioFormat {
Mp3 {
bitrate: Bitrate,
quality: Quality,
},
Wav {
bps: u16,
sample_format: SampleFormat,
},
Flac {
bps: usize,
},
Raw(RawAudioFormat),
}
#[cfg(debug_assertions)]
impl Debug for AudioFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Raw(r) => f.debug_tuple("Raw").field(r).finish(),
_ => self.discriminant().fmt(f),
}
}
}
impl Default for AudioFormat {
fn default() -> Self {
AudioFormat::Raw(Default::default())
}
}
fn audio_format_parser(input: &str) -> Result<AudioFormat, anyhow::Error> {
fn mp3<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
preceded(
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Mp3.into(),
),
u16.or(success(320))
.and(
alt((
value(Quality::Best, tag("b")),
value(Quality::SecondBest, tag("sb")),
value(Quality::NearBest, tag("nb")),
value(Quality::VeryNice, tag("v")),
value(Quality::Nice, tag("n")),
value(Quality::Good, tag("g")),
value(Quality::Decent, tag("d")),
value(Quality::Ok, tag("o")),
value(Quality::SecondWorst, tag("sw")),
value(Quality::Worst, tag("w")),
))
.or(success(Quality::Good)),
)
.and(position)
.map_res(|((b, q), p)| {
Ok(AudioFormat::Mp3 {
bitrate: match b {
8 => Bitrate::Kbps8,
16 => Bitrate::Kbps16,
24 => Bitrate::Kbps24,
32 => Bitrate::Kbps32,
40 => Bitrate::Kbps40,
48 => Bitrate::Kbps48,
64 => Bitrate::Kbps64,
80 => Bitrate::Kbps80,
96 => Bitrate::Kbps96,
112 => Bitrate::Kbps112,
128 => Bitrate::Kbps128,
160 => Bitrate::Kbps160,
192 => Bitrate::Kbps192,
224 => Bitrate::Kbps224,
256 => Bitrate::Kbps256,
320 => Bitrate::Kbps320,
_ => return Err(nom::error::Error::new(p, ErrorKind::Verify)),
},
quality: q,
})
}),
)
}
fn wav<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
preceded(
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Wav.into(),
),
u16.or(success(320))
.and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Int)))
.map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }),
)
}
fn flac<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
preceded(
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Flac.into(),
),
usize
.or(success(320000))
.map(|bps| AudioFormat::Flac { bps }),
)
}
fn parser<'a>() -> impl P<
LocatedSpan<&'a str>,
Output = AudioFormat,
Error = nom::error::Error<LocatedSpan<&'a str>>,
> {
alt((
mp3::<'a>(),
wav::<'a>(),
flac::<'a>(),
rest.map_res(|r: LocatedSpan<&'a str>| {
Ok::<AudioFormat, nom::error::Error<LocatedSpan<&'a str>>>(AudioFormat::Raw(
RawAudioFormat::try_from(*r)
.map_err(|_| nom::error::Error::new(r, ErrorKind::Verify))?,
))
}),
))
}
parser()
.parse_complete(LocatedSpan::new(input))
.finish()
.map(|(_, o)| o)
.map_err(|e| anyhow!("{e:?}"))
}
#[derive(Parser, Clone, EnumString, Default)]
#[cfg_attr(debug_assertions, derive(Debug))]
#[strum(ascii_case_insensitive)]
enum RawAudioFormat {
ALaw,
F32Be,
F32Le,
F64Be,
F64Le,
#[default]
MuLaw,
S8,
S16Be,
S16Le,
S24Be,
S24Le,
S32Be,
S32Le,
U8,
U16Be,
U16Le,
U24Be,
U24Le,
U32Be,
U32Le,
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
enum Memo {
Syntax,
#[command(subcommand)]
Example(Example),
Formats,
}
#[derive(Parser, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
enum Example {
List,
N { id: u8 },
}