Skip to content

Commit

Permalink
Remaining fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
prokopyl committed Apr 4, 2024
1 parent 33a3e2d commit 9c5c9e4
Show file tree
Hide file tree
Showing 34 changed files with 279 additions and 289 deletions.
263 changes: 135 additions & 128 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<div align="center"><img src="./logo.svg" width="128px" height="128px" alt="Clack logo"/><h1>Clack</h1></div>

A set of crates offering low-level, safe Rust wrappers to create audio plugins and hosts using the [CLAP](https://github.com/free-audio/clap) audio plugin API.
A set of crates offering low-level, safe Rust wrappers to create audio plugins and hosts using
the [CLAP](https://github.com/free-audio/clap) audio plugin API.

This library is made of lightweight, low-level wrappers built on top of [`clap-sys`](https://github.com/prokopyl/clap-sys),
and is split in two main crates: `clack-plugin`, which allows to implement CLAP plugins, and `clack-host`, which allows to implement
This library is made of lightweight, low-level wrappers built on top
of [`clap-sys`](https://github.com/prokopyl/clap-sys),
and is split in two main crates: `clack-plugin`, which allows to implement CLAP plugins, and `clack-host`, which allows
to implement
CLAP hosts. A common, separate `clack-extensions` crate implements all the standard and stable CLAP extensions.

## Who is this crate for?
Expand Down Expand Up @@ -34,7 +37,7 @@ documentation.
type system when making large refactorings or performance optimizations, especially when dealing with CLAP's
powerful, multithreaded model.
* **Low-level**: When safe designs permit it, Clack aims to be as close as possible to the underlying
[CLAP](https://github.com/free-audio/clap) C API. Whenever it is safe to do so, it does not make any assumptions or
[CLAP](https://github.com/free-audio/clap) C API. Whenever it is safe to do so, it does not make any assumptions or
interpretations about any operation, and simply passes data through to the user.

Anything that's possible to do with the CLAP C API, should also be possible to do using only safe Rust and Clack.
Expand All @@ -45,17 +48,19 @@ documentation.
add, or extend extensions, event types, factories, and more. As a matter of fact, all extensions in the
`clack-extensions` crate are implemented using tools all Clack users have access to, and users may add or replace
the provided implementations with their own!
* **Reasonably defensive**: While Clack performs very little extra sanity checks to keep its overhead as low as possible,
it will report any erroneous behavior it stumbles upon to user code if possible, or just log it and fall back to
* **Reasonably defensive**: While Clack performs very little extra sanity checks to keep its overhead as low as
possible,
it will report any erroneous behavior it stumbles upon to user code if possible, or just log it and fall back to
the safest option otherwise (e.g. in case of panics originating from user code).
* **Fairly ergonomic**: Despite Clack being a low-level, fast API, it also tries to provide some additional ergonomic APIs
* **Fairly ergonomic**: Despite Clack being a low-level, fast API, it also tries to provide some additional ergonomic
APIs
when wrapping the C APIs, as long as they don't have any impact on performance or low-level capability. Examples
include using Rust's `Option`, `Result`, allowing the use of iterators, implementing standard traits for e.g. I/O,
`From` implementations for buffer types, etc.

### Development status

Clack is now in a feature-complete status, however it is still under active development and constant
Clack is now in a feature-complete status, however it is still under active development and constant
refinement, therefore APIs can still have breaking changes made to them in the future. Documentation and examples,
while present, are also incomplete and still being actively worked on.

Expand All @@ -74,61 +79,61 @@ use clack_plugin::prelude::*;
pub struct MyGainPlugin;

impl Plugin for MyGainPlugin {
type AudioProcessor<'a> = MyGainPluginAudioProcessor;
type AudioProcessor<'a> = MyGainPluginAudioProcessor;

type Shared<'a> = ();
type MainThread<'a> = ();
type Shared<'a> = ();
type MainThread<'a> = ();
}

impl DefaultPluginFactory for MyGainPlugin {
fn get_descriptor() -> PluginDescriptor {
PluginDescriptor::new("org.rust-audio.clack.gain", "Clack Gain Example")
}
fn get_descriptor() -> PluginDescriptor {
PluginDescriptor::new("org.rust-audio.clack.gain", "Clack Gain Example")
}

fn new_shared(_host: HostHandle) -> Result<Self::Shared<'_>, PluginError> {
Ok(())
}
fn new_shared(_host: HostHandle) -> Result<Self::Shared<'_>, PluginError> {
Ok(())
}

fn new_main_thread<'a>(
_host: HostMainThreadHandle<'a>,
_shared: &'a Self::Shared<'a>,
) -> Result<Self::MainThread<'a>, PluginError> {
Ok(())
}
fn new_main_thread<'a>(
_host: HostMainThreadHandle<'a>,
_shared: &'a Self::Shared<'a>,
) -> Result<Self::MainThread<'a>, PluginError> {
Ok(())
}
}

pub struct MyGainPluginAudioProcessor;

impl<'a> PluginAudioProcessor<'a, (), ()> for MyGainPluginAudioProcessor {
fn activate(_host: HostAudioThreadHandle<'a>, _main_thread: &mut (), _shared: &'a (), _audio_config: AudioConfiguration) -> Result<Self, PluginError> {
Ok(Self)
}

fn process(&mut self, _process: Process, mut audio: Audio, _events: Events) -> Result<ProcessStatus, PluginError> {
for mut port_pair in &mut audio {
// For this example, we'll only care about 32-bit sample data.
let Some(channel_pairs) = port_pair.channels()?.into_f32() else { continue };

for channel_pair in channel_pairs {
match channel_pair {
ChannelPair::InputOnly(_) => {}
ChannelPair::OutputOnly(buf) => buf.fill(0.0),
ChannelPair::InputOutput(input, output) => {
for (input, output) in input.iter().zip(output) {
*output = input * 2.0
}
}
ChannelPair::InPlace(buf) => {
for sample in buf {
*sample *= 2.0
}
}
}
fn activate(_host: HostAudioThreadHandle<'a>, _main_thread: &mut (), _shared: &'a (), _audio_config: AudioConfiguration) -> Result<Self, PluginError> {
Ok(Self)
}

fn process(&mut self, _process: Process, mut audio: Audio, _events: Events) -> Result<ProcessStatus, PluginError> {
for mut port_pair in &mut audio {
// For this example, we'll only care about 32-bit sample data.
let Some(channel_pairs) = port_pair.channels()?.into_f32() else { continue; };

for channel_pair in channel_pairs {
match channel_pair {
ChannelPair::InputOnly(_) => {}
ChannelPair::OutputOnly(buf) => buf.fill(0.0),
ChannelPair::InputOutput(input, output) => {
for (input, output) in input.iter().zip(output) {
*output = input * 2.0
}
}
ChannelPair::InPlace(buf) => {
for sample in buf {
*sample *= 2.0
}
}
}

Ok(ProcessStatus::Continue)
}
}

Ok(ProcessStatus::Continue)
}
}

clack_export_entry!(SinglePluginEntry<MyGainPlugin>);
Expand All @@ -153,98 +158,100 @@ use clack_host::prelude::*;
struct MyHostShared;

impl<'a> HostShared<'a> for MyHostShared {
/* ... */
fn request_restart(&self) { /* ... */ }
fn request_process(&self) { /* ... */ }
fn request_callback(&self) { /* ... */ }
/* ... */
fn request_restart(&self) { /* ... */ }
fn request_process(&self) { /* ... */ }
fn request_callback(&self) { /* ... */ }
}

struct MyHost;

impl Host for MyHost {
type Shared<'a> = MyHostShared;
type Shared<'a> = MyHostShared;

type MainThread<'a> = ();
type AudioProcessor<'a> = ();
type MainThread<'a> = ();
type AudioProcessor<'a> = ();
}

pub fn load_and_process() -> Result<(), Box<dyn std::error::Error>> {
// Information about our totally legit host.
let host_info = HostInfo::new("Legit Studio", "Legit Ltd.", "https://example.com", "4.3.2")?;

let bundle = PluginBundle::load("/home/user/.clap/u-he/libdiva.so")?;
let plugin_factory = bundle.get_plugin_factory().unwrap();

let plugin_descriptor = plugin_factory.plugin_descriptors()
.find(|d| d.id().unwrap().to_bytes() == b"com.u-he.diva")
.unwrap();

let mut plugin_instance = PluginInstance::<MyHost>::new(
|_| MyHostShared,
|_| (),
&bundle,
plugin_descriptor.id().unwrap(),
&host_info
)?;

let audio_configuration = PluginAudioConfiguration {
sample_rate: 48_000.0,
frames_count_range: 4..=4
};
let audio_processor = plugin_instance.activate(|_, _, _| (), audio_configuration)?;

let note_on_event = NoteOnEvent(NoteEvent::new(EventHeader::new(0), 60, 0, 12, 0, 4.2));
let input_events_buffer = [note_on_event];
let mut output_events_buffer = EventBuffer::new();

let mut input_audio_buffers = [[0.0f32; 4]; 2]; // 2 channels (stereo), 1 port
let mut output_audio_buffers = [[0.0f32; 4]; 2];

let mut input_ports = AudioPorts::with_capacity(2, 1); // 2 channels (stereo), 1 port
let mut output_ports = AudioPorts::with_capacity(2, 1);

// Let's send the audio processor to a dedicated audio processing thread.
let audio_processor = std::thread::scope(|s| s.spawn(|| {
let mut audio_processor = audio_processor.start_processing().unwrap();

let input_events = InputEvents::from_buffer(&input_events_buffer);
let mut output_events = OutputEvents::from_buffer(&mut output_events_buffer);

let mut input_audio = input_ports.with_input_buffers([AudioPortBuffer {
latency: 0,
channels: AudioPortBufferType::f32_input_only(
input_audio_buffers.iter_mut().map(|b| InputChannel::constant(b))
)
}]);

let mut output_audio = output_ports.with_output_buffers([AudioPortBuffer {
latency: 0,
channels: AudioPortBufferType::f32_output_only(
output_audio_buffers.iter_mut().map(|b| b.as_mut_slice())
)
}]);

// Finally do the processing itself.
let status = audio_processor.process(
&input_audio,
&mut output_audio,
&input_events,
&mut output_events,
None,
None
).unwrap();

// Send the audio processor back to be deallocated by the main thread.
audio_processor.stop_processing()
}).join().unwrap());

plugin_instance.deactivate(audio_processor);
Ok(())
// Information about our totally legit host.
let host_info = HostInfo::new("Legit Studio", "Legit Ltd.", "https://example.com", "4.3.2")?;

let bundle = PluginBundle::load("/home/user/.clap/u-he/libdiva.so")?;
let plugin_factory = bundle.get_plugin_factory().unwrap();

let plugin_descriptor = plugin_factory.plugin_descriptors()
.find(|d| d.id().unwrap().to_bytes() == b"com.u-he.diva")
.unwrap();

let mut plugin_instance = PluginInstance::<MyHost>::new(
|_| MyHostShared,
|_| (),
&bundle,
plugin_descriptor.id().unwrap(),
&host_info
)?;

let audio_configuration = PluginAudioConfiguration {
sample_rate: 48_000.0,
frames_count_range: 4..=4
};
let audio_processor = plugin_instance.activate(|_, _| (), audio_configuration)?;

let note_on_event = NoteOnEvent(NoteEvent::new(EventHeader::new(0), 60, 0, 12, 0, 4.2));
let input_events_buffer = [note_on_event];
let mut output_events_buffer = EventBuffer::new();

let mut input_audio_buffers = [[0.0f32; 4]; 2]; // 2 channels (stereo), 1 port
let mut output_audio_buffers = [[0.0f32; 4]; 2];

let mut input_ports = AudioPorts::with_capacity(2, 1); // 2 channels (stereo), 1 port
let mut output_ports = AudioPorts::with_capacity(2, 1);

// Let's send the audio processor to a dedicated audio processing thread.
let audio_processor = std::thread::scope(|s| s.spawn(|| {
let mut audio_processor = audio_processor.start_processing().unwrap();

let input_events = InputEvents::from_buffer(&input_events_buffer);
let mut output_events = OutputEvents::from_buffer(&mut output_events_buffer);

let mut input_audio = input_ports.with_input_buffers([AudioPortBuffer {
latency: 0,
channels: AudioPortBufferType::f32_input_only(
input_audio_buffers.iter_mut().map(|b| InputChannel::constant(b))
)
}]);

let mut output_audio = output_ports.with_output_buffers([AudioPortBuffer {
latency: 0,
channels: AudioPortBufferType::f32_output_only(
output_audio_buffers.iter_mut().map(|b| b.as_mut_slice())
)
}]);

// Finally do the processing itself.
let status = audio_processor.process(
&input_audio,
&mut output_audio,
&input_events,
&mut output_events,
None,
None
).unwrap();

// Send the audio processor back to be deallocated by the main thread.
audio_processor.stop_processing()
}).join().unwrap());

plugin_instance.deactivate(audio_processor);
Ok(())
}
```

## License
`clack` is distributed under the terms of both the [MIT license](LICENSE-MIT) and the [Apache license, version 2.0](LICENSE-APACHE).

`clack` is distributed under the terms of both the [MIT license](LICENSE-MIT) and
the [Apache license, version 2.0](LICENSE-APACHE).
Contributions are accepted under the same terms.

### Special Thanks
Expand Down
Loading

0 comments on commit 9c5c9e4

Please sign in to comment.