Patchwerk

What is it?

Patchwerk is a portable ANSI C library for constructing audio graphs and DSP signal chains. It designed to be portable, fast, and memory efficient. The core Patchwerk is written using the CWEB literate programming system.

In addition to the core patchwerk API, there is also an actively maintained Runt interface with a built-in collection of DSP nodes from Soundpipe. The goal this Runt interface is to provide a similar feeling and faster alternative to Sporth, a stack-based audio language previously created by the author.

Why was it created?

Patchwerk was built to be a better engine than Sporth. In most ways, this is true. It is much easier to introduce new modules than in Sporth. Patchwerk can often perform much better than the Sporth system as well.

Where does one get this?

Patchwerk is privately maintained as a Fossil repo, but a read-only git export can be found on sourcehut

How does it work?

The gist

Patchwerk produces audio graphs known as patches.

Patches are made up of interconnected things called nodes.

Nodes connect to one another with the use of cables.

All of this is managed using the patchwerk API.

Computation

Patchwerk computes audio one block at a time. A block size of 64 samples is used by default.

Nodes are executed sequentially in the order they are created. Since nodes can modulate other nodes, nodes can only make a connection if the node they are connecting to was created after itself. This simple rule solves the issue of dependency.

Cables and block memory

But wait! Things get a bit trickier. Patchwerk is a block-based system, meaning any cable that wants to be audio-rate needs to store things in a buffer. Every audio-rate cable could have allocated their own buffer, but this does not scale well from a memory standpoint, especially on constrained computing environments.

To make memory more manageable, Patchwerk implements a combination of a memory pool manage buffers. The memory pool has a pre-allocated set of buffers to choose from, which removes the scaling issue.

Stack-based interface

Buffers in the pool are consantly being overwritten and re-used. This is managed using a stack interface. Buffers pushed to the stack are immutable, and when they are popped, they return back to the pool to be reclaimed.

Highly serial patches (A to B to C), such as the ones you find in Sporth, lend themselves quite well to this approach. In fact, thinking in Patchwerk feels a lot like thinking in Sporth. Many Runt + Patchwerk patches look identical to Sporth code.

Buffer Holding

The buffer stack stops being useful when you have signals that need to modulate many different components. Stack operation would make this incredibly difficult to read.

The workaround for this is to explicitly hold the buffer to ensure it doesn't get reclaimed for the duration that it is needed, then explicitely un-hold the buffer so that it can be re-used.

For those reading this far down: buffer holding is the big gotcha in Patchwerk. If you do not unhold a previously held buffer, it will cause a certain kind of resource leak, which is troublesome if you want to recompile + hotswap patchwerk patches.

What kind of DSP modules does Patchwerk have?

By itself, none to speak of. One must define these externally. The runt patchwerk interface provides a default set of DSP routines via the soundpipe DSP library. With some work, one could easily write new DSP modules. With even more work, one could use FAUST to generate DSP code for patchwerk (this is doable).


Projects

Home