@@ -194,9 +194,138 @@ async fn init() -> anyhow::Result<()> {
194194 ) ;
195195 }
196196
197- // Per-voice master gains -> destination
197+ // Master mix bus -> destination
198+ let master_gain = match web:: GainNode :: new ( & audio_ctx) {
199+ Ok ( g) => g,
200+ Err ( e) => {
201+ log:: error!( "Master GainNode error: {:?}" , e) ;
202+ return ;
203+ }
204+ } ;
205+ master_gain. gain ( ) . set_value ( 0.8 ) ;
206+ if let Err ( e) = master_gain. connect_with_audio_node ( & audio_ctx. destination ( ) ) {
207+ log:: error!( "connect error: {:?}" , e) ;
208+ return ;
209+ }
210+
211+ // Global lush reverb (Convolver) and tempo-synced dark delay bus
212+ // Reverb input and wet level
213+ let reverb_in = match web:: GainNode :: new ( & audio_ctx) {
214+ Ok ( g) => g,
215+ Err ( e) => {
216+ log:: error!( "Reverb in GainNode error: {:?}" , e) ;
217+ return ;
218+ }
219+ } ;
220+ reverb_in. gain ( ) . set_value ( 1.0 ) ;
221+ let reverb = match web:: ConvolverNode :: new ( & audio_ctx) {
222+ Ok ( n) => n,
223+ Err ( e) => {
224+ log:: error!( "ConvolverNode error: {:?}" , e) ;
225+ return ;
226+ }
227+ } ;
228+ reverb. set_normalize ( true ) ;
229+ // Create a long, dark stereo impulse response procedurally
230+ {
231+ let sr = audio_ctx. sample_rate ( ) ;
232+ let seconds = 5.0_f32 ; // lush tail
233+ let len = ( sr as f32 * seconds) as u32 ;
234+ if let Ok ( ir) = audio_ctx. create_buffer ( 2 , len, sr) {
235+ // simple xorshift32 for deterministic noise
236+ let mut seed_l: u32 = 0x1234ABCD ;
237+ let mut seed_r: u32 = 0x7890FEDC ;
238+ for ch in 0 ..2 {
239+ let mut buf: Vec < f32 > = vec ! [ 0.0 ; len as usize ] ;
240+ let mut t = 0.0_f32 ;
241+ let dt = 1.0_f32 / sr as f32 ;
242+ for i in 0 ..len as usize {
243+ let s = if ch == 0 { & mut seed_l } else { & mut seed_r } ;
244+ let mut x = * s;
245+ x ^= x << 13 ;
246+ x ^= x >> 17 ;
247+ x ^= x << 5 ;
248+ * s = x;
249+ let n = ( ( x as f32 / std:: u32:: MAX as f32 ) * 2.0 - 1.0 ) as f32 ;
250+ // Exponential decay envelope, dark tilt
251+ let decay = ( -t / 3.0 ) . exp ( ) ;
252+ let dark = ( 1.0 - ( t / seconds) ) . max ( 0.0 ) ;
253+ let v = n * decay * ( 0.6 + 0.4 * dark) ;
254+ buf[ i] = v;
255+ t += dt;
256+ }
257+ let _ = ir. copy_to_channel ( & mut buf, ch as i32 ) ;
258+ }
259+ reverb. set_buffer ( Some ( & ir) ) ;
260+ }
261+ }
262+ let reverb_wet = match web:: GainNode :: new ( & audio_ctx) {
263+ Ok ( g) => g,
264+ Err ( e) => {
265+ log:: error!( "Reverb wet GainNode error: {:?}" , e) ;
266+ return ;
267+ }
268+ } ;
269+ reverb_wet. gain ( ) . set_value ( 0.6 ) ;
270+ let _ = reverb_in. connect_with_audio_node ( & reverb) ;
271+ let _ = reverb. connect_with_audio_node ( & reverb_wet) ;
272+ let _ = reverb_wet. connect_with_audio_node ( & master_gain) ;
273+
274+ // Delay bus with feedback loop and lowpass tone for darkness
275+ let delay_in = match web:: GainNode :: new ( & audio_ctx) {
276+ Ok ( g) => g,
277+ Err ( e) => {
278+ log:: error!( "Delay in GainNode error: {:?}" , e) ;
279+ return ;
280+ }
281+ } ;
282+ delay_in. gain ( ) . set_value ( 1.0 ) ;
283+ let delay = match audio_ctx. create_delay_with_max_delay_time ( 3.0 ) {
284+ Ok ( n) => n,
285+ Err ( e) => {
286+ log:: error!( "DelayNode error: {:?}" , e) ;
287+ return ;
288+ }
289+ } ;
290+ // Around ~3/8 to ~1/2 note depending on BPM 110 → ~0.55s feels lush
291+ delay. delay_time ( ) . set_value ( 0.55 ) ;
292+ let delay_tone = match web:: BiquadFilterNode :: new ( & audio_ctx) {
293+ Ok ( n) => n,
294+ Err ( e) => {
295+ log:: error!( "BiquadFilterNode error: {:?}" , e) ;
296+ return ;
297+ }
298+ } ;
299+ delay_tone. set_type ( web:: BiquadFilterType :: Lowpass ) ;
300+ delay_tone. frequency ( ) . set_value ( 1400.0 ) ;
301+ let delay_feedback = match web:: GainNode :: new ( & audio_ctx) {
302+ Ok ( g) => g,
303+ Err ( e) => {
304+ log:: error!( "Delay feedback GainNode error: {:?}" , e) ;
305+ return ;
306+ }
307+ } ;
308+ delay_feedback. gain ( ) . set_value ( 0.6 ) ;
309+ let delay_wet = match web:: GainNode :: new ( & audio_ctx) {
310+ Ok ( g) => g,
311+ Err ( e) => {
312+ log:: error!( "Delay wet GainNode error: {:?}" , e) ;
313+ return ;
314+ }
315+ } ;
316+ delay_wet. gain ( ) . set_value ( 0.5 ) ;
317+ let _ = delay_in. connect_with_audio_node ( & delay) ;
318+ let _ = delay. connect_with_audio_node ( & delay_tone) ;
319+ let _ = delay_tone. connect_with_audio_node ( & delay_feedback) ;
320+ let _ = delay_feedback. connect_with_audio_node ( & delay) ;
321+ let _ = delay_tone. connect_with_audio_node ( & delay_wet) ;
322+ let _ = delay_wet. connect_with_audio_node ( & master_gain) ;
323+
324+ // Per-voice master gains -> master bus, plus effect sends
198325 let mut voice_gains: Vec < web:: GainNode > = Vec :: new ( ) ;
199326 let mut voice_panners: Vec < web:: PannerNode > = Vec :: new ( ) ;
327+ let mut delay_sends_vec: Vec < web:: GainNode > = Vec :: new ( ) ;
328+ let mut reverb_sends_vec: Vec < web:: GainNode > = Vec :: new ( ) ;
200329 for v in 0 ..engine. borrow ( ) . voices . len ( ) {
201330 let panner = match web:: PannerNode :: new ( & audio_ctx) {
202331 Ok ( p) => p,
@@ -225,13 +354,36 @@ async fn init() -> anyhow::Result<()> {
225354 log:: error!( "connect error: {:?}" , e) ;
226355 return ;
227356 }
228- if let Err ( e) = panner. connect_with_audio_node ( & audio_ctx . destination ( ) ) {
357+ if let Err ( e) = panner. connect_with_audio_node ( & master_gain ) {
229358 log:: error!( "connect error: {:?}" , e) ;
230359 return ;
231360 }
361+ // Per-voice sends
362+ let d_send = match web:: GainNode :: new ( & audio_ctx) {
363+ Ok ( g) => g,
364+ Err ( e) => {
365+ log:: error!( "Delay send GainNode error: {:?}" , e) ;
366+ return ;
367+ }
368+ } ;
369+ d_send. gain ( ) . set_value ( 0.4 ) ;
370+ let _ = d_send. connect_with_audio_node ( & delay_in) ;
371+ delay_sends_vec. push ( d_send) ;
372+ let r_send = match web:: GainNode :: new ( & audio_ctx) {
373+ Ok ( g) => g,
374+ Err ( e) => {
375+ log:: error!( "Reverb send GainNode error: {:?}" , e) ;
376+ return ;
377+ }
378+ } ;
379+ r_send. gain ( ) . set_value ( 0.65 ) ;
380+ let _ = r_send. connect_with_audio_node ( & reverb_in) ;
381+ reverb_sends_vec. push ( r_send) ;
232382 voice_gains. push ( gain) ;
233383 voice_panners. push ( panner) ;
234384 }
385+ let delay_sends = Rc :: new ( delay_sends_vec) ;
386+ let reverb_sends = Rc :: new ( reverb_sends_vec) ;
235387
236388 // Initialize WebGPU (leak a canvas clone to satisfy 'static lifetime for surface)
237389 let leaked_canvas = Box :: leak ( Box :: new ( canvas_for_click_inner. clone ( ) ) ) ;
@@ -430,6 +582,7 @@ async fn init() -> anyhow::Result<()> {
430582 let master_muted_k = master_muted. clone ( ) ;
431583 let orbit_enabled_k = orbit_enabled. clone ( ) ;
432584 let voice_gains_k = voice_gains. clone ( ) ;
585+ let master_gain_k = master_gain. clone ( ) ;
433586 let window = web:: window ( ) . unwrap ( ) ;
434587 let closure = Closure :: wrap ( Box :: new ( move |ev : web:: KeyboardEvent | {
435588 let key = ev. key ( ) ;
@@ -546,10 +699,8 @@ async fn init() -> anyhow::Result<()> {
546699 "m" | "M" => {
547700 let mut muted = master_muted_k. borrow_mut ( ) ;
548701 * muted = !* muted;
549- let new_val = if * muted { 0.0 } else { 0.2 } ;
550- for g in voice_gains_k. iter ( ) {
551- g. gain ( ) . set_value ( new_val) ;
552- }
702+ let new_val = if * muted { 0.0 } else { 0.8 } ;
703+ master_gain_k. gain ( ) . set_value ( new_val) ;
553704 log:: info!( "[keys] master muted={}" , * muted) ;
554705 // If hint visible, refresh its content
555706 if let Some ( win) = web:: window ( ) {
@@ -713,6 +864,8 @@ async fn init() -> anyhow::Result<()> {
713864 let hover_tick = hover_index. clone ( ) ;
714865 let canvas_for_tick = canvas_for_click_inner. clone ( ) ;
715866 let voice_gains_tick = voice_gains. clone ( ) ;
867+ let delay_sends_tick = delay_sends. clone ( ) ;
868+ let reverb_sends_tick = reverb_sends. clone ( ) ;
716869 // Optional slow camera orbit
717870 let mut orbit_t: f32 = 0.0 ;
718871 let orbit_tick = orbit_enabled. clone ( ) ;
@@ -809,6 +962,25 @@ async fn init() -> anyhow::Result<()> {
809962 BASE_SCALE + ps[ 2 ] * SCALE_PULSE_MULTIPLIER ,
810963 ] ;
811964
965+ // Orbiting ring particles around each voice center
966+ let two_pi = std:: f32:: consts:: PI * 2.0 ;
967+ let ring_count = 48usize ;
968+ for vi in 0 ..3 {
969+ let center = positions[ vi] ;
970+ let base_col = Vec3 :: from ( e_ref. configs [ vi] . color_rgb ) ;
971+ let ring_r = 0.9 + ps[ vi] * 0.25 ;
972+ for j in 0 ..ring_count {
973+ let a =
974+ orbit_t * 0.8 + ( j as f32 ) * ( two_pi / ring_count as f32 ) ;
975+ let offset = Vec3 :: new ( a. cos ( ) * ring_r, 0.0 , a. sin ( ) * ring_r) ;
976+ positions. push ( center + offset) ;
977+ let c = base_col * 0.55 ;
978+ colors. push ( Vec4 :: from ( ( c, 0.9 ) ) ) ;
979+ let s = 0.06 + 0.04 * ( ( j % 12 ) as f32 / 12.0 ) ;
980+ scales. push ( s) ;
981+ }
982+ }
983+
812984 // Optional analyser-driven dot spectrum row
813985 if let Some ( a) = & analyser {
814986 let bins = a. frequency_bin_count ( ) as usize ;
@@ -903,6 +1075,11 @@ async fn init() -> anyhow::Result<()> {
9031075 let _ = src. connect_with_audio_node ( & gain) ;
9041076 let _ =
9051077 gain. connect_with_audio_node ( & voice_gains_tick[ ev. voice_index ] ) ;
1078+ // Effect sends per note
1079+ let _ =
1080+ gain. connect_with_audio_node ( & delay_sends_tick[ ev. voice_index ] ) ;
1081+ let _ = gain
1082+ . connect_with_audio_node ( & reverb_sends_tick[ ev. voice_index ] ) ;
9061083
9071084 let _ = src. start_with_when ( t0) ;
9081085 let _ = src. stop_with_when ( t0 + ev. duration_sec as f64 + 0.02 ) ;
@@ -1079,10 +1256,10 @@ impl<'a> GpuState<'a> {
10791256 contents : bytemuck:: cast_slice ( & quad_vertices) ,
10801257 usage : wgpu:: BufferUsages :: VERTEX ,
10811258 } ) ;
1082- // Instance buffer (capacity for 32 instances)
1259+ // Instance buffer (capacity for many instances: 3 voices + rings + spectrum )
10831260 let instance_vb = device. create_buffer ( & wgpu:: BufferDescriptor {
10841261 label : Some ( "instance_vb" ) ,
1085- size : ( std:: mem:: size_of :: < InstanceData > ( ) * 32 ) as u64 ,
1262+ size : ( std:: mem:: size_of :: < InstanceData > ( ) * 1024 ) as u64 ,
10861263 usage : wgpu:: BufferUsages :: VERTEX | wgpu:: BufferUsages :: COPY_DST ,
10871264 mapped_at_creation : false ,
10881265 } ) ;
0 commit comments