NRT

NRT

1. Overview

Note, rhythms, and timing. An homage to one of the first microlanguages I ever wrote.

2. Tangled Code

<<nrt.lua>>=
NRT = {}

<<lpeg_constructs>>
<<solfvals>>
<<eval>>
return NRT

3. LPeg Constructs

<<lpeg_constructs>>=
Oct = lpeg.S(",'")^0
Solf = lpeg.S("drmfsltDRMFSLT")
Acc = lpeg.S("+-=")^0
Dot = lpeg.P(".")^0
Rhythm = lpeg.Ct(lpeg.Cg(lpeg.R("09")^1, "dur") *
    lpeg.Cg(Dot, "dot"))^0

Behavior = lpeg.S("~^/_")^0

Space = lpeg.S(" \n\t")^0

Note = lpeg.Ct(lpeg.Cg(Solf, "solf") *
    lpeg.Cg(Oct, "oct") *
    lpeg.Cg(Acc, "acc") *
    lpeg.Cg(Rhythm, "rhythm") *
    lpeg.Cg(Behavior, "behavior"))

Notes = lpeg.Ct((Note * Space)^0)

4. Solfege Values

<<solfvals>>=
solfvals = {
    d = 0,
    r = 2,
    m = 4,
    f = 5,
    s = 7,
    l = 9,
    t = 11,
    D = 12,
    R = 14,
    M = 16,
    F = 17,
    S = 19,
    L = 21,
    T = 23,
}

5. Eval

<<eval>>=
function NRT.eval(str, cfg)
    cfg = cfg or {}
    local t = lpeg.match(Notes, str)

    if #t == 0 then
        error("Couldn't parse string: " .. str)
    end

    -- pulses per quarter note
    local ppq = cfg.ppq or 12

    -- pulses per bar (4 beats per bar)
    local ppb = 4 * ppq

    local dur = ppq

    local out = {}

    local base = cfg.base or 60
    local bhvr = 2

    for _, nt in pairs(t) do
        local val = solfvals[nt.solf]

        if nt.oct ~= '' then
            local oct = {}
            nt.oct:gsub(".", 
                function(c) table.insert(oct, c) end)
            for _, o in pairs(oct) do
                if o == "'" then
                    val = val + 12
                elseif o == "," then
                    val = val - 12
                end
            end
        end

        if nt.acc ~= '' then
            if nt.acc == "-" then
                val = val - 1
            elseif nt.acc == "+" then
                val = val + 1
            end
        end

        if nt.rhythm.dur ~= nil then
            dur = ppb / nt.rhythm.dur

            -- TODO: handle more than one dot
            if nt.rhythm.dot ~= "" then
                dur = dur + dur / 2
            end
        end

        if nt.behavior == "~" then
            -- medium gliss
            bhvr = 2
        elseif nt.behavior == "^" then
            -- gliss
            bhvr = 3
        elseif nt.behavior == "/" then
            -- linear
            bhvr = 0
        elseif nt.behavior == "_" then
            -- step
            bhvr = 1
        end

        val = val + base

        table.insert(out, {val, dur, bhvr})
    end

    return out
end