pub fn map_to_scale(values: &[u32], scale: &[u32], root: f32, octave_range: u32) -> Vec<f32> {
if values.is_empty() || scale.is_empty() {
return vec![];
}
let scale_len = scale.len();
let total_notes = scale_len * octave_range as usize;
values
.iter()
.map(|&val| {
let idx = (val as usize) % total_notes;
let octave = idx / scale_len;
let scale_degree = idx % scale_len;
let semitones = (octave * 12) as f32 + scale[scale_degree] as f32;
root * 2_f32.powf(semitones / 12.0)
})
.collect()
}
pub fn map_to_scale_f32(values: &[f32], scale: &[u32], root: f32, octave_range: u32) -> Vec<f32> {
if values.is_empty() || scale.is_empty() {
return vec![];
}
let min = values.iter().cloned().fold(f32::INFINITY, f32::min);
let max = values.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
if (max - min).abs() < f32::EPSILON {
return vec![root; values.len()];
}
let scale_len = scale.len();
let total_notes = scale_len * octave_range as usize;
values
.iter()
.map(|&val| {
let normalized = (val - min) / (max - min);
let idx = (normalized * (total_notes - 1) as f32).round() as usize;
let idx = idx.min(total_notes - 1);
let octave = idx / scale_len;
let scale_degree = idx % scale_len;
let semitones = (octave * 12) as f32 + scale[scale_degree] as f32;
root * 2_f32.powf(semitones / 12.0)
})
.collect()
}
pub struct Scale;
impl Scale {
pub fn major_pentatonic() -> Vec<u32> {
vec![0, 2, 4, 7, 9]
}
pub fn minor_pentatonic() -> Vec<u32> {
vec![0, 3, 5, 7, 10]
}
pub fn major() -> Vec<u32> {
vec![0, 2, 4, 5, 7, 9, 11]
}
pub fn minor() -> Vec<u32> {
vec![0, 2, 3, 5, 7, 8, 10]
}
pub fn harmonic_minor() -> Vec<u32> {
vec![0, 2, 3, 5, 7, 8, 11]
}
pub fn blues() -> Vec<u32> {
vec![0, 3, 5, 6, 7, 10]
}
pub fn chromatic() -> Vec<u32> {
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
}
pub fn whole_tone() -> Vec<u32> {
vec![0, 2, 4, 6, 8, 10]
}
pub fn dorian() -> Vec<u32> {
vec![0, 2, 3, 5, 7, 9, 10]
}
pub fn phrygian() -> Vec<u32> {
vec![0, 1, 3, 5, 7, 8, 10]
}
pub fn lydian() -> Vec<u32> {
vec![0, 2, 4, 6, 7, 9, 11]
}
pub fn mixolydian() -> Vec<u32> {
vec![0, 2, 4, 5, 7, 9, 10]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_map_to_scale_basic() {
use crate::consts::*;
let seq = vec![0, 1, 2, 3, 4];
let scale = Scale::major_pentatonic(); let mapped = map_to_scale(&seq, &scale, C4, 1);
assert_eq!(mapped.len(), 5);
assert!((mapped[0] - C4).abs() < 0.1);
assert!((mapped[1] - D4).abs() < 0.1);
assert!((mapped[2] - E4).abs() < 0.1);
assert!((mapped[3] - G4).abs() < 0.1);
assert!((mapped[4] - A4).abs() < 0.1);
}
#[test]
fn test_map_to_scale_edge_cases() {
use crate::consts::*;
let empty = map_to_scale(&[], &Scale::major(), C4, 2);
assert_eq!(empty, Vec::<f32>::new());
let empty_scale = map_to_scale(&[1, 2, 3], &[], C4, 2);
assert_eq!(empty_scale, Vec::<f32>::new());
}
#[test]
fn test_scale_definitions() {
assert_eq!(Scale::major_pentatonic().len(), 5);
assert_eq!(Scale::minor_pentatonic().len(), 5);
assert_eq!(Scale::major().len(), 7);
assert_eq!(Scale::minor().len(), 7);
assert_eq!(Scale::chromatic().len(), 12);
assert_eq!(Scale::major()[0], 0);
assert_eq!(Scale::minor()[0], 0);
assert!(Scale::major().contains(&4));
assert!(Scale::minor().contains(&3));
}
#[test]
fn test_map_to_scale_with_fibonacci() {
use crate::consts::*;
use crate::sequences::fibonacci;
let fib = fibonacci::generate(10);
let scale = Scale::minor_pentatonic();
let melody = map_to_scale(&fib, &scale, E3, 3);
assert_eq!(melody.len(), 10);
for &freq in &melody {
assert!(freq >= E3);
}
for &freq in &melody {
assert!(freq < E3 * 8.0);
}
}
#[test]
fn test_map_to_scale_wrapping() {
use crate::consts::*;
let seq = vec![0, 5, 10]; let scale = Scale::major_pentatonic(); let mapped = map_to_scale(&seq, &scale, C4, 2);
assert!((mapped[0] - C4).abs() < 0.1); assert!((mapped[1] - C5).abs() < 0.1); }
#[test]
fn test_map_to_scale_f32_basic() {
use crate::consts::*;
let seq = vec![0.0, 0.25, 0.5, 0.75, 1.0];
let scale = Scale::major_pentatonic(); let mapped = map_to_scale_f32(&seq, &scale, C4, 1);
assert_eq!(mapped.len(), 5);
assert!((mapped[0] - C4).abs() < 0.1); assert!((mapped[4] - A4).abs() < 0.1); }
#[test]
fn test_map_to_scale_f32_normalization() {
use crate::consts::*;
let seq = vec![-10.0, -5.0, 0.0, 5.0, 10.0];
let scale = Scale::major_pentatonic();
let mapped = map_to_scale_f32(&seq, &scale, C4, 1);
assert!((mapped[0] - C4).abs() < 0.1); assert!((mapped[4] - A4).abs() < 0.1); }
#[test]
fn test_map_to_scale_f32_edge_cases() {
use crate::consts::*;
let empty = map_to_scale_f32(&[], &Scale::major(), C4, 2);
assert_eq!(empty, Vec::<f32>::new());
let empty_scale = map_to_scale_f32(&[1.0, 2.0, 3.0], &[], C4, 2);
assert_eq!(empty_scale, Vec::<f32>::new());
let constant = map_to_scale_f32(&[5.0, 5.0, 5.0], &Scale::major(), C4, 2);
for &freq in &constant {
assert!((freq - C4).abs() < 0.1); }
}
#[test]
fn test_map_to_scale_f32_with_lorenz() {
use crate::consts::*;
use crate::sequences::lorenz_butterfly;
let butterfly = lorenz_butterfly(50);
let x_vals: Vec<f32> = butterfly.iter().map(|(x, _, _)| *x).collect();
let scale = Scale::minor();
let melody = map_to_scale_f32(&x_vals, &scale, E3, 3);
assert_eq!(melody.len(), 50);
for &freq in &melody {
assert!(freq >= E3);
}
for &freq in &melody {
assert!(freq < E3 * 8.0);
}
}
#[test]
fn test_map_to_scale_f32_octave_spanning() {
use crate::consts::*;
let seq = vec![0.0, 0.5, 1.0];
let scale = Scale::major_pentatonic(); let mapped = map_to_scale_f32(&seq, &scale, C4, 2);
assert!((mapped[0] - C4).abs() < 0.1); assert!(mapped[1] > C4 && mapped[1] < C4 * 4.0);
assert!((mapped[2] - (C4 * 2_f32.powf(21.0/12.0))).abs() < 1.0);
}
#[test]
fn test_map_to_scale_f32_stays_in_scale() {
use crate::consts::*;
let seq = vec![0.1, 0.3, 0.5, 0.7, 0.9];
let scale = Scale::major(); let mapped = map_to_scale_f32(&seq, &scale, C4, 1);
let valid_freqs = vec![C4, D4, E4, F4, G4, A4, B4];
for &freq in &mapped {
let is_valid = valid_freqs.iter().any(|&v| (freq - v).abs() < 0.1);
assert!(is_valid, "Frequency {} not in C major scale", freq);
}
}
}