Lua Opcodes

The purposes of the Lua opcodes are:

  1. Make it possible to write Csound code in a user-friendly, high-level language with full lexical scoping, structures and classes, and support for functional programming, using LuaJIT (the Lua programming language, implemented with a just-in-time compiler and foreign function interface).
  2. Require the installation of no third party software packages, or at least a minimum installation; also, require no build system or external compilation.
  3. Run really fast; typically, almost as fast as compiled C code, and several times faster than user-defined opcodes.

Using the Lua opcode family, you can interact with the Lua interpreter and just-in-time compiler (luajit) embedded in Csound as follows:

  1. Execute any arbitrary block of Lua code (the lua_exec opcode),
  2. Define an opcode in Lua taking any number or type of parameters, and returning any number or type of parameters (the lua_opdef opcode),
  3. Call a Lua opcode at i-rate (the lua_iopcall opcode),
  4. Call a Lua opcode at i-rate and k-rate (the lua_ikopcall opcods), or
  5. Call a Lua opcode at i-rate and a-rate (the lua_iaopcall opcode).

Lua is Portuguese for "moon." And Lua (http://www.lua.org) is a lightweight, efficient dynamic programming language, designed for embedding in C/C++ and extending with C/C++. Lua has a stack-based calling mechanism and provides a toolkit of features (tables, metatables, anonymous functions, and closures) with which many styles of object-oriented and functional programming may be implemented. Lua's syntax is only slightly harder than Python's.

Lua is already one of the fastest dynamic languages; yet LuaJIT by Mike Pall (http://luajit.org) goes much further, giving Lua a just-in-time optimizing trace compiler for Intel architectures. LuaJIT includes an efficient foreign function interface (FFI) with the ability to define C arrays, structures, and other types in Lua. The speed of LuaJIT/FFI ranges from several times as fast as Lua, to faster (in some contexts) than optimized C.

Example

Here is an example of a Lua opcode, implementing a Moog ladder filter. For purposes of comparison, a user-defined opcode and the native Csound opcode that compute the same sound using the same algorithm also are shown, and timed.. The example uses the file luamoog.csd.

Example 12.  Example of a Lua opcode.

<CsoundSynthesizer>
<CsInstruments>
sr =    48000
ksmps =   100
nchnls =    1

    gibegan     rtclock

    lua_opdef   "moogladder", {{
local ffi = require("ffi")
local math = require("math")
local string = require("string")
local csoundApi = ffi.load('csound64.dll.5.2')
ffi.cdef[[
    int csoundGetKsmps(void *);
    double csoundGetSr(void *);
    struct moogladder_t {
      double *out;
      double *inp;
      double *freq;
      double *res;
      double *istor;
      double sr;
      double ksmps;
      double thermal;
      double f;
      double fc;
      double fc2;
      double fc3;
      double fcr;
      double acr;
      double tune;
      double res4;
      double input;
      double i;
      double j;
      double k;
      double kk;
      double stg[6];
      double delay[6];
      double tanhstg[6];
    };
]]

local moogladder_ct = ffi.typeof('struct moogladder_t *')

function moogladder_init(csound, opcode, carguments)
    local p = ffi.cast(moogladder_ct, carguments)
    p.sr = csoundApi.csoundGetSr(csound)
    p.ksmps = csoundApi.csoundGetKsmps(csound)
    if p.istor[0] == 0 then
        for i = 0, 5 do
            p.delay[i] = 0.0
        end
        for i = 0, 3 do
            p.tanhstg[i] = 0.0
        end
    end
    return 0
end

function moogladder_kontrol(csound, opcode, carguments)
    local p = ffi.cast(moogladder_ct, carguments)
    -- transistor thermal voltage
    p.thermal = 1.0 / 40000.0
    if p.res[0] < 0.0 then
        p.res[0] = 0.0
    end
    -- sr is half the actual filter sampling rate
    p.fc = p.freq[0] / p.sr
    p.f = p.fc / 2.0
    p.fc2 = p.fc * p.fc
    p.fc3 = p.fc2 * p.fc
    -- frequency & amplitude correction
    p.fcr = 1.873 * p.fc3 + 0.4955 * p.fc2 - 0.6490 * p.fc + 0.9988
    p.acr = -3.9364 * p.fc2 + 1.8409 * p.fc + 0.9968
    -- filter tuning
    p.tune = (1.0 - math.exp(-(2.0 * math.pi * p.f * p.fcr))) / p.thermal
    p.res4 = 4.0 * p.res[0] * p.acr
    -- Nested 'for' loops crash, not sure why.
    -- Local loop variables also are problematic.
    -- Lower-level loop constructs don't crash.
    p.i = 0
    while p.i < p.ksmps do
        p.j = 0
        while p.j < 2 do
            p.k = 0
            while p.k < 4 do
                if p.k == 0 then
                    p.input = p.inp[p.i] - p.res4 * p.delay[5]
                    p.stg[p.k] = p.delay[p.k] + p.tune * (math.tanh(p.input * p.thermal) - p.tanhstg[p.k])
                else
                    p.input = p.stg[p.k - 1]
                    p.tanhstg[p.k - 1] = math.tanh(p.input * p.thermal)
                    if p.k < 3 then
                        p.kk = p.tanhstg[p.k]
                    else
                        p.kk = math.tanh(p.delay[p.k] * p.thermal)
                    end
                    p.stg[p.k] = p.delay[p.k] + p.tune * (p.tanhstg[p.k - 1] - p.kk)
                end
                p.delay[p.k] = p.stg[p.k]
                p.k = p.k + 1
            end
            -- 1/2-sample delay for phase compensation
            p.delay[5] = (p.stg[3] + p.delay[4]) * 0.5
            p.delay[4] = p.stg[3]
            p.j = p.j + 1
        end
        p.out[p.i] = p.delay[5]
        p.i = p.i + 1
    end
    return 0
end
}}

/*
Moogladder - An improved implementation of the Moog ladder filter

DESCRIPTION
This is an new digital implementation of the Moog ladder filter based on the work of Antti Huovilainen,
described in the paper \"Non-Linear Digital Implementation of the Moog Ladder Filter\" (Proceedings of DaFX04, Univ of Napoli).
This implementation is probably a more accurate digital representation of the original analogue filter.
This is version 2 (revised 14/DEC/04), with improved amplitude/resonance scaling and frequency correction using a couple of polynomials,as suggested by Antti.

SYNTAX
ar  Moogladder  asig, kcf, kres

PERFORMANCE
asig - input signal
kcf - cutoff frequency (Hz)
kres - resonance (0 - 1).

CREDITS
Victor Lazzarini
*/

                    opcode  moogladderu, a, akk
asig, kcf, kres     xin
                    setksmps    1
ipi                 =           4 * taninv(1)
/* filter delays */
az1                 init        0
az2                 init        0
az3                 init        0
az4                 init        0
az5                 init        0
ay4                 init        0
amf                 init        0
                    if          kres > 1 then
kres                =           1
                    elseif      kres < 0 then
kres                =           0
                    endif
/* twice the \'thermal voltage of a transistor\' */
i2v                 =           40000
/* sr is half the actual filter sampling rate  */
kfc                 =           kcf/sr
kf                  =           kcf/(sr*2)
/* frequency & amplitude correction  */
kfcr                =           1.8730 * (kfc^3) + 0.4955 * (kfc^2) - 0.6490 * kfc + 0.9988
kacr                =           -3.9364 * (kfc^2) + 1.8409 * kfc + 0.9968;
/* filter tuning  */
k2vg                =           i2v * (1 - exp(-2 * ipi * kfcr * kf))
/* cascade of 4 1st order sections         */
ay1                 =           az1 + k2vg * (tanh((asig - 4 * kres * amf * kacr) / i2v) - tanh(az1 / i2v))
az1                 =           ay1
ay2                 =           az2 + k2vg * (tanh(ay1 / i2v) - tanh(az2 / i2v ))
az2                 =           ay2
ay3                 =           az3 + k2vg * (tanh(ay2 / i2v) - tanh(az3 / i2v))
az3                 =           ay3
ay4                 =           az4 + k2vg * (tanh(ay3 / i2v) - tanh(az4 / i2v))
az4                 =           ay4
/* 1/2-sample delay for phase compensation  */
amf                 =           (ay4 + az5) *0.5
az5                 =           ay4
/* oversampling  */
ay1                 =           az1 + k2vg * (tanh((asig - 4 * kres * amf * kacr) / i2v) - tanh(az1 / i2v))
az1                 =           ay1
ay2                 =           az2 + k2vg * (tanh(ay1 / i2v) - tanh(az2 / i2v ))
az2                 =           ay2
ay3                 =           az3 + k2vg * (tanh(ay2 / i2v) - tanh(az3 / i2v))
az3                 =           ay3
ay4                 =           az4 + k2vg * (tanh(ay3 / i2v) - tanh(az4 / i2v))
az4                 =           ay4
amf                 =           (ay4 + az5) * 0.5
az5                 =           ay4
                    xout        amf
                    endop

instr 1
                prints      "No filter.\n"
	kfe         expseg      500, p3*0.9, 1800, p3*0.1, 3000
    kenv        linen       10000, 0.05, p3, 0.05
    asig        buzz        kenv, 100, sr/(200), 1
    ; afil      moogladder  asig, kfe, 1
                out         asig
endin

instr 2
                prints      "Native moogladder.\n"
	kfe         expseg      500, p3*0.9, 1800, p3*0.1, 3000
    kenv        linen       10000, 0.05, p3, 0.05
    asig        buzz        kenv, 100, sr/(200), 1
    afil        moogladder  asig, kfe, 1
                out         afil
endin

instr 3
                prints      "UDO moogladder.\n"
	kfe         expseg      500, p3*0.9, 1800, p3*0.1, 3000
    kenv        linen       10000, 0.05, p3, 0.05
    asig        buzz        kenv, 100, sr/(200), 1
    afil        moogladderu asig, kfe, 1
                out         afil
endin

instr 4
                prints      "Lua moogladder.\n"
    kres        init        1
    istor       init        0
	kfe         expseg      500, p3*0.9, 1800, p3*0.1, 3000
    kenv        linen       10000, 0.05, p3, 0.05
    asig        buzz        kenv, 100, sr/(200), 1
    afil        init        0
                lua_ikopcall    "moogladder", afil, asig, kfe, kres, istor
                out         afil
endin

instr 5
    giended     rtclock
    ielapsed    =           giended - gibegan
                print       ielapsed
    gibegan     rtclock
endin

</CsInstruments>
<CsScore>
f 1     0 65536 10 1
i 5.1   0   1
i 4     1   20
i 5.2   21  1
i 4     22  20
i 5.3   42  1
i 2     43  20
i 5.4   63  1
i 2     64  20
i 5.5   84  1
i 3     85  20
i 5.6   105 1
i 3     106 20
i 5.7   126 1
i 1     127 20
i 5.8   147 1
i 1     148 20
i 5.9   168 1
i 4     169 20
i 4     170 20
i 4     171 20
e
</CsScore>
</CsoundSynthesizer>


Credits

Copyright (c) 2011 by Michael Gogins. All rights reserved.