Access Koto AST via Rust API #384
Replies: 4 comments 1 reply
-
Hi @cornedriesprong 👋 Glad you like it!
All good! I converted the issue into a discussion so this can be a bit more open ended.
This is really great to see! I've had in mind to try something like this one day (I was thinking about a scriptable front-end for Some thoughts in no particular order:
|
Beta Was this translation helpful? Give feedback.
-
Thanks for your reply @irh!
I figured I wouldn't need the full language to express the kinds of manipulations I want to express, but then again it wouldn't hurt to have it available either. I guess I'm still torn between using Koto's parser as the basis for my own DSL or embedding the entire language runtime in my audio engine.
This was my initial approach. I guess I'm struggling to see how I can represent an audio graph in Koto as a recursive structure, whilst keeping the syntax as lightweight as in my example above. For instance, the sine generator takes a frequency argument, which can either be a constant value (like 440 Hz) or another generator (for example an LFO). To represent this as a enum Expr {
Operator {
kind: OperatorType,
input: Box<Expr>,
args: Vec<Expr>,
},
Number(f32),
} Another thought I had: the pipe operator is great for chaining audio processors, but I could see myself wanting to split, merge and parallel process signals as well. Seeing that Koto doesn't support operator overloading I'm guessing I'd need to extend the parser to add custom operators for these.
Yeah, fundsp is great! I'm implementing this DSP library mostly as a learning exercise for myself, so I'm just reinventing the wheel over here 🙂. If it goes anywhere I'll make sure to give an update. |
Beta Was this translation helpful? Give feedback.
-
If it helps I think of Koto scripts as being like a UI for your app, so the runtime would likely run in your main thread, and then send messages to the audio engine on updates.
Function call arguments coming from Koto are received in Rust as You could have something like a struct Node {
expr: Expr,
outputs: Vec<Arc<Node>>,
} and then you can override impl KotoObject for Node {
fn call(&mut self, ctx: &mut CallContext) -> Result<KValue> {
match ctx.args {
[KValue::Object(other_object)] if other.is_a::<Node>() => {
// Cast the object to get access to the other `Node`
let other_node = other_object.cast::<Node>?.unwrap();
self.connect(other);
// Return the other node to continue the call chain (or something)
Ok(KValue::Object(other_object))
}
unexpected => return unexpected_args("|Node|", unexpected),
}
}
}
You can't define new operators but you can overload all existing operators, in Koto using metamaps, and in Rust via Good luck! Happy to answer any questions as they come up. |
Beta Was this translation helpful? Give feedback.
-
I ended up having my koto.prelude().add_fn("sine", |ctx| match ctx.args() {
[KValue::Number(hz)] => Ok(KValue::Object(
Expr::Operator {
kind: OperatorType::Sine,
input: Box::new(Expr::Number(hz.into())),
args: vec![],
}
.into(),
)),
[KValue::Object(obj)] if obj.is_a::<Expr>() => Ok(KValue::Object(
Expr::Operator {
kind: OperatorType::Sine,
input: Box::new(obj.cast::<Expr>()?.to_owned()),
args: vec![],
}
.into(),
)),
unexpected => type_error_with_slice("a number", unexpected),
}); I can also define arithmetic operations on nodes like so: impl KotoObject for Expr {
fn multiply(&self, rhs: &KValue) -> koto::Result<KValue> {
match (self, rhs) {
(Self::Number(value), KValue::Number(num)) => {
Ok(KValue::Number((value * f32::from(num)).into()))
}
(Self::Operator { .. }, KValue::Number(num)) => Ok(KValue::Object(
Expr::Operator {
kind: OperatorType::Gain,
input: Box::new(self.clone()),
args: vec![Expr::Number(num.into())],
}
.into(),
)),
_ => panic!("invalid multiply operation"),
}
}
} that way I can can compose them like so (modulating the frequency of a sine oscillator with the output of another 3 hz sine multiplied by 100):
I'll keep working on this, if you have any suggestions based on my approach I'd love to hear them of course @irh . Thanks a lot for your help so far. |
Beta Was this translation helpful? Give feedback.
-
Hi! First, thanks for creating Koto, what a beautifully expressive language. This is not so much an issue as a question/request for advice, hope that's ok. I'm attempting to use (a subset of) Koto as a DSL of sorts to construct audio processing graphs in my Rust DSP library, in the spirit of Faust or Genexpr. The way I ended up doing this is like so:
this lets me write, for example:
In my rust code, this shows up as a nested map
...which I then parse into a recursive data structure (
parse_graph
) that my DSP library can construct an audio graph out of.Now this feels a bit cumbersome and leads me to wonder if there is a way that I can inspect Koto's AST directly from my Rust code? I suppose this would let me use Koto's built in arithmetic operators, for example, instead of defining my own
mul
andadd
functions. I don't see such functionality exposed through the Rust API, so I suppose it's currently not possible, but I'm wondering if it's a use case you've considered, of if maybe there is another way to go about this that I haven't realized.Beta Was this translation helpful? Give feedback.
All reactions