Write patches in Rust!

Well, I suggest familiarizing yourself with the language first to decide if you like it and it might seem a bit alien initially.

Adding all the extension features that the C++ integration has would take a bit longer of course.

There’s not that much extensions. We can consider a minimal implementation something that just connects patch-defined parameters to OWL controls and runs generated DSP code. Then all optional extras would be:

  1. V/Oct conversion using calibration data (exposed as FFI functions in owl.lib for C++ integration, this should be fairly simple if FFI works with their Rust backend)
  2. Optional MIDI control (maps parameters with names like “freq”/“bend”/“gain” to MIDI input)
  3. Soundfile support for loading sample buffers from flash storage - you’ll need an actual WAV parser to be compliant with how FAUST works

We have one of FAUST core devs here, so you can ping Stephane (his username is sletz) if necessary. They also have a Rust specific channel on FAUST discord too.

If you’ll need some patches to experiment, this covers all OWL specific features except loading soundfiles. The latter is still not enabled in Rebeltech library and requires compiling offline to enable it via env var, but I have an example for testing here

Here’s another interesting project: a rust port of MI Plaits. It seems like converting it to an OWL patch should be fairly easy. I’ve tried a few C++ ports from DaisySP in the past and it was tons of fun, but here there are way more opportunities as every engine from Plaits is present.

What I’m not sure if there’s a straightforward way to have an MPE engine like OWL’s C++ template. The closest I’ve found is this, but I’m not sure how usable it would end up outside of Surge project.

Yes! I discovered that Plaits port just yesterday - maybe the same way you did - reading about Tiliqua. I will have a go at using it in a patch soon, should hopefully be fairly straightforward.
The other thing I want to try soon is making a WASM wrapper.
My general feeling is to avoid including too many dsp/synth primitives in the owl_patch crate, it’s purpose is just to do all the basic plumbing required to get up and running with whatever dsp code you want to write or find. However, I’ve not really found a dsp crate that’s as well optimised for the Arm chips as DaisySP and OWL are, so maybe one day I’ll start building one.
Until then, a list of potentially useful external crates would be a good thing to have in the README

Your guess is correct :wink:

The other thing I want to try soon is making a WASM wrapper.

I’m sure it would be possible. On C++ side there’s support for building patches using EMCC which is used for building patches that run in browser for RebelTech web patcher.

I’m not too familiar with its details, but generally its a matter of writing an application that provides program vector with correct callbacks. And in case of Rust we have much better WASM support, effectively you might be able to have a single UI for running patches in browser or natively. I was experimenting with wring a egui based client for OWL (that is all communication was done with MIDI). It’s very incomplete and my Rust wasn’t too good at that time, but it was possible to write a single app that builds WASM and native version.

However, I’ve not really found a dsp crate that’s as well optimised for the Arm chips as DaisySP and OWL are, so maybe one day I’ll start building one.

I would say that there’s not much to optimize for in case of Arm Cortex-M. It’s just necessary to be able to build code that computes in float32 with no allocations when the DSP code itself runs. So generally it’s about writing code in a certain way rather than specifically optimizing.

Things are a bit different when we’re talking about a proper Arm CPU with NEON instructions, that requires block-based processing and in certain cases using SIMD intrinsics expicilty. It’s not relevant for OWL, but actually there were plans for a new version of OWL board that will run on MCU with ARM Helium once ST releases something like that. Now there’s STM32N6 chip, but unfortunately no signs of Martin returning to make new OWL hardware.

Here’s a quick patch using the Plaits port:

Button 2 cycles between the 24 engines, and updates the patch/param names so you can see what you’re doing a bit better.

It works really well… but only if I include a slightly horrible hack to get more stack memory.

This has been a constant pain while writing the Rust SDK - The firmware only allocates 16kb for the audio task’s stack. The Rust compiler likes to avoid heap allocations, and will sometimes ‘optimise’ them away. Even without that, having such a small stack makes it difficult to use 3rd party code which is not written with such a small stack in mind. The Plaits port uses about 64Kb of stack memory during initialisation, even if the whole thing is eventually heap allocated.

I wish there was a better solution, if the firmware was still being developed, more stack memory would be my no.1 wishlist item (I have a few others).

Hmm well by “audio task’s stack” I understand the stack of FreeRTOS task which is just 8kb. But it’s not used by your DSP code itself, only for object that represent patch that gets loaded and whatever OWL need to store for running it.

The patch defines and manages its own stack via its startup file. Then during patch memory initialization you get stack limited by available memory on your device.

The interesting part there is the section guarded by USE_PLUS_RAM flag. It’s enabled only on OWL3. This moves patch code (and stack) to 512 KB D1 section. This behavior is triggered if your linker builds the patch that runs from there

So you should most likely add a way to link patches into D1 section for OWL3 and then you should be able to use larger stack without hacks on OWL3. As a brief summary for logic implemented by OWL1/2/3 patch linker scripts:

  1. OWL1 has 80KB for patch RAM
  2. OWL2 has 144KB patch RAM, but linker limits it to 80KB for backwards compatibility with patches built for OWL1
  3. OWL3 has 80KB for patch RAM, but can be built to use 512 KB in plus RAM mode.

In theory yes you have from the end of your program up to the end of PATCHRAM available as stack memory, but in practice you don’t - because in the patch startup file, the stack pointer is never set. This is true of C++ patches too.
You do get access to this region of memory, but it’s in the form of a heapSegment, so it’s expected you’ll use it as heap, not stack.

I’ve verified this using a debugger - when your patch is first run, the stack pointer is set to 0x3fe0 (which is why I thought it was 16kb, you’re correct here, it’s actually 8kb).
If you compare the startup files for the Firmware - startup_owl*.s They all contain this:

/* normal startup */
  ldr   sp, =_estack     /* set stack pointer */

but this is absent from the patch startup file.

The more I think about it, my ‘hack’ seems like the behaviour you would expect, so I might merge it. I just have to be careful to ignore the matching heapSegment in the PV, so I’m not trying to use the same region as heap!

And btw I am using the right linker file, so my patch is loaded at 0x24000000, and if I manually set the stack pointer with the hack, then even though the Plaits patch comes out at about 324Kb, I still have plenty of stack memory to use.

I reread your hack code, comments and OWL code, so things have cleared up a bit. Unfortunately I don’t have write access to the repo and it’s not being developed. Also, new patches relying on increased stack size would be failing on older FW versions. Of course there’s a legit reasons to want a larger stack size due to third party libraries requiring it, so I guess this hack is as good of a solution as we can get.

The only concern related to it that I have is that overriding SP register can lead to problems if your patch crashes due to an error. It seems like those few lines would end up running with stack set to patch memory instead of audio task’s stack. I’m not sure if that actually is a problem or not (i.e. it might not pop anything it didn’t push from stack since patch exit), in worst case maybe original SP could be restored by the panic handler?

I don’t think those lines could ever be hit by a Rust patch - by contract the main patch function never returns. My panic handler calls pv.programStatus, which notifies the manager task and loops waiting to be deleted.
The manager task has its own stack, so is unaffected by the hack.
The same thing is true of the screen task, you’re still stuck with just 4kB there

This is amazing. You’ve given new life to my little Witch, which I had consigned to the abandonware crate and posted for sale

Just tested some basic patches and looking forward to getting deeper into it.

Awesome! Let me know if you encounter any problems, or have any ideas for improvements