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