Skip to content

Commit 7d95719

Browse files
committed
web(audio): sync AudioListener position/orientation with camera (including orbit); visuals: subtle halo glow in WGSL
1 parent 40280f0 commit 7d95719

File tree

3 files changed

+50
-24
lines changed

3 files changed

+50
-24
lines changed

crates/app-core/shaders/scene.wgsl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ fn fs_main(inf: VsOut) -> @location(0) vec4<f32> {
3535

3636
// Emissive pulse boosts brightness subtly
3737
let emissive = 0.7 * clamp(inf.pulse, 0.0, 1.5);
38-
let rgb = inf.color.rgb * (1.0 + emissive);
38+
var rgb = inf.color.rgb * (1.0 + emissive);
39+
40+
// Subtle outer glow/halo based on radius
41+
let halo = smoothstep(0.75, 0.55, r) * 0.12; // outer ring brightness
42+
rgb += halo * inf.color.rgb;
43+
3944
return vec4<f32>(rgb, shape_alpha * inf.color.a);
4045
}
4146

crates/app-native/src/main.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,11 @@ fn main() {
480480
if new_hover != hover {
481481
// update colors to highlight hovered voice
482482
let mut vis = state.shared.lock().unwrap();
483-
if let Some(prev) = hover {
484-
// restore base color
485-
let base = DEFAULT_VOICE_COLORS[prev];
486-
vis.colors[prev] = Vec4::new(base[0], base[1], base[2], 1.0);
483+
// restore all to base first then apply hover brighten for determinism
484+
for (i, base) in DEFAULT_VOICE_COLORS.iter().enumerate() {
485+
vis.colors[i] = Vec4::new(base[0], base[1], base[2], 1.0);
487486
}
488487
if let Some(i) = new_hover {
489-
// brighten
490488
vis.colors[i].x = (vis.colors[i].x * 1.4).min(1.0);
491489
vis.colors[i].y = (vis.colors[i].y * 1.4).min(1.0);
492490
vis.colors[i].z = (vis.colors[i].z * 1.4).min(1.0);
@@ -543,7 +541,18 @@ struct AudioState {
543541
oscillators: Vec<ActiveOscillator>,
544542
}
545543

546-
fn start_audio_engine(shared_vis: Arc<Mutex<VisState>>, shared_engine: Arc<Mutex<MusicEngine>>) -> Option<cpal::Stream> {
544+
fn compute_equal_power_gains(pos_x_engine: f32) -> (f32, f32) {
545+
// Map engine-space X (roughly -1..1 typical) into pan -1..1
546+
let pan = (pos_x_engine / 1.5).clamp(-1.0, 1.0);
547+
// Equal-power panning
548+
let angle = (pan + 1.0) * std::f32::consts::FRAC_PI_4; // 0..pi/2
549+
(angle.cos(), angle.sin())
550+
}
551+
552+
fn start_audio_engine(
553+
shared_vis: Arc<Mutex<VisState>>,
554+
shared_engine: Arc<Mutex<MusicEngine>>,
555+
) -> Option<cpal::Stream> {
547556
let host = cpal::default_host();
548557
let device = host.default_output_device()?;
549558
let config = device.default_output_config().ok()?;
@@ -579,12 +588,13 @@ fn start_audio_engine(shared_vis: Arc<Mutex<VisState>>, shared_engine: Arc<Mutex
579588
last = now;
580589
let now_sec = start_instant.elapsed().as_secs_f64();
581590
events.clear();
582-
engine.tick(dt, now_sec, &mut events);
583-
// Apply any changes back to shared engine voices (positions/mute/solo)
591+
// Pull latest voice state from shared engine to reflect input changes
584592
{
585-
let mut guard = shared.lock().unwrap();
586-
guard.voices = engine.voices.clone();
593+
if let Ok(guard) = shared.lock() {
594+
engine.voices = guard.voices.clone();
595+
}
587596
}
597+
engine.tick(dt, now_sec, &mut events);
588598

589599
if !events.is_empty() {
590600
let mut guard = state_clone.lock().unwrap();
@@ -602,10 +612,7 @@ fn start_audio_engine(shared_vis: Arc<Mutex<VisState>>, shared_engine: Arc<Mutex
602612
};
603613
// Stereo pan from voice X position (engine-space)
604614
let pos_x = engine.voices[ev.voice_index].position.x;
605-
let pan = (pos_x / 1.5).clamp(-1.0, 1.0); // -1 left .. 1 right
606-
let angle = (pan + 1.0) * std::f32::consts::FRAC_PI_4; // 0..pi/2
607-
let left_gain = angle.cos();
608-
let right_gain = angle.sin();
615+
let (left_gain, right_gain) = compute_equal_power_gains(pos_x);
609616
guard.oscillators.push(ActiveOscillator {
610617
amplitude: ev.velocity.min(1.0),
611618
phase: 0.0,

crates/app-web/src/lib.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ async fn init() -> anyhow::Result<()> {
154154
};
155155
let listener = audio_ctx.listener();
156156
listener.set_position(0.0, 0.0, 1.5);
157+
let listener_for_tick = listener.clone();
157158

158159
// Music engine
159160
let voice_configs = vec![
@@ -808,16 +809,29 @@ async fn init() -> anyhow::Result<()> {
808809
BASE_SCALE + ps[2] * SCALE_PULSE_MULTIPLIER,
809810
];
810811

812+
// Compute camera eye for orbit or fixed
813+
let mut cam_eye = Vec3::new(0.0, 0.0, CAMERA_Z);
814+
if *orbit_tick.borrow() {
815+
orbit_t += dt_sec * 0.1; // rad/s
816+
let r = 6.0f32;
817+
cam_eye = Vec3::new(r * orbit_t.cos(), 0.0, r * orbit_t.sin());
818+
}
819+
let cam_target = Vec3::ZERO;
820+
// Sync AudioListener position + orientation to camera
821+
let fwd = (cam_target - cam_eye).normalize();
822+
listener_for_tick
823+
.set_position(cam_eye.x as f64, cam_eye.y as f64, cam_eye.z as f64);
824+
let _ = listener_for_tick.set_orientation(
825+
fwd.x as f64,
826+
fwd.y as f64,
827+
fwd.z as f64,
828+
0.0,
829+
1.0,
830+
0.0,
831+
);
832+
811833
if let Some(g) = &mut gpu {
812-
// Optional slow orbit
813-
if *orbit_tick.borrow() {
814-
orbit_t += dt_sec * 0.1; // rad/s
815-
let r = 6.0f32;
816-
let eye = Vec3::new(r * orbit_t.cos(), 0.0, r * orbit_t.sin());
817-
g.set_camera(eye, Vec3::ZERO);
818-
} else {
819-
g.set_camera(Vec3::new(0.0, 0.0, CAMERA_Z), Vec3::ZERO);
820-
}
834+
g.set_camera(cam_eye, cam_target);
821835
// Keep WebGPU surface sized to canvas backing size
822836
let w = canvas_for_tick.width();
823837
let h = canvas_for_tick.height();

0 commit comments

Comments
 (0)