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 let mut events = Vec::new();
720 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
721 {
722 let mut last_pedal = None;
724 if let Some(controller_usb) = self
725 .dev
726 .controller
727 .as_any()
728 .downcast_mut::<varvara::controller_usb::ControllerUsb>()
729 {
730 events = varvara::controller_usb::ControllerPollEvents::poll_usb_events(controller_usb, &mut self.vm);
731 last_pedal = controller_usb.last_pedal;
732 }
733 static mut PREV_PEDAL: u8 = 0;
735 if let Some(pedal) = last_pedal {
736 self.last_usb_event = Some((pedal, vec![]));
738 let _prev;
739 unsafe {
740 _prev = PREV_PEDAL;
741 PREV_PEDAL = pedal;
742 }
743 }
746 }
747 let pedal_data: Vec<u8> = events.iter().flat_map(|e| e.data.iter().map(|b| b.value)).collect();
748 for value in pedal_data {
749 self.dev.pressed(&mut self.vm, varvara::Key::Char(value), true);
750 self.dev.char(&mut self.vm, value);
751 }
752
753 ctx.request_repaint();
754 ctx.input(|i| {
755 while i.time >= self.next_frame {
756 self.next_frame += 0.0166667;
757 self.dev.redraw(&mut self.vm);
758 }
759 if i.raw.dropped_files.len() == 1 {
760 let target = &i.raw.dropped_files[0];
761 let r = if let Some(path) = &target.path {
762 info!("loading ROM from {path:?}");
763 self.load_rom_with_path(path)
764 } else if let Some(data) = &target.bytes {
765 self.load_rom(data)
766 } else {
767 Ok(())
768 };
769 if let Err(e) = r {
770 error!("could not load ROM: {e:?}");
771 }
772 }
773 let shift_held = i.modifiers.shift;
774 for e in i.events.iter() {
775 match e {
776 egui::Event::Text(s) => {
777 for c in s.bytes() {
778 self.dev.char(&mut self.vm, c);
779 }
780 }
781 egui::Event::Key {
782 key,
783 pressed,
784 repeat,
785 ..
786 } => {
787 if let Some(k) = decode_key(*key, shift_held) {
788 if *pressed {
789 self.dev.pressed(&mut self.vm, k, *repeat);
790 } else {
791 self.dev.released(&mut self.vm, k);
792 }
793 }
794 }
795 _ => (),
796 }
797 }
798 for (b, k) in [
799 (i.modifiers.ctrl, Key::Ctrl),
800 (i.modifiers.alt, Key::Alt),
801 (i.modifiers.shift, Key::Shift),
802 ] {
803 if b {
804 self.dev.pressed(&mut self.vm, k, false)
805 } else {
806 self.dev.released(&mut self.vm, k)
807 }
808 }
809 let ptr = &i.pointer;
810 if let Some(p) = ptr.latest_pos() {
811 self.cursor_pos = Some((p.x / self.scale, p.y / self.scale));
812 }
813 let buttons = [
814 egui::PointerButton::Primary,
815 egui::PointerButton::Middle,
816 egui::PointerButton::Secondary,
817 ]
818 .into_iter()
819 .enumerate()
820 .map(|(i, b)| (ptr.button_down(b) as u8) << i)
821 .fold(0, |a, b| a | b);
822 let m = MouseState {
823 pos: self.cursor_pos.unwrap_or((0.0, 0.0)),
824 scroll: std::mem::take(&mut self.scroll),
825 buttons,
826 };
827 self.dev.mouse(&mut self.vm, m);
828 let m = TrackerState {
829 pos: self
830 .cursor_pos
831 .map(|(x, y)| (x + 16.0, y + 16.0))
832 .unwrap_or((16.0, 16.0)),
833 scroll: std::mem::take(&mut self.scroll),
834 buttons,
835 };
836 self.dev.tracker(&mut self.vm, m);
837 i.time
838 });
839 self.dev.audio(&mut self.vm);
840 let out = self.dev.output(&self.vm);
841 if out.hide_mouse {
842 ctx.set_cursor_icon(egui::CursorIcon::None);
843 }
844 if self.size != out.size {
845 let current_window_size = ctx.input(|i| {
847 i.viewport()
848 .inner_rect
849 .map_or(egui::Vec2::ZERO, |rect| rect.size())
850 });
851 let new_size =
852 egui::Vec2::new(out.size.0 as f32, out.size.1 as f32)
853 * self.scale;
854 if new_size.x > current_window_size.x
860 || new_size.y > current_window_size.y
861 {
862 info!("resizing window to {:?}", out.size);
863 self.size = out.size;
864 let mut size = new_size;
865 if self.window_mode == "proportional" {
867 let aspect = self.aspect_ratio;
868 let w = size.x;
869 let h = size.y;
870 let (new_w, new_h) = if w / h > aspect {
871 (h * aspect, h)
872 } else {
873 (w, w / aspect)
874 };
875 size = egui::Vec2::new(new_w, new_h);
876 }
877 ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
878 if let Some(f) = self.resized.as_mut() {
879 f(out.size.0, out.size.1);
880 }
881 }
882 }
883 let w = out.size.0 as usize;
884 let h = out.size.1 as usize;
885 let mut image =
886 egui::ColorImage::new([w, h], vec![egui::Color32::BLACK; w * h]);
887 for (i, o) in out.frame.chunks(4).zip(image.pixels.iter_mut()) {
888 *o = egui::Color32::from_rgba_unmultiplied(i[2], i[1], i[0], i[3]);
889 }
890 self.texture.set(image, egui::TextureOptions::NEAREST);
891 egui::CentralPanel::default()
892 .frame(egui::Frame::default().fill(egui::Color32::BLACK))
893 .show(ctx, |ui| {
894 let available = ui.available_size();
895 let frame_size = egui::Vec2::new(
896 out.size.0 as f32 * self.scale,
897 out.size.1 as f32 * self.scale,
898 );
899 let offset = egui::Vec2::new(
900 (available.x - frame_size.x) * 0.5,
901 (available.y - frame_size.y) * 0.5,
902 );
903 let top_left = ui.min_rect().min + offset;
904 let mut mesh = egui::Mesh::with_texture(self.texture.id());
905 mesh.add_rect_with_uv(
906 egui::Rect {
907 min: top_left,
908 max: top_left + frame_size,
909 },
910 egui::Rect {
911 min: egui::Pos2::new(0.0, 0.0),
912 max: egui::Pos2::new(1.0, 1.0),
913 },
914 egui::Color32::WHITE,
915 );
916 ui.painter().add(egui::Shape::mesh(mesh));
917 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
919 egui::CollapsingHeader::new("USB Pedal Debug").default_open(true).show(ui, |ui| {
920 if let Some((pedal_state, ref data)) = self.last_usb_event {
921 ui.label(format!("[USB] Last pedal event: state=0x{pedal_state:02X}, data={data:?}"));
922 } else {
923 ui.label("[USB] No pedal events received yet.");
924 }
925 });
926 if self.auto_rom_select && !self.auto_roms.is_empty() {
928 ui.label(format!(
929 "[AUTO ROM CYCLING] Switching ROM every 10s. ROMs loaded: {}",
930 self.auto_roms.len()
931 ));
932 }
933 });
934 out.check().ok();
935 }
936}
937
938pub fn decode_key(k: egui::Key, shift: bool) -> Option<Key> {
939 match (k, shift) {
940 (egui::Key::ArrowUp, _) => Some(Key::Up),
941 (egui::Key::ArrowDown, _) => Some(Key::Down),
942 (egui::Key::ArrowLeft, _) => Some(Key::Left),
943 (egui::Key::ArrowRight, _) => Some(Key::Right),
944 (egui::Key::Home, _) => Some(Key::Home),
945 (egui::Key::Num0, false) => Some(Key::Char(b'0')),
946 (egui::Key::Num0, true) => Some(Key::Char(b')')),
947 (egui::Key::Num1, false) => Some(Key::Char(b'1')),
948 (egui::Key::Num1, true) => Some(Key::Char(b'!')),
949 (egui::Key::Num2, false) => Some(Key::Char(b'2')),
950 (egui::Key::Num2, true) => Some(Key::Char(b'@')),
951 (egui::Key::Num3, false) => Some(Key::Char(b'3')),
952 (egui::Key::Num3, true) => Some(Key::Char(b'#')),
953 (egui::Key::Num4, false) => Some(Key::Char(b'4')),
954 (egui::Key::Num4, true) => Some(Key::Char(b'$')),
955 (egui::Key::Num5, false) => Some(Key::Char(b'5')),
956 (egui::Key::Num5, true) => Some(Key::Char(b'%')),
957 (egui::Key::Num6, false) => Some(Key::Char(b'6')),
958 (egui::Key::Num6, true) => Some(Key::Char(b'^')),
959 (egui::Key::Num7, false) => Some(Key::Char(b'7')),
960 (egui::Key::Num7, true) => Some(Key::Char(b'&')),
961 (egui::Key::Num8, false) => Some(Key::Char(b'8')),
962 (egui::Key::Num8, true) => Some(Key::Char(b'*')),
963 (egui::Key::Num9, false) => Some(Key::Char(b'9')),
964 (egui::Key::Num9, true) => Some(Key::Char(b'(')),
965 (egui::Key::A, false) => Some(Key::Char(b'a')),
966 (egui::Key::A, true) => Some(Key::Char(b'A')),
967 (egui::Key::B, false) => Some(Key::Char(b'b')),
968 (egui::Key::B, true) => Some(Key::Char(b'B')),
969 (egui::Key::C, false) => Some(Key::Char(b'c')),
970 (egui::Key::C, true) => Some(Key::Char(b'C')),
971 (egui::Key::D, false) => Some(Key::Char(b'd')),
972 (egui::Key::D, true) => Some(Key::Char(b'D')),
973 (egui::Key::E, false) => Some(Key::Char(b'e')),
974 (egui::Key::E, true) => Some(Key::Char(b'E')),
975 (egui::Key::F, false) => Some(Key::Char(b'f')),
976 (egui::Key::F, true) => Some(Key::Char(b'F')),
977 (egui::Key::G, false) => Some(Key::Char(b'g')),
978 (egui::Key::G, true) => Some(Key::Char(b'G')),
979 (egui::Key::H, false) => Some(Key::Char(b'h')),
980 (egui::Key::H, true) => Some(Key::Char(b'H')),
981 (egui::Key::I, false) => Some(Key::Char(b'i')),
982 (egui::Key::I, true) => Some(Key::Char(b'I')),
983 (egui::Key::J, false) => Some(Key::Char(b'j')),
984 (egui::Key::J, true) => Some(Key::Char(b'J')),
985 (egui::Key::K, false) => Some(Key::Char(b'k')),
986 (egui::Key::K, true) => Some(Key::Char(b'K')),
987 (egui::Key::L, false) => Some(Key::Char(b'l')),
988 (egui::Key::L, true) => Some(Key::Char(b'L')),
989 (egui::Key::M, false) => Some(Key::Char(b'm')),
990 (egui::Key::M, true) => Some(Key::Char(b'M')),
991 (egui::Key::N, false) => Some(Key::Char(b'n')),
992 (egui::Key::N, true) => Some(Key::Char(b'N')),
993 (egui::Key::O, false) => Some(Key::Char(b'o')),
994 (egui::Key::O, true) => Some(Key::Char(b'O')),
995 (egui::Key::P, false) => Some(Key::Char(b'p')),
996 (egui::Key::P, true) => Some(Key::Char(b'P')),
997 (egui::Key::Q, false) => Some(Key::Char(b'q')),
998 (egui::Key::Q, true) => Some(Key::Char(b'Q')),
999 (egui::Key::R, false) => Some(Key::Char(b'r')),
1000 (egui::Key::R, true) => Some(Key::Char(b'R')),
1001 (egui::Key::S, false) => Some(Key::Char(b's')),
1002 (egui::Key::S, true) => Some(Key::Char(b'S')),
1003 (egui::Key::T, false) => Some(Key::Char(b't')),
1004 (egui::Key::T, true) => Some(Key::Char(b'T')),
1005 (egui::Key::U, false) => Some(Key::Char(b'u')),
1006 (egui::Key::U, true) => Some(Key::Char(b'U')),
1007 (egui::Key::V, false) => Some(Key::Char(b'v')),
1008 (egui::Key::V, true) => Some(Key::Char(b'V')),
1009 (egui::Key::W, false) => Some(Key::Char(b'w')),
1010 (egui::Key::W, true) => Some(Key::Char(b'W')),
1011 (egui::Key::X, false) => Some(Key::Char(b'x')),
1012 (egui::Key::X, true) => Some(Key::Char(b'X')),
1013 (egui::Key::Y, false) => Some(Key::Char(b'y')),
1014 (egui::Key::Y, true) => Some(Key::Char(b'Y')),
1015 (egui::Key::Z, false) => Some(Key::Char(b'z')),
1016 (egui::Key::Z, true) => Some(Key::Char(b'Z')),
1017 (egui::Key::Backtick, false) => Some(Key::Char(b'`')),
1018 (egui::Key::Backtick, true) => Some(Key::Char(b'~')),
1019 (egui::Key::Backslash, _) => Some(Key::Char(b'\\')),
1020 (egui::Key::Pipe, _) => Some(Key::Char(b'|')),
1021 (egui::Key::Comma, false) => Some(Key::Char(b',')),
1022 (egui::Key::Comma, true) => Some(Key::Char(b'<')),
1023 (egui::Key::Equals, _) => Some(Key::Char(b'=')),
1024 (egui::Key::Plus, _) => Some(Key::Char(b'+')),
1025 (egui::Key::OpenBracket, false) => Some(Key::Char(b'[')),
1026 (egui::Key::OpenBracket, true) => Some(Key::Char(b'{')),
1027 (egui::Key::Minus, false) => Some(Key::Char(b'-')),
1028 (egui::Key::Minus, true) => Some(Key::Char(b'_')),
1029 (egui::Key::Period, false) => Some(Key::Char(b'.')),
1030 (egui::Key::Period, true) => Some(Key::Char(b'>')),
1031 (egui::Key::CloseBracket, false) => Some(Key::Char(b']')),
1032 (egui::Key::CloseBracket, true) => Some(Key::Char(b'}')),
1033 (egui::Key::Semicolon, _) => Some(Key::Char(b';')),
1034 (egui::Key::Colon, _) => Some(Key::Char(b':')),
1035 (egui::Key::Slash, _) => Some(Key::Char(b'/')),
1036 (egui::Key::Questionmark, _) => Some(Key::Char(b'?')),
1037 (egui::Key::Space, _) => Some(Key::Char(b' ')),
1038 (egui::Key::Tab, _) => Some(Key::Char(b'\t')),
1039 (egui::Key::Enter, _) => Some(Key::Char(b'\r')),
1040 (egui::Key::Backspace, _) => Some(Key::Char(0x08)),
1041 _ => None,
1042 }
1043}
1044
1045pub fn audio_setup(
1055 data: [Arc<Mutex<varvara::StreamData>>; 4],
1056) -> Option<(cpal::Device, [cpal::Stream; 4])> {
1057 use cpal::traits::{DeviceTrait, HostTrait};
1058
1059 let host = cpal::default_host();
1060 let device = host
1061 .default_output_device()
1062 .expect("no output device available");
1063 let supported_configs_range = device
1064 .supported_output_configs()
1065 .expect("error while querying configs")
1066 .collect::<Vec<_>>();
1067
1068 let mut supported_config = None;
1069 let mut used_rate = 0;
1070 for &rate in &[48000, 44100] {
1071 supported_config = supported_configs_range
1072 .iter()
1073 .filter(|c| usize::from(c.channels()) == varvara::AUDIO_CHANNELS)
1074 .filter(|c| c.sample_format() == cpal::SampleFormat::F32)
1075 .find_map(|c| c.try_with_sample_rate(cpal::SampleRate(rate)));
1076 if supported_config.is_some() {
1077 used_rate = rate;
1078 break;
1079 }
1080 }
1081 let supported_config = match supported_config {
1082 Some(cfg) => cfg,
1083 None => {
1084 log::error!(
1085 "could not find supported audio config ({} channels, 48000 or 44100 Hz, f32)",
1086 varvara::AUDIO_CHANNELS
1087 );
1088 log::error!("available configs:");
1089 for c in &supported_configs_range {
1090 if c.min_sample_rate() == c.max_sample_rate() {
1091 log::error!(
1092 " channels: {}, sample_rate: {} Hz, {}",
1093 c.channels(),
1094 c.min_sample_rate().0,
1095 c.sample_format(),
1096 );
1097 } else {
1098 log::error!(
1099 " channels: {}, sample_rate: {} - {} Hz, {}",
1100 c.channels(),
1101 c.min_sample_rate().0,
1102 c.max_sample_rate().0,
1103 c.sample_format(),
1104 );
1105 }
1106 }
1107 return None;
1108 }
1109 };
1110 varvara::set_sample_rate(used_rate);
1112 let config = supported_config.config();
1113
1114 let streams = data.map(|d| {
1115 let stream = device
1116 .build_output_stream(
1117 &config,
1118 move |data: &mut [f32], _opt: &cpal::OutputCallbackInfo| {
1119 d.lock().unwrap().next(data);
1120 },
1121 move |err| {
1122 panic!("{err}");
1123 },
1124 None,
1125 )
1126 .expect("could not build stream");
1127 cpal::traits::StreamTrait::play(&stream).unwrap();
1128 stream
1129 });
1130 Some((device, streams))
1131}
1132
1133pub fn inject_key_events(
1135 vm: &mut ::uxn::Uxn,
1136 dev: &mut varvara::Varvara,
1137 events: &[egui::Event],
1138 modifiers: &egui::Modifiers,
1139 _repeat: bool,
1140) {
1141 use varvara::Key;
1142 let shift_held = modifiers.shift;
1143 for e in events {
1144 match e {
1145 egui::Event::Text(s) => {
1146 const RAW_CHARS: [u8; 16] = [
1147 b'"', b'\'', b'{', b'}', b'_', b')', b'(', b'*', b'&',
1148 b'^', b'%', b'$', b'#', b'@', b'!', b'~',
1149 ];
1150 for c in s.bytes() {
1151 if RAW_CHARS.contains(&c) {
1152 dev.char(vm, c);
1153 }
1154 }
1155 }
1156 egui::Event::Key {
1157 key,
1158 pressed,
1159 repeat,
1160 ..
1161 } => {
1162 if let Some(k) = decode_key(*key, shift_held) {
1163 if *pressed {
1164 dev.pressed(vm, k, *repeat);
1165 } else {
1166 dev.released(vm, k);
1167 }
1168 }
1169 }
1170 egui::Event::MouseWheel { delta, .. } => {
1171 dev.mouse(
1172 vm,
1173 varvara::MouseState {
1174 pos: (0.0, 0.0),
1175 scroll: (delta.x, -delta.y),
1176 buttons: 0,
1177 },
1178 );
1179 }
1180 _ => (),
1181 }
1182 }
1183 for (b, k) in [
1184 (modifiers.ctrl, Key::Ctrl),
1185 (modifiers.alt, Key::Alt),
1186 (modifiers.shift, Key::Shift),
1187 ] {
1188 if b {
1189 dev.pressed(vm, k, false)
1190 } else {
1191 dev.released(vm, k)
1192 }
1193 }
1194}