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