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 Self::midi_to_hz(self.degree(degree, octave))
84 }
85
86 pub fn octave_freqs(&self, octave: i32) -> Vec<f32> {
88 (0..self.scale.intervals().len() as i32)
89 .map(|d| self.freq(d, octave))
90 .collect()
91 }
92}
93
94#[derive(Clone, Debug)]
98pub struct Chord {
99 pub degrees: Vec<i32>, pub octave: i32,
101 pub name: String,
102}
103
104impl Chord {
105 pub fn new(degrees: Vec<i32>, octave: i32, name: impl Into<String>) -> Self {
106 Self { degrees, octave, name: name.into() }
107 }
108
109 pub fn triad_major(octave: i32) -> Self { Self::new(vec![0, 2, 4], octave, "Maj") }
111 pub fn triad_minor(octave: i32) -> Self { Self::new(vec![0, 2, 4], octave, "min") }
112 pub fn seventh(octave: i32) -> Self { Self::new(vec![0, 2, 4, 6], octave, "7th") }
113 pub fn sus2(octave: i32) -> Self { Self::new(vec![0, 1, 4], octave, "sus2") }
114 pub fn sus4(octave: i32) -> Self { Self::new(vec![0, 3, 4], octave, "sus4") }
115 pub fn power(octave: i32) -> Self { Self::new(vec![0, 4], octave, "5th") }
116 pub fn diminished(octave: i32) -> Self { Self::new(vec![0, 2, 5], octave, "dim") }
117 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") }
119
120 pub fn frequencies(&self, scale: &Scale) -> Vec<f32> {
122 self.degrees.iter().map(|&d| scale.freq(d + 0, self.octave)).collect()
123 }
124}
125
126#[derive(Clone, Debug)]
130pub struct Progression {
131 pub chords: Vec<(Chord, f32)>, pub current: usize,
133 pub beat_clock: f32,
134}
135
136impl Progression {
137 pub fn new(chords: Vec<(Chord, f32)>) -> Self {
138 Self { chords, current: 0, beat_clock: 0.0 }
139 }
140
141 pub fn one_five_six_four(octave: i32) -> Self {
143 Self::new(vec![
144 (Chord::new(vec![0, 2, 4], octave, "I"), 4.0),
145 (Chord::new(vec![4, 6, 1], octave, "V"), 4.0),
146 (Chord::new(vec![5, 0, 2], octave, "vi"), 4.0),
147 (Chord::new(vec![3, 5, 0], octave, "IV"), 4.0),
148 ])
149 }
150
151 pub fn minor_pop(octave: i32) -> Self {
153 Self::new(vec![
154 (Chord::new(vec![0, 2, 4], octave, "i"), 4.0),
155 (Chord::new(vec![5, 0, 2], octave, "VI"), 4.0),
156 (Chord::new(vec![2, 4, 6], octave, "III"), 4.0),
157 (Chord::new(vec![6, 1, 3], octave, "VII"), 4.0),
158 ])
159 }
160
161 pub fn two_five_one(octave: i32) -> Self {
163 Self::new(vec![
164 (Chord::new(vec![1, 3, 5, 0], octave, "ii7"), 4.0),
165 (Chord::new(vec![4, 6, 1, 3], octave, "V7"), 4.0),
166 (Chord::new(vec![0, 2, 4, 6], octave, "Imaj7"),8.0),
167 ])
168 }
169
170 pub fn tick(&mut self, beat_delta: f32) -> Option<&Chord> {
172 if self.chords.is_empty() { return None; }
173 self.beat_clock += beat_delta;
174 let current_dur = self.chords[self.current].1;
175 if self.beat_clock >= current_dur {
176 self.beat_clock -= current_dur;
177 self.current = (self.current + 1) % self.chords.len();
178 return Some(&self.chords[self.current].0);
179 }
180 None
181 }
182
183 pub fn current_chord(&self) -> Option<&Chord> {
184 self.chords.get(self.current).map(|(c, _)| c)
185 }
186
187 pub fn progress_in_chord(&self) -> f32 {
188 let dur = self.chords.get(self.current).map(|(_, d)| *d).unwrap_or(1.0);
189 (self.beat_clock / dur).clamp(0.0, 1.0)
190 }
191}
192
193#[derive(Clone, Debug)]
197pub struct RhythmPattern {
198 pub hits: Vec<f32>, pub length: f32, pub cursor: f32, pub next: usize, }
203
204impl RhythmPattern {
205 pub fn new(hits: Vec<f32>, length: f32) -> Self {
206 let next = 0;
207 Self { hits, length, cursor: 0.0, next }
208 }
209
210 pub fn four_on_floor() -> Self {
211 Self::new(vec![0.0, 1.0, 2.0, 3.0], 4.0)
212 }
213
214 pub fn eighth_notes() -> Self {
215 Self::new(vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], 4.0)
216 }
217
218 pub fn syncopated() -> Self {
219 Self::new(vec![0.0, 0.75, 1.5, 2.25, 3.0, 3.5], 4.0)
220 }
221
222 pub fn offbeat() -> Self {
223 Self::new(vec![0.5, 1.5, 2.5, 3.5], 4.0)
224 }
225
226 pub fn clave_son() -> Self {
227 Self::new(vec![0.0, 0.75, 1.5, 2.5, 3.5], 4.0)
229 }
230
231 pub fn waltz() -> Self {
232 Self::new(vec![0.0, 1.0, 2.0], 3.0)
233 }
234
235 pub fn tick(&mut self, beat_delta: f32) -> u32 {
237 if self.hits.is_empty() { return 0; }
238 self.cursor += beat_delta;
239 if self.cursor >= self.length {
240 self.cursor -= self.length;
241 self.next = 0;
242 }
243 let mut count = 0;
244 while self.next < self.hits.len() && self.cursor >= self.hits[self.next] {
245 count += 1;
246 self.next += 1;
247 }
248 count
249 }
250}
251
252#[derive(Clone, Debug)]
256pub struct MelodyGenerator {
257 pub scale: Scale,
258 pub octave: i32,
259 pub step_bias: f32,
261 pub rest_prob: f32,
263 pub chord_weight: f32,
265 last_degree: i32,
266 rng: u64,
268}
269
270impl MelodyGenerator {
271 pub fn new(scale: Scale, octave: i32) -> Self {
272 Self {
273 scale,
274 octave,
275 step_bias: 0.7,
276 rest_prob: 0.15,
277 chord_weight: 0.6,
278 last_degree: 0,
279 rng: 12345,
280 }
281 }
282
283 fn rand_f32(&mut self) -> f32 {
284 self.rng ^= self.rng << 13;
286 self.rng ^= self.rng >> 7;
287 self.rng ^= self.rng << 17;
288 (self.rng & 0xFFFF) as f32 / 65535.0
289 }
290
291 fn rand_range(&mut self, lo: i32, hi: i32) -> i32 {
292 if hi <= lo { return lo; }
293 lo + (self.rand_f32() * (hi - lo) as f32) as i32
294 }
295
296 pub fn next_note(&mut self, chord: &Chord) -> Option<f32> {
298 if self.rand_f32() < self.rest_prob { return None; }
299
300 let n = self.scale.scale.intervals().len() as i32;
301
302 let degree = if self.rand_f32() < self.chord_weight && !chord.degrees.is_empty() {
304 let idx = self.rand_range(0, chord.degrees.len() as i32) as usize;
305 chord.degrees[idx]
306 } else if self.rand_f32() < self.step_bias {
307 let step = if self.rand_f32() < 0.5 { 1 } else { -1 };
309 self.last_degree + step
310 } else {
311 self.rand_range(-3, 8)
313 };
314
315 let degree = degree.clamp(-2, n + 3);
317 self.last_degree = degree;
318
319 Some(self.scale.freq(degree, self.octave))
320 }
321
322 pub fn phrase(&mut self, n: usize, chord: &Chord, beat_dur: f32) -> Vec<(Option<f32>, f32)> {
324 (0..n).map(|_| (self.next_note(chord), beat_dur)).collect()
325 }
326}
327
328#[derive(Clone, Debug)]
332pub struct VibeConfig {
333 pub scale: Scale,
334 pub bpm: f32,
335 pub progression: Progression,
336 pub rhythm: RhythmPattern,
337 pub bass_enabled: bool,
338 pub melody_enabled: bool,
339 pub pad_enabled: bool,
340 pub arp_enabled: bool,
341 pub volume: f32,
343 pub spaciousness: f32,
345}
346
347impl VibeConfig {
348 pub fn silence() -> Self {
349 Self {
350 scale: Scale::new(60, ScaleType::Major),
351 bpm: 80.0,
352 progression: Progression::new(vec![]),
353 rhythm: RhythmPattern::new(vec![], 4.0),
354 bass_enabled: false,
355 melody_enabled: false,
356 pad_enabled: false,
357 arp_enabled: false,
358 volume: 0.0,
359 spaciousness: 0.0,
360 }
361 }
362
363 pub fn ambient() -> Self {
364 Self {
365 scale: Scale::new(57, ScaleType::NaturalMinor), bpm: 60.0,
367 progression: Progression::minor_pop(3),
368 rhythm: RhythmPattern::eighth_notes(),
369 bass_enabled: true,
370 melody_enabled: false,
371 pad_enabled: true,
372 arp_enabled: false,
373 volume: 0.5,
374 spaciousness: 0.8,
375 }
376 }
377
378 pub fn combat() -> Self {
379 Self {
380 scale: Scale::new(57, ScaleType::HarmonicMinor), bpm: 140.0,
382 progression: Progression::minor_pop(3),
383 rhythm: RhythmPattern::four_on_floor(),
384 bass_enabled: true,
385 melody_enabled: true,
386 pad_enabled: false,
387 arp_enabled: true,
388 volume: 0.8,
389 spaciousness: 0.3,
390 }
391 }
392
393 pub fn boss() -> Self {
394 Self {
395 scale: Scale::new(45, ScaleType::Diminished), bpm: 160.0,
397 progression: Progression::two_five_one(2),
398 rhythm: RhythmPattern::syncopated(),
399 bass_enabled: true,
400 melody_enabled: true,
401 pad_enabled: true,
402 arp_enabled: true,
403 volume: 1.0,
404 spaciousness: 0.2,
405 }
406 }
407
408 pub fn victory() -> Self {
409 Self {
410 scale: Scale::new(60, ScaleType::Major), bpm: 120.0,
412 progression: Progression::one_five_six_four(4),
413 rhythm: RhythmPattern::eighth_notes(),
414 bass_enabled: true,
415 melody_enabled: true,
416 pad_enabled: false,
417 arp_enabled: false,
418 volume: 0.9,
419 spaciousness: 0.5,
420 }
421 }
422
423 pub fn exploration() -> Self {
424 Self {
425 scale: Scale::new(60, ScaleType::Lydian),
426 bpm: 85.0,
427 progression: Progression::one_five_six_four(3),
428 rhythm: RhythmPattern::waltz(),
429 bass_enabled: true,
430 melody_enabled: true,
431 pad_enabled: true,
432 arp_enabled: false,
433 volume: 0.6,
434 spaciousness: 0.7,
435 }
436 }
437
438 pub fn tension() -> Self {
439 Self {
440 scale: Scale::new(60, ScaleType::Phrygian),
441 bpm: 100.0,
442 progression: Progression::minor_pop(3),
443 rhythm: RhythmPattern::offbeat(),
444 bass_enabled: true,
445 melody_enabled: false,
446 pad_enabled: true,
447 arp_enabled: false,
448 volume: 0.65,
449 spaciousness: 0.4,
450 }
451 }
452}
453
454#[derive(Clone, Debug)]
458pub struct NoteEvent {
459 pub frequency: f32,
460 pub amplitude: f32,
461 pub duration: f32,
462 pub pan: f32, pub voice: NoteVoice,
464}
465
466#[derive(Clone, Copy, Debug, PartialEq, Eq)]
467pub enum NoteVoice {
468 Bass,
469 Melody,
470 Pad,
471 Arp,
472 Chord,
473}
474
475pub struct MusicEngine {
479 pub vibe: VibeConfig,
480 time: f32,
482 beat_clock: f32,
483 beats_per_second: f32,
485 melody_gen: MelodyGenerator,
486 arp_gen: MelodyGenerator,
487 pending_notes: Vec<NoteEvent>,
489 arp_index: usize,
491 arp_chord_cache: Vec<f32>,
492 next_vibe: Option<VibeConfig>,
494 transition: f32,
496 pub master_volume: f32,
497}
498
499impl MusicEngine {
500 pub fn new() -> Self {
501 let vibe = VibeConfig::silence();
502 let scale = vibe.scale;
503 Self {
504 vibe: vibe.clone(),
505 time: 0.0,
506 beat_clock: 0.0,
507 beats_per_second: 0.0,
508 melody_gen: MelodyGenerator::new(scale, 5),
509 arp_gen: MelodyGenerator::new(scale, 4),
510 pending_notes: Vec::new(),
511 arp_index: 0,
512 arp_chord_cache: Vec::new(),
513 next_vibe: None,
514 transition: 1.0,
515 master_volume: 1.0,
516 }
517 }
518
519 pub fn set_vibe(&mut self, config: VibeConfig) {
521 let scale = config.scale;
522 self.vibe = config;
523 self.beats_per_second = self.vibe.bpm / 60.0;
524 self.melody_gen = MelodyGenerator::new(scale, 5);
525 self.arp_gen = MelodyGenerator::new(scale, 4);
526 self.arp_chord_cache.clear();
527 self.arp_index = 0;
528 }
529
530 pub fn transition_to(&mut self, config: VibeConfig, _duration_beats: f32) {
532 self.next_vibe = Some(config);
533 self.transition = 0.0;
534 }
535
536 pub fn set_vibe_by_name(&mut self, name: &str) {
538 let config = match name {
539 "silence" => VibeConfig::silence(),
540 "ambient" => VibeConfig::ambient(),
541 "combat" => VibeConfig::combat(),
542 "boss" => VibeConfig::boss(),
543 "victory" => VibeConfig::victory(),
544 "exploration" => VibeConfig::exploration(),
545 "tension" => VibeConfig::tension(),
546 _ => {
547 log::warn!("MusicEngine: unknown vibe '{}'", name);
548 return;
549 }
550 };
551 self.set_vibe(config);
552 }
553
554 pub fn tick(&mut self, dt: f32) -> Vec<NoteEvent> {
557 self.pending_notes.clear();
558 self.time += dt;
559
560 if self.beats_per_second < 0.001 { return Vec::new(); }
561
562 let beat_delta = dt * self.beats_per_second;
563 self.beat_clock += beat_delta;
564
565 let _chord_changed = self.vibe.progression.tick(beat_delta);
567 let chord_changed = _chord_changed.is_some();
568
569 let chord = match self.vibe.progression.current_chord() {
571 Some(c) => c.clone(),
572 None => Chord::triad_major(3),
573 };
574
575 let chord_freqs: Vec<f32> = chord.frequencies(&self.vibe.scale);
576
577 if self.vibe.bass_enabled {
579 let hits = self.vibe.rhythm.tick(beat_delta);
580 for _ in 0..hits {
581 let root_freq = self.vibe.scale.freq(chord.degrees.first().copied().unwrap_or(0), 2);
582 let vol = self.vibe.volume * 0.7 * self.master_volume;
583 self.pending_notes.push(NoteEvent {
584 frequency: root_freq,
585 amplitude: vol,
586 duration: 0.18,
587 pan: 0.0,
588 voice: NoteVoice::Bass,
589 });
590 }
591 }
592
593 if self.vibe.pad_enabled && chord_changed {
595 for (i, &freq) in chord_freqs.iter().enumerate() {
596 let pan = (i as f32 - chord_freqs.len() as f32 * 0.5) * 0.3;
597 self.pending_notes.push(NoteEvent {
598 frequency: freq * 2.0, amplitude: self.vibe.volume * 0.3 * self.master_volume,
600 duration: 60.0 / self.vibe.bpm * 4.0, pan,
602 voice: NoteVoice::Pad,
603 });
604 }
605 }
606
607 if self.vibe.melody_enabled {
609 let eighth_beats = (self.beat_clock * 2.0).floor();
610 let prev_eighth = ((self.beat_clock - beat_delta) * 2.0).floor();
611 if eighth_beats > prev_eighth {
612 if let Some(freq) = self.melody_gen.next_note(&chord) {
613 self.pending_notes.push(NoteEvent {
614 frequency: freq,
615 amplitude: self.vibe.volume * 0.5 * self.master_volume,
616 duration: 0.12,
617 pan: 0.2,
618 voice: NoteVoice::Melody,
619 });
620 }
621 }
622 }
623
624 if self.vibe.arp_enabled {
626 if chord_changed || self.arp_chord_cache.is_empty() {
627 self.arp_chord_cache = chord_freqs.clone();
628 for &f in &chord_freqs { self.arp_chord_cache.push(f * 2.0); }
630 self.arp_index = 0;
631 }
632 let sixteenth_beats = (self.beat_clock * 4.0).floor();
633 let prev_sixteenth = ((self.beat_clock - beat_delta) * 4.0).floor();
634 if sixteenth_beats > prev_sixteenth && !self.arp_chord_cache.is_empty() {
635 let freq = self.arp_chord_cache[self.arp_index % self.arp_chord_cache.len()];
636 self.arp_index += 1;
637 self.pending_notes.push(NoteEvent {
638 frequency: freq * 4.0, amplitude: self.vibe.volume * 0.25 * self.master_volume,
640 duration: 0.06,
641 pan: -0.3,
642 voice: NoteVoice::Arp,
643 });
644 }
645 }
646
647 self.pending_notes.clone()
648 }
649
650 pub fn current_bpm(&self) -> f32 { self.vibe.bpm }
651 pub fn current_beat(&self) -> f32 { self.beat_clock }
652 pub fn current_bar(&self) -> u32 { (self.beat_clock / 4.0) as u32 }
653}
654
655impl Default for MusicEngine {
656 fn default() -> Self { Self::new() }
657}
658
659#[cfg(test)]
662mod tests {
663 use super::*;
664
665 #[test]
666 fn scale_major_intervals() {
667 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);
673 assert!((f1 - 293.66).abs() < 0.5);
674 assert!((f4 - 392.00).abs() < 0.5);
675 }
676
677 #[test]
678 fn scale_midi_to_hz_a4() {
679 let hz = Scale::midi_to_hz(69);
680 assert!((hz - 440.0).abs() < 0.01);
681 }
682
683 #[test]
684 fn chord_frequencies_non_empty() {
685 let scale = Scale::new(60, ScaleType::Major);
686 let chord = Chord::triad_major(4);
687 let freqs = chord.frequencies(&scale);
688 assert_eq!(freqs.len(), 3);
689 assert!(freqs.iter().all(|&f| f > 0.0));
690 }
691
692 #[test]
693 fn progression_advances() {
694 let mut prog = Progression::one_five_six_four(4);
695 let first = prog.current_chord().unwrap().name.clone();
696 prog.tick(4.0); let second = prog.current_chord().unwrap().name.clone();
698 assert_ne!(first, second);
699 }
700
701 #[test]
702 fn rhythm_fires_hits() {
703 let mut r = RhythmPattern::four_on_floor();
704 let hits = r.tick(1.0); assert_eq!(hits, 1);
706 }
707
708 #[test]
709 fn rhythm_full_measure() {
710 let mut r = RhythmPattern::four_on_floor();
711 let total: u32 = (0..4).map(|_| r.tick(1.0)).sum();
712 assert_eq!(total, 4);
713 }
714
715 #[test]
716 fn melody_gen_produces_notes() {
717 let scale = Scale::new(60, ScaleType::Major);
718 let mut gen = MelodyGenerator::new(scale, 4);
719 let chord = Chord::triad_major(4);
720 let phrase = gen.phrase(8, &chord, 0.5);
721 let non_rests = phrase.iter().filter(|(f, _)| f.is_some()).count();
722 assert!(non_rests > 0);
724 }
725
726 #[test]
727 fn engine_silence_no_notes() {
728 let mut engine = MusicEngine::new();
729 engine.set_vibe(VibeConfig::silence());
730 let notes = engine.tick(1.0 / 60.0);
731 assert!(notes.is_empty());
732 }
733
734 #[test]
735 fn engine_combat_produces_notes() {
736 let mut engine = MusicEngine::new();
737 engine.set_vibe(VibeConfig::combat());
738 let mut all_notes = Vec::new();
740 for _ in 0..120 {
741 all_notes.extend(engine.tick(1.0 / 60.0));
742 }
743 assert!(!all_notes.is_empty(), "Expected notes in combat vibe");
744 }
745
746 #[test]
747 fn vibe_config_by_name() {
748 let mut engine = MusicEngine::new();
749 engine.set_vibe_by_name("ambient");
750 assert!((engine.current_bpm() - 60.0).abs() < 0.1);
751 engine.set_vibe_by_name("boss");
752 assert!((engine.current_bpm() - 160.0).abs() < 0.1);
753 }
754}