lilv 0.2.1

Library for discovering and running LV2 plugins.
Documentation
use lilv_sys as lib;
use lv2_raw::core::LV2Descriptor;
use lv2_raw::core::LV2Handle;
use std::convert::TryFrom;
use std::ffi::CStr;
use std::ptr::NonNull;

/// An LV2 plugin instance.
#[allow(clippy::module_name_repetitions)]
pub struct Instance {
    pub(crate) inner: NonNull<lib::LilvInstanceImpl>,
}

/// An LV2 plugin instance that has been activated and is ready to process data.
#[allow(clippy::module_name_repetitions)]
pub struct ActiveInstance {
    pub(crate) inner: Instance,
}

unsafe impl Send for Instance {}

impl Instance {
    /// Returns the URI of the plugin for the instance.
    /// This is a globally unique string for the plugin.
    #[must_use]
    pub fn uri(&self) -> Option<&str> {
        unsafe {
            CStr::from_ptr(lib::lilv_instance_get_uri(self.inner.as_ptr()))
                .to_str()
                .ok()
        }
    }

    /// Connect a port on a plugin instance to a memory location.
    ///
    /// Plugin writers should be aware that the host may elect to use the same
    /// buffer for more than one port and even use the same buffer for both
    /// input and output (see lv2:inPlaceBroken in lv2.ttl).
    ///
    /// If the plugin has the feature lv2:hardRTCapable then there are various
    /// things that the plugin MUST NOT do within the `connect_port()` function;
    /// see lv2core.ttl for details.
    ///
    /// `connect_port()` MUST be called at least once for each port before
    /// `run()` is called, unless that port is lv2:connectionOptional. The
    /// plugin must pay careful attention to the block size passed to run()
    /// since the block allocated may only just be large enough to contain the
    /// data, and is not guaranteed to remain constant between run() calls.
    ///
    /// `connect_port()` may be called more than once for a plugin instance to
    /// allow the host to change the buffers that the plugin is reading or
    /// writing.
    ///
    /// The host MUST NOT try to connect a `port_index` that is not defined in
    /// the plugin's RDF data. If it does, the plugin's behaviour is undefined
    /// (a crash is likely).
    ///
    /// `data` should point to data of the type defined by the port type in the
    /// plugin's RDF data (e.g. an array of float for an lv2:AudioPort). This
    /// pointer must be stored by the plugin instance and used to read/write
    /// data when run() is called. Data present at the time of the
    /// `connect_port()` call MUST NOT be considered meaningful.
    ///
    /// # Safety
    /// Connecting a port calls a plugin's code, which itself may be unsafe.
    pub unsafe fn connect_port_mut<T>(&mut self, port_index: usize, data: *mut T) {
        match u32::try_from(port_index) {
            Ok(port_index) => {
                lib::lilv_instance_connect_port(self.inner.as_ptr(), port_index, data.cast())
            }
            Err(e) => debug_assert!(false, "port_index is too large: {}", e),
        }
    }

    /// Connect data pointer to a port on a plugin instance. Similar to
    /// `connect_port_mut` but takes a const pointer instead.
    ///
    /// # Note
    /// Although this takes a const pointer, it is not guaranteed that the
    /// plugin wants to treat the data as const. This method exists for
    /// convinience, but developers should still make sure double check that the
    /// port is an input and not an output port.
    ///
    /// # Safety
    /// Connecting a port calls a plugin's code, which itself may be unsafe.
    pub unsafe fn connect_port<T>(&mut self, port_index: usize, data: *const T) {
        self.connect_port_mut(port_index, data as *mut T);
    }

    /// Activate a plugin instance.
    ///
    /// This resets all state information in the plugin except for port
    /// connections.
    ///
    /// # Safety
    /// Calling external code may be unsafe.
    #[must_use]
    pub unsafe fn activate(self) -> ActiveInstance {
        lib::lilv_instance_activate(self.inner.as_ptr());
        ActiveInstance { inner: self }
    }

    /// Get the extension data for a plugin instance.
    ///
    /// The type and semantics of the data returned is specific to the
    /// particular extension, though in all cases it is shared and must not be
    /// deleted.
    ///
    /// # Safety
    /// Gathering extension data call's a plugins code, which itself may be unsafe.
    #[must_use]
    pub unsafe fn extension_data<T>(&self, uri: &str) -> Option<NonNull<T>> {
        let uri = std::ffi::CString::new(uri).ok()?;
        NonNull::new(
            lib::lilv_instance_get_extension_data(self.inner.as_ptr(), uri.as_ptr().cast()) as _,
        )
    }

    /// Get the raw descriptor for the plugin.
    #[must_use]
    pub fn descriptor(&self) -> Option<&LV2Descriptor> {
        let d = unsafe { lib::lilv_instance_get_descriptor(self.inner.as_ptr()) };
        unsafe { d.as_ref() }
    }

    /// Get the raw handle for the plugin instance.
    #[must_use]
    pub fn handle(&self) -> LV2Handle {
        unsafe { lib::lilv_instance_get_handle(self.inner.as_ptr()) }
    }
}

impl Drop for Instance {
    fn drop(&mut self) {
        unsafe { lib::lilv_instance_free(self.inner.as_ptr().cast()) };
    }
}

impl ActiveInstance {
    /// Run the plugin instance for `sample_count` frames.
    ///
    /// # Safety
    /// Calling external code may be unsafe.
    #[allow(clippy::cast_possible_truncation)]
    pub unsafe fn run(&mut self, sample_count: usize) {
        let sample_count = match u32::try_from(sample_count) {
            Ok(sample_count) => sample_count,
            Err(_) => u32::MAX,
        };
        lib::lilv_instance_run(self.instance().inner.as_ptr(), sample_count);
    }

    /// Deactivate the plugin instance.
    ///
    /// Note: This will reset all state information except for port connections.
    ///
    /// # Safety
    /// Calling external code may be unsafe.
    #[must_use]
    pub unsafe fn deactivate(self) -> Option<Instance> {
        let mut active_instance = self;
        let instance = active_instance
            .deactive_impl()
            .map(|i| Instance { inner: i })?;
        // Prevent running deactivate twice since we manually called the drop
        // side-effects with `deactivate_impl`..
        std::mem::forget(active_instance);
        Some(instance)
    }

    /// Get the underlying instance.
    #[must_use]
    pub fn instance(&self) -> &Instance {
        &self.inner
    }

    /// Get the underlying instance.
    ///
    /// This is useful to call `connect_port` if the data locations have changed.
    pub fn instance_mut(&mut self) -> &mut Instance {
        &mut self.inner
    }

    fn deactive_impl(&mut self) -> Option<NonNull<lib::LilvInstanceImpl>> {
        let deactivate_fn = unsafe { (*self.inner.inner.as_ref().lv2_descriptor).deactivate }?;
        unsafe { deactivate_fn(self.inner.inner.as_ref().lv2_handle) };
        Some(self.inner.inner)
    }
}

impl Drop for ActiveInstance {
    fn drop(&mut self) {
        self.deactive_impl();
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_can_run_plugin() {
        let world = crate::World::with_load_all();
        // This is the only plugin that doesn't require a feature.
        // Most require at least URID Map.
        let uri = world.new_uri("http://lv2plug.in/plugins/eg-amp");
        let plugin = world
            .plugins()
            .plugin(&uri)
            .unwrap_or_else(|| panic!("Could not find plugin {:?}", uri));
        let uri = plugin.uri().as_uri().unwrap_or("").to_string();
        let mut instance = unsafe {
            plugin.instantiate(44100.0, []).unwrap_or_else(|| {
                panic!(
                    "failed to instantiate {} which has required features {:?}",
                    uri,
                    plugin.required_features()
                )
            })
        };
        // The plugin instance needs a pointer to data to read and write
        // from.
        let mut port_values: Vec<f32> = plugin
            .iter_ports()
            .map(|p| {
                p.range()
                    .default
                    .map_or(0.0, |n| n.as_float().unwrap_or(0.0))
            })
            .collect();
        for (index, value) in port_values.iter_mut().enumerate() {
            unsafe { instance.connect_port(index, value) };
        }
        let mut active_instance = unsafe { instance.activate() };
        unsafe {
            active_instance.run(1);
        }
    }
}