1#![warn(missing_docs)]
3#[cfg(feature = "uses_gilrs")]
4use crate::controller_gilrs::ControllerGilrs;
5use log::warn;
6use std::collections::HashMap;
7use std::fs::File;
8use std::io::{self, Read};
9use std::{
10 io::Write,
11 sync::{Arc, Mutex},
12};
13mod audio;
15mod console;
16pub mod controller;
18mod controller_device;
19#[cfg(feature = "uses_gilrs")]
21pub mod controller_gilrs;
22#[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
24pub mod controller_usb;
25
26#[cfg(any(not(feature = "uses_usb"), target_arch = "wasm32"))]
27#[path = "controller_usb_stub.rs"]
28pub mod controller_usb;
29
30mod datetime;
31mod file;
32mod mouse;
33mod screen;
34mod system;
35mod tracker;
36
37pub use audio::set_sample_rate;
38pub use audio::StreamData;
39pub use audio::CHANNELS as AUDIO_CHANNELS;
40pub use console::spawn_worker as spawn_console_worker;
41pub use controller::Key;
42pub use mouse::MouseState;
43pub use tracker::TrackerState;
44
45use uxn::{Device, Ports, Uxn};
46
47#[derive(Clone)]
49pub struct RomData {
50 pub rom: Vec<u8>,
52 pub sym: Option<Vec<u8>>,
54}
55
56#[derive(Copy, Clone, Debug)]
58pub struct EventData {
59 pub addr: u8,
61 pub value: u8,
63 pub clear: bool,
65}
66
67#[derive(Copy, Clone, Debug, Default)]
69pub struct Event {
70 pub data: Option<EventData>,
72
73 pub vector: u16,
75}
76
77pub struct Output<'a> {
79 pub size: (u16, u16),
81
82 pub frame: &'a [u8],
84
85 pub hide_mouse: bool,
87
88 pub stdout: Vec<u8>,
90
91 pub stderr: Vec<u8>,
93
94 pub exit: Option<i32>,
96}
97
98impl Output<'_> {
99 pub fn print(&self) -> std::io::Result<()> {
101 if !self.stdout.is_empty() {
102 let mut stdout = std::io::stdout().lock();
103 stdout.write_all(&self.stdout)?;
104 stdout.flush()?;
105 }
106 if !self.stderr.is_empty() {
107 let mut stderr = std::io::stderr().lock();
108 stderr.write_all(&self.stderr)?;
109 stderr.flush()?;
110 }
111 Ok(())
112 }
113
114 pub fn check(&self) -> std::io::Result<()> {
119 self.print()?;
120 if let Some(e) = self.exit {
121 log::info!("requested exit ({e})");
122
123 #[cfg(not(target_arch = "wasm32"))]
124 std::process::exit(e);
125
126 #[cfg(target_arch = "wasm32")]
127 return Err(std::io::Error::other("exit requested"));
128 }
129 Ok(())
130 }
131}
132
133pub struct Varvara {
135 pub system: system::System,
137 pub console: console::Console,
139 pub datetime: datetime::Datetime,
141 pub audio: audio::Audio,
143 pub screen: screen::Screen,
145 pub mouse: mouse::Mouse,
147 pub file: file::File,
149 pub controller: Box<dyn controller::ControllerDevice>,
151 pub tracker: tracker::Tracker,
153 pub already_warned: [bool; 16],
155 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
157 pub uses_usb: bool,
158 pub symbols: Option<HashMap<u16, String>>,
160 pub last_vector: u16,
162}
163
164impl Default for Varvara {
165 fn default() -> Self {
166 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
167 {
168 let mut v = Self::new(true);
169 v.symbols = None;
170 v.last_vector = 0;
171 v
172 }
173 #[cfg(any(not(feature = "uses_usb"), target_arch = "wasm32"))]
174 {
175 let mut v = Self::new();
176 v.symbols = None;
177 v.last_vector = 0;
178 v
179 }
180 }
181}
182
183impl Device for Varvara {
184 fn deo(&mut self, vm: &mut Uxn, target: u8) -> bool {
185 match target & 0xF0 {
186 system::SystemPorts::BASE => self.system.deo(vm, target),
187 console::ConsolePorts::BASE => self.console.deo(vm, target),
188 datetime::DatetimePorts::BASE => self.datetime.deo(vm, target),
189 screen::ScreenPorts::BASE => self.screen.deo(vm, target),
190 mouse::MousePorts::BASE => self.mouse.set_active(),
191 tracker::TrackerPorts::BASE => self.tracker.set_active(),
192 f if file::FilePorts::matches(f) => self.file.deo(vm, target),
193 controller::ControllerPorts::BASE => (),
194 a if audio::AudioPorts::matches(a) => self.audio.deo(vm, target),
195
196 t => self.warn_missing(t),
198 }
199 !self.system.should_exit()
200 }
201 fn dei(&mut self, vm: &mut Uxn, target: u8) {
202 match target & 0xF0 {
203 system::SystemPorts::BASE => self.system.dei(vm, target),
204 console::ConsolePorts::BASE => self.console.dei(vm, target),
205 datetime::DatetimePorts::BASE => self.datetime.dei(vm, target),
206 screen::ScreenPorts::BASE => self.screen.dei(vm, target),
207 mouse::MousePorts::BASE => self.mouse.set_active(),
208 tracker::TrackerPorts::BASE => self.tracker.set_active(),
209 f if file::FilePorts::matches(f) => (),
210 controller::ControllerPorts::BASE => (),
211 a if audio::AudioPorts::matches(a) => self.audio.dei(vm, target),
212
213 t => self.warn_missing(t),
215 }
216 }
217}
218
219impl Varvara {
220 pub fn load_symbols_into_self(&mut self, path: &str) -> std::io::Result<()> {
222 let map = Self::load_symbols(path)?;
223 self.symbols = Some(map);
224 Ok(())
225 }
226
227 pub fn load_sym_with_rom_path<P: AsRef<std::path::Path>>(&mut self, path: P) {
229 let path = path.as_ref();
230 println!("[DEBUG][VARVARA] Loading symbols from {path:?}");
231 if path.exists() {
232 println!("[DEBUG][VARVARA] Found ROM at {path:?}");
233 if let Some(file_name) = path.file_name() {
235 use std::ffi::OsString;
236 use std::path::PathBuf;
237 let mut sys_file = OsString::from(file_name);
238 sys_file.push(".sym");
239 let mut sys_path = PathBuf::from(path);
240 sys_path.set_file_name(sys_file);
241 #[cfg(windows)]
242 let mut sys_path_str = sys_path.to_string_lossy().to_string();
243 #[cfg(windows)]
244 {
245 sys_path_str = sys_path_str.replace("/", "\\");
246 }
247 #[cfg(not(windows))]
248 let sys_path_str = sys_path.to_string_lossy().to_string();
249 println!("[DEBUG][VARVARA] Attempting to load symbols from {sys_path_str}");
250 if sys_path.exists() {
251 let _ = self.load_symbols_into_self(&sys_path_str);
252 println!("[DEBUG][VARVARA] Loaded symbols from {sys_path_str}");
253 }
254 }
255 }
256 }
257
258 pub fn load_symbols_from_vec(&mut self, data: &[u8]) -> io::Result<HashMap<u16, String>> {
260 let map = Self::parse_symbols_from_bytes(data)?;
261 self.symbols = Some(map.clone());
262 Ok(map)
263 }
264 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
266 pub fn controller_usb_mut(&mut self) -> Option<&mut controller_usb::ControllerUsb> {
267 self.controller
268 .as_mut()
269 .as_any()
270 .downcast_mut::<controller_usb::ControllerUsb>()
271 }
272
273 pub fn vector_to_label(&self, vector: u16) -> &str {
275 if let Some(ref map) = self.symbols {
276 map.get(&vector).map(|s| s.as_str()).unwrap_or("unknown")
277 } else {
278 "unknown"
279 }
280 }
281 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
283 pub fn new(uses_usb: bool) -> Self {
284 log::info!("[Varvara::new] (USB) uses_usb={}", uses_usb);
285 let controller: Box<dyn controller::ControllerDevice> = if uses_usb {
286 #[cfg(all(feature = "uses_gilrs", not(target_arch = "wasm32")))]
287 {
288 log::info!("[Varvara::new] USB+gilrs: constructing ControllerUsb with gilrs");
289 Box::new(controller_usb::ControllerUsb {
290 rx: controller_usb::spawn_usb_controller_thread(
291 controller_usb::UsbDeviceConfig::default(),
292 ),
293 last_pedal: None,
294 controller: controller::Controller::default(),
295 gilrs: Some(ControllerGilrs::new(controller::Controller::default())),
296 })
297 }
298 #[cfg(all(not(feature = "uses_gilrs"), not(target_arch = "wasm32")))]
299 {
300 log::info!("[Varvara::new] USB: constructing ControllerUsb without gilrs");
301 Box::new(controller_usb::ControllerUsb {
302 rx: controller_usb::spawn_usb_controller_thread(
303 controller_usb::UsbDeviceConfig::default(),
304 ),
305 last_pedal: None,
306 controller: controller::Controller::default(),
307 })
308 }
309 #[cfg(target_arch = "wasm32")]
310 {
311 log::info!("[Varvara::new] USB: WASM stub controller");
312 Box::new(controller::Controller::default())
313 }
314 } else {
315 #[cfg(all(feature = "uses_gilrs", not(target_arch = "wasm32")))]
316 {
317 log::info!("[Varvara::new] non-USB+gilrs: constructing ControllerGilrs");
318 use crate::controller_gilrs::ControllerGilrs;
319 Box::new(ControllerGilrs::new(controller::Controller::default()))
320 }
321 #[cfg(any(not(feature = "uses_gilrs"), target_arch = "wasm32"))]
322 {
323 log::info!("[Varvara::new] non-USB: WASM stub controller");
324 Box::new(controller::Controller::default())
325 }
326 };
327 Self {
328 console: console::Console::new(),
329 system: system::System::new(),
330 datetime: datetime::Datetime,
331 audio: audio::Audio::new(),
332 screen: screen::Screen::new(),
333 mouse: mouse::Mouse::new(),
334 file: file::File::new(),
335 controller,
336 tracker: tracker::Tracker::new(),
337 already_warned: [false; 16],
338 uses_usb,
339 symbols: None,
340 last_vector: 0,
341 }
342 }
343
344 #[cfg(any(not(feature = "uses_usb"), target_arch = "wasm32"))]
346 pub fn new() -> Self {
347 log::info!("[Varvara::new] (non-USB/wasm) constructing");
353 Self {
354 console: console::Console::new(),
355 system: system::System::new(),
356 datetime: datetime::Datetime,
357 audio: audio::Audio::new(),
358 screen: screen::Screen::new(),
359 mouse: mouse::Mouse::new(),
360 file: file::File::new(),
361 controller: {
362 #[cfg(all(feature = "uses_gilrs", not(target_arch = "wasm32")))]
363 {
364 log::info!("[Varvara::new] (non-USB/wasm) ControllerGilrs");
365 Box::new(ControllerGilrs::new(controller::Controller::default()))
366 }
367 #[cfg(any(not(feature = "uses_gilrs"), target_arch = "wasm32"))]
368 {
369 log::info!("[Varvara::new] (non-USB/wasm) WASM stub controller");
370 Box::new(controller::Controller::default())
371 }
372 },
373 tracker: tracker::Tracker::new(),
374 already_warned: [false; 16],
375 symbols: None,
376 last_vector: 0,
377 }
378 }
379
380 #[cfg(all(feature = "uses_usb", not(target_arch = "wasm32")))]
382 pub fn reset(&mut self, extra: &[u8]) {
383 self.system.reset(extra);
384 self.console = console::Console::new();
385 self.audio.reset();
386 self.screen = screen::Screen::new();
387 self.mouse = mouse::Mouse::new();
388 self.file = file::File::new();
389
390 self.controller = if self.uses_usb {
391 #[cfg(all(
392 feature = "uses_usb",
393 not(target_arch = "wasm32"),
394 feature = "uses_gilrs"
395 ))]
396 {
397 Box::new(controller_usb::ControllerUsb {
398 rx: controller_usb::spawn_usb_controller_thread(
399 controller_usb::UsbDeviceConfig::default(),
400 ),
401 last_pedal: None,
402 controller: controller::Controller::default(),
403 gilrs: Some(ControllerGilrs::new(controller::Controller::default())),
404 })
405 }
406 #[cfg(all(
407 feature = "uses_usb",
408 not(target_arch = "wasm32"),
409 not(feature = "uses_gilrs")
410 ))]
411 {
412 Box::new(controller_usb::ControllerUsb {
413 rx: controller_usb::spawn_usb_controller_thread(
414 controller_usb::UsbDeviceConfig::default(),
415 ),
416 last_pedal: None,
417 controller: controller::Controller::default(),
418 })
419 }
420 #[cfg(target_arch = "wasm32")]
421 {
422 Box::new(controller::Controller::default())
424 }
425 } else {
426 #[cfg(all(feature = "uses_gilrs", not(target_arch = "wasm32")))]
427 {
428 Box::new(ControllerGilrs::new(controller::Controller::default()))
429 }
430 #[cfg(any(not(feature = "uses_gilrs"), target_arch = "wasm32"))]
431 {
432 Box::new(controller::Controller::default())
434 }
435 };
436 self.tracker = tracker::Tracker::new();
437 self.already_warned.fill(false);
438 }
439
440 #[cfg(any(not(feature = "uses_usb"), target_arch = "wasm32"))]
442 pub fn reset(&mut self, extra: &[u8]) {
443 self.system.reset(extra);
444 self.console = console::Console::new();
445 self.audio.reset();
446 self.screen = screen::Screen::new();
447 self.mouse = mouse::Mouse::new();
448 self.file = file::File::new();
449
450 self.controller = {
451 #[cfg(feature = "uses_gilrs")]
452 {
453 Box::new(ControllerGilrs::new(controller::Controller::default()))
454 }
455 #[cfg(not(feature = "uses_gilrs"))]
456 {
457 Box::new(controller::Controller::default())
458 }
459 };
460 self.tracker = tracker::Tracker::new();
461 self.already_warned.fill(false);
462 }
463
464 fn warn_missing(&mut self, t: u8) {
466 if !self.already_warned[usize::from(t >> 4)] {
467 warn!("unimplemented device {t:#02x}");
468 self.already_warned[usize::from(t >> 4)] = true;
469 }
470 }
471
472 pub fn redraw(&mut self, vm: &mut Uxn) {
476 let e = self.screen.update(vm);
477 self.process_event(vm, e);
478 }
479
480 pub fn init_args(&mut self, vm: &mut Uxn, args: &[String]) {
484 self.console.set_has_args(vm, !args.is_empty());
485 }
486
487 #[must_use]
492 pub fn output(&mut self, vm: &Uxn) -> Output<'_> {
493 Output {
494 size: self.screen.size(),
495 frame: self.screen.frame(vm),
496 hide_mouse: self.mouse.active(),
497 stdout: self.console.stdout(),
498 stderr: self.console.stderr(),
499 exit: self.system.exit(),
500 }
501 }
502
503 pub fn send_args(&mut self, vm: &mut Uxn, args: &[String]) -> Output<'_> {
508 for (i, a) in args.iter().enumerate() {
509 self.console.set_type(vm, console::Type::Argument);
510 for c in a.bytes() {
511 self.process_event(vm, self.console.update(vm, c));
512 }
513
514 let ty = if i == args.len() - 1 {
515 console::Type::ArgumentEnd
516 } else {
517 console::Type::ArgumentSpacer
518 };
519 self.console.set_type(vm, ty);
520 self.process_event(vm, self.console.update(vm, b'\n'));
521 }
522 self.console.set_type(vm, console::Type::Stdin);
523 self.output(vm)
524 }
525
526 pub fn char(&mut self, vm: &mut Uxn, k: u8) {
529 println!("Sending character: {k}");
530 let e = self.controller.char(vm, k);
531 println!("Processing event: {e:?}");
532 self.process_event(vm, e);
533 }
534
535 pub fn pressed(&mut self, vm: &mut Uxn, k: Key, repeat: bool) {
538 println!("Pressed key: {k:?}");
540 if let Key::Char(k) = k {
541 self.char(vm, k);
543 } else if let Some(e) = self.controller.pressed(vm, k, repeat) {
544 println!("Processing event: {e:?}");
545 self.process_event(vm, e);
546 }
547 }
548
549 pub fn released(&mut self, vm: &mut Uxn, k: Key) {
551 if let Some(e) = self.controller.released(vm, k) {
552 self.process_event(vm, e);
553 }
554 }
555
556 pub fn console(&mut self, vm: &mut Uxn, c: u8) {
558 let e = self.console.update(vm, c);
559 self.process_event(vm, e);
560 }
561
562 pub fn mouse(&mut self, vm: &mut Uxn, m: MouseState) {
564 if let Some(e) = self.mouse.update(vm, m) {
565 self.process_event(vm, e);
566 }
567 }
568
569 pub fn audio(&mut self, vm: &mut Uxn) {
571 for i in 0..audio::DEV_COUNT {
572 if let Some(e) = self.audio.update(vm, usize::from(i)) {
573 self.process_event(vm, e);
574 }
575 }
576 }
577
578 pub fn tracker(&mut self, vm: &mut Uxn, m: tracker::TrackerState) {
580 if let Some(e) = self.tracker.update(vm, m) {
581 self.process_event(vm, e);
582 }
583 }
584
585 pub fn process_event(&mut self, vm: &mut Uxn, e: Event) {
589 if e.vector != 0 {
590 let label = self.vector_to_label(e.vector);
591 let skip_labels = ["timer/on-play", "on-mouse"];
592 let skip_print = skip_labels.contains(&label);
593
594 if self.last_vector != e.vector && !skip_print {
595 println!(
596 "[VARVARA][process_event] vector: 0x{:04x} [{}], data: {:?}",
597 e.vector, label, e.data
598 );
599 self.last_vector = e.vector;
600 }
601
602 if let Some(d) = e.data {
603 if !skip_print {
604 println!("[VARVARA][process_event] write_dev_mem addr: 0x{:02x}, value: 0x{:02x} ('{}')", d.addr, d.value, d.value as char);
605 }
606 vm.write_dev_mem(d.addr, d.value);
607 }
608 vm.run(self, e.vector);
609 if let Some(d) = e.data {
610 if d.clear {
611 if !skip_print {
612 println!("[VARVARA][process_event] clear addr: 0x{:02x}", d.addr);
613 }
614 vm.write_dev_mem(d.addr, 0);
615 }
616 }
617 }
618 }
619
620 pub fn audio_streams(&self) -> [Arc<Mutex<audio::StreamData>>; 4] {
622 [0, 1, 2, 3].map(|i| self.audio.stream(i))
623 }
624
625 pub fn audio_set_muted(&mut self, m: bool) {
627 self.audio.set_muted(m)
628 }
629
630 pub fn load_symbols(path: &str) -> io::Result<HashMap<u16, String>> {
632 let mut file = File::open(path)?;
633 let mut buf = Vec::new();
634 file.read_to_end(&mut buf)?;
635 println!(
636 "[DEBUG] Loaded symbols from {path:?}, length: {}",
637 buf.len()
638 );
639 let mut map = HashMap::new();
640 let mut i = 0;
641 while i + 2 < buf.len() {
642 let addr = ((buf[i] as u16) << 8) | (buf[i + 1] as u16);
643 i += 2;
644 let mut end = i;
645 while end < buf.len() && buf[end] != 0 {
646 end += 1;
647 }
648 let label = String::from_utf8_lossy(&buf[i..end]).to_string();
649 map.insert(addr, label);
650 i = end + 1;
651 }
652 Ok(map)
653 }
654
655 pub fn parse_symbols_from_bytes(
657 data: &[u8],
658 ) -> std::io::Result<std::collections::HashMap<u16, String>> {
659 let mut map = std::collections::HashMap::new();
660 let mut i = 0;
661 while i + 2 < data.len() {
662 let addr = ((data[i] as u16) << 8) | (data[i + 1] as u16);
663 i += 2;
664 let mut end = i;
665 while end < data.len() && data[end] != 0 {
666 end += 1;
667 }
668 let label = String::from_utf8_lossy(&data[i..end]).to_string();
669 map.insert(addr, label);
670 i = end + 1;
671 }
672 Ok(map)
673 }
674}