use std::{cmp::Ordering, io, iter, mem};
use structopt::StructOpt;
use tune::{math, pitch::Ratio};
use crate::App;
#[derive(StructOpt)]
pub(crate) enum MosCommand {
#[structopt(name = "find")]
FindMoses(FindMosesOptions),
#[structopt(name = "gen")]
FindGenerators(FindGeneratorsOptions),
}
impl MosCommand {
pub fn run(&self, app: &mut App) -> io::Result<()> {
match self {
MosCommand::FindMoses(options) => options.run(app),
MosCommand::FindGenerators(options) => options.run(app),
}
}
}
#[derive(StructOpt)]
pub(crate) struct FindMosesOptions {
#[structopt(long = "per", default_value = "2.0")]
period: Ratio,
generator: Ratio,
#[structopt(long = "chroma", default_value = "0.5c")]
threshold: Ratio,
}
impl FindMosesOptions {
pub fn run(&self, app: &mut App) -> io::Result<()> {
for mos in Mos::new(self.generator.num_equal_steps_of_size(self.period)).children() {
if self.period.repeated(mos.chroma()) >= self.threshold {
app.writeln(format_args!(
"num_notes = {}, {}L{}s, L = {:#.0}, s = {:#.0}",
mos.num_steps(),
mos.num_large_steps,
mos.num_small_steps,
self.period.repeated(mos.large_step_size),
self.period.repeated(mos.small_step_size),
))?;
} else {
return app.writeln(format_args!(
"num_notes = {}, L = s = {:#.0}",
mos.num_steps(),
self.period.repeated(mos.large_step_size),
));
}
}
Ok(())
}
}
#[derive(StructOpt)]
pub(crate) struct FindGeneratorsOptions {
#[structopt(long = "per", default_value = "2.0")]
period: Ratio,
num_large_steps: u16,
num_small_steps: u16,
}
impl FindGeneratorsOptions {
pub fn run(&self, app: &mut App) -> io::Result<()> {
let gcd = math::gcd_u16(self.num_large_steps, self.num_small_steps);
let period = self.period.divided_into_equal_steps(gcd);
let num_large_steps = self.num_large_steps / gcd;
let num_small_steps = self.num_small_steps / gcd;
let (equalized, paucitonic) = get_gen_range(num_large_steps, num_small_steps);
app.writeln(format_args!(
"{}L{}s: \
period={:#.0}, \
equalized_gen = {}\\{} ({:#.0}), \
proper_gen = {}\\{} ({:#.0}), \
paucitonic_gen = {}\\{} ({:#.0})",
num_large_steps,
num_small_steps,
period,
equalized.0,
equalized.1,
period
.repeated(equalized.0)
.divided_into_equal_steps(equalized.1),
equalized.0 + paucitonic.0,
equalized.1 + paucitonic.1,
period
.repeated(equalized.0 + paucitonic.0)
.divided_into_equal_steps(equalized.1 + paucitonic.1),
paucitonic.0,
paucitonic.1,
period
.repeated(paucitonic.0)
.divided_into_equal_steps(paucitonic.1),
))
}
}
#[derive(Clone, Debug)]
struct Mos {
num_large_steps: u16,
num_small_steps: u16,
large_step_size: f64,
small_step_size: f64,
}
impl Mos {
fn new(generator: f64) -> Self {
Mos {
num_large_steps: 1,
num_small_steps: 0,
large_step_size: 1.0,
small_step_size: generator.rem_euclid(1.0),
}
}
fn equalized(num_large_steps: u16, num_small_steps: u16) -> Self {
let num_steps = f64::from(num_large_steps) + f64::from(num_small_steps);
Self {
num_large_steps,
num_small_steps,
large_step_size: 1.0 / num_steps,
small_step_size: 1.0 / num_steps,
}
}
fn paucitonic(num_large_steps: u16, num_small_steps: u16) -> Self {
Self {
num_large_steps,
num_small_steps,
large_step_size: 1.0 / f64::from(num_large_steps),
small_step_size: 0.0,
}
}
fn children(&self) -> impl Iterator<Item = Mos> {
let mut mos = self.clone();
iter::from_fn(move || {
let child = mos.child();
if let Some(child) = &child {
mos = child.clone();
}
child
})
}
fn child(&self) -> Option<Mos> {
let mut result = self.clone();
result.num_small_steps = result.num_small_steps.checked_add(result.num_large_steps)?;
result.large_step_size -= result.small_step_size;
if result.small_step_size > result.large_step_size {
mem::swap(&mut result.large_step_size, &mut result.small_step_size);
mem::swap(&mut result.num_large_steps, &mut result.num_small_steps);
}
Some(result)
}
fn genesis_mos(&self) -> Mos {
let mut mos = self.clone();
loop {
let parent = mos.parent();
if let Some(parent) = parent {
mos = parent;
} else {
return mos;
}
}
}
fn parent(&self) -> Option<Mos> {
if self.num_large_steps == 0 || self.num_small_steps == 0 {
return None;
}
match self.num_large_steps.cmp(&self.num_small_steps) {
Ordering::Greater => Some(Self {
num_large_steps: self.num_small_steps,
num_small_steps: self.num_large_steps - self.num_small_steps,
large_step_size: self.large_step_size + self.small_step_size,
small_step_size: self.large_step_size,
}),
Ordering::Less => Some(Self {
num_large_steps: self.num_large_steps,
num_small_steps: self.num_small_steps - self.num_large_steps,
large_step_size: self.large_step_size + self.small_step_size,
small_step_size: self.small_step_size,
}),
Ordering::Equal => None,
}
}
fn num_steps(&self) -> u32 {
u32::from(self.num_large_steps) + u32::from(self.num_small_steps)
}
fn chroma(&self) -> f64 {
self.large_step_size - self.small_step_size
}
}
fn get_gen_range(num_large_steps: u16, num_small_steps: u16) -> ((u32, u32), (u32, u32)) {
let equalized_gen = Mos::equalized(num_large_steps, num_small_steps).genesis_mos();
let paucitonic_gen = Mos::paucitonic(num_large_steps, num_small_steps).genesis_mos();
let num_steps = u32::from(num_large_steps) + u32::from(num_small_steps);
let (equalized_step, paucitonic_step) =
if equalized_gen.large_step_size < paucitonic_gen.large_step_size {
(
equalized_gen.large_step_size,
paucitonic_gen.large_step_size,
)
} else {
(
equalized_gen.small_step_size,
paucitonic_gen.small_step_size,
)
};
(
(
(equalized_step * f64::from(num_steps)).round() as u32,
num_steps,
),
(
(paucitonic_step * f64::from(num_large_steps)).round() as u32,
u32::from(num_large_steps),
),
)
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use super::*;
#[test]
fn mos_generator_ranges() {
let mut output = String::new();
for num_notes in 2..=50 {
writeln!(&mut output, "---- MOSes with {} notes ----", num_notes).unwrap();
for num_large_steps in 1..num_notes {
let num_small_steps = num_notes - num_large_steps;
let (equalized, paucitonic) = get_gen_range(num_large_steps, num_small_steps);
writeln!(
&mut output,
"{}L{}s: equalized_gen = {}\\{}, proper_gen = {}\\{}, paucitonic_gen = {}\\{}",
num_large_steps,
num_small_steps,
equalized.0,
equalized.1,
equalized.0 + paucitonic.0,
equalized.1 + paucitonic.1,
paucitonic.0,
paucitonic.1,
)
.unwrap();
}
}
std::fs::write("../mos-generators-2-to-50.txt", &output).unwrap();
assert_eq!(output, include_str!("../../mos-generators-2-to-50.txt"));
}
}