use rustzx_core::{
zx::{machine::ZXMachine, sound::ay::ZXAYMode},
EmulationMode, RustzxSettings,
};
use std::path::PathBuf;
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
#[cfg(feature = "sound-cpal")]
const DEFAULT_SOUND_BACKEND_VALUE: &str = "cpal";
#[cfg(not(feature = "sound-cpal"))]
const DEFAULT_SOUND_BACKEND_VALUE: &str = "sdl";
#[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)]
#[strum(serialize_all = "snake_case")]
pub enum SoundBackend {
Sdl,
#[cfg(feature = "sound-cpal")]
Cpal,
}
#[derive(StructOpt)]
#[structopt(about = env!("CARGO_PKG_DESCRIPTION"))]
#[structopt(name = "RustZX")]
pub struct Settings {
#[structopt(verbatim_doc_comment, short, long, default_value = "48k", parse(try_from_str = machine_from_str))]
pub machine: ZXMachine,
#[structopt(long, default_value = "1", parse(try_from_str = emulation_speed_from_str))]
pub speed: EmulationMode,
#[structopt(long = "nofastload")]
pub disable_fastload: bool,
#[structopt(long = "noautoload")]
pub disable_autoload: bool,
#[structopt(short, long, default_value = "2", parse(try_from_str = scale_from_str))]
pub scale: usize,
#[structopt(long = "nokempston")]
pub disable_kempston: bool,
#[structopt(long = "mouse")]
pub enable_mouse: bool,
#[structopt(long = "mouse-sensitivity", default_value = "20")]
pub mouse_sensitivity: usize,
#[structopt(long, default_value = "abc", parse(try_from_str = ay_mode_from_str))]
pub ay_mode: ZXAYMode,
#[structopt(long = "ay", conflicts_with = "force-disable-ay")]
pub force_enable_ay: bool,
#[structopt(long = "noay", conflicts_with = "force-enable-ay")]
pub force_disable_ay: bool,
#[structopt(long = "nobeeper")]
pub disable_beeper: bool,
#[structopt(long = "nosound")]
pub disable_sound: bool,
#[structopt(long, parse(try_from_str = sound_latency_from_str))]
pub sound_latency: Option<usize>,
#[structopt(long, parse(try_from_str = sound_sample_rate_from_str))]
pub sound_sample_rate: Option<usize>,
#[structopt(
long,
default_value = DEFAULT_SOUND_BACKEND_VALUE,
possible_values = &SoundBackend::VARIANTS
)]
pub sound_backend: SoundBackend,
#[structopt(long, conflicts_with = "file-autodetect")]
pub rom: Option<PathBuf>,
#[structopt(long, conflicts_with = "file-autodetect")]
pub tape: Option<PathBuf>,
#[structopt(long, conflicts_with = "file-autodetect")]
pub snap: Option<PathBuf>,
#[structopt(long, conflicts_with = "file-autodetect")]
pub screen: Option<PathBuf>,
pub file_autodetect: Option<PathBuf>,
}
fn machine_from_str(s: &str) -> Result<ZXMachine, anyhow::Error> {
match s.to_lowercase().as_str() {
"48k" | "48" => Ok(ZXMachine::Sinclair48K),
"128k" | "128" => Ok(ZXMachine::Sinclair128K),
s => Err(anyhow::anyhow!("Invalid machine type `{}`", s)),
}
}
fn emulation_speed_from_str(s: &str) -> Result<EmulationMode, anyhow::Error> {
match s.to_lowercase().as_str() {
"max" => Ok(EmulationMode::Max),
s => {
let speed: std::num::NonZeroUsize = s
.parse()
.map_err(|_| anyhow::anyhow!("Invalid emulation speed `{}`", s))?;
Ok(EmulationMode::FrameCount(speed.into()))
}
}
}
fn scale_from_str(s: &str) -> Result<usize, anyhow::Error> {
let scale: std::num::NonZeroUsize = s
.parse()
.map_err(|_| anyhow::anyhow!("Invalid screen scale `{}`", s))?;
Ok(scale.into())
}
fn ay_mode_from_str(s: &str) -> Result<ZXAYMode, anyhow::Error> {
match s.to_lowercase().as_str() {
"mono" => Ok(ZXAYMode::Mono),
"abc" => Ok(ZXAYMode::ABC),
"acb" => Ok(ZXAYMode::ACB),
s => Err(anyhow::anyhow!("Invalid AY chip mode `{}`", s)),
}
}
fn sound_latency_from_str(s: &str) -> Result<usize, anyhow::Error> {
let latency = s
.parse::<usize>()
.map_err(|_| anyhow::anyhow!("Invalid sound latency `{}`", s))?;
if latency < 64 {
anyhow::bail!("Setting sound latency lower than 64 is bad for your health");
}
if latency > 1024 * 64 {
anyhow::bail!("This sound latency is HUGE. Please don't try this at home!");
}
Ok(latency)
}
fn sound_sample_rate_from_str(s: &str) -> Result<usize, anyhow::Error> {
let sample_rate = s
.parse::<usize>()
.map_err(|_| anyhow::anyhow!("Invalid sound sample rate {}", s))?;
if sample_rate < 8000 {
anyhow::bail!("Provided sound sample rate `{}` is too low", sample_rate);
}
if sample_rate > 384000 {
anyhow::bail!("Provided sound sample rate `{}` is too high", sample_rate);
}
Ok(sample_rate)
}
impl Settings {
pub fn to_rustzx_settings(&self, sound_sample_rate: usize) -> RustzxSettings {
let ay_enabled = (matches!(self.machine, ZXMachine::Sinclair128K) || self.force_enable_ay)
&& (!self.force_disable_ay);
RustzxSettings {
machine: self.machine,
emulation_mode: self.speed,
tape_fastload_enabled: !self.disable_fastload,
kempston_enabled: !self.disable_kempston,
mouse_enabled: self.enable_mouse,
ay_mode: self.ay_mode,
ay_enabled,
beeper_enabled: !self.disable_beeper,
sound_enabled: !self.disable_sound,
sound_volume: 100,
load_default_rom: self.rom.is_none(),
sound_sample_rate,
autoload_enabled: !self.disable_autoload,
}
}
}