Skip to content

Commit fb85d96

Browse files
committed
app-web: dedupe pointermove wiring, prune unused imports; extract smooth_pulses() in frame; docs: update TODO
1 parent 428c5f9 commit fb85d96

File tree

4 files changed

+29
-113
lines changed

4 files changed

+29
-113
lines changed

crates/app-web/src/events.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::audio;
22
use crate::input;
33
use crate::render;
44
use app_core::MusicEngine;
5-
use app_core::Waveform;
65
use app_core::{
76
midi_to_hz, z_offset_vec3, AEOLIAN, DORIAN, ENGINE_DRAG_MAX_RADIUS, IONIAN, LOCRIAN, LYDIAN,
87
MIXOLYDIAN, PHRYGIAN, PICK_SPHERE_RADIUS, SPREAD,

crates/app-web/src/frame.rs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use wasm_bindgen::closure::Closure;
99
use wasm_bindgen::JsCast;
1010
use web_sys as web;
1111

12-
const CAMERA_Z: f32 = 6.0;
12+
const CAMERA_Z: f32 = 6.0; // keep for local readability; matches crate-level value
1313

1414
pub struct FrameContext<'a> {
1515
pub engine: Rc<RefCell<MusicEngine>>,
@@ -73,23 +73,7 @@ impl<'a> FrameContext<'a> {
7373
(self.pulse_energy[ev.voice_index] + ev.velocity as f32).min(1.8);
7474
}
7575
}
76-
let energy_decay = (-dt_sec * 1.6).exp();
77-
for i in 0..n {
78-
self.pulse_energy[i] *= energy_decay;
79-
}
80-
let tau_up = 0.10_f32;
81-
let tau_down = 0.45_f32;
82-
let alpha_up = 1.0 - (-dt_sec / tau_up).exp();
83-
let alpha_down = 1.0 - (-dt_sec / tau_down).exp();
84-
for i in 0..n {
85-
let target = self.pulse_energy[i].clamp(0.0, 1.5);
86-
let alpha = if target > pulses[i] {
87-
alpha_up
88-
} else {
89-
alpha_down
90-
};
91-
pulses[i] += (target - pulses[i]) * alpha;
92-
}
76+
smooth_pulses(&mut pulses, &mut self.pulse_energy, dt_sec);
9377

9478
// Swirl input
9579
let ms = self.mouse.borrow();
@@ -290,6 +274,28 @@ impl<'a> FrameContext<'a> {
290274
}
291275
}
292276

277+
#[inline]
278+
fn smooth_pulses(pulses: &mut [f32], pulse_energy: &mut [f32; 3], dt_sec: f32) {
279+
let n = pulses.len().min(3);
280+
let energy_decay = (-dt_sec * 1.6).exp();
281+
for i in 0..n {
282+
pulse_energy[i] *= energy_decay;
283+
}
284+
let tau_up = 0.10_f32;
285+
let tau_down = 0.45_f32;
286+
let alpha_up = 1.0 - (-dt_sec / tau_up).exp();
287+
let alpha_down = 1.0 - (-dt_sec / tau_down).exp();
288+
for i in 0..n {
289+
let target = pulse_energy[i].clamp(0.0, 1.5);
290+
let alpha = if target > pulses[i] {
291+
alpha_up
292+
} else {
293+
alpha_down
294+
};
295+
pulses[i] += (target - pulses[i]) * alpha;
296+
}
297+
}
298+
293299
pub async fn init_gpu(canvas: &web::HtmlCanvasElement) -> Option<render::GpuState<'static>> {
294300
// leak a canvas clone to satisfy 'static lifetime for surface
295301
let leaked_canvas = Box::leak(Box::new(canvas.clone()));

crates/app-web/src/lib.rs

Lines changed: 2 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#![cfg(target_arch = "wasm32")]
22
use app_core::{
3-
midi_to_hz, z_offset_vec3, EngineParams, MusicEngine, VoiceConfig, Waveform, BASE_SCALE,
4-
C_MAJOR_PENTATONIC, DEFAULT_VOICE_COLORS, DEFAULT_VOICE_POSITIONS, ENGINE_DRAG_MAX_RADIUS,
5-
PICK_SPHERE_RADIUS, SCALE_PULSE_MULTIPLIER, SPREAD,
3+
EngineParams, MusicEngine, VoiceConfig, Waveform, C_MAJOR_PENTATONIC, DEFAULT_VOICE_COLORS,
4+
DEFAULT_VOICE_POSITIONS,
65
};
76
use glam::Vec3;
87
use instant::Instant;
@@ -239,97 +238,6 @@ async fn init() -> anyhow::Result<()> {
239238
let hover_index = Rc::new(RefCell::new(None::<usize>));
240239
let drag_state = Rc::new(RefCell::new(input::DragState::default()));
241240

242-
// Screen -> canvas coords inline helper
243-
244-
// Mouse move: hover + drag
245-
{
246-
let mouse_state_m = mouse_state.clone();
247-
let hover_m = hover_index.clone();
248-
let drag_m = drag_state.clone();
249-
let engine_m = engine.clone();
250-
let canvas_mouse = canvas_for_click_inner.clone();
251-
let canvas_connected = canvas_mouse.is_connected();
252-
let closure = Closure::wrap(Box::new(move |ev: web::PointerEvent| {
253-
let pos = input::pointer_canvas_px(&ev, &canvas_mouse);
254-
// For CI/headless environments without real mouse, synthesize hover over center
255-
if !canvas_connected {
256-
return;
257-
}
258-
{
259-
// Store pointer position; render() converts to uv for swirl uniforms
260-
let mut ms = mouse_state_m.borrow_mut();
261-
ms.x = pos.x;
262-
ms.y = pos.y;
263-
}
264-
// Compute hover or drag update
265-
let (ro, rd) =
266-
render::screen_to_world_ray(&canvas_mouse, pos.x, pos.y, CAMERA_Z);
267-
let mut best = None::<(usize, f32)>;
268-
let spread = SPREAD;
269-
let z_offset = z_offset_vec3();
270-
for (i, v) in engine_m.borrow().voices.iter().enumerate() {
271-
let center_world = v.position * spread + z_offset;
272-
if let Some(t) =
273-
input::ray_sphere(ro, rd, center_world, PICK_SPHERE_RADIUS)
274-
{
275-
if t >= 0.0 {
276-
match best {
277-
Some((_, bt)) if t >= bt => {}
278-
_ => best = Some((i, t)),
279-
}
280-
}
281-
}
282-
}
283-
if drag_m.borrow().active {
284-
// Drag on plane z = constant (locked at mousedown)
285-
let plane_z = drag_m.borrow().plane_z_world;
286-
if rd.z.abs() > 1e-6 {
287-
let t = (plane_z - ro.z) / rd.z;
288-
if t >= 0.0 {
289-
let hit_world = ro + rd * t;
290-
let mut eng_pos = (hit_world - z_offset_vec3()) / SPREAD;
291-
// Clamp drag radius to avoid losing objects
292-
let max_r = ENGINE_DRAG_MAX_RADIUS; // engine-space radius
293-
let len =
294-
(eng_pos.x * eng_pos.x + eng_pos.z * eng_pos.z).sqrt();
295-
if len > max_r {
296-
let scale = max_r / len;
297-
eng_pos.x *= scale;
298-
eng_pos.z *= scale;
299-
}
300-
let mut eng = engine_m.borrow_mut();
301-
let vi = drag_m.borrow().voice;
302-
eng.set_voice_position(
303-
vi,
304-
Vec3::new(eng_pos.x, 0.0, eng_pos.z),
305-
);
306-
// noisy drag debug log removed
307-
}
308-
} else {
309-
// noisy drag-parallel debug log removed
310-
}
311-
// While dragging, boost swirl strength (used during render)
312-
} else {
313-
match best {
314-
Some((i, _t)) => {
315-
*hover_m.borrow_mut() = Some(i);
316-
}
317-
None => {
318-
*hover_m.borrow_mut() = None;
319-
}
320-
}
321-
}
322-
}) as Box<dyn FnMut(_)>);
323-
if let Some(w) = web::window() {
324-
w.add_event_listener_with_callback(
325-
"pointermove",
326-
closure.as_ref().unchecked_ref(),
327-
)
328-
.ok();
329-
}
330-
closure.forget();
331-
}
332-
333241
// Keyboard controls
334242
events::wire_global_keydown(
335243
engine.clone(),

docs/TODO.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ Desktop UI support has been removed to simplify the project and focus on the web
124124
- [ ] app-web: extract WebGPU pipeline builders
125125
- Rationale: deduplicate pipeline/buffer setup for waves/post passes
126126
- Plan: create `pipeline.rs` helpers returning typed bundles; no functional changes
127+
- [x] app-web: deduplicate pointer event wiring
128+
- Rationale: avoid drift by handling `pointermove`/drag/hover in one place (`events::wire_input_handlers`)
129+
- Impact: removed duplicate handler from `lib.rs`; behavior unchanged
127130

128131
### Testing Enhancements
129132

0 commit comments

Comments
 (0)