#![cfg(feature = "cli")]
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use crate::engine::audio::settings::{AudioBitDepth, AudioChannels, AudioFormat, ResampleQuality};
use crate::platform::config::AppConfig;
use crate::services::build::pipeline::{BuildRequest, ProjectBuilder};
use crate::services::live::play::{LivePlayRequest, LivePlayService};
use crate::tools::cli::rules_reporter::RulesReporter;
use crate::tools::cli::state::CliContext;
#[derive(Debug, Clone, Args)]
pub struct PlayCommand {
#[arg(long = "input")]
pub input: Option<PathBuf>,
#[arg(long = "output")]
pub output: Option<PathBuf>,
#[arg(long, value_enum)]
pub format: Option<AudioFormat>,
#[arg(long = "bit-depth", value_enum)]
pub bit_depth: Option<AudioBitDepth>,
#[arg(long, value_enum)]
pub channels: Option<AudioChannels>,
#[arg(long = "sample-rate")]
pub sample_rate: Option<u32>,
#[arg(long = "resample-quality", value_enum)]
pub resample_quality: Option<ResampleQuality>,
#[arg(long)]
pub live: bool,
#[arg(long = "crossfade-ms")]
pub crossfade_ms: Option<u64>,
#[arg(long)]
pub quiet: bool,
#[arg(long)]
pub volume: Option<f32>,
#[arg(long, default_value_t = false)]
pub no_rule: bool,
}
pub async fn execute(command: PlayCommand, ctx: &CliContext) -> Result<()> {
let logger = ctx.logger();
let cwd = std::env::current_dir()?;
let config = AppConfig::load(&cwd)?;
let rules_reporter = if !command.no_rule {
Some(RulesReporter::new(config.clone(), logger.clone()))
} else {
None
};
#[cfg(not(target_arch = "wasm32"))]
{
use crate::engine::audio::samples;
logger.info("Loading sample banks...");
if let Err(e) = samples::auto_load_banks() {
logger.warn(format!("Failed to auto-load banks: {}", e));
}
}
let entry_path = command
.input
.clone()
.unwrap_or_else(|| config.entry_path(&cwd));
let output_root = command
.output
.clone()
.unwrap_or_else(|| config.output_path(&cwd));
let audio_format = command.format.unwrap_or_else(|| config.audio_format());
let bit_depth = command
.bit_depth
.unwrap_or_else(|| config.audio_bit_depth());
let channels = command.channels.unwrap_or_else(|| config.audio_channels());
let sample_rate = command.sample_rate.unwrap_or_else(|| config.sample_rate());
let resample_quality = command
.resample_quality
.unwrap_or_else(|| config.resample_quality());
let crossfade_ms = command
.crossfade_ms
.unwrap_or_else(|| config.crossfade_ms());
let live_mode = command.live;
if let Some(ref reporter) = rules_reporter {
if let Ok(content) = fs::read_to_string(&entry_path) {
for (line_num, line) in content.lines().enumerate() {
let line_number = line_num + 1;
if line.trim_start().starts_with('@') {
if let Some(rule_msg) = reporter.checker().check_deprecated_syntax(
line_number,
"@ prefix syntax",
"keyword syntax (import, export, use, load)",
) {
reporter.logger().log_rule_message(&rule_msg);
}
}
}
}
}
logger.info(format!(
"Using entry={} output={} format={:?} bit_depth={} channels={} sample_rate={} resample={}",
entry_path.display(),
output_root.display(),
audio_format,
bit_depth.bits(),
channels.count(),
sample_rate,
resample_quality
));
fs::create_dir_all(&output_root)?;
let build_request = BuildRequest {
entry_path: entry_path.clone(),
output_root: output_root.clone(),
audio_formats: vec![audio_format],
bit_depth,
channels,
resample_quality,
sample_rate,
bpm: config.audio.bpm,
};
let builder = ProjectBuilder::new(logger.clone());
let service = LivePlayService::new(logger.clone(), builder)?;
let volume = if command.quiet {
0.0
} else {
let vol = command.volume.unwrap_or(1.0);
if vol < 0.0 || vol > 1.0 {
logger.error("Volume must be between 0.0 and 1.0");
return Err(anyhow::anyhow!("Invalid volume value: {}", vol));
}
vol
};
let request = LivePlayRequest {
build: build_request,
live_mode,
crossfade_ms,
volume,
};
service.run(request).await
}