1use glam::Vec3;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ScaleType {
15 Major,
16 NaturalMinor,
17 HarmonicMinor,
18 MelodicMinor,
19 Dorian,
20 Phrygian,
21 Lydian,
22 Mixolydian,
23 Locrian,
24 WholeTone,
25 Diminished,
26 Chromatic,
27 Pentatonic,
28 PentatonicMinor,
29 Blues,
30}
31
32impl ScaleType {
33 pub fn intervals(self) -> &'static [u8] {
35 match self {
36 ScaleType::Major => &[0, 2, 4, 5, 7, 9, 11],
37 ScaleType::NaturalMinor => &[0, 2, 3, 5, 7, 8, 10],
38 ScaleType::HarmonicMinor => &[0, 2, 3, 5, 7, 8, 11],
39 ScaleType::MelodicMinor => &[0, 2, 3, 5, 7, 9, 11],
40 ScaleType::Dorian => &[0, 2, 3, 5, 7, 9, 10],
41 ScaleType::Phrygian => &[0, 1, 3, 5, 7, 8, 10],
42 ScaleType::Lydian => &[0, 2, 4, 6, 7, 9, 11],
43 ScaleType::Mixolydian => &[0, 2, 4, 5, 7, 9, 10],
44 ScaleType::Locrian => &[0, 1, 3, 5, 6, 8, 10],
45 ScaleType::WholeTone => &[0, 2, 4, 6, 8, 10],
46 ScaleType::Diminished => &[0, 2, 3, 5, 6, 8, 9, 11],
47 ScaleType::Chromatic => &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
48 ScaleType::Pentatonic => &[0, 2, 4, 7, 9],
49 ScaleType::PentatonicMinor=> &[0, 3, 5, 7, 10],
50 ScaleType::Blues => &[0, 3, 5, 6, 7, 10],
51 }
52 }
53}
54
55#[derive(Clone, Copy, Debug)]
57pub struct Scale {
58 pub root: u8,
60 pub scale: ScaleType,
61}
62
63impl Scale {
64 pub fn new(root: u8, scale: ScaleType) -> Self { Self { root, scale } }
65
66 pub fn degree(&self, degree: i32, octave_offset: i32) -> u8 {
68 let intervals = self.scale.intervals();
69 let n = intervals.len() as i32;
70 let oct_shift = degree.div_euclid(n);
71 let idx = degree.rem_euclid(n) as usize;
72 let semitone = intervals[idx] as i32 + oct_shift * 12 + octave_offset * 12;
73 ((self.root as i32 + semitone).clamp(0, 127)) as u8
74 }
75
76 pub fn midi_to_hz(midi: u8) -> f32 {
78 440.0 * 2f32.powf((midi as f32 - 69.0) / 12.0)
79 }
80
81 pub fn freq(&self, degree: i32, octave: i32) -> f32 {
83 let base_octave = (self.root as i32 / 12) - 1;
84 Self::midi_to_hz(self.degree(degree, octave - base_octave))
85 }
86
87 pub fn octave_freqs(&self, octave: i32) -> Vec<f32> {
89 (0..self.scale.intervals().len() as i32)
90 .map(|d| self.freq(d, octave))
91 .collect()
92 }
93}
94
95#[derive(Clone, Debug)]
99pub struct Chord {
100 pub degrees: Vec<i32>, pub octave: i32,
102 pub name: String,
103}
104
105impl Chord {
106 pub fn new(degrees: Vec<i32>, octave: i32, name: impl Into<String>) -> Self {
107 Self { degrees, octave, name: name.into() }
108 }
109
110 pub fn triad_major(octave: i32) -> Self { Self::new(vec![0, 2, 4], octave, "Maj") }
112 pub fn triad_minor(octave: i32) -> Self { Self::new(vec![0, 2, 4], octave, "min") }
113 pub fn seventh(octave: i32) -> Self { Self::new(vec![0, 2, 4, 6], octave, "7th") }
114 pub fn sus2(octave: i32) -> Self { Self::new(vec![0, 1, 4], octave, "sus2") }
115 pub fn sus4(octave: i32) -> Self { Self::new(vec![0, 3, 4], octave, "sus4") }
116 pub fn power(octave: i32) -> Self { Self::new(vec![0, 4], octave, "5th") }
117 pub fn diminished(octave: i32) -> Self { Self::new(vec![0, 2, 5], octave, "dim") }
118 pub fn augmented(octave: i32) -> Self { Self::new(vec![0, 2, 5], octave, "aug") } pub fn add9(octave: i32) -> Self { Self::new(vec![0, 2, 4, 8], octave, "add9") }
120
121 pub fn frequencies(&self, scale: &Scale) -> Vec<f32> {
123 self.degrees.iter().map(|&d| scale.freq(d + 0, self.octave)).collect()
124 }
125}
126
127#[derive(Clone, Debug)]
131pub struct Progression {
132 pub chords: Vec<(Chord, f32)>, pub current: usize,
134 pub beat_clock: f32,
135}
136
137impl Progression {
138 pub fn new(chords: Vec<(Chord, f32)>) -> Self {
139 Self { chords, current: 0, beat_clock: 0.0 }
140 }
141
142 pub fn one_five_six_four(octave: i32) -> Self {
144 Self::new(vec![
145 (Chord::new(vec![0, 2, 4], octave, "I"), 4.0),
146 (Chord::new(vec![4, 6, 1], octave, "V"), 4.0),
147 (Chord::new(vec![5, 0, 2], octave, "vi"), 4.0),
148 (Chord::new(vec![3, 5, 0], octave, "IV"), 4.0),
149 ])
150 }
151
152 pub fn minor_pop(octave: i32) -> Self {
154 Self::new(vec![
155 (Chord::new(vec![0, 2, 4], octave, "i"), 4.0),
156 (Chord::new(vec![5, 0, 2], octave, "VI"), 4.0),
157 (Chord::new(vec![2, 4, 6], octave, "III"), 4.0),
158 (Chord::new(vec![6, 1, 3], octave, "VII"), 4.0),
159 ])
160 }
161
162 pub fn two_five_one(octave: i32) -> Self {
164 Self::new(vec![
165 (Chord::new(vec![1, 3, 5, 0], octave, "ii7"), 4.0),
166 (Chord::new(vec![4, 6, 1, 3], octave, "V7"), 4.0),
167 (Chord::new(vec![0, 2, 4, 6], octave, "Imaj7"),8.0),
168 ])
169 }
170
171 pub fn tick(&mut self, beat_delta: f32) -> Option<&Chord> {
173 if self.chords.is_empty() { return None; }
174 self.beat_clock += beat_delta;
175 let current_dur = self.chords[self.current].1;
176 if self.beat_clock >= current_dur {
177 self.beat_clock -= current_dur;
178 self.current = (self.current + 1) % self.chords.len();
179 return Some(&self.chords[self.current].0);
180 }
181 None
182 }
183
184 pub fn current_chord(&self) -> Option<&Chord> {
185 self.chords.get(self.current).map(|(c, _)| c)
186 }
187
188 pub fn progress_in_chord(&self) -> f32 {
189 let dur = self.chords.get(self.current).map(|(_, d)| *d).unwrap_or(1.0);
190 (self.beat_clock / dur).clamp(0.0, 1.0)
191 }
192}
193
194#[derive(Clone, Debug)]
198pub struct RhythmPattern {
199 pub hits: Vec<f32>, pub length: f32, pub cursor: f32, pub next: usize, }
204
205impl RhythmPattern {
206 pub fn new(hits: Vec<f32>, length: f32) -> Self {
207 let next = 0;
208 Self { hits, length, cursor: 0.0, next }
209 }
210
211 pub fn four_on_floor() -> Self {
212 Self::new(vec![0.0, 1.0, 2.0, 3.0], 4.0)
213 }
214
215 pub fn eighth_notes() -> Self {
216 Self::new(vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], 4.0)
217 }
218
219 pub fn syncopated() -> Self {
220 Self::new(vec![0.0, 0.75, 1.5, 2.25, 3.0, 3.5], 4.0)
221 }
222
223 pub fn offbeat() -> Self {
224 Self::new(vec![0.5, 1.5, 2.5, 3.5], 4.0)
225 }
226
227 pub fn clave_son() -> Self {
228 Self::new(vec![0.0, 0.75, 1.5, 2.5, 3.5], 4.0)
230 }
231
232 pub fn waltz() -> Self {
233 Self::new(vec![0.0, 1.0, 2.0], 3.0)
234 }
235
236 pub fn tick(&mut self, beat_delta: f32) -> u32 {
238 if self.hits.is_empty() { return 0; }
239 self.cursor += beat_delta;
240 let mut count = 0;
241 while self.next < self.hits.len() && self.hits[self.next] < self.cursor {
243 count += 1;
244 self.next += 1;
245 }
246 if self.cursor >= self.length {
247 self.cursor -= self.length;
248 self.next = 0;
249 while self.next < self.hits.len() && self.hits[self.next] < self.cursor {
251 count += 1;
252 self.next += 1;
253 }
254 }
255 count
256 }
257}
258
259#[derive(Clone, Debug)]
263pub struct MelodyGenerator {
264 pub scale: Scale,
265 pub octave: i32,
266 pub step_bias: f32,
268 pub rest_prob: f32,
270 pub chord_weight: f32,
272 last_degree: i32,
273 rng: u64,
275}
276
277impl MelodyGenerator {
278 pub fn new(scale: Scale, octave: i32) -> Self {
279 Self {
280 scale,
281 octave,
282 step_bias: 0.7,
283 rest_prob: 0.15,
284 chord_weight: 0.6,
285 last_degree: 0,
286 rng: 12345,
287 }
288 }
289
290 fn rand_f32(&mut self) -> f32 {
291 self.rng ^= self.rng << 13;
293 self.rng ^= self.rng >> 7;
294 self.rng ^= self.rng << 17;
295 (self.rng & 0xFFFF) as f32 / 65535.0
296 }
297
298 fn rand_range(&mut self, lo: i32, hi: i32) -> i32 {
299 if hi <= lo { return lo; }
300 lo + (self.rand_f32() * (hi - lo) as f32) as i32
301 }
302
303 pub fn next_note(&mut self, chord: &Chord) -> Option<f32> {
305 if self.rand_f32() < self.rest_prob { return None; }
306
307 let n = self.scale.scale.intervals().len() as i32;
308
309 let degree = if self.rand_f32() < self.chord_weight && !chord.degrees.is_empty() {
311 let idx = self.rand_range(0, chord.degrees.len() as i32) as usize;
312 chord.degrees[idx]
313 } else if self.rand_f32() < self.step_bias {
314 let step = if self.rand_f32() < 0.5 { 1 } else { -1 };
316 self.last_degree + step
317 } else {
318 self.rand_range(-3, 8)
320 };
321
322 let degree = degree.clamp(-2, n + 3);
324 self.last_degree = degree;
325
326 Some(self.scale.freq(degree, self.octave))
327 }
328
329 pub fn phrase(&mut self, n: usize, chord: &Chord, beat_dur: f32) -> Vec<(Option<f32>, f32)> {
331 (0..n).map(|_| (self.next_note(chord), beat_dur)).collect()
332 }
333}
334
335#[derive(Clone, Debug)]
339pub struct VibeConfig {
340 pub scale: Scale,
341 pub bpm: f32,
342 pub progression: Progression,
343 pub rhythm: RhythmPattern,
344 pub bass_enabled: bool,
345 pub melody_enabled: bool,
346 pub pad_enabled: bool,
347 pub arp_enabled: bool,
348 pub volume: f32,
350 pub spaciousness: f32,
352}
353
354impl VibeConfig {
355 pub fn silence() -> Self {
356 Self {
357 scale: Scale::new(60, ScaleType::Major),
358 bpm: 80.0,
359 progression: Progression::new(vec![]),
360 rhythm: RhythmPattern::new(vec![], 4.0),
361 bass_enabled: false,
362 melody_enabled: false,
363 pad_enabled: false,
364 arp_enabled: false,
365 volume: 0.0,
366 spaciousness: 0.0,
367 }
368 }
369
370 pub fn ambient() -> Self {
371 Self {
372 scale: Scale::new(57, ScaleType::NaturalMinor), bpm: 60.0,
374 progression: Progression::minor_pop(3),
375 rhythm: RhythmPattern::eighth_notes(),
376 bass_enabled: true,
377 melody_enabled: false,
378 pad_enabled: true,
379 arp_enabled: false,
380 volume: 0.5,
381 spaciousness: 0.8,
382 }
383 }
384
385 pub fn combat() -> Self {
386 Self {
387 scale: Scale::new(57, ScaleType::HarmonicMinor), bpm: 140.0,
389 progression: Progression::minor_pop(3),
390 rhythm: RhythmPattern::four_on_floor(),
391 bass_enabled: true,
392 melody_enabled: true,
393 pad_enabled: false,
394 arp_enabled: true,
395 volume: 0.8,
396 spaciousness: 0.3,
397 }
398 }
399
400 pub fn boss() -> Self {
401 Self {
402 scale: Scale::new(45, ScaleType::Diminished), bpm: 160.0,
404 progression: Progression::two_five_one(2),
405 rhythm: RhythmPattern::syncopated(),
406 bass_enabled: true,
407 melody_enabled: true,
408 pad_enabled: true,
409 arp_enabled: true,
410 volume: 1.0,
411 spaciousness: 0.2,
412 }
413 }
414
415 pub fn victory() -> Self {
416 Self {
417 scale: Scale::new(60, ScaleType::Major), bpm: 120.0,
419 progression: Progression::one_five_six_four(4),
420 rhythm: RhythmPattern::eighth_notes(),
421 bass_enabled: true,
422 melody_enabled: true,
423 pad_enabled: false,
424 arp_enabled: false,
425 volume: 0.9,
426 spaciousness: 0.5,
427 }
428 }
429
430 pub fn exploration() -> Self {
431 Self {
432 scale: Scale::new(60, ScaleType::Lydian),
433 bpm: 85.0,
434 progression: Progression::one_five_six_four(3),
435 rhythm: RhythmPattern::waltz(),
436 bass_enabled: true,
437 melody_enabled: true,
438 pad_enabled: true,
439 arp_enabled: false,
440 volume: 0.6,
441 spaciousness: 0.7,
442 }
443 }
444
445 pub fn tension() -> Self {
446 Self {
447 scale: Scale::new(60, ScaleType::Phrygian),
448 bpm: 100.0,
449 progression: Progression::minor_pop(3),
450 rhythm: RhythmPattern::offbeat(),
451 bass_enabled: true,
452 melody_enabled: false,
453 pad_enabled: true,
454 arp_enabled: false,
455 volume: 0.65,
456 spaciousness: 0.4,
457 }
458 }
459}
460
461#[derive(Clone, Debug)]
465pub struct NoteEvent {
466 pub frequency: f32,
467 pub amplitude: f32,
468 pub duration: f32,
469 pub pan: f32, pub voice: NoteVoice,
471}
472
473#[derive(Clone, Copy, Debug, PartialEq, Eq)]
474pub enum NoteVoice {
475 Bass,
476 Melody,
477 Pad,
478 Arp,
479 Chord,
480}
481
482pub struct MusicEngine {
486 pub vibe: VibeConfig,
487 time: f32,
489 beat_clock: f32,
490 beats_per_second: f32,
492 melody_gen: MelodyGenerator,
493 arp_gen: MelodyGenerator,
494 pending_notes: Vec<NoteEvent>,
496 arp_index: usize,
498 arp_chord_cache: Vec<f32>,
499 next_vibe: Option<VibeConfig>,
501 transition: f32,
503 pub master_volume: f32,
504}
505
506impl MusicEngine {
507 pub fn new() -> Self {
508 let vibe = VibeConfig::silence();
509 let scale = vibe.scale;
510 Self {
511 vibe: vibe.clone(),
512 time: 0.0,
513 beat_clock: 0.0,
514 beats_per_second: 0.0,
515 melody_gen: MelodyGenerator::new(scale, 5),
516 arp_gen: MelodyGenerator::new(scale, 4),
517 pending_notes: Vec::new(),
518 arp_index: 0,
519 arp_chord_cache: Vec::new(),
520 next_vibe: None,
521 transition: 1.0,
522 master_volume: 1.0,
523 }
524 }
525
526 pub fn set_vibe(&mut self, config: VibeConfig) {
528 let scale = config.scale;
529 self.vibe = config;
530 self.beats_per_second = self.vibe.bpm / 60.0;
531 self.melody_gen = MelodyGenerator::new(scale, 5);
532 self.arp_gen = MelodyGenerator::new(scale, 4);
533 self.arp_chord_cache.clear();
534 self.arp_index = 0;
535 }
536
537 pub fn transition_to(&mut self, config: VibeConfig, _duration_beats: f32) {
539 self.next_vibe = Some(config);
540 self.transition = 0.0;
541 }
542
543 pub fn set_vibe_by_name(&mut self, name: &str) {
545 let config = match name {
546 "silence" => VibeConfig::silence(),
547 "ambient" => VibeConfig::ambient(),
548 "combat" => VibeConfig::combat(),
549 "boss" => VibeConfig::boss(),
550 "victory" => VibeConfig::victory(),
551 "exploration" => VibeConfig::exploration(),
552 "tension" => VibeConfig::tension(),
553 _ => {
554 log::warn!("MusicEngine: unknown vibe '{}'", name);
555 return;
556 }
557 };
558 self.set_vibe(config);
559 }
560
561 pub fn tick(&mut self, dt: f32) -> Vec<NoteEvent> {
564 self.pending_notes.clear();
565 self.time += dt;
566
567 if self.beats_per_second < 0.001 { return Vec::new(); }
568
569 let beat_delta = dt * self.beats_per_second;
570 self.beat_clock += beat_delta;
571
572 let _chord_changed = self.vibe.progression.tick(beat_delta);
574 let chord_changed = _chord_changed.is_some();
575
576 let chord = match self.vibe.progression.current_chord() {
578 Some(c) => c.clone(),
579 None => Chord::triad_major(3),
580 };
581
582 let chord_freqs: Vec<f32> = chord.frequencies(&self.vibe.scale);
583
584 if self.vibe.bass_enabled {
586 let hits = self.vibe.rhythm.tick(beat_delta);
587 for _ in 0..hits {
588 let root_freq = self.vibe.scale.freq(chord.degrees.first().copied().unwrap_or(0), 2);
589 let vol = self.vibe.volume * 0.7 * self.master_volume;
590 self.pending_notes.push(NoteEvent {
591 frequency: root_freq,
592 amplitude: vol,
593 duration: 0.18,
594 pan: 0.0,
595 voice: NoteVoice::Bass,
596 });
597 }
598 }
599
600 if self.vibe.pad_enabled && chord_changed {
602 for (i, &freq) in chord_freqs.iter().enumerate() {
603 let pan = (i as f32 - chord_freqs.len() as f32 * 0.5) * 0.3;
604 self.pending_notes.push(NoteEvent {
605 frequency: freq * 2.0, amplitude: self.vibe.volume * 0.3 * self.master_volume,
607 duration: 60.0 / self.vibe.bpm * 4.0, pan,
609 voice: NoteVoice::Pad,
610 });
611 }
612 }
613
614 if self.vibe.melody_enabled {
616 let eighth_beats = (self.beat_clock * 2.0).floor();
617 let prev_eighth = ((self.beat_clock - beat_delta) * 2.0).floor();
618 if eighth_beats > prev_eighth {
619 if let Some(freq) = self.melody_gen.next_note(&chord) {
620 self.pending_notes.push(NoteEvent {
621 frequency: freq,
622 amplitude: self.vibe.volume * 0.5 * self.master_volume,
623 duration: 0.12,
624 pan: 0.2,
625 voice: NoteVoice::Melody,
626 });
627 }
628 }
629 }
630
631 if self.vibe.arp_enabled {
633 if chord_changed || self.arp_chord_cache.is_empty() {
634 self.arp_chord_cache = chord_freqs.clone();
635 for &f in &chord_freqs { self.arp_chord_cache.push(f * 2.0); }
637 self.arp_index = 0;
638 }
639 let sixteenth_beats = (self.beat_clock * 4.0).floor();
640 let prev_sixteenth = ((self.beat_clock - beat_delta) * 4.0).floor();
641 if sixteenth_beats > prev_sixteenth && !self.arp_chord_cache.is_empty() {
642 let freq = self.arp_chord_cache[self.arp_index % self.arp_chord_cache.len()];
643 self.arp_index += 1;
644 self.pending_notes.push(NoteEvent {
645 frequency: freq * 4.0, amplitude: self.vibe.volume * 0.25 * self.master_volume,
647 duration: 0.06,
648 pan: -0.3,
649 voice: NoteVoice::Arp,
650 });
651 }
652 }
653
654 self.pending_notes.clone()
655 }
656
657 pub fn current_bpm(&self) -> f32 { self.vibe.bpm }
658 pub fn current_beat(&self) -> f32 { self.beat_clock }
659 pub fn current_bar(&self) -> u32 { (self.beat_clock / 4.0) as u32 }
660}
661
662impl Default for MusicEngine {
663 fn default() -> Self { Self::new() }
664}
665
666#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn scale_major_intervals() {
674 let s = Scale::new(60, ScaleType::Major); let f0 = s.freq(0, 4); let f1 = s.freq(1, 4); let f4 = s.freq(4, 4); assert!((f0 - 261.63).abs() < 0.5);
680 assert!((f1 - 293.66).abs() < 0.5);
681 assert!((f4 - 392.00).abs() < 0.5);
682 }
683
684 #[test]
685 fn scale_midi_to_hz_a4() {
686 let hz = Scale::midi_to_hz(69);
687 assert!((hz - 440.0).abs() < 0.01);
688 }
689
690 #[test]
691 fn chord_frequencies_non_empty() {
692 let scale = Scale::new(60, ScaleType::Major);
693 let chord = Chord::triad_major(4);
694 let freqs = chord.frequencies(&scale);
695 assert_eq!(freqs.len(), 3);
696 assert!(freqs.iter().all(|&f| f > 0.0));
697 }
698
699 #[test]
700 fn progression_advances() {
701 let mut prog = Progression::one_five_six_four(4);
702 let first = prog.current_chord().unwrap().name.clone();
703 prog.tick(4.0); let second = prog.current_chord().unwrap().name.clone();
705 assert_ne!(first, second);
706 }
707
708 #[test]
709 fn rhythm_fires_hits() {
710 let mut r = RhythmPattern::four_on_floor();
711 let hits = r.tick(1.0); assert_eq!(hits, 1);
713 }
714
715 #[test]
716 fn rhythm_full_measure() {
717 let mut r = RhythmPattern::four_on_floor();
718 let total: u32 = (0..4).map(|_| r.tick(1.0)).sum();
719 assert_eq!(total, 4);
720 }
721
722 #[test]
723 fn melody_gen_produces_notes() {
724 let scale = Scale::new(60, ScaleType::Major);
725 let mut gen = MelodyGenerator::new(scale, 4);
726 let chord = Chord::triad_major(4);
727 let phrase = gen.phrase(8, &chord, 0.5);
728 let non_rests = phrase.iter().filter(|(f, _)| f.is_some()).count();
729 assert!(non_rests > 0);
731 }
732
733 #[test]
734 fn engine_silence_no_notes() {
735 let mut engine = MusicEngine::new();
736 engine.set_vibe(VibeConfig::silence());
737 let notes = engine.tick(1.0 / 60.0);
738 assert!(notes.is_empty());
739 }
740
741 #[test]
742 fn engine_combat_produces_notes() {
743 let mut engine = MusicEngine::new();
744 engine.set_vibe(VibeConfig::combat());
745 let mut all_notes = Vec::new();
747 for _ in 0..120 {
748 all_notes.extend(engine.tick(1.0 / 60.0));
749 }
750 assert!(!all_notes.is_empty(), "Expected notes in combat vibe");
751 }
752
753 #[test]
754 fn vibe_config_by_name() {
755 let mut engine = MusicEngine::new();
756 engine.set_vibe_by_name("ambient");
757 assert!((engine.current_bpm() - 60.0).abs() < 0.1);
758 engine.set_vibe_by_name("boss");
759 assert!((engine.current_bpm() - 160.0).abs() < 0.1);
760 }
761}