|
1 | 1 | use app_core::MusicEngine; |
2 | | -use app_core::{AEOLIAN, DORIAN, IONIAN, LOCRIAN, LYDIAN, MIXOLYDIAN, PHRYGIAN}; |
| 2 | +use app_core::{ |
| 3 | + midi_to_hz, z_offset_vec3, AEOLIAN, DORIAN, ENGINE_DRAG_MAX_RADIUS, IONIAN, LOCRIAN, LYDIAN, |
| 4 | + MIXOLYDIAN, PHRYGIAN, PICK_SPHERE_RADIUS, SPREAD, |
| 5 | +}; |
| 6 | +use crate::audio; |
| 7 | +use crate::input; |
| 8 | +use crate::render; |
| 9 | +use app_core::Waveform; |
3 | 10 | use std::cell::RefCell; |
4 | 11 | use std::rc::Rc; |
| 12 | +use wasm_bindgen::JsCast; |
5 | 13 | use web_sys as web; |
6 | 14 |
|
7 | 15 | #[inline] |
@@ -136,3 +144,188 @@ pub fn wire_overlay_toggle_h(document: &web::Document) { |
136 | 144 | closure.forget(); |
137 | 145 | } |
138 | 146 | } |
| 147 | + |
| 148 | +pub fn wire_global_keydown( |
| 149 | + engine: Rc<RefCell<MusicEngine>>, |
| 150 | + paused: Rc<RefCell<bool>>, |
| 151 | + master_gain: web::GainNode, |
| 152 | + canvas: web::HtmlCanvasElement, |
| 153 | +) { |
| 154 | + if let Some(window) = web::window() { |
| 155 | + let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |ev: web::KeyboardEvent| { |
| 156 | + super::events::handle_global_keydown(&ev, &engine, &paused, &master_gain, &canvas); |
| 157 | + }) as Box<dyn FnMut(_)>); |
| 158 | + let _ = window.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()); |
| 159 | + closure.forget(); |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +pub struct InputWiring { |
| 164 | + pub canvas: web::HtmlCanvasElement, |
| 165 | + pub engine: Rc<RefCell<MusicEngine>>, |
| 166 | + pub mouse_state: Rc<RefCell<input::MouseState>>, |
| 167 | + pub hover_index: Rc<RefCell<Option<usize>>>, |
| 168 | + pub drag_state: Rc<RefCell<input::DragState>>, |
| 169 | + pub voice_gains: Rc<Vec<web::GainNode>>, |
| 170 | + pub delay_sends: Rc<Vec<web::GainNode>>, |
| 171 | + pub reverb_sends: Rc<Vec<web::GainNode>>, |
| 172 | + pub audio_ctx: web::AudioContext, |
| 173 | + pub queued_ripple_uv: Rc<RefCell<Option<[f32; 2]>>>, |
| 174 | +} |
| 175 | + |
| 176 | +pub fn wire_input_handlers(w: InputWiring) { |
| 177 | + // pointermove |
| 178 | + { |
| 179 | + let mouse_state_m = w.mouse_state.clone(); |
| 180 | + let hover_m = w.hover_index.clone(); |
| 181 | + let drag_m = w.drag_state.clone(); |
| 182 | + let engine_m = w.engine.clone(); |
| 183 | + let canvas_mouse = w.canvas.clone(); |
| 184 | + let canvas_connected = canvas_mouse.is_connected(); |
| 185 | + let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |ev: web::PointerEvent| { |
| 186 | + let pos = input::pointer_canvas_px(&ev, &canvas_mouse); |
| 187 | + if !canvas_connected { |
| 188 | + return; |
| 189 | + } |
| 190 | + { |
| 191 | + let mut ms = mouse_state_m.borrow_mut(); |
| 192 | + ms.x = pos.x; |
| 193 | + ms.y = pos.y; |
| 194 | + } |
| 195 | + let (ro, rd) = render::screen_to_world_ray(&canvas_mouse, pos.x, pos.y, super::CAMERA_Z); |
| 196 | + let mut best = None::<(usize, f32)>; |
| 197 | + let z_offset = z_offset_vec3(); |
| 198 | + for (i, v) in engine_m.borrow().voices.iter().enumerate() { |
| 199 | + let center_world = v.position * SPREAD + z_offset; |
| 200 | + if let Some(t) = input::ray_sphere(ro, rd, center_world, PICK_SPHERE_RADIUS) { |
| 201 | + if t >= 0.0 { |
| 202 | + match best { |
| 203 | + Some((_, bt)) if t >= bt => {} |
| 204 | + _ => best = Some((i, t)), |
| 205 | + } |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + if drag_m.borrow().active { |
| 210 | + let plane_z = drag_m.borrow().plane_z_world; |
| 211 | + if rd.z.abs() > 1e-6 { |
| 212 | + let t = (plane_z - ro.z) / rd.z; |
| 213 | + if t >= 0.0 { |
| 214 | + let hit_world = ro + rd * t; |
| 215 | + let mut eng_pos = (hit_world - z_offset_vec3()) / SPREAD; |
| 216 | + let max_r = ENGINE_DRAG_MAX_RADIUS; |
| 217 | + let len = (eng_pos.x * eng_pos.x + eng_pos.z * eng_pos.z).sqrt(); |
| 218 | + if len > max_r { |
| 219 | + let scale = max_r / len; |
| 220 | + eng_pos.x *= scale; |
| 221 | + eng_pos.z *= scale; |
| 222 | + } |
| 223 | + let mut eng = engine_m.borrow_mut(); |
| 224 | + let vi = drag_m.borrow().voice; |
| 225 | + eng.set_voice_position(vi, glam::Vec3::new(eng_pos.x, 0.0, eng_pos.z)); |
| 226 | + } |
| 227 | + } |
| 228 | + } else { |
| 229 | + match best { |
| 230 | + Some((i, _t)) => { |
| 231 | + *hover_m.borrow_mut() = Some(i); |
| 232 | + } |
| 233 | + None => { |
| 234 | + *hover_m.borrow_mut() = None; |
| 235 | + } |
| 236 | + } |
| 237 | + } |
| 238 | + }) as Box<dyn FnMut(_)>); |
| 239 | + if let Some(wnd) = web::window() { |
| 240 | + let _ = wnd.add_event_listener_with_callback("pointermove", closure.as_ref().unchecked_ref()); |
| 241 | + } |
| 242 | + closure.forget(); |
| 243 | + } |
| 244 | + |
| 245 | + // pointerdown |
| 246 | + { |
| 247 | + let hover_m = w.hover_index.clone(); |
| 248 | + let drag_m = w.drag_state.clone(); |
| 249 | + let mouse_m = w.mouse_state.clone(); |
| 250 | + let engine_m = w.engine.clone(); |
| 251 | + let canvas_target = w.canvas.clone(); |
| 252 | + let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |ev: web::PointerEvent| { |
| 253 | + if let Some(i) = *hover_m.borrow() { |
| 254 | + let mut ds = drag_m.borrow_mut(); |
| 255 | + ds.active = true; |
| 256 | + ds.voice = i; |
| 257 | + ds.plane_z_world = engine_m.borrow().voices[i].position.z * SPREAD + z_offset_vec3().z; |
| 258 | + log::info!("[mouse] begin drag on voice {}", i); |
| 259 | + } |
| 260 | + mouse_m.borrow_mut().down = true; |
| 261 | + let _ = canvas_target.set_pointer_capture(ev.pointer_id()); |
| 262 | + ev.prevent_default(); |
| 263 | + }) as Box<dyn FnMut(_)>); |
| 264 | + let _ = w.canvas.add_event_listener_with_callback("pointerdown", closure.as_ref().unchecked_ref()); |
| 265 | + closure.forget(); |
| 266 | + } |
| 267 | + |
| 268 | + // pointerup |
| 269 | + { |
| 270 | + let hover_m = w.hover_index.clone(); |
| 271 | + let drag_m = w.drag_state.clone(); |
| 272 | + let mouse_m = w.mouse_state.clone(); |
| 273 | + let engine_m = w.engine.clone(); |
| 274 | + let voice_gains_click = w.voice_gains.clone(); |
| 275 | + let delay_sends_click = w.delay_sends.clone(); |
| 276 | + let reverb_sends_click = w.reverb_sends.clone(); |
| 277 | + let canvas_click = w.canvas.clone(); |
| 278 | + let audio_ctx_click = w.audio_ctx.clone(); |
| 279 | + let ripple_queue = w.queued_ripple_uv.clone(); |
| 280 | + let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |ev: web::PointerEvent| { |
| 281 | + let was_dragging = drag_m.borrow().active; |
| 282 | + if was_dragging { |
| 283 | + drag_m.borrow_mut().active = false; |
| 284 | + } else if let Some(i) = *hover_m.borrow() { |
| 285 | + let shift = ev.shift_key(); |
| 286 | + let alt = ev.alt_key(); |
| 287 | + if alt { |
| 288 | + engine_m.borrow_mut().toggle_solo(i); |
| 289 | + } else if shift { |
| 290 | + engine_m.borrow_mut().reseed_voice(i, None); |
| 291 | + } else { |
| 292 | + engine_m.borrow_mut().toggle_mute(i); |
| 293 | + } |
| 294 | + } else { |
| 295 | + let [uvx, uvy] = input::pointer_canvas_uv(&ev, &canvas_click); |
| 296 | + if uvx.is_finite() && uvy.is_finite() { |
| 297 | + let midi = 60.0 + uvx * 24.0; |
| 298 | + let freq = midi_to_hz(midi as f32); |
| 299 | + let vel = (0.35 + 0.65 * uvy) as f32; |
| 300 | + let eng = engine_m.borrow(); |
| 301 | + let norm_xs: Vec<f32> = eng |
| 302 | + .voices |
| 303 | + .iter() |
| 304 | + .map(|v| (v.position.x / 3.0).clamp(-1.0, 1.0) * 0.5 + 0.5) |
| 305 | + .collect(); |
| 306 | + let best_i = crate::input::nearest_index_by_uvx(&norm_xs, uvx); |
| 307 | + let dur = 0.35 + 0.25 * (1.0 - uvy as f64); |
| 308 | + let wf = eng.configs[best_i].waveform; |
| 309 | + drop(eng); |
| 310 | + audio::trigger_one_shot( |
| 311 | + &audio_ctx_click, |
| 312 | + wf, |
| 313 | + freq, |
| 314 | + vel, |
| 315 | + dur, |
| 316 | + &voice_gains_click[best_i], |
| 317 | + &delay_sends_click[best_i], |
| 318 | + &reverb_sends_click[best_i], |
| 319 | + ); |
| 320 | + *ripple_queue.borrow_mut() = Some([uvx, uvy]); |
| 321 | + } |
| 322 | + } |
| 323 | + mouse_m.borrow_mut().down = false; |
| 324 | + ev.prevent_default(); |
| 325 | + }) as Box<dyn FnMut(_)>); |
| 326 | + if let Some(wnd) = web::window() { |
| 327 | + let _ = wnd.add_event_listener_with_callback("pointerup", closure.as_ref().unchecked_ref()); |
| 328 | + } |
| 329 | + closure.forget(); |
| 330 | + } |
| 331 | +} |
0 commit comments