ramblings on my forth sampler
2026-10-09
i have recently written a forth sampler
i wrote this tool because i was inspired by the collage-type music of chuquimamani-condori, and the associated project los thuthanaka.
(i saw los thuthanaka perform at cafe oto and it was probably one of the best live music experiences of my life)
i am also a bit tired of the use of livecoding tools like strudel, which i see in the london scene as basically the only entry point presented into livecoding. the tools that you use will shape the output of what you make, and i've had enough with four-on-the-floor livecoded techno.
because of this, i wanted to make a tool with a reasonably fluid idea of rhythm. there is no beat grid in the sampler, only a playhead that loops from one end of the screen to the other (and even the speed of this playhead can be played with).
i knew i wanted some way to change the length of the loop, but i knew i didn't want to create something like an infinite canvas, which to me seems to be accepted uncritically into every single software tool at the moment. fortunately, i wrote this program shortly after reading stephen kell's convivial design heuristics for software systems. although i'm still figuring out how to integrate these heuristics into my work, i had the idea of simply changing the window size, and listening for that within the program. operating systems have already invented a way to do this, i do not need to re-invent the wheel.
crucially, what i think makes this sampler interesting is the fact that every clip (i'm not using the word sample here, since i'll use that later to mean a single sample of PCM audio) has an associated forth program and stack. instead of just playing each audio sample, it gets pushed to the stack, the forth program runs, and the top of the stack is popped and played to the audio device. this means that you can write a program that e.g. reduces a clip's amplitude by 50% with the program 0.5 *:
the stack starts empty
| |
| |
|____|
then, we push our audio sample on, say x[i]
| |
|x[i]|
|____|
then we run our forth program, which first pushes the literal 0.5 on
|0.5 |
|x[i]|
|____|
then we evaluate the * word, which multiplies the two together
| |
|x[i]*0.5|
|________|
finally, we pop the stack and play the audio sample x[i] * 0.5, which leaves our stack empty.
this idea also lets us express more complicated audio processing techniques.
a simple low-pass filter can be represented by the following equation:
y[i] = a * x[i]+ (1-a)y[i-1]
suppose before we pushed our audio sample on the stack, the stack contained the element y[i-1] instead of being empty. then, we could write a simple program to compute y[i]. if we choose a=0.01, then let's look at the program 0.01 * SWAP 0.99 * +
the stack starts with y[i-1]
| |
|y[i-1]|
|______|
then, we push our audio sample on
|x[i] |
|y[i-1]|
|______|
then we start evaluating our forth program, which starts by pushing 0.01 on
|0.01 |
|x[i] |
|y[i-1]|
|______|
then we evaluate the * word
| |
|x[i]*0.01|
|y[i-1] |
|_________|
now we evaluate the SWAP word, which swaps the top two elements of the stack
| |
|y[i-1] |
|x[i]*0.01|
|_________|
then we push 0.99 on
|0.99 |
|y[i-1] |
|x[i]*0.01|
|_________|
then we evaluate the * word
| |
|y[i-1]*0.99|
|x[i]*0.01 |
|___________|
finally, we evaluate the + word
| |
|x[i]*0.01+y[i-1]*0.99|
|_____________________|
this is precisely the equation for our low pass filter! but it relies on having y[i-1] on the stack before the program executes. let's amend our little program by appending a DUP word which duplicates the top dork on the stack, so we have 0.01 * SWAP 0.99 * + DUP. this leaves our stack looking like this:
|x[i]*0.01+y[i-1]*0.99|
|x[i]*0.01+y[i-1]*0.99|
|_____________________|
when we then pop the stack to play a sample, the stack is left with a single element, which is exactly y[i], which leaves the stack ready for our next sample. i think this makes the tool interesting: the user can choose what state they went preserved, essentially the arguments to a function that computes the next sample. in this case, we're preserving the last sample we played, but you could choose any kind of intermediary value.
you can also write a more general low-pass filter word that takes a as an argument. this one takes, in order from top-to-bottom, a, x[i], then y[i-1], and leaving just y[i].
:LPF DUP ROT * SWAP 1 SWAP - ROT * + DUP;
i would recommend tracing this one out yourself, or better yet trying to write your own LPF word.
however, there is one problem that i haven't mentioned: what about playing the first sample? there is no y[i-1] if i is 0. implementation initial thought was for the user to provide two programs, an initialisation program that runs for some user-specified n samples, and a regular program that runs the rest of the time. for this case, we would just have the initialisation program be the single word DUP, and have it run for a single sample.
even though this solved the problem, i found it incredibly clunky, and i thought it would be confusing for a new user. the solution i opted for instead some might consider a hack, but i tell myself that its elegant: by implementing the stack as a ring buffer and exposing the seams, no stack access is invalid. we can somewhat 'get away' with using our low-pass filter program from before, incurring some unexpected behaviour for the first sample (although inaudible to me).
we can also write a delay using similar techniques: 44100 PICK + 2 / DUP is a 1 second delay.
you can even intentionally try and accumulate garbage on the stack (with no worry about overflow because of the ring buffer), and then write strange programs like the singular -. i don't know how you would even go about expressing the effect you get from this in any other audio processing software!
overall, i was pretty pleased with the way this turned out. i'm yet to perform with it, or even write a 'proper' patch outside of just playing around. i probably need to implement (de)-serialisation for that. coming soon(tm) i'm sure.
source code here