use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HarmonicRing {
pub n: u32,
}
impl HarmonicRing {
pub fn new(n: u32) -> Self {
Self {
n: if n == 0 { 12 } else { n },
}
}
pub fn chromatic() -> Self {
Self::new(12)
}
pub fn add(&self, a: i32, b: i32) -> u32 {
((a + b).rem_euclid(self.n as i32)) as u32
}
pub fn mul(&self, a: i32, b: i32) -> u32 {
((a * b).rem_euclid(self.n as i32)) as u32
}
pub fn neg(&self, a: i32) -> u32 {
((-a).rem_euclid(self.n as i32)) as u32
}
pub fn sub(&self, a: i32, b: i32) -> u32 {
self.add(a, self.neg(b) as i32)
}
pub fn is_unit(&self, a: i32) -> bool {
gcd(a.rem_euclid(self.n as i32) as u32, self.n) == 1
}
pub fn inv(&self, a: i32) -> Option<u32> {
if !self.is_unit(a) {
return None;
}
(1..self.n).find(|&x| self.mul(a, x as i32) == 1)
}
pub fn transpose(&self, notes: &[u32], interval: i32) -> Vec<u32> {
notes
.iter()
.map(|&n| self.add(n as i32, interval))
.collect()
}
pub fn invert(&self, notes: &[u32], axis: i32) -> Vec<u32> {
notes.iter().map(|&n| self.sub(axis, n as i32)).collect()
}
}
fn gcd(a: u32, b: u32) -> u32 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chromatic() {
let r = HarmonicRing::chromatic();
assert_eq!(r.add(7, 5), 0);
assert_eq!(r.mul(7, 7), 1);
}
#[test]
fn test_inv() {
let r = HarmonicRing::chromatic();
assert_eq!(r.inv(7), Some(7));
assert_eq!(r.inv(2), None);
}
#[test]
fn test_transpose() {
let r = HarmonicRing::chromatic();
assert_eq!(r.transpose(&[0, 4, 7], 5), vec![5, 9, 0]);
}
}