diff --git a/Cargo.toml b/Cargo.toml index d9fdf6cc..a5ecbc17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ include = [ ] rust-version = "1.71" +[workspace] +members = ["python"] + [dependencies] arc-swap = "1.6" arrayvec = "0.7" diff --git a/python/Cargo.toml b/python/Cargo.toml index a7364b78..57ec3eb6 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [lib] name = "web_audio_api" crate-type = ["cdylib"] +doc = false [dependencies] pyo3 = "0.21.1" diff --git a/python/README.md b/python/README.md new file mode 100644 index 00000000..1485e747 --- /dev/null +++ b/python/README.md @@ -0,0 +1,25 @@ +# Python bindings for web-audio-api-rs + +## Local development + +```bash +# cd to this directory + +# if not already, create a virtual env +python3 -m venv .env + +# enter the virtual env +source .env/bin/activate + +# (re)build the package +maturin develop +``` + +```python +import web_audio_api +ctx = web_audio_api.AudioContext() +osc = web_audio_api.OscillatorNode(ctx) +osc.connect(ctx.destination()) +osc.start() +osc.frequency().set_value(300) +``` diff --git a/python/src/lib.rs b/python/src/lib.rs index b9322a45..cb16ca7c 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,22 +1,95 @@ use pyo3::prelude::*; +use std::sync::{Arc, Mutex}; -use web_audio_api_rs::context::{AudioContext, BaseAudioContext}; -use web_audio_api_rs::node::{AudioNode, AudioScheduledSourceNode}; +use web_audio_api_rs::context::BaseAudioContext; +use web_audio_api_rs::node::{AudioNode as RsAudioNode, AudioScheduledSourceNode as _}; -/// Formats the sum of two numbers as string. -#[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult { - let ctx = AudioContext::default(); - let mut osc = ctx.create_oscillator(); - osc.connect(&ctx.destination()); - osc.start(); +#[pyclass] +struct AudioContext(web_audio_api_rs::context::AudioContext); - Ok((a + b).to_string()) +#[pymethods] +impl AudioContext { + #[new] + fn new() -> Self { + Self(Default::default()) + } + + fn destination(&self) -> AudioNode { + let dest = self.0.destination(); + let node = Arc::new(Mutex::new(dest)) as Arc>; + AudioNode(node) + } +} + +#[pyclass(subclass)] +struct AudioNode(Arc>); + +#[pymethods] +impl AudioNode { + fn connect(&self, other: &Self) { + self.0.lock().unwrap().connect(&*other.0.lock().unwrap()); + } + fn disconnect(&self, other: &Self) { + self.0 + .lock() + .unwrap() + .disconnect_dest(&*other.0.lock().unwrap()); + } +} + +#[pyclass] +struct AudioParam(web_audio_api_rs::AudioParam); + +#[pymethods] +impl AudioParam { + fn value(&self) -> f32 { + self.0.value() + } + + fn set_value(&self, value: f32) -> Self { + Self(self.0.set_value(value).clone()) + } +} + +#[pyclass(extends = AudioNode)] +struct OscillatorNode(Arc>); + +#[pymethods] +impl OscillatorNode { + #[new] + fn new(ctx: &AudioContext) -> (Self, AudioNode) { + let osc = ctx.0.create_oscillator(); + let node = Arc::new(Mutex::new(osc)); + let audio_node = Arc::clone(&node) as Arc>; + (OscillatorNode(node), AudioNode(audio_node)) + } + + #[pyo3(signature = (when=0.0))] + fn start(&mut self, when: f64) { + self.0.lock().unwrap().start_at(when) + } + + #[pyo3(signature = (when=0.0))] + fn stop(&mut self, when: f64) { + self.0.lock().unwrap().stop_at(when) + } + + fn frequency(&self) -> AudioParam { + AudioParam(self.0.lock().unwrap().frequency().clone()) + } + + fn detune(&self) -> AudioParam { + AudioParam(self.0.lock().unwrap().detune().clone()) + } } /// A Python module implemented in Rust. #[pymodule] fn web_audio_api(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) }