use std::fs;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
fn find_tunes_root() -> anyhow::Result<PathBuf> {
let mut current = std::env::current_dir()?;
loop {
let cargo_toml = current.join("Cargo.toml");
if cargo_toml.exists() {
let content = fs::read_to_string(&cargo_toml)?;
if content.contains("name = \"tunes\"") {
return Ok(current);
}
}
if let Some(parent) = current.parent() {
current = parent.to_path_buf();
} else {
return Err(anyhow::anyhow!(
"Could not find tunes project root. Make sure you're running from within the tunes directory."
));
}
}
}
pub struct LiveRunner {
source_file: PathBuf,
temp_dir: PathBuf,
current_process: Option<Child>,
}
impl LiveRunner {
pub fn new(source_file: PathBuf) -> anyhow::Result<Self> {
let temp_dir = std::env::temp_dir().join("tunes_live");
fs::create_dir_all(&temp_dir)?;
Ok(Self {
source_file,
temp_dir,
current_process: None,
})
}
pub fn compile_and_run(&mut self) -> anyhow::Result<()> {
println!("🔨 Compiling {}...", self.source_file.display());
self.stop();
let project_dir = self.temp_dir.join("live_project");
fs::create_dir_all(&project_dir)?;
let tunes_root = find_tunes_root()?;
let cargo_toml = format!(
r#"[package]
name = "tunes-live-session"
version = "0.1.0"
edition = "2021"
[dependencies]
tunes = {{ path = "{}" }}
anyhow = "1.0"
[profile.live]
inherits = "release"
opt-level = 2
lto = false
incremental = true
codegen-units = 256
"#,
tunes_root.display()
);
fs::write(project_dir.join("Cargo.toml"), cargo_toml)?;
let src_dir = project_dir.join("src");
fs::create_dir_all(&src_dir)?;
let mut source_content = fs::read_to_string(&self.source_file)?;
source_content = source_content
.replace("use crate::", "use tunes::")
.replace("use crate::composition", "use tunes::composition")
.replace("use crate::consts", "use tunes::consts")
.replace("use crate::engine", "use tunes::engine")
.replace("use crate::instruments", "use tunes::instruments")
.replace("use crate::prelude", "use tunes::prelude");
fs::write(src_dir.join("main.rs"), source_content)?;
let compile_output = Command::new("cargo")
.arg("build")
.arg("--profile")
.arg("live")
.current_dir(&project_dir)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
if !compile_output.status.success() {
let stderr = String::from_utf8_lossy(&compile_output.stderr);
println!("❌ Compilation failed:\n{}", stderr);
return Err(anyhow::anyhow!("Compilation failed"));
}
println!("✅ Compiled successfully!");
let binary_path = project_dir
.join("target/live")
.join("tunes-live-session");
println!("▶️ Starting playback...");
let child = Command::new(binary_path)
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()?;
self.current_process = Some(child);
Ok(())
}
pub fn stop(&mut self) {
if let Some(mut process) = self.current_process.take() {
println!("⏹ Stopping current session...");
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = process.kill();
let _ = process.wait();
std::thread::sleep(std::time::Duration::from_millis(200));
}
}
pub fn is_running(&mut self) -> bool {
if let Some(ref mut process) = self.current_process {
matches!(process.try_wait(), Ok(None))
} else {
false
}
}
}
impl Drop for LiveRunner {
fn drop(&mut self) {
self.stop();
}
}