Functional DSP?

#1

Hi all,
I’ve done some tinkering with DSP programming but haven’t created any libraries or done the heavy lifting of my DSP applications.

When thinking through DSP, it appears to a prime domain for functional programming as with many applications, I’m really just transforming signals. So this leads to my question, does DSP cater well to functional programming? Or are the optimisation/speed requirements too great to complete different processes in a functional or non-mutative way?

Just wondering really.

0 Likes

#2

Functional style DSP code is very hard to implement efficiently with C++. You are basically better off with mutable state and all that nastiness. Audio DSP code fundamentally is quite functional, though. C++ just isn’t the right language to express that.

0 Likes

#3

Have a look at Faust : https://faust.grame.fr. Basically Faust is a functional DSL that generates C++ (or C, LLVM IR, WebAssembly…)

1 Like

#4

Functional techniques are capable of outperforming classic styles of programming. If you have time, check out this video in which a text-editor performs massive cut and pastes virtually instantly. It’s performance blows away any thing else on the market.
Even more impressive is the ability to share the document between threads without any locking , and supporting transactional updates of the document without any race conditions. This technique could be very useful for passing parameter trees to an audio thread in a non-blocking manner. It could be used in a DAW to allow saving the session on a background thread while the user keeps editing it on the foreground thread. I think functional techniques are highly relevant to our industry.

The new “ranges” additions to C++ also open up the possibility of FAUST-style composable pipelines of audio processing with standard C++…

audio = oscillator | filter | amplifier;

0 Likes

#5

I didn’t see anything like that in the video. Can you point me to it?

0 Likes

#6

The document is immutable, but copying the document is ultra-cheap (just a few pointers copied). So every edit to the document creates a modified copy of the entire document. The implication of this is that you can freely share pointers to the document between threads without locking (because no thread will ever modify the data you have a pointer to).

Hope that makes sense. It is covered better in the video than I can explain it.

0 Likes

#7

I understand that the original question was more "does DSP can be expressed in the Functional paradigm ?, than “does the Functional paradigm can be expressed in C++ ?”.

They have been several attempts to directly use C++ to express Functional kind of DSPs programs, even using C++ template based approaches. I remember a presentation given at ADC JUCE in 2016. But AFAICS none of them ever reached real usability beside kind of toy examples.

0 Likes

#8

That’s not true. These are nothing but clever data-structures, but the document is actually not copied at all.

Can you maybe explain how you think it would work to have e.g. an FX like a reverb processing input data and overwriting output data, while another thread may wants to turn the reverb off and free it’s memory? Wouldn’t some locking be necessarily involved?

0 Likes

#9

I’m unsure how your question relates specifically to the functional approach, but If I was to take a stab at it I would assume that the act of freeing memory usually involves a call into the OS, which has the potential to block, so therefore to turn-off and free the memory of a plugin without blocking the audio thread you would need to swap ownership of the plugin instance to a secondary thread which can then proceed to free the plugin’s resources “offline”. In this scenario the audio thread would continue to run other plugins while this happens.
This process of swapping ownership of a plugin instance between threads can be done without locking (provided the platform supports atomic assignment of pointers).

0 Likes

#10

Well, neither am I. Why would you think that using these data structures suddenly makes this functional programming? How would you even implement a state-variable filter that is purely functional?

Exchanging pointers is not enough. The audio-thread may be in that reverb->process function for a while, so the other thread would need to know when it’s safe to release the object.

Audio-DSP code is by it’s very nature very mutating. You’re reading input, mutate it and usually even overwrite the input (or mix your output into the input). Lots and lots of audio-dsp algorithms are stateful and mutate their own state for every sample. I can’t see how we can transform these algorithms to be functional.

0 Likes

#11

That is just syntactic sugar. You can already create objects and overwrite their | operator to do exactly that. Doesn’t make it functional programming.

0 Likes

#12

I’m no expert, but IMHO an imperative style of audio programming would be to write explicit loops iterating over the sample buffers, whereas a functional approach would avoid mutable state such as iterators and instead specify the processing in terms of what needs to be done. e.g. as a “pipeline” of composable function objects.

Perhaps we have different understanding of what functional programming is, how do you understand it?

0 Likes

#13

Well, somewhere you would need to loop over the samples…

Functional programming would be to use only “pure” functions that never mutate state. Completely deterministic and predicable. Has huge advantages. These functions are easily testable and once set in stone, can never fail.

But that is only a tiny portion of a plugin. Let’s have the most simple example I can think of right now: a simple gain plugin.

So you write a function that takes the input, mutates it and returns the new output. Great. We wrote a pure function that essentially only multiplies the input with another input.

const float gain (const float input, const float multiplier)
{
    return input * mutiplier;
}

Now if we use this and we modulate the second input value (the multiplier) we get clicks and crackles. We need to smooth the second input parameter. Where do we put that smoothing? Use a LERP inside the gain? Then it would mutate its own state, thus making it “impure” and not testable anymore.

Outside? OK, then we need an interpolator class that, but that needs to maintain its own state, because it needs to keep track of the “current” multiplier, so it can advance it to the “destination” multiplier.

How do we achieve that in a purely functional way, without modifying state? If we can’t even get a simple gain plugin to work in a purely functional way, how do we expect to do the same with more complicated algorithms? I’m afraid that we’re stuck with mutating data forever. There are recursive algorithms that are impossible to achieve without mutations. So we’re already talking about mixing pure and impure functions. Is that still functional programming?

0 Likes

#14

Do I understand. You are claiming that strictly functional programming languages (like for example c++ template metaprogramming) are not Turing-complete?

Specifically you are claiming that it’s not possible to express multiplication (gain) on a vector of floats (samples) in a purely functional manner?

0 Likes

#15

That’s not at all what I wrote. Please re-read my previous message.

0 Likes

#16

You basically need to forget the sample level semantic, and reason on signals, which are (conceptually) infinite stream of samples. Then you can define operations working on signals, we can them “signal processors” in the Faust terminology. See https://faust.grame.fr/doc/manual/index.html#signal-processor-semantic

0 Likes

#17

Rather than having a function gain -> input -> output, you write a function gain -> interpolator -> input -> (output, newInterpolator). Every sample, you calculate the current output value, but also the new state of the ramp. To process multiple consecutive samples, you just pass the interpolator instance returned by the last function call as an argument to the next function call.

0 Likes

#18

Sigh… and how do you implement an interpolator in a purely functional way? Spoiler alert: you don’t. It needs a state that is mutated.

0 Likes

#19

No, that’s the point, you return a completely new ‘interpolator state’ instance each time. So, to increment the interpolator, you have some function interpolator -> interpolator. Expressing the algorithm requires no in-place mutation.

0 Likes

#20

So instead of mutating a single float, your return a new interpolator object, with a new internal state that replaces the old interpolator.

Sounds like mutating the state of the interpolator with more steps.

How is that approach supposed to be easier, faster and safer?

0 Likes