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().flatten() {
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 in 0..rows + 2 {
52 for c in 0..cols + 2 {
53 let ch = grid[r][c];
54 if ch != '.' {
55 let dr = r as isize - cur_row as isize;
57 let dc = c as isize - cur_col as isize;
58 for _ in 0..dr.abs() {
59 queue.push_back(if dr > 0 {
60 InjectEvent::KeyPress(DOWN)
61 } else {
62 InjectEvent::KeyPress(UP)
63 });
64 queue.push_back(if dr > 0 {
65 InjectEvent::KeyRelease(DOWN)
66 } else {
67 InjectEvent::KeyRelease(UP)
68 });
69 }
70 for _ in 0..dc.abs() {
71 queue.push_back(if dc > 0 {
72 InjectEvent::KeyPress(RIGHT)
73 } else {
74 InjectEvent::KeyPress(LEFT)
75 });
76 queue.push_back(if dc > 0 {
77 InjectEvent::KeyRelease(RIGHT)
78 } else {
79 InjectEvent::KeyRelease(LEFT)
80 });
81 }
82 cur_row = r;
83 cur_col = c;
84 queue.push_back(InjectEvent::Char(ch as u8));
86 if ch == '/' && r != 0 && r != rows + 1 {
88 let hex = format!("{:02X}{:02X}", c, r);
90 for b in hex.bytes() {
91 queue.push_back(InjectEvent::Char(b));
92 }
93 }
94 }
95 }
96 }
97 queue
98}
99
100#[cfg(not(target_arch = "wasm32"))]
101use getrandom;
102#[derive(Debug, Clone)]
103pub enum InjectEvent {
104 Char(u8),
105 KeyPress(Key),
106 KeyRelease(Key),
107 Sleep(u64), Chord(Vec<Key>), }
110#[cfg(feature = "uses_uxn")]
113pub mod uxn {
114
115 use cardinal_uxn::{Backend, Uxn};
116 use cardinal_varvara::Varvara;
117 use std::path::Path;
118 use std::sync::{Arc, Mutex};
119 pub struct UxnModule {
121 pub uxn: Arc<Mutex<Uxn<'static>>>,
122 pub varvara: Option<Varvara>,
123 }
124
125 impl UxnModule {
126 pub fn new(rom_path: Option<&Path>) -> Result<Self, String> {
128 static mut RAM: [u8; 65536] = [0; 65536];
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 uxn.reset(&rom);
140 }
141
142 Ok(UxnModule {
143 uxn: Arc::new(Mutex::new(uxn)),
144 varvara: Some(varvara),
145 })
146 }
147
148 pub fn reset(&self, rom: &[u8]) {
150 let mut uxn = self.uxn.lock().unwrap();
151 uxn.reset(rom);
152 }
153
154 pub fn load_rom(&self, rom_path: &Path) -> Result<(), String> {
156 let rom = std::fs::read(rom_path)
157 .map_err(|e| format!("Failed to read ROM: {e}"))?;
158 self.reset(&rom);
159 Ok(())
160 }
161 }
162}
163
164#[cfg(not(feature = "uses_uxn"))]
165pub mod uxn {
166 }
168
169use log::{error, info};
170use std::path::Path;
171use std::sync::{Arc, Mutex};
172use ::uxn::{Backend, Uxn};
174use varvara::Key;
175use varvara::MouseState;
176use varvara::Varvara;
177pub struct UxnModule {
179 pub uxn: Arc<Mutex<Uxn<'static>>>,
180 pub varvara: Option<Varvara>,
181}
182
183impl UxnModule {
184 pub fn new(rom_path: Option<&Path>) -> Result<Self, String> {
186 static mut RAM: [u8; 65536] = [0; 65536];
188 let ram: &'static mut [u8; 65536] = unsafe { &mut RAM };
189 let mut uxn = Uxn::new(ram, Backend::Interpreter);
190 let varvara = Varvara::default();
191 if let Some(path) = rom_path {
192 let rom = std::fs::read(path)
193 .map_err(|e| format!("Failed to read ROM: {e}"))?;
194 uxn.reset(&rom);
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 uxn.reset(rom);
210 }
211
212 pub fn load_rom(&self, rom_path: &Path) -> Result<(), String> {
214 let rom = std::fs::read(rom_path)
215 .map_err(|e| format!("Failed to read ROM: {e}"))?;
216 self.reset(&rom);
217 Ok(())
218 }
219
220 }
222
223#[cfg(feature = "egui")]
225pub mod egui_ui {
226 use super::*;
227 use egui::{CollapsingHeader, Ui};
228
229 pub fn show_uxn_panel(ui: &mut Ui, _uxn_mod: &UxnModule) {
230 CollapsingHeader::new("Uxn VM State").show(ui, |ui| {
231 ui.label("Uxn state display not implemented (no UxnState in _uxn)");
232 });
233 }
234}
235use eframe::egui;
236use std::sync::mpsc;
237#[derive(Debug)]
240pub enum Event {
241 LoadRom(Vec<u8>),
242 SetMuted(bool),
243 Console(u8),
244}
245
246pub struct UxnApp<'a> {
247 pub vm: Uxn<'a>,
248 pub dev: Varvara,
249 scale: f32,
250 size: (u16, u16),
251 next_frame: f64,
252 scroll: (f32, f32),
253 cursor_pos: Option<(f32, f32)>,
254 texture: egui::TextureHandle,
255 event_rx: mpsc::Receiver<Event>,
256 resized: Option<Box<dyn FnMut(u16, u16)>>,
257 window_mode: String,
258 aspect_ratio: f32,
259 auto_rom_select: bool,
261 auto_timer: f64,
262 auto_index: usize,
263 auto_roms: Vec<Vec<u8>>,
264 auto_rom_labels: Vec<String>,
266 on_rom_change: Option<Box<dyn Fn(&str) + Send + Sync>>,
268 on_first_update: Option<Box<dyn FnOnce(&mut UxnApp<'a>) + Send + 'a>>,
270 first_update_done: bool,
271 current_rom_label: Option<String>,
273 input_queue: std::collections::VecDeque<InjectEvent>,
275 pub reload_rx: std::sync::mpsc::Receiver<()>,
277 pub rom_path_arc:
278 std::sync::Arc<std::sync::Mutex<Option<std::path::PathBuf>>>,
279}
280
281impl<'a> UxnApp<'a> {
282 pub fn set_on_first_update(
284 &mut self,
285 f: Box<dyn FnOnce(&mut UxnApp<'a>) + Send + 'a>,
286 ) {
287 self.on_first_update = Some(f);
288 self.first_update_done = false;
289 }
290 pub fn set_rom_label<S: Into<String>>(&mut self, label: S) {
292 self.current_rom_label = Some(label.into());
293 }
294 pub fn new_with_mode(
295 mut vm: Uxn<'a>,
296 mut dev: Varvara,
297 mut size: (u16, u16),
298 scale: f32,
299 event_rx: mpsc::Receiver<Event>,
300 ctx: &egui::Context,
301 window_mode: String,
302 auto_roms: Vec<Vec<u8>>,
303 auto_rom_labels: Vec<String>,
304 auto_rom_select: bool,
305 reload_rx: std::sync::mpsc::Receiver<()>,
306 rom_path_arc: std::sync::Arc<
307 std::sync::Mutex<Option<std::path::PathBuf>>,
308 >,
309 ) -> Self {
310 vm.run(&mut dev, 0x100);
312
313 dev.redraw(&mut vm);
314
315 let w = usize::from(2048 as usize); let h = usize::from(2048 as usize);
317 size.0 = w as u16;
318 size.1 = h as u16;
319 let image =
320 egui::ColorImage::new([w, h], vec![egui::Color32::BLACK; w * h]);
321 let texture =
322 ctx.load_texture("frame", image, egui::TextureOptions::NEAREST);
323 let aspect_ratio = w as f32 / h as f32;
324 let mut auto_index = 0;
325 let mut auto_timer = 0.0;
326 let mut current_rom_label = None;
327 if auto_rom_select && !auto_roms.is_empty() {
328 println!(
329 "[AUTO ROM CYCLING] Enabled. Cycling through {} ROMs every 10s.",
330 auto_roms.len()
331 );
332 let rom = &auto_roms[0];
334 let _ = vm.reset(rom);
335 dev.reset(rom);
336 vm.run(&mut dev, 0x100);
337 dev.redraw(&mut vm);
338 auto_index = 0;
339 auto_timer = 0.0;
340 if !auto_rom_labels.is_empty() {
342 current_rom_label = Some(auto_rom_labels[0].clone());
343 } else {
344 current_rom_label = Some("ROM 1".to_string());
345 }
346 }
347 UxnApp {
348 vm,
349 dev,
350 scale,
351 size,
352 next_frame: 0.0,
353 event_rx,
354 resized: None,
355 scroll: (0.0, 0.0),
356 cursor_pos: None,
357 texture,
358 window_mode,
359 aspect_ratio,
360 auto_rom_select,
361 auto_timer,
362 auto_index,
363 auto_roms,
364 on_rom_change: None,
365 current_rom_label,
366 auto_rom_labels,
367 on_first_update: None,
368 first_update_done: false,
369 input_queue: std::collections::VecDeque::new(),
370 reload_rx,
371 rom_path_arc,
372 }
373 }
374 pub fn set_on_rom_change<F>(&mut self, f: F)
376 where
377 F: Fn(&str) + Send + Sync + 'static,
378 {
379 self.on_rom_change = Some(Box::new(f));
380 }
381
382 pub fn reload_rom(&mut self, path: &std::path::Path) -> Result<(), String> {
384 println!("[UxnApp] Reloading ROM from: {}", path.display());
385 let rom = std::fs::read(path)
386 .map_err(|e| format!("Failed to read ROM: {e}"))?;
387 self.vm.reset(&rom);
388 self.dev.reset(&rom);
389 self.vm.run(&mut self.dev, 0x100);
390 self.dev.redraw(&mut self.vm);
391 #[cfg(windows)]
393 {
394 unsafe {
395 winapi::um::winuser::MessageBeep(0xFFFFFFFF);
396 }
397 }
398 Ok(())
399 }
400
401 pub fn set_resize_callback(&mut self, f: Box<dyn FnMut(u16, u16)>) {
402 self.resized = Some(f);
403 }
404
405 fn load_rom(&mut self, data: &[u8]) -> anyhow::Result<()> {
406 let data = self.vm.reset(data);
407 self.dev.reset(data);
408 self.vm.run(&mut self.dev, 0x100);
409 let out = self.dev.output(&self.vm);
410 out.check()?;
411 let label =
413 self.current_rom_label.as_deref().unwrap_or("[unknown ROM]");
414 if let Some(cb) = &self.on_rom_change {
415 cb(label);
416 }
417 Ok(())
418 }
419
420 pub fn queue_input<I: IntoIterator<Item = InjectEvent>>(
422 &mut self,
423 input: I,
424 ) {
425 self.input_queue.extend(input);
426 }
427}
428
429impl eframe::App for UxnApp<'_> {
430 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
431 if self.reload_rx.try_recv().is_ok() {
433 let path_opt = { self.rom_path_arc.lock().unwrap().clone() };
434 if let Some(ref path) = path_opt {
435 println!(
436 "[ROM WATCHER] Detected ROM file change, reloading: {}",
437 path.display()
438 );
439 if let Err(e) = self.reload_rom(path) {
440 eprintln!(
441 "[ROM WATCHER] Failed to reload ROM in app: {}",
442 e
443 );
444 } else {
445 println!("[ROM WATCHER] ROM reloaded in app successfully.");
446 }
447 }
448 }
449 if !self.first_update_done {
451 if let Some(cb) = self.on_first_update.take() {
452 cb(self);
453 }
454 self.first_update_done = true;
455 }
456 let orca_dir = r"C:\w\music\Orca-c\examples\basics";
458 let files: Vec<std::path::PathBuf> = std::fs::read_dir(orca_dir)
459 .map(|read_dir| {
460 read_dir
461 .filter_map(|entry| {
462 entry.ok().and_then(|e| {
463 let path = e.path();
464 if path.extension().and_then(|ext| ext.to_str())
465 == Some("orca")
466 {
467 Some(path)
468 } else {
469 None
470 }
471 })
472 })
473 .collect::<Vec<_>>()
474 })
475 .unwrap_or_else(|_| Vec::new());
476 static mut LAST_CTRL_R: Option<std::time::Instant> = None;
477 let mut exit_requested = false;
478 for event in ctx.input(|i| i.events.clone()) {
479 match event {
480 egui::Event::Key { key, pressed, .. } => {
481 if key == egui::Key::C
482 && ctx.input(|i| i.modifiers.ctrl)
483 && pressed
484 {
485 exit_requested = true;
486 }
487 if key == egui::Key::R
488 && ctx.input(|i| i.modifiers.ctrl)
489 && pressed
490 {
491 let now = std::time::Instant::now();
492 let last = unsafe { LAST_CTRL_R };
493 let allow = match last {
494 Some(t) => now.duration_since(t).as_millis() > 500,
495 None => true,
496 };
497 if allow {
498 unsafe {
499 LAST_CTRL_R = Some(now);
500 }
501 if !files.is_empty() {
502 #[cfg(target_arch = "wasm32")]
503 {
504 static mut LAST_IDX: usize = 0;
506 let idx = unsafe {
507 let i = LAST_IDX;
508 LAST_IDX = (LAST_IDX + 1) % files.len();
509 i
510 };
511 let random_file = &files[idx];
512 let queue = build_orca_inject_queue(
513 random_file.to_str().unwrap(),
514 );
515 self.queue_input(queue);
516 }
517 #[cfg(not(target_arch = "wasm32"))]
518 {
519 if let Ok(idx) = get_random_u128()
521 .map(|v| (v as usize) % files.len())
522 {
523 let idx: usize = idx;
524 let random_file = &files[idx];
525 let queue = build_orca_inject_queue(
526 random_file.to_str().unwrap(),
527 );
528 self.queue_input(queue);
529 }
530 }
531 }
532 }
533 }
534 }
535 _ => {}
536 }
537 }
538
539 #[cfg(not(target_arch = "wasm32"))]
541 pub fn get_random_u128() -> Result<u128, getrandom::Error> {
542 let mut buf = [0u8; 16];
543 getrandom::fill(&mut buf)?;
544 Ok(u128::from_ne_bytes(buf))
545 }
546
547 #[cfg(target_arch = "wasm32")]
548 pub fn get_random_u128() -> Result<u128, Result<u128, ()>> {
549 Ok(42)
551 }
552 if exit_requested {
553 std::process::exit(0);
554 }
555 if let Some(event) = self.input_queue.pop_front() {
557 match event {
558 InjectEvent::Char(c) => self.dev.char(&mut self.vm, c),
559 InjectEvent::KeyPress(k) => {
560 self.dev.pressed(&mut self.vm, k, false)
561 }
562 InjectEvent::KeyRelease(k) => {
563 self.dev.released(&mut self.vm, k)
564 }
565 InjectEvent::Sleep(ms) => {
566 std::thread::sleep(std::time::Duration::from_millis(ms));
567 }
568 InjectEvent::Chord(keys) => {
569 for k in &keys {
570 self.dev.pressed(&mut self.vm, *k, false);
571 }
572 }
573 }
574 }
575 if self.auto_rom_select && !self.auto_roms.is_empty() {
577 let dt = ctx.input(|i| i.stable_dt) as f64;
578 log::debug!(
579 "[AUTO ROM CYCLING] auto_timer: {:.3}, dt: {:.3}, auto_index: {}, roms: {}",
580 self.auto_timer,
581 dt,
582 self.auto_index,
583 self.auto_roms.len()
584 );
585 self.auto_timer += dt;
586 if self.auto_timer == 0.0 && self.auto_index == 0 {
587 log::debug!("[AUTO ROM CYCLING] First ROM already loaded.");
588 } else if self.auto_timer > 10.0 {
590 log::debug!(
591 "[AUTO ROM CYCLING] Switching ROM: {} -> {}",
592 self.auto_index,
593 (self.auto_index + 1) % self.auto_roms.len()
594 );
595 self.auto_timer = 0.0;
596 self.auto_index = (self.auto_index + 1) % self.auto_roms.len();
597 let rom = self.auto_roms[self.auto_index].clone();
598 let label = if self.auto_index < self.auto_rom_labels.len() {
600 self.auto_rom_labels[self.auto_index].clone()
601 } else {
602 format!("ROM {}", self.auto_index + 1)
603 };
604 self.set_rom_label(label);
605 let _ = self.load_rom(&rom);
606 }
607 }
608 while let Ok(e) = self.event_rx.try_recv() {
609 match e {
610 Event::LoadRom(data) => {
611 if let Err(e) = self.load_rom(&data) {
612 error!("could not load rom: {e:?}");
613 }
614 }
615 Event::SetMuted(m) => {
616 self.dev.audio_set_muted(m);
617 }
618 Event::Console(b) => {
619 self.dev.console(&mut self.vm, b);
620 }
621 }
622 }
623
624 ctx.request_repaint();
625 ctx.input(|i| {
626 while i.time >= self.next_frame {
627 self.next_frame += 0.0166667;
628 self.dev.redraw(&mut self.vm);
629 }
630 if i.raw.dropped_files.len() == 1 {
631 let target = &i.raw.dropped_files[0];
632 let r = if let Some(path) = &target.path {
633 let data =
634 std::fs::read(path).expect("failed to read file");
635 info!("loading {} bytes from {path:?}", data.len());
636 self.load_rom(&data)
637 } else if let Some(data) = &target.bytes {
638 self.load_rom(data)
639 } else {
640 Ok(())
641 };
642 if let Err(e) = r {
643 error!("could not load ROM: {e:?}");
644 }
645 }
646 let shift_held = i.modifiers.shift;
647 for e in i.events.iter() {
648 match e {
649 egui::Event::Text(s) => {
650 const RAW_CHARS: [u8; 16] = [
651 b'"', b'\'', b'{', b'}', b'_', b')', b'(', b'*',
652 b'&', b'^', b'%', b'$', b'#', b'@', b'!', b'~',
653 ];
654 for c in s.bytes() {
655 if RAW_CHARS.contains(&c) {
656 self.dev.char(&mut self.vm, c);
657 }
658 }
659 }
660 egui::Event::Key {
661 key,
662 pressed,
663 repeat,
664 ..
665 } => {
666 if let Some(k) = decode_key(*key, shift_held) {
667 if *pressed {
668 self.dev.pressed(&mut self.vm, k, *repeat);
669 } else {
670 self.dev.released(&mut self.vm, k);
671 }
672 }
673 }
674 _ => (),
679 }
680 }
681 for (b, k) in [
682 (i.modifiers.ctrl, Key::Ctrl),
683 (i.modifiers.alt, Key::Alt),
684 (i.modifiers.shift, Key::Shift),
685 ] {
686 if b {
687 self.dev.pressed(&mut self.vm, k, false)
688 } else {
689 self.dev.released(&mut self.vm, k)
690 }
691 }
692 let ptr = &i.pointer;
693 if let Some(p) = ptr.latest_pos() {
694 self.cursor_pos = Some((p.x / self.scale, p.y / self.scale));
695 }
696 let buttons = [
697 egui::PointerButton::Primary,
698 egui::PointerButton::Middle,
699 egui::PointerButton::Secondary,
700 ]
701 .into_iter()
702 .enumerate()
703 .map(|(i, b)| (ptr.button_down(b) as u8) << i)
704 .fold(0, |a, b| a | b);
705 let m = MouseState {
706 pos: self.cursor_pos.unwrap_or((0.0, 0.0)),
707 scroll: std::mem::take(&mut self.scroll),
708 buttons,
709 };
710 self.dev.mouse(&mut self.vm, m);
711 i.time
712 });
713 self.dev.audio(&mut self.vm);
714 let out = self.dev.output(&self.vm);
715 if out.hide_mouse {
716 ctx.set_cursor_icon(egui::CursorIcon::None);
717 }
718 if self.size != out.size {
719 let current_window_size = ctx.input(|i| {
721 i.viewport()
722 .inner_rect
723 .map_or(egui::Vec2::ZERO, |rect| rect.size())
724 });
725 let new_size =
726 egui::Vec2::new(out.size.0 as f32, out.size.1 as f32)
727 * self.scale;
728 if new_size.x > current_window_size.x
734 || new_size.y > current_window_size.y
735 {
736 info!("resizing window to {:?}", out.size);
737 self.size = out.size;
738 let mut size = new_size;
739 if self.window_mode == "proportional" {
741 let aspect = self.aspect_ratio;
742 let w = size.x;
743 let h = size.y;
744 let (new_w, new_h) = if w / h > aspect {
745 (h * aspect, h)
746 } else {
747 (w, w / aspect)
748 };
749 size = egui::Vec2::new(new_w, new_h);
750 }
751 ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
752 if let Some(f) = self.resized.as_mut() {
753 f(out.size.0, out.size.1);
754 }
755 }
756 }
757 let w = out.size.0 as usize;
758 let h = out.size.1 as usize;
759 let mut image =
760 egui::ColorImage::new([w, h], vec![egui::Color32::BLACK; w * h]);
761 for (i, o) in out.frame.chunks(4).zip(image.pixels.iter_mut()) {
762 *o = egui::Color32::from_rgba_unmultiplied(i[2], i[1], i[0], i[3]);
763 }
764 self.texture.set(image, egui::TextureOptions::NEAREST);
765 egui::CentralPanel::default()
766 .frame(egui::Frame::default().fill(egui::Color32::BLACK))
767 .show(ctx, |ui| {
768 let available = ui.available_size();
769 let frame_size = egui::Vec2::new(
770 out.size.0 as f32 * self.scale,
771 out.size.1 as f32 * self.scale,
772 );
773 let offset = egui::Vec2::new(
774 (available.x - frame_size.x) * 0.5,
775 (available.y - frame_size.y) * 0.5,
776 );
777 let top_left = ui.min_rect().min + offset;
778 let mut mesh = egui::Mesh::with_texture(self.texture.id());
779 mesh.add_rect_with_uv(
780 egui::Rect {
781 min: top_left,
782 max: top_left + frame_size,
783 },
784 egui::Rect {
785 min: egui::Pos2::new(0.0, 0.0),
786 max: egui::Pos2::new(1.0, 1.0),
787 },
788 egui::Color32::WHITE,
789 );
790 ui.painter().add(egui::Shape::mesh(mesh));
791 if self.auto_rom_select && !self.auto_roms.is_empty() {
793 ui.label(format!(
794 "[AUTO ROM CYCLING] Switching ROM every 10s. ROMs loaded: {}",
795 self.auto_roms.len()
796 ));
797 }
798 });
799 out.check().ok();
800 }
801}
802
803pub fn decode_key(k: egui::Key, shift: bool) -> Option<Key> {
804 let c = match (k, shift) {
805 (egui::Key::ArrowUp, _) => Key::Up,
806 (egui::Key::ArrowDown, _) => Key::Down,
807 (egui::Key::ArrowLeft, _) => Key::Left,
808 (egui::Key::ArrowRight, _) => Key::Right,
809 (egui::Key::Home, _) => Key::Home,
810 (egui::Key::Num0, false) => Key::Char(b'0'),
811 (egui::Key::Num0, true) => Key::Char(b')'),
812 (egui::Key::Num1, false) => Key::Char(b'1'),
813 (egui::Key::Num1, true) => Key::Char(b'!'),
814 (egui::Key::Num2, false) => Key::Char(b'2'),
815 (egui::Key::Num2, true) => Key::Char(b'@'),
816 (egui::Key::Num3, false) => Key::Char(b'3'),
817 (egui::Key::Num3, true) => Key::Char(b'#'),
818 (egui::Key::Num4, false) => Key::Char(b'4'),
819 (egui::Key::Num4, true) => Key::Char(b'$'),
820 (egui::Key::Num5, false) => Key::Char(b'5'),
821 (egui::Key::Num5, true) => Key::Char(b'5'),
822 (egui::Key::Num6, false) => Key::Char(b'6'),
823 (egui::Key::Num6, true) => Key::Char(b'^'),
824 (egui::Key::Num7, false) => Key::Char(b'7'),
825 (egui::Key::Num7, true) => Key::Char(b'&'),
826 (egui::Key::Num8, false) => Key::Char(b'8'),
827 (egui::Key::Num8, true) => Key::Char(b'*'),
828 (egui::Key::Num9, false) => Key::Char(b'9'),
829 (egui::Key::Num9, true) => Key::Char(b'('),
830 (egui::Key::A, false) => Key::Char(b'a'),
831 (egui::Key::A, true) => Key::Char(b'A'),
832 (egui::Key::B, false) => Key::Char(b'b'),
833 (egui::Key::B, true) => Key::Char(b'B'),
834 (egui::Key::C, false) => Key::Char(b'c'),
835 (egui::Key::C, true) => Key::Char(b'C'),
836 (egui::Key::D, false) => Key::Char(b'd'),
837 (egui::Key::D, true) => Key::Char(b'D'),
838 (egui::Key::E, false) => Key::Char(b'e'),
839 (egui::Key::E, true) => Key::Char(b'E'),
840 (egui::Key::F, false) => Key::Char(b'f'),
841 (egui::Key::F, true) => Key::Char(b'F'),
842 (egui::Key::G, false) => Key::Char(b'g'),
843 (egui::Key::G, true) => Key::Char(b'G'),
844 (egui::Key::H, false) => Key::Char(b'h'),
845 (egui::Key::H, true) => Key::Char(b'H'),
846 (egui::Key::I, false) => Key::Char(b'i'),
847 (egui::Key::I, true) => Key::Char(b'I'),
848 (egui::Key::J, false) => Key::Char(b'j'),
849 (egui::Key::J, true) => Key::Char(b'J'),
850 (egui::Key::K, false) => Key::Char(b'k'),
851 (egui::Key::K, true) => Key::Char(b'K'),
852 (egui::Key::L, false) => Key::Char(b'l'),
853 (egui::Key::L, true) => Key::Char(b'L'),
854 (egui::Key::M, false) => Key::Char(b'm'),
855 (egui::Key::M, true) => Key::Char(b'M'),
856 (egui::Key::N, false) => Key::Char(b'n'),
857 (egui::Key::N, true) => Key::Char(b'N'),
858 (egui::Key::O, false) => Key::Char(b'o'),
859 (egui::Key::O, true) => Key::Char(b'O'),
860 (egui::Key::P, false) => Key::Char(b'p'),
861 (egui::Key::P, true) => Key::Char(b'P'),
862 (egui::Key::Q, false) => Key::Char(b'q'),
863 (egui::Key::Q, true) => Key::Char(b'Q'),
864 (egui::Key::R, false) => Key::Char(b'r'),
865 (egui::Key::R, true) => Key::Char(b'R'),
866 (egui::Key::S, false) => Key::Char(b's'),
867 (egui::Key::S, true) => Key::Char(b'S'),
868 (egui::Key::T, false) => Key::Char(b't'),
869 (egui::Key::T, true) => Key::Char(b'T'),
870 (egui::Key::U, false) => Key::Char(b'u'),
871 (egui::Key::U, true) => Key::Char(b'U'),
872 (egui::Key::V, false) => Key::Char(b'v'),
873 (egui::Key::V, true) => Key::Char(b'V'),
874 (egui::Key::W, false) => Key::Char(b'w'),
875 (egui::Key::W, true) => Key::Char(b'W'),
876 (egui::Key::X, false) => Key::Char(b'x'),
877 (egui::Key::X, true) => Key::Char(b'X'),
878 (egui::Key::Y, false) => Key::Char(b'y'),
879 (egui::Key::Y, true) => Key::Char(b'Y'),
880 (egui::Key::Z, false) => Key::Char(b'z'),
881 (egui::Key::Z, true) => Key::Char(b'Z'),
882 (egui::Key::Backtick, false) => Key::Char(b'`'),
883 (egui::Key::Backtick, true) => Key::Char(b'~'),
884 (egui::Key::Backslash, _) => Key::Char(b'\\'),
885 (egui::Key::Pipe, _) => Key::Char(b'|'),
886 (egui::Key::Comma, false) => Key::Char(b','),
887 (egui::Key::Comma, true) => Key::Char(b'<'),
888 (egui::Key::Equals, _) => Key::Char(b'='),
889 (egui::Key::Plus, _) => Key::Char(b'+'),
890 (egui::Key::OpenBracket, false) => Key::Char(b'['),
891 (egui::Key::OpenBracket, true) => Key::Char(b'{'),
892 (egui::Key::Minus, false) => Key::Char(b'-'),
893 (egui::Key::Minus, true) => Key::Char(b'_'),
894 (egui::Key::Period, false) => Key::Char(b'.'),
895 (egui::Key::Period, true) => Key::Char(b'>'),
896 (egui::Key::CloseBracket, false) => Key::Char(b']'),
897 (egui::Key::CloseBracket, true) => Key::Char(b'}'),
898 (egui::Key::Semicolon, _) => Key::Char(b';'),
899 (egui::Key::Colon, _) => Key::Char(b':'),
900 (egui::Key::Slash, _) => Key::Char(b'/'),
901 (egui::Key::Questionmark, _) => Key::Char(b'?'),
902 (egui::Key::Space, _) => Key::Char(b' '),
903 (egui::Key::Tab, _) => Key::Char(b'\t'),
904 (egui::Key::Enter, _) => Key::Char(b'\r'),
905 (egui::Key::Backspace, _) => Key::Char(0x08),
906 _ => return None,
907 };
908 Some(c)
909}
910
911pub fn audio_setup(
921 data: [Arc<Mutex<varvara::StreamData>>; 4],
922) -> Option<(cpal::Device, [cpal::Stream; 4])> {
923 use cpal::traits::{DeviceTrait, HostTrait};
924 let host = cpal::default_host();
925 let device = host
926 .default_output_device()
927 .expect("no output device available");
928 let supported_configs_range = device
929 .supported_output_configs()
930 .expect("error while querying configs")
931 .collect::<Vec<_>>();
932
933 let mut supported_config = None;
934 let mut used_rate = 0;
935 for &rate in &[48000, 44100] {
936 supported_config = supported_configs_range
937 .iter()
938 .filter(|c| usize::from(c.channels()) == varvara::AUDIO_CHANNELS)
939 .filter(|c| c.sample_format() == cpal::SampleFormat::F32)
940 .find_map(|c| c.try_with_sample_rate(cpal::SampleRate(rate)));
941 if supported_config.is_some() {
942 used_rate = rate;
943 break;
944 }
945 }
946 let supported_config = match supported_config {
947 Some(cfg) => cfg,
948 None => {
949 log::error!(
950 "could not find supported audio config ({} channels, 48000 or 44100 Hz, f32)",
951 varvara::AUDIO_CHANNELS
952 );
953 log::error!("available configs:");
954 for c in &supported_configs_range {
955 if c.min_sample_rate() == c.max_sample_rate() {
956 log::error!(
957 " channels: {}, sample_rate: {} Hz, {}",
958 c.channels(),
959 c.min_sample_rate().0,
960 c.sample_format(),
961 );
962 } else {
963 log::error!(
964 " channels: {}, sample_rate: {} - {} Hz, {}",
965 c.channels(),
966 c.min_sample_rate().0,
967 c.max_sample_rate().0,
968 c.sample_format(),
969 );
970 }
971 }
972 return None;
973 }
974 };
975 varvara::set_sample_rate(used_rate);
977 let config = supported_config.config();
978
979 let streams = data.map(|d| {
980 let stream = device
981 .build_output_stream(
982 &config,
983 move |data: &mut [f32], _opt: &cpal::OutputCallbackInfo| {
984 d.lock().unwrap().next(data);
985 },
986 move |err| {
987 panic!("{err}");
988 },
989 None,
990 )
991 .expect("could not build stream");
992 cpal::traits::StreamTrait::play(&stream).unwrap();
993 stream
994 });
995 Some((device, streams))
996}