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