Functional Programming & C code generation

DSP, Plug-in and Host development discussion.
doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Tue May 14, 2013 7:48 am

Is there anyone here interested in Functional Programming and C code generation for DSP code?

I'm working on a system for DSP code development based on the principle of Abstract Interpretation
http://en.wikipedia.org/wiki/Abstract_interpretation

Basically, it will allow several interpretations of a single specification program:
- specification as a pure functional program
- imperative C code generation (for passing to C compiler or LLVM)
- Z transform for frequency plots of (linearized) transfer function
- automatic differentiation for all kinds of derivative-based tricks
- ...

It is written in Racket
http://racket-lang.org/

It's starting to get to a point where it is actually useful (there is a basic synth core) and could use some feedback, but please note it is very experimental and still needs a lot of work. Familiarity with Racket is probably a prerequisite at this point to make sense of it. Current code is here:
http://zwizwa.be/darcs/meta/rai/

It is part of a darcs archive that can be fetched using

Code: Select all

darcs get http://zwizwa.be/darcs/meta

Maurizio Giri
KVRer
9 posts since 30 Apr, 2013 from Italy

Post Thu May 16, 2013 1:12 am

Very interesting, thank you.
I didn't know racket, it seems pretty cool.

BTW, do you know FAUST? http://faust.grame.fr/

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Thu May 16, 2013 6:47 am

Maurizio Giri wrote:Very interesting, thank you.
I didn't know racket, it seems pretty cool.

BTW, do you know FAUST? http://faust.grame.fr/
Racket is a really great project. It's also incredibly well documented.

The basic language in the RAI project is related to FAUST, at least to the point of being a pure stream processing language. FAUST however is "point free", while RAI uses a more traditional named variable approach (it is internally based on lambda expressions). Both should be +- equivalent in expressive power.

What RAI adds is the abstract interpretation part, a flexible way to manipulate programs for compilation, analysis, ...

mystran
KVRAF
5256 posts since 12 Feb, 2006 from Helsinki, Finland

Post Thu May 16, 2013 7:50 am

Out of curiosity, have you tried getting Racket (formerly known as PLT Scheme) and LLVM to work in a VST plugin? :)
If you'd like Signaldust to return, please ask Katinka Tuisku to resign.

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Thu May 16, 2013 8:19 am

mystran wrote:Out of curiosity, have you tried getting Racket (formerly known as PLT Scheme) and LLVM to work in a VST plugin? :)
No, but I'm not so far removed from that being a possibility.
I have code that bridges internal rai API to VST (and Pd, Jack).
The VST currently generates headless windows binary. I'm cross-compiling from a single Linux build system atm to keep things contained.

I'm not sure how useful it is to have racket running in a real-time context though. It is possible to write code that doesn't cause any garbage collection, but such code usually can also be written in C or in a simpler functional language that can be compiled to C (like the RAI system exposes).

Basically, I think it makes more sense to have Racket run as a meta system / code generator, and possibly as a control-rate process, i.e. much in the same way as supercollider.

mystran
KVRAF
5256 posts since 12 Feb, 2006 from Helsinki, Finland

Post Thu May 16, 2013 11:57 am

Oh, but I don't mean running scheme-code in real-time thread, rather just using it to generate native real-time code on the fly could be useful. It's not exactly hard to generate C code for offline compilation, but it gets a bit messier if you want to do it all at run-time. Also embedding anything in a plugin usually requires some constraints of resource management and such, and I was just wondering if you've investigated whether either Racket or (more interestingly) LLVM can happily exist in such an environment. I'd imagine it's probably possible, so I was simply wondering whether you'd tried.
If you'd like Signaldust to return, please ask Katinka Tuisku to resign.

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Thu May 16, 2013 12:28 pm

mystran wrote:Oh, but I don't mean running scheme-code in real-time thread, rather just using it to generate native real-time code on the fly could be useful. It's not exactly hard to generate C code for offline compilation, but it gets a bit messier if you want to do it all at run-time. Also embedding anything in a plugin usually requires some constraints of resource management and such, and I was just wondering if you've investigated whether either Racket or (more interestingly) LLVM can happily exist in such an environment. I'd imagine it's probably possible, so I was simply wondering whether you'd tried.
Re-loading compiled binary from generated C code at run time is already working. My dev setup is in Pd on Linux, and I have a tiny ad-hoc binary object format that can reload processing code while leaving the internal state intact, as long as the state and parameter configuration is compatible. This allows small tweaks to the algorithm to be made with about a 2 second delay from code save to it being uploaded automatically in the running Pd test patch. This was an important point to get right!

The same could possibly be done with a VST api, but I don't have enough low-level windows or OSX experience to make that work at this time, and no particular inclination since the Linux setup works so well. Happy to assist anyone wanting to give a stab at it though :)

The constraints on resource management are pretty severe at this point: the internal binary object format does not allow for any external dynamic dependencies. Only static code is allowed (note: not even libc or libm!). This might sound a bit drastic but it works surprisingly well in practice when you're working on low-level code. I.e. I don't use any libm calls in my code anyway: all is done with special-purpose polynomial approximation.

As for LLVM, it is designed to make it generate LLVM code in a straightforward way. For now I am generating C because that's easier to use until the specification language stabilizes, but the C code that comes out is just LLVM-style single assignment.

What I'm thinking is that the internal plugin format could probably be separated out into a separate project. It might be useful as an internal test tool in other projects, without exposing the binary interface. Such a thing can be kept simple if there is no requirement for backward compatibility.

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Sat Jun 01, 2013 9:47 am

I cleaned it up a bit: made a Racket package on github, and put some docs up.
http://zwizwa.be/rai

User avatar
cturner
KVRian
510 posts since 7 Dec, 2009 from GWB

Post Sun Jun 02, 2013 6:10 am

Hi Tom-

Really great to hear of what you've been up to. I'll try out RAI in the next couple of weeks.

Readers here might also be interested in Terra, an LLVM language that uses LuaJit for it's meta-layer:

<http://terralang.org>

Wesley Smith, whom I believe developed the Lua interface and [gen~] object for Max/MSP, is on the Terra list, so perhaps there's some audio (and/or OpenGL) synergy afoot.

Best wishes, Charles

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Sun Jun 02, 2013 11:19 am

cturner wrote:Hi Tom-

Really great to hear of what you've been up to. I'll try out RAI in the next couple of weeks.
Great :)
Don't hesitate to ask questions. There are bound to be some points of confusion as it's all quite new.
cturner wrote:
Readers here might also be interested in Terra, an LLVM language that uses LuaJit for it's meta-layer:

<http://terralang.org>

Wesley Smith, whom I believe developed the Lua interface and [gen~] object for Max/MSP, is on the Terra list, so perhaps there's some audio (and/or OpenGL) synergy afoot.

Best wishes, Charles
I recently ran into Terra. Haven't used it yet though. Lua is quite a nice language. Simple and powerful.

Swiss Frank
KVRist
136 posts since 29 Aug, 2011

Post Mon Jun 10, 2013 9:12 pm

I'm designing a rudimentary Functional language for modular synthesis, and a Windows stand-alone synth/integrated development environment for it.

I'm about to start discussing it publicly, but here's a taste. The language still has 3-4 major improvements to come that should make it a lot more concise. Since you guys seem to know functional languages forwards and backwards I'm especially curious to hear your comments.

The following patch is just two oscillators. Only Osc1's audio output is used. Osc2 is just used to control Osc1, albeit with FM, AM, and hard sync. (It was inspired by a page I read on Casio CZ synthesis.) That goes into a multitap delay that is part of the voice.


[Osc1]

Frequency = Voice:Frequency * ( 1 + (ModWheel+.1)*10 * Env1:Control * ( 1.1 - Osc2:Control ) )

Waveform = Square
Duty = Velocity / 2
SyncIn = Osc2:SyncOut

# Osc2 is used for AM and FM modulation of Osc1, as well as hard-syncing it. Therefore the frequency of Osc2 is the frequency of the sound, though only Osc1 is routed to the output.

[Osc2]

Frequency = Voice:Frequency
Waveform = Sawtooth

[Env1]

Decay = .8

[Env2]

Decay = .05
Sustain = .6

[Filter]

Input = Env2:Audio * Osc1:Control* ((Osc2:Control+1)/2)^ (GeneralPurpose3^.5)
Cutoff = 300 + 2000 * Voice:Velocity
Resonance = 10
Poles = 2

[Delay]

Input = Filter:Audio
Time1 = .25
Time2 = .375

# These delays are 1ms apart, causing a metallic comb-filter on the 3rd echo.
Time3 = .500
Time4 = .501
Time5 = .502
Time6 = .503
Time7 = .504

[Voice]

Mono = Delay:Output

[Scope]

# Notice we sync to Osc2, which sets the note's real frequency even though its not part of the mix.
Probe1 = Voice:Mono
Start = Osc2:SyncOut
Stop = Osc2:SyncOut

Swiss Frank
KVRist
136 posts since 29 Aug, 2011

Post Mon Jun 10, 2013 9:23 pm

doelie wrote:
mystran wrote:Oh, but I don't mean running scheme-code in real-time thread, rather just using it to generate native real-time code on the fly could be useful. It's not exactly hard to generate C code for offline compilation, but it gets a bit messier if you want to do it all at run-time.
Hi Doelie, Mystran:

In the past, I've had C++ programs output simple C, invoke the compiler, create a shared library, and open that library with dlopen(). It will resolve all symbols for you, just like any normal dynamic library. Also, if the generated code uses few (or no) standard headers it might only take <1 sec or something.

Actually, my functional synth Moselle is currently "compiled" into an object heirachy, which is then asked to evaluate its output for every sample, but this is very slow. I intend to output C and compile it in the manner I mentioned, and hope for a 10x speedup. My only concern is that Moselle as a product would then require a compiler in the environment. I could bundle g++/gcc with it on Linux, but I'm not sure whether a g++/gcc-produced object file on say Windows will link with a MSFT VS2008 app. (My hunch is yes for C but maybe no for C++.) (And I don't know enough about Macs to even know what to be worried about there.)

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Tue Jun 11, 2013 4:59 am

Swiss Frank wrote:I'm designing a rudimentary Functional language for modular synthesis, and a Windows stand-alone synth/integrated development environment for it.
Hi Frank,

What I find most interesting in language design is the basic principles or "axioms", being

1) primitive operations, and
2) composition/abstraction mechanisms to build new operations.

IIUC, your patch language is functional, except for the primitives, which have state. Some questions about the composition mechanism:

- How do you differentiate between "class" and "instance" for the primitives? I.e. there is Osc1 and Osc2. Are these fixed instances of a shared oscillator class?

- Is there an abstraction mechanism? I.e. is there a way to take a couple of modules and create a new black box to be used in another patch?

Cheers
Tom

doelie
KVRer
10 posts since 26 Apr, 2013 from Lansing, MI

Post Tue Jun 11, 2013 5:16 am

Swiss Frank wrote:
doelie wrote:
mystran wrote:Oh, but I don't mean running scheme-code in real-time thread, rather just using it to generate native real-time code on the fly could be useful. It's not exactly hard to generate C code for offline compilation, but it gets a bit messier if you want to do it all at run-time.
Hi Doelie, Mystran:

In the past, I've had C++ programs output simple C, invoke the compiler, create a shared library, and open that library with dlopen(). It will resolve all symbols for you, just like any normal dynamic library. Also, if the generated code uses few (or no) standard headers it might only take <1 sec or something.
This works well, except for one problem: if the .so is referenced by more than one call to dlopen(), the library will not be unloaded until all refereces call dlclose(). Sometimes the "shared" part of a shared library is annoying.

This is one reason I'm using an ad-hoc binary object file format in my project. If I ask it to load, it will load the current version, regardless of other users of the file.



Actually, my functional synth Moselle is currently "compiled" into an object heirachy, which is then asked to evaluate its output for every sample, but this is very slow. I intend to output C and compile it in the manner I mentioned, and hope for a 10x speedup. My only concern is that Moselle as a product would then require a compiler in the environment. I could bundle g++/gcc with it on Linux, but I'm not sure whether a g++/gcc-produced object file on say Windows will link with a MSFT VS2008 app. (My hunch is yes for C but maybe no for C++.) (And I don't know enough about Macs to even know what to be worried about there.)
Have a look at LLVM: http://llvm.org/.
It can JIT your code and be loaded as a cross-platform library.
It can be a lot to take in, but well worth the time investment.

Swiss Frank
KVRist
136 posts since 29 Aug, 2011

Post Tue Jun 11, 2013 7:34 am

This works well, except for one problem: if the .so is referenced by more than one call to dlopen(), the library will not be unloaded until all refereces call dlclose(). Sometimes the "shared" part of a shared library is annoying.
I didn't supply enough detail: I was talking about the process creating the C source, and SO, as files known only to that process. Even in iterative compilations of a given "patch" by a musician, the compiler, linker, and DL loader would not know that, say MyLib130610_pid12345_27.so defining symbol Fn130610_pid12345_27() was a "new version" of _24.so, etc. So the OS and other running processes wouldn't have a chance to out-think you.

Have a look at LLVM: http://llvm.org/.
It can JIT your code and be loaded as a cross-platform library.
It can be a lot to take in, but well worth the time investment.
At first glance it seems like a suite of libraries (or tools?) based around some kind of p-code? So in my example of having a high-level functional language optimized for writing synthesizer patches, do you see me attempting to translate that into C or p-code? And would I write that C or p-code to disk or just pass it as a string to a library? I probably don't understand this well enough to see how it could make my life (or those of my users) easier?

Return to “DSP and Plug-in Development”