@@ -856,15 +856,23 @@ async fn init() -> anyhow::Result<()> {
856856 let engine_tick = engine. clone ( ) ;
857857 let hover_tick = hover_index. clone ( ) ;
858858 let canvas_for_tick = canvas_for_click_inner. clone ( ) ;
859+ let mouse_tick = mouse_state. clone ( ) ;
859860 let voice_gains_tick = voice_gains. clone ( ) ;
860861 let delay_sends_tick = delay_sends. clone ( ) ;
861862 let reverb_sends_tick = reverb_sends. clone ( ) ;
863+ // Global effect controls accessible during tick
864+ let reverb_wet_tick = Rc :: new ( reverb_wet) . clone ( ) ;
865+ let delay_wet_tick = Rc :: new ( delay_wet) . clone ( ) ;
866+ let delay_feedback_tick = Rc :: new ( delay_feedback) . clone ( ) ;
862867 // Optional slow camera orbit
863868 let mut orbit_t: f32 = 0.0 ;
864869 let orbit_tick = orbit_enabled. clone ( ) ;
865870 let tick: Rc < RefCell < Option < Closure < dyn FnMut ( ) > > > > =
866871 Rc :: new ( RefCell :: new ( None ) ) ;
867872 let tick_clone = tick. clone ( ) ;
873+ // State for mouse-driven swirl energy
874+ let mut prev_uv: [ f32 ; 2 ] = [ 0.5 , 0.5 ] ;
875+ let mut swirl_energy: f32 = 0.0 ;
868876 * tick. borrow_mut ( ) = Some ( Closure :: wrap ( Box :: new ( move || {
869877 let now = Instant :: now ( ) ;
870878 let dt = now - last_instant;
@@ -890,6 +898,30 @@ async fn init() -> anyhow::Result<()> {
890898 // Exponential decay for smoother falloff
891899 * p *= ( 1.0 - ( dt_sec * 1.8 ) . min ( 0.9 ) ) ;
892900 }
901+ // Mouse-driven swirl effect intensity (visual + global audio whoosh)
902+ let w = canvas_for_tick. width ( ) . max ( 1 ) as f32 ;
903+ let h = canvas_for_tick. height ( ) . max ( 1 ) as f32 ;
904+ let ms = mouse_tick. borrow ( ) ;
905+ let uv = [
906+ ( ms. x / w) . clamp ( 0.0 , 1.0 ) ,
907+ ( 1.0 - ( ms. y / h) ) . clamp ( 0.0 , 1.0 ) ,
908+ ] ;
909+ let du = uv[ 0 ] - prev_uv[ 0 ] ;
910+ let dv = uv[ 1 ] - prev_uv[ 1 ] ;
911+ let speed = ( ( du * du + dv * dv) . sqrt ( ) / ( dt_sec + 1e-5 ) ) . min ( 10.0 ) ;
912+ let target = ( ( speed * 0.25 ) + if ms. down { 0.6 } else { 0.0 } )
913+ . clamp ( 0.0 , 1.0 ) ;
914+ swirl_energy = 0.85 * swirl_energy + 0.15 * target;
915+ prev_uv = uv;
916+ drop ( ms) ;
917+
918+ // Apply global FX modulation based on swirl_energy
919+ let _ = reverb_wet_tick. gain ( ) . set_value ( 0.35 + 0.65 * swirl_energy) ;
920+ let _ = delay_wet_tick. gain ( ) . set_value ( 0.20 + 0.80 * swirl_energy) ;
921+ let _ = delay_feedback_tick
922+ . gain ( )
923+ . set_value ( 0.45 + 0.45 * swirl_energy) ;
924+
893925 for i in 0 ..voice_panners. len ( ) {
894926 let pos = engine_tick. borrow ( ) . voices [ i] . position ;
895927 voice_panners[ i] . set_position (
@@ -900,9 +932,14 @@ async fn init() -> anyhow::Result<()> {
900932 // Direct sound↔visual link: map position to per-voice mix and fx
901933 let dist = ( pos. x * pos. x + pos. z * pos. z ) . sqrt ( ) ;
902934 // Delay send increases with |x|, reverb with radial distance
903- let d_amt = ( 0.15 + 0.85 * pos. x . abs ( ) . min ( 1.0 ) ) . clamp ( 0.0 , 1.0 ) ;
904- let r_amt =
905- ( 0.25 + 0.75 * ( dist / 2.5 ) . clamp ( 0.0 , 1.0 ) ) . clamp ( 0.0 , 1.2 ) ;
935+ let mut d_amt = ( 0.15 + 0.85 * pos. x . abs ( ) . min ( 1.0 ) )
936+ . clamp ( 0.0 , 1.0 ) ;
937+ let mut r_amt = ( 0.25 + 0.75 * ( dist / 2.5 ) . clamp ( 0.0 , 1.0 ) )
938+ . clamp ( 0.0 , 1.2 ) ;
939+ // Boost sends with swirl energy for pronounced movement effect
940+ let boost = 1.0 + 0.8 * swirl_energy;
941+ d_amt = ( d_amt * boost) . clamp ( 0.0 , 1.2 ) ;
942+ r_amt = ( r_amt * boost) . clamp ( 0.0 , 1.5 ) ;
906943 delay_sends_tick[ i] . gain ( ) . set_value ( d_amt) ;
907944 reverb_sends_tick[ i] . gain ( ) . set_value ( r_amt) ;
908945 // Subtle level change with proximity to center (prevents clipping)
@@ -944,16 +981,17 @@ async fn init() -> anyhow::Result<()> {
944981 let e_ref = engine_tick. borrow ( ) ;
945982 let z_offset = z_offset_vec3 ( ) ;
946983 let spread = SPREAD ;
947- let mut positions: Vec < Vec3 > = vec ! [
948- e_ref. voices[ 0 ] . position * spread + z_offset,
949- e_ref. voices[ 1 ] . position * spread + z_offset,
950- e_ref. voices[ 2 ] . position * spread + z_offset,
951- ] ;
952- let mut colors: Vec < Vec4 > = vec ! [
953- Vec4 :: from( ( Vec3 :: from( e_ref. configs[ 0 ] . color_rgb) , 1.0 ) ) ,
954- Vec4 :: from( ( Vec3 :: from( e_ref. configs[ 1 ] . color_rgb) , 1.0 ) ) ,
955- Vec4 :: from( ( Vec3 :: from( e_ref. configs[ 2 ] . color_rgb) , 1.0 ) ) ,
956- ] ;
984+ // Pre-allocate to avoid per-frame reallocations
985+ let ring_count = 48usize ;
986+ let mut positions: Vec < Vec3 > =
987+ Vec :: with_capacity ( 3 + ring_count * 3 + 16 ) ;
988+ positions. push ( e_ref. voices [ 0 ] . position * spread + z_offset) ;
989+ positions. push ( e_ref. voices [ 1 ] . position * spread + z_offset) ;
990+ positions. push ( e_ref. voices [ 2 ] . position * spread + z_offset) ;
991+ let mut colors: Vec < Vec4 > = Vec :: with_capacity ( 3 + ring_count * 3 + 16 ) ;
992+ colors. push ( Vec4 :: from ( ( Vec3 :: from ( e_ref. configs [ 0 ] . color_rgb ) , 1.0 ) ) ) ;
993+ colors. push ( Vec4 :: from ( ( Vec3 :: from ( e_ref. configs [ 1 ] . color_rgb ) , 1.0 ) ) ) ;
994+ colors. push ( Vec4 :: from ( ( Vec3 :: from ( e_ref. configs [ 2 ] . color_rgb ) , 1.0 ) ) ) ;
957995 let hovered = * hover_tick. borrow ( ) ;
958996 for i in 0 ..3 {
959997 if e_ref. voices [ i] . muted {
@@ -968,15 +1006,14 @@ async fn init() -> anyhow::Result<()> {
9681006 colors[ i] . z = ( colors[ i] . z * 1.4 ) . min ( 1.0 ) ;
9691007 }
9701008 }
971- let mut scales: Vec < f32 > = vec ! [
972- BASE_SCALE + ps [ 0 ] * SCALE_PULSE_MULTIPLIER ,
973- BASE_SCALE + ps[ 1 ] * SCALE_PULSE_MULTIPLIER ,
974- BASE_SCALE + ps[ 2 ] * SCALE_PULSE_MULTIPLIER ,
975- ] ;
1009+ let mut scales: Vec < f32 > =
1010+ Vec :: with_capacity ( 3 + ring_count * 3 + 16 ) ;
1011+ scales . push ( BASE_SCALE + ps[ 0 ] * SCALE_PULSE_MULTIPLIER ) ;
1012+ scales . push ( BASE_SCALE + ps[ 1 ] * SCALE_PULSE_MULTIPLIER ) ;
1013+ scales . push ( BASE_SCALE + ps [ 2 ] * SCALE_PULSE_MULTIPLIER ) ;
9761014
9771015 // Orbiting ring particles around each voice center
9781016 let two_pi = std:: f32:: consts:: PI * 2.0 ;
979- let ring_count = 48usize ;
9801017 for vi in 0 ..3 {
9811018 let center = positions[ vi] ;
9821019 let base_col = Vec3 :: from ( e_ref. configs [ vi] . color_rgb ) ;
@@ -1049,6 +1086,8 @@ async fn init() -> anyhow::Result<()> {
10491086
10501087 if let Some ( g) = & mut gpu {
10511088 g. set_camera ( cam_eye, cam_target) ;
1089+ // Feed mouse-driven swirl into GPU uniforms
1090+ g. set_swirl ( uv, 1.0 , swirl_energy > 0.05 ) ;
10521091 // Keep WebGPU surface sized to canvas backing size
10531092 let w = canvas_for_tick. width ( ) ;
10541093 let h = canvas_for_tick. height ( ) ;
0 commit comments