├── Makefile
├── README
├── app
├── Makefile
├── autoload.lua
├── co.lua
├── evq.lua
├── getopt.lua
├── list.lua
├── log.lua
├── luaposix.c
├── misc.lua
├── sandbox.lua
└── strict.lua
├── doc
├── .gitignore
├── Makefile
├── about.txt
├── appregiator.mp3
├── architecture.txt
├── bin
│ ├── libdocs
│ └── txt2html
├── concepts.txt
├── dsp.txt
├── example.mp3
├── examples.txt
├── gui.txt
├── htdocs
│ ├── highlight.css
│ ├── style.css
│ └── zplayer.swf
├── img
│ ├── connect-1.png
│ ├── connect-2.png
│ ├── gui.png
│ ├── worp-icon.png
│ └── worp-long.png
├── index.txt
├── jack.txt
├── libs.txt
├── live_coding.txt
├── main.txt
├── modules.txt
└── worp.svg
├── examples
├── appregiator.lua
├── gen.lua
└── scales.lua
├── lib
├── Chord.lua
├── Core.lua
├── Dsp
│ ├── Adsr.lua
│ ├── Const.lua
│ ├── Control.lua
│ ├── Filter.lua
│ ├── Mod.lua
│ ├── Noise.lua
│ ├── Osc.lua
│ ├── Pan.lua
│ ├── Pitchshift.lua
│ ├── Reverb.lua
│ ├── Saw.lua
│ ├── Square.lua
│ └── Width.lua
├── Fluidsynth.lua
├── Gui.lua
├── Jack.lua
├── Linuxsampler.lua
├── Makefile
├── Metro.lua
├── Mixer.lua
├── Poly.lua
├── jack_c.c
└── jack_c.so
├── test
├── blip.lua
├── filter.lua
├── gen.lua
├── looper.lua
├── pitch-shift.lua
├── reich.lua
├── simple-piano.lua
└── simple-synth.lua
├── vimplugin.lua
└── worp
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | CFLAGS += -Wall -Werror
3 | CFLAGS += -shared -fPIC
4 | CFLAGS += $(shell pkg-config --cflags luajit)
5 | CFLAGS += -g
6 |
7 | LDFLAGS += $(shell pkg-config --libs luajit)
8 | LDFLAGS += -lrt -lcrypt
9 | LDFLAGS += -g
10 |
11 | default:
12 | make all
13 |
14 | %:
15 | make -C app $@
16 | make -C lib $@
17 |
18 | export CFLAGS LDFLAGS
19 |
20 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 |
2 |
3 | Worp is an experimental sound / music / DSP engine written in LuaJIT, currently
4 | in early alpha state.
5 |
6 | The main Worp web site and documentation can be found at http://worp.zevv.nl
7 |
8 | == Some highlights:
9 |
10 | - Built on LuaJIT: the ease of using a high level language, but getting near
11 | native C performance. Even low-level DSP functions (filters, reverb) can be
12 | coded in Lua, still giving great performance.
13 |
14 | - Live coding: add or update code while the application is running through the
15 | use of a vim plugin. New chunks can be sent and compiled on the fly.
16 |
17 | - Bindings for Fluidsynth and midi support
18 |
19 |
20 | == License
21 |
22 | Copyright (C) 2013 Ico Doornekamp.
23 |
24 | This program is free software; you can redistribute it and/or modify it under
25 | the terms of the GNU General Public License version 2 as published by the Free
26 | Software Foundation.
27 |
28 |
--------------------------------------------------------------------------------
/app/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all: luaposix.so
3 |
4 | LDFLAGS += -lrt -lcrypt
5 |
6 | %.so: %.c
7 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
8 |
9 | clean:
10 | rm -f *.so
11 |
--------------------------------------------------------------------------------
/app/autoload.lua:
--------------------------------------------------------------------------------
1 |
2 | local mtime = {}
3 |
4 | function autoload(id, parent, path)
5 |
6 | local function lookup(env, s)
7 |
8 | -- Check if parent provides
9 |
10 | local v = rawget(parent, s)
11 | if v then return v end
12 |
13 | -- Check if it is a lib we can load
14 |
15 | local fname = "%s/%s.lua" % { path, s }
16 | local stat = P.stat(fname)
17 | if stat and stat.type == "regular" then
18 | logf(LG_DBG, "Loading library %s", fname)
19 | local chunk, err = loadfile(fname, "t", env)
20 | if chunk then
21 | local ok = safecall(chunk)
22 | if not ok then
23 | logf(LG_WRN, "%s", err)
24 | end
25 | else
26 | logf(LG_WRN, "%s", err)
27 | end
28 | local v = rawget(env, s)
29 | if v then return v end
30 | end
31 |
32 | -- Check if lib subdirectory
33 |
34 | local fname = "%s/%s" % { path, s }
35 | local stat = P.stat(fname)
36 | if stat and stat.type == "directory" then
37 | return autoload(path, sandbox.env, "lib/" .. s)
38 | end
39 |
40 | -- Fallback
41 |
42 | return parent[s]
43 | end
44 |
45 | return setmetatable({}, {
46 | __index = function(env, s)
47 | v = lookup(env, s)
48 | env[s] = v
49 | return v
50 | end,
51 | __tostring = function(t)
52 | return "Autoload library %q" % id
53 | end,
54 | })
55 |
56 | end
57 |
58 | -- vi: ft=lua ts=3 sw=3
59 |
--------------------------------------------------------------------------------
/app/co.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Coroutine shortcuts
4 | --
5 |
6 | function sleep(d)
7 | at(d, resumer())
8 | coroutine.yield()
9 | end
10 |
11 |
12 | --
13 | -- Return function that will resume the running coroutine, propagating
14 | -- errors
15 | --
16 |
17 | function resumer(co)
18 | co = co or coroutine.running()
19 | return function(...)
20 | local function aux(ok, ...)
21 | if ok then
22 | return ...
23 | else
24 | print(... .. debug.traceback(co, "", 1), 0)
25 | end
26 | end
27 | return aux(coroutine.resume(co, ...))
28 | end
29 | end
30 |
31 | -- vi: ft=lua ts=3 sw=3
32 |
--------------------------------------------------------------------------------
/app/evq.lua:
--------------------------------------------------------------------------------
1 |
2 | t_now = 0
3 |
4 | local evs = {}
5 | local fds = {}
6 | local t_start
7 | local running = true
8 |
9 | --
10 | -- Schedule function 'fn' to be called in 't' seconds. 'fn' can be as string,
11 | -- which will be resolved in the 'env' table at calling time
12 | --
13 |
14 | function at(t, fn, ...)
15 | local ev = {
16 | t_when = t_now + t,
17 | fn = fn,
18 | args = { ... }
19 | }
20 | table.insert(evs, ev)
21 | table.sort(evs, function(a, b)
22 | return a.t_when > b.t_when
23 | end)
24 | end
25 |
26 |
27 | --
28 | -- Play a note for the given duration using the given sound generator
29 | --
30 |
31 | function play(fn, note, vol, dur)
32 | fn(note, vol or 127)
33 | at(dur * 0.99, function()
34 | fn(note, 0)
35 | end)
36 | end
37 |
38 |
39 | --
40 | -- Return monotonic time, starts at zero at first invocation
41 | --
42 |
43 | function time()
44 | local s, ns = P.clock_gettime(P.CLOCK_MONOTONIC)
45 | local t = s + ns / 1e9
46 | t_start = t_start or t
47 | return t - t_start
48 | end
49 |
50 |
51 | --
52 | -- Register the given file descriptor to the main poll() looP. 'fn' is called
53 | -- when new data is available on the fd
54 | --
55 |
56 | function watch_fd(fd, fn)
57 | assert(fn)
58 | assert(type(fn) == "function")
59 | fds[fd] = { events = { IN = true }, fn = fn }
60 | end
61 |
62 |
63 | --
64 | -- Exit event loop
65 | --
66 |
67 | local stop_list = {}
68 |
69 | function stop()
70 | for fn in pairs(stop_list) do
71 | fn()
72 | end
73 | running = false
74 | end
75 |
76 |
77 | function on_stop(fn)
78 | stop_list[fn] = true
79 | end
80 |
81 |
82 | P.signal(P.SIGINT, function()
83 | print("")
84 | running = false
85 | end)
86 |
87 |
88 | --
89 | -- Run the main event loop, scheduling timers and handling file descriptors
90 | --
91 |
92 | function mainloop()
93 |
94 | while running do
95 |
96 | local dt = 10
97 | local ev = evs[#evs]
98 |
99 | if ev then
100 | dt = math.min(ev.t_when - time())
101 | end
102 |
103 | if dt > 0 then
104 |
105 | local fds2 = {}
106 | for k, v in pairs(fds) do fds2[k] = v end
107 |
108 | local r, a = P.poll(fds2, dt * 1000)
109 | if r and r > 0 then
110 | for fd in pairs(fds2) do
111 | if fds2[fd].revents and fds[fd].revents.IN then
112 | t_now = time()
113 | local ok, rv = safecall(fds2[fd].fn)
114 | if not ok or rv == false then
115 | fds[fd] = nil
116 | end
117 | end
118 | end
119 | end
120 | end
121 |
122 | while ev and time() > ev.t_when do
123 | table.remove(evs)
124 | t_now = ev.t_when
125 | safecall(ev.fn, unpack(ev.args))
126 | ev = evs[#evs]
127 | end
128 |
129 | end
130 | end
131 |
132 | -- vi: ft=lua ts=3 sw=3
133 |
--------------------------------------------------------------------------------
/app/getopt.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Simple getopt
4 | --
5 |
6 | function getopt(arg_in, optstring)
7 |
8 | local opt_out = {}
9 | local arg_out = {}
10 |
11 | while arg_in[1] do
12 | local char, val = string.match(arg_in[1], "^-(.)(.*)")
13 | if char then
14 | local found, needarg = string.match(optstring, "(" ..char .. ")(:?)")
15 | if not found then
16 | print("Invalid option '%s'\n" % char)
17 | return nil
18 | end
19 | if needarg == ":" then
20 | if not val or string.len(val)==0 then
21 | table.remove(arg, 1)
22 | val = arg_in[1]
23 | end
24 | if not val then
25 | print("option '%s' requires an argument\n" % char)
26 | return nil
27 | end
28 | else
29 | val = true
30 | end
31 | opt_out[char] = val
32 | else
33 | table.insert(arg_out, arg_in[1])
34 | end
35 | table.remove(arg_in, 1)
36 | end
37 | return opt_out, arg_out
38 | end
39 |
40 |
41 | -- vi: ft=lua ts=3 sw=3
42 |
--------------------------------------------------------------------------------
/app/list.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Generic list class with handy operators
4 | --
5 |
6 |
7 | local list_meta = {
8 |
9 | __index = {
10 |
11 | -- Add item to end of list
12 |
13 | append = function(list, e)
14 | list[#list+1] = e
15 | return list
16 | end,
17 |
18 | -- Add item to end of list
19 |
20 | push = function(list, e)
21 | list[#list+1] = e
22 | return list
23 | end,
24 |
25 | -- Insert item at front of list
26 |
27 | prepend = function(list, e)
28 | table.insert(list, 1, e)
29 | return list
30 | end,
31 |
32 | unshift = function(list, e)
33 | table.insert(list, 1, e)
34 | return list
35 | end,
36 |
37 | -- Return copy of list
38 |
39 | copy = function(list)
40 | local list2 = List()
41 | for e in list:each() do
42 | list2:append(e)
43 | end
44 | return list2
45 | end,
46 |
47 | -- Return first element
48 |
49 | head = function(list)
50 | return list[1]
51 | end,
52 |
53 | -- Return copy of list omitting first element
54 |
55 | tail = function(list)
56 | return list:splice(2)
57 | end,
58 |
59 | -- transpose list-of-list
60 |
61 | zip = function(list)
62 | local list2 = List()
63 | for i, row in ipairs(list) do
64 | for j, col in ipairs(row) do
65 | list2[j] = list2[j] or List()
66 | list2[j][i] = col
67 | end
68 | end
69 | return list2
70 | end,
71 |
72 | -- fold function
73 |
74 | fold = function(list, fn)
75 | local r
76 | for a in list:each() do
77 | r = r and fn(r, a) or a
78 | end
79 | return r
80 | end,
81 |
82 | -- Remove and return last element
83 |
84 | pop = function(list)
85 | local e = list[1]
86 | table.remove(list)
87 | return e
88 | end,
89 |
90 | -- Remove and return first element
91 |
92 | shift = function(list)
93 | local e = list[1]
94 | table.remove(list, 1)
95 | return e
96 | end,
97 |
98 | -- Take first n elements
99 |
100 | take = function(list, n)
101 | return list:splice(1, n)
102 | end,
103 |
104 | -- Return splice of list in given range
105 |
106 | splice = function(list, a, b)
107 | local o = List()
108 | for i = (a or 1), (b or #list) do
109 | o:append(list[i])
110 | end
111 | return o
112 | end,
113 |
114 | -- Return a new list with the elements in reverse order
115 |
116 | reverse = function(list)
117 | local list2 = List()
118 | local n = #list
119 | for i = 1, n do
120 | list2[i] = list[n-i+1]
121 | end
122 | return list2
123 | end,
124 |
125 | -- Iterate over each element in the list
126 |
127 | each = function(list)
128 | local n = 0
129 | return function()
130 | n = n + 1
131 | if list[n] then return list[n] end
132 | end
133 | end,
134 |
135 | -- functional map
136 |
137 | map = function(list, fn)
138 | if type(fn) == "string" then fn = lambda(fn) end
139 | local l2 = List()
140 | for e in list:each() do
141 | l2:append(fn(e))
142 | end
143 | return l2
144 | end,
145 |
146 | -- functional map on 2 tables
147 |
148 | map2 = function(list, list2, fn)
149 | local out = List()
150 | for i = 1, #list do
151 | out:append(fn(list[i], list2[i]))
152 | end
153 | return out
154 | end,
155 |
156 | -- Sort
157 |
158 | sort = function(list, fn)
159 | table.sort(list, fn)
160 | return list
161 | end,
162 |
163 | -- Concatenate all elements into a string
164 |
165 | concat = function(list, sep)
166 | return table.concat(list, sep)
167 | end,
168 |
169 | -- Unpack to argument list
170 |
171 | unpack = function(list)
172 | return unpack(list)
173 | end,
174 |
175 | -- Return number of elements on the list
176 |
177 | size = function(list)
178 | return #list
179 | end,
180 |
181 | -- Return true if empty
182 |
183 | is_empty = function(list)
184 | return #list == 0
185 | end,
186 |
187 | -- Return true if the given item is present in the list
188 |
189 | contains = function(list, e)
190 | for n = 1, #list do
191 | if list[n] == e then return true
192 | end
193 | end
194 | end,
195 |
196 | remove = function(list, e)
197 | for n = 1, #list do
198 | if list[n] == e then
199 | table.remove(list, n)
200 | end
201 | end
202 |
203 | end
204 |
205 | }
206 | }
207 |
208 |
209 | function List(t)
210 | local list = t or {}
211 | setmetatable(list, list_meta)
212 | return list
213 | end
214 |
215 |
216 |
217 | -- vi: ft=lua ts=3 sw=3
218 |
219 |
--------------------------------------------------------------------------------
/app/log.lua:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/app/log.lua
--------------------------------------------------------------------------------
/app/misc.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Create a '%' operator on strings for ruby- and python-like formatting
4 | --
5 |
6 | getmetatable("").__mod = function(a, b)
7 | if not b then
8 | return a
9 | elseif type(b) == "table" then
10 | return string.format(a, unpack(b))
11 | else
12 | return string.format(a, b)
13 | end
14 | end
15 |
16 |
17 | --
18 | -- Fix up line numbers of loaded chunks in stack traces and error messages
19 | --
20 |
21 | function fixup_error(msg)
22 | return msg:gsub('%[string "live (.-):(%d+)"]:(%d+)', function(n, l1, l2)
23 | return n .. ":" .. (l1+l2-1)
24 | end)
25 | end
26 |
27 |
28 | --
29 | -- Safe call: calls the given function with an error handler, print error
30 | -- and stacktrace to stdout
31 | --
32 |
33 | function safecall(fn, ...)
34 | local function errhandler(err)
35 | local msg = debug.traceback("Error: " .. err, 3)
36 | print(fixup_error(msg))
37 | end
38 | if type(fn) == "string" then fn = sandbox:get(fn) end
39 | return xpcall(fn, errhandler, ...)
40 | end
41 |
42 |
43 | --
44 | -- Dump lua data structure
45 | --
46 |
47 | function dump(obj, maxdepth)
48 |
49 | maxdepth = maxdepth or 999
50 | local dumped = {}
51 | local fd = io.stderr
52 |
53 | local function _dumpf(obj, depth)
54 |
55 | local i, n, v
56 |
57 | if(type(obj) == "table") then
58 | fd:write("{")
59 | if depth < maxdepth then
60 | if not dumped[obj] then
61 | dumped[obj] = true
62 | for n, v in pairs(obj) do
63 | fd:write("\n");
64 | fd:write(string.rep(" ", depth+1))
65 | _dumpf(n, depth+1);
66 | fd:write(" = ")
67 | _dumpf(v, depth+1);
68 | fd:write(", ")
69 | end
70 | fd:write("\n")
71 | fd:write(string.rep(" ", depth))
72 | else
73 | fd:write(" *** ")
74 | end
75 | else
76 | fd:write(" <<< ")
77 | end
78 | fd:write("}")
79 | local mt = getmetatable(obj)
80 | if mt then
81 | fd:write(" metatable: ")
82 | _dumpf(mt, depth)
83 | end
84 | elseif(type(obj) == "string") then
85 | fd:write("%q" % obj)
86 | elseif(type(obj) == "number") then
87 | fd:write(obj)
88 | elseif(type(obj) == "boolean") then
89 | fd:write(obj and "true" or "false")
90 | elseif(type(obj) == "function") then
91 | local i = debug.getinfo(obj)
92 | fd:write("function(%s:%s)" % { i.short_src, i.linedefined })
93 | else
94 | fd:write("(%s)" % type(obj))
95 | end
96 |
97 | if depth == 0 then
98 | fd:write("\n")
99 | io.flush()
100 | end
101 | end
102 |
103 | _dumpf(obj, 0)
104 | end
105 |
106 |
107 | --
108 | -- Serialize lua data to string
109 | --
110 |
111 | function serialize(o)
112 | local t = type(o)
113 | if t == "string" then
114 | return string.format("%q", o)
115 | elseif t == "boolean" then
116 | return tostring(o)
117 | elseif t == "number" then
118 | return tostring(o)
119 | elseif t == "table" then
120 | local out = {}
121 | local done = {}
122 | for i, c in ipairs(o) do
123 | table.insert(out, serialize(c))
124 | done[i] = true
125 | end
126 | for k, c in pairs(o) do
127 | if not done[k] then
128 | if type(k) ~= "function" and type(k) ~= "table" then
129 | table.insert(out, "[" .. serialize(k) .. "]=" .. serialize(c))
130 | end
131 | end
132 | end
133 | return "{" .. table.concat(out, ",") .. "}"
134 | else
135 | return "nil"
136 | end
137 | end
138 |
139 | -- vi: ft=lua ts=3 sw=3
140 |
--------------------------------------------------------------------------------
/app/sandbox.lua:
--------------------------------------------------------------------------------
1 |
2 | Sandbox = {}
3 |
4 | local function sandbox_load(sandbox, code, name)
5 | local fn, err = load(code, name, "t", sandbox.env)
6 | if fn then
7 | local co = coroutine.create(fn)
8 | safecall(resumer(co))
9 | else
10 | print("Error: " .. fixup_error(err))
11 | end
12 | end
13 |
14 |
15 | local function sandbox_get(sandbox, name)
16 | return sandbox.env[name]
17 | end
18 |
19 |
20 | function Sandbox:new()
21 |
22 | local sandbox = {
23 |
24 | -- methods
25 |
26 | load = sandbox_load,
27 | get = sandbox_get,
28 |
29 | -- data
30 |
31 | srate = 44100,
32 | env = {}
33 | }
34 |
35 | sandbox.env = autoload("sandbox", _G, "lib")
36 | sandbox.env._ENV = sandbox.env
37 |
38 | return sandbox
39 | end
40 |
41 | -- vi: ft=lua ts=3 sw=3
42 |
--------------------------------------------------------------------------------
/app/strict.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- strict.lua
3 | -- checks uses of undeclared global variables
4 | -- All global variables must be 'declared' through a regular assignment
5 | -- (even assigning nil will do) in a main chunk before being used
6 | -- anywhere or assigned to inside a function.
7 | --
8 |
9 | local mt = getmetatable(_G)
10 | if mt == nil then
11 | mt = {}
12 | setmetatable(_G, mt)
13 | end
14 |
15 | mt.__declared = {}
16 |
17 | mt.__newindex = function (t, n, v)
18 | if not mt.__declared[n] then
19 | local w = debug.getinfo(2, "S").what
20 | if w ~= "main" and w ~= "C" then
21 | error("assign to undeclared variable '"..n.."'", 2)
22 | end
23 | mt.__declared[n] = true
24 | end
25 | rawset(t, n, v)
26 | end
27 |
28 | mt.__index = function (t, n)
29 | if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then
30 | error("Accessing undeclared variable '"..n.."'", 2)
31 | end
32 | return rawget(t, n)
33 | end
34 |
35 |
--------------------------------------------------------------------------------
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | webalizer
3 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 |
2 | PAGES := index architecture concepts jack dsp modules examples live_coding gui libs about
3 |
4 | LIBS := $(wildcard ../lib/*.lua ../lib/Dsp/*.lua)
5 |
6 | HTML += $(addsuffix .html, $(PAGES))
7 | HTML += libs.html
8 |
9 | all: $(HTML)
10 |
11 | %.html: %.txt bin/txt2html
12 | bin/txt2html $(PAGES) < $< > $@
13 |
14 | libs.txt: bin/libdocs $(LIBS)
15 | bin/libdocs $(LIBS) > $@
16 |
17 | clean:
18 | rm -f *.html
19 |
20 |
--------------------------------------------------------------------------------
/doc/about.txt:
--------------------------------------------------------------------------------
1 |
2 | == Dependencies
3 |
4 | Worp has the following dependencies:
5 |
6 | * LuaJIT >= 2.0.0
7 | * Fluidsynth (optional)
8 | * LGI (optional)
9 | * Linuxsampler (optional)
10 |
11 |
12 | == Misc
13 |
14 | During the development of Worp I learned of the existence of Impromptu and
15 | SuperCollider, which both have features similar to worp (Event scheduling, live
16 | coding, etc). I've shamelessly stolen some ideas here and there from both
17 | projects where I saw fit.
18 |
19 | * [http://impromptu.moso.com.au]
20 | * [http://supercollider.sourceforge.net]
21 |
22 | This would probably be a good place to write about the likes and the
23 | differences between these systems and Worp, but I feel I don't know enough
24 | about either to be able to write anything useful here.
25 |
26 |
27 | == License
28 |
29 | Copyright (C) 2013 Ico Doornekamp.
30 |
31 | This program is free software; you can redistribute it and/or modify it under
32 | the terms of the GNU General Public License version 2 as published by the Free
33 | Software Foundation.
34 |
35 |
36 |
--------------------------------------------------------------------------------
/doc/appregiator.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/appregiator.mp3
--------------------------------------------------------------------------------
/doc/architecture.txt:
--------------------------------------------------------------------------------
1 |
2 | = Architecture
3 |
4 | Worp is written mostly in Lua (except for some C code for binding to the OS and
5 | Jack), and is run using LuaJIT. LuaJIT is an alternate implementation of the
6 | Lua language that is highly optimized and can deliver near native performance
7 | for numeric applications, which comes in handy for generating sound and doing
8 | digital signal processing.
9 |
10 | == Running code
11 |
12 | At startup, Worp creates a Lua sandbox and is ready to receive Lua code to
13 | process. Any scripts to run at startup can be passed on the command line, or
14 | chunks of Lua code can be sent over a local TCP socket to the Worp process. The
15 | network connection is used by a Vim plug-in to send blocks or lines of code
16 | directly from the editor to Worp, which will compile and run the code without
17 | disrupting running audio.
18 |
19 | == Loading libraries
20 |
21 | When worp encounters a reference to an undefined variable in the sandbox, it
22 | will check if a library is available which will provide this symbol, and load
23 | it on the fly.
24 |
25 | The current method looks for a file with the given name in the ./lib directory,
26 | load the chunk and use the return value of the chunk.
27 |
--------------------------------------------------------------------------------
/doc/bin/libdocs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | table.sort(arg)
4 |
5 | print [[
6 |
7 | = Libraries
8 |
9 | This page briefly describes all available library functions.
10 |
11 |
12 | ]]
13 |
14 | for _, fname in ipairs(arg) do
15 |
16 | local lib = fname:gsub("../lib/", ""):gsub(".lua", ""):gsub("/", ":")
17 |
18 | print("")
19 | print("== " .. lib)
20 | print("")
21 |
22 | local f = io.open(fname)
23 | if f then
24 | local a = f:read("*a")
25 | f:close()
26 |
27 | -- Get first comment block of file
28 |
29 | doc = a:match("^%s*(%-%-.-)\n\n")
30 | if doc then
31 | doc = ("\n" .. doc):gsub("%-%- ?", "")
32 | print(doc)
33 | end
34 |
35 | -- Cut out 'controls' section of module, assuming only
36 | -- a single module is defined in each file
37 |
38 | cs = a:match("controls%s+=%s+(%b{})")
39 | if cs then
40 | ok, cs = pcall(loadstring("return " .. cs))
41 | if ok then
42 | print("Controls:")
43 | print("")
44 | for _, c in ipairs(cs) do
45 | local range = ""
46 | if c.options and #c.options > 0 then
47 | range = table.concat(c.options, "/")
48 | else
49 | range = (c.min or 0) .. ".." .. (c.max or 1)
50 | end
51 | print("* " .. c.id .. " : " .. c.description .. " (" .. (c.type or "number") .. ", " .. range .. ")")
52 | end
53 | end
54 | end
55 | end
56 | end
57 |
58 |
59 | -- vi: ft=lua ts=3 sw=3
60 |
--------------------------------------------------------------------------------
/doc/bin/txt2html:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local menu = {}
4 | for i, page in ipairs(arg) do
5 | menu[i] = "" .. page:gsub("_", " ") .. ""
6 | end
7 | menu = table.concat(menu, " | ")
8 |
9 |
10 | function code(s)
11 | local fname = os.tmpname()
12 | local fd = io.open(fname, "w")
13 | fd:write(s)
14 | fd:close()
15 |
16 | local h = io.popen("highlight --fragment --css=/dev/null --syntax lua " .. fname, "r")
17 | local v = h:read("*a")
18 | h:close()
19 |
20 | return "
" .. v .. "
\n\n"
21 | end
22 |
23 | function link(s)
24 | if s:find("png$") then
25 | return ""
26 | elseif s:find("mp3$") then
27 | return [[
28 |
29 | ]] .. s .. [[
30 |
31 |
32 |
33 | ]]
34 | elseif s:find("vimeo") then
35 | local id = s:match("%d+")
36 | return [[]]
38 | else
39 | local u = s
40 | if not u:find("^http") then u = u .. ".html" end
41 | return "" .. s .. ""
42 | end
43 | end
44 |
45 | function list(s, n)
46 | return "
" .. s:gsub("\n%*", "\n
") .. "
\n\n" .. n
47 | end
48 |
49 | local toc = {}
50 |
51 | function header(s)
52 | table.insert(toc, s)
53 | end
54 |
55 | local c = io.read("*a")
56 |
57 | c = c:gsub("<", "<")
58 | c = c:gsub(">", ">")
59 | c = c:gsub("'(%w+)'", "%1")
60 | c = c:gsub("_(%w+)_", "%1")
61 | c = c:gsub("\n=+ ([^\n]+)", header)
62 | c = c:gsub("\n= ([^\n]+)", "
%1
")
63 | c = c:gsub("\n== ([^\n]+)", "
%1
")
64 | c = c:gsub("\n(\n .-)\n\n", code)
65 | c = c:gsub("%[([%S+]+)%]", link)
66 | c = c:gsub("NOTE: (.-)\n\n", "
%1
\n\n")
67 | c = c:gsub("(\n%*.-)\n([^%s%*])", list)
68 | c = c:gsub("\n\n", "\n\n
\n")
69 |
70 | for i = 1, #toc do
71 | local s = toc[i]
72 | toc[i] = "" .. s .. ""
73 | end
74 | toc = table.concat(toc, " ")
75 |
76 | print([[
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | ]] .. menu .. [[
88 |
89 |
90 |
91 |
92 |
93 |
94 | ]] .. toc .. [[
95 |
96 |
97 |
98 | ]] .. c .. [[
99 |
100 |
101 |
102 |
103 |
104 |
105 | ]])
106 |
107 |
108 | -- vi: ft=lua ts=3 sw=3
109 |
--------------------------------------------------------------------------------
/doc/concepts.txt:
--------------------------------------------------------------------------------
1 |
2 | = Basic concepts
3 |
4 | Worp is built on the following core concepts:
5 |
6 | == The sandbox
7 |
8 | Worp scripts are run in a Lua sandbox. This is simple a separate namespace in
9 | the Lua interpreter where code can be loaded without interfering with Worp
10 | itself.
11 |
12 | Any variables and functions which are not defined local will end up being
13 | globals in the sandbox namespace. While globals are usually considered bad
14 | practice, they come in handy when using live programming: when new chunks are
15 | sent from the editor to Worp they are run in the same sandbox and can access or
16 | alter any defined variables.
17 |
18 | == Instruments
19 |
20 | An instrument in Worp is basically a function which, when called, will start
21 | generating sound at the given note and given volume. Calling the same function
22 | more than once will play more then one note for polyphonic instruments, and
23 | calling the same function for an active note with the volume set to zero will
24 | mute the note.
25 |
26 | Worp uses midi note numbers for instruments, where 60 is defined as the middle
27 | C at 261.6 Hz. Volume is given in the range 0 to 1.
28 |
29 | For example, the following code will play the middle C on piano for 1 second at
30 | medium volume (assuming the function 'piano' has been defined earlier):
31 |
32 | piano(60, 0.8) at(1, function()
33 | piano(60, 0)
34 | end)
35 |
36 | Because Worp is all about playing notes, the 'play()' function is provided as a
37 | shorthand. Again playing the middle C for one second:
38 |
39 | play(piano, 0.8, 1)
40 |
41 |
42 | == Time
43 |
44 | Time in Worp is handled by the event scheduler, which does nothing more than
45 | calling functions at a requested time. To schedule a function for future
46 | execution, use the built in function 'at':
47 | at(TIME, FUNCTION [, ...])
48 |
49 | For example, the following line will print the text "Hello world" after one
50 | second:
51 |
52 | at(1, function()
53 | print("Hello", "world")
54 | end)
55 |
56 | The 'at()' function has a shorthand notation for functions which are defined
57 | globally:
58 |
59 | at(1, "print", "Hello", "world")
60 |
61 | Worp takes care not to let time slip unexpectedly. Consider the following
62 | fragment:
63 |
64 | at(1, 'play', piano, 60, 0.3)
65 | at(1, 'play', piano, 72, 0.3)
66 |
67 | If at() would use the current time as reference for scheduling function calls
68 | in the future, there would be no guarantee that the two notes would be played
69 | at the same time: if any time goes by between the two calls to 'at()', the two
70 | calls will be scheduled to run at a different time.
71 |
72 | Instead, at() uses another time reference: the time the current function was
73 | scheduled to run, even if for some reason the current function was scheduled
74 | too early or too late.
75 |
76 | The result is that the following code:
77 |
78 | function tick()
79 | print("tick")
80 | at(1, tick)
81 | end
82 | tick()
83 |
84 | is sure to call the tick() function at exact 1 second intervals on average,
85 | without introducing additional delay caused by the time needed to run the code.
86 |
87 | On top of this function more complex objects such as metronomes and pattern
88 | generators can be built.
89 |
90 |
--------------------------------------------------------------------------------
/doc/dsp.txt:
--------------------------------------------------------------------------------
1 |
2 | = DSP
3 |
4 | Worp comes with a library with basic constructs for doing audio synthesis and
5 | processing. This library provides abstracts for 'modules', which are objects
6 | that process of generate sound, and can be controlled by 'controls'.
7 |
8 | == Modules
9 |
10 | Technicaly, a module is a Lua table with a __call entry in its metatable. This
11 | allows the table to be called like a function, but also to have callable methods.
12 |
13 | Modules are instantiated by calling the appropriate library function, and can
14 | optionally be passed initial values for the controls. The following will generate
15 | a sine oscillator with an initial frequency of 500 Hz:
16 |
17 | osc = Dsp:Osc { f = 1500 }
18 |
19 | osc can be handled as a function, and will generate a new sample each time it is called.
20 |
21 | For example, to see the above oscillator in action try:
22 |
23 | for i = 1, 100 do
24 | print(osc())
25 | end
26 |
27 | The Worp console output will print the sample values:
28 |
29 | 0.21209065141554
30 | 0.41453117669030
31 | 0.59811053049122
32 | ...
33 |
34 | Some modules like filters can also process audio, and will take
35 | one or more sample values when called. To send the output of a 30 Hz saw wave
36 | into a 100 Hz low pass filter with a resonance of 3:
37 |
38 | o = Dsp:Saw { f = 30 }
39 | f = Dsp:Filter { f = 100, Q = 3 }
40 |
41 | for = i 1, 100 do
42 | print(f(o())
43 | end
44 |
45 | For more details, check the [modules] page
46 |
47 |
48 | == Controls
49 |
50 | Modules often have one or more controls to effect the behaviour. The module
51 | object provides a handy :help() method to show what controls are available:
52 |
53 | f = Dsp:Filter { f = 100, Q = 3 }
54 | f:help()
55 |
56 | will print
57 |
58 | Biquad filter:
59 | - type: Filter type (lp/hp/bp/bs/ls/hs/eq/ap)
60 | - f: Frequency (0..20000)
61 | - Q: Resonance (0.1..100)
62 | - gain: Shelf/EQ filter gain (-60..60)
63 |
64 | To set the value of a control, use the :set() method:
65 |
66 | f:set { type = "hp", f = 200 }
67 |
68 |
69 | The Jack library knows about controls, and provides a shorthand to map controls
70 | to midi CC's. The following snippet will map all four filter controls to CC 10,
71 | 11, 12 and 13 on midi channel 1:
72 |
73 | j = Jack:new("worp")
74 | f = Dsp:Filter()
75 | m = Jack:midi("midi")
76 | m:map_mod(1, 10, f)
77 |
78 | To map a single control, use:
79 |
80 | m:map_control(1, 10, f:control "Q")
81 |
82 |
83 | == Generating sound
84 |
85 | This is where the Jack and Dsp libraries come together. The fragment
86 | below will low-pass filter a saw wave, send the result to a jack audio
87 | port called 'worp:synth-out-1', and connect this port to all available
88 | input ports in the system:
89 |
90 | s = Dsp:Saw { f = 50 }
91 | f = Dsp:Filter { ft = "lp", f = 1500, Q = 4 }
92 |
93 | j = Jack:new("worp")
94 |
95 | j:dsp("synth", 0, 1, function()
96 | return f(s())
97 | end)
98 |
99 | j:connect("worp")
100 |
101 |
102 |
--------------------------------------------------------------------------------
/doc/example.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/example.mp3
--------------------------------------------------------------------------------
/doc/examples.txt:
--------------------------------------------------------------------------------
1 |
2 | = Examples
3 |
4 | This page is a bit too empty at this time.
5 |
6 | More examples are available in the /examples directory.
7 |
8 |
9 | == Polyphonic synth appregiator
10 |
11 | A polyphonic synthesizer with filtering envelope, played by a simple appregiator.
12 |
13 |
14 | [appregiator.mp3]
15 |
16 |
17 | j = Jack:new()
18 |
19 | function Synth()
20 |
21 | local o1 = Dsp:Square()
22 | local o2 = Dsp:Saw()
23 | local f = Dsp:Filter { f = 800, Q = 8 }
24 | local adsr = Dsp:Adsr { A = 0.01, D = 0.01, S = 0.3 }
25 | local adsr2 = Dsp:Adsr { A = 0.03, D = 1.00, S = 0.1 }
26 | local pan = Dsp:Pan()
27 |
28 | local depth = 0 + 0.1
29 |
30 | local function instr(note, vel)
31 | if vel > 0 then depth = vel end
32 | local freq = n2f(note)
33 | o1:set { f = freq }
34 | o2:set { f = freq * 4.0 + 0.2 }
35 | adsr:set { vel = vel }
36 | adsr2:set { vel = vel }
37 | pan:set { pan = rr(-0.8, 0.8) }
38 | end
39 |
40 | local function gen()
41 | f:set { f = adsr2() * 3000 * depth + 20}
42 | return pan(0.1 * adsr() * f(o1() + o2() + math.random() * 0.75))
43 | end
44 |
45 | return instr, gen
46 | end
47 |
48 | instr, gen = Poly(Synth)
49 |
50 | rev = Dsp:Reverb { damp = 0.2 }
51 |
52 | j:dsp("synth", 0, 2, function()
53 | return rev(gen())
54 | end)
55 |
56 | j:connect("worp")
57 |
58 | function play2(instr, note, vel, dur)
59 | play(instr, note, vel, dur)
60 | at(0.18*3, play, instr, note + 12, vel * 0.9, dur)
61 | end
62 |
63 | ns = { 34, 22, 70, 34, 65, 34, 17, 74, 36, 72, 53, 58 }
64 |
65 | function appreg(i)
66 |
67 | local n = ns[i]
68 | local v = (i % 5) == 1 and 0.9 or 0.7
69 | play2(instr, n, v, 0.16)
70 |
71 | at(0.18, function()
72 | appreg((i % #ns) + 1)
73 | end)
74 | end
75 |
76 | appreg(1)
77 |
78 |
79 | == Linuxsampler + DSP effects
80 |
81 | The fragment below plays a piano loop with Linuxsampler, adds a wah-wah effect
82 | with a LFO-controlled bandpass filter, and plays some simple reverberated
83 | percussion in the background:
84 |
85 | [example.mp3]
86 |
87 |
88 | j = Jack:new("worp")
89 | l = Linuxsampler:new("piano", "/opt/samples")
90 | m = Metro:new(150, 10)
91 | v = l:add("piano/megapiano.gig", 0)
92 | n = Dsp:Noise()
93 | nf = Dsp:Filter { type = "bp", f = 8000, Q = 5 }
94 | p = Dsp:Pan()
95 | a = Dsp:Adsr { A = 0, D = 0.03, S = 1, R = 0.05 }
96 | r = Dsp:Reverb { }
97 | f = Dsp:Filter { type = "bp", Q = 5 }
98 | lfo = Dsp:Osc { f = 4 / m:t_meas() }
99 |
100 | ns = { 36, 75, 79, 84, 34, 75, 79, 74, 84, 82 }
101 |
102 | function loop(i)
103 | play(v, ns[i], ns[i] < 40 and 0.8 or 0.6, 1.0)
104 | i = (i % #ns) + 1
105 | at(m:t_beat(), loop, i)
106 | end
107 |
108 | j:dsp("wah", 1, 1, function(vi)
109 | f:set { f = lfo() * 500 + 700 }
110 | return f(vi)
111 | end)
112 |
113 | j:dsp("perc", 0, 2, function()
114 | return p( r( nf( a() * n() ) ) )
115 | end)
116 |
117 | function click()
118 | nf:set { f = rr(8000, 12000) }
119 | p:set { pan = rr(-1, 1) }
120 | a:set { vel = rr(0.2, 0.8) }
121 | at(0.01, function() a:set { vel = 0 } end)
122 | at(m:t_beat() * 0.5, "click")
123 | end
124 |
125 | loop(1)
126 | click()
127 |
128 | j:connect("piano", "worp:wah")
129 | j:connect("worp:wah", "system:playback")
130 | j:connect("worp:perc", "system:playback")
131 |
132 |
133 | == Linuxsampler
134 |
135 | Create a linuxsampler instrument, and map this to midi channel 1.
136 |
137 | ls = Linuxsampler:new("synth", "/opt/samples")
138 | j = Jack:new("worp")
139 | midi = j:midi()
140 | piano = ls:add("piano/Bosendorfer.gig", 0)
141 | midi:map_instr(1, piano)
142 | j:connect("synth")
143 | j:connect("worp")
144 |
145 |
--------------------------------------------------------------------------------
/doc/gui.txt:
--------------------------------------------------------------------------------
1 |
2 | = GUI
3 |
4 | Support for a graphical user interface is under development. The Gui library
5 | creates a window where controls and modules can be added.
6 |
7 | == Example
8 |
9 | For example, the following fragment:
10 |
11 | gui = Gui:new()
12 | f = Dsp:Filter()
13 | gui:add_mod(f)
14 |
15 | will generate this user interface window, mapping each control of the module to
16 | a suitable graphical representation:
17 |
18 | [gui.png]
19 |
20 | Turning the knobs will adjust the control values, and any changes in the controls
21 | will reflect in the knob positions.
22 |
23 | NOTE: The current code does not handle fast control updates well, so use with care.
24 |
25 |
26 | == Implementation
27 |
28 | The Gui is generated using Gtk through the LGI library:
29 | [https://github.com/pavouk/lgi]
30 |
31 |
--------------------------------------------------------------------------------
/doc/htdocs/highlight.css:
--------------------------------------------------------------------------------
1 | /* Style definition file generated by highlight 3.8, http://www.andre-simon.de/ */
2 |
3 | /* Highlighting theme: Kwrite Editor */
4 |
5 | pre.hl { margin-left: 40px; color:#000000; font-size:11pt; font-family:'Courier New';}
6 | .hl.num { color:#b07e00; }
7 | .hl.esc { color:#ff00ff; }
8 | .hl.str { color:#bf0303; }
9 | .hl.pps { color:#818100; }
10 | .hl.slc { color:#838183; font-style:italic; }
11 | .hl.com { color:#838183; font-style:italic; }
12 | .hl.ppc { color:#008200; }
13 | .hl.opt { color:#000000; }
14 | .hl.lin { color:#555555; }
15 | .hl.kwa { color:#000000; font-weight:bold; }
16 | .hl.kwb { color:#0057ae; }
17 | .hl.kwc { color:#000000; font-weight:bold; }
18 | .hl.kwd { color:#010181; }
19 |
20 |
--------------------------------------------------------------------------------
/doc/htdocs/style.css:
--------------------------------------------------------------------------------
1 |
2 | html {
3 | margin-left: 0px;
4 | }
5 |
6 | body {
7 | margin-left: 0px;
8 | margin-top: 20px;
9 | }
10 |
11 | div, p, h1, h2, li, a, .warn {
12 | font-family: 'Text Me One';
13 | }
14 |
15 |
16 | #menu {
17 | background-image: url(../img/worp-long.png);
18 | background-repeat: no-repeat;
19 | height: 60px;
20 | padding-top: 60px;
21 | text-align: right;
22 | }
23 |
24 | img {
25 | border: solid 1px black;
26 | }
27 |
28 | .video {
29 | text-align: center;
30 | }
31 |
32 | .mp3 {
33 | margin-left: 100px;
34 | width: 400px;
35 | text-align: center;
36 | }
37 |
38 | #tocmain {
39 | margin-top: 20px;
40 | margin-left: auto;
41 | margin-right: auto;
42 | width: 80%;
43 | }
44 |
45 | #toc {
46 | white-space: nowrap;
47 | text-align: right;
48 | margin-right: 20px;
49 | }
50 |
51 | #main {
52 | }
53 |
54 | td {
55 | vertical-align: top;
56 | }
57 |
58 | p {
59 | margin-left: 20px;
60 | }
61 |
62 | a {
63 | color: #ff3300;
64 | text-decoration: none;
65 | }
66 |
67 | h1 {
68 | font-size: 1.2em;
69 | border-bottom: solid 1px #cccccc;
70 | }
71 |
72 | h2 {
73 | font-size: 1em;
74 | }
75 |
76 |
77 | .warn {
78 | margin-left: 20px;
79 | color: #880000;
80 | border-left: solid 3px red;
81 | padding-left: 5px;
82 | }
83 |
84 | // vi: ts=3 sw=3
85 |
86 |
--------------------------------------------------------------------------------
/doc/htdocs/zplayer.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/htdocs/zplayer.swf
--------------------------------------------------------------------------------
/doc/img/connect-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/img/connect-1.png
--------------------------------------------------------------------------------
/doc/img/connect-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/img/connect-2.png
--------------------------------------------------------------------------------
/doc/img/gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/img/gui.png
--------------------------------------------------------------------------------
/doc/img/worp-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/img/worp-icon.png
--------------------------------------------------------------------------------
/doc/img/worp-long.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/doc/img/worp-long.png
--------------------------------------------------------------------------------
/doc/index.txt:
--------------------------------------------------------------------------------
1 |
2 | = Introduction
3 |
4 | Worp is an experimental sound / music / DSP engine written in LuaJIT, currently
5 | in early alpha state.
6 |
7 | == Example code
8 |
9 | The fragment below plays a piano loop with Linuxsampler, adds a wah-wah effect
10 | with a LFO-controlled bandpass filter, and plays some simple reverberated
11 | percussion in the background:
12 |
13 | [example.mp3]
14 |
15 |
16 | j = Jack:new("worp")
17 | l = Linuxsampler:new("piano", "/opt/samples")
18 | m = Metro:new(150, 10)
19 | v = l:add("piano/megapiano.gig", 0)
20 | n = Dsp:Noise()
21 | nf = Dsp:Filter { type = "bp", f = 8000, Q = 5 }
22 | p = Dsp:Pan()
23 | a = Dsp:Adsr { A = 0, D = 0.03, S = 1, R = 0.05 }
24 | r = Dsp:Reverb { }
25 | f = Dsp:Filter { type = "bp", Q = 5 }
26 | lfo = Dsp:Osc { f = 4 / m:t_meas() }
27 |
28 | ns = { 36, 75, 79, 84, 34, 75, 79, 74, 84, 82 }
29 |
30 | function loop(i)
31 | play(v, ns[i], ns[i] < 40 and 0.8 or 0.6, 1.0)
32 | i = (i % #ns) + 1
33 | at(m:t_beat(), loop, i)
34 | end
35 |
36 | j:dsp("wah", 1, 1, function(vi)
37 | f:set { f = lfo() * 500 + 700 }
38 | return f(vi)
39 | end)
40 |
41 | j:dsp("perc", 0, 2, function()
42 | return p( r( nf( a() * n() ) ) )
43 | end)
44 |
45 | function click()
46 | nf:set { f = rr(8000, 12000) }
47 | p:set { pan = rr(-1, 1) }
48 | a:set { vel = rr(0.2, 0.8) }
49 | at(0.01, function() a:set { vel = 0 } end)
50 | at(m:t_beat() * 0.5, "click")
51 | end
52 |
53 | loop(1)
54 | click()
55 |
56 | j:connect("piano", "worp:wah")
57 | j:connect("worp:wah", "system:playback")
58 | j:connect("worp:perc", "system:playback")
59 |
60 |
61 | == Built on LuaJIT
62 |
63 | Worp is built on LuaJIT, a Just-In-Time Compiler (JIT) for the Lua programming
64 | language. LuaJIT offers the flexibility of the Lua programming language, while
65 | delivering near native performance. This allows for low-level DSP code
66 | (filters, reverbs, etc.) to be written in a high level language.
67 |
68 |
69 | == Libraries
70 |
71 | Worp comes with default bindings for Jack audio and midi, and has built-in
72 | support for Linuxsampler and Fluidsynth. A growing library of DSP primitives is
73 | provided: oscillators, filters, delays, pitch-shifters, etc.
74 |
75 |
76 | == Live coding
77 |
78 | Worp supports live coding through the use of a plug-in for your favourite
79 | editor (if your favourite editor is Vim). Code is sent from the editor into
80 | the Worp process, where it is compiled and executed without disrupting the
81 | current program flow.
82 |
83 | Worp is in early development and subject to lots of changes. Expect
84 | documentation to be outdated, examples to fail and API's to be different then
85 | described.
86 |
87 |
88 | = Running
89 |
90 | The Worp source code is hosted on github. Currently there are no official
91 | releases available, so get the latest code from Github and hope for the best.
92 | There is no 'make install' target, simply start Worp from the source directory.
93 |
94 | Check [http://github.com/zevv/worp] for the latest code.
95 |
96 |
97 | $ git clone https://github.com/zevv/worp.git
98 | $ cd worp
99 | $ make
100 | $ ./worp
101 |
102 |
--------------------------------------------------------------------------------
/doc/jack.txt:
--------------------------------------------------------------------------------
1 |
2 | = Jack
3 |
4 | While Worp itself does not depend on Jack, a Jack library is available which
5 | provides some essential functionalty for making sound and music.
6 |
7 | To use jack from a worp script, first a jack client object must be created. The
8 | following code creates a jack object 'j', and uses 'foo' as the jack client
9 | name:
10 |
11 |
12 | j = Jack("worp")
13 |
14 |
15 | Now that a jack client is available, we can use it to do some work:
16 |
17 | == Ports
18 |
19 | Connecting Jack ports is done with the ':connect()' method of the jack client
20 | object:
21 |
22 |
23 | j:connect(FROM [,TO])
24 |
25 |
26 | The connect function does only one thing: it will try to connect all ports
27 | matched by 'FROM' to all ports of the same types matched by 'TO'. If the 'TO'
28 | argument is not given, it will match any jack port in the sytem with the same
29 | port type.
30 |
31 | For example, given a system with the following (unconnected) jack clients and
32 | ports:
33 |
34 | [connect-1.png]
35 |
36 | When calling:
37 |
38 | j:connect("worp")
39 |
40 | Worp will:
41 |
42 | * find all ports starting with the FROM string 'worp'.
43 |
44 | * find all ports starting with the TO string. Since this string is
45 | not given, it will match all Jack ports.
46 |
47 | * For each of the found FROM ports, it will try to find and
48 | connect to any TO port of the same type, but with the opposite direction
49 | (input/output)
50 |
51 | * If the number of matching ports do not match, it will try to do the
52 | right thing to connect a single mono to two stereo ports.
53 |
54 | The result will be the following graph:
55 |
56 | [connect-2.png]
57 |
58 | For more fine grained control, ports can be specified with more detail. Some
59 | examples:
60 |
61 |
62 | j:connect("worp", "system:playback")
63 | j:connect("worp:synth", "system")
64 | j:connect("worp:synth-in")
65 | j:connect("worp:synth-in-1", "system:capture_1")
66 |
67 |
68 | Check the sample scripts for more examples of the usage of the connect()
69 | function.
70 |
71 |
72 | == MIDI
73 |
74 | The jack client provides two methods for receiving midi data:
75 |
76 |
77 | j:on_note(CHANNEL, FN)
78 | j:on_cc(CHANNEL, CC, FN)
79 |
80 |
81 | For example, the following snippet will print all received midi notes and
82 | control changes for midi channel 1 and CC #5:
83 |
84 |
85 | j:on_note(1, function(note, vel)
86 | print("key", note, vel)
87 | end)
88 |
89 | j:on_cc(1, 5, function(val)
90 | print("cc", val)
91 | end)
92 |
93 |
94 | == Sound
95 |
96 | NOTE: The DSP API is currently being reworked, so the documentation below will be
97 | outdated soon
98 |
99 | The jack client object can be used to connect Worp to a number of Jack audio
100 | ports. The call
101 |
102 |
103 | j:dsp(NAME, INPUT_COUNT, OUTPUT_COUNT, FN)
104 |
105 |
106 | will create a number of audio ports with the name NAME-[in|out]-#, and will
107 | from that moment call the given function FN for each audio sample.
108 |
109 | For example, the following function will generate a 1 Khz sine wave on a single
110 | Jack output port called worp:beep-out-1:
111 |
112 |
113 | i, di = 0, 1000 * math.pi * 2 / srate
114 | jack:dsp("beep", 0, 1, function()
115 | i = i + di
116 | return cos(i)
117 | end)
118 |
119 |
120 | Worp provides a convenient abstraction layer for doing digital signal
121 | processing, for more details see the dsp page.
122 |
123 |
--------------------------------------------------------------------------------
/doc/libs.txt:
--------------------------------------------------------------------------------
1 |
2 | = Libraries
3 |
4 | This page briefly describes all available library functions.
5 |
6 |
7 |
8 |
9 | == Chord
10 |
11 |
12 | == Core
13 |
14 |
15 |
16 | The Core library defines some handy low level functions:
17 |
18 | rl(LIST): return a random item from the given list
19 | rr(MIN, MAX): return a random number from the given range
20 | ntf(NOTE): return frequency of midi note number NOTE
21 |
22 |
23 | == Dsp:Adsr
24 |
25 |
26 |
27 | Attack / Decay / Sustain / Release module.
28 |
29 | This module generates an envelope amplitude between 0.0 and 1.0. When the
30 | 'vel' argument is set to >0 the envelope generator will start (note on),
31 | when 'vel' is set to zero, the generator will go to the decay phase and fall
32 | down to zero amplitude (note off)
33 |
34 | Controls:
35 |
36 | * vel : Velocity (number, 0..1)
37 | * A : Attack (number, 0..10)
38 | * D : Decay (number, 0..10)
39 | * S : Sustain (number, 0..1)
40 | * R : Release (number, 0..10)
41 |
42 | == Dsp:Const
43 |
44 |
45 |
46 | Generator which outputs a constant value in the range 0..1, controlled by
47 | the 'c' control. Useful for easy mapping of a GUI knob or midi CC to a value.
48 |
49 | Controls:
50 |
51 | * c : Value (number, 0..1)
52 |
53 | == Dsp:Control
54 |
55 |
56 | == Dsp:Filter
57 |
58 |
59 |
60 | The Filter module is a basic audio filter with configurable frequency, resonance and gain.
61 | A number of different filter types are provided:
62 |
63 | * hp: High pass
64 | * lp: Low pass
65 | * bp: Band pass
66 | * bs: Band stop (aka, Notch)
67 | * ls: Low shelf
68 | * hs: High shelf
69 | * ap: All pass
70 | * eq: Peaking EQ filter
71 |
72 | The code is based on a document from Robert Bristow-Johnson, check the original
73 | at [http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt] for more details
74 | about the filter implementation
75 |
76 | Controls:
77 |
78 | * type : Filter type (enum, lp/hp/bp/bs/ls/hs/eq/ap)
79 | * f : Frequency (number, 0..20000)
80 | * Q : Resonance (number, 0.1..100)
81 | * gain : Shelf/EQ filter gain (number, -60..60)
82 |
83 | == Dsp:Mod
84 |
85 |
86 | == Dsp:Noise
87 |
88 |
89 |
90 | Random noise generator module, generates noise in the range -1.0 .. +1.0
91 |
92 | The noise module can generate both uniform and gaussian white noise.
93 |
94 | The gaussian noise is based on code from ..., check the original document at
95 | [http://www.taygeta.com/random/gaussian.html] for more details about the
96 | implementation
97 |
98 | Controls:
99 |
100 | * type : Noise type (enum, uniform/gaussian)
101 |
102 | == Dsp:Osc
103 |
104 |
105 |
106 | The Osc module generates a cosine wave at the given frequency. The output
107 | range is -1.0 .. +1.0
108 |
109 | Controls:
110 |
111 | * f : Frequency (number, 0..20000)
112 |
113 | == Dsp:Pan
114 |
115 |
116 |
117 | Stereo pan. Takes one or two inputs and pans them between the two outputs.
118 |
119 | Controls:
120 |
121 | * pan : Pan (number, -1..1)
122 |
123 | == Dsp:Pitchshift
124 |
125 | Controls:
126 |
127 | * f : Factor (number, 0.5..2)
128 |
129 | == Dsp:Reverb
130 |
131 |
132 |
133 | Freeverb reverberator, based on Jezar's public domain C++ sources.
134 |
135 | This is a relatively simple and cheap stereo reverberator, based on a
136 | cascade of comb and allpass filters.
137 |
138 | Controls:
139 |
140 | * wet : Wet volume (number, 0..1)
141 | * dry : Dry volume (number, 0..1)
142 | * room : Room size (number, 0..1.1)
143 | * damp : Damping (number, 0..1)
144 |
145 | == Dsp:Saw
146 |
147 |
148 |
149 | The Saw module generates a sawtooth wave at the given frequency. The output
150 | range is -1.0 .. +1.0
151 |
152 | Controls:
153 |
154 | * f : Frequency (number, 0..20000)
155 |
156 | == Dsp:Square
157 |
158 |
159 |
160 | The Square module generates a square wave at the given frequency and pwm
161 | offset. The output range is -1.0 .. +1.0
162 |
163 | Controls:
164 |
165 | * f : Frequency (number, 0..20000)
166 | * pwm : PWM (number, 0..1)
167 |
168 | == Dsp:Width
169 |
170 | Controls:
171 |
172 | * width : Stereo width (number, 0..2)
173 |
174 | == Fluidsynth
175 |
176 |
177 | == Gui
178 |
179 |
180 |
181 | See the [gui] documentation page
182 |
183 |
184 | == Jack
185 |
186 |
187 |
188 | See the [jack] documentation page
189 |
190 |
191 | == Linuxsampler
192 |
193 |
194 |
195 | The Linuxsampler library provides a simple method to connect to a running
196 | Linuxsample instance. Example:
197 |
198 | l = Linuxsampler:new("synth", "/opt/samples")
199 | piano = l:add("piano/Bosendorfer.gig", 0)
200 | play(piano, 60, 1)
201 |
202 | Create a new linuxsampler connection. The given JACK_NAME is used for
203 | it's audio ports, the SAMPLE_PATH is an optional directory prefix
204 | for sample files:
205 |
206 | l = Linuxsampler:new(JACK_NAME, SAMPLE_PATH)
207 |
208 | Create in instrument function, using the given sample and linuxsampler instrument ID:
209 |
210 | piano = l:add(SAMPLE_FNAME, INSTRUMENT_NUMBER)
211 |
212 |
213 | == Metro
214 |
215 |
216 | == Mixer
217 |
218 |
219 | == Poly
220 |
221 |
222 |
223 | The Poly function provides an easy way to create polyphonic instruments.
224 |
225 |
--------------------------------------------------------------------------------
/doc/live_coding.txt:
--------------------------------------------------------------------------------
1 |
2 | = Live coding
3 |
4 | Worp has been designed to allow live editing of the code without interrupting
5 | sound playback. New chunks of Lua code can be sent to worp trough a local
6 | network socket, and are compiled and executed on the fly. A proof-of-concept
7 | vim plugin is available which can send parts of the current file to a running
8 | worp process.
9 |
10 | Load the plugin with the vim command
11 |
12 | :luafile ~/sandbox/prjs/worp/vimplugin.lua
13 |
14 | The plugin currently remaps the following vim key sequences:
15 |
16 | * ,a : send the whole file to worp
17 | * ,f : send the current function
18 | * ,p : send the current paragraph
19 | * , : send the current line
20 |
21 |
22 | Be careful with locals when using live coding: any functions or variables
23 | defined local during chunk loading will not be available when loading the next
24 | chunk. All code is loaded into the same environment, so use globals instead.
25 |
26 |
27 | test
28 |
29 |
--------------------------------------------------------------------------------
/doc/main.txt:
--------------------------------------------------------------------------------
1 |
2 | = About
3 |
4 | Worp is an experimental sound / music / DSP engine written in LuaJIT, currently
5 | in early alpha state.
6 |
7 | Some highlights:
8 |
9 | == Bindings for Jack, Linuxsampler, Fluidsynth:
10 |
11 | Worp comes with default bindings for Jack audio and midi, and has built-in
12 | support for Linuxsampler and Fluidsynth.
13 |
14 | == Built on LuaJIT:
15 |
16 | Worp is built on LuaJIT, a Just-In-Time Compiler (JIT) for the Lua
17 | programming language. LuaJIT gives the flexibility and robustness of a
18 | proven language, while delivering near native performance. This allows for
19 | low-level DSP code (filters, reverbs, etc.) to be written in a high level
20 | language.
21 |
22 | == Live coding:
23 |
24 | Worp supports live coding through the use of a plug-in for your favourite
25 | editor (if your favourite editor is Vim). Code is sent from the editor into
26 | the Worp process, where it is compiled and executed without disrupting the
27 | current program flow.
28 |
29 | Worp is in early development and subject to lots of changes. Expect
30 | documentation to be outdated, examples to fail and API's to be different then
31 | described.
32 |
--------------------------------------------------------------------------------
/doc/modules.txt:
--------------------------------------------------------------------------------
1 |
2 | = Modules
3 |
4 | The Dsp library uses 'modules' as an abstraction type for generators and signal
5 | processors. A module is a Lua table with a __call metamethod so it can be
6 | called like a regular function.
7 |
8 | == Making
9 |
10 | Modules are created using the Dsp:Mod() constructor. For example, the Pan module
11 | looks like this:
12 |
13 |
14 | function Dsp:Pan(init)
15 |
16 | local v1, v2
17 |
18 | return Dsp:Mod({
19 | description = "Pan",
20 | controls = {
21 | {
22 | id = "pan",
23 | description = "Pan",
24 | min = -1,
25 | max = 1,
26 | default = 0,
27 | fn_set = function(val)
28 | v1 = math.min(1 + val, 1)
29 | v2 = math.min(1 - val, 1)
30 | end,
31 | },
32 | },
33 |
34 | fn_gen = function(i1, i2)
35 | i2 = i2 or i1
36 | return i1*v1, i2*v2
37 | end
38 |
39 | }, init)
40 |
41 | end
42 |
43 |
44 | == Using
45 |
46 | To create an instance of the above module, call the constructor:
47 |
48 | pan = Dsp:Pan()
49 |
50 | This wil generate a Pan module with default values. It is possible
51 | to initialize controls at construction time:
52 |
53 | pan = Dsp:Pan { pan = -0.5 }
54 |
55 | Controls can be adjusted by using the 'set' method.
56 |
57 | pan:set { "pan" = 1 }
58 |
59 | To run the module, simply call the pan() object with the input samples
60 | as arguments:
61 |
62 | o1, o2 = pan(i1, i2)
63 |
64 |
65 | == Details
66 |
67 | What's happening here:
68 |
69 |
70 | function Dsp:Pan(init)
71 |
72 | local v1, v2
73 |
74 | Here the function is defined, with some local variables which will be available to the code
75 | below as upvalues
76 |
77 | return Dsp:Mod({
78 | description = "Pan",
79 |
80 | Calling the constructor, which takes a table with the modules definition.
81 |
82 | controls = {
83 | {
84 | id = "pan",
85 | description = "Pan",
86 | min = -1,
87 | max = 1,
88 | default = 0,
89 | fn_set = function(val)
90 | v1 = math.min(1 + val, 1)
91 | v2 = math.min(1 - val, 1)
92 | end,
93 | },
94 | },
95 |
96 | These are the modules controls. The Pan module has only one which is called 'pan'. This control
97 | ranges from -1 to 1, and has a default of 0. When changed, the fn_set() function is called.
98 |
99 | fn_gen = function(i1, i2)
100 | i2 = i2 or i1
101 | return i1*v1, i2*v2
102 | end
103 |
104 | fn_gen is where the signal is generated; this function is called for each
105 | sample. The Pan module takes one or two inputs, i1 and i2, and generates two
106 | outputs, with the input signal panned left or right, depending on the v1 and v2 constants, which
107 | where calculated in the fn_set() function of the 'pan' control
108 |
109 | }, init)
110 |
111 | The constructor takes an optional 'init' table with initial values for the controls.
112 |
113 | end
114 |
115 | The end.
116 |
117 |
--------------------------------------------------------------------------------
/doc/worp.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
139 |
--------------------------------------------------------------------------------
/examples/appregiator.lua:
--------------------------------------------------------------------------------
1 |
2 | j = Jack:new()
3 |
4 | function Synth()
5 |
6 | local o1 = Dsp:Square()
7 | local o2 = Dsp:Saw()
8 | local f = Dsp:Filter { f = 800, Q = 4 }
9 | local adsr = Dsp:Adsr { A = 0.01, D = 0.01, S = 0.3 }
10 | local adsr2 = Dsp:Adsr { A = 0.03, D = 1.00, S = 0.1 }
11 | local pan = Dsp:Pan()
12 |
13 | local depth = 0 + 0.1
14 |
15 | local function instr(note, vel)
16 | if vel > 0 then depth = vel end
17 | local freq = n2f(note)
18 | o1:set { f = freq }
19 | o2:set { f = freq * 4.0 + 0.2 }
20 | adsr:set { vel = vel }
21 | adsr2:set { vel = vel }
22 | pan:set { pan = rr(-0.8, 0.8) }
23 | end
24 |
25 | local function gen()
26 | f:set { f = adsr2() * 3000 * depth + 20}
27 | return pan(0.1 * adsr() * f(o1() + o2() + math.random() * 0.75))
28 | end
29 |
30 | return instr, gen
31 | end
32 |
33 |
34 | instr, gen = Poly(Synth)
35 |
36 | rev = Dsp:Reverb { damp = 0.2 }
37 |
38 | j:dsp("synth", 0, 2, function()
39 | return rev(gen())
40 | end)
41 |
42 |
43 | j:connect("worp")
44 |
45 | function play2(instr, note, vel, dur)
46 | play(instr, note, vel, dur)
47 | at(0.18*3, play, instr, note - 12, vel * 0.9, dur)
48 | end
49 |
50 | ns = { 34, 22, 70, 34, 65, 34, 17, 74, 36, 72, 53, 58 }
51 |
52 | function appreg(i)
53 |
54 | local n = ns[i]
55 | local v = (i % 5) == 1 and 0.9 or 0.7
56 | play2(instr, n, v, 0.16)
57 |
58 | at(0.18, function()
59 | appreg((i % #ns) + 1)
60 | end)
61 | end
62 |
63 |
64 | appreg(1)
65 |
66 | -- vi: ft=lua ts=3 sw=3
67 |
--------------------------------------------------------------------------------
/examples/gen.lua:
--------------------------------------------------------------------------------
1 |
2 | j = Jack:new("worp")
3 | l = Linuxsampler:new("piano", "/opt/samples")
4 | m = Metro:new(150, 10)
5 | v = l:add("piano/megapiano.gig", 0)
6 | n = Dsp:Noise()
7 | nf = Dsp:Filter { type = "hp", f = 8000, Q = 5 }
8 | p = Dsp:Pan()
9 | a = Dsp:Adsr { A = 0, D = 0.03, S = 1, R = 0.05 }
10 | r = Dsp:Reverb { }
11 | f = Dsp:Filter { type = "bp", Q = 5 }
12 | lfo = Dsp:Osc { f = 4 / m:t_meas() }
13 |
14 | ns = { 36, 75, 79, 84, 34, 75, 79, 74, 84, 82,
15 | 36, 75, 79, 34, 84, 75, 79, 74, 84, 82 }
16 |
17 | function loop(i)
18 | play(v, ns[i], ns[i] < 40 and 0.7 or 0.6, 1.0)
19 | i = (i % #ns) + 1
20 | at(m:t_beat(), loop, i)
21 | end
22 |
23 | j:dsp("wah", 1, 1, function(vi)
24 | f:set { f = lfo() * 500 + 700 }
25 | return f(vi)
26 | end)
27 |
28 |
29 | j:dsp("perc", 0, 2, function()
30 | return p( r( nf( a() * n() ) ) )
31 | end)
32 |
33 | function click()
34 | nf:set { f = rr(8000, 12000) }
35 | p:set { pan = rr(-1, 1) }
36 | a:set { vel = rr(0.2, 0.6) }
37 | at(0.01, function() a:set { vel = 0 } end)
38 | at(m:t_beat() * 0.5, "click")
39 | end
40 |
41 | loop(1)
42 | click()
43 |
44 | j:connect("piano", "worp:wah")
45 | j:connect("worp:wah", "system:playback")
46 | j:connect("worp:perc", "system:playback")
47 |
48 | -- vi: ft=lua ts=3 sw=3
49 |
--------------------------------------------------------------------------------
/examples/scales.lua:
--------------------------------------------------------------------------------
1 |
2 | m = Metro:new(60)
3 |
4 | jack = Jack:new("worp")
5 |
6 | linuxsampler = Linuxsampler:new("synth", "/opt/samples")
7 | linuxsampler:reset()
8 |
9 | harp = linuxsampler:add("concert_harp.gig", 2)
10 | violin = linuxsampler:add("violins.gig", 5)
11 | bass = linuxsampler:add("basses.gig", 0)
12 |
13 | function mkchord(min, max, n, ns)
14 | local os = {}
15 | for i = 1, n do
16 | local o = ns[(i % #ns) + 1]
17 | local p = o
18 | while p < min or p >= max do
19 | p = o + math.random(0, 8) * 12
20 | end
21 | os[#os+1] = p
22 | end
23 | table.sort(os)
24 | return os
25 | end
26 |
27 |
28 | function playchord(c)
29 | local d = m:t_beat() * 2
30 | local ms = Chord:new(0, "minor", c)
31 | play(bass, 36+ms[1], 0.7, d)
32 | local ns = mkchord(40, 63, 7, ms)
33 | local vel = rl { 0.5, 0.6 }
34 | for i = 1, #ns do
35 | local n = ns[i]
36 | play(violin, n, vel/4, d)
37 | end
38 | local prog = {
39 | i7 = { 'vii7', 'v7' },
40 | vii7 = { 'i7' },
41 | v7 = { 'i7', 'vi7' },
42 | vi7 = { 'ii7' },
43 | ii7 = { 'v7', 'vii7' }
44 | }
45 | at(d, "playchord", rl(prog[c]))
46 | end
47 |
48 |
49 | function pulse()
50 | play(harp, rl { 71, 72, 72, 72, 74, 75, 79, }, rl { 0.6, 0.8 }, m:t_beat() / 4 )
51 | at(m:t_beat() / rl { 1.333333, 2, 4, } , "pulse")
52 | end
53 |
54 | pulse()
55 |
56 | math.randomseed(5)
57 |
58 | m:at_beat("pulse")
59 | m:at_beat("playchord", 'i7')
60 |
61 | jack:connect("synth", "system")
62 |
63 |
64 | -- vi: ft=lua ts=3 sw=3
65 |
66 |
--------------------------------------------------------------------------------
/lib/Chord.lua:
--------------------------------------------------------------------------------
1 |
2 | Chord = {}
3 |
4 | local chord_list = {
5 |
6 | -- triad chords
7 |
8 | [""] = { 0, 4, 7 }, -- major
9 | ["-"] = { 0, 3, 7 }, -- minor
10 | ["o" ] = { 0, 3, 6 }, -- diminshed
11 | ["+"] = { 0, 3, 8 }, -- augmented
12 |
13 | -- four note chords
14 |
15 | ["6"] = { 0, 4, 7, 9 }, -- major 6th
16 | ["-6"] = { 0, 3, 7, 9 }, -- minor 6th
17 | ["^7"] = { 0, 4, 7, 11 }, -- major 7th
18 | ["7"] = { 0, 4, 7, 10 }, -- dominant 7th
19 | ["-7"] = { 0, 3, 7, 10 }, -- minor 7th
20 | ["-7b5"] = { 0, 3, 6, 9 }, -- minor 7th ♭5
21 | ["o7"] = { 0, 3, 6, 8 }, -- diminished 7th
22 | ["+7"] = { 0, 4, 6, 8 }, -- augmented 7th
23 | }
24 |
25 |
26 | local diatonic_list = {
27 | major = {
28 | i = { 0, "" },
29 | i7 = { 0, "^7" },
30 | ii = { 2, "-" },
31 | ii7 = { 2, "-7" },
32 | iii = { 4, "-" },
33 | iii = { 4, "-7" },
34 | iv = { 5, "" },
35 | iv7 = { 5, "^7" },
36 | v = { 7, "" },
37 | v7 = { 7, "7" },
38 | vi = { 9, "-" },
39 | vi7 = { 9, "-7" },
40 | vii = { 11, "o" },
41 | vii7 = { 11, "o7" },
42 | },
43 | minor = {
44 | i = { 0, "-" },
45 | i7 = { 0, "-7" },
46 | ii = { 2, "o" },
47 | ii7 = { 2, "o7" },
48 | iii = { 3, "" },
49 | iii7 = { 3, "^7" },
50 | iv = { 5, "-" },
51 | iv7 = { 5, "-7" },
52 | v = { 7, "" },
53 | v7 = { 7, "7" },
54 | vi = { 8, "" },
55 | vi7 = { 8, "^7" },
56 | vii = { 11, "o" },
57 | vii7 = { 11, "o7" },
58 | }
59 | }
60 |
61 |
62 | function Chord:new(base, type, degree)
63 | local ns = {}
64 | local i = diatonic_list[type][degree]
65 | for _, n in ipairs(chord_list[i[2]]) do
66 | ns[#ns+1] = base + i[1] + n
67 | end
68 | return ns
69 | end
70 |
71 | -- vi: ft=lua ts=3 sw=3
72 |
73 |
--------------------------------------------------------------------------------
/lib/Core.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- The Core library defines some handy low level functions:
4 | --
5 | -- rl(LIST): return a random item from the given list
6 | -- rr(MIN, MAX): return a random number from the given range
7 | -- ntf(NOTE): return frequency of midi note number NOTE
8 | --
9 |
10 | function rl(vs)
11 | return vs[math.random(1, #vs)]
12 | end
13 |
14 |
15 | function rr(min, max)
16 | return min + math.random() * (max-min)
17 | end
18 |
19 |
20 | function n2f(n)
21 | return 440 * math.pow(2, (n-57) / 12)
22 | end
23 |
24 |
25 | -- vi: ft=lua ts=3 sw=3
26 |
--------------------------------------------------------------------------------
/lib/Dsp/Adsr.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Attack / Decay / Sustain / Release module.
4 | --
5 | -- This module generates an envelope amplitude between 0.0 and 1.0. When the
6 | -- 'vel' argument is set to >0 the envelope generator will start (note on),
7 | -- when 'vel' is set to zero, the generator will go to the decay phase and fall
8 | -- down to zero amplitude (note off)
9 | --
10 |
11 | function Dsp:Adsr(init)
12 |
13 | local arg = {
14 | A = 0,
15 | D = 0,
16 | S = 1,
17 | R = 0,
18 | on = true,
19 | }
20 |
21 | local state, v = nil, 0
22 | local velocity = 0
23 | local dv_A, dv_D, dv_R, level_S = 0, 0, 0, 1
24 | local dv = 0
25 |
26 | return Dsp:Mod({
27 | description = "ADSR envelope generator",
28 | controls = {
29 | {
30 | id = "vel",
31 | description = "Velocity",
32 | fn_set = function(val)
33 | if val > 0 then
34 | velocity = val
35 | state, dv = "A", dv_A
36 | end
37 | if val == 0 then
38 | state, dv = "R", dv_R
39 | end
40 | end
41 | }, {
42 | id = "A",
43 | description = "Attack",
44 | max = 10,
45 | unit = "s",
46 | default = 0.1,
47 | fn_set = function(val)
48 | dv_A = math.min(1/(srate * val), 1)
49 | end,
50 | }, {
51 | id = "D",
52 | description = "Decay",
53 | max = 10,
54 | unit = "s",
55 | default = 0.1,
56 | fn_set = function(val)
57 | dv_D = math.max(-1/(srate * val), -1)
58 | end,
59 | }, {
60 | id = "S",
61 | description = "Sustain",
62 | default = 1,
63 | fn_set = function(val)
64 | level_S = val
65 | end
66 | }, {
67 | id = "R",
68 | description = "Release",
69 | max = 10,
70 | unit = "s",
71 | default = 0.1,
72 | fn_set = function(val)
73 | dv_R = math.max(-1/(srate * val), -1)
74 | end
75 | },
76 | },
77 | fn_gen = function()
78 | if state == "A" and v >= 1 then
79 | state, dv = "D", dv_D
80 | elseif state == "D" and v < level_S then
81 | state, dv = "S", 0
82 | end
83 | v = v + dv
84 | v = math.max(v, 0)
85 | v = math.min(v, 1)
86 | return v * velocity
87 | end,
88 |
89 | }, init)
90 | end
91 |
92 | -- vi: ft=lua ts=3 sw=3
93 |
--------------------------------------------------------------------------------
/lib/Dsp/Const.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Generator which outputs a constant value in the range 0..1, controlled by
4 | -- the 'c' control. Useful for easy mapping of a GUI knob or midi CC to a value.
5 | --
6 |
7 | function Dsp:Const(init)
8 |
9 | local c
10 |
11 | return Dsp:Mod({
12 | description = "Const",
13 | controls = {
14 | {
15 | id = "c",
16 | description = "Value",
17 | default = 1,
18 | fmt = "%0.2f",
19 | fn_set = function(val) c = val end,
20 | },
21 | },
22 |
23 | fn_gen = function()
24 | return c
25 | end
26 |
27 | }, init)
28 |
29 | end
30 |
31 | -- vi: ft=lua ts=3 sw=3
32 |
--------------------------------------------------------------------------------
/lib/Dsp/Control.lua:
--------------------------------------------------------------------------------
1 |
2 | function Dsp:Control(def, mod)
3 |
4 | local control = {
5 |
6 | -- methods
7 |
8 | get = function(control)
9 | return control.value
10 | end,
11 |
12 | set = function(control, value, update)
13 | if control.type == "enum" and type(value) == "number" then
14 | value = control.options[math.floor(value + 0.5)]
15 | end
16 | if value ~= nil then
17 | control.value = value
18 | for fn in pairs(control.fn_set) do
19 | fn(value)
20 | end
21 | if update ~= false then
22 | mod:update()
23 | end
24 | end
25 | end,
26 |
27 | set_uni = function(control, v, update)
28 | if control.min and control.max then
29 | v = control.min + v * (control.max-control.min)
30 | if control.log then v = (control.max+1) ^ (v/control.max) - 1 end
31 | control:set(v)
32 | end
33 | end,
34 |
35 | on_set = function(control, fn)
36 | control.fn_set[fn] = true
37 | end,
38 |
39 | -- data
40 |
41 | id = def.id or "",
42 | description = def.description or "",
43 | type = def.type or "number",
44 | fmt = def.fmt,
45 | min = def.min or 0,
46 | max = def.max or 1,
47 | options = def.options or {},
48 | log = def.log,
49 | unit = def.unit,
50 | default = def.default or 0,
51 | value = nil,
52 | fn_set = {},
53 | }
54 |
55 | if def.type == "enum" then
56 | control.min, control.max = 1, #def.options
57 | end
58 |
59 | setmetatable(control, {
60 | __tostring = function()
61 | return "control:%s(%s)" % { control.id, control.value }
62 | end
63 | })
64 |
65 | if def.fn_set then
66 | control:on_set(def.fn_set)
67 | end
68 | control:set(def.default, false)
69 |
70 | return control
71 |
72 | end
73 |
74 |
75 | -- vi: ft=lua ts=3 sw=3
76 |
--------------------------------------------------------------------------------
/lib/Dsp/Filter.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- The Filter module is a basic audio filter with configurable frequency, resonance and gain.
4 | -- A number of different filter types are provided:
5 | --
6 | -- * hp: High pass
7 | -- * lp: Low pass
8 | -- * bp: Band pass
9 | -- * bs: Band stop (aka, Notch)
10 | -- * ls: Low shelf
11 | -- * hs: High shelf
12 | -- * ap: All pass
13 | -- * eq: Peaking EQ filter
14 | --
15 | -- The code is based on a document from Robert Bristow-Johnson, check the original
16 | -- at [http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt] for more details
17 | -- about the filter implementation
18 | --
19 |
20 | function Dsp:Filter(init)
21 |
22 | local fs = 44100
23 | local a0, a1, a2, b0, b1, b2
24 | local x0, x1, x2 = 0, 0, 0
25 | local y0, y1, y2 = 0, 0, 0
26 |
27 | local type, f, Q, gain
28 |
29 | return Dsp:Mod({
30 | description = "Biquad filter",
31 | controls = {
32 | fn_update = function()
33 | local w0 = 2 * math.pi * (f / fs)
34 | local alpha = math.sin(w0) / (2*Q)
35 | local cos_w0 = math.cos(w0)
36 | local A = math.pow(10, gain/40)
37 |
38 | if type == "hp" then
39 | b0, b1, b2 = (1 + cos_w0)/2, -(1 + cos_w0), (1 + cos_w0)/2
40 | a0, a1, a2 = 1 + alpha, -2*cos_w0, 1 - alpha
41 |
42 | elseif type == "lp" then
43 | b0, b1, b2 = (1 - cos_w0)/2, 1 - cos_w0, (1 - cos_w0)/2
44 | a0, a1, a2 = 1 + alpha, -2*cos_w0, 1 - alpha
45 |
46 | elseif type == "bp" then
47 | b0, b1, b2 = Q*alpha, 0, -Q*alpha
48 | a0, a1, a2 = 1 + alpha, -2*cos_w0, 1 - alpha
49 |
50 | elseif type == "bs" then
51 | b0, b1, b2 = 1, -2*cos_w0, 1
52 | a0, a1, a2 = 1 + alpha, -2*cos_w0, 1 - alpha
53 |
54 | elseif type == "ls" then
55 | local ap1, am1, tsAa = A+1, A-1, 2 * math.sqrt(A) * alpha
56 | local am1_cos_w0, ap1_cos_w0 = am1 * cos_w0, ap1 * cos_w0
57 | b0, b1, b2 = A*( ap1 - am1_cos_w0 + tsAa ), 2*A*( am1 - ap1_cos_w0 ), A*( ap1 - am1_cos_w0 - tsAa )
58 | a0, a1, a2 = ap1 + am1_cos_w0 + tsAa, -2*( am1 + ap1_cos_w0 ), ap1 + am1_cos_w0 - tsAa
59 |
60 | elseif type == "hs" then
61 | local ap1, am1, tsAa = A+1, A-1, 2 * math.sqrt(A) * alpha
62 | local am1_cos_w0, ap1_cos_w0 = am1 * cos_w0, ap1 * cos_w0
63 | b0, b1, b2 = A*( ap1 + am1_cos_w0 + tsAa ), -2*A*( am1 + ap1_cos_w0 ), A*( ap1 + am1_cos_w0 - tsAa )
64 | a0, a1, a2 = ap1 - am1_cos_w0 + tsAa, 2*( am1 - ap1_cos_w0 ), ap1 - am1_cos_w0 - tsAa
65 |
66 | elseif type == "eq" then
67 | b0, b1, b2 = 1 + alpha*A, -2*cos_w0, 1 - alpha*A
68 | a0, a1, a2 = 1 + alpha/A, -2*cos_w0, 1 - alpha/A
69 |
70 | elseif type == "ap" then
71 | b0, b1, b2 = 1 - alpha, -2*cos_w0, 1 + alpha
72 | a0, a1, a2 = 1 + alpha, -2*cos_w0, 1 - alpha
73 |
74 | else
75 | error("Unsupported filter type " .. type)
76 | end
77 | end,
78 | {
79 | id = "type",
80 | description = "Filter type",
81 | type = "enum",
82 | options = { "lp", "hp", "bp", "bs", "ls", "hs", "eq", "ap" },
83 | default = "lp",
84 | fn_set = function(val) type = val end
85 | }, {
86 | id = "f",
87 | description = "Frequency",
88 | max = 20000,
89 | log = true,
90 | unit = "Hz",
91 | default = 440,
92 | fn_set = function(val) f = val end
93 | }, {
94 | id = "Q",
95 | description = "Resonance",
96 | min = 0.1,
97 | max = 100,
98 | log = true,
99 | default = 1,
100 | fn_set = function(val) Q = val end
101 | }, {
102 | id = "gain",
103 | description = "Shelf/EQ filter gain",
104 | min = -60,
105 | max = 60,
106 | unit = "dB",
107 | default = 0,
108 | fn_set = function(val) gain = val end
109 | }
110 | },
111 |
112 | fn_gen = function(x0)
113 | y2, y1 = y1, y0
114 | y0 = (b0 / a0) * x0 + (b1 / a0) * x1 + (b2 / a0) * x2 - (a1 / a0) * y1 - (a2 / a0) * y2
115 | x2, x1 = x1, x0
116 | return y0
117 | end
118 | }, init)
119 |
120 | end
121 |
122 | -- vi: ft=lua ts=3 sw=3
123 |
--------------------------------------------------------------------------------
/lib/Dsp/Mod.lua:
--------------------------------------------------------------------------------
1 |
2 |
3 | function Dsp:Mod(def, init)
4 |
5 | local mod = {
6 |
7 | -- methods
8 |
9 | update = function(mod)
10 | if def.controls.fn_update then
11 | def.controls.fn_update()
12 | end
13 | end,
14 |
15 | set = function(mod, id, value)
16 | if type(id) == "table" then
17 | for id, value in pairs(id) do
18 | mod:control(id):set(value, false)
19 | end
20 | mod:update()
21 | else
22 | mod:control(id):set(value)
23 | end
24 | end,
25 |
26 | controls = function(mod)
27 | return mod.control_list
28 | end,
29 |
30 | get = function(mod)
31 | return mod
32 | end,
33 |
34 | control = function(mod, id)
35 | return mod.control_list[id]
36 | end,
37 |
38 | help = function(mod)
39 | print("%s:" % { mod.description })
40 | for _, control in ipairs(mod:controls()) do
41 | local range = ""
42 | if #control.options > 0 then
43 | range = table.concat(control.options, "/")
44 | elseif control.min or control.max then
45 | range = "%s..%s" % { control.min or 0, control.max or 1 }
46 | end
47 | print(" - %s: %s (%s)" % { control.id, control.description, range })
48 | end
49 | end,
50 |
51 | -- data
52 |
53 | id = def.id,
54 | description = def.description,
55 | control_list = {}
56 | }
57 |
58 | setmetatable(mod, {
59 | __tostring = function()
60 | return "mod:%s" % { def.description }
61 | end,
62 | __call = function(_, ...)
63 | return def.fn_gen(...)
64 | end,
65 | })
66 |
67 | for i, def in ipairs(def.controls) do
68 | local control = Dsp:Control(def, mod)
69 | mod.control_list[i] = control
70 | mod.control_list[control.id] = control
71 | if init then
72 | control:set(init[control.id], false)
73 | end
74 | end
75 |
76 | mod:update()
77 |
78 | return mod
79 | end
80 |
81 | -- vi: ft=lua ts=3 sw=3
82 |
--------------------------------------------------------------------------------
/lib/Dsp/Noise.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Random noise generator module, generates noise in the range -1.0 .. +1.0
4 | --
5 | -- The noise module can generate both uniform and gaussian white noise.
6 | --
7 | -- The gaussian noise is based on code from ..., check the original document at
8 | -- [http://www.taygeta.com/random/gaussian.html] for more details about the
9 | -- implementation
10 | --
11 |
12 | function Dsp:Noise(init)
13 |
14 | local random, sqrt, log = math.random, math.sqrt, math.log
15 | local type, y1, y2
16 |
17 | local function rand()
18 | return 2 * random() - 1
19 | end
20 |
21 | return Dsp:Mod({
22 | description = "Noise generator",
23 | controls = {
24 | {
25 | id = "type",
26 | description = "Noise type",
27 | type = "enum",
28 | options = { "uniform", "gaussian" },
29 | default = "uniform",
30 | fn_set = function(val) type = val end
31 | },
32 | },
33 |
34 | fn_gen = function()
35 |
36 | if type == "uniform" then
37 | return rand()
38 | end
39 |
40 | if type == "gaussian" then
41 |
42 | local x1, x2, w
43 | repeat
44 | x1, x2 = rand(), rand()
45 | w = x1 * x1 + x2 * x2
46 | until w < 1
47 | w = sqrt((-2 * log(w)) / w)
48 | y1 = x1 * w
49 | y2 = x2 * w
50 | return y1
51 | end
52 | end,
53 |
54 | }, init)
55 |
56 | end
57 |
58 | -- vi: ft=lua ts=3 sw=3
59 |
--------------------------------------------------------------------------------
/lib/Dsp/Osc.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- The Osc module generates a cosine wave at the given frequency. The output
4 | -- range is -1.0 .. +1.0
5 | --
6 |
7 | function Dsp:Osc(init)
8 |
9 | local cos = math.cos
10 | local i, di = 0, 0
11 |
12 | return Dsp:Mod({
13 | id = "osc",
14 | description = "Sine oscillator",
15 | controls = {
16 | {
17 | id = "f",
18 | description = "Frequency",
19 | max = 20000,
20 | log = true,
21 | unit = "Hz",
22 | default = 440,
23 | fn_set = function(val)
24 | di = val * math.pi * 2 / srate
25 | end
26 | },
27 | },
28 | fn_gen = function()
29 | i = i + di
30 | return cos(i)
31 | end
32 | }, init)
33 | end
34 |
35 | -- vi: ft=lua ts=3 sw=3
36 |
--------------------------------------------------------------------------------
/lib/Dsp/Pan.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Stereo pan. Takes one or two inputs and pans them between the two outputs.
4 | --
5 |
6 | function Dsp:Pan(init)
7 |
8 | local v1, v2
9 |
10 | return Dsp:Mod({
11 | description = "Pan",
12 | controls = {
13 | {
14 | id = "pan",
15 | description = "Pan",
16 | min = -1,
17 | max = 1,
18 | default = 0,
19 | fn_set = function(val)
20 | v1 = math.min(1 + val, 1)
21 | v2 = math.min(1 - val, 1)
22 | end,
23 | },
24 | },
25 |
26 | fn_gen = function(i1, i2)
27 | i2 = i2 or i1
28 | return i1*v1, i2*v2
29 | end
30 |
31 | }, init)
32 |
33 | end
34 |
35 | -- vi: ft=lua ts=3 sw=3
36 |
--------------------------------------------------------------------------------
/lib/Dsp/Pitchshift.lua:
--------------------------------------------------------------------------------
1 |
2 | function Dsp:Pitchshift(init)
3 |
4 | local pr, prn, pw = 0, 0, 0
5 | local size = 0x10000
6 | local buf = {}
7 | local nmix = 50
8 | local mix = 0
9 | local dmax = 1200
10 | local win = 250
11 | local step = 10
12 | local factor = 1
13 |
14 | local floor = math.floor
15 |
16 | for i = 0, size do buf[i] = 0 end
17 |
18 | local function wrap(i)
19 | return i % size
20 | end
21 |
22 | local function read(i)
23 | return buf[floor(wrap(i))]
24 | end
25 |
26 | local function read4(fi)
27 | local i = floor(fi)
28 | local f = fi - i
29 | local a, b, c, d = read(i-1), read(i), read(i+1), read(i+2)
30 | local c_b = c-b
31 | return b + f * ( c_b - 0.16667 * (1.-f) * ( (d - a - 3*c_b) * f + (d + 2*a - 3*b)))
32 | end
33 |
34 | local function find(pf, pt1, pt2)
35 |
36 | local cmax, ptmax = 0, pt1
37 |
38 | for pt = pt1, pt2-win, step do
39 | local c = 0
40 | for i = 0, win-1, step do
41 | c = c + read(pf+i) * read(pt+i)
42 | end
43 | if c > cmax then
44 | cmax = c
45 | ptmax = pt
46 | end
47 | end
48 |
49 | return ptmax
50 | end
51 |
52 | return Dsp:Mod({
53 | description = "Pitch shift",
54 | controls = {
55 | {
56 | id = "f",
57 | description = "Factor",
58 | default = 1,
59 | min = 0.5,
60 | max = 2,
61 | fmt = "%0.2f",
62 | fn_set = function(val) factor = val end,
63 | },
64 | },
65 |
66 | fn_gen = function(vi)
67 |
68 | if vi == "factor" then
69 | factor = arg
70 | return
71 | end
72 |
73 | buf[pw] = vi
74 | local vo = read4(pr)
75 |
76 | if mix > 0 then
77 | local f = mix / nmix
78 | vo = vo * f + read4(prn) * (1-f)
79 | mix = mix - 1
80 | if mix == 0 then pr = prn end
81 | end
82 |
83 | if mix == 0 then
84 | local d = (pw - pr) % size
85 | if factor < 1 then
86 | if d > dmax then
87 | mix = nmix
88 | prn = find(pr, pr+dmax/2, pr+dmax)
89 | end
90 | else
91 | if d < win or d > dmax * 2 then
92 | mix = nmix
93 | prn = find(pr, pr-dmax, pr-win)
94 | end
95 | end
96 | end
97 |
98 | pw = wrap(pw + 1)
99 | pr = wrap(pr + factor)
100 | prn = wrap(prn + factor)
101 |
102 | return vo
103 | end
104 | }, init)
105 |
106 | end
107 |
108 | -- vi: ft=lua ts=3 sw=3
109 |
--------------------------------------------------------------------------------
/lib/Dsp/Reverb.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Freeverb reverberator, based on Jezar's public domain C++ sources.
4 | --
5 | -- This is a relatively simple and cheap stereo reverberator, based on a
6 | -- cascade of comb and allpass filters.
7 | --
8 |
9 | function Dsp:Reverb(init)
10 |
11 | local function allpass(bufsize)
12 | local buffer = {}
13 | local feedback = 0
14 | local bufidx = 0
15 | return function(input)
16 | local bufout = buffer[bufidx] or 0
17 | local output = -input + bufout
18 | buffer[bufidx] = input + (bufout*feedback)
19 | bufidx = (bufidx + 1) % bufsize
20 | return output
21 | end
22 | end
23 |
24 | local comb_fb = 0
25 | local comb_damp1 = 0.5
26 | local comb_damp2 = 0.5
27 |
28 | local function fcomb(bufsize, feedback, damp)
29 | local buffer = {}
30 | local bufidx = 0
31 | local filterstore = 0
32 | return function(input)
33 | local output = buffer[bufidx] or 0
34 | local filterstore = (output*comb_damp2) + (filterstore*comb_damp1)
35 | buffer[bufidx] = input + (filterstore*comb_fb)
36 | bufidx = (bufidx + 1) % bufsize
37 | return output
38 | end
39 | end
40 |
41 | local fixedgain = 0.015
42 | local scalewet = 3
43 | local scaledry = 2
44 | local scaledamp = 0.4
45 | local scaleroom = 0.28
46 | local offsetroom = 0.7
47 | local stereospread = 23
48 |
49 | local comb, allp
50 | local gain
51 | local dry, wet1, wet2
52 |
53 | local comb, allp = {
54 | {
55 | fcomb(1116), fcomb(1188),
56 | fcomb(1277), fcomb(1356),
57 | fcomb(1422), fcomb(1491),
58 | fcomb(1557), fcomb(1617),
59 | }, {
60 | fcomb(1116+stereospread), fcomb(1188+stereospread),
61 | fcomb(1277+stereospread), fcomb(1356+stereospread),
62 | fcomb(1422+stereospread), fcomb(1491+stereospread),
63 | fcomb(1557+stereospread), fcomb(1617+stereospread),
64 | }
65 | }, {
66 | {
67 | allpass(556), allpass(441), allpass(341), allpass(225),
68 | }, {
69 | allpass(556+stereospread), allpass(441+stereospread),
70 | allpass(341+stereospread), allpass(225+stereospread),
71 | }
72 | }
73 |
74 | local arg_wet, arg_dry, arg_room, arg_damp
75 |
76 | return Dsp:Mod({
77 | description = "Reverb",
78 | controls = {
79 | fn_update = function()
80 | local initialroom = arg_room
81 | local initialdamp = arg_damp
82 | local initialwet = arg_wet/scalewet
83 | local initialdry = arg_dry or 0
84 | local initialwidth = 2
85 | local initialmode = 0
86 |
87 | local wet = initialwet * scalewet
88 | local roomsize = (initialroom*scaleroom) + offsetroom
89 | dry = initialdry * scaledry
90 | local damp = initialdamp * scaledamp
91 | local width = initialwidth
92 | local mode = initialmode
93 |
94 | wet1 = wet*(width/2 + 0.5)
95 | wet2 = wet*((1-width)/2)
96 |
97 | comb_fb = roomsize
98 | comb_damp1 = damp
99 | comb_damp2 = 1 - damp
100 | gain = fixedgain
101 |
102 | end,
103 | {
104 | id = "wet",
105 | description = "Wet volume",
106 | default = 0.5,
107 | fmt = "%0.2f",
108 | fn_set = function(val) arg_wet = val end
109 | }, {
110 | id = "dry",
111 | description = "Dry volume",
112 | default = 0.5,
113 | fmt = "%0.2f",
114 | fn_set = function(val) arg_dry = val end
115 | }, {
116 | id = "room",
117 | description = "Room size",
118 | max = 1.1,
119 | default = 0.5,
120 | fmt = "%0.2f",
121 | fn_set = function(val) arg_room = val end
122 | }, {
123 | id = "damp",
124 | description = "Damping",
125 | default = 0.5,
126 | fmt = "%0.2f",
127 | fn_set = function(val) arg_damp = val end
128 | }
129 | },
130 | fn_gen = function(in1, in2)
131 | in2 = in2 or in1
132 | local input = (in1 + in2) * gain
133 |
134 | local out = { 0, 0 }
135 |
136 | for c = 1, 2 do
137 | for i = 1, #comb[c] do
138 | out[c] = out[c] + comb[c][i](input)
139 | end
140 | for i = 1, #allp[c] do
141 | out[c] = allp[c][i](out[c])
142 | end
143 | end
144 |
145 | local out1 = out[1]*wet1 + out[2]*wet2 + in1*dry
146 | local out2 = out[2]*wet1 + out[1]*wet2 + in2*dry
147 |
148 | return out1, out2
149 | end
150 | }, init)
151 |
152 | end
153 |
154 | -- vi: ft=lua ts=3 sw=3
155 |
--------------------------------------------------------------------------------
/lib/Dsp/Saw.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- The Saw module generates a sawtooth wave at the given frequency. The output
4 | -- range is -1.0 .. +1.0
5 | --
6 |
7 | function Dsp:Saw(init)
8 |
9 | local v, dv = 0, 0
10 |
11 | return Dsp:Mod({
12 | description = "Saw tooth oscillator",
13 | controls = {
14 | {
15 | id = "f",
16 | description = "Frequency",
17 | max = 20000,
18 | log = true,
19 | unit = "Hz",
20 | default = 440,
21 | fn_set = function(val)
22 | dv = 2 * val / srate
23 | end,
24 | },
25 | },
26 | fn_gen = function()
27 | v = v + dv
28 | if v > 1 then v = v - 2 end
29 | return v
30 | end
31 | }, init)
32 |
33 | end
34 |
35 | -- vi: ft=lua ts=3 sw=3
36 |
--------------------------------------------------------------------------------
/lib/Dsp/Square.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- The Square module generates a square wave at the given frequency and pwm
4 | -- offset. The output range is -1.0 .. +1.0
5 | --
6 |
7 | function Dsp:Square(init)
8 |
9 | local saw = Dsp:Saw()
10 | local pwm = 0.5
11 |
12 | return Dsp:Mod({
13 | id = "osc",
14 | description = "Sine oscillator",
15 | controls = {
16 | {
17 | id = "f",
18 | description = "Frequency",
19 | max = 20000,
20 | log = true,
21 | unit = "Hz",
22 | default = 440,
23 | fn_set = function(val)
24 | saw:set { f = val }
25 | end
26 | }, {
27 | id = "pwm",
28 | description = "PWM",
29 | default = 0,
30 | fn_set = function(val)
31 | pwm = val
32 | end
33 | },
34 | },
35 | fn_gen = function()
36 | return saw() < pwm and -1 or 1
37 | end
38 | }, init)
39 | end
40 |
41 | -- vi: ft=lua ts=3 sw=3
42 |
--------------------------------------------------------------------------------
/lib/Dsp/Width.lua:
--------------------------------------------------------------------------------
1 |
2 | function Dsp:Width(init)
3 |
4 | local a1, a2
5 |
6 | return Dsp:Mod({
7 | description = "Width",
8 | controls = {
9 | {
10 | id = "width",
11 | description = "Stereo width",
12 | min = 0,
13 | max = 2,
14 | default = 1,
15 | fn_set = function(v)
16 | a1 = math.min(0.5 + v/2, 1)
17 | a2 = 0.5 - v/2
18 | end,
19 | },
20 | },
21 |
22 | fn_gen = function(i1, i2)
23 | return i1 * a1 + i2 * a2,
24 | i2 * a1 + i1 * a2
25 | end
26 |
27 | }, init)
28 |
29 | end
30 |
31 | -- vi: ft=lua ts=3 sw=3
32 |
--------------------------------------------------------------------------------
/lib/Fluidsynth.lua:
--------------------------------------------------------------------------------
1 |
2 | Fluidsynth = {}
3 |
4 | local ffi = require("ffi")
5 |
6 | local fs = ffi.load("fluidsynth")
7 |
8 | ffi.cdef [[
9 | typedef struct fluid_settings_t *fluid_settings_t;
10 | typedef struct fluid_synth_t * fluid_synth_t;
11 | typedef struct fluid_audio_driver_t *fluid_audio_driver_t;
12 | fluid_settings_t *new_fluid_settings(void);
13 | int fluid_settings_setstr(fluid_settings_t* settings, const char *name, const char *str);
14 | int fluid_settings_setint(fluid_settings_t* settings, const char *name, int val);
15 | int fluid_settings_setnum(fluid_settings_t* settings, const char *name, double val);
16 | fluid_synth_t* new_fluid_synth(fluid_settings_t* settings);
17 | fluid_audio_driver_t* new_fluid_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth);
18 | int fluid_synth_sfload(fluid_synth_t* synth, const char* filename, int reset_presets);
19 | int fluid_synth_all_notes_off(fluid_synth_t* synth, int chan);
20 | int fluid_synth_program_change(fluid_synth_t* synth, int chan, int program);
21 | int fluid_synth_noteon(fluid_synth_t* synth, int chan, int key, int vel);
22 | int fluid_synth_noteoff(fluid_synth_t* synth, int chan, int key, int vel);
23 | ]]
24 |
25 |
26 | --
27 | -- Send note off to all channels
28 | --
29 |
30 | local function reset(fluidsynth)
31 | for i = 1, 16 do
32 | fs.fluid_synth_all_notes_off(fluidsynth.synth, i);
33 | end
34 | end
35 |
36 |
37 | --
38 | -- Add channel with given program, return instrument function
39 | --
40 |
41 | local function add(fluidsynth, prog)
42 |
43 | local ch
44 | if prog == 127 then
45 | ch = 9
46 | else
47 | prog = prog or 9
48 | ch = fluidsynth.channels
49 | fluidsynth.channels = fluidsynth.channels + 1
50 | fs.fluid_synth_program_change(fluidsynth.synth, ch, prog)
51 | end
52 |
53 | return function(note, vel)
54 | if vel > 0 then
55 | fs.fluid_synth_noteon(fluidsynth.synth, ch, note, vel * 127);
56 | else
57 | fs.fluid_synth_noteoff(fluidsynth.synth, ch, note, vel * 127);
58 | end
59 | end
60 | end
61 |
62 |
63 | --
64 | -- Create fluidsynth instance, create and attach jack client with the given
65 | -- name
66 | --
67 |
68 |
69 | function Fluidsynth:new(name, fname)
70 |
71 | name = name or "Fluidsynth"
72 |
73 | local fluidsynth = {
74 |
75 | -- methods
76 |
77 | reset = reset,
78 | add = add,
79 |
80 | -- data
81 |
82 | channels = 0,
83 | }
84 |
85 | local settings = fs.new_fluid_settings()
86 | fs.fluid_settings_setstr(settings, "audio.jack.id", name)
87 | fluidsynth.synth = fs.new_fluid_synth(settings);
88 | local adriver = fs.new_fluid_audio_driver(settings, fluidsynth.synth);
89 | fs.fluid_synth_sfload(fluidsynth.synth, fname, 1);
90 |
91 | return fluidsynth
92 |
93 | end
94 |
95 | -- vi: ft=lua ts=3 sw=3
96 |
97 |
--------------------------------------------------------------------------------
/lib/Gui.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- See the [gui] documentation page
4 | --
5 |
6 | Gui = {}
7 |
8 |
9 | --
10 | -- GUI process. This is a forked subprocess handling the GTK gui
11 | --
12 |
13 | local lgi, GLib, Gtk, Gdk
14 |
15 | local function random_id()
16 | return "%08x" % math.random(0, 0xffffffff)
17 | end
18 |
19 | --
20 | -- Implementation of knob widget
21 | --
22 |
23 | local function Knob(parm)
24 |
25 | local size = 40
26 | local w, h
27 | local cx = size * 0.5
28 | local cy = size * 0.5
29 | local r = size * 0.45
30 | local value = 0
31 | local def = parm.default
32 | local min = parm.lower
33 | local max = parm.upper
34 | local range = max - min
35 | local drag_x, drag_y, dragging = 0, 0, false
36 |
37 | local da = Gtk.DrawingArea {
38 | width = size,
39 | height = size,
40 | can_focus = true,
41 | }
42 |
43 | local knob = {
44 |
45 | da = da,
46 |
47 | set_value = function(knob, v)
48 | v = math.max(v, min)
49 | v = math.min(v, max)
50 | value = v
51 | da.window:invalidate_rect(nil)
52 | parm.on_value_changed(knob)
53 | end,
54 |
55 | get_value = function(knob)
56 | return value
57 | end
58 | }
59 |
60 | local sc = da:get_style_context()
61 |
62 | function da:on_draw(cr)
63 |
64 | local function v2a(v)
65 | return (v - min) / range * 5 + 2.25
66 | end
67 |
68 | local function l(v, r1, r2)
69 | local a = v2a(v)
70 | local x, y = math.cos(a), math.sin(a)
71 | cr:move_to(cx + x * r * r1, cy + y * r * r1)
72 | cr:line_to(cx + x * r * r2, cy + y * r * r2)
73 | cr:stroke()
74 | end
75 |
76 | cr:set_line_width(1)
77 |
78 | cr:arc(cx, cy, r, 0, 6.28)
79 | cr:set_source_rgb(0.7, 0.7, 0.7)
80 | cr:fill_preserve()
81 | cr:set_source_rgb(0.9, 0.9, 0.9)
82 | cr:stroke()
83 |
84 | cr:arc(cx, cy, r*0.7, 0, 6.28)
85 | cr:set_source_rgb(0.5, 0.5, 0.5)
86 | cr:fill_preserve()
87 | cr:set_source_rgb(0.3, 0.3, 0.3)
88 | cr:stroke()
89 |
90 | cr:set_line_width(2)
91 | local a1, a2 = v2a(0), v2a(value)
92 | cr:arc(cx, cy, r*0.85, math.min(a1, a2), math.max(a1, a2))
93 | cr:set_source_rgb(0.0, 1.0, 0.0)
94 | cr:stroke()
95 |
96 | cr:set_line_width(2)
97 | cr:set_source_rgb(0.0, 0.0, 0.0)
98 | l(min, 0.7, 0.9)
99 | l(max, 0.7, 0.9)
100 | if def ~= min and def ~= max then
101 | l(def, 0.8, 0.9)
102 | end
103 |
104 | cr:set_line_width(3)
105 | cr:set_source_rgb(1.0, 1.0, 1.0)
106 | l(value, 0, 0.6)
107 |
108 | cr:set_line_width(1)
109 |
110 | if da.has_focus then
111 | Gtk.render_focus(sc, cr, 0, 0, w, h)
112 | end
113 |
114 | end
115 |
116 | function da:on_configure_event(event)
117 | w, h = self.allocation.width, self.allocation.height
118 | cx, cy = w/2, h/2
119 | return true
120 | end
121 |
122 | function da:on_scroll_event(event)
123 | local d = 0
124 | if event.direction == 'UP' then d = parm.page_increment end
125 | if event.direction == 'DOWN' then d = -parm.page_increment end
126 | if event.state.SHIFT_MASK then d = d / 10 end
127 | if event.state.CONTROL_MASK then d = d * 10 end
128 | knob:set_value(value + d)
129 | end
130 |
131 | function da:on_button_press_event(event)
132 | da.has_focus = true
133 | end
134 |
135 | function da:on_motion_notify_event(event)
136 | local _, x, y, state = event.window:get_device_position(event.device)
137 |
138 | if not dragging and state.BUTTON1_MASK then dragging, drag_x, drag_y = true, x, y end
139 | if dragging and not state.BUTTON1_MASK then dragging = false end
140 |
141 | if dragging then
142 | local dy = drag_y - y
143 | knob:set_value(value + dy * range / 100)
144 | da.window:invalidate_rect(nil)
145 | drag_y = y
146 | end
147 | end
148 |
149 | function da:on_key_press_event(event)
150 | local d = 0
151 | local k = event.keyval
152 | if k == Gdk.KEY_Up then d = parm.step_increment end
153 | if k == Gdk.KEY_Down then d = -parm.step_increment end
154 | if k == Gdk.KEY_Page_Up then d = parm.page_increment end
155 | if k == Gdk.KEY_Page_Down then d = -parm.page_increment end
156 | if event.state.SHIFT_MASK then d = d / 10 end
157 | if event.state.CONTROL_MASK then d = d * 10 end
158 | knob:set_value(value + d)
159 | end
160 |
161 | da:add_events(Gdk.EventMask {
162 | 'SCROLL_MASK',
163 | 'KEY_PRESS_MASK',
164 | 'BUTTON_PRESS_MASK',
165 | 'LEAVE_NOTIFY_MASK',
166 | 'BUTTON_PRESS_MASK',
167 | 'POINTER_MOTION_MASK',
168 | 'POINTER_MOTION_HINT_MASK' })
169 |
170 | return knob
171 |
172 | end
173 |
174 |
175 |
176 | local cmd_handler = {
177 |
178 | new = function(worker, id, data)
179 |
180 | local window = Gtk.Window {
181 | resizable = false,
182 | title = data.gui_id,
183 | Gtk.Box {
184 | id = "box",
185 | valign = 'START',
186 | orientation = 'HORIZONTAL',
187 | {
188 | Gtk.Label {
189 | use_markup = true,
190 | label = data.label and "" .. data.label .. "" or nil,
191 | },
192 | }
193 | }
194 | }
195 |
196 | worker.gui_list[data.gui_id] = {
197 | window = window,
198 | group_list = {},
199 | }
200 | window:show_all()
201 |
202 | end,
203 |
204 |
205 | add_group = function(worker, id, data)
206 |
207 | local gui = worker.gui_list[data.gui_id]
208 | local info = data.info
209 | local mute = false
210 |
211 | local frame = Gtk.Frame {
212 | label = data.label,
213 | margin = 4,
214 | Gtk.Grid {
215 | margin = 4,
216 | column_spacing = 4,
217 | id = data.group_id,
218 | }
219 | }
220 |
221 | frame:set_label_align(0.5, 1)
222 |
223 | gui.window.child.box:add(frame)
224 |
225 | gui.window:show_all()
226 |
227 | gui.group_list[data.group_id] = {
228 | index = 0,
229 | control_list = {}
230 | }
231 |
232 | end,
233 |
234 |
235 | add_control = function(worker, id, data)
236 |
237 | local gui = worker.gui_list[data.gui_id]
238 | local group = gui.group_list[data.group_id]
239 |
240 | local window = gui.window
241 | local grid = window.child[data.group_id]
242 |
243 | local x = group.index
244 | group.index = group.index + 1
245 |
246 | local control = data.control
247 | local mute = false
248 | local fn_set = function(v) end
249 |
250 | grid:add {
251 | left_attach = x, top_attach = 0,
252 | Gtk.Label {
253 | label = control.id,
254 | halign = 'CENTER',
255 | }
256 | }
257 |
258 | if control.type == "number" then
259 |
260 | local label = Gtk.Label {
261 | halign = 'CENTER',
262 | }
263 |
264 | local function k2v(v)
265 | if control.log then
266 | if v < 0.001 then v = 0.001 end
267 | v = (control.max+1) ^ (v/control.max) - 1
268 | end
269 | return v
270 | end
271 |
272 | local function v2k(v)
273 | if control.log then
274 | v = (control.max) * math.log(v+1) / math.log(control.max)
275 | end
276 | return v
277 | end
278 |
279 | local function on_value_changed(knob)
280 | local val = k2v(knob:get_value())
281 | if not mute then
282 | worker:tx { cmd = "set", data = {
283 | uid = data.uid,
284 | value = val,
285 | }}
286 | end
287 | local unit = control.unit
288 | if val > 1000 then
289 | val = val / 1000
290 | unit = "k" .. unit
291 | end
292 | local fmt = "%d"
293 | if math.abs(val) < 100 then fmt = "%.1f" end
294 | if math.abs(val) < 10 then fmt = "%.2f" end
295 | if control.unit then
296 | fmt = fmt .. " " .. unit
297 | end
298 | label:set_text((control.fmt or fmt) % val)
299 | end
300 |
301 | local knob = Knob {
302 | lower = v2k(control.min),
303 | upper = v2k(control.max),
304 | default = v2k(control.default),
305 | step_increment = (control.max-control.min)/100,
306 | page_increment = (control.max-control.min)/10,
307 | on_value_changed = on_value_changed,
308 | }
309 |
310 | grid:add {
311 | left_attach = x, top_attach = 1,
312 | knob.da
313 | }
314 |
315 | grid:add {
316 | left_attach = x, top_attach = 2,
317 | label,
318 | }
319 |
320 | fn_set = function(val)
321 | mute = true
322 | knob:set_value(v2k(val))
323 | mute = false
324 | end
325 |
326 |
327 | elseif control.type == "enum" then
328 |
329 | local combo = Gtk.ComboBoxText {
330 | on_changed = function(s)
331 | worker:tx { cmd = "set", data = {
332 | uid = data.uid,
333 | value = s:get_active_text(),
334 | }}
335 | end
336 | }
337 |
338 | grid:add {
339 | left_attach = x, top_attach = 1,
340 | combo
341 | }
342 |
343 | for i, o in ipairs(control.options) do
344 | combo:append(i-1, o)
345 | end
346 |
347 | fn_set = function(v)
348 | for i, o in ipairs(control.options) do
349 | if v == o then combo:set_active(i-1) end
350 | end
351 | end
352 |
353 | elseif control.type == "bool" then
354 |
355 | local switch = Gtk.ToggleButton {
356 | label = " ",
357 | on_toggled = function(s)
358 | worker:tx { cmd = "set", data = {
359 | uid = data.uid,
360 | value = s:get_active()
361 | }}
362 | end
363 | }
364 |
365 | grid:add {
366 | left_attach = x, top_attach = 1,
367 | switch,
368 | }
369 |
370 | fn_set = function(v)
371 | switch:set_active(v)
372 | end
373 |
374 | end
375 |
376 | group.control_list[control.id] = {
377 | control = control,
378 | fn_set = fn_set,
379 | }
380 |
381 | window:show_all()
382 | end,
383 |
384 |
385 | set_control = function(worker, id, data)
386 |
387 | local gui = worker.gui_list[data.gui_id]
388 | local group = gui.group_list[data.group_id]
389 | local control = group.control_list[data.control_id]
390 | control.fn_set(data.value)
391 |
392 | end
393 |
394 | }
395 |
396 |
397 | local function handle_msg(worker, code)
398 | local fn, err = load("return " .. code, "msg")
399 | if fn then
400 | local ok, msg = safecall(fn)
401 | if ok then
402 | local h = cmd_handler[msg.cmd]
403 | if h then
404 | h(worker, msg.modid, msg.data)
405 | end
406 | else
407 | logf(LG_WRN, "ipc run error: %s", data, code)
408 | end
409 | else
410 | logf(LG_WRN, "ipc syntax error: %s", err)
411 | end
412 | end
413 |
414 |
415 | local function gui_main(fd)
416 |
417 | P.signal(P.SIGINT, nil)
418 |
419 | lgi = require "lgi"
420 | GLib, Gtk, Gdk = lgi.GLib, lgi.Gtk, lgi.Gdk
421 |
422 | local worker = {
423 |
424 | -- methods
425 |
426 | tx = function(worker, msg)
427 | P.send(fd, serialize(msg))
428 | end,
429 |
430 | -- data
431 |
432 | fd = fd,
433 | gui_list = {}
434 | }
435 |
436 | -- Main GTK loop: receive messages from main process and handle GUI
437 |
438 | local ioc = GLib.IOChannel.unix_new(worker.fd)
439 | GLib.io_add_watch(ioc, 1, 1, function(a, b, c ,d)
440 | local code, err = P.recv(worker.fd, 65535)
441 | if code then
442 | handle_msg(worker, code)
443 | end
444 | return true
445 | end)
446 |
447 | Gtk.main()
448 |
449 | end
450 |
451 |
452 | --
453 | -- Main process
454 | --
455 |
456 |
457 | local function group_add_control(group, control, uid, fn_set)
458 |
459 | group.gui.Gui:tx { cmd = "add_control", data = {
460 | gui_id = group.gui.id,
461 | group_id = group.id,
462 | control = control,
463 | uid = uid,
464 | }}
465 |
466 | local function set(val)
467 | group.gui.Gui:tx { cmd = "set_control", data = {
468 | gui_id = group.gui.id,
469 | group_id = group.id,
470 | control_id = control.id,
471 | value = val
472 | }}
473 | end
474 |
475 | control:on_set(set)
476 |
477 | if control.value then
478 | set(control.value)
479 | end
480 |
481 | --group.gui.fn_set[id] = fn_set
482 | end
483 |
484 |
485 | local function gui_add_group(gui, label)
486 |
487 | local group = {
488 |
489 | -- methods
490 |
491 | add_control = group_add_control,
492 |
493 | -- data
494 |
495 | gui = gui,
496 | label = label,
497 | id = random_id(),
498 | }
499 |
500 |
501 | gui.Gui:tx { cmd = "add_group", data = {
502 | gui_id = gui.id,
503 | label = group.label,
504 | group_id = group.id }}
505 |
506 | return group
507 |
508 | end
509 |
510 |
511 | local function gui_start(Gui)
512 | local s1, s2 = P.socketpair(P.AF_UNIX, P.SOCK_DGRAM, 0)
513 | Gui.pid = P.fork()
514 | if Gui.pid == 0 then
515 | for i = 3, 255 do
516 | if i ~= s2 then P.close(i) end
517 | end
518 | gui_main(s2)
519 | end
520 | P.close(s2)
521 | Gui.s = s1
522 | Gui.uid_to_control = {}
523 |
524 | watch_fd(s1, function()
525 | local code = P.recv(s1, 65535)
526 | local fn, err = load("return " .. code)
527 | if fn then
528 | local ok, msg = safecall(fn)
529 | if ok then
530 | if msg.cmd == "set" then
531 | local control = Gui.uid_to_control[msg.data.uid]
532 | if control then
533 | control:set(msg.data.value)
534 | end
535 | end
536 | else
537 | logf(LG_WRN, "ipc error: %s", data)
538 | end
539 | else
540 | logf(LG_WRN, "ipc error: %s", err)
541 | end
542 | end)
543 |
544 | Gui.tx = function(_, msg)
545 | P.send(Gui.s, serialize(msg))
546 | end
547 | end
548 |
549 |
550 | local function gui_add_mod(gui, gen, label)
551 |
552 | local controls = gen:controls()
553 | local group = gui:add_group(label or gen.description)
554 | for _, control in ipairs(controls) do
555 | local uid = random_id()
556 | gui.Gui.uid_to_control[uid] = control
557 | group:add_control(control, uid, function(v)
558 | gen:set { [control.id] = v }
559 | end)
560 | end
561 | end
562 |
563 |
564 | function Gui:new(label)
565 |
566 | if not Gui.pid then
567 | gui_start(Gui)
568 | end
569 |
570 | local gui = {
571 |
572 | -- methods
573 |
574 | add_group = gui_add_group,
575 | add_mod = gui_add_mod,
576 |
577 | -- data
578 |
579 | id = random_id(),
580 | Gui = Gui,
581 |
582 | }
583 |
584 | gui.Gui:tx { cmd = "new", data = { gui_id = gui.id, label = label } }
585 |
586 | return gui
587 |
588 | end
589 |
590 |
591 | -- vi: ft=lua ts=3 sw=3
592 |
--------------------------------------------------------------------------------
/lib/Jack.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- See the [jack] documentation page
4 | --
5 |
6 | local jack_c = require "jack_c"
7 |
8 | --
9 | -- Register jack audio ports, n_in input ports and n_out output ports.
10 | -- Processing is done in given fn, all input data is given as arguments, all
11 | -- returned data is passed as output data.
12 | --
13 |
14 | local function jack_dsp(jack, name, n_in, n_out, fn)
15 |
16 | local group = jack.group_list[name]
17 |
18 | if not group then
19 |
20 | group = {
21 | fn = fn,
22 | fn_ok = function() end
23 | }
24 | jack.group_list[name] = group
25 |
26 | local t = 0
27 | local dt = 1/jack.srate
28 | local gu, fd = jack_c.add_group(jack.j, name, n_in, n_out)
29 |
30 | -- This function gets called when the jack thread needs more data. The
31 | -- many calls into C should probably be optimized at some time
32 |
33 | watch_fd(fd, function()
34 | P.read(fd, 1)
35 | local ok = safecall(function()
36 | for i = 1, jack.bsize do
37 | jack_c.write(gu, group.fn(jack_c.read(gu)))
38 | t = t + dt
39 | end
40 | end)
41 | if not ok then
42 | print("Restoring last known good function")
43 | group.fn = group.fn_ok
44 | end
45 | end)
46 |
47 | else
48 | group.fn_ok = group.fn
49 | group.fn = fn
50 | end
51 |
52 | end
53 |
54 |
55 |
56 | --
57 | -- Register jack midi port with given name. Received midi events are passed to
58 | -- callback function
59 | --
60 |
61 | local function jack_midi(jack, name)
62 |
63 | name = name or "midi"
64 |
65 | if jack.midi_list[name] then
66 | return jack.midi_list[name]
67 | end
68 |
69 | local midi = {
70 |
71 | -- methods
72 |
73 | on_note = function(midi, ch, fn)
74 | midi.fn_note[ch] = midi.fn_note[ch] or {}
75 | midi.fn_note[ch][fn] = true
76 | end,
77 |
78 | on_cc = function(midi, ch, nr, fn)
79 | midi.fn_cc[ch] = midi.fn_cc[ch] or {}
80 | midi.fn_cc[ch][nr] = midi.fn_cc[ch][nr] or {}
81 | midi.fn_cc[ch][nr][fn] = true
82 | end,
83 |
84 | map_instr = function(midi, ch, instr, mapping)
85 | local t, fn_map = type(mapping)
86 | if t == "number" then
87 | fn_map = function(v) return v + mapping end
88 | elseif t == "table" then
89 | fn_map = function(v) return mapping[v] end
90 | elseif t == "function" then
91 | fn_map = mapping
92 | else
93 | fn_map = function(v) return v end
94 | end
95 |
96 | midi:on_note(ch, function(note, vel)
97 | instr(fn_map(note), vel/127)
98 | end)
99 | end,
100 |
101 | map_control = function(midi, ch, nr, control)
102 | midi:on_cc(ch, nr, function(v)
103 | control:set_uni(v)
104 | end)
105 | end,
106 |
107 | map_mod = function(midi, ch, nr, mod)
108 | for i, control in ipairs(mod:controls()) do
109 | midi:map_control(ch, nr+i-1, control)
110 | end
111 | end,
112 |
113 | -- data
114 |
115 | fd = jack_c.add_midi(jack.j, name),
116 | fn_note = {},
117 | fn_cc = {},
118 | }
119 |
120 | watch_fd(midi.fd, function()
121 | local msg = P.read(midi.fd, 3)
122 |
123 | local b1, b2, b3 = string.byte(msg, 1, #msg)
124 | logf(LG_DMP, "%02x %02x %02x", b1, b2, b3)
125 |
126 | local t = bit.band(b1, 0xf0)
127 | local ch = bit.band(b1, 0x0f) + 1
128 |
129 | if t == 0x80 or t == 0x90 then
130 | if midi.fn_note[ch] then
131 | for fn in pairs(midi.fn_note[ch]) do
132 | fn(b2, t == 0x90 and b3 or 0)
133 | end
134 | end
135 | elseif t == 0xb0 then
136 | if midi.fn_cc[ch] and midi.fn_cc[ch][b2] then
137 | for fn in pairs(midi.fn_cc[ch][b2] or {}) do
138 | fn(b3/127)
139 | end
140 | end
141 | end
142 |
143 | end)
144 |
145 | jack.midi_list[name] = midi
146 | return midi
147 |
148 | end
149 |
150 |
151 | --
152 | -- Connect jack ports. All ports matching patt1 are connected to ports matching
153 | -- patt2. If patt2 is not given, it defaults to '*'
154 | --
155 |
156 | local function jack_conn(jack, patt1, patt2, connect)
157 |
158 | patt2 = patt2 or "*"
159 | logf(LG_DBG, "Connect %s -> %s", patt1, patt2)
160 |
161 | local function find(ps, patt)
162 | local l = {}
163 | for _, p in ipairs(ps) do
164 | local client = p.name:match("[^:]+")
165 | local dir = p.input and "input" or "output"
166 | if patt == '*' or p.name:find(patt, 1, true) then
167 | l[p.type] = l[p.type] or {}
168 | l[p.type][dir] = l[p.type][dir] or {}
169 | l[p.type][dir][client] = l[p.type][dir][client] or {}
170 | table.insert(l[p.type][dir][client], p)
171 | end
172 | end
173 | return l
174 | end
175 |
176 | -- Find all ports matching given patterns, index by type/direction/client
177 |
178 | local ps = jack_c.list_ports(jack.j)
179 | local l1 = find(ps, patt1)
180 | local l2 = find(ps, patt2)
181 |
182 | -- Iterate all types/directions/clients on patt1
183 |
184 | for t, ds in pairs(l1) do
185 | for d1, cs in pairs(ds) do
186 | for c1, ps1 in pairs(cs) do
187 |
188 | -- find and connect matching ports from patt2
189 |
190 | local d2 = (d1 == "input") and "output" or "input"
191 | if l2[t] and l2[t][d2] then
192 | for c2, ps2 in pairs(l2[t][d2]) do
193 | if c2 ~= c1 then
194 | for i = 1, math.max(#ps1, #ps2) do
195 | local p1 = ps1[math.min(i, #ps1)]
196 | local p2 = ps2[math.min(i, #ps2)]
197 | if p1.input then p1,p2 = p2,p1 end
198 | logf(LG_DBG, " %s -> %s", p1.name, p2.name)
199 | if connect then
200 | jack_c.connect(jack.j, p1.name, p2.name)
201 | else
202 | jack_c.disconnect(jack.j, p1.name, p2.name)
203 | end
204 | end
205 | end
206 | end
207 | end
208 | end
209 | end
210 | end
211 |
212 | end
213 |
214 |
215 | local function jack_connect(jack, p1, p2)
216 | jack_conn(jack, p1, p2, true)
217 | end
218 |
219 |
220 | local function jack_disconnect(jack, p1, p2)
221 | jack_conn(jack, p1, p2, false)
222 | end
223 |
224 | --
225 | -- Create new jack client with the given name
226 | --
227 |
228 | Jack = {}
229 |
230 | function Jack:new(_, name)
231 |
232 | local j, srate, bsize = jack_c.open(name or "worp")
233 |
234 | local jack = {
235 |
236 | -- methods
237 |
238 | dsp = jack_dsp,
239 | midi = jack_midi,
240 | connect = jack_connect,
241 | disconnect = jack_disconnect,
242 |
243 | -- data
244 |
245 | j = j,
246 | srate = srate,
247 | bsize = bsize,
248 | group_list = {},
249 | midi_list = {},
250 |
251 | }
252 |
253 | return jack
254 |
255 | end
256 |
257 | -- vi: ft=lua ts=3 sw=3
258 |
259 |
--------------------------------------------------------------------------------
/lib/Linuxsampler.lua:
--------------------------------------------------------------------------------
1 |
2 |
3 | --
4 | -- The Linuxsampler library provides a simple method to connect to a running
5 | -- Linuxsample instance. Example:
6 | --
7 | -- l = Linuxsampler:new("synth", "/opt/samples")
8 | -- piano = l:add("piano/Bosendorfer.gig", 0)
9 | -- play(piano, 60, 1)
10 | --
11 | -- Create a new linuxsampler connection. The given JACK_NAME is used for
12 | -- it's audio ports, the SAMPLE_PATH is an optional directory prefix
13 | -- for sample files:
14 | --
15 | -- l = Linuxsampler:new(JACK_NAME, SAMPLE_PATH)
16 | --
17 | -- Create in instrument function, using the given sample and linuxsampler instrument ID:
18 | --
19 | -- piano = l:add(SAMPLE_FNAME, INSTRUMENT_NUMBER)
20 | --
21 |
22 |
23 | Linuxsampler = {}
24 |
25 | --
26 | -- Simple LSCP interface to configure and play linuxsampler. The code below
27 | -- implements a asynchronous interface to make sure not to block the main
28 | -- thread while waiting for linuxsampler to reply.
29 | --
30 |
31 | --
32 | -- Execute a linuxsampler command and returns the results. This function
33 | -- assumes the caller is running in a coroutine if an answer is required
34 | --
35 |
36 | local function cmd(ls, data)
37 |
38 | logf(LG_DMP, "tx> %s", data)
39 | P.send(ls.fd, data .. "\n")
40 | ls.rx_buf = ""
41 |
42 | if coroutine.running() then
43 | ls.rx_fn = resumer()
44 | return coroutine.yield()
45 | else
46 | ls.rx_fn = function() end
47 | end
48 | end
49 |
50 |
51 | --
52 | -- Add a linuxsampler channel and load the instrument from the given file and
53 | -- index, return instrument function
54 | --
55 |
56 | local function add(ls, fname, index)
57 |
58 | fname = ls.path .. "/" .. fname
59 |
60 | local ch = ls:cmd("ADD CHANNEL")
61 | ls:cmd("LOAD ENGINE gig %s" % ch)
62 | ls:cmd("SET CHANNEL AUDIO_OUTPUT_DEVICE %s %s" % { ch, ls.audio_dev })
63 | logf(LG_INF, "Loading instrument %s...", fname)
64 | ok, err = ls:cmd("LOAD INSTRUMENT %q %s %s" % { fname, index or 0, ch })
65 | if not ok then logf(LG_WRN, "%s", err) end
66 |
67 | local info = ls:cmd("GET CHANNEL INFO %s" % ch)
68 | local inst = info:match("INSTRUMENT_NAME: ([^\n\r]+)") or "-"
69 | logf(LG_INF, " channel %s: %s", ch, inst)
70 |
71 | ls.channel_list[ch] = info
72 |
73 | return function(key, vel)
74 | if vel > 0 then
75 | ls:cmd("SEND CHANNEL MIDI_DATA NOTE_ON %s %s %s\n" % { ch, key, vel * 127 })
76 | else
77 | ls:cmd("SEND CHANNEL MIDI_DATA NOTE_OFF %s %s %s\n" % { ch, key, vel * 127 })
78 | end
79 | end
80 |
81 | end
82 |
83 |
84 | --
85 | -- Send noteoff to all active channels
86 | --
87 |
88 | local function reset(ls)
89 | for c in pairs(ls.channel_list) do
90 | ls:cmd("SEND CHANNEL MIDI_DATA CC %s 120 0" % c)
91 | end
92 | end
93 |
94 |
95 | --
96 | -- Create new linuxsampler connection and create jack audio client
97 | -- with the given name.
98 | --
99 |
100 | function Linuxsampler:new(name, path)
101 |
102 | local fd, err = P.socket(P.AF_INET, P.SOCK_STREAM, 0)
103 | if not fd then
104 | return logf(LG_WRN, "Error connecting to linuxsampler: %s", err)
105 | end
106 |
107 | local ok, err = P.connect(fd, { family = P.AF_INET, addr = "127.0.0.1", port = 8888 })
108 | if not ok then
109 | return logf(LG_WRN, "Error connecting to linuxsampler: %s", err)
110 | end
111 |
112 | local ls = {
113 |
114 | -- methods
115 |
116 | add = add,
117 | cmd = cmd,
118 | reset = reset,
119 |
120 | -- data
121 |
122 | name = name,
123 | path = path or "",
124 | fd = fd,
125 | tx_queue = {},
126 | channel_list = {},
127 | rx_buf = "",
128 |
129 | }
130 |
131 | watch_fd(fd, function()
132 | local data = P.recv(fd, 1024)
133 | if not data then
134 | logf(LG_WRN, "Connection to linuxsampler lost")
135 | return false
136 | end
137 | for l in data:gmatch("([^\r\n]*)[\n\r]+") do
138 | logf(LG_DMP, "rx> %s", l)
139 | local rv = l:match("^OK%[?(%d*)%]?") or l:match("^%.") or l:match("^([%d,]*)$")
140 | local err = l:match("ERR:(.+)") or l:match("WRN:(.+)")
141 | if rv then
142 | ls.rx_fn(#ls.rx_buf > 0 and ls.rx_buf or rv)
143 | elseif err then
144 | ls.rx_fn(nil, err)
145 | else
146 | ls.rx_buf = ls.rx_buf .. l .. "\n"
147 | end
148 | end
149 | end)
150 |
151 | -- Find existing audio dev with given name, or create if needed
152 |
153 | local ds = ls:cmd("LIST AUDIO_OUTPUT_DEVICES")
154 | for d in ds:gmatch("%d+") do
155 | local info = ls:cmd("GET AUDIO_OUTPUT_DEVICE INFO %d" % d)
156 | local name2 = info:match("NAME: '(.-)'")
157 | if name == name2 then
158 | ls.audio_dev = d
159 | end
160 | end
161 |
162 | if not ls.audio_dev then
163 | ls.audio_dev = ls:cmd("CREATE AUDIO_OUTPUT_DEVICE JACK NAME=%q" % name)
164 | ls:cmd("SET AUDIO_OUTPUT_CHANNEL_PARAMETER %d 0 NAME='out_1'" % ls.audio_dev)
165 | ls:cmd("SET AUDIO_OUTPUT_CHANNEL_PARAMETER %d 1 NAME='out_2'" % ls.audio_dev)
166 | end
167 |
168 | on_stop(function()
169 | for c in pairs(ls.channel_list) do
170 | ls:cmd("REMOVE CHANNEL %d" % c)
171 | end
172 | end)
173 |
174 | return ls
175 |
176 | end
177 |
178 |
179 | -- vi: ft=lua ts=3 sw=3
180 |
181 |
--------------------------------------------------------------------------------
/lib/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all: jack_c.so
3 |
4 | CFLAGS += $(shell pkg-config --cflags jack)
5 | LDFLAGS += $(shell pkg-config --libs jack)
6 |
7 | %.so: %.c
8 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
9 |
10 | clean:
11 | rm -f *.so
12 |
--------------------------------------------------------------------------------
/lib/Metro.lua:
--------------------------------------------------------------------------------
1 |
2 | Metro = {}
3 |
4 | function Metro:new(bpm, b)
5 | bpm, b = bpm or 120, b or 4
6 | local spb = 60 / bpm
7 | local spm = spb * b
8 | local spt = spb / 120
9 | return {
10 | t_beat = function()
11 | return spb
12 | end,
13 | t_meas = function()
14 | return spm
15 | end,
16 | at_meas = function(m, ...)
17 | at((math.floor(t_now / spm) + 1) * spm, ...)
18 | end,
19 | at_beat = function(m, ...)
20 | at((math.floor(t_now / spb) + 1) * spb, ...)
21 | end
22 | }
23 | end
24 |
25 | -- vi: ft=lua ts=3 sw=3
26 |
27 |
--------------------------------------------------------------------------------
/lib/Mixer.lua:
--------------------------------------------------------------------------------
1 |
2 | Mixer = {}
3 |
4 | local ffi = require("ffi")
5 |
6 | ffi.cdef [[
7 | typedef void *snd_mixer_t;
8 | typedef void *snd_mixer_elem_t;
9 | typedef void *snd_mixer_class_t;
10 | typedef struct { char foo[64]; } snd_mixer_selem_id_t;
11 | int snd_mixer_open(snd_mixer_t **mixer, int mode);
12 | int snd_mixer_attach(snd_mixer_t *mixer, const char *name);
13 | int snd_mixer_selem_register(snd_mixer_t *mixer, struct snd_mixer_selem_regopt *options, snd_mixer_class_t **classp);
14 | int snd_mixer_load(snd_mixer_t *mixer);
15 | void *malloc(size_t size);
16 | void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t *obj, unsigned int val);
17 | void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val);
18 | snd_mixer_elem_t *snd_mixer_find_selem(snd_mixer_t *mixer, const snd_mixer_selem_id_t *id);
19 | int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value);
20 | size_t snd_mixer_selem_id_sizeof(void);
21 | int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem, long *min, long *max);
22 | ]]
23 |
24 | local alsa = ffi.load("asound")
25 |
26 | local handle = ffi.new("snd_mixer_t *[1]")
27 | local card = "default"
28 |
29 | alsa.snd_mixer_open(handle, 0);
30 | alsa.snd_mixer_attach(handle[0], card);
31 | alsa.snd_mixer_selem_register(handle[0], ffi.NULL, ffi.NULL);
32 | alsa.snd_mixer_load(handle[0]);
33 |
34 |
35 | function Mixer:new(_, name)
36 |
37 | local sid = ffi.new("snd_mixer_selem_id_t *[1]")
38 | sid = ffi.new("snd_mixer_selem_id_t[1]")
39 |
40 | return {
41 | set = function(_, v)
42 | alsa.snd_mixer_selem_id_set_index(sid[0], 0);
43 | alsa.snd_mixer_selem_id_set_name(sid[0], name);
44 | local elem = alsa.snd_mixer_find_selem(handle[0], sid[0]);
45 | local min, max = ffi.new("long[1]", 0), ffi.new("long[1]", 0)
46 | alsa.snd_mixer_selem_get_playback_volume_range(elem, min, max);
47 | v = v * (max[0] - min[0]) + min[0]
48 | alsa.snd_mixer_selem_set_playback_volume_all(elem, v)
49 | end
50 | }
51 | end
52 |
53 | -- vi: ft=lua ts=3 sw=3
54 |
55 |
--------------------------------------------------------------------------------
/lib/Poly.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- The Poly function provides an easy way to create polyphonic instruments.
4 | --
5 |
6 | function Poly(mkinstr, count)
7 |
8 | count = count or 5
9 |
10 | local vs = {}
11 | for i = 1, count do
12 | local vi, vg = mkinstr()
13 | vs[i] = { instr = vi, gen = vg, free = true, age = time() }
14 | end
15 |
16 | local function instr(note, vel)
17 | if vel > 0 then
18 | for i, v in pairs(vs) do
19 | if v.free then
20 | v.note = note
21 | v.instr(note, vel)
22 | v.free = false
23 | break
24 | end
25 | end
26 | else
27 | for i, v in pairs(vs) do
28 | if v.note == note then
29 | v.instr(note, 0)
30 | v.free = true
31 | v.age = time()
32 | end
33 | table.sort(vs, function(a, b) return a.age < b.age end)
34 | end
35 | end
36 | end
37 |
38 | local function gen(note, vel)
39 | local o1, o2 = 0, 0
40 | for _, v in ipairs(vs) do
41 | local i1, i2 = v.gen()
42 | o1 = o1 + (i1 or 0)
43 | o2 = o2 + (i2 or 0)
44 | end
45 | return o1, o2
46 | end
47 |
48 | return instr, gen
49 | end
50 |
51 | -- vi: ft=lua ts=3 sw=3
52 |
53 |
--------------------------------------------------------------------------------
/lib/jack_c.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 | #include
16 |
17 | #define MAX_PORTS 4
18 | #define RB_SIZE 16384
19 |
20 | #define sample_t jack_default_audio_sample_t
21 |
22 |
23 | struct port {
24 | int flags;
25 | jack_port_t *port;
26 | jack_ringbuffer_t *rb;
27 | struct port *next;
28 | };
29 |
30 |
31 | struct group {
32 | int id;
33 | int fd;
34 | char *name;
35 | struct port *port_list;
36 | struct group *next;
37 | };
38 |
39 |
40 | struct midi {
41 | jack_port_t *port;
42 | int fd;
43 | struct midi *next;
44 | };
45 |
46 |
47 | struct jack {
48 | int fd;
49 | int direction;
50 | jack_client_t *client;
51 | jack_port_t *port[MAX_PORTS];
52 | jack_ringbuffer_t *rb[MAX_PORTS];
53 | jack_port_t *midi_in;
54 |
55 | int group_seq;
56 | struct group *group_list;
57 | struct midi *midi_list;
58 | };
59 |
60 |
61 | static int process2(jack_nframes_t nframes, void *arg)
62 | {
63 | struct jack *jack = arg;
64 | struct group *group = jack->group_list;
65 | int len = nframes * sizeof(sample_t);
66 |
67 | while(group) {
68 |
69 | int need_data = 0;
70 |
71 | struct port *port = group->port_list;
72 | while(port) {
73 |
74 | jack_ringbuffer_t *rb = port->rb;
75 | jack_port_t *p = port->port;
76 |
77 | if(port->flags == JackPortIsOutput) {
78 |
79 | int avail = jack_ringbuffer_read_space(rb);
80 |
81 | if(avail < len * 3) need_data = 1;
82 |
83 | if(avail >= len) {
84 | sample_t *buf = jack_port_get_buffer(p, nframes);
85 | int r = jack_ringbuffer_read(rb, (void *)buf, len);
86 | if(0 && r != len) printf("underrun\n");
87 | }
88 |
89 | }
90 |
91 | if(port->flags == JackPortIsInput) {
92 |
93 | if(jack_ringbuffer_write_space(rb) >= len) {
94 | sample_t *buf = jack_port_get_buffer(p, nframes);
95 | int r = jack_ringbuffer_write(rb, (void *)buf, len);
96 | if(0 && r != len) printf("overrun\n");
97 | }
98 | }
99 |
100 | port = port->next;
101 | }
102 |
103 | if(need_data) {
104 | write(group->fd, " ", 1);
105 | }
106 |
107 |
108 | group = group->next;
109 | }
110 |
111 | struct midi *midi = jack->midi_list;
112 |
113 | while(midi) {
114 | void *buf = jack_port_get_buffer(midi->port, nframes);
115 | int n = jack_midi_get_event_count(buf);
116 | int i;
117 | for(i=0; ifd, ev.buffer, ev.size);
121 | }
122 | midi = midi->next;
123 | }
124 |
125 | return 0;
126 | }
127 |
128 |
129 | static int l_open(lua_State *L)
130 | {
131 | const char *client_name = luaL_checkstring(L, 1);
132 | jack_options_t options = JackNullOption;
133 | jack_status_t status;
134 |
135 | struct jack *jack = lua_newuserdata(L, sizeof *jack);
136 | lua_getfield(L, LUA_REGISTRYINDEX, "jack_c");
137 | lua_setmetatable(L, -2);
138 | memset(jack, 0, sizeof *jack);
139 |
140 | jack->client = jack_client_open (client_name, options, &status, NULL);
141 | if (jack->client == NULL) {
142 | lua_pushnil(L);
143 | lua_pushfstring(L, "Error creating jack client, status = %x", status);
144 | return 2;
145 | }
146 |
147 | jack_set_process_callback(jack->client, process2, jack);
148 | jack_activate (jack->client);
149 |
150 | lua_pushnumber(L, jack_get_sample_rate(jack->client));
151 | lua_pushnumber(L, jack_get_buffer_size(jack->client));
152 | return 3;
153 | }
154 |
155 |
156 | static int l_gc(lua_State *L)
157 | {
158 | struct jack *jack = luaL_checkudata(L, 1, "jack_c");
159 | jack_client_close(jack->client);
160 | return 0;
161 | }
162 |
163 |
164 |
165 | static int l_add_group(lua_State *L)
166 | {
167 | struct jack *jack = luaL_checkudata(L, 1, "jack_c");
168 | const char *name = luaL_checkstring(L, 2);
169 | int n_in = luaL_checknumber(L, 3);
170 | int n_out = luaL_checknumber(L, 4);
171 | int i;
172 | int fd[2];
173 |
174 | pipe(fd);
175 |
176 | char pname[64];
177 | struct group *group = lua_newuserdata(L, sizeof *group);
178 | lua_getfield(L, LUA_REGISTRYINDEX, "jack_group");
179 | lua_setmetatable(L, -2);
180 | memset(group, 0, sizeof *group);
181 |
182 | group->name = strdup(name);
183 | group->id = jack->group_seq ++;
184 | group->fd = fd[1];
185 |
186 | for(i=0; iflags = (i < n_in) ? JackPortIsInput : JackPortIsOutput;
190 |
191 | snprintf(pname, sizeof(pname), "%s-%s-%d", name, (iport = jack_port_register(jack->client, pname, JACK_DEFAULT_AUDIO_TYPE, port->flags, 0);
194 | port->rb = jack_ringbuffer_create(RB_SIZE);
195 |
196 | port->next = group->port_list;
197 | group->port_list = port;
198 | }
199 |
200 | group->next = jack->group_list;
201 | jack->group_list = group;
202 |
203 | lua_pushnumber(L, fd[0]);
204 | return 2;
205 | }
206 |
207 |
208 | static int l_add_midi(lua_State *L)
209 | {
210 | struct jack *jack = luaL_checkudata(L, 1, "jack_c");
211 | const char *name = luaL_checkstring(L, 2);
212 | int fd[2];
213 | char pname[64];
214 |
215 | pipe(fd);
216 |
217 | struct midi *midi = calloc(sizeof *midi, 1);
218 |
219 | snprintf(pname, sizeof(pname), "%s-in", name);
220 | midi->port = jack_port_register(jack->client, pname, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
221 | midi->fd = fd[1];
222 |
223 | midi->next = jack->midi_list;
224 | jack->midi_list = midi;
225 |
226 | lua_pushnumber(L, fd[0]);
227 | return 1;
228 | }
229 |
230 |
231 |
232 | static int l_write(lua_State *L)
233 | {
234 | struct group *group = luaL_checkudata(L, 1, "jack_group");
235 | int n = 2;
236 |
237 | struct port *port = group->port_list;
238 |
239 | while(port) {
240 | if(port->flags == JackPortIsOutput) {
241 | sample_t s = lua_tonumber(L, n++);
242 | if(jack_ringbuffer_write_space(port->rb) >= sizeof s) {
243 | jack_ringbuffer_write(port->rb, (void *)&s, sizeof s);
244 | }
245 | }
246 | port = port->next;
247 | }
248 |
249 | return 0;
250 | }
251 |
252 |
253 | static int l_read(lua_State *L)
254 | {
255 | struct group *group = luaL_checkudata(L, 1, "jack_group");
256 | int n = 0;
257 |
258 | struct port *port = group->port_list;
259 |
260 | while(port) {
261 | if(port->flags == JackPortIsInput) {
262 | sample_t s = 0;
263 | if(jack_ringbuffer_read_space(port->rb) >= sizeof s) {
264 | jack_ringbuffer_read(port->rb, (void *)&s, sizeof s);
265 |
266 | }
267 | lua_pushnumber(L, s);
268 | n++;
269 | }
270 | port = port->next;
271 | }
272 |
273 | return n;
274 | }
275 |
276 |
277 | static int l_connect(lua_State *L)
278 | {
279 | struct jack *jack = luaL_checkudata(L, 1, "jack_c");
280 | const char *p1 = luaL_checkstring(L, 2);
281 | const char *p2 = luaL_checkstring(L, 3);
282 |
283 | int r = jack_connect(jack->client, p1, p2);
284 | lua_pushnumber(L, r);
285 | return 1;
286 | }
287 |
288 |
289 | static int l_disconnect(lua_State *L)
290 | {
291 | struct jack *jack = luaL_checkudata(L, 1, "jack_c");
292 | const char *p1 = luaL_checkstring(L, 2);
293 | const char *p2 = luaL_checkstring(L, 3);
294 |
295 | int r = jack_disconnect(jack->client, p1, p2);
296 | lua_pushnumber(L, r);
297 | return 1;
298 | }
299 |
300 |
301 | static int l_list_ports(lua_State *L)
302 | {
303 | struct jack *jack = luaL_checkudata(L, 1, "jack_c");
304 | const char **ns = jack_get_ports(jack->client, NULL, NULL, 0);
305 | if(!ns) return 0;
306 | int i, j;
307 |
308 | lua_newtable(L);
309 | for(i=0; ns && ns[i]; i++) {
310 | jack_port_t *p = jack_port_by_name(jack->client, ns[i]);
311 | lua_pushnumber(L, i+1);
312 | lua_newtable(L);
313 |
314 | lua_pushstring(L, ns[i]); lua_setfield(L, -2, "name");
315 | lua_pushstring(L, jack_port_type(p)); lua_setfield(L, -2, "type");
316 |
317 | int flags = jack_port_flags(p);
318 | if(flags & JackPortIsInput) { lua_pushboolean(L, 1); lua_setfield(L, -2, "input"); }
319 | if(flags & JackPortIsOutput) { lua_pushboolean(L, 1); lua_setfield(L, -2, "output"); }
320 | if(flags & JackPortIsPhysical) { lua_pushboolean(L, 1); lua_setfield(L, -2, "physical"); }
321 | if(flags & JackPortIsTerminal) { lua_pushboolean(L, 1); lua_setfield(L, -2, "terminal"); }
322 |
323 | lua_newtable(L);
324 | const char **cs = jack_port_get_connections(p);
325 | for(j=0; cs && cs[j]; j++) {
326 | lua_pushnumber(L, j+1);
327 | lua_pushstring(L, cs[j]);
328 | lua_settable(L, -3);
329 | }
330 | free(cs);
331 | lua_setfield(L, -2, "connections");
332 |
333 | lua_settable(L, -3);
334 | }
335 | return 1;
336 | }
337 |
338 |
339 | static struct luaL_Reg jack_table[] = {
340 |
341 | { "open", l_open },
342 | { "add_group", l_add_group },
343 | { "add_midi", l_add_midi },
344 |
345 | { "write", l_write },
346 | { "read", l_read },
347 | { "connect", l_connect },
348 | { "disconnect", l_disconnect },
349 | { "list_ports", l_list_ports },
350 |
351 | { NULL },
352 | };
353 |
354 |
355 | int luaopen_jack_c(lua_State *L)
356 | {
357 | luaL_newmetatable(L, "jack_c");
358 | lua_pushstring(L, "__gc");
359 | lua_pushcfunction(L, l_gc);
360 | lua_settable(L, -3);
361 |
362 | luaL_newmetatable(L, "jack_group");
363 | luaL_register(L, "jack_c", jack_table);
364 | return 1;
365 | }
366 |
367 | /*
368 | * End
369 | */
370 |
--------------------------------------------------------------------------------
/lib/jack_c.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zevv/worp/9724431e0cadc014753236d1cf67e0ef310cd073/lib/jack_c.so
--------------------------------------------------------------------------------
/test/blip.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Filter test. White noise generator is passed through a CC controlled filter.
4 | --
5 |
6 | jack = Jack:new("worp")
7 |
8 |
9 | f = Dsp:Filter("lp", 2000, 5)
10 |
11 | d = 0
12 | e = 0
13 |
14 | jack:midi("midi", function(channel, t, d1, d2)
15 | if t == "cc" then
16 | local v = d2 / 127
17 | if d1 == 1 then f("f0", math.exp(v * 10)) end
18 | if d1 == 2 then f("Q", math.pow(2, v*3)) end
19 | if d1 == 3 then f("ft", ({"lp", "hp", "bp", "bs", "ap"})[math.floor(v*4)+1]) end
20 | if d1 == 4 then f("gain", (v - 0.5) * 30) end
21 | if d1 == 5 then d = v end
22 | if d1 == 6 then e = v end
23 | end
24 | end)
25 |
26 |
27 | o = Dsp:Osc(60)
28 | s = Dsp:Saw(90.5)
29 |
30 | print(s)
31 |
32 | r = Dsp:reverb()
33 |
34 | function rl(ns)
35 | return ns[math.random(1, #ns)]
36 | end
37 |
38 | p = 0.5
39 | a2 = 0.02
40 | env = 0
41 |
42 | function rev()
43 | p = math.random() - 0.5
44 | e = math.random(1, 6)
45 | d = math.random() * 0.3 + 0.30
46 | env = 1
47 | at(rl { 1, 2, 3 } * .15, "rev")
48 | a2 = math.random() * 0.03
49 | at(0.15 * rl { 1 }, function()
50 | oo2 = Dsp:osc(math.random(10, 600))
51 | at(0.15/2 * rl { 1, 2 }, function()
52 | a2 = 0
53 | end)
54 | end)
55 | end
56 |
57 | oo1 = Dsp:osc(9000)
58 | oo2 = Dsp:osc(100)
59 | o2 = function() oo1(oo2() * 2000 + 10000) return oo1() end
60 | --o2 = Dsp:osc(10000)
61 | rev()
62 |
63 | sawvol = 0.04
64 | damp = 0.99995
65 |
66 | jack:dsp("fx", 0, 2, function(t)
67 | local v = env * o() + s() * sawvol
68 | env = env * damp
69 | local w = r(v * d) + v * d
70 | v = f(v + w^math.floor(e*10))
71 | local vl, vr = p * v * 0.2, v*0.2 * (1-p)
72 | local bip = o2() * a2
73 | vl = vl + bip
74 | vr = vr - bip
75 | return vl, vr
76 | end)
77 |
78 |
79 | jack:connect("worp")
80 |
81 |
82 | -- vi: ft=lua ts=3 sw=3
83 |
84 |
--------------------------------------------------------------------------------
/test/filter.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Filter test. White noise generator is passed through a CC controlled filter.
4 | --
5 |
6 | jack = Jack:new("worp")
7 |
8 | f = Dsp:Filter("bp", 1000, 5)
9 | r = Dsp:Reverb()
10 |
11 | jack:midi("midi", function(channel, t, d1, d2)
12 | if t == "cc" then
13 | local v = d2 / 127
14 | if d1 == 1 then f("f0", math.exp(v * 10)) end
15 | if d1 == 2 then f("Q", math.pow(2, v*3)) end
16 | if d1 == 3 then f("ft", ({"lp", "hp", "bp", "bs", "ap"})[math.floor(v*4)+1]) end
17 | if d1 == 4 then f("gain", (v - 0.5) * 30) end
18 | end
19 | end)
20 |
21 |
22 | jack:dsp("fx", 0, 1, function(t)
23 | return f(math.random()) * 0.1
24 | end)
25 |
26 | jack:connect("worp")
27 |
28 |
29 | -- vi: ft=lua ts=3 sw=3
30 |
31 |
--------------------------------------------------------------------------------
/test/gen.lua:
--------------------------------------------------------------------------------
1 |
2 | a = Dsp
3 |
4 | --
5 | -- DSP test
6 | --
7 |
8 | jack = Jack:new("worp")
9 | midi = jack:midi("midi")
10 |
11 | -- Voice generator
12 |
13 | o = Dsp:Saw { f = 100 }
14 | f = Dsp:Filter()
15 | lfo = Dsp:Osc { f = 5 }
16 | rev = Dsp:Reverb()
17 | pan = Dsp:Pan()
18 |
19 | gui = Gui:new("Worp")
20 |
21 | n = Dsp:Noise()
22 | c = Dsp:Const()
23 |
24 | midi:map_mod(1, 1, f)
25 | midi:map_mod(1, 5, rev)
26 |
27 | f:help()
28 |
29 | gui:add_mod(o, "Osc")
30 | gui:add_mod(lfo, "LFO")
31 | gui:add_mod(c, "Noise")
32 | gui:add_mod(f)
33 | gui:add_mod(rev)
34 | gui:add_mod(pan)
35 |
36 | jack:dsp("synth", 0, 2, function(i1)
37 | local v = f(o() * (1+n()*c()*10)) * lfo() * 0.1
38 | return pan(rev(v, v))
39 | end)
40 |
41 | -- Connect ports
42 |
43 | jack:connect("worp")
44 |
45 | -- vi: ft=lua ts=3 sw=3
46 |
47 |
--------------------------------------------------------------------------------
/test/looper.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Simple 4 track looper controlled by Akai MKP mini
4 | --
5 |
6 | jack = Jack:new("worp")
7 |
8 | local function pan(v, p)
9 | return math.max(1 - p, 1) * v, math.max(1 + p, 1) * v
10 | end
11 |
12 | local function looper(t)
13 |
14 | local l = {
15 |
16 | -- methods
17 |
18 | run = function(l, v)
19 | if l.rec then
20 | l.buf[l.ptr] = l.buf[l.ptr] * 0.5 + v
21 | end
22 | l.ptr = (l.ptr + 1) % l.len
23 | local v = l.buf[l.ptr] * l.vol
24 | return pan(v, l.pan)
25 | end,
26 |
27 | -- data
28 |
29 | len = 44800 * t,
30 | buf = {},
31 | vol = 1,
32 | pan = 1,
33 | ptr = 0,
34 | rec = false,
35 | }
36 |
37 | for i = 0, l.len do
38 | l.buf[i] = 0
39 | end
40 |
41 | return l
42 |
43 | end
44 |
45 | local channels = 4
46 |
47 | local loop = {}
48 |
49 | for i = 1, 8 do
50 | loop[i] = looper(2)
51 | end
52 |
53 |
54 |
55 | local rec = nil
56 |
57 | jack:dsp("looper", 1, 2, function(t, i1)
58 | local o1, o2 = 0, 0
59 | for i = 1,8 do
60 | local t1, t2 = loop[i]:run(i1)
61 | o1 = o1 + t1
62 | o2 = o2 + t2
63 | end
64 | return o1, o2
65 | end)
66 |
67 |
68 | jack:midi("midi", function(channel, t, d1, d2)
69 |
70 | if t == "noteon" or t == "noteoff" then
71 | local onoff = t == "noteon"
72 | local note = d1
73 | if note >= 48 then
74 | local i = note - 47
75 | loop[i].rec = onoff
76 | print("REC", i, onoff)
77 | elseif note >= 44 then
78 | local i = note - 43
79 | print("CLEAR", i)
80 | if onoff then
81 | for j = 1, #loop[i].buf do
82 | loop[i].buf[j] = 0
83 | end
84 | end
85 | end
86 | end
87 |
88 | if t == "cc" then
89 | local v = d2 / 127
90 | if d1 > 4 then
91 | loop[d1-4].pan = v * 2 - 1
92 | else
93 | loop[d1].vol = v
94 | end
95 | end
96 | end)
97 |
98 | jack:connect("worp")
99 |
100 | -- vi: ft=lua ts=3 sw=3
101 |
102 |
--------------------------------------------------------------------------------
/test/pitch-shift.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Pitch shifter. Inspired by http://dafx.labri.fr/main/papers/p007.pdf
4 | --
5 |
6 | jack = Jack:new("worp")
7 |
8 | midi = jack:midi(1)
9 |
10 | gui = Gui:new("worp")
11 | s = Dsp:pitchshift()
12 |
13 | gui:add_mod(s)
14 | midi:map_mod(1, 1, s)
15 |
16 |
17 | jack:dsp("fx", 1, 1, function(t, i)
18 | return s(i)
19 | end)
20 |
21 |
22 | jack:connect("worp")
23 |
24 |
25 | -- vi: ft=lua ts=3 sw=3
26 |
27 |
--------------------------------------------------------------------------------
/test/reich.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- Some generated music with effects
4 | --
5 |
6 | ls = Linuxsampler:new("worp", "/opt/samples")
7 | ls:reset()
8 |
9 | jack = Jack:new()
10 |
11 | piano = ls:add("grand.gig")
12 | violin = ls:add("grand.gig", 0)
13 |
14 | jack:connect("worp")
15 |
16 | function mkchord(min, max, n, ns)
17 | local os = {}
18 | for i = 1, n do
19 | local o = ns[(i % #ns) + 1]
20 | local p = o
21 | while p < min or p >= max do
22 | p = o + math.random(0, 8) * 12
23 | end
24 | os[#os+1] = p
25 | end
26 | table.sort(os)
27 | return os
28 | end
29 |
30 |
31 | math.randomseed(os.time())
32 | ns = mkchord(40, 83, 5, Chord:new(0, "minor", 'i7'))
33 |
34 | d = { 1, 2, 1, 2, 2, 2, 1 }
35 |
36 | function doe(instr, a, ns, i, dur)
37 | local d = dur / d[i]
38 | play(instr, a + ns[i], i == 1 and 0.7 or 0.5, d/2)
39 | at(d, "doe", instr, a, ns, (i%#ns)+1, dur)
40 | end
41 |
42 | doe(piano, 0, ns, 1, 0.6)
43 | doe(violin, -12, ns, 1, 0.62)
44 |
45 |
46 |
47 | -- vi: ft=lua ts=3 sw=3
48 |
49 |
--------------------------------------------------------------------------------
/test/simple-piano.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- A simple midi piano using fluidsynth
4 | --
5 |
6 | jack = Jack:new("worp")
7 | ls = Linuxsampler:new("synth", "/opt/samples")
8 |
9 | midi = jack:midi()
10 |
11 | piano = ls:add("piano/Bosendorfer.gig", 0)
12 | midi:map_instr(1, piano)
13 |
14 | jack:connect("synth")
15 | jack:connect("worp")
16 |
17 | -- vi: ft=lua ts=3 sw=3
18 |
19 |
--------------------------------------------------------------------------------
/test/simple-synth.lua:
--------------------------------------------------------------------------------
1 |
2 | --
3 | -- A simple polyphonic synth, using DSP code to generate nodes on midi input
4 | --
5 |
6 | jack = Jack:new("worp")
7 | gui = Gui:new("worp")
8 | fs = Fluidsynth:new("worp", "/usr/share/sounds/sf2/FluidR3_GM.sf2")
9 |
10 |
11 | midi = jack:midi("midi")
12 |
13 | c = Dsp:Const()
14 | gui:add_mod(c)
15 |
16 | midi:map_mod(1, 1, c)
17 |
18 | -- Voice generator module
19 |
20 | function voice()
21 |
22 | local osc = Dsp:Saw()
23 | local filter = Dsp:Filter { type = "lp" }
24 | local adsr = Dsp:Adsr { A = 0.03, D = 0.03, S = 0.6, R = 0.6 }
25 | local adsr2 = Dsp:Adsr { A = 0.3, D = 0.8, S = 0.5, R = 0.6 }
26 | local lfo = Dsp:Osc { f = 6 }
27 | local freq
28 |
29 | return Dsp:Mod({
30 | id = "synth",
31 | description = "Simple synth",
32 | controls = {
33 | {
34 | id = "f",
35 | description = "Frequency",
36 | max = 20000,
37 | log = true,
38 | unit = "Hz",
39 | default = 440,
40 | fn_set = function(v)
41 | osc:set { f = v }
42 | freq = v
43 | end
44 | }, {
45 | id = "vel",
46 | description = "Velocity",
47 | fn_set = function(v)
48 | adsr:set { vel = v }
49 | adsr2:set { vel = v == 0 and 0 or 1 }
50 | end
51 | },
52 | },
53 | fn_gen = function()
54 | filter:set { f = adsr2() * (lfo() * 0.1 + 1) * freq * c() * 5 }
55 | return filter(osc()) * adsr()
56 | end
57 | }, init)
58 |
59 | end
60 |
61 |
62 | v, synth = Dsp:Poly { gen = voice }
63 |
64 | midi:map_instr(1, synth)
65 |
66 | jack:dsp("worp", 0, 1, function()
67 | return v()
68 | end)
69 |
70 | -- Connect ports
71 |
72 | jack:connect("worp")
73 |
74 | -- vi: ft=lua ts=3 sw=3
75 |
76 |
--------------------------------------------------------------------------------
/vimplugin.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | package.path = package.path .. ";./lib/?.lua"
4 | package.cpath = package.cpath .. ";./lib/?.so"
5 | local p = require "posix"
6 |
7 | local function send(code)
8 | fd = p.socket(p.AF_INET, p.SOCK_DGRAM, 0)
9 | p.sendto(fd, code, { family = p.AF_INET, addr = "127.0.0.1", port = 9889 })
10 | p.close(fd)
11 | end
12 |
13 |
14 | function worp(what)
15 |
16 | local b = vim.buffer()
17 |
18 | local from = vim.firstline
19 | local to = vim.lastline
20 |
21 | if what == "stop" then
22 | from, to = 0, 0
23 | send("stop()")
24 | elseif what == "all" then
25 | from = 1
26 | to = #b
27 | elseif what == "paragraph" then
28 | while from > 1 and b[from-1]:find("%S") do
29 | from = from - 1
30 | end
31 | while to < #b and b[to+1]:find("%S") do
32 | to = to + 1
33 | end
34 | elseif what == "function" then
35 | while from > 1 and not b[from]:find("^function") do
36 | from = from - 1
37 | end
38 | while to < #b and not b[to]:find("^end") do
39 | to = to + 1
40 | end
41 | end
42 |
43 | vim.command("sign define sent text=┆ texthl=nonText")
44 | vim.command("sign unplace *")
45 |
46 | if from > 0 then
47 | local code = {}
48 | for i = from, to do
49 | vim.command("sign place " .. i .. " line=" .. i .. " name=sent file=" .. b.fname)
50 | code[#code+1] = b[i]
51 | end
52 | code[#code+1] = "-- live " .. from .. " " .. to .. " " .. (b.name or "")
53 | send(table.concat(code, "\n"))
54 | else
55 | vim.command("sign place 1 line=1 name=sent file=" .. b.fname)
56 | end
57 |
58 | end
59 |
60 | vim.command(':noremap ,a :lua worp("all")')
61 | vim.command(':noremap ,f :lua worp("function")')
62 | vim.command(':noremap ,p :lua worp("paragraph")')
63 | vim.command(':noremap ,v :lua worp("visual")')
64 | vim.command(':noremap ,, :lua worp("stop")')
65 | vim.command(':noremap :lua worp("line")')
66 |
67 | -- vi: ft=lua ts=3 sw=3
68 |
--------------------------------------------------------------------------------
/worp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/luajit
2 |
3 | package.path = package.path .. ";./lib/?.lua"
4 | package.cpath = package.cpath .. ";./lib/?.so;./app/?.so"
5 | _ENV = _G
6 |
7 | srate = 44100
8 |
9 | --
10 | -- Application libraries
11 | --
12 |
13 | P = require "luaposix"
14 | ffi = require "ffi"
15 |
16 | --require "app.strict"
17 | require "app.log"
18 | require "app.misc"
19 | require "app.evq"
20 | require "app.sandbox"
21 | require "app.co"
22 | require "app.getopt"
23 | require "app.autoload"
24 |
25 | require "lib/Core"
26 |
27 | --
28 | -- Main
29 | --
30 |
31 | local opt, arg = getopt(arg, 'l:')
32 |
33 | logf_init(opt.l or "")
34 |
35 | --
36 | -- Setup sandbox and load any files passed as arguments
37 | --
38 |
39 | sandbox = Sandbox:new()
40 |
41 | for _, fname in ipairs(arg) do
42 | local fd, err = io.open(fname)
43 | if fd then
44 | sandbox:load(fd:read("*a"), fname .. ":1")
45 | else
46 | logf(LG_WRN, "Could not open %s: %s", fname, err)
47 | end
48 | end
49 |
50 |
51 | --
52 | -- Open an UDP socket to receive Lua code chunks, and register to the
53 | -- mail loop.
54 | --
55 |
56 | local s = P.socket(P.AF_INET, P.SOCK_DGRAM, 0)
57 | P.bind(s, { family = P.AF_INET, port = 9889, addr = "127.0.0.1" })
58 |
59 | watch_fd(s, function()
60 | local code = P.recv(s, 65535)
61 | local from, to, name = 1, 1, "?"
62 | local f, t, n = code:match("\n%-%- live (%d+) (%d+) ([^\n]+)")
63 | if f then from, to, name = f, t, n end
64 | sandbox:load(code, "live " .. name .. ":" .. from)
65 | end)
66 |
67 |
68 | logf(LG_INF, "Ready")
69 |
70 | mainloop()
71 | stop()
72 |
73 | logf(LG_INF, "Bye")
74 |
75 | -- vi: ft=lua ts=3 sw=3
76 |
--------------------------------------------------------------------------------