Skip to content

Commit 4fc968c

Browse files
committed
Fix review issues and update lockfile
1 parent eb6dc52 commit 4fc968c

File tree

12 files changed

+175
-217
lines changed

12 files changed

+175
-217
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Additional scripts:
100100
- **Space**: Pause/resume playback
101101
- **←/→**: Adjust tempo (BPM shown in hint overlay)
102102
- **↑/↓**: Adjust master volume
103+
- **M**: Mute/unmute master output
103104
- **Enter/Escape**: Toggle fullscreen
104105

105106
**🎯 Voice Interaction:**

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ <h3>Keys</h3>
186186
<span class="kbd">←/→</span>: tempo •
187187
<span class="kbd">↑/↓</span>: volume
188188
</li>
189+
<li><span class="kbd">M</span>: mute/unmute master</li>
189190
</ul>
190191
</div>
191192
<div>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/music.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ impl MusicEngine {
146146

147147
/// Set beats-per-minute for the internal scheduler.
148148
pub fn set_bpm(&mut self, bpm: f32) {
149-
self.params.bpm = bpm;
149+
if !bpm.is_finite() {
150+
return;
151+
}
152+
self.params.bpm = bpm.clamp(1.0, 400.0);
150153
}
151154

152155
/// Set the global detune offset in cents.
@@ -210,11 +213,18 @@ impl MusicEngine {
210213

211214
/// Advance the scheduler by `dt`, pushing any newly scheduled `NoteEvent`s into `out_events`.
212215
pub fn tick(&mut self, dt: Duration, out_events: &mut Vec<NoteEvent>) {
213-
let seconds_per_beat = 60.0 / self.params.bpm as f64;
216+
let bpm = self.params.bpm as f64;
217+
if !bpm.is_finite() || bpm <= 0.0 {
218+
return;
219+
}
220+
let step = (60.0 / bpm) / 2.0;
221+
if !step.is_finite() || step <= 0.0 {
222+
return;
223+
}
214224
self.beat_accum += dt.as_secs_f64();
215-
while self.beat_accum >= seconds_per_beat / 2.0 {
225+
while self.beat_accum >= step {
216226
// eighth notes grid
217-
self.beat_accum -= seconds_per_beat / 2.0;
227+
self.beat_accum -= step;
218228
self.schedule_step(out_events);
219229
}
220230
}

src/events/keyboard.rs

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ use crate::core::{
33
AEOLIAN, C_MAJOR_PENTATONIC, DORIAN, IONIAN, LOCRIAN, LYDIAN, MIXOLYDIAN, PHRYGIAN,
44
TET19_PENTATONIC, TET24_PENTATONIC, TET31_PENTATONIC,
55
};
6+
use crate::events::keymap::{mode_scale_for_digit, root_midi_for_key};
67
use crate::overlay;
78
use std::cell::RefCell;
89
use std::rc::Rc;
910
use wasm_bindgen::JsCast;
1011
use web_sys as web;
1112

13+
thread_local! {
14+
static MASTER_UNMUTED_GAIN: RefCell<Option<f32>> = RefCell::new(None);
15+
}
16+
1217
/// Get the name of the current scale for display purposes
1318
fn get_scale_name(scale: &[f32]) -> &'static str {
1419
match scale {
@@ -45,37 +50,6 @@ fn update_hint_after_change(engine: &Rc<RefCell<MusicEngine>>) {
4550
}
4651
}
4752

48-
#[inline]
49-
pub fn root_midi_for_key(key: &str) -> Option<i32> {
50-
match key {
51-
"a" | "A" => Some(69), // A4
52-
"b" | "B" => Some(71), // B4
53-
"c" | "C" => Some(60), // C4 (middle C)
54-
"d" | "D" => Some(62), // D4
55-
"e" | "E" => Some(64), // E4
56-
"f" | "F" => Some(65), // F4
57-
"g" | "G" => Some(67), // G4
58-
_ => None,
59-
}
60-
}
61-
62-
#[inline]
63-
pub fn mode_scale_for_digit(key: &str) -> Option<&'static [f32]> {
64-
match key {
65-
"1" => Some(IONIAN),
66-
"2" => Some(DORIAN),
67-
"3" => Some(PHRYGIAN),
68-
"4" => Some(LYDIAN),
69-
"5" => Some(MIXOLYDIAN),
70-
"6" => Some(AEOLIAN),
71-
"7" => Some(LOCRIAN),
72-
"8" => Some(TET19_PENTATONIC),
73-
"9" => Some(TET24_PENTATONIC),
74-
"0" => Some(TET31_PENTATONIC),
75-
_ => None,
76-
}
77-
}
78-
7953
pub fn handle_global_keydown(
8054
ev: &web::KeyboardEvent,
8155
engine: &Rc<RefCell<MusicEngine>>,
@@ -141,6 +115,22 @@ pub fn handle_global_keydown(
141115
drop(eng);
142116
update_hint_after_change(engine);
143117
}
118+
"m" | "M" => {
119+
let current_gain = master_gain.gain().value();
120+
if current_gain <= 0.0001 {
121+
let restored = MASTER_UNMUTED_GAIN
122+
.with(|state| state.borrow_mut().take())
123+
.unwrap_or(0.25)
124+
.clamp(0.0, 1.0);
125+
_ = master_gain.gain().set_value(restored);
126+
log::info!("[keys] master muted=false");
127+
} else {
128+
MASTER_UNMUTED_GAIN.with(|state| *state.borrow_mut() = Some(current_gain));
129+
_ = master_gain.gain().set_value(0.0);
130+
log::info!("[keys] master muted=true");
131+
}
132+
ev.prevent_default();
133+
}
144134
"," => {
145135
let mut eng = engine.borrow_mut();
146136
if ev.shift_key() {

src/events/keymap.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::core::{
2+
AEOLIAN, DORIAN, IONIAN, LOCRIAN, LYDIAN, MIXOLYDIAN, PHRYGIAN, TET19_PENTATONIC,
3+
TET24_PENTATONIC, TET31_PENTATONIC,
4+
};
5+
6+
#[inline]
7+
pub fn root_midi_for_key(key: &str) -> Option<i32> {
8+
match key {
9+
"a" | "A" => Some(69), // A4
10+
"b" | "B" => Some(71), // B4
11+
"c" | "C" => Some(60), // C4 (middle C)
12+
"d" | "D" => Some(62), // D4
13+
"e" | "E" => Some(64), // E4
14+
"f" | "F" => Some(65), // F4
15+
"g" | "G" => Some(67), // G4
16+
_ => None,
17+
}
18+
}
19+
20+
#[inline]
21+
pub fn mode_scale_for_digit(key: &str) -> Option<&'static [f32]> {
22+
match key {
23+
"1" => Some(IONIAN),
24+
"2" => Some(DORIAN),
25+
"3" => Some(PHRYGIAN),
26+
"4" => Some(LYDIAN),
27+
"5" => Some(MIXOLYDIAN),
28+
"6" => Some(AEOLIAN),
29+
"7" => Some(LOCRIAN),
30+
"8" => Some(TET19_PENTATONIC),
31+
"9" => Some(TET24_PENTATONIC),
32+
"0" => Some(TET31_PENTATONIC),
33+
_ => None,
34+
}
35+
}

src/events/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod keyboard;
2+
pub mod keymap;
23
pub mod pointer;
34

45
pub use keyboard::{wire_global_keydown, wire_overlay_toggle_h};

src/input.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ pub fn ray_sphere(ray_origin: Vec3, ray_dir: Vec3, center: Vec3, radius: f32) ->
2323
if disc < 0.0 {
2424
return None;
2525
}
26-
let t = -b - disc.sqrt();
27-
(t >= 0.0).then_some(t)
26+
let sqrt_disc = disc.sqrt();
27+
let t_near = -b - sqrt_disc;
28+
if t_near >= 0.0 {
29+
return Some(t_near);
30+
}
31+
let t_far = -b + sqrt_disc;
32+
(t_far >= 0.0).then_some(t_far)
2833
}
2934

3035
#[inline]

tests/input_tests.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,14 @@ fn ray_sphere_intersection_inside() {
6868
let radius = 3.0;
6969

7070
let result = ray_sphere(ray_origin, ray_dir, center, radius);
71-
// When ray starts inside sphere, the discriminant calculation might fail
72-
// This is a limitation of the current implementation
73-
// For now, we'll test that it either succeeds or fails gracefully
74-
if let Some(t) = result {
75-
assert!(t > 0.0);
76-
// Should hit at radius distance from center
77-
assert!((t - 3.0).abs() < 0.1);
78-
}
79-
// If it returns None, that's also acceptable for this edge case
71+
assert!(
72+
result.is_some(),
73+
"ray from inside sphere should still intersect"
74+
);
75+
let t = result.unwrap();
76+
assert!(t > 0.0);
77+
// Should hit at radius distance from center
78+
assert!((t - 3.0).abs() < 0.1);
8079
}
8180

8281
#[test]

0 commit comments

Comments
 (0)