Lunar DSP Scripting

Lunar is the dsp scripting engine embedded within libzzub. At the time of writing, lunar supports LLVM bytecode exclusively, and hence supports any LLVM frontend - including C, C++, and, somewhere in the future, most likely Java and Python.

Lunar modules and source code is always being saved with ccm songs, so they play on most systems without any additional installation of plugins. Through LLVM, X86, X86-64, PowerPC, PowerPC-64, SPARC, Alpha, and IA-64 can be supported, however at the time of writing X86 is the only target.

A Simple Example

To make libzzub recognize your scripts, you need to provide at least two files: a manifest file describing your plugin and a script defining the way your plugin works. Both files need to be put into the subdirectory meant to contain Lunar scripts. For Aldrin, this is e.g. /usr/lib/lunar/fx or ~/.aldrin/lunar. Create a subdirectory with a name losely associated with your plugin (e.g. "simplesynth") and put your files into that directory.

manifest.xml

This is the file that describes the metadata of your plugin: name, author, parameters, required files and used scripts. Below is an example file describing a gain plugin.

<?xml version="1.0" encoding="utf-8"?>
<zzub>
        <plugin
                uri="@trac.zeitherrschaft.org/aldrin/lunar/effect/gain;1"
                name="Lunar Gain"
                shortname="Gain"
                type="effect"
                description="A simple gain effect demonstrating dsp scripting with Lunar."
                author="Leonard Ritter"
                email="contact at leonard-ritter+com">
                <parameters>
                        <global>
                                <parameter
                                        id="gain"
                                        name="Gain (%)"
                                        description="Gain of output signal"
                                        type="float"
                                        minvalue="0"
                                        maxvalue="100"
                                        defvalue="100"
                                        precision="1"
                                        state="true"/>
                                <parameter
                                        id="mgain"
                                        name="Master Gain (dB)"
                                        description="Final gain of output signal"
                                        type="float"
                                        minvalue="-48"
                                        maxvalue="12"
                                        defvalue="0"
                                        precision="0.01"
                                        state="true"/>
                                <parameter
                                        id="inertia"
                                        name="Inertia (ms)"
                                        description="Reaction time of amplitude"
                                        type="float"
                                        minvalue="0"
                                        maxvalue="250"
                                        defvalue="100"
                                        precision="0.1"
                                        state="true"/>
                        </global>
                        <track>
                        </track>
                </parameters>
                <attributes>
                        <attribute
                                id="gainscale"
                                name="Gain Scale"
                                minvalue="1"
                                maxvalue="16"
                                defvalue="1"/>
                </attributes>
                <files>
                        <file ref="gain.cpp"/>
                        <file ref="gain.h"/>
                        <file ref="gain.bc"/>
                </files>
                <modules>
                        <module language="llvm" ref="gain.bc">
                                <source language="c++" ref="gain.cpp" fxdef="gain.h"/>
                        </module>
                </modules>
        </plugin>
</zzub>

gain.cpp

The script defines the actual dsp code at work. It usually consists of two functions, process_events, which is called once per tick, and process_stereo, which is called everytime some audio data needs to be rendered.

The script is actual plain C++ code, which is required to be translated into LLVM bytecode before Lunar can load it. Lunar then compiles the code into machine code on the fly for best performance.

Below is the sample script for the gain plugin defined above.

#include <lunar/fx.h>
#include "gain.h"

extern "C" {

float amp, mamp, famp, iamp, step;

void init() {
        amp = 1.0;
        mamp = 1.0;
        famp = 0.0;
        iamp = 0.0;
        step = 1000.0f/(5.0f * samples_per_second);
}

void exit() {
}

void process_events() {
        if (globals.gain) {
                amp = *globals.gain / 100.0f;
        }
        if (globals.mgain) {
                mamp = dbtoamp(*globals.mgain, -48.0f);
        }
        famp = amp * mamp;
        if (globals.inertia) {
                if (!*globals.inertia)
                        step = 1.0f;
                else
                        step = 1000.0f/(*globals.inertia * samples_per_second);
        }
}

void process_stereo(float **i2, float **o2, int n) {
        float sn, oldiamp;
        
        dsp_set(o2[0],n,famp);
        dsp_set(o2[1],n,famp);
        sn = min(abs((famp - iamp)/step),n);
        if (sn > 1) {
                oldiamp = iamp;
                if (famp > iamp) {
                        iamp = dsp_slope(o2[0],sn,oldiamp,step);
                        iamp = dsp_slope(o2[1],sn,oldiamp,step);
                } else {
                        iamp = dsp_slope(o2[0],sn,oldiamp,-step);
                        iamp = dsp_slope(o2[1],sn,oldiamp,-step);
                }
        }
        dsp_mul(i2[0], o2[0], n);
        dsp_mul(i2[1], o2[1], n);
        dsp_clip(o2[0], n, 1); // signal may never exceed -1..1
        dsp_clip(o2[1], n, 1);
}

} // extern "C"

and this is the gain header file:

#if defined(__cplusplus)
extern "C" {
#endif
extern struct gparams {
        float *gain;
        float *mgain;
        float *inertia;
} globals;
#if defined(__cplusplus)
}
#endif

Module Reference

Lunar exposes various tool functions to code, most of them designed to process or mutilate soundbuffers. Here is the fx.h header file declaring symbols which Lunar exposes during runtime:

#if !defined(LUNAR_FX_H)
#define LUNAR_FX_H

#if defined(__cplusplus)
extern "C" {
#endif

#define M_PI 3.14159265358979323846
        
float dsp_slope(float *b, int ns, float start, float step);
void dsp_amp(float *b, int numsamples, float s);
void dsp_copy(float *i, float *o, int numsamples);
void dsp_add(float *i, float *o, int numsamples);
void dsp_mul(float *i, float *o, int numsamples);
void dsp_powmap(float *b, int numsamples, float c, float base, float offset, float factor);
void dsp_copyamp(float *i, float *o, int numsamples, float s);
void dsp_addamp(float *i, float *o, int numsamples, float s);
void dsp_set(float *b, int numsamples, float s);
void dsp_clip(float *b, int numsamples, float s);
void dsp_zero(float *b, int numsamples);
void dsp_fixdn(float *b, int numsamples);
float dbtoamp(float db, float limit);
float fixdn(float);
// src
typedef struct _dsp_src dsp_src_t;
dsp_src_t *dsp_src_new();
void dsp_src_delete(dsp_src_t *o);
int dsp_src_process(dsp_src_t *o, float *pin, int isize, float *pout, int osize, float ratio, int end_of_input, int *read, int *written);
int dsp_src_reset(dsp_src_t *o);
// math
float pow(float, float);
float log(float);
float exp(float);
float abs(float);
float min(float, float);
float max(float, float);
float sin(float);
float cos(float);
float tan(float);
// stdlib
int printf(const char *format, ...);
void *memset(void *b, int c, unsigned long len);

extern int beats_per_minute;
extern int ticks_per_beat;
extern int samples_per_second;
extern float samples_per_tick;
extern int tick_position;
extern float ticks_per_second;
extern int track_count;

#if defined(__cplusplus)
} // extern "C"
#endif

#endif // LUNAR_FX_H