1pub fn build_orca_inject_queue_ref(
3) -> Option<fn(&str) -> std::collections::VecDeque<InjectEvent>> {
4 Some(build_orca_inject_queue)
5}
6pub fn build_orca_inject_queue(
8 file_path: &str,
9) -> std::collections::VecDeque<InjectEvent> {
10 use std::collections::VecDeque;
11 use std::fs::File;
12 use std::io::{BufRead, BufReader};
13 use varvara::Key;
14
15 let mut queue = VecDeque::new();
16 const CTRL_H: Key = Key::Ctrl;
17 const RIGHT: Key = Key::Right;
18 const LEFT: Key = Key::Left;
19 const UP: Key = Key::Up;
20 const DOWN: Key = Key::Down;
21 let mut lines: Vec<Vec<char>> = Vec::new();
23 let mut max_len = 0;
24 if let Ok(file) = File::open(file_path) {
25 let reader = BufReader::new(file);
26 for line in reader.lines().map_while(Result::ok) {
27 let chars: Vec<char> = line.chars().collect();
28 max_len = max_len.max(chars.len());
29 lines.push(chars);
30 }
31 }
32 let rows = lines.len();
33 let cols = max_len;
34 let mut grid = vec![vec!['/'; cols + 2]; rows + 2];
36 for i in 1..=rows {
37 for j in 1..=cols {
38 grid[i][j] = if j - 1 < lines[i - 1].len() {
39 lines[i - 1][j - 1]
40 } else {
41 ' '
42 };
43 }
44 }
45 let mut cur_row = 1;
47 let mut cur_col = 1;
48 queue.push_back(InjectEvent::KeyPress(CTRL_H));
49 queue.push_back(InjectEvent::KeyRelease(CTRL_H));
50 for (r, row) in grid.iter().enumerate().take(rows + 2) {
52 for (c, &ch) in row.iter().enumerate().take(cols + 2) {
53 if ch != '.' {
54 let dr = r as isize - cur_row as isize;
56 let dc = c as isize - cur_col as isize;
57 for _ in 0..dr.abs() {
58 queue.push_back(if dr > 0 {
59 InjectEvent::KeyPress(DOWN)
60 } else {
61 InjectEvent::KeyPress(UP)
62 });
63 queue.push_back(if dr > 0 {
64 InjectEvent::KeyRelease(DOWN)
65 } else {
66 InjectEvent::KeyRelease(UP)
67 });
68 }
69 for _ in 0..dc.abs() {
70 queue.push_back(if dc > 0 {
71 InjectEvent::KeyPress(RIGHT)
72 } else {
73 InjectEvent::KeyPress(LEFT)
74 });
75 queue.push_back(if dc > 0 {
76 InjectEvent::KeyRelease(RIGHT)
77 } else {
78 InjectEvent::KeyRelease(LEFT)
79 });
80 }
81 cur_row = r;
82 cur_col = c;
83 queue.push_back(InjectEvent::Char(ch as u8));
85 if ch == '/' && r != 0 && r != rows + 1 {
87 let hex = format!("{c:02X}{r:02X}");
89 for b in hex.bytes() {
90 queue.push_back(InjectEvent::Char(b));
91 }
92 }
93 }
94 }
95 }
96 queue
97}
98
99#[cfg(not(target_arch = "wasm32"))]
100use getrandom;
101#[derive(Debug, Clone)]
102pub enum InjectEvent {
103 Char(u8),
104 KeyPress(Key),
105 KeyRelease(Key),
106 Sleep(u64), Chord(Vec<Key>), }
109#[allow(clippy::module_inception)]
112pub mod uxn {
113
114 use std::path::Path;
115 use std::sync::{Arc, Mutex};
116 use uxn::{Backend, Uxn};
117 use varvara::Varvara;
118 pub struct UxnModule {
120 pub uxn: Arc<Mutex<Uxn<'static>>>,
121 pub varvara: Option<Varvara>,
122 }
123
124 impl UxnModule {
125 pub fn new(rom_path: Option<&Path>) -> Result<Self, String> {
127 static mut RAM: [u8; 65536] = [0; 65536];
129 #[allow(static_mut_refs)]
130 let ram: &'static mut [u8; 65536] = unsafe { &mut RAM };
131 let mut uxn = Uxn::new(ram, Backend::Interpreter);
132 let mut varvara = Varvara::default();
133
134 if let Some(path) = rom_path {
137 let rom = std::fs::read(path)
138 .map_err(|e| format!("Failed to read ROM: {e}"))?;
139 let _ = uxn.reset(&rom);
140 varvara.reset(&rom);
141
142 varvara.load_sym_with_rom_path(path);
143 }
144
145 Ok(UxnModule {
146 uxn: Arc::new(Mutex::new(uxn)),
147 varvara: Some(varvara),
148 })
149 }
150
151 pub fn load_rom(&mut self, rom_path: &Path) -> Result<(), String> {
159 let rom = std::fs::read(rom_path)
160 .map_err(|e| format!("Failed to read ROM: {e}"))?;
161 let mut uxn = self.uxn.lock().unwrap();
162 let _ = uxn.reset(&rom);
163 if let Some(varvara) = self.varvara.as_mut() {
164 varvara.load_sym_with_rom_path(rom_path);
165 }
166 Ok(())
167 }
168 }
169}
170
171use log::{error, info};
172use std::path::Path;
173use std::sync::{Arc, Mutex};
174use ::uxn::{Backend, Uxn};
176use varvara::Key;
177use varvara::MouseState;
178use varvara::TrackerState;
179use varvara::Varvara;
180pub struct UxnModule {
182 pub uxn: Arc<Mutex<Uxn<'static>>>,
183 pub varvara: Option<Varvara>,
184}
185
186impl UxnModule {
187 pub fn new(rom_path: Option<&Path>) -> Result<Self, String> {
189 static mut RAM: [u8; 65536] = [0; 65536];
191 #[allow(static_mut_refs)]
192 let ram: &'static mut [u8; 65536] = unsafe { &mut RAM };
193 let mut uxn = Uxn::new(ram, Backend::Interpreter);
194 let mut varvara = Varvara::default();
195 if let Some(path) = rom_path {
196 let rom = std::fs::read(path)
197 .map_err(|e| format!("Failed to read ROM: {e}"))?;
198 let _ = uxn.reset(&rom);
199 varvara.load_sym_with_rom_path(path);
200 }
201 Ok(UxnModule {
202 uxn: Arc::new(Mutex::new(uxn)),
203 varvara: Some(varvara),
204 })
205 }
206
207 pub fn reset(&self, rom: &[u8]) {
213 let mut uxn = self.uxn.lock().unwrap();
214 let _ = uxn.reset(rom);
215 }
216
217 pub fn load_rom(&mut self, rom_path: &Path) -> Result<(), String> {
219 let rom = std::fs::read(rom_path)
220 .map_err(|e| format!("Failed to read ROM: {e}"))?;
221 if let Some(varvara) = self.varvara.as_mut() {
222 varvara.load_sym_with_rom_path(rom_path);
223 }
224 self.reset(&rom);
225 Ok(())
226 }
227
228 }
230
231pub mod egui_ui {
233 use super::*;
234 use egui::{CollapsingHeader, Ui};
235
236 pub fn show_uxn_panel(ui: &mut Ui, _uxn_mod: &UxnModule) {
237 CollapsingHeader::new("Uxn VM State").show(ui, |ui| {
238 ui.label("Uxn state display not implemented (no UxnState in _uxn)");
239 });
240 }
241}
242use eframe::egui;
243use std::sync::mpsc;
244#[derive(Debug)]
247pub enum Event {
248 LoadRom(Vec<u8>),
249 SetMuted(bool),
250 Console(u8),
251}
252
253pub struct UxnApp<'a> {
254 pub vm: Uxn<'a>,
255 pub dev: Varvara,
256 scale: f32,
257 size: (u16, u16),
258 next_frame: f64,
259 scroll: (f32, f32),
260 cursor_pos: Option<(f32, f32)>,
261 texture: egui::TextureHandle,
262 event_rx: mpsc::Receiver<Event>,
263 resized: Option<Box<dyn FnMut(u16, u16)>>,
264 window_mode: String,
265 aspect_ratio: f32,
266 auto_rom_select: bool,
268 auto_timer: f64,
269 auto_index: usize,
270 auto_roms: Vec<varvara::RomData>,
271 auto_rom_labels: Vec<String>,
273 #[allow(clippy::type_complexity)]
275 on_rom_change: Option<Box<dyn Fn(&str) + Send + Sync>>,
276 #[allow(clippy::type_complexity)]
278 on_first_update: Option<Box<dyn FnOnce(&mut UxnApp<'a>) + Send + 'a>>,
279 first_update_done: bool,
280 current_rom_label: Option<String>,
282 input_queue: std::collections::VecDeque<InjectEvent>,
284 last_rom_path: Option<std::path::PathBuf>,
286 pub reload_rx: std::sync::mpsc::Receiver<()>,
288 pub rom_path_arc:
289 std::sync::Arc<std::sync::Mutex<Option<std::path::PathBuf>>>,
290 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
291 pub usb_controller: Option<UsbControllerHandle>,
292 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
293 pub last_usb_event: Option<(u8, Vec<u8>)>, }
295
296#[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
297pub struct UsbControllerHandle {
298 pub rx: std::sync::mpsc::Receiver<
299 varvara::controller_usb::UsbControllerMessage,
300 >,
301}
302
303impl<'a> UxnApp<'a> {
304 pub fn set_on_first_update(
306 &mut self,
307 f: Box<dyn FnOnce(&mut UxnApp<'a>) + Send + 'a>,
308 ) {
309 self.on_first_update = Some(f);
310 self.first_update_done = false;
311 }
312 pub fn set_rom_label<S: Into<String>>(&mut self, label: S) {
314 self.current_rom_label = Some(label.into());
315 }
316 #[allow(clippy::too_many_arguments)]
317 pub fn new_with_mode(
318 mut vm: Uxn<'a>,
319 mut dev: Varvara,
320 mut size: (u16, u16),
321 scale: f32,
322 event_rx: mpsc::Receiver<Event>,
323 ctx: &egui::Context,
324 window_mode: String,
325 auto_roms: Vec<varvara::RomData>,
326 auto_rom_labels: Vec<String>,
327 auto_rom_select: bool,
328 reload_rx: std::sync::mpsc::Receiver<()>,
329 rom_path_arc: std::sync::Arc<
330 std::sync::Mutex<Option<std::path::PathBuf>>,
331 >,
332 ) -> Self {
333 if let Some(path) = rom_path_arc.lock().unwrap().as_ref() {
334 if path.exists() {
335 dev.load_sym_with_rom_path(path);
336 }
337 }
338 vm.run(&mut dev, 0x100);
340
341 dev.redraw(&mut vm);
342
343 let w = 2048_usize; let h = 2048_usize;
345 size.0 = w as u16;
346 size.1 = h as u16;
347 let image =
348 egui::ColorImage::new([w, h], vec![egui::Color32::BLACK; w * h]);
349 let texture =
350 ctx.load_texture("frame", image, egui::TextureOptions::NEAREST);
351 let aspect_ratio = w as f32 / h as f32;
352 let mut auto_index = 0;
353 let mut auto_timer = 0.0;
354 let mut current_rom_label = None;
355 if auto_rom_select && !auto_roms.is_empty() {
356 println!(
357 "[AUTO ROM CYCLING] Enabled. Cycling through {} ROMs every 10s.",
358 auto_roms.len()
359 );
360 let rom = &auto_roms[0];
362 let _ = vm.reset(&rom.rom);
363 dev.reset(&rom.rom);
364 vm.run(&mut dev, 0x100);
365 dev.redraw(&mut vm);
366
367 auto_index = 0;
368 auto_timer = 0.0;
369 if !auto_rom_labels.is_empty() {
371 current_rom_label = Some(auto_rom_labels[0].clone());
372 } else {
373 current_rom_label = Some("ROM 1".to_string());
374 }
375 }
376 UxnApp {
377 vm,
378 dev,
379 scale,
380 size,
381 next_frame: 0.0,
382 event_rx,
383 resized: None,
384 scroll: (0.0, 0.0),
385 cursor_pos: None,
386 texture,
387 window_mode,
388 aspect_ratio,
389 auto_rom_select,
390 auto_timer,
391 auto_index,
392 auto_roms,
393 on_rom_change: None,
394 current_rom_label,
395 auto_rom_labels,
396 on_first_update: None,
397 first_update_done: false,
398 input_queue: std::collections::VecDeque::new(),
399 reload_rx,
400 rom_path_arc,
401 last_rom_path: None,
402 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
403 usb_controller: None,
404 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
405 last_usb_event: None,
406 }
407 }
408 pub fn set_on_rom_change<F>(&mut self, f: F)
410 where
411 F: Fn(&str) + Send + Sync + 'static,
412 {
413 self.on_rom_change = Some(Box::new(f));
414 }
415
416 pub fn load_symbols(
417 &mut self,
418 path: &std::path::Path,
419 ) -> Result<(), String> {
420 println!("[UxnApp] Loading symbols from: {}", path.display());
421 let _ = self.dev.load_symbols_into_self(path.to_str().unwrap());
422 Ok(())
423 }
424
425 pub fn reload_rom(&mut self, path: &std::path::Path) -> Result<(), String> {
427 println!("[UxnApp] Reloading ROM from: {}", path.display());
428 let rom = std::fs::read(path)
429 .map_err(|e| format!("Failed to read ROM: {e}"))?;
430 let _ = self.vm.reset(&rom);
431 self.dev.reset(&rom);
432 self.vm.run(&mut self.dev, 0x100);
433 self.dev.redraw(&mut self.vm);
434
435 self.load_symbols(path)?;
436
437 #[cfg(windows)]
439 {
440 unsafe {
441 winapi::um::winuser::MessageBeep(0xFFFFFFFF);
442 }
443 }
444 Ok(())
445 }
446
447 pub fn set_resize_callback(&mut self, f: Box<dyn FnMut(u16, u16)>) {
448 self.resized = Some(f);
449 }
450
451 pub fn load_rom_with_path<P: AsRef<std::path::Path>>(
453 &mut self,
454 path: P,
455 ) -> anyhow::Result<()> {
456 let path = path.as_ref();
457 let data = std::fs::read(path)?;
458 self.last_rom_path = Some(path.to_path_buf());
459 let _ = self.vm.reset(&data);
460 self.dev.reset(&data);
461 self.vm.run(&mut self.dev, 0x100);
462 let sym_path = path.with_extension("sym");
464 if sym_path.exists() {
465 let _ = self.dev.load_symbols_into_self(sym_path.to_str().unwrap());
466 }
467 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
469 self.set_rom_label(name);
470 }
471 let out = self.dev.output(&self.vm);
472 out.check()?;
473 Ok(())
474 }
475
476 fn load_rom(&mut self, data: &[u8]) -> anyhow::Result<()> {
478 self.last_rom_path = None;
479 let _ = self.vm.reset(data);
480 self.dev.reset(data);
481 self.vm.run(&mut self.dev, 0x100);
482 let out = self.dev.output(&self.vm);
483 out.check()?;
484 Ok(())
485 }
486
487 pub fn queue_input<I: IntoIterator<Item = InjectEvent>>(
494 &mut self,
495 input: I,
496 ) {
497 self.input_queue.extend(input);
498 }
499}
500
501impl eframe::App for UxnApp<'_> {
502 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
503 if self.reload_rx.try_recv().is_ok() {
505 let path_opt = { self.rom_path_arc.lock().unwrap().clone() };
506 if let Some(ref path) = path_opt {
507 println!(
508 "[ROM WATCHER] Detected ROM file change, reloading: {}",
509 path.display()
510 );
511 if let Err(e) = self.reload_rom(path) {
512 eprintln!("[ROM WATCHER] Failed to reload ROM in app: {e}");
513 } else {
514 println!("[ROM WATCHER] ROM reloaded in app successfully.");
515 }
516 }
517 }
518 if !self.first_update_done {
520 if let Some(cb) = self.on_first_update.take() {
521 cb(self);
522 }
523 self.first_update_done = true;
524 }
525 let orca_dir = r"C:\w\music\Orca-c\examples\basics";
527 let files: Vec<std::path::PathBuf> = std::fs::read_dir(orca_dir)
528 .map(|read_dir| {
529 read_dir
530 .filter_map(|entry| {
531 entry.ok().and_then(|e| {
532 let path = e.path();
533 if path.extension().and_then(|ext| ext.to_str())
534 == Some("orca")
535 {
536 Some(path)
537 } else {
538 None
539 }
540 })
541 })
542 .collect::<Vec<_>>()
543 })
544 .unwrap_or_else(|_| Vec::new());
545 static mut LAST_CTRL_R: Option<std::time::Instant> = None;
546 let mut exit_requested = false;
547 for event in ctx.input(|i| i.events.clone()) {
548 if let egui::Event::Key { key, pressed, .. } = event {
549 if key == egui::Key::C
550 && ctx.input(|i| i.modifiers.ctrl)
551 && pressed
552 {
553 exit_requested = true;
554 }
555 if key == egui::Key::R
556 && ctx.input(|i| i.modifiers.ctrl)
557 && pressed
558 {
559 let now = std::time::Instant::now();
560 let last = unsafe { LAST_CTRL_R };
561 let allow = match last {
562 Some(t) => now.duration_since(t).as_millis() > 500,
563 None => true,
564 };
565 if allow {
566 unsafe {
567 LAST_CTRL_R = Some(now);
568 }
569 if !files.is_empty() {
570 #[cfg(target_arch = "wasm32")]
571 {
572 static mut LAST_IDX: usize = 0;
574 let idx = unsafe {
575 let i = LAST_IDX;
576 LAST_IDX = (LAST_IDX + 1) % files.len();
577 i
578 };
579 let random_file = &files[idx];
580 let queue = build_orca_inject_queue(
581 random_file.to_str().unwrap(),
582 );
583 self.queue_input(queue);
584 }
585 #[cfg(not(target_arch = "wasm32"))]
586 {
587 if let Ok(idx) = get_random_u128()
589 .map(|v| (v as usize) % files.len())
590 {
591 let idx: usize = idx;
592 let random_file = &files[idx];
593 let queue = build_orca_inject_queue(
594 random_file.to_str().unwrap(),
595 );
596 self.queue_input(queue);
597 }
598 }
599 }
600 }
601 }
602 }
603 }
604
605 #[cfg(not(target_arch = "wasm32"))]
607 pub fn get_random_u128() -> Result<u128, getrandom::Error> {
608 let mut buf = [0u8; 16];
609 getrandom::fill(&mut buf)?;
610 Ok(u128::from_ne_bytes(buf))
611 }
612
613 #[cfg(target_arch = "wasm32")]
614 #[allow(dead_code)]
615 pub fn get_random_u128() -> Result<u128, Result<u128, ()>> {
616 Ok(42)
618 }
619 if exit_requested {
620 std::process::exit(0);
621 }
622 if let Some(event) = self.input_queue.pop_front() {
624 match event {
625 InjectEvent::Char(c) => self.dev.char(&mut self.vm, c),
626 InjectEvent::KeyPress(k) => {
627 self.dev.pressed(&mut self.vm, k, false)
628 }
629 InjectEvent::KeyRelease(k) => {
630 self.dev.released(&mut self.vm, k)
631 }
632 InjectEvent::Sleep(ms) => {
633 std::thread::sleep(std::time::Duration::from_millis(ms));
634 }
635 InjectEvent::Chord(keys) => {
636 for k in &keys {
637 self.dev.pressed(&mut self.vm, *k, false);
638 }
639 }
640 }
641 }
642 if self.auto_rom_select && !self.auto_roms.is_empty() {
644 let dt = ctx.input(|i| i.stable_dt) as f64;
645 log::debug!(
646 "[AUTO ROM CYCLING] auto_timer: {:.3}, dt: {:.3}, auto_index: {}, roms: {}",
647 self.auto_timer,
648 dt,
649 self.auto_index,
650 self.auto_roms.len()
651 );
652 self.auto_timer += dt;
653 if self.auto_timer == 0.0 && self.auto_index == 0 {
654 log::debug!("[AUTO ROM CYCLING] First ROM already loaded.");
655 } else if self.auto_timer > 10.0 {
657 log::debug!(
658 "[AUTO ROM CYCLING] Switching ROM: {} -> {}",
659 self.auto_index,
660 (self.auto_index + 1) % self.auto_roms.len()
661 );
662 self.auto_timer = 0.0;
663 self.auto_index = (self.auto_index + 1) % self.auto_roms.len();
664 let rom = self.auto_roms[self.auto_index].clone();
665 let label = if self.auto_index < self.auto_rom_labels.len() {
667 self.auto_rom_labels[self.auto_index].clone()
668 } else {
669 format!("ROM {}", self.auto_index + 1)
670 };
671 self.set_rom_label(label);
672 let _ = self.load_rom(&rom.rom);
673 if rom.sym.is_some() {
674 if let Some(sym) = &rom.sym {
676 let _ = self.dev.load_symbols_from_vec(sym);
677 }
678 }
679 }
680 }
681 while let Ok(e) = self.event_rx.try_recv() {
682 match e {
683 Event::LoadRom(data) => {
684 if let Err(e) = self.load_rom(&data) {
685 error!("could not load rom: {e:?}");
686 }
687 }
688 Event::SetMuted(m) => {
689 self.dev.audio_set_muted(m);
690 }
691 Event::Console(b) => {
692 self.dev.console(&mut self.vm, b);
693 }
694 }
695 }
696 #[cfg(target_arch = "wasm32")]
720 let events = Vec::new();
721 #[cfg(not(target_arch = "wasm32"))]
722 let mut events = Vec::new();
723 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
724 {
725 let mut last_pedal = None;
727 if let Some(controller_usb) =
728 self.dev
729 .controller
730 .as_any()
731 .downcast_mut::<varvara::controller_usb::ControllerUsb>()
732 {
733 events = varvara::controller_usb::ControllerPollEvents::poll_usb_events(controller_usb, &mut self.vm);
734 last_pedal = controller_usb.last_pedal;
735 }
736 static mut PREV_PEDAL: u8 = 0;
738 if let Some(pedal) = last_pedal {
739 self.last_usb_event = Some((pedal, vec![]));
741 let _prev;
742 unsafe {
743 _prev = PREV_PEDAL;
744 PREV_PEDAL = pedal;
745 }
746 }
749 }
750 let pedal_data: Vec<u8> = events
751 .iter()
752 .flat_map(|e: &varvara::Event| e.data.iter().map(|b| b.value))
753 .collect();
754 for value in pedal_data {
755 self.dev
756 .pressed(&mut self.vm, varvara::Key::Char(value), true);
757 self.dev.char(&mut self.vm, value);
758 }
759
760 ctx.request_repaint();
761 ctx.input(|i| {
762 while i.time >= self.next_frame {
763 self.next_frame += 0.0166667;
764 self.dev.redraw(&mut self.vm);
765 }
766 if i.raw.dropped_files.len() == 1 {
767 let target = &i.raw.dropped_files[0];
768 let r = if let Some(path) = &target.path {
769 info!("loading ROM from {path:?}");
770 self.load_rom_with_path(path)
771 } else if let Some(data) = &target.bytes {
772 self.load_rom(data)
773 } else {
774 Ok(())
775 };
776 if let Err(e) = r {
777 error!("could not load ROM: {e:?}");
778 }
779 }
780 let shift_held = i.modifiers.shift;
781 for e in i.events.iter() {
782 match e {
783 egui::Event::Text(s) => {
784 for c in s.bytes() {
785 self.dev.char(&mut self.vm, c);
786 }
787 }
788 egui::Event::Key {
789 key,
790 pressed,
791 repeat,
792 ..
793 } => {
794 if let Some(k) = decode_key(*key, shift_held) {
795 if *pressed {
796 self.dev.pressed(&mut self.vm, k, *repeat);
797 } else {
798 self.dev.released(&mut self.vm, k);
799 }
800 }
801 }
802 _ => (),
803 }
804 }
805 for (b, k) in [
806 (i.modifiers.ctrl, Key::Ctrl),
807 (i.modifiers.alt, Key::Alt),
808 (i.modifiers.shift, Key::Shift),
809 ] {
810 if b {
811 self.dev.pressed(&mut self.vm, k, false)
812 } else {
813 self.dev.released(&mut self.vm, k)
814 }
815 }
816 let ptr = &i.pointer;
817 if let Some(p) = ptr.latest_pos() {
818 self.cursor_pos = Some((p.x / self.scale, p.y / self.scale));
819 }
820 let buttons = [
821 egui::PointerButton::Primary,
822 egui::PointerButton::Middle,
823 egui::PointerButton::Secondary,
824 ]
825 .into_iter()
826 .enumerate()
827 .map(|(i, b)| (ptr.button_down(b) as u8) << i)
828 .fold(0, |a, b| a | b);
829 let m = MouseState {
830 pos: self.cursor_pos.unwrap_or((0.0, 0.0)),
831 scroll: std::mem::take(&mut self.scroll),
832 buttons,
833 };
834 self.dev.mouse(&mut self.vm, m);
835 let m = TrackerState {
836 pos: self
837 .cursor_pos
838 .map(|(x, y)| (x + 16.0, y + 16.0))
839 .unwrap_or((16.0, 16.0)),
840 scroll: std::mem::take(&mut self.scroll),
841 buttons,
842 };
843 self.dev.tracker(&mut self.vm, m);
844 i.time
845 });
846 self.dev.audio(&mut self.vm);
847 let out = self.dev.output(&self.vm);
848 if out.hide_mouse {
849 ctx.set_cursor_icon(egui::CursorIcon::None);
850 }
851 if self.size != out.size {
852 let current_window_size = ctx.input(|i| {
854 i.viewport()
855 .inner_rect
856 .map_or(egui::Vec2::ZERO, |rect| rect.size())
857 });
858 let new_size =
859 egui::Vec2::new(out.size.0 as f32, out.size.1 as f32)
860 * self.scale;
861 if new_size.x > current_window_size.x
867 || new_size.y > current_window_size.y
868 {
869 info!("resizing window to {:?}", out.size);
870 self.size = out.size;
871 let mut size = new_size;
872 if self.window_mode == "proportional" {
874 let aspect = self.aspect_ratio;
875 let w = size.x;
876 let h = size.y;
877 let (new_w, new_h) = if w / h > aspect {
878 (h * aspect, h)
879 } else {
880 (w, w / aspect)
881 };
882 size = egui::Vec2::new(new_w, new_h);
883 }
884 ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
885 if let Some(f) = self.resized.as_mut() {
886 f(out.size.0, out.size.1);
887 }
888 }
889 }
890 let w = out.size.0 as usize;
891 let h = out.size.1 as usize;
892 let mut image =
893 egui::ColorImage::new([w, h], vec![egui::Color32::BLACK; w * h]);
894 for (i, o) in out.frame.chunks(4).zip(image.pixels.iter_mut()) {
895 *o = egui::Color32::from_rgba_unmultiplied(i[2], i[1], i[0], i[3]);
896 }
897 self.texture.set(image, egui::TextureOptions::NEAREST);
898 egui::CentralPanel::default()
899 .frame(egui::Frame::default().fill(egui::Color32::BLACK))
900 .show(ctx, |ui| {
901 let available = ui.available_size();
902 let frame_size = egui::Vec2::new(
903 out.size.0 as f32 * self.scale,
904 out.size.1 as f32 * self.scale,
905 );
906 let offset = egui::Vec2::new(
907 (available.x - frame_size.x) * 0.5,
908 (available.y - frame_size.y) * 0.5,
909 );
910 let top_left = ui.min_rect().min + offset;
911 let mut mesh = egui::Mesh::with_texture(self.texture.id());
912 mesh.add_rect_with_uv(
913 egui::Rect {
914 min: top_left,
915 max: top_left + frame_size,
916 },
917 egui::Rect {
918 min: egui::Pos2::new(0.0, 0.0),
919 max: egui::Pos2::new(1.0, 1.0),
920 },
921 egui::Color32::WHITE,
922 );
923 ui.painter().add(egui::Shape::mesh(mesh));
924 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
926 egui::CollapsingHeader::new("USB Pedal Debug").default_open(true).show(ui, |ui| {
927 if let Some((pedal_state, ref data)) = self.last_usb_event {
928 ui.label(format!("[USB] Last pedal event: state=0x{pedal_state:02X}, data={data:?}"));
929 } else {
930 ui.label("[USB] No pedal events received yet.");
931 }
932 });
933 if self.auto_rom_select && !self.auto_roms.is_empty() {
935 ui.label(format!(
936 "[AUTO ROM CYCLING] Switching ROM every 10s. ROMs loaded: {}",
937 self.auto_roms.len()
938 ));
939 }
940 });
941 out.check().ok();
942 }
943}
944
945pub fn decode_key(k: egui::Key, shift: bool) -> Option<Key> {
946 match (k, shift) {
947 (egui::Key::ArrowUp, _) => Some(Key::Up),
948 (egui::Key::ArrowDown, _) => Some(Key::Down),
949 (egui::Key::ArrowLeft, _) => Some(Key::Left),
950 (egui::Key::ArrowRight, _) => Some(Key::Right),
951 (egui::Key::Home, _) => Some(Key::Home),
952 (egui::Key::Num0, false) => Some(Key::Char(b'0')),
953 (egui::Key::Num0, true) => Some(Key::Char(b')')),
954 (egui::Key::Num1, false) => Some(Key::Char(b'1')),
955 (egui::Key::Num1, true) => Some(Key::Char(b'!')),
956 (egui::Key::Num2, false) => Some(Key::Char(b'2')),
957 (egui::Key::Num2, true) => Some(Key::Char(b'@')),
958 (egui::Key::Num3, false) => Some(Key::Char(b'3')),
959 (egui::Key::Num3, true) => Some(Key::Char(b'#')),
960 (egui::Key::Num4, false) => Some(Key::Char(b'4')),
961 (egui::Key::Num4, true) => Some(Key::Char(b'$')),
962 (egui::Key::Num5, false) => Some(Key::Char(b'5')),
963 (egui::Key::Num5, true) => Some(Key::Char(b'%')),
964 (egui::Key::Num6, false) => Some(Key::Char(b'6')),
965 (egui::Key::Num6, true) => Some(Key::Char(b'^')),
966 (egui::Key::Num7, false) => Some(Key::Char(b'7')),
967 (egui::Key::Num7, true) => Some(Key::Char(b'&')),
968 (egui::Key::Num8, false) => Some(Key::Char(b'8')),
969 (egui::Key::Num8, true) => Some(Key::Char(b'*')),
970 (egui::Key::Num9, false) => Some(Key::Char(b'9')),
971 (egui::Key::Num9, true) => Some(Key::Char(b'(')),
972 (egui::Key::A, false) => Some(Key::Char(b'a')),
973 (egui::Key::A, true) => Some(Key::Char(b'A')),
974 (egui::Key::B, false) => Some(Key::Char(b'b')),
975 (egui::Key::B, true) => Some(Key::Char(b'B')),
976 (egui::Key::C, false) => Some(Key::Char(b'c')),
977 (egui::Key::C, true) => Some(Key::Char(b'C')),
978 (egui::Key::D, false) => Some(Key::Char(b'd')),
979 (egui::Key::D, true) => Some(Key::Char(b'D')),
980 (egui::Key::E, false) => Some(Key::Char(b'e')),
981 (egui::Key::E, true) => Some(Key::Char(b'E')),
982 (egui::Key::F, false) => Some(Key::Char(b'f')),
983 (egui::Key::F, true) => Some(Key::Char(b'F')),
984 (egui::Key::G, false) => Some(Key::Char(b'g')),
985 (egui::Key::G, true) => Some(Key::Char(b'G')),
986 (egui::Key::H, false) => Some(Key::Char(b'h')),
987 (egui::Key::H, true) => Some(Key::Char(b'H')),
988 (egui::Key::I, false) => Some(Key::Char(b'i')),
989 (egui::Key::I, true) => Some(Key::Char(b'I')),
990 (egui::Key::J, false) => Some(Key::Char(b'j')),
991 (egui::Key::J, true) => Some(Key::Char(b'J')),
992 (egui::Key::K, false) => Some(Key::Char(b'k')),
993 (egui::Key::K, true) => Some(Key::Char(b'K')),
994 (egui::Key::L, false) => Some(Key::Char(b'l')),
995 (egui::Key::L, true) => Some(Key::Char(b'L')),
996 (egui::Key::M, false) => Some(Key::Char(b'm')),
997 (egui::Key::M, true) => Some(Key::Char(b'M')),
998 (egui::Key::N, false) => Some(Key::Char(b'n')),
999 (egui::Key::N, true) => Some(Key::Char(b'N')),
1000 (egui::Key::O, false) => Some(Key::Char(b'o')),
1001 (egui::Key::O, true) => Some(Key::Char(b'O')),
1002 (egui::Key::P, false) => Some(Key::Char(b'p')),
1003 (egui::Key::P, true) => Some(Key::Char(b'P')),
1004 (egui::Key::Q, false) => Some(Key::Char(b'q')),
1005 (egui::Key::Q, true) => Some(Key::Char(b'Q')),
1006 (egui::Key::R, false) => Some(Key::Char(b'r')),
1007 (egui::Key::R, true) => Some(Key::Char(b'R')),
1008 (egui::Key::S, false) => Some(Key::Char(b's')),
1009 (egui::Key::S, true) => Some(Key::Char(b'S')),
1010 (egui::Key::T, false) => Some(Key::Char(b't')),
1011 (egui::Key::T, true) => Some(Key::Char(b'T')),
1012 (egui::Key::U, false) => Some(Key::Char(b'u')),
1013 (egui::Key::U, true) => Some(Key::Char(b'U')),
1014 (egui::Key::V, false) => Some(Key::Char(b'v')),
1015 (egui::Key::V, true) => Some(Key::Char(b'V')),
1016 (egui::Key::W, false) => Some(Key::Char(b'w')),
1017 (egui::Key::W, true) => Some(Key::Char(b'W')),
1018 (egui::Key::X, false) => Some(Key::Char(b'x')),
1019 (egui::Key::X, true) => Some(Key::Char(b'X')),
1020 (egui::Key::Y, false) => Some(Key::Char(b'y')),
1021 (egui::Key::Y, true) => Some(Key::Char(b'Y')),
1022 (egui::Key::Z, false) => Some(Key::Char(b'z')),
1023 (egui::Key::Z, true) => Some(Key::Char(b'Z')),
1024 (egui::Key::Backtick, false) => Some(Key::Char(b'`')),
1025 (egui::Key::Backtick, true) => Some(Key::Char(b'~')),
1026 (egui::Key::Backslash, _) => Some(Key::Char(b'\\')),
1027 (egui::Key::Pipe, _) => Some(Key::Char(b'|')),
1028 (egui::Key::Comma, false) => Some(Key::Char(b',')),
1029 (egui::Key::Comma, true) => Some(Key::Char(b'<')),
1030 (egui::Key::Equals, _) => Some(Key::Char(b'=')),
1031 (egui::Key::Plus, _) => Some(Key::Char(b'+')),
1032 (egui::Key::OpenBracket, false) => Some(Key::Char(b'[')),
1033 (egui::Key::OpenBracket, true) => Some(Key::Char(b'{')),
1034 (egui::Key::Minus, false) => Some(Key::Char(b'-')),
1035 (egui::Key::Minus, true) => Some(Key::Char(b'_')),
1036 (egui::Key::Period, false) => Some(Key::Char(b'.')),
1037 (egui::Key::Period, true) => Some(Key::Char(b'>')),
1038 (egui::Key::CloseBracket, false) => Some(Key::Char(b']')),
1039 (egui::Key::CloseBracket, true) => Some(Key::Char(b'}')),
1040 (egui::Key::Semicolon, _) => Some(Key::Char(b';')),
1041 (egui::Key::Colon, _) => Some(Key::Char(b':')),
1042 (egui::Key::Slash, _) => Some(Key::Char(b'/')),
1043 (egui::Key::Questionmark, _) => Some(Key::Char(b'?')),
1044 (egui::Key::Space, _) => Some(Key::Char(b' ')),
1045 (egui::Key::Tab, _) => Some(Key::Char(b'\t')),
1046 (egui::Key::Enter, _) => Some(Key::Char(b'\r')),
1047 (egui::Key::Backspace, _) => Some(Key::Char(0x08)),
1048 _ => None,
1049 }
1050}
1051
1052pub fn audio_setup(
1062 data: [Arc<Mutex<varvara::StreamData>>; 4],
1063) -> Option<(cpal::Device, [cpal::Stream; 4])> {
1064 use cpal::traits::{DeviceTrait, HostTrait};
1065
1066 let host = cpal::default_host();
1067 let device = host
1068 .default_output_device()
1069 .expect("no output device available");
1070 let supported_configs_range = device
1071 .supported_output_configs()
1072 .expect("error while querying configs")
1073 .collect::<Vec<_>>();
1074
1075 let mut supported_config = None;
1076 let mut used_rate = 0;
1077 for &rate in &[48000, 44100] {
1078 supported_config = supported_configs_range
1079 .iter()
1080 .filter(|c| usize::from(c.channels()) == varvara::AUDIO_CHANNELS)
1081 .filter(|c| c.sample_format() == cpal::SampleFormat::F32)
1082 .find_map(|c| c.try_with_sample_rate(cpal::SampleRate(rate)));
1083 if supported_config.is_some() {
1084 used_rate = rate;
1085 break;
1086 }
1087 }
1088 let supported_config = match supported_config {
1089 Some(cfg) => cfg,
1090 None => {
1091 log::error!(
1092 "could not find supported audio config ({} channels, 48000 or 44100 Hz, f32)",
1093 varvara::AUDIO_CHANNELS
1094 );
1095 log::error!("available configs:");
1096 for c in &supported_configs_range {
1097 if c.min_sample_rate() == c.max_sample_rate() {
1098 log::error!(
1099 " channels: {}, sample_rate: {} Hz, {}",
1100 c.channels(),
1101 c.min_sample_rate().0,
1102 c.sample_format(),
1103 );
1104 } else {
1105 log::error!(
1106 " channels: {}, sample_rate: {} - {} Hz, {}",
1107 c.channels(),
1108 c.min_sample_rate().0,
1109 c.max_sample_rate().0,
1110 c.sample_format(),
1111 );
1112 }
1113 }
1114 return None;
1115 }
1116 };
1117 varvara::set_sample_rate(used_rate);
1119 let config = supported_config.config();
1120
1121 let streams = data.map(|d| {
1122 let stream = device
1123 .build_output_stream(
1124 &config,
1125 move |data: &mut [f32], _opt: &cpal::OutputCallbackInfo| {
1126 d.lock().unwrap().next(data);
1127 },
1128 move |err| {
1129 panic!("{err}");
1130 },
1131 None,
1132 )
1133 .expect("could not build stream");
1134 cpal::traits::StreamTrait::play(&stream).unwrap();
1135 stream
1136 });
1137 Some((device, streams))
1138}
1139
1140pub fn inject_key_events(
1142 vm: &mut ::uxn::Uxn,
1143 dev: &mut varvara::Varvara,
1144 events: &[egui::Event],
1145 modifiers: &egui::Modifiers,
1146 _repeat: bool,
1147) {
1148 use varvara::Key;
1149 let shift_held = modifiers.shift;
1150 for e in events {
1151 match e {
1152 egui::Event::Text(s) => {
1153 const RAW_CHARS: [u8; 16] = [
1154 b'"', b'\'', b'{', b'}', b'_', b')', b'(', b'*', b'&',
1155 b'^', b'%', b'$', b'#', b'@', b'!', b'~',
1156 ];
1157 for c in s.bytes() {
1158 if RAW_CHARS.contains(&c) {
1159 dev.char(vm, c);
1160 }
1161 }
1162 }
1163 egui::Event::Key {
1164 key,
1165 pressed,
1166 repeat,
1167 ..
1168 } => {
1169 if let Some(k) = decode_key(*key, shift_held) {
1170 if *pressed {
1171 dev.pressed(vm, k, *repeat);
1172 } else {
1173 dev.released(vm, k);
1174 }
1175 }
1176 }
1177 egui::Event::MouseWheel { delta, .. } => {
1178 dev.mouse(
1179 vm,
1180 varvara::MouseState {
1181 pos: (0.0, 0.0),
1182 scroll: (delta.x, -delta.y),
1183 buttons: 0,
1184 },
1185 );
1186 }
1187 _ => (),
1188 }
1189 }
1190 for (b, k) in [
1191 (modifiers.ctrl, Key::Ctrl),
1192 (modifiers.alt, Key::Alt),
1193 (modifiers.shift, Key::Shift),
1194 ] {
1195 if b {
1196 dev.pressed(vm, k, false)
1197 } else {
1198 dev.released(vm, k)
1199 }
1200 }
1201}