use crate::{
audio::AudioBuffers,
error::{Error, Result},
midi::MidiEvent,
parameters::Parameter,
plugin::{PluginInfo, PluginInternal},
process_isolation::{HostCommand, HostResponse, PluginHostProcess},
};
use std::sync::Mutex;
pub struct IsolatedPluginImpl {
process: Mutex<PluginHostProcess>,
info: PluginInfo,
sample_rate: f64,
block_size: usize,
is_processing: bool,
has_open_editor: bool,
output_channels: usize,
output_midi: Mutex<Vec<MidiEvent>>,
}
const MAX_OUTPUT_MIDI: usize = 4096;
impl IsolatedPluginImpl {
pub fn new(
process: PluginHostProcess,
info: PluginInfo,
sample_rate: f64,
block_size: usize,
output_channels: usize,
) -> Self {
Self {
process: Mutex::new(process),
info,
sample_rate,
block_size,
is_processing: false,
has_open_editor: false,
output_channels,
output_midi: Mutex::new(Vec::new()),
}
}
fn send_command(&self, command: HostCommand) -> Result<HostResponse> {
let mut process = self
.process
.lock()
.map_err(|e| Error::Other(format!("Failed to lock process: {}", e)))?;
process
.send_command(command)
.map_err(|e| classify_ipc_error(&e))
}
}
fn classify_ipc_error(message: &str) -> Error {
let lo = message.to_lowercase();
if lo.contains("timed out") {
Error::PluginTimeout
} else if lo.contains("crash")
|| lo.contains("no longer running")
|| lo.contains("gone")
|| lo.contains("exited")
|| lo.contains("not running")
{
Error::PluginCrashed
} else {
Error::Other(format!("IPC error: {message}"))
}
}
impl IsolatedPluginImpl {
fn expect_success(&self, command: HostCommand, what: &str) -> Result<()> {
match self.send_command(command)? {
HostResponse::Success { .. } => Ok(()),
HostResponse::Error { message } => Err(Error::Other(format!("{what}: {message}"))),
_ => Err(Error::Other(format!("{what}: unexpected response"))),
}
}
}
impl PluginInternal for IsolatedPluginImpl {
fn set_parameter(&mut self, id: u32, value: f64) -> Result<()> {
self.expect_success(HostCommand::SetParameter { id, value }, "SetParameter")
}
fn get_parameter(&self, id: u32) -> Result<f64> {
match self.send_command(HostCommand::GetParameter { id })? {
HostResponse::ParameterValue { value } => Ok(value),
HostResponse::Error { message } => {
Err(Error::Other(format!("GetParameter: {message}")))
}
_ => Err(Error::Other(
"GetParameter: unexpected response".to_string(),
)),
}
}
fn get_all_parameters(&self) -> Result<Vec<Parameter>> {
match self.send_command(HostCommand::GetAllParameters)? {
HostResponse::Parameters { params } => Ok(params),
HostResponse::Error { message } => {
Err(Error::Other(format!("GetAllParameters: {message}")))
}
_ => Err(Error::Other(
"GetAllParameters: unexpected response".to_string(),
)),
}
}
fn format_parameter(&self, id: u32, normalized: f64) -> Result<String> {
match self.send_command(HostCommand::FormatParameter { id, normalized })? {
HostResponse::ParameterString { value } => Ok(value),
HostResponse::Error { message } => {
Err(Error::Other(format!("FormatParameter: {message}")))
}
_ => Err(Error::Other(
"FormatParameter: unexpected response".to_string(),
)),
}
}
fn process(&mut self, buffers: &mut AudioBuffers) -> Result<()> {
let frames = buffers
.outputs
.first()
.map(|c| c.len())
.unwrap_or(self.block_size);
let response = self.send_command(HostCommand::Process {
inputs: buffers.inputs.clone(),
frames: frames as u32,
})?;
match response {
HostResponse::AudioOutput {
outputs,
output_midi,
} => {
for (ch_idx, output_channel) in buffers.outputs.iter_mut().enumerate() {
if let Some(src) = outputs.get(ch_idx) {
let n = output_channel.len().min(src.len());
output_channel[..n].copy_from_slice(&src[..n]);
for s in &mut output_channel[n..] {
*s = 0.0;
}
} else {
output_channel.fill(0.0);
}
}
if !output_midi.is_empty() {
if let Ok(mut buf) = self.output_midi.lock() {
buf.extend(output_midi);
if buf.len() > MAX_OUTPUT_MIDI {
let drop = buf.len() - MAX_OUTPUT_MIDI;
buf.drain(0..drop);
}
}
}
Ok(())
}
HostResponse::Error { message } => {
Err(Error::ProcessError(format!("Process error: {}", message)))
}
_ => Err(Error::Other(
"Unexpected response from process command".to_string(),
)),
}
}
fn send_midi_event(&mut self, event: MidiEvent) -> Result<()> {
self.expect_success(HostCommand::SendMidi { event }, "SendMidi")
}
fn start_processing(&mut self) -> Result<()> {
self.expect_success(HostCommand::StartProcessing, "StartProcessing")?;
self.is_processing = true;
Ok(())
}
fn stop_processing(&mut self) -> Result<()> {
self.expect_success(HostCommand::StopProcessing, "StopProcessing")?;
self.is_processing = false;
Ok(())
}
fn has_editor(&self) -> bool {
self.info.has_gui
}
fn open_editor(&mut self, _parent: *mut std::ffi::c_void) -> Result<()> {
let response = self.send_command(HostCommand::CreateGui)?;
match response {
HostResponse::Success { .. } => {
self.has_open_editor = true;
Ok(())
}
HostResponse::Error { message } => {
Err(Error::Other(format!("Failed to open editor: {}", message)))
}
_ => Err(Error::Other(
"Unexpected response from CreateGui command".to_string(),
)),
}
}
fn close_editor(&mut self) -> Result<()> {
if !self.has_open_editor {
return Ok(());
}
let response = self.send_command(HostCommand::CloseGui)?;
match response {
HostResponse::Success { .. } => {
self.has_open_editor = false;
Ok(())
}
HostResponse::Error { message } => {
Err(Error::Other(format!("Failed to close editor: {}", message)))
}
_ => Err(Error::Other(
"Unexpected response from CloseGui command".to_string(),
)),
}
}
fn get_editor_size(&self) -> Result<(i32, i32)> {
Ok((800, 600))
}
fn get_parameter_changes(&self) -> Vec<(u32, f64)> {
Vec::new()
}
fn save_state(&self) -> Result<Vec<u8>> {
match self.send_command(HostCommand::SaveState)? {
HostResponse::State { data } => Ok(data),
HostResponse::Error { message } => Err(Error::Other(format!("SaveState: {message}"))),
_ => Err(Error::Other("SaveState: unexpected response".to_string())),
}
}
fn load_state(&mut self, data: &[u8]) -> Result<()> {
self.expect_success(
HostCommand::LoadState {
data: data.to_vec(),
},
"LoadState",
)
}
fn take_output_events(&self) -> Vec<MidiEvent> {
self.output_midi
.lock()
.map(|mut o| std::mem::take(&mut *o))
.unwrap_or_default()
}
fn output_channel_count(&self) -> usize {
self.output_channels
}
fn helper_pid(&self) -> Option<u32> {
self.process.lock().ok().and_then(|p| p.helper_pid())
}
fn recover(&mut self) -> Result<()> {
let mut process = self
.process
.lock()
.map_err(|e| Error::Other(format!("Failed to lock process: {}", e)))?;
let mut fresh = PluginHostProcess::new()
.map_err(|e| Error::ProcessError(format!("Failed to respawn helper: {e}")))?;
match fresh.send_command(HostCommand::LoadPlugin {
path: self.info.path.display().to_string(),
sample_rate: self.sample_rate,
block_size: self.block_size as u32,
}) {
Ok(HostResponse::PluginInfo { .. }) => {}
Ok(HostResponse::Error { message }) => {
return Err(Error::PluginLoadFailed(format!("reload failed: {message}")))
}
Ok(_) => return Err(Error::Other("unexpected response while reloading".into())),
Err(e) => return Err(classify_ipc_error(&e)),
}
if self.is_processing {
let _ = fresh.send_command(HostCommand::StartProcessing);
}
*process = fresh;
Ok(())
}
}
unsafe impl Send for IsolatedPluginImpl {}