use rack::prelude::*;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
fn CFRunLoopRunInMode(
mode: CFRunLoopMode,
seconds: f64,
return_after_source_handled: bool,
) -> i32;
static kCFRunLoopDefaultMode: CFRunLoopMode;
}
type CFRunLoopMode = *const std::ffi::c_void;
fn process_main_event_loop(duration_ms: u64) {
unsafe {
CFRunLoopRunInMode(
kCFRunLoopDefaultMode,
duration_ms as f64 / 1000.0,
false,
);
}
}
fn main() -> Result<()> {
println!("Rack CPAL Host Example");
println!("======================\n");
let scanner = Scanner::new()?;
let plugins = scanner.scan()?;
println!("Found {} plugins total", plugins.len());
let synth_info = plugins
.iter()
.find(|p| p.plugin_type == PluginType::Instrument)
.ok_or_else(|| {
Error::Other(
"No instrument plugins found. Install a synthesizer AudioUnit to run this example.\n\
macOS includes DLSMusicDevice by default. You can also install free synths like:\n\
- Dexed (DX7 emulator)\n\
- Surge XT\n\
- Vital"
.to_string(),
)
})?;
println!("\n🎹 Using instrument: {}", synth_info.name);
println!(" Manufacturer: {}", synth_info.manufacturer);
let mut plugin = scanner.load(synth_info)?;
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or_else(|| Error::Other("No output device available".to_string()))?;
println!(" Audio device: {}", device.name().unwrap_or_default());
let config = device
.default_output_config()
.map_err(|e| Error::Other(format!("Failed to get default output config: {}", e)))?;
println!(" Sample rate: {} Hz", config.sample_rate().0);
println!(" Channels: {}", config.channels());
println!(" Format: {:?}", config.sample_format());
let sample_rate = config.sample_rate().0 as f64;
let channels = config.channels() as usize;
let buffer_frames = 512;
plugin.initialize(sample_rate, buffer_frames)?;
println!("\n✓ Plugin initialized");
println!(" Sample rate: {:.0} Hz", sample_rate);
println!(" Buffer size: {} frames", buffer_frames);
let plugin = Arc::new(Mutex::new(plugin));
let plugin_clone = plugin.clone();
let left_in = vec![0.0f32; buffer_frames];
let right_in = vec![0.0f32; buffer_frames];
let input = Arc::new((left_in, right_in));
let input_clone = input.clone();
println!("\n🔊 Starting audio stream...");
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => build_stream::<f32>(
&device,
&config.into(),
plugin_clone,
input_clone,
channels,
buffer_frames,
)?,
cpal::SampleFormat::I16 => build_stream::<i16>(
&device,
&config.into(),
plugin_clone,
input_clone,
channels,
buffer_frames,
)?,
cpal::SampleFormat::U16 => build_stream::<u16>(
&device,
&config.into(),
plugin_clone,
input_clone,
channels,
buffer_frames,
)?,
_ => {
return Err(Error::Other(
"Unsupported sample format".to_string(),
))
}
};
stream
.play()
.map_err(|e| Error::Other(format!("Failed to play stream: {}", e)))?;
println!("✓ Audio stream started\n");
let gui_handle: Arc<Mutex<Option<AudioUnitGui>>> = Arc::new(Mutex::new(None));
println!("Controls:");
println!(" A S D F G H J K - Play notes (C D E F G A B C)");
println!(" 1-9 - Select preset");
println!(" G - Open plugin GUI window (may take a moment)");
println!(" L - List available presets");
println!(" Q - Quit\n");
let stdin = io::stdin();
let mut input_line = String::new();
loop {
print!("> ");
io::stdout().flush().unwrap();
input_line.clear();
stdin.read_line(&mut input_line).unwrap();
let input = input_line.trim().to_lowercase();
if input == "q" {
break;
} else if input == "g" {
println!("\n🎨 Opening plugin GUI...");
{
let gui = gui_handle.lock().unwrap();
if gui.is_some() {
println!("GUI is already open!\n");
continue;
}
}
let mut plugin = plugin.lock().unwrap();
let plugin_name = plugin.info().name.clone();
let gui_clone = gui_handle.clone();
plugin.create_gui(move |result| {
match result {
Ok(gui) => {
println!("✓ GUI created successfully!");
if let Ok((width, height)) = gui.get_size() {
println!(" GUI size: {:.0}x{:.0} points", width, height);
}
if let Err(e) = gui.show_window(Some(&plugin_name)) {
eprintln!("Failed to show window: {}", e);
return Err(e);
}
println!("✓ Plugin window is now visible!\n");
print!("> ");
io::stdout().flush().unwrap();
*gui_clone.lock().unwrap() = Some(gui);
}
Err(e) => {
eprintln!("✗ GUI creation failed: {}\n", e);
print!("> ");
io::stdout().flush().unwrap();
return Err(e);
}
}
Ok(())
});
drop(plugin);
println!("GUI creation in progress (processing events)...");
for _ in 0..20 {
process_main_event_loop(100);
let gui = gui_handle.lock().unwrap();
if gui.is_some() {
break;
}
}
} else if input == "l" {
let plugin = plugin.lock().unwrap();
match plugin.preset_count() {
Ok(preset_count) if preset_count > 0 => {
println!("\nPlugin has {} presets", preset_count);
println!("Use 1-9 to select presets\n");
}
_ => {
println!("\nNo presets available for this plugin\n");
}
}
} else if let Some(ch) = input.chars().next() {
if let Some(note) = key_to_note(ch) {
let events = vec![MidiEvent::note_on(note, 100, 0, 0)];
{
let mut plugin = plugin.lock().unwrap();
if let Err(e) = plugin.send_midi(&events) {
println!("Error sending MIDI: {}", e);
} else {
println!("♪ Note {} on", note);
}
}
let plugin_clone = Arc::clone(&plugin);
thread::spawn(move || {
thread::sleep(Duration::from_millis(500));
let mut plugin = plugin_clone.lock().unwrap();
let events = vec![MidiEvent::note_off(note, 64, 0, 0)];
let _ = plugin.send_midi(&events);
});
} else if let Some(preset_num) = ch.to_digit(10) {
if preset_num > 0 {
let mut plugin = plugin.lock().unwrap();
match plugin.load_preset((preset_num - 1) as i32) {
Ok(_) => println!("✓ Loaded preset {}", preset_num),
Err(e) => println!("Error loading preset: {}", e),
}
}
}
}
}
println!("\n👋 Shutting down...");
drop(stream);
println!("✓ Done!");
Ok(())
}
fn build_stream<T>(
device: &cpal::Device,
config: &cpal::StreamConfig,
plugin: Arc<Mutex<Plugin>>,
input: Arc<(Vec<f32>, Vec<f32>)>,
channels: usize,
buffer_frames: usize,
) -> Result<cpal::Stream>
where
T: cpal::Sample + cpal::SizedSample + cpal::FromSample<f32>,
{
let mut left_out = vec![0.0f32; buffer_frames];
let mut right_out = vec![0.0f32; buffer_frames];
let stream = device
.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
let mut plugin = plugin.lock().unwrap();
if let Err(e) = plugin.process(
&[&input.0, &input.1],
&mut [&mut left_out, &mut right_out],
buffer_frames
) {
eprintln!("Error processing audio: {}", e);
return;
}
let frames = data.len() / channels;
for i in 0..frames.min(buffer_frames) {
let left = left_out[i];
let right = right_out[i];
if channels == 2 {
data[i * 2] = cpal::Sample::from_sample(left);
data[i * 2 + 1] = cpal::Sample::from_sample(right);
} else if channels == 1 {
let mono = (left + right) * 0.5;
data[i] = cpal::Sample::from_sample(mono);
} else {
for ch in 0..channels {
let sample = if ch % 2 == 0 { left } else { right };
data[i * channels + ch] = cpal::Sample::from_sample(sample);
}
}
}
},
move |err| {
eprintln!("Stream error: {}", err);
},
None,
)
.map_err(|e| Error::Other(format!("Failed to build output stream: {}", e)))?;
Ok(stream)
}
fn key_to_note(key: char) -> Option<u8> {
match key {
'a' => Some(60), 's' => Some(62), 'd' => Some(64), 'f' => Some(65), 'g' => Some(67), 'h' => Some(69), 'j' => Some(71), 'k' => Some(72), _ => None,
}
}