├── .gitignore ├── .gitmodules ├── README.md ├── example.lua ├── example_midi.lua ├── lua ├── clock.lua ├── grid.lua ├── help.lua ├── isms.lua ├── midi.lua ├── sequins.lua └── tabutil.lua ├── polyperc.scd ├── src ├── clock.c ├── clock.h ├── clocks │ ├── clock_crow.c │ ├── clock_crow.h │ ├── clock_internal.c │ ├── clock_internal.h │ ├── clock_midi.c │ ├── clock_midi.h │ ├── clock_scheduler.c │ └── clock_scheduler.h ├── event.c ├── event.h ├── input.c ├── input.h ├── interface.c ├── interface.h ├── lua.c ├── lua.h ├── main.c ├── metro.c ├── metro.h ├── midi.c ├── midi.h ├── monome.c ├── monome.h ├── osc.c ├── osc.h ├── platform_clock.c ├── platform_clock.h ├── sdl.c ├── sdl.h ├── socket.c └── socket.h ├── test.lua ├── waf └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | *.swp 4 | 5 | # Prerequisites 6 | *.d 7 | 8 | # Object files 9 | *.o 10 | *.ko 11 | *.obj 12 | *.elf 13 | 14 | # Linker output 15 | *.ilk 16 | *.map 17 | *.exp 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | 29 | # Shared objects (inc. Windows DLLs) 30 | *.dll 31 | *.so 32 | *.so.* 33 | *.dylib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.i*86 40 | *.x86_64 41 | *.hex 42 | 43 | # Debug files 44 | *.dSYM/ 45 | *.su 46 | *.idb 47 | *.pdb 48 | 49 | # Kernel Module Compile Results 50 | *.mod* 51 | *.cmd 52 | .tmp_versions/ 53 | modules.order 54 | Module.symvers 55 | Mkfile.old 56 | dkms.conf 57 | 58 | # waf 59 | .lock* 60 | .waf-* 61 | .waf3-* 62 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rtmidi"] 2 | path = rtmidi 3 | url = https://github.com/thestk/rtmidi.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isms 2 | 3 | lua + clocks + osc + grid + midi + ui 4 | 5 | requires lua 5.4, sdl2, liblo 6 | 7 | (apt-get: liblua5.4-dev libsdl2-dev liblo-dev) 8 | 9 | currently only tested on ubuntu linux 10 | 11 | ## install 12 | 13 | ``` 14 | ./waf configure 15 | ./waf 16 | sudo ./waf install 17 | sudo ./waf install_lib 18 | ``` 19 | 20 | ## currently 21 | 22 | - run `isms example.lua` (or whatever file as argument) 23 | - a new window is created which accepts key input to place a random pixel line and send an OSC message to supercollider 24 | - `polyperc.scd` will provide a rudimentary osc-listening synth for testing OSC using SuperCollider 25 | - if found, connects to grid via serialosc and sends OSC on key input 26 | - a rudimentary REPL is implemented, try `print('hello')` 27 | - from graphic window, ctrl-q quits (or close the window), ctrl-r reloads lua script 28 | - `q` command quits from the repl, `isms.run()` reloads current file (or clears and loads a different file) 29 | - remote lua input via UDP on port 11001 ie `echo -n "print('hello')" > /dev/udp/localhost/11001` (set up your editor to send to this port) 30 | - .vimrc map: 31 | map :silent .w !xargs -0 echo -n > /dev/udp/localhost/11001 32 | - multiple instances supported, allocated ports shown at startup 33 | 34 | 35 | ## user/system folder structure 36 | 37 | library folder: `~/.local/share/isms` 38 | 39 | - any user libraries can go here, which can be included with `require` 40 | - `system` subfolder is copied by `./waf install_lib`. 41 | - `init.lua` will be run at startup, before the script. global configuration data can go here 42 | 43 | 44 | ## TODO 45 | ``` 46 | - cpu usage at rest (should be lower) 47 | - consider implications of running multiple instances 48 | x osc port assignments (ie auto-increment if requested is taken) 49 | - grid/midi "focus" 50 | - config file (pre-run) for setting "reserved" grid/midi slots (ie serial numbers) 51 | - ~/.local/share/isms/init.lua 52 | 53 | - sdl 54 | - text 55 | - more drawing functions 56 | - (?) mouse events 57 | - socket input: allow blocks, not just line 58 | - repl history (readline/etc) 59 | - repl: fix color coding (where text is coming from: socket or local) 60 | 61 | - optimization 62 | - sdl should probably have its own thread 63 | - security: should udp socket input check incoming ip (restrict to localhost?) 64 | - or arg to disable 65 | 66 | - (?) metro allocator 67 | ``` 68 | 69 | 70 | ## lua 71 | 72 | ``` 73 | g = grid.connect(id) -- id = index or default 0 74 | g.all(z) 75 | g.led(x,y,z) 76 | g.redraw() 77 | g.event.key(x,y,z) 78 | 79 | grid.all(id,z) 80 | grid.led(id,x,y,z) 81 | grid.redraw(id) 82 | grid.event.key(id,x,y,z) 83 | grid.event.add(id,serial) 84 | grid.event.remove(id,serial) 85 | 86 | window.init(x,y) 87 | window.pixel(x,y,color) 88 | window.line(x1,y1,x2,y2,color) 89 | window.redraw() 90 | window.event.key(code) 91 | 92 | metro.start(index, time_sec, count, stage) 93 | metro.stop(index) 94 | metro.event.tick(index, stage) 95 | 96 | osc.send(path, args) 97 | osc.event.receive(path, args, from) 98 | 99 | clock -- TODO 100 | midi -- currently removed, needs reimplementation 101 | 102 | ``` 103 | 104 | ## acknowledgements 105 | 106 | reimagining of norns. reconsidering design for use on any computer with large screen and keyboard. 107 | 108 | - based heavily on `matron` from norns, written by @catfact 109 | - sdl use patterned on work by @neauoire 110 | - additional thanks to @artem (clocks), @ngwese, and @csboling 111 | 112 | 113 | ## reference 114 | 115 | - https://www.lua.org/manual/5.4/manual.html#4 116 | - https://github.com/klassmann/sdl2-lua53-example/blob/master/src/main.c 117 | - https://lucasklassmann.com/blog/2019-02-02-how-to-embeddeding-lua-in-c/#running-lua-code 118 | - http://mech.math.msu.su/~vvb/2course/Borisenko/CppProjects/GWindow/xintro.html 119 | - https://hdante.wordpress.com/2014/07/08/the-hello-wayland-tutorial/ 120 | -------------------------------------------------------------------------------- /example.lua: -------------------------------------------------------------------------------- 1 | --- script 2 | print("example.lua -------------> hello there") 3 | 4 | window.init(256,128) 5 | window.clear() 6 | 7 | for i=0,255 do 8 | window.pixel(i,4,0xFFFF00-i) 9 | window.pixel(i,8,0x00FFFF-i) 10 | window.pixel(i,12,0xFF00FF-i) 11 | end 12 | 13 | window.redraw() 14 | 15 | x1,y1,x2,y2,c = 128,80,0,0,0xffffff 16 | 17 | g = grid.connect() 18 | 19 | window.event.key = function(x) 20 | x2 = x1 21 | y2 = y1 22 | x1 = math.random(192)+32 23 | y1 = math.random(64)+32 24 | g.led(x1%16,y1%8,0) 25 | g.redraw() 26 | c = c - 0x111111 27 | window.line(x1,y1,x2,y2,c) 28 | window.redraw() 29 | osc.send({"localhost",57120},"/n",{x%127}) 30 | print("key: "..x) 31 | end 32 | 33 | metro.event.tick = function(i,s) 34 | print("metro",i,s) 35 | g.all(s) 36 | g.redraw() 37 | end 38 | 39 | metro.start(1,0.1,5,0) 40 | 41 | g.event.key = function(x,y,z) 42 | print("example grid key",x,y,z) 43 | --osc.send({"localhost",57120},"/n",{(7-y)*5+x+30}) 44 | g.led(x,y,15); 45 | g.redraw(); 46 | end 47 | 48 | dofile("test.lua") 49 | 50 | --m = midi.connect() 51 | --m.event = function(d) tab.print(d) end 52 | 53 | --w = midi.connect(2) 54 | --[[ 55 | w:note_on(60,100) 56 | w:note_off(60,100) 57 | ]]-- 58 | 59 | -------------------------------------------------------------------------------- /example_midi.lua: -------------------------------------------------------------------------------- 1 | --- script 2 | 3 | window.init(256,128) 4 | window.clear() 5 | 6 | for i=0,255 do 7 | window.pixel(i,4,0xFFFF00-i) 8 | window.pixel(i,8,0x00FFFF-i) 9 | window.pixel(i,12,0xFF00FF-i) 10 | end 11 | 12 | window.redraw() 13 | 14 | midi.event = function(channel, type, data1, data2) 15 | if type == midi.types.note_on then 16 | print('midi note_on', channel, data1, data2) 17 | elseif type == midi.types.note_off then 18 | print('midi note_off', channel, data1) 19 | else 20 | print('midi other', channel, type, data1, data2) 21 | end 22 | end 23 | 24 | midi.send(1, midi.types.note_on, 48, 127) 25 | 26 | dofile("test.lua") 27 | 28 | --m = midi.connect() 29 | --m.event = function(d) tab.print(d) end 30 | 31 | --w = midi.connect(2) 32 | --[[ 33 | w:note_on(60,100) 34 | w:note_off(60,100) 35 | ]]-- 36 | 37 | -------------------------------------------------------------------------------- /lua/clock.lua: -------------------------------------------------------------------------------- 1 | --- clock coroutines 2 | -- @module clock 3 | 4 | local clock = {} 5 | 6 | clock.threads = {} 7 | 8 | local clock_id_counter = 1 9 | local function new_id() 10 | local id = clock_id_counter 11 | clock_id_counter = clock_id_counter + 1 12 | return id 13 | end 14 | 15 | --- create and start a coroutine using the norns clock scheduler. 16 | -- @tparam function f coroutine body function 17 | -- @param[opt] ... any extra arguments will be passed to the body function 18 | -- @treturn integer coroutine handle that can be used with clock.cancel 19 | -- @see clock.cancel 20 | clock.run = function(f, ...) 21 | local coro = coroutine.create(f) 22 | local coro_id = new_id() 23 | clock.threads[coro_id] = coro 24 | clock.resume(coro_id, ...) 25 | return coro_id 26 | end 27 | 28 | --- stop execution of a coroutine previously started using clock.run. 29 | -- @tparam integer coro_id coroutine handle 30 | -- @see clock.run 31 | clock.cancel = function(coro_id) 32 | isms.clock_cancel(coro_id) 33 | clock.threads[coro_id] = nil 34 | end 35 | 36 | local SCHEDULE_SLEEP = 0 37 | local SCHEDULE_SYNC = 1 38 | 39 | --- suspend execution of the calling coroutine and schedule resuming in specified time. 40 | -- must be called from a coroutine previously started using clock.run. 41 | -- @tparam float s seconds to wait for 42 | clock.sleep = function(...) 43 | return coroutine.yield(SCHEDULE_SLEEP, ...) 44 | end 45 | 46 | --- suspend execution of the calling coroutine and schedule resuming at the next sync quantum of the specified value. 47 | -- must be called from a coroutine previously started using clock.run. 48 | -- @tparam float beat sync quantum. may be larger than 1 49 | -- @tparam[opt] float offset if set, this value will be added to the sync quantum 50 | clock.sync = function(...) 51 | return coroutine.yield(SCHEDULE_SYNC, ...) 52 | end 53 | 54 | 55 | -- todo: use c api instead 56 | clock.resume = function(coro_id, ...) 57 | local coro = clock.threads[coro_id] 58 | 59 | local result, mode, time, offset = coroutine.resume(coro, ...) 60 | 61 | if coroutine.status(coro) == "dead" then 62 | if result then 63 | clock.cancel(coro_id) 64 | else 65 | error(mode) 66 | end 67 | else 68 | -- not dead 69 | if result and mode ~= nil then 70 | if mode == SCHEDULE_SLEEP then 71 | isms.clock_schedule_sleep(coro_id, time) 72 | elseif mode == SCHEDULE_SYNC then 73 | isms.clock_schedule_sync(coro_id, time, offset) 74 | else 75 | error('invalid clock scheduler mode') 76 | end 77 | end 78 | end 79 | end 80 | 81 | 82 | clock.clear = function() 83 | for id, coro in pairs(clock.threads) do 84 | if coro then 85 | clock.cancel(id) 86 | end 87 | end 88 | 89 | clock.transport.start = nil 90 | clock.transport.stop = nil 91 | end 92 | 93 | --- select the sync source. 94 | -- @tparam string source "internal", "midi" 95 | clock.set_source = function(source) 96 | if type(source) == "number" then 97 | isms.clock_set_source(util.clamp(source-1,0,1)) -- lua list is 1-indexed 98 | elseif source == "internal" then 99 | isms.clock_set_source(0) 100 | elseif source == "midi" then 101 | isms.clock_set_source(1) 102 | else 103 | error("unknown clock source: "..source) 104 | end 105 | end 106 | 107 | --- get current time in beats. 108 | clock.get_beats = function() 109 | return isms.clock_get_time_beats() 110 | end 111 | 112 | --- get current tempo. 113 | clock.get_tempo = function() 114 | return isms.clock_get_tempo() 115 | end 116 | 117 | --- get length of a single beat at current tempo in seconds. 118 | clock.get_beat_sec = function() 119 | return 60.0 / clock.get_tempo() 120 | end 121 | 122 | 123 | clock.transport = {} 124 | 125 | clock.transport.start = nil 126 | clock.transport.stop = nil 127 | 128 | 129 | clock.internal = {} 130 | 131 | clock.internal.set_tempo = function(bpm) 132 | return isms.clock_internal_set_tempo(bpm) 133 | end 134 | 135 | clock.internal.start = function() 136 | return isms.clock_internal_start() 137 | end 138 | 139 | clock.internal.stop = function() 140 | return isms.clock_internal_stop() 141 | end 142 | 143 | clock.crow = {} 144 | 145 | clock.crow.in_div = function(div) 146 | isms.clock_crow_in_div(div) 147 | end 148 | 149 | 150 | clock.midi = {} 151 | 152 | isms.clock_start = function() 153 | if clock.transport.start ~= nil then 154 | clock.transport.start() 155 | end 156 | end 157 | 158 | isms.clock_stop = function() 159 | if clock.transport.stop ~= nil then 160 | clock.transport.stop() 161 | end 162 | end 163 | 164 | 165 | clock.help = [[ 166 | -------------------------------------------------------------------------------- 167 | clock.run( func ) start a new coroutine with function [func] 168 | (returns) created id 169 | clock.cancel( id ) cancel coroutine [id] 170 | clock.sleep( time ) resume in [time] seconds 171 | clock.sync( beats ) resume at next sync quantum of value [beats] 172 | following to global tempo 173 | clock.get_beats() (returns) current time in beats 174 | clock.get_tempo() (returns) current tempo 175 | clock.get_beat_sec() (returns) length of a single beat at current 176 | tempo in seconds 177 | -------------------------------------------------------------------------------- 178 | ]] 179 | 180 | return clock 181 | -------------------------------------------------------------------------------- /lua/grid.lua: -------------------------------------------------------------------------------- 1 | grid.lookup = {} 2 | 3 | function grid.connect(n) 4 | n = n or 0 5 | local g = {} 6 | function g.all(z) grid.all(n,z) end 7 | function g.led(x,y,z) grid.led(n,x,y,z) end 8 | function g.redraw() grid.redraw(n) end 9 | function g.all(z) grid.all(n,z) end 10 | g.event = {} 11 | function g.event.key(x,y,z) print("grid key",x,y,z) end 12 | grid.lookup[n] = g 13 | return g 14 | end 15 | 16 | function grid.event.key(n,x,y,z) 17 | if grid.lookup[n] then 18 | grid.lookup[n].event.key(x,y,z) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lua/help.lua: -------------------------------------------------------------------------------- 1 | function help(topic) 2 | local help_topics = "help(topic): clock, grid" 3 | if topic == nil then 4 | print(help_topics) 5 | elseif type(topic)=="table" then 6 | if topic.help then 7 | print(topic.help) 8 | end 9 | end 10 | end 11 | 12 | grid.help = [[ 13 | -------------------------------------------------------------------------------- 14 | grid.led( id, x, y, level ) set LED at [x,y] to [level] 15 | grid.all( id, level ) set all grid LED to [level] 16 | grid.redraw( id ) update the grid LED state 17 | 18 | grid.key( id, x, y, z ) function called with incoming grid key event 19 | -------------------------------------------------------------------------------- 20 | ]] 21 | -------------------------------------------------------------------------------- /lua/isms.lua: -------------------------------------------------------------------------------- 1 | --package.path = '/usr/local/share/isms/?.lua;'..package.path 2 | --package.path = '/usr/local/share/isms/system/?.lua;'..package.path 3 | local home = os.getenv('HOME') 4 | package.path = home..'/.local/share/isms/?.lua;'..package.path 5 | package.path = home..'/.local/share/isms/system/?.lua;'..package.path 6 | --print("lua init >> package.path: "..package.path) 7 | 8 | --function window.key(x) print('window key: '..x) end 9 | function osc.receive(path, args, from) print('osc: '..path) end 10 | function metro.tick(i,stage) print('metro: '..i..' '..stage) end 11 | 12 | tab = require('tabutil') 13 | midi = require('midi') 14 | clock = require('clock') 15 | require('help') 16 | require('grid') 17 | 18 | isms.state = {} 19 | 20 | function isms.clear() 21 | print(">> isms.clear") 22 | local state = {} 23 | setmetatable(_G, { 24 | __index = function (_,k) return state[k] end, 25 | __newindex = function(_,k,v) state[k] = v end, 26 | }) 27 | clock.clear() 28 | metro.clear() 29 | end 30 | 31 | function isms.run(file) 32 | isms.clear() 33 | if file then isms.state.file = file end 34 | if isms.state.file then 35 | dofile(isms.state.file) 36 | end 37 | end 38 | 39 | -- user config 40 | local config = home..'/.local/share/isms/init.lua' 41 | local f=io.open(config,"r") 42 | if f~=nil then 43 | io.close(f) 44 | dofile(config) 45 | end 46 | -------------------------------------------------------------------------------- /lua/midi.lua: -------------------------------------------------------------------------------- 1 | local midi = {} 2 | 3 | midi.types = { 4 | note_on = 0x90, 5 | note_off = 0x80, 6 | cc = 0xb0, 7 | } 8 | 9 | midi.event = function(channel, type, data1, data2) 10 | print('midi event', channel, type, data1, data2) 11 | end 12 | 13 | midi.send = function(channel, type, data1, data2) 14 | channel = channel - 1 15 | status = type + channel 16 | isms.midi_send({status, data1, data2}) 17 | end 18 | 19 | midi._event = function(data) 20 | status = data[1] 21 | 22 | channel = (status & 0x0F) + 1 23 | type = status & 0xF0 24 | 25 | data1 = data[2] 26 | data2 = data[3] 27 | 28 | midi.event(channel, type, data1, data2) 29 | end 30 | 31 | return midi 32 | -------------------------------------------------------------------------------- /lua/sequins.lua: -------------------------------------------------------------------------------- 1 | --- sequins 2 | -- nestable tables with sequencing behaviours & control flow 3 | -- TODO i think ASL can be defined in terms of a sequins... 4 | -- 5 | -- for the norns port, @tyleretters copy-pasta'd with no changes from: 6 | -- https://github.com/monome/crow/blob/34ce1e455f01fdef65a0d37aa97163b4cd14a115/lua/sequins.lua 7 | 8 | local S = {} 9 | 10 | function S.new(t) 11 | -- wrap a table in a sequins with defaults 12 | local s = { data = t 13 | , length = #t -- memoize table length for speed 14 | , set_ix = 1 -- force first stage to start at elem 1 15 | , ix = 1 -- current val 16 | , n = 1 -- can be a sequin 17 | } 18 | s.action = {up = s} 19 | setmetatable(s, S) 20 | return s 21 | end 22 | 23 | local function wrap_index(s, ix) return ((ix - 1) % s.length) + 1 end 24 | 25 | -- can this be generalized to cover every/count/times etc 26 | function S.setdata(self, t) 27 | self.data = t 28 | self.length = #t 29 | self.ix = wrap_index(self, self.ix) 30 | end 31 | 32 | function S.is_sequins(t) return getmetatable(t) == S end 33 | 34 | local function turtle(t, fn) 35 | -- apply fn to all nested sequins. default to 'next' 36 | if S.is_sequins(t) then 37 | if fn then 38 | return fn(t) 39 | else return S.next(t) end 40 | end 41 | return t 42 | end 43 | 44 | 45 | ------------------------------ 46 | --- control flow execution 47 | 48 | function S.next(self) 49 | local act = self.action 50 | if act.action then 51 | return S.do_ctrl(act) 52 | else return S.do_step(act) end 53 | end 54 | 55 | function S.select(self, ix) 56 | rawset(self, 'set_ix', ix) 57 | return self 58 | end 59 | 60 | function S.do_step(act) 61 | local s = act.up 62 | -- if .set_ix is set, it will be used, rather than incrementing by s.n 63 | local newix = wrap_index(s, s.set_ix or s.ix + turtle(s.n)) 64 | local retval, exec = turtle(s.data[newix]) 65 | if exec ~= 'again' then s.ix = newix; s.set_ix = nil end 66 | -- FIXME add protection for list of dead sequins. for now we just recur, hoping for a live sequin in nest 67 | if exec == 'skip' then return S.next(s) end 68 | return retval, exec 69 | end 70 | 71 | 72 | ------------------------------ 73 | --- control flow manipulation 74 | 75 | function S.do_ctrl(act) 76 | act.ix = act.ix + 1 77 | if not act.cond or act.cond(act) then 78 | retval, exec = S.next(act) 79 | if exec then act.ix = act.ix - 1 end -- undo increment 80 | else 81 | retval, exec = {}, 'skip' 82 | end 83 | if act.rcond then 84 | if act.rcond(act) then 85 | if exec == 'skip' then retval, exec = S.next(act) 86 | else exec = 'again' end 87 | end 88 | end 89 | return retval, exec 90 | end 91 | 92 | function S.reset(self) 93 | self.ix = self.length 94 | for _,v in ipairs(self.data) do turtle(v, S.reset) end 95 | local a = self.action 96 | while a.ix do 97 | a.ix = 0 98 | turtle(a.n, S.reset) 99 | a = a.action 100 | end 101 | end 102 | 103 | --- behaviour modifiers 104 | function S.step(self, s) self.n = s; return self end 105 | 106 | 107 | function S.extend(self, t) 108 | self.action = { up = self -- containing sequins 109 | , action = self.action -- wrap nested actions 110 | , ix = 0 111 | } 112 | for k,v in pairs(t) do self.action[k] = v end 113 | return self 114 | end 115 | 116 | function S._every(self) 117 | return (self.ix % turtle(self.n)) == 0 118 | end 119 | 120 | function S._times(self) 121 | return self.ix <= turtle(self.n) 122 | end 123 | 124 | function S._count(self) 125 | if self.ix < turtle(self.n) then return true 126 | else self.ix = 0 end -- reset 127 | end 128 | 129 | function S.cond(self, p) return S.extend(self, {cond = p}) end 130 | function S.condr(self, p) return S.extend(self, {cond = p, rcond = p}) end 131 | function S.every(self, n) return S.extend(self, {cond = S._every, n = n}) end 132 | function S.times(self, n) return S.extend(self, {cond = S._times, n = n}) end 133 | function S.count(self, n) return S.extend(self, {rcond = S._count, n = n}) end 134 | 135 | --- helpers in terms of core 136 | function S.all(self) return self:count(self.length) end 137 | function S.once(self) return self:times(1) end 138 | function S.peek(self) return self.data[self.ix] end 139 | 140 | 141 | --- metamethods 142 | 143 | S.__call = function(self, ...) 144 | return (self == S) and S.new(...) or S.next(self) 145 | end 146 | 147 | S.metaix = { settable = S.setdata 148 | , step = S.step 149 | , cond = S.cond 150 | , condr = S.condr 151 | , every = S.every 152 | , times = S.times 153 | , count = S.count 154 | , all = S.all 155 | , once = S.once 156 | , reset = S.reset 157 | , select = S.select 158 | , peek = S.peek 159 | } 160 | S.__index = function(self, ix) 161 | -- runtime calls to step() and select() should return values, not functions 162 | if type(ix) == 'number' then return self.data[ix] 163 | else 164 | return S.metaix[ix] 165 | end 166 | end 167 | 168 | S.__newindex = function(self, ix, v) 169 | if type(ix) == 'number' then self.data[ix] = v 170 | elseif ix == 'n' then rawset(self,ix,v) 171 | end 172 | end 173 | 174 | 175 | setmetatable(S, S) 176 | 177 | return S 178 | -------------------------------------------------------------------------------- /lua/tabutil.lua: -------------------------------------------------------------------------------- 1 | --- table utility 2 | -- @module lib.tabutil 3 | -- @alias tab 4 | 5 | local tab = {} 6 | 7 | --- print the contents of a table 8 | -- @tparam table t table to print 9 | tab.print = function(t) 10 | for k,v in pairs(t) do print(k .. '\t' .. tostring(v)) end 11 | end 12 | 13 | --- return a lexigraphically sorted array of keys for a table 14 | -- @tparam table t table to sort 15 | -- @treturn table sorted table 16 | tab.sort = function(t) 17 | local keys = {} 18 | for k in pairs(t) do table.insert(keys, k) end 19 | table.sort(keys) 20 | return keys 21 | end 22 | 23 | --- count the number of entries in a table; 24 | -- unlike table.getn() or #table, nil entries won't break the loop 25 | -- @tparam table t table to count 26 | -- @treturn number count 27 | tab.count = function(t) 28 | local c = 0 29 | for _ in pairs(t) do c = c + 1 end 30 | return c 31 | end 32 | 33 | --- search table for element 34 | -- @tparam table t table to check 35 | -- @param e element to look for 36 | -- @treturn boolean t/f is element is present 37 | tab.contains = function(t,e) 38 | for index, value in ipairs(t) do 39 | if value == e then return true end 40 | end 41 | return false 42 | end 43 | 44 | 45 | --- given a simple table of primitives, 46 | --- "invert" it so that values become keys and vice versa. 47 | --- this allows more efficient checks on multiple values 48 | -- @param t: a simple table 49 | tab.invert = function(t) 50 | local inv = {} 51 | for k,v in pairs(t) do 52 | inv[v] = k 53 | end 54 | return inv 55 | end 56 | 57 | 58 | --- search table for element, return key 59 | -- @tparam table t table to check 60 | -- @param e element to look for 61 | -- @return key, nil if not found 62 | tab.key = function(t,e) 63 | for index, value in ipairs(t) do 64 | if value == e then return index end 65 | end 66 | return nil 67 | end 68 | 69 | --- split multi-line string into table of strings 70 | -- @tparam string str string with line breaks 71 | -- @treturn table table with entries for each line 72 | tab.lines = function(str) 73 | local t = {} 74 | local function helper(line) 75 | table.insert(t, line) 76 | return "" 77 | end 78 | helper((str:gsub("(.-)\r?\n", helper))) 79 | return t 80 | end 81 | 82 | 83 | --- split string into table with delimiter 84 | -- @tparam string inputstr : string to split 85 | -- @tparam string sep : delimiter 86 | tab.split = function(inputstr, sep) 87 | if sep == nil then 88 | sep = "%s" 89 | end 90 | local t={} 91 | for str in string.gmatch(inputstr, "([^"..sep.."]+)") do 92 | table.insert(t, str) 93 | end 94 | return t 95 | end 96 | 97 | 98 | --- Save a table to disk. 99 | -- Saves tables, numbers, booleans and strings. 100 | -- Inside table references are saved. 101 | -- Does not save userdata, metatables, functions and indices of these. 102 | -- Based on http://lua-users.org/wiki/SaveTableToFile by ChillCode. 103 | -- @tparam table tbl Table to save. 104 | -- @tparam string filename Location to save to. 105 | -- @return On failure, returns an error msg. 106 | function tab.save(tbl, filename) 107 | local charS, charE = " ", "\n" 108 | local file, err = io.open(filename, "wb") 109 | if err then return err end 110 | 111 | -- initiate variables for save procedure 112 | local tables, lookup = { tbl }, { [tbl] = 1 } 113 | file:write("return {"..charE) 114 | 115 | for idx, t in ipairs(tables) do 116 | file:write("-- Table: {"..idx.."}"..charE) 117 | file:write("{"..charE) 118 | local thandled = {} 119 | 120 | for i, v in ipairs(t) do 121 | thandled[i] = true 122 | local stype = type(v) 123 | -- only handle value 124 | if stype == "table" then 125 | if not lookup[v] then 126 | table.insert(tables, v) 127 | lookup[v] = #tables 128 | end 129 | file:write(charS.."{"..lookup[v].."},"..charE) 130 | elseif stype == "string" then 131 | file:write(charS..string.format("%q", v)..","..charE) 132 | elseif stype == "number" then 133 | file:write(charS..tostring(v)..","..charE) 134 | elseif stype == "boolean" then 135 | file:write(charS..tostring(v)..","..charE) 136 | end 137 | end 138 | 139 | for i, v in pairs(t) do 140 | -- escape handled values 141 | if (not thandled[i]) then 142 | 143 | local str = "" 144 | local stype = type(i) 145 | -- handle index 146 | if stype == "table" then 147 | if not lookup[i] then 148 | table.insert(tables, i) 149 | lookup[i] = #tables 150 | end 151 | str = charS.."[{"..lookup[i].."}]=" 152 | elseif stype == "string" then 153 | str = charS.."["..string.format("%q", i).."]=" 154 | elseif stype == "number" then 155 | str = charS.."["..tostring(i).."]=" 156 | elseif stype == "boolean" then 157 | str = charS.."["..tostring(i).."]=" 158 | end 159 | 160 | if str ~= "" then 161 | stype = type(v) 162 | -- handle value 163 | if stype == "table" then 164 | if not lookup[v] then 165 | table.insert(tables, v) 166 | lookup[v] = #tables 167 | end 168 | file:write(str.."{"..lookup[v].."},"..charE) 169 | elseif stype == "string" then 170 | file:write(str..string.format("%q", v)..","..charE) 171 | elseif stype == "number" then 172 | file:write(str..tostring(v)..","..charE) 173 | elseif stype == "boolean" then 174 | file:write(str..tostring(v)..","..charE) 175 | end 176 | end 177 | end 178 | end 179 | file:write("},"..charE) 180 | end 181 | file:write("}") 182 | file:close() 183 | end 184 | 185 | --- Load a table that has been saved via the tab.save() function. 186 | -- @tparam string sfile Filename or stringtable to load. 187 | -- @return On success, returns a previously saved table. On failure, returns as second argument an error msg. 188 | function tab.load(sfile) 189 | local ftables,err = loadfile(sfile) 190 | if err then return _, err end 191 | local tables = ftables() 192 | for idx = 1, #tables do 193 | local tolinki = {} 194 | for i, v in pairs(tables[idx]) do 195 | if type(v) == "table" then 196 | tables[idx][i] = tables[v[1]] 197 | end 198 | if type(i) == "table" and tables[i[1]] then 199 | table.insert(tolinki, { i, tables[i[1]] }) 200 | end 201 | end 202 | -- link indices 203 | for _, v in ipairs(tolinki) do 204 | tables[idx][v[2]], tables[idx][v[1]] = tables[idx][v[1]], nil 205 | end 206 | end 207 | return tables[1] 208 | end 209 | 210 | --- Create a read-only proxy for a given table. 211 | -- @param params params.table is the table to proxy, params.except a list of writable keys, params.expose limits which keys from params.table are exposed (optional) 212 | -- @treturn table the proxied read-only table 213 | function tab.readonly(params) 214 | local t = params.table 215 | local exceptions = params.except or {} 216 | local proxy = {} 217 | local mt = { 218 | __index = function(_, k) 219 | if params.expose == nil or tab.contains(params.expose, k) then 220 | return t[k] 221 | end 222 | return nil 223 | end, 224 | __newindex = function (_,k,v) 225 | if (tab.contains(exceptions, k)) then 226 | t[k] = v 227 | else 228 | error("'"..k.."', a read-only key, cannot be re-assigned.") 229 | end 230 | end, 231 | __pairs = function (_) return pairs(proxy) end, 232 | __ipairs = function (_) return ipairs(proxy) end, 233 | } 234 | setmetatable(proxy, mt) 235 | return proxy 236 | end 237 | 238 | 239 | --- return new table, gathering values: 240 | --- - first from default_values, 241 | --- - then from (i.e. overridden by) custom_values 242 | --- nils in custom_values are ignored 243 | -- @tparam table default_values base values (provides keys & fallback values) 244 | -- @tparam table custom_values override values (take precedence) 245 | -- @treturn table composite table 246 | function tab.gather(default_values, custom_values) 247 | local result = {} 248 | for k,v in pairs(default_values) do 249 | result[k] = (custom_values[k] ~= nil) and custom_values[k] or v 250 | end 251 | return result 252 | end 253 | 254 | --- mutate first table, updating values from second table. 255 | --- new keys from second table will be added to first. 256 | --- nils in updated_values are ignored 257 | -- @tparam table table_to_mutate table to mutate 258 | -- @tparam table updated_values override values (take precedence) 259 | -- @treturn table composite table 260 | function tab.update(table_to_mutate, updated_values) 261 | for k,v in pairs(updated_values) do 262 | if updated_values[k] ~= nil then 263 | table_to_mutate[k] = updated_values[k] 264 | end 265 | end 266 | return table_to_mutate 267 | end 268 | 269 | --- Create a new table with all values that pass the test implemented by the provided function. 270 | -- @tparam table t table to check 271 | -- @param condition callback function that tests all values of provided table, passes value and key as arguments 272 | -- @treturn table table with values that pass the test 273 | tab.select_values = function(tbl, condition) 274 | local t = {} 275 | 276 | for k,v in pairs(tbl) do 277 | if condition(v,k) then t[k] = v end 278 | end 279 | 280 | return t 281 | end 282 | 283 | return tab 284 | -------------------------------------------------------------------------------- /polyperc.scd: -------------------------------------------------------------------------------- 1 | // example synth 2 | SynthDef("PolyPerc", { 3 | arg out, freq = 880, pw=0.5, amp=0.5, cutoff=800, gain=1, release=0.9, pan=0; 4 | var snd = Pulse.ar(freq, pw); 5 | var filt = MoogFF.ar(snd,cutoff,gain); 6 | var env = Env.perc(level: amp, releaseTime: release).kr(2); 7 | Out.ar(out, Pan2.ar((filt*env), pan)); 8 | }).add; 9 | 10 | o = OSCFunc({ arg msg, time, addr, recvPort; 11 | Synth(\PolyPerc, [\freq, msg[1].midicps]); 12 | }, '/n', n); 13 | 14 | 15 | // send test msg 16 | b = NetAddr.new("127.0.0.1", 10011); 17 | b.sendMsg("/hello", "there"); -------------------------------------------------------------------------------- /src/clock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "clock.h" 9 | #include "clocks/clock_internal.h" 10 | #include "clocks/clock_midi.h" 11 | #include "clocks/clock_scheduler.h" 12 | #include "event.h" 13 | 14 | static clock_source_t clock_source; 15 | 16 | void clock_init() { clock_set_source(CLOCK_SOURCE_INTERNAL); } 17 | 18 | void clock_deinit() { 19 | printf("clock >> deinit\n"); 20 | // TODO cancel threads? 21 | } 22 | 23 | void clock_reference_init(clock_reference_t *reference) { 24 | pthread_mutex_init(&(reference->lock), NULL); 25 | clock_update_source_reference(reference, 0, 0.5); 26 | } 27 | 28 | double clock_get_system_time() { 29 | struct timespec spec; 30 | clock_gettime(CLOCK_MONOTONIC, &spec); 31 | return spec.tv_sec + (spec.tv_nsec / 1.0e9); 32 | } 33 | 34 | double clock_get_beats() { 35 | double beat; 36 | 37 | switch (clock_source) { 38 | case CLOCK_SOURCE_INTERNAL: 39 | beat = clock_internal_get_beat(); 40 | break; 41 | case CLOCK_SOURCE_MIDI: 42 | beat = clock_midi_get_beat(); 43 | break; 44 | case CLOCK_SOURCE_CROW: 45 | // TODO beat = clock_crow_get_beat(); 46 | break; 47 | default: 48 | beat = 0; 49 | break; 50 | } 51 | 52 | return beat; 53 | } 54 | 55 | double clock_get_reference_beat(clock_reference_t *reference) { 56 | pthread_mutex_lock(&(reference->lock)); 57 | 58 | double current_time = clock_get_system_time(); 59 | double beat = reference->beat + ((current_time - reference->last_beat_time) / 60 | reference->beat_duration); 61 | 62 | pthread_mutex_unlock(&(reference->lock)); 63 | 64 | return beat; 65 | } 66 | 67 | double clock_get_tempo() { 68 | double tempo; 69 | 70 | switch (clock_source) { 71 | case CLOCK_SOURCE_INTERNAL: 72 | tempo = clock_internal_get_tempo(); 73 | break; 74 | case CLOCK_SOURCE_MIDI: 75 | tempo = clock_midi_get_tempo(); 76 | break; 77 | case CLOCK_SOURCE_CROW: 78 | // TODO tempo = clock_crow_get_tempo(); 79 | break; 80 | default: 81 | tempo = 0; 82 | break; 83 | } 84 | 85 | return tempo; 86 | } 87 | 88 | double clock_get_reference_tempo(clock_reference_t *reference) { 89 | pthread_mutex_lock(&(reference->lock)); 90 | 91 | double tempo = 60.0 / reference->beat_duration; 92 | 93 | pthread_mutex_unlock(&(reference->lock)); 94 | 95 | return tempo; 96 | } 97 | 98 | void clock_update_source_reference(clock_reference_t *reference, double beat, 99 | double beat_duration) { 100 | pthread_mutex_lock(&(reference->lock)); 101 | 102 | double current_time = clock_get_system_time(); 103 | 104 | reference->beat_duration = beat_duration; 105 | reference->last_beat_time = current_time; 106 | reference->beat = beat; 107 | 108 | pthread_mutex_unlock(&(reference->lock)); 109 | } 110 | 111 | void clock_start_from_source(clock_source_t source) { 112 | if (clock_source == source) { 113 | clock_scheduler_reset_sync_events(); 114 | union event_data *ev = event_data_new(EVENT_CLOCK_START); 115 | event_post(ev); 116 | } 117 | } 118 | 119 | void clock_stop_from_source(clock_source_t source) { 120 | if (clock_source == source) { 121 | union event_data *ev = event_data_new(EVENT_CLOCK_STOP); 122 | event_post(ev); 123 | } 124 | } 125 | 126 | void clock_reschedule_sync_events_from_source(clock_source_t source) { 127 | if (clock_source == source) { 128 | clock_scheduler_reschedule_sync_events(); 129 | } 130 | } 131 | 132 | void clock_set_source(clock_source_t source) { 133 | clock_source = source; 134 | clock_scheduler_reschedule_sync_events(); 135 | } 136 | -------------------------------------------------------------------------------- /src/clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef enum { 7 | CLOCK_SOURCE_INTERNAL = 0, 8 | CLOCK_SOURCE_MIDI = 1, 9 | CLOCK_SOURCE_CROW = 2, 10 | } clock_source_t; 11 | 12 | typedef struct { 13 | double beat; 14 | double beat_duration; 15 | double last_beat_time; 16 | pthread_mutex_t lock; 17 | } clock_reference_t; 18 | 19 | void clock_init(); 20 | void clock_reference_init(clock_reference_t *reference); 21 | void clock_deinit(); 22 | void clock_update_source_reference(clock_reference_t *reference, double beats, 23 | double beat_duration); 24 | double clock_get_reference_beat(clock_reference_t *reference); 25 | double clock_get_reference_tempo(clock_reference_t *reference); 26 | void clock_start_from_source(clock_source_t source); 27 | void clock_stop_from_source(clock_source_t source); 28 | void clock_reschedule_sync_events_from_source(clock_source_t source); 29 | void clock_set_source(clock_source_t source); 30 | 31 | double clock_get_beats(); 32 | double clock_get_system_time(); 33 | double clock_get_tempo(); 34 | -------------------------------------------------------------------------------- /src/clocks/clock_crow.c: -------------------------------------------------------------------------------- 1 | #include "clock_crow.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static bool clock_crow_last_time_set; 7 | static int clock_crow_counter; 8 | static double clock_crow_last_time; 9 | static clock_reference_t clock_crow_reference; 10 | 11 | #define DURATION_BUFFER_LENGTH 4 12 | 13 | static double duration_buf[DURATION_BUFFER_LENGTH] = {0}; 14 | static uint8_t beat_duration_buf_pos = 0; 15 | static uint8_t beat_duration_buf_len = 0; 16 | static double mean_sum; 17 | static double mean_scale; 18 | 19 | static double crow_in_div = 4.0; 20 | static bool crow_in_div_changed = false; 21 | static pthread_mutex_t crow_in_div_lock; 22 | 23 | void clock_crow_in_div(int div) { 24 | pthread_mutex_lock(&crow_in_div_lock); 25 | crow_in_div = (double)div; 26 | crow_in_div_changed = true; 27 | pthread_mutex_unlock(&crow_in_div_lock); 28 | } 29 | 30 | void clock_crow_init() { 31 | clock_crow_counter = 0; 32 | clock_crow_last_time_set = false; 33 | mean_sum = 0; 34 | clock_reference_init(&clock_crow_reference); 35 | } 36 | 37 | void clock_crow_handle_clock() { 38 | double beat_duration; 39 | double current_time = clock_get_system_time(); 40 | 41 | if (clock_crow_last_time_set == false) { 42 | clock_crow_last_time_set = true; 43 | clock_crow_last_time = current_time; 44 | } else { 45 | pthread_mutex_lock(&crow_in_div_lock); 46 | beat_duration = (current_time - clock_crow_last_time) * crow_in_div; 47 | 48 | if (beat_duration > 4) { // assume clock stopped 49 | clock_crow_last_time = current_time; 50 | } else { 51 | if (beat_duration_buf_len < DURATION_BUFFER_LENGTH) { 52 | beat_duration_buf_len++; 53 | mean_scale = 1.0 / beat_duration_buf_len; 54 | } 55 | 56 | double a = beat_duration * mean_scale; 57 | mean_sum = mean_sum + a; 58 | mean_sum = mean_sum - duration_buf[beat_duration_buf_pos]; 59 | duration_buf[beat_duration_buf_pos] = a; 60 | 61 | beat_duration_buf_pos = 62 | (beat_duration_buf_pos + 1) % DURATION_BUFFER_LENGTH; 63 | 64 | clock_crow_counter++; 65 | clock_crow_last_time = current_time; 66 | 67 | double reference_beat = clock_crow_counter / crow_in_div; 68 | clock_update_source_reference(&clock_crow_reference, reference_beat, 69 | mean_sum); 70 | 71 | if (crow_in_div_changed) { 72 | clock_reschedule_sync_events_from_source(CLOCK_SOURCE_CROW); 73 | crow_in_div_changed = false; 74 | } 75 | } 76 | 77 | pthread_mutex_unlock(&crow_in_div_lock); 78 | } 79 | } 80 | 81 | double clock_crow_get_beat() { 82 | return clock_get_reference_beat(&clock_crow_reference); 83 | } 84 | 85 | double clock_crow_get_tempo() { 86 | return clock_get_reference_tempo(&clock_crow_reference); 87 | } 88 | -------------------------------------------------------------------------------- /src/clocks/clock_crow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void clock_crow_init(); 6 | void clock_crow_handle_clock(); 7 | void clock_crow_in_div(int div); 8 | double clock_crow_get_beat(); 9 | double clock_crow_get_tempo(); 10 | -------------------------------------------------------------------------------- /src/clocks/clock_internal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "clock.h" 10 | #include "clock_internal.h" 11 | #include "clock_scheduler.h" 12 | #include "platform_clock.h" 13 | 14 | #define CLOCK_INTERNAL_TICKS_PER_BEAT 24 15 | 16 | typedef struct { 17 | double beat_duration; 18 | double tick_duration; 19 | } clock_internal_tempo_t; 20 | 21 | static pthread_t clock_internal_thread; 22 | static clock_reference_t clock_internal_reference; 23 | static bool clock_internal_restarted; 24 | 25 | static clock_internal_tempo_t clock_internal_tempo; 26 | static pthread_mutex_t clock_internal_tempo_lock; 27 | 28 | static void clock_internal_sleep(double seconds) { 29 | struct timespec ts; 30 | 31 | ts.tv_sec = seconds; 32 | ts.tv_nsec = (seconds - ts.tv_sec) * 1000000000; 33 | 34 | platform_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); 35 | } 36 | 37 | static void *clock_internal_thread_run(void *p) { 38 | (void)p; 39 | 40 | double current_time; 41 | double next_tick_time; 42 | 43 | double beat_duration; 44 | double tick_duration; 45 | double reference_beat; 46 | 47 | int ticks = -1; 48 | 49 | current_time = clock_get_system_time(); 50 | next_tick_time = current_time; 51 | 52 | while (true) { 53 | current_time = clock_get_system_time(); 54 | 55 | pthread_mutex_lock(&clock_internal_tempo_lock); 56 | beat_duration = clock_internal_tempo.beat_duration; 57 | tick_duration = clock_internal_tempo.tick_duration; 58 | pthread_mutex_unlock(&clock_internal_tempo_lock); 59 | 60 | clock_internal_sleep(tick_duration + (next_tick_time - current_time)); 61 | 62 | if (clock_internal_restarted) { 63 | ticks = 0; 64 | reference_beat = 0; 65 | 66 | clock_update_source_reference(&clock_internal_reference, reference_beat, 67 | beat_duration); 68 | clock_start_from_source(CLOCK_SOURCE_INTERNAL); 69 | 70 | clock_internal_restarted = false; 71 | } else { 72 | ticks++; 73 | reference_beat = (double)ticks / CLOCK_INTERNAL_TICKS_PER_BEAT; 74 | clock_update_source_reference(&clock_internal_reference, reference_beat, 75 | beat_duration); 76 | } 77 | 78 | next_tick_time += tick_duration; 79 | } 80 | 81 | return NULL; 82 | } 83 | 84 | static void clock_internal_start() { 85 | pthread_attr_t attr; 86 | 87 | pthread_attr_init(&attr); 88 | pthread_create(&clock_internal_thread, &attr, &clock_internal_thread_run, 89 | NULL); 90 | #ifdef _GNU_SOURCE 91 | pthread_setname_np(clock_internal_thread, "clock_internal"); 92 | #endif 93 | pthread_attr_destroy(&attr); 94 | } 95 | 96 | void clock_internal_init() { 97 | pthread_mutex_init(&clock_internal_tempo_lock, NULL); 98 | clock_internal_set_tempo(120); 99 | clock_reference_init(&clock_internal_reference); 100 | clock_internal_start(); 101 | } 102 | 103 | void clock_internal_set_tempo(double bpm) { 104 | pthread_mutex_lock(&clock_internal_tempo_lock); 105 | 106 | clock_internal_tempo.beat_duration = 60.0 / bpm; 107 | clock_internal_tempo.tick_duration = 108 | clock_internal_tempo.beat_duration / CLOCK_INTERNAL_TICKS_PER_BEAT; 109 | 110 | pthread_mutex_unlock(&clock_internal_tempo_lock); 111 | } 112 | 113 | void clock_internal_restart() { clock_internal_restarted = true; } 114 | 115 | void clock_internal_stop() { clock_stop_from_source(CLOCK_SOURCE_INTERNAL); } 116 | 117 | double clock_internal_get_beat() { 118 | return clock_get_reference_beat(&clock_internal_reference); 119 | } 120 | 121 | double clock_internal_get_tempo() { 122 | return clock_get_reference_tempo(&clock_internal_reference); 123 | } 124 | -------------------------------------------------------------------------------- /src/clocks/clock_internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void clock_internal_init(); 4 | void clock_internal_set_tempo(double bpm); 5 | void clock_internal_restart(); 6 | void clock_internal_stop(); 7 | double clock_internal_get_beat(); 8 | double clock_internal_get_tempo(); 9 | -------------------------------------------------------------------------------- /src/clocks/clock_midi.c: -------------------------------------------------------------------------------- 1 | #include "clock_midi.h" 2 | #include 3 | #include 4 | 5 | static int clock_midi_counter; 6 | static bool clock_midi_last_tick_time_set; 7 | static double clock_midi_last_tick_time; 8 | static clock_reference_t clock_midi_reference; 9 | 10 | #define DURATION_BUFFER_LENGTH 24 11 | 12 | static double duration_buf[DURATION_BUFFER_LENGTH] = {0}; 13 | static uint8_t beat_duration_buf_pos = 0; 14 | static uint8_t beat_duration_buf_len = 0; 15 | 16 | static double mean_scale; 17 | static double mean_sum; 18 | 19 | void clock_midi_init() { 20 | clock_midi_counter = 0; 21 | clock_midi_last_tick_time_set = false; 22 | mean_sum = 0; 23 | mean_scale = 1; 24 | clock_reference_init(&clock_midi_reference); 25 | } 26 | 27 | static void clock_midi_handle_clock() { 28 | double beat_duration; 29 | double current_time = clock_get_system_time(); 30 | 31 | if (clock_midi_last_tick_time_set == false) { 32 | clock_midi_last_tick_time_set = true; 33 | clock_midi_last_tick_time = current_time; 34 | } else { 35 | beat_duration = (current_time - clock_midi_last_tick_time) * 24.0; 36 | 37 | if (beat_duration > 4) { // assume clock stopped 38 | clock_midi_last_tick_time = current_time; 39 | } else { 40 | if (beat_duration_buf_len < DURATION_BUFFER_LENGTH) { 41 | beat_duration_buf_len++; 42 | mean_scale = 1.0 / beat_duration_buf_len; 43 | } 44 | 45 | double a = beat_duration * mean_scale; 46 | mean_sum = mean_sum + a; 47 | mean_sum = mean_sum - duration_buf[beat_duration_buf_pos]; 48 | duration_buf[beat_duration_buf_pos] = a; 49 | 50 | beat_duration_buf_pos = 51 | (beat_duration_buf_pos + 1) % DURATION_BUFFER_LENGTH; 52 | 53 | clock_midi_counter++; 54 | clock_midi_last_tick_time = current_time; 55 | 56 | double reference_beat = clock_midi_counter / 24.0; 57 | clock_update_source_reference(&clock_midi_reference, reference_beat, 58 | mean_sum); 59 | 60 | if (clock_midi_counter == 0) { 61 | clock_start_from_source(CLOCK_SOURCE_MIDI); 62 | } 63 | } 64 | } 65 | } 66 | 67 | static void clock_midi_handle_start() { clock_midi_counter = -1; } 68 | 69 | static void clock_midi_handle_stop() { 70 | clock_stop_from_source(CLOCK_SOURCE_MIDI); 71 | } 72 | 73 | void clock_midi_handle_message(uint8_t message) { 74 | switch (message) { 75 | case 0xfa: 76 | clock_midi_handle_start(); 77 | break; 78 | case 0xf8: 79 | clock_midi_handle_clock(); 80 | break; 81 | case 0xfc: 82 | clock_midi_handle_stop(); 83 | break; 84 | } 85 | } 86 | 87 | double clock_midi_get_beat() { 88 | return clock_get_reference_beat(&clock_midi_reference); 89 | } 90 | 91 | double clock_midi_get_tempo() { 92 | return clock_get_reference_tempo(&clock_midi_reference); 93 | } 94 | -------------------------------------------------------------------------------- /src/clocks/clock_midi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void clock_midi_init(); 6 | void clock_midi_handle_message(uint8_t message); 7 | double clock_midi_get_beat(); 8 | double clock_midi_get_tempo(); 9 | -------------------------------------------------------------------------------- /src/clocks/clock_scheduler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../clock.h" 9 | #include "../event.h" 10 | 11 | #include "clock_scheduler.h" 12 | 13 | typedef enum { 14 | CLOCK_SCHEDULER_EVENT_SYNC, 15 | CLOCK_SCHEDULER_EVENT_SLEEP, 16 | } clock_scheduler_event_type_t; 17 | 18 | typedef struct { 19 | clock_scheduler_event_type_t type; 20 | bool ready; 21 | int thread_id; 22 | 23 | double sync_beat; 24 | double sync_beat_offset; 25 | double sync_clock_beat; 26 | 27 | double sleep_time; 28 | double sleep_clock_time; 29 | } clock_scheduler_event_t; 30 | 31 | static pthread_t clock_scheduler_tick_thread; 32 | 33 | static clock_scheduler_event_t 34 | clock_scheduler_events[NUM_CLOCK_SCHEDULER_EVENTS]; 35 | static pthread_mutex_t clock_scheduler_events_lock; 36 | 37 | static void clock_scheduler_post_clock_resume_event(int thread_id, 38 | double value) { 39 | union event_data *ev = event_data_new(EVENT_CLOCK_RESUME); 40 | ev->clock_resume.thread_id = thread_id; 41 | ev->clock_resume.value = value; 42 | event_post(ev); 43 | } 44 | 45 | static double clock_scheduler_next_clock_beat(double clock_beat, 46 | double sync_beat, 47 | double sync_beat_offset) { 48 | double next_beat; 49 | 50 | next_beat = ceil((clock_beat + FLT_EPSILON) / sync_beat) * sync_beat; 51 | next_beat = next_beat + sync_beat_offset; 52 | 53 | while (next_beat < (clock_beat + FLT_EPSILON)) { 54 | next_beat += sync_beat; 55 | } 56 | 57 | return fmax(next_beat, 0); 58 | } 59 | 60 | static clock_scheduler_event_t *clock_scheduler_find_event(int thread_id) { 61 | int result = -1; 62 | 63 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 64 | if (clock_scheduler_events[i].thread_id == thread_id) { 65 | result = i; 66 | break; 67 | } 68 | 69 | // if an event for the given thread_id was not found, 70 | // return the index for the first available event 71 | if (clock_scheduler_events[i].thread_id == -1 && result < 0) { 72 | result = i; 73 | } 74 | } 75 | 76 | if (result > -1) { 77 | return &clock_scheduler_events[result]; 78 | } 79 | 80 | return NULL; 81 | } 82 | 83 | static void *clock_scheduler_tick_thread_run(void *p) { 84 | (void)p; 85 | clock_scheduler_event_t *event; 86 | double clock_beat; 87 | double clock_time; 88 | 89 | while (true) { 90 | pthread_mutex_lock(&clock_scheduler_events_lock); 91 | 92 | clock_time = clock_get_system_time(); 93 | clock_beat = clock_get_beats(); 94 | 95 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 96 | event = &clock_scheduler_events[i]; 97 | 98 | if (event->ready) { 99 | if (event->type == CLOCK_SCHEDULER_EVENT_SYNC) { 100 | if (clock_beat > event->sync_clock_beat) { 101 | clock_scheduler_post_clock_resume_event(event->thread_id, 102 | clock_beat); 103 | event->ready = false; 104 | } 105 | } else { 106 | if (clock_time >= event->sleep_clock_time) { 107 | clock_scheduler_post_clock_resume_event(event->thread_id, 108 | clock_time); 109 | event->ready = false; 110 | } 111 | } 112 | }; 113 | } 114 | 115 | pthread_mutex_unlock(&clock_scheduler_events_lock); 116 | usleep(1000); 117 | } 118 | 119 | return NULL; 120 | } 121 | 122 | void clock_scheduler_init() { 123 | pthread_mutex_init(&clock_scheduler_events_lock, NULL); 124 | 125 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 126 | clock_scheduler_events[i].ready = false; 127 | clock_scheduler_events[i].thread_id = -1; 128 | } 129 | 130 | clock_scheduler_start(); 131 | } 132 | 133 | void clock_scheduler_start() { 134 | pthread_attr_t attr; 135 | 136 | pthread_attr_init(&attr); 137 | pthread_create(&clock_scheduler_tick_thread, &attr, 138 | &clock_scheduler_tick_thread_run, NULL); 139 | #ifdef _GNU_SOURCE 140 | pthread_setname_np(clock_scheduler_tick_thread, "clock_sched_tick"); 141 | #endif 142 | pthread_attr_destroy(&attr); 143 | } 144 | 145 | bool clock_scheduler_schedule_sync(int thread_id, double sync_beat, 146 | double sync_beat_offset) { 147 | pthread_mutex_lock(&clock_scheduler_events_lock); 148 | 149 | double clock_beat = clock_get_beats(); 150 | clock_scheduler_event_t *event = clock_scheduler_find_event(thread_id); 151 | 152 | if (event != NULL) { 153 | if (event->thread_id == thread_id) { 154 | event->ready = true; 155 | event->sync_beat = sync_beat; 156 | event->sync_beat_offset = sync_beat_offset; 157 | 158 | // count from the stored sync_clock_beat when syncing the same coroutine 159 | if (event->type == CLOCK_SCHEDULER_EVENT_SYNC) { 160 | event->sync_clock_beat = clock_scheduler_next_clock_beat( 161 | event->sync_clock_beat, sync_beat, sync_beat_offset); 162 | } else { 163 | event->sync_clock_beat = clock_scheduler_next_clock_beat( 164 | clock_beat, sync_beat, sync_beat_offset); 165 | event->type = CLOCK_SCHEDULER_EVENT_SYNC; 166 | } 167 | } else { 168 | event->thread_id = thread_id; 169 | event->ready = true; 170 | event->sync_beat = sync_beat; 171 | event->sync_clock_beat = clock_scheduler_next_clock_beat( 172 | clock_beat, sync_beat, sync_beat_offset); 173 | event->type = CLOCK_SCHEDULER_EVENT_SYNC; 174 | } 175 | 176 | pthread_mutex_unlock(&clock_scheduler_events_lock); 177 | return true; 178 | } 179 | 180 | pthread_mutex_unlock(&clock_scheduler_events_lock); 181 | return false; 182 | } 183 | 184 | bool clock_scheduler_schedule_sleep(int thread_id, double seconds) { 185 | pthread_mutex_lock(&clock_scheduler_events_lock); 186 | 187 | double clock_time = clock_get_system_time(); 188 | clock_scheduler_event_t *event = clock_scheduler_find_event(thread_id); 189 | 190 | if (event != NULL) { 191 | event->ready = true; 192 | event->thread_id = thread_id; 193 | event->sleep_time = seconds; 194 | event->sleep_clock_time = clock_time + seconds; 195 | event->type = CLOCK_SCHEDULER_EVENT_SLEEP; 196 | 197 | pthread_mutex_unlock(&clock_scheduler_events_lock); 198 | return true; 199 | } 200 | 201 | pthread_mutex_unlock(&clock_scheduler_events_lock); 202 | return false; 203 | } 204 | 205 | void clock_scheduler_clear(int thread_id) { 206 | pthread_mutex_lock(&clock_scheduler_events_lock); 207 | 208 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 209 | if (clock_scheduler_events[i].thread_id == thread_id) { 210 | clock_scheduler_events[i].ready = false; 211 | clock_scheduler_events[i].thread_id = -1; 212 | } 213 | } 214 | 215 | pthread_mutex_unlock(&clock_scheduler_events_lock); 216 | } 217 | 218 | void clock_scheduler_clear_all() { 219 | pthread_mutex_lock(&clock_scheduler_events_lock); 220 | 221 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 222 | clock_scheduler_events[i].ready = false; 223 | clock_scheduler_events[i].thread_id = -1; 224 | } 225 | 226 | pthread_mutex_unlock(&clock_scheduler_events_lock); 227 | } 228 | 229 | void clock_scheduler_reschedule_sync_events() { 230 | clock_scheduler_event_t *event; 231 | 232 | pthread_mutex_lock(&clock_scheduler_events_lock); 233 | 234 | double clock_beat = clock_get_beats(); 235 | 236 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 237 | event = &clock_scheduler_events[i]; 238 | 239 | if (event->ready && event->type == CLOCK_SCHEDULER_EVENT_SYNC) { 240 | event->sync_clock_beat = clock_scheduler_next_clock_beat( 241 | clock_beat, event->sync_beat, event->sync_beat_offset); 242 | } 243 | } 244 | 245 | pthread_mutex_unlock(&clock_scheduler_events_lock); 246 | } 247 | 248 | void clock_scheduler_reset_sync_events() { 249 | clock_scheduler_event_t *event; 250 | 251 | pthread_mutex_lock(&clock_scheduler_events_lock); 252 | 253 | for (int i = 0; i < NUM_CLOCK_SCHEDULER_EVENTS; i++) { 254 | event = &clock_scheduler_events[i]; 255 | 256 | if (event->ready && event->type == CLOCK_SCHEDULER_EVENT_SYNC) { 257 | event->sync_clock_beat = 0; 258 | } 259 | } 260 | 261 | pthread_mutex_unlock(&clock_scheduler_events_lock); 262 | } 263 | -------------------------------------------------------------------------------- /src/clocks/clock_scheduler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define NUM_CLOCK_SCHEDULER_EVENTS 100 6 | 7 | void clock_scheduler_init(); 8 | void clock_scheduler_start(); 9 | bool clock_scheduler_schedule_sync(int thread_id, double sync_beat, 10 | double sync_beat_offset); 11 | bool clock_scheduler_schedule_sleep(int thread_id, double seconds); 12 | void clock_scheduler_clear(int thread_id); 13 | void clock_scheduler_clear_all(); 14 | void clock_scheduler_reschedule_sync_events(); 15 | void clock_scheduler_reset_sync_events(); 16 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "event.h" 15 | #include "interface.h" 16 | #include "lua.h" 17 | #include "osc.h" 18 | #include "sdl.h" 19 | 20 | //--- types and vars 21 | 22 | struct ev_node { 23 | struct ev_node *next; 24 | struct ev_node *prev; 25 | union event_data *ev; 26 | }; 27 | 28 | struct ev_q { 29 | struct ev_node *head; 30 | struct ev_node *tail; 31 | ssize_t size; 32 | pthread_cond_t nonempty; 33 | pthread_mutex_t lock; 34 | }; 35 | 36 | struct ev_q evq; 37 | bool quit; 38 | 39 | //--- static function declarations 40 | 41 | //--- handlers 42 | static void handle_event(union event_data *ev); 43 | 44 | // add an event data struct to the end of the event queue 45 | // *does* allocate queue node memory! 46 | // *does not* allocate event data memory! 47 | // call with the queue locked 48 | static void evq_push(union event_data *ev) { 49 | struct ev_node *evn = calloc(1, sizeof(struct ev_node)); 50 | evn->ev = ev; 51 | if (evq.size == 0) { 52 | insque(evn, NULL); 53 | evq.head = evn; 54 | } else { 55 | insque(evn, evq.tail); 56 | } 57 | evq.tail = evn; 58 | evq.size += 1; 59 | } 60 | 61 | // remove and return the event data struct from the top of the event queue 62 | // call with the queue locked 63 | // *does* free queue node memory! 64 | // *does not* free the event data memory! 65 | static union event_data *evq_pop() { 66 | struct ev_node *evn = evq.head; 67 | if (evn == NULL) { 68 | return NULL; 69 | } 70 | union event_data *ev = evn->ev; 71 | evq.head = evn->next; 72 | if (evn == evq.tail) { 73 | assert(evq.size == 1); 74 | evq.tail = NULL; 75 | } 76 | remque(evn); 77 | free(evn); 78 | evq.size -= 1; 79 | return ev; 80 | } 81 | 82 | //------------------------------- 83 | //-- extern function definitions 84 | 85 | void init_event(void) { 86 | evq.size = 0; 87 | evq.head = NULL; 88 | evq.tail = NULL; 89 | pthread_cond_init(&evq.nonempty, NULL); 90 | } 91 | 92 | union event_data *event_data_new(event_t type) { 93 | // FIXME: better not to allocate here, use object pool 94 | union event_data *ev = calloc(1, sizeof(union event_data)); 95 | ev->type = type; 96 | return ev; 97 | } 98 | 99 | void event_data_free(union event_data *ev) { 100 | switch (ev->type) { 101 | case EVENT_EXEC_CODE_LINE: 102 | free(ev->exec_code_line.line); 103 | break; 104 | case EVENT_OSC: 105 | free(ev->osc.path); 106 | free(ev->osc.from_host); 107 | free(ev->osc.from_port); 108 | lo_message_free(ev->osc.msg); 109 | break; 110 | } 111 | free(ev); 112 | } 113 | 114 | // add an event to the q and signal if necessary 115 | void event_post(union event_data *ev) { 116 | assert(ev != NULL); 117 | pthread_mutex_lock(&evq.lock); 118 | if (evq.size == 0) { 119 | // signal handler thread to wake up... 120 | pthread_cond_signal(&evq.nonempty); 121 | } 122 | evq_push(ev); 123 | // ...handler actually wakes up once we release the lock 124 | pthread_mutex_unlock(&evq.lock); 125 | } 126 | 127 | // main loop to read events! 128 | void event_loop(void) { 129 | union event_data *ev; 130 | while (!quit) { 131 | pthread_mutex_lock(&evq.lock); 132 | // while() because contention may produce spurious wakeup 133 | while (evq.size == 0) { 134 | //// FIXME: if we have an input device thread running, 135 | //// then we get segfaults here on SIGINT 136 | //// need to set an explicit sigint handler 137 | // atomically unlocks the mutex, sleeps on condvar, locks again on 138 | // wakeup 139 | pthread_cond_wait(&evq.nonempty, &evq.lock); 140 | } 141 | // fprintf(stderr, "evq.size : %d\n", (int) evq.size); 142 | assert(evq.size > 0); 143 | ev = evq_pop(); 144 | pthread_mutex_unlock(&evq.lock); 145 | if (ev != NULL) { 146 | handle_event(ev); 147 | } 148 | } 149 | } 150 | 151 | //------------------------------ 152 | //-- static function definitions 153 | 154 | static void handle_event(union event_data *ev) { 155 | switch (ev->type) { 156 | case EVENT_QUIT: 157 | quit = true; 158 | break; 159 | case EVENT_RESET: 160 | handle_reset(); 161 | break; 162 | case EVENT_SDL_CHECK: 163 | sdl_check(); 164 | break; 165 | case EVENT_EXEC_CODE_LINE: 166 | // printf("e: codeline: %s\n", ev->exec_code_line.line); 167 | lua_run(ev->exec_code_line.line); 168 | break; 169 | case EVENT_METRO: 170 | // printf("e: metro: %i %i\n",ev->metro.id, ev->metro.stage); 171 | handle_metro(ev->metro.id, ev->metro.stage); 172 | break; 173 | case EVENT_KEY: 174 | // printf("e: key: %i\n",ev->key.scancode); 175 | handle_sdl_key(ev->key.scancode); 176 | break; 177 | case EVENT_OSC: 178 | handle_osc(ev->osc.from_host, ev->osc.from_port, ev->osc.path, ev->osc.msg); 179 | break; 180 | case EVENT_GRID: 181 | handle_grid(ev->grid.id, ev->grid.x, ev->grid.y, ev->grid.state); 182 | break; 183 | case EVENT_GRID_ADD: 184 | handle_grid_add(ev->grid_add.id, ev->grid_add.serial, ev->grid_add.name); 185 | break; 186 | case EVENT_GRID_REMOVE: 187 | handle_grid_remove(ev->grid_remove.id); 188 | break; 189 | case EVENT_MIDI: 190 | handle_midi(ev->midi.data, ev->midi.nbytes); 191 | break; 192 | // case EVENT_MIDI_ADD: 193 | // handle_midi_add(ev->midi_add.id, ev->midi_add.name); 194 | // break; 195 | // case EVENT_MIDI_REMOVE: 196 | // handle_midi_remove(ev->midi_remove.id); 197 | case EVENT_CLOCK_RESUME: 198 | handle_clock_resume(ev->clock_resume.thread_id, ev->clock_resume.value); 199 | break; 200 | case EVENT_CLOCK_START: 201 | handle_clock_start(); 202 | break; 203 | case EVENT_CLOCK_STOP: 204 | handle_clock_stop(); 205 | break; 206 | } 207 | 208 | event_data_free(ev); 209 | } 210 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | // #include "device.h" 6 | 7 | extern bool quit; 8 | 9 | typedef enum { 10 | EVENT_FIRST_EVENT = 0, // unused (do not remove) 11 | EVENT_QUIT, // quit 12 | EVENT_RESET, // reload script 13 | EVENT_SDL_CHECK, // check for sdl events 14 | EVENT_EXEC_CODE_LINE, // code to be executed by luavm 15 | EVENT_METRO, // metro 16 | EVENT_KEY, // SDL keyboard input 17 | EVENT_OSC, // OSC received 18 | EVENT_GRID, // GRID key event 19 | EVENT_GRID_ADD, // GRID add 20 | EVENT_GRID_REMOVE, // GRID remove 21 | EVENT_MIDI, // MIDI event 22 | // EVENT_MIDI_ADD, 23 | // EVENT_MIDI_REMOVE, 24 | EVENT_CLOCK_START, 25 | EVENT_CLOCK_STOP, 26 | EVENT_CLOCK_RESUME, 27 | } event_t; 28 | 29 | extern void init_event(void); 30 | 31 | extern void event_loop(void); 32 | extern union event_data *event_data_new(event_t evcode); 33 | extern void event_data_free(union event_data *ev); 34 | extern void event_post(union event_data *ev); 35 | 36 | struct event_common { 37 | uint32_t type; 38 | }; 39 | 40 | struct event_exec_code_line { 41 | struct event_common common; 42 | char *line; 43 | }; 44 | 45 | struct event_metro { 46 | struct event_common common; 47 | uint32_t id; 48 | uint32_t stage; 49 | }; 50 | 51 | struct event_key { 52 | struct event_common common; 53 | uint16_t scancode; 54 | }; 55 | 56 | struct event_osc { 57 | struct event_common common; 58 | char *path; 59 | char *from_host; 60 | char *from_port; 61 | lo_message msg; 62 | }; 63 | 64 | struct event_grid { 65 | struct event_common common; 66 | uint8_t id; 67 | uint8_t x; 68 | uint8_t y; 69 | uint8_t state; 70 | }; 71 | 72 | struct event_grid_add { 73 | struct event_common common; 74 | uint8_t id; 75 | char *serial; 76 | char *name; 77 | }; 78 | 79 | struct event_grid_remove { 80 | struct event_common common; 81 | uint8_t id; 82 | }; 83 | 84 | struct event_midi { 85 | struct event_common common; 86 | // uint32_t id; 87 | uint8_t data[3]; 88 | size_t nbytes; 89 | }; 90 | 91 | // struct event_midi_add { 92 | // struct event_common common; 93 | // uint8_t id; 94 | // char *name; 95 | // }; 96 | 97 | // struct event_midi_remove { 98 | // struct event_common common; 99 | // uint8_t id; 100 | // char *name; 101 | // }; 102 | 103 | struct event_clock_resume { 104 | struct event_common common; 105 | uint32_t thread_id; 106 | double value; 107 | }; 108 | 109 | struct event_clock_start { 110 | struct event_common common; 111 | }; 112 | 113 | struct event_clock_stop { 114 | struct event_common common; 115 | }; 116 | 117 | union event_data { 118 | uint32_t type; 119 | struct event_exec_code_line exec_code_line; 120 | struct event_metro metro; 121 | struct event_key key; 122 | struct event_osc osc; 123 | struct event_midi midi; 124 | // struct event_midi_add midi_add; 125 | // struct event_midi_remove midi_remove; 126 | struct event_grid grid; 127 | struct event_grid_add grid_add; 128 | struct event_grid_remove grid_remove; 129 | struct event_clock_resume clock_resume; 130 | }; 131 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "event.h" 11 | #include "input.h" 12 | 13 | static pthread_t pid; 14 | 15 | #define RX_BUF_LEN 4096 16 | static void *input_run(void *p) { 17 | (void)p; 18 | bool quit = false; 19 | char rxbuf[RX_BUF_LEN]; 20 | int nb; 21 | bool newline; 22 | char b; 23 | 24 | while (!quit) { 25 | nb = 0; 26 | newline = false; 27 | while (!newline) { 28 | if (nb < RX_BUF_LEN) { 29 | if (read(STDIN_FILENO, &b, 1) < 1) { 30 | fprintf(stderr, "failed to read from stdin\n"); 31 | return NULL; 32 | } 33 | 34 | if (b == '\0') { 35 | continue; 36 | } 37 | if ((b == '\n') || (b == '\r')) { 38 | newline = true; 39 | } 40 | rxbuf[nb++] = b; 41 | } 42 | } 43 | if (nb == 2) { 44 | if (rxbuf[0] == 'q') { 45 | event_post(event_data_new(EVENT_QUIT)); 46 | quit = true; 47 | continue; 48 | } else if (rxbuf[0] == 'r') { 49 | fprintf(stderr, ">> script reload"); 50 | event_post(event_data_new(EVENT_RESET)); 51 | continue; 52 | } 53 | } 54 | if (nb > 0) { 55 | // event handler must free this chunk! 56 | char *line = malloc((nb + 1) * sizeof(char)); 57 | strncpy(line, rxbuf, nb); 58 | line[nb] = '\0'; 59 | union event_data *ev = event_data_new(EVENT_EXEC_CODE_LINE); 60 | ev->exec_code_line.line = line; 61 | event_post(ev); 62 | } 63 | } 64 | return NULL; 65 | } 66 | 67 | void init_input(void) { 68 | pthread_attr_t attr; 69 | int s; 70 | s = pthread_attr_init(&attr); 71 | if (s != 0) { 72 | fprintf(stderr, "input_init(): error in pthread_attr_init(): %d\n", s); 73 | } 74 | s = pthread_create(&pid, &attr, &input_run, NULL); 75 | 76 | #ifdef _GNU_SOURCE 77 | pthread_setname_np(pid, "input"); 78 | #endif 79 | 80 | if (s != 0) { 81 | fprintf(stderr, "input_init(): error in pthread_create(): %d\n", s); 82 | } 83 | pthread_attr_destroy(&attr); 84 | } 85 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern void init_input(void); 4 | -------------------------------------------------------------------------------- /src/interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "clock.h" 6 | #include "clocks/clock_crow.h" 7 | #include "clocks/clock_internal.h" 8 | #include "clocks/clock_scheduler.h" 9 | #include "event.h" 10 | #include "interface.h" 11 | #include "lua.h" 12 | #include "metro.h" 13 | #include "midi.h" 14 | #include "monome.h" 15 | #include "osc.h" 16 | #include "sdl.h" 17 | 18 | static inline void push_isms_func(const char *field, const char *func) { 19 | lua_getglobal(L, "isms"); 20 | lua_getfield(L, -1, field); 21 | lua_remove(L, -2); 22 | lua_getfield(L, -1, func); 23 | lua_remove(L, -2); 24 | } 25 | 26 | static inline void push_event_func(const char *field, const char *func) { 27 | lua_getglobal(L, field); 28 | lua_getfield(L, -1, "event"); 29 | lua_remove(L, -2); 30 | lua_getfield(L, -1, func); 31 | lua_remove(L, -2); 32 | } 33 | 34 | static int _nop(lua_State *l); 35 | static int _grid_redraw(lua_State *l); 36 | static int _grid_led(lua_State *l); 37 | static int _grid_all(lua_State *l); 38 | static int _metro_start(lua_State *l); 39 | static int _metro_stop(lua_State *l); 40 | static int _metro_clear(lua_State *l); 41 | static int _midi_send(lua_State *l); 42 | static int _osc_send(lua_State *l); 43 | static int _sdl_init(lua_State *l); 44 | static int _sdl_redraw(lua_State *l); 45 | static int _sdl_clear(lua_State *l); 46 | static int _sdl_pixel(lua_State *l); 47 | static int _sdl_line(lua_State *l); 48 | static int _clock_schedule_sleep(lua_State *l); 49 | static int _clock_schedule_sync(lua_State *l); 50 | static int _clock_cancel(lua_State *l); 51 | static int _clock_internal_set_tempo(lua_State *l); 52 | static int _clock_internal_start(lua_State *l); 53 | static int _clock_internal_stop(lua_State *l); 54 | static int _clock_crow_in_div(lua_State *l); 55 | static int _clock_set_source(lua_State *l); 56 | static int _clock_get_time_beats(lua_State *l); 57 | static int _clock_get_tempo(lua_State *l); 58 | 59 | void init_interface(void) { 60 | lua_newtable(L); 61 | 62 | // midi 63 | lua_reg_func("midi_send", _midi_send); 64 | 65 | // clock 66 | lua_reg_func("clock_schedule_sleep", &_clock_schedule_sleep); 67 | lua_reg_func("clock_schedule_sync", &_clock_schedule_sync); 68 | lua_reg_func("clock_cancel", &_clock_cancel); 69 | lua_reg_func("clock_internal_set_tempo", &_clock_internal_set_tempo); 70 | lua_reg_func("clock_internal_start", &_clock_internal_start); 71 | lua_reg_func("clock_internal_stop", &_clock_internal_stop); 72 | lua_reg_func("clock_crow_in_div", &_clock_crow_in_div); 73 | lua_reg_func("clock_set_source", &_clock_set_source); 74 | lua_reg_func("clock_get_time_beats", &_clock_get_time_beats); 75 | lua_reg_func("clock_get_tempo", &_clock_get_tempo); 76 | lua_setglobal(L, "isms"); 77 | 78 | // grid 79 | lua_newtable(L); 80 | lua_reg_func("redraw", &_grid_redraw); 81 | lua_reg_func("led", &_grid_led); 82 | lua_reg_func("all", &_grid_all); 83 | // lua_reg_func("intensity", &_grid_rotation); 84 | // lua_reg_func("rotation", &_grid_intensity); 85 | lua_newtable(L); // event 86 | lua_reg_func("key", &_nop); 87 | lua_reg_func("add", &_nop); 88 | lua_reg_func("remove", &_nop); 89 | lua_setfield(L, -2, "event"); 90 | lua_setglobal(L, "grid"); 91 | 92 | // metro 93 | lua_newtable(L); 94 | lua_reg_func("start", _metro_start); 95 | lua_reg_func("stop", _metro_stop); 96 | lua_reg_func("clear", _metro_clear); 97 | lua_newtable(L); // event 98 | lua_reg_func("tick", &_nop); 99 | lua_setfield(L, -2, "event"); 100 | lua_setglobal(L, "metro"); 101 | 102 | // osc 103 | lua_newtable(L); 104 | lua_reg_func("send", _osc_send); 105 | lua_newtable(L); // event 106 | lua_reg_func("receive", &_nop); 107 | lua_setfield(L, -2, "event"); 108 | lua_setglobal(L, "osc"); 109 | 110 | // sdl 111 | lua_newtable(L); 112 | lua_reg_func("init", _sdl_init); 113 | lua_reg_func("redraw", _sdl_redraw); 114 | lua_reg_func("clear", _sdl_clear); 115 | lua_reg_func("pixel", _sdl_pixel); 116 | lua_reg_func("line", _sdl_line); 117 | lua_newtable(L); // event 118 | lua_reg_func("nop", &_nop); 119 | lua_setfield(L, -2, "event"); 120 | lua_setglobal(L, "window"); 121 | 122 | printf("lib\t\t~/.local/share/isms/\n"); 123 | char *home = getenv("HOME"); 124 | char cmd[128]; 125 | snprintf(cmd, 128, "dofile('%s/.local/share/isms/system/isms.lua')\n", home); 126 | l_dostring(L, cmd, "init"); 127 | } 128 | 129 | static int _nop(lua_State *l) { 130 | (void)l; 131 | return 0; 132 | } 133 | 134 | //////// grid 135 | 136 | static int _grid_redraw(lua_State *l) { 137 | lua_check_num_args(1); 138 | int i = (int)luaL_checkinteger(l, 1); 139 | monome_redraw(i); 140 | lua_settop(l, 0); 141 | return 0; 142 | } 143 | 144 | static int _grid_led(lua_State *l) { 145 | lua_check_num_args(4); 146 | int i = (int)luaL_checkinteger(l, 1); 147 | int x = (int)luaL_checkinteger(l, 2); 148 | int y = (int)luaL_checkinteger(l, 3); 149 | int z = (int)luaL_checkinteger(l, 4); 150 | monome_led(i, x, y, z); 151 | lua_settop(l, 0); 152 | return 0; 153 | } 154 | 155 | static int _grid_all(lua_State *l) { 156 | lua_check_num_args(2); 157 | int i = (int)luaL_checkinteger(l, 1); 158 | int z = (int)luaL_checkinteger(l, 2); 159 | monome_all(i, z); 160 | lua_settop(l, 0); 161 | return 0; 162 | } 163 | 164 | //////// metro 165 | 166 | static int _metro_start(lua_State *l) { 167 | // printf("metro start\n"); 168 | lua_check_num_args(4); 169 | double idx = luaL_checknumber(l, 1) - 1; // convert to 1-based 170 | double seconds = luaL_checknumber(l, 2); 171 | double count = luaL_checknumber(l, 3); 172 | double stage = luaL_checknumber(l, 4); 173 | metro_start(idx, seconds, count, stage); 174 | lua_settop(l, 0); 175 | return 0; 176 | } 177 | 178 | static int _metro_stop(lua_State *l) { 179 | // printf("metro stop\n"); 180 | lua_check_num_args(1); 181 | double idx = luaL_checknumber(l, 1); 182 | metro_stop(idx); 183 | lua_settop(l, 0); 184 | return 0; 185 | } 186 | 187 | static int _metro_clear(lua_State *l) { 188 | // printf("metro stop\n"); 189 | lua_check_num_args(0); 190 | metro_clear(); 191 | lua_settop(l, 0); 192 | return 0; 193 | } 194 | 195 | //////// midi 196 | 197 | int _midi_send(lua_State *l) { 198 | size_t nbytes; 199 | uint8_t *data; 200 | 201 | lua_check_num_args(1); 202 | luaL_checktype(l, 1, LUA_TTABLE); 203 | 204 | nbytes = lua_rawlen(l, 1); 205 | data = malloc(nbytes); 206 | 207 | for (unsigned int i = 1; i <= nbytes; i++) { 208 | lua_pushinteger(l, i); 209 | lua_gettable(l, 1); 210 | 211 | // TODO: lua_isnumber 212 | data[i - 1] = lua_tointeger(l, -1); 213 | lua_pop(l, 1); 214 | } 215 | 216 | midi_send(data, nbytes); 217 | 218 | free(data); 219 | 220 | return 0; 221 | } 222 | 223 | //////// osc 224 | 225 | static int _osc_send(lua_State *l) { 226 | const char *host = NULL; 227 | const char *port = NULL; 228 | const char *path = NULL; 229 | lo_message msg; 230 | 231 | int nargs = lua_gettop(l); 232 | 233 | // address 234 | luaL_checktype(l, 1, LUA_TTABLE); 235 | 236 | if (lua_rawlen(l, 1) != 2) { 237 | luaL_argerror(l, 1, "address should be a table in the form {host, port}"); 238 | } 239 | 240 | lua_pushnumber(l, 1); 241 | lua_gettable(l, 1); 242 | if (lua_isstring(l, -1)) { 243 | host = lua_tostring(l, -1); 244 | } else { 245 | luaL_argerror(l, 1, "address should be a table in the form {host, port}"); 246 | } 247 | lua_pop(l, 1); 248 | 249 | lua_pushnumber(l, 2); 250 | lua_gettable(l, 1); 251 | if (lua_isstring(l, -1)) { 252 | port = lua_tostring(l, -1); 253 | } else { 254 | luaL_argerror(l, 1, "address should be a table in the form {host, port}"); 255 | } 256 | lua_pop(l, 1); 257 | 258 | // path 259 | luaL_checktype(l, 2, LUA_TSTRING); 260 | path = lua_tostring(l, 2); 261 | 262 | if ((host == NULL) || (port == NULL) || (path == NULL)) { 263 | return 1; 264 | } 265 | 266 | msg = lo_message_new(); 267 | 268 | // add args (optional) 269 | if (nargs > 2) { 270 | luaL_checktype(l, 3, LUA_TTABLE); 271 | for (size_t i = 1; i <= lua_rawlen(l, 3); i++) { 272 | lua_pushnumber(l, i); 273 | lua_gettable(l, 3); 274 | int argtype = lua_type(l, -1); 275 | 276 | switch (argtype) { 277 | case LUA_TNIL: 278 | lo_message_add_nil(msg); 279 | break; 280 | case LUA_TNUMBER: 281 | lo_message_add_float(msg, lua_tonumber(l, -1)); 282 | break; 283 | case LUA_TBOOLEAN: 284 | if (lua_toboolean(l, -1)) { 285 | lo_message_add_true(msg); 286 | } else { 287 | lo_message_add_false(msg); 288 | } 289 | break; 290 | case LUA_TSTRING: 291 | lo_message_add_string(msg, lua_tostring(l, -1)); 292 | break; 293 | default: 294 | lo_message_free(msg); 295 | luaL_error(l, "invalid osc argument type %s", lua_typename(l, argtype)); 296 | break; 297 | } /* switch */ 298 | 299 | lua_pop(l, 1); 300 | } 301 | } 302 | lo_address address = lo_address_new(host, port); 303 | if (!address) { 304 | printf(">> OSC: failed to create lo_address\n"); 305 | return 1; 306 | } 307 | lo_send_message(address, path, msg); 308 | lo_address_free(address); 309 | lo_message_free(msg); 310 | 311 | lua_settop(l, 0); 312 | return 0; 313 | } 314 | 315 | //////// sdl 316 | 317 | int _sdl_init(lua_State *l) { 318 | lua_check_num_args(2); 319 | double x = luaL_checknumber(l, 1); 320 | double y = luaL_checknumber(l, 2); 321 | init_sdl(x, y); 322 | lua_settop(l, 0); 323 | return 0; 324 | } 325 | 326 | int _sdl_redraw(lua_State *l) { 327 | lua_check_num_args(0); 328 | sdl_redraw(pixels); 329 | lua_settop(l, 0); 330 | return 0; 331 | } 332 | 333 | int _sdl_clear(lua_State *l) { 334 | lua_check_num_args(0); 335 | sdl_clear(surface->pixels); 336 | lua_settop(l, 0); 337 | return 0; 338 | } 339 | 340 | int _sdl_pixel(lua_State *l) { 341 | lua_check_num_args(3); 342 | double x = luaL_checknumber(l, 1); 343 | double y = luaL_checknumber(l, 2); 344 | double c = luaL_checknumber(l, 3); 345 | sdl_pixel(surface->pixels, x, y, c); 346 | lua_settop(l, 0); 347 | return 0; 348 | } 349 | 350 | int _sdl_line(lua_State *l) { 351 | lua_check_num_args(5); 352 | double x1 = luaL_checknumber(l, 1); 353 | double y1 = luaL_checknumber(l, 2); 354 | double x2 = luaL_checknumber(l, 3); 355 | double y2 = luaL_checknumber(l, 4); 356 | double c = luaL_checknumber(l, 5); 357 | sdl_line(surface->pixels, x1, y1, x2, y2, c); 358 | lua_settop(l, 0); 359 | return 0; 360 | } 361 | 362 | int _clock_schedule_sleep(lua_State *l) { 363 | lua_check_num_args(2); 364 | int coro_id = (int)luaL_checkinteger(l, 1); 365 | double seconds = luaL_checknumber(l, 2); 366 | 367 | if (seconds < 0) { 368 | seconds = 0; 369 | } 370 | 371 | clock_scheduler_schedule_sleep(coro_id, seconds); 372 | 373 | return 0; 374 | } 375 | 376 | int _clock_schedule_sync(lua_State *l) { 377 | int coro_id = (int)luaL_checkinteger(l, 1); 378 | double sync_beat = luaL_checknumber(l, 2); 379 | double offset = luaL_optnumber(l, 3, 0); 380 | 381 | if (sync_beat <= 0) { 382 | luaL_error(l, "invalid sync beat: %f", sync_beat); 383 | } else { 384 | clock_scheduler_schedule_sync(coro_id, sync_beat, offset); 385 | } 386 | 387 | return 0; 388 | } 389 | 390 | int _clock_cancel(lua_State *l) { 391 | lua_check_num_args(1); 392 | int coro_id = (int)luaL_checkinteger(l, 1); 393 | clock_scheduler_clear(coro_id); 394 | return 0; 395 | } 396 | 397 | int _clock_internal_set_tempo(lua_State *l) { 398 | lua_check_num_args(1); 399 | double bpm = luaL_checknumber(l, 1); 400 | clock_internal_set_tempo(bpm); 401 | return 0; 402 | } 403 | 404 | int _clock_internal_start(lua_State *l) { 405 | clock_internal_restart(); 406 | return 0; 407 | } 408 | 409 | int _clock_internal_stop(lua_State *l) { 410 | clock_internal_stop(); 411 | return 0; 412 | } 413 | 414 | int _clock_crow_in_div(lua_State *l) { 415 | lua_check_num_args(1); 416 | int div = (int)luaL_checkinteger(l, 1); 417 | clock_crow_in_div(div); 418 | return 0; 419 | } 420 | 421 | int _clock_set_source(lua_State *l) { 422 | lua_check_num_args(1); 423 | int source = (int)luaL_checkinteger(l, 1); 424 | clock_set_source(source); 425 | return 0; 426 | } 427 | 428 | int _clock_get_time_beats(lua_State *l) { 429 | lua_pushnumber(l, clock_get_beats()); 430 | return 1; 431 | } 432 | 433 | int _clock_get_tempo(lua_State *l) { 434 | lua_pushnumber(l, clock_get_tempo()); 435 | return 1; 436 | } 437 | 438 | //////////////////////////////////////////////////////////////// 439 | // handlers 440 | 441 | //////// isms 442 | 443 | void handle_reset() { lua_run("isms.run()"); } 444 | 445 | //////// grid 446 | 447 | void handle_grid(uint8_t i, uint8_t x, uint8_t y, uint8_t state) { 448 | push_event_func("grid", "key"); 449 | lua_pushinteger(L, i); 450 | lua_pushinteger(L, x); 451 | lua_pushinteger(L, y); 452 | lua_pushinteger(L, state); 453 | l_report(L, l_docall(L, 4, 0)); 454 | } 455 | 456 | void handle_grid_add(uint8_t id, char *serial, char *name) { 457 | push_event_func("grid", "add"); 458 | lua_pushinteger(L, id); 459 | lua_pushstring(L, serial); 460 | lua_pushstring(L, name); 461 | l_report(L, l_docall(L, 3, 0)); 462 | } 463 | 464 | void handle_grid_remove(uint8_t id) { 465 | push_event_func("grid", "remove"); 466 | lua_pushinteger(L, id); 467 | l_report(L, l_docall(L, 1, 0)); 468 | } 469 | 470 | //////// metro 471 | 472 | void handle_metro(int idx, int stage) { 473 | // printf("e: metro: %i %i\n",idx, stage); 474 | push_event_func("metro", "tick"); 475 | lua_pushinteger(L, idx + 1); // convert to 1-based 476 | lua_pushinteger(L, stage + 1); // convert to 1-based 477 | l_report(L, l_docall(L, 2, 0)); 478 | // if (!(lua_pcall(L, 2, 0, 0) == LUA_OK)) { 479 | // printf("Error on run method\n"); 480 | // } 481 | } 482 | 483 | //////// midi 484 | 485 | // void handle_midi_add(uint8_t id, char *name) { 486 | // push_event_func("midi", "add"); 487 | // lua_pushinteger(L, id); 488 | // lua_pushstring(L, name); 489 | // l_report(L, l_docall(L, 2, 0)); 490 | // } 491 | 492 | // void handle_midi_remove(uint8_t id) { 493 | // push_event_func("midi", "remove"); 494 | // lua_pushinteger(L, id); 495 | // l_report(L, l_docall(L, 1, 0)); 496 | // } 497 | 498 | void handle_midi(uint8_t *data, size_t nbytes) { 499 | lua_getglobal(L, "midi"); 500 | lua_getfield(L, -1, "_event"); 501 | lua_remove(L, -2); 502 | 503 | lua_createtable(L, nbytes, 0); 504 | 505 | for (size_t i = 0; i < nbytes; i++) { 506 | lua_pushinteger(L, data[i]); 507 | lua_rawseti(L, -2, i + 1); 508 | } 509 | 510 | l_report(L, l_docall(L, 1, 0)); 511 | } 512 | 513 | //////// osc 514 | 515 | void handle_osc(char *from_host, char *from_port, char *path, lo_message msg) { 516 | const char *types = NULL; 517 | int argc; 518 | lo_arg **argv = NULL; 519 | 520 | types = lo_message_get_types(msg); 521 | argc = lo_message_get_argc(msg); 522 | argv = lo_message_get_argv(msg); 523 | 524 | push_event_func("window","key"); 525 | //lua_getglobal(L, "osc"); 526 | //lua_getfield(L, -1, "receive"); 527 | //lua_remove(L, -2); 528 | //_push_isms_func("osc", "receive"); 529 | 530 | lua_pushstring(L, path); 531 | 532 | lua_createtable(L, argc, 0); 533 | for (int i = 0; i < argc; i++) { 534 | switch (types[i]) { 535 | case LO_INT32: 536 | lua_pushinteger(L, argv[i]->i); 537 | break; 538 | case LO_FLOAT: 539 | lua_pushnumber(L, argv[i]->f); 540 | break; 541 | case LO_STRING: 542 | lua_pushstring(L, &argv[i]->s); 543 | break; 544 | case LO_BLOB: 545 | lua_pushlstring(L, lo_blob_dataptr((lo_blob)argv[i]), 546 | lo_blob_datasize((lo_blob)argv[i])); 547 | break; 548 | case LO_INT64: 549 | lua_pushinteger(L, argv[i]->h); 550 | break; 551 | case LO_DOUBLE: 552 | lua_pushnumber(L, argv[i]->d); 553 | break; 554 | case LO_SYMBOL: 555 | lua_pushstring(L, &argv[i]->S); 556 | break; 557 | case LO_CHAR: 558 | lua_pushlstring(L, (const char *)&argv[i]->c, 1); 559 | break; 560 | case LO_MIDI: 561 | lua_pushlstring(L, (const char *)&argv[i]->m, 4); 562 | break; 563 | case LO_TRUE: 564 | lua_pushboolean(L, 1); 565 | break; 566 | case LO_FALSE: 567 | lua_pushboolean(L, 0); 568 | break; 569 | case LO_NIL: 570 | lua_pushnil(L); 571 | break; 572 | case LO_INFINITUM: 573 | // FIXME: build error despite -std=c11 574 | // lua_pushnumber(L, INFINITY); 575 | break; 576 | default: 577 | fprintf(stderr, "unknown osc typetag: %c\n", types[i]); 578 | lua_pushnil(L); 579 | break; 580 | } /* switch */ 581 | lua_rawseti(L, -2, i + 1); 582 | } 583 | 584 | lua_createtable(L, 2, 0); 585 | lua_pushstring(L, from_host); 586 | lua_rawseti(L, -2, 1); 587 | lua_pushstring(L, from_port); 588 | lua_rawseti(L, -2, 2); 589 | 590 | l_report(L, l_docall(L, 3, 0)); 591 | } 592 | 593 | //////// sdl 594 | 595 | void handle_sdl_key(int code) { 596 | // push_isms_func("window", "key"); 597 | // lua_pushinteger(L, code); 598 | // l_report(L, l_docall(L, 1, 0)); 599 | 600 | push_event_func("window","key"); 601 | //lua_getglobal(L, "window"); 602 | //lua_getfield(L, -1, "event"); 603 | //lua_getfield(L, -2, "key"); 604 | //lua_remove(L, -3); 605 | lua_pushinteger(L, code); 606 | l_report(L, l_docall(L, 1, 0)); 607 | } 608 | 609 | //////// clock 610 | 611 | void handle_clock_resume(const int coro_id, double value) { 612 | lua_getglobal(L, "clock"); 613 | lua_getfield(L, -1, "resume"); 614 | lua_remove(L, -2); 615 | lua_pushinteger(L, coro_id); 616 | lua_pushnumber(L, value); 617 | l_report(L, l_docall(L, 2, 0)); 618 | } 619 | 620 | void handle_clock_start() { 621 | push_isms_func("clock", "start"); 622 | l_report(L, l_docall(L, 0, 0)); 623 | } 624 | 625 | void handle_clock_stop() { 626 | push_isms_func("clock", "stop"); 627 | l_report(L, l_docall(L, 0, 0)); 628 | } 629 | -------------------------------------------------------------------------------- /src/interface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern void init_interface(void); 6 | 7 | extern void handle_reset(void); 8 | extern void handle_grid_add(uint8_t id, char *serial, char *name); 9 | extern void handle_grid_remove(uint8_t id); 10 | extern void handle_grid(uint8_t i, uint8_t x, uint8_t y, uint8_t state); 11 | extern void handle_metro(int idx, int stage); 12 | // extern void handle_midi_add(uint8_t id, char* name); 13 | // extern void handle_midi_remove(uint8_t id); 14 | extern void handle_midi(uint8_t *data, size_t nbytes); 15 | extern void handle_osc(char *from_host, char *from_port, char *path, 16 | lo_message msg); 17 | extern void handle_sdl_key(int code); 18 | extern void handle_clock_resume(const int thread_id, double value); 19 | extern void handle_clock_start(); 20 | extern void handle_clock_stop(); 21 | -------------------------------------------------------------------------------- /src/lua.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "interface.h" 11 | #include "lua.h" 12 | 13 | lua_State *L; 14 | 15 | void init_lua() { 16 | // printf(">> LUA: init\n"); 17 | L = luaL_newstate(); 18 | luaL_openlibs(L); 19 | } 20 | 21 | void deinit_lua() { 22 | // printf(">> LUA: deinit\n"); 23 | lua_close(L); 24 | } 25 | 26 | void lua_run(char *line) { l_report(L, luaL_dostring(L, line)); } 27 | 28 | ////////////////////////////////////////////////////////////////////////////// 29 | 30 | static lua_State *globalL = NULL; 31 | 32 | /* 33 | ** Hook set by signal function to stop the interpreter. 34 | */ 35 | static void lstop(lua_State *L, lua_Debug *ar) { 36 | (void)ar; /* unused arg. */ 37 | lua_sethook(L, NULL, 0, 0); /* reset hook */ 38 | luaL_error(L, "interrupted!"); 39 | } 40 | 41 | /* 42 | ** Function to be called at a C signal. Because a C signal cannot 43 | ** just change a Lua state (as there is no proper synchronization), 44 | ** this function only sets a hook that, when called, will stop the 45 | ** interpreter. 46 | */ 47 | static void laction(int i) { 48 | signal(i, SIG_DFL); /* if another SIGINT happens, terminate process */ 49 | lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); 50 | } 51 | 52 | /* 53 | ** Message handler used to run all chunks 54 | */ 55 | static int msghandler(lua_State *L) { 56 | const char *msg = lua_tostring(L, 1); 57 | if (msg == NULL) { /* is error object not a 58 | * string? 59 | * */ 60 | if (luaL_callmeta(L, 1, "__tostring") && /* does it have a 61 | * metamethod 62 | **/ 63 | (lua_type(L, -1) == LUA_TSTRING)) { /* that produces a string? 64 | **/ 65 | return 1; /* that is the message */ 66 | } else { 67 | msg = lua_pushfstring(L, "(error object is a %s value)", 68 | luaL_typename(L, 1)); 69 | } 70 | } 71 | luaL_traceback(L, L, msg, 1); /* append a standard traceback */ 72 | return 1; /* return the traceback */ 73 | } 74 | 75 | /* 76 | ** Check whether 'status' is not OK and, if so, prints the error 77 | ** message on the top of the stack. It assumes that the error object 78 | ** is a string, as it was either generated by Lua or by 'msghandler'. 79 | */ 80 | static int report(lua_State *L, int status) { 81 | if (status != LUA_OK) { 82 | const char *msg = lua_tostring(L, -1); 83 | lua_writestringerror("%s\n", msg); 84 | lua_pop(L, 1); /* remove message */ 85 | } 86 | return status; 87 | } 88 | 89 | int l_report(lua_State *L, int status) { 90 | report(L, status); 91 | return 0; 92 | } 93 | 94 | static int docall(lua_State *L, int narg, int nres) { 95 | int status; 96 | int base = lua_gettop(L) - narg; /* function index */ 97 | lua_pushcfunction(L, msghandler); /* push message handler */ 98 | lua_insert(L, base); /* put it under function and args */ 99 | globalL = L; /* to be available to 'laction' */ 100 | signal(SIGINT, laction); /* set C-signal handler */ 101 | status = lua_pcall(L, narg, nres, base); 102 | signal(SIGINT, SIG_DFL); /* reset C-signal handler */ 103 | lua_remove(L, base); /* remove message handler from the stack **/ 104 | return status; 105 | } 106 | 107 | int l_docall(lua_State *L, int narg, int nres) { 108 | int stat = docall(L, narg, nres); 109 | // FIXME: error handling 110 | return stat; 111 | } 112 | 113 | static int dochunk(lua_State *L, int status) { 114 | if (status == LUA_OK) { 115 | status = docall(L, 0, 0); 116 | } 117 | return report(L, status); 118 | } 119 | 120 | int l_dofile(lua_State *L, const char *name) { 121 | return dochunk(L, luaL_loadfile(L, name)); 122 | } 123 | 124 | int l_dostring(lua_State *L, const char *s, const char *name) { 125 | return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name)); 126 | } 127 | -------------------------------------------------------------------------------- /src/lua.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define lua_reg_func(n, f) (lua_pushcfunction(L, f), lua_setfield(L, -2, n)) 8 | #define STRING_NUM(n) #n 9 | #define LUA_ARG_ERROR(n) "error: requires " STRING_NUM(n) " arguments" 10 | #define lua_check_num_args(n) \ 11 | if (lua_gettop(l) != n) { \ 12 | return luaL_error(l, LUA_ARG_ERROR(n)); \ 13 | } 14 | 15 | extern lua_State *L; 16 | 17 | void init_lua(); 18 | void deinit_lua(); 19 | 20 | void lua_run(char *); 21 | 22 | int l_report(lua_State *L, int status); 23 | int l_docall(lua_State *L, int narg, int nres); 24 | int l_dostring(lua_State *L, const char *s, const char *name); 25 | int l_dofile(lua_State *L, const char *name); 26 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "lo/lo.h" 7 | 8 | #include "clock.h" 9 | #include "clocks/clock_internal.h" 10 | #include "clocks/clock_midi.h" 11 | #include "clocks/clock_scheduler.h" 12 | #include "event.h" 13 | #include "input.h" 14 | #include "interface.h" 15 | #include "lua.h" 16 | #include "metro.h" 17 | #include "midi.h" 18 | #include "monome.h" 19 | #include "osc.h" 20 | #include "sdl.h" 21 | #include "socket.h" 22 | 23 | #define DEFAULT_OSC_PORT 10011 24 | #define DEFAULT_SOCKET_PORT 11001 25 | 26 | int main(int argc, char **argv) { 27 | printf("isms ////////\n"); 28 | 29 | init_event(); 30 | init_input(); 31 | 32 | clock_init(); 33 | clock_internal_init(); 34 | clock_midi_init(); 35 | clock_scheduler_init(); 36 | 37 | init_midi(); 38 | init_osc(DEFAULT_OSC_PORT); 39 | init_socket(DEFAULT_SOCKET_PORT); 40 | init_monome(); 41 | init_metro(); 42 | init_lua(); 43 | init_interface(); 44 | 45 | if (argc > 1) { 46 | char cmd[64]; 47 | snprintf(cmd, 64, "isms.run('%s')\n", argv[1]); 48 | printf(">>>> %s\n", cmd); 49 | lua_run(cmd); 50 | } 51 | 52 | printf(">> starting event loop\n"); 53 | event_loop(); 54 | 55 | deinit_osc(); 56 | deinit_monome(); 57 | clock_deinit(); 58 | deinit_metro(); 59 | deinit_lua(); 60 | deinit_sdl(); 61 | 62 | printf(">> farewell.\n"); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /src/metro.c: -------------------------------------------------------------------------------- 1 | /* 2 | * metro.c 3 | * 4 | * accurate metros using pthreads and clock_nanosleep. 5 | */ 6 | 7 | // std 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // posix / linux 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "event.h" 22 | #include "metro.h" 23 | #include "platform_clock.h" 24 | 25 | #define MAX_NUM_METROS_OK 32 26 | 27 | enum { METRO_STATUS_RUNNING, METRO_STATUS_STOPPED }; 28 | 29 | const int MAX_NUM_METROS = MAX_NUM_METROS_OK; 30 | struct metro { 31 | int idx; // metro index 32 | int status; // running/stopped status 33 | double seconds; // period in seconds 34 | uint64_t count; // total iterations ( <=0 -> infinite ) 35 | uint64_t stage; // current count of iterations 36 | uint64_t time; // current time (in nsec) 37 | uint64_t delta; // current delta (in nsec) 38 | pthread_t tid; // thread id 39 | pthread_mutex_t stage_lock; // mutex protecting stage number 40 | pthread_mutex_t status_lock; // mutex protecting status 41 | }; 42 | 43 | struct metro metros[MAX_NUM_METROS_OK]; 44 | 45 | //--------------------------- 46 | //---- static declarations 47 | 48 | static void metro_handle_error(int code, const char *msg) { 49 | fprintf(stderr, "error code: %d (%s) in \"%s\"\n", code, strerror(code), msg); 50 | } 51 | 52 | static void metro_init(struct metro *t, uint64_t nsec, int count); 53 | static void metro_set_current_time(struct metro *t); 54 | static void *metro_thread_loop(void *metro); 55 | static void metro_bang(struct metro *t); 56 | static void metro_sleep(struct metro *t); 57 | static void metro_reset(struct metro *t, int stage); 58 | static void metro_cancel(struct metro *t); 59 | 60 | //------------------------ 61 | //---- extern definitions 62 | 63 | void init_metro(void) { 64 | for (int i = 0; i < MAX_NUM_METROS_OK; i++) { 65 | metros[i].status = METRO_STATUS_STOPPED; 66 | metros[i].seconds = 1.0; 67 | } 68 | } 69 | 70 | void deinit_metro() { 71 | for (int i = 0; i < MAX_NUM_METROS_OK; i++) 72 | metro_stop(i); 73 | } 74 | 75 | void metro_clear() { deinit_metro(); } 76 | 77 | void metro_start(int idx, double seconds, int count, int stage) { 78 | uint64_t nsec; 79 | 80 | if ((idx >= 0) && (idx < MAX_NUM_METROS_OK)) { 81 | struct metro *t = &metros[idx]; 82 | pthread_mutex_lock(&t->status_lock); 83 | if (t->status == METRO_STATUS_RUNNING) { 84 | metro_cancel(t); 85 | } 86 | pthread_mutex_unlock(&t->status_lock); 87 | if (seconds > 0.0) { 88 | metros[idx].seconds = seconds; 89 | } 90 | nsec = (uint64_t)(metros[idx].seconds * 1000000000.0); 91 | metros[idx].idx = idx; 92 | metro_reset(&metros[idx], stage); 93 | metro_init(&metros[idx], nsec, count); 94 | } else { 95 | fprintf(stderr, 96 | "invalid metro index, not added. max count of metros is %d\n", 97 | MAX_NUM_METROS_OK); 98 | } 99 | } 100 | 101 | void metro_stop(int idx) { 102 | if ((idx >= 0) && (idx < MAX_NUM_METROS_OK)) { 103 | pthread_mutex_lock(&(metros[idx].status_lock)); 104 | if (metros[idx].status == METRO_STATUS_STOPPED) { 105 | // fprintf(stderr, "metro_stop: already stopped\n"); 106 | ; 107 | ; // nothing to do 108 | } else { 109 | metro_cancel(&metros[idx]); 110 | } 111 | pthread_mutex_unlock(&(metros[idx].status_lock)); 112 | } else { 113 | fprintf(stderr, 114 | "metro_stop(): invalid metro index, max count of metros is %d\n", 115 | MAX_NUM_METROS_OK); 116 | } 117 | } 118 | 119 | void metro_set_time(int idx, float sec) { 120 | // fprintf(stderr, "metro_set_time(%d, %f)\n", idx, sec); 121 | if ((idx >= 0) && (idx < MAX_NUM_METROS_OK)) { 122 | metros[idx].seconds = sec; 123 | metros[idx].delta = (uint64_t)(sec * 1000000000.0); 124 | } 125 | } 126 | 127 | //------------------------ 128 | //---- static definitions 129 | 130 | static void metro_reset(struct metro *t, int stage) { 131 | pthread_mutex_lock(&(t->stage_lock)); 132 | if (stage > 0) { 133 | t->stage = stage; 134 | } else { 135 | t->stage = 0; 136 | } 137 | pthread_mutex_unlock(&(t->stage_lock)); 138 | } 139 | 140 | void metro_init(struct metro *t, uint64_t nsec, int count) { 141 | int res; 142 | pthread_attr_t attr; 143 | 144 | res = pthread_attr_init(&attr); 145 | if (res != 0) { 146 | metro_handle_error(res, "pthread_attr_init"); 147 | return; 148 | } 149 | 150 | // set other thread attributes here... 151 | res = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); 152 | if (res != 0) { 153 | metro_handle_error(res, "pthread_attr_init"); 154 | return; 155 | } 156 | res |= pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 157 | if (res != 0) { 158 | metro_handle_error(res, "pthread_attr_init"); 159 | return; 160 | } 161 | 162 | t->delta = nsec; 163 | t->count = count; 164 | res = pthread_create(&(t->tid), &attr, &metro_thread_loop, (void *)t); 165 | if (res != 0) { 166 | metro_handle_error(res, "pthread_create"); 167 | return; 168 | } else { 169 | #ifdef _GNU_SOURCE 170 | pthread_setname_np(t->tid, "metro_handler)"); 171 | #endif 172 | t->status = METRO_STATUS_RUNNING; 173 | if (res != 0) { 174 | metro_handle_error(res, "pthread_setschedparam"); 175 | switch (res) { 176 | case ESRCH: 177 | fprintf(stderr, "specified thread does not exist\n"); 178 | assert(false); 179 | break; 180 | case EINVAL: 181 | fprintf(stderr, 182 | "invalid thread policy value or associated parameter\n"); 183 | assert(false); 184 | break; 185 | case EPERM: 186 | fprintf(stderr, "failed to set scheduling priority.\n"); 187 | // this doesn't need to assert; it can happen with wrong 188 | // permissions 189 | // still good for user to know about 190 | break; 191 | default: 192 | fprintf(stderr, "unknown error code\n"); 193 | assert(false); 194 | } /* switch */ 195 | return; 196 | } 197 | } 198 | } 199 | 200 | void *metro_thread_loop(void *metro) { 201 | struct metro *t = (struct metro *)metro; 202 | int stop = 0; 203 | 204 | pthread_mutex_lock(&(t->status_lock)); 205 | t->status = METRO_STATUS_RUNNING; 206 | pthread_mutex_unlock(&(t->status_lock)); 207 | 208 | metro_set_current_time(t); 209 | while (!stop) { 210 | metro_sleep(t); 211 | pthread_mutex_lock(&(t->stage_lock)); 212 | if ((t->stage >= t->count) && (t->count > 0)) { 213 | stop = 1; 214 | } 215 | pthread_mutex_unlock(&(t->stage_lock)); 216 | 217 | if (stop) { 218 | break; 219 | } 220 | pthread_testcancel(); 221 | 222 | pthread_mutex_lock(&(t->stage_lock)); 223 | metro_bang(t); 224 | t->stage += 1; 225 | pthread_mutex_unlock(&(t->stage_lock)); 226 | } 227 | pthread_mutex_lock(&(t->status_lock)); 228 | t->status = METRO_STATUS_STOPPED; 229 | pthread_mutex_unlock(&(t->status_lock)); 230 | return NULL; 231 | } 232 | 233 | void metro_set_current_time(struct metro *t) { 234 | struct timespec time; 235 | clock_gettime(CLOCK_MONOTONIC, &time); 236 | t->time = 237 | (uint64_t)((1000000000 * (int64_t)time.tv_sec) + (int64_t)time.tv_nsec); 238 | } 239 | 240 | void metro_bang(struct metro *t) { 241 | union event_data *ev = event_data_new(EVENT_METRO); 242 | ev->metro.id = t->idx; 243 | ev->metro.stage = t->stage; 244 | event_post(ev); 245 | } 246 | 247 | void metro_sleep(struct metro *t) { 248 | struct timespec ts; 249 | t->time += t->delta; 250 | ts.tv_sec = t->time / 1000000000; 251 | ts.tv_nsec = t->time % 1000000000; 252 | platform_clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL); 253 | } 254 | 255 | void metro_wait(int idx) { pthread_join(metros[idx].tid, NULL); } 256 | 257 | void metro_cancel(struct metro *t) { 258 | // NB: no, we don't want to lock the state mutex here, 259 | // b/c we're already locking in callers 260 | if (t->status == METRO_STATUS_STOPPED) { 261 | fprintf(stderr, "metro_cancel(): already stopped. shouldn't get here\n"); 262 | return; 263 | } 264 | int ret = pthread_cancel(t->tid); 265 | if (ret) { 266 | fprintf(stderr, "metro_stop(): pthread_cancel() failed; error: "); 267 | switch (ret) { 268 | case ESRCH: 269 | fprintf(stderr, "specified thread does not exist\n"); 270 | break; 271 | default: 272 | fprintf(stderr, "unknown error code\n"); 273 | assert(false); 274 | } 275 | } else { 276 | t->status = METRO_STATUS_STOPPED; 277 | } 278 | } 279 | 280 | #undef MAX_NUM_METROS_OK 281 | -------------------------------------------------------------------------------- /src/metro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern const int MAX_NUM_METROS; 4 | 5 | // intialize the metros system 6 | extern void init_metro(void); 7 | extern void deinit_metro(void); 8 | 9 | extern void metro_start(int idx, double seconds, int count, int stage); 10 | extern void metro_stop(int idx); 11 | extern void metro_clear(void); 12 | 13 | // set period of metro 14 | // NB: if the metro is running, its hard to say if new value will take effect 15 | // on current period or next period 16 | extern void metro_set_time(int idx, float sec); 17 | -------------------------------------------------------------------------------- /src/midi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "event.h" 11 | 12 | static struct RtMidiWrapper *midi_in; 13 | static struct RtMidiWrapper *midi_out; 14 | 15 | static void midi_in_callback(double t, const uint8_t *message, size_t n, void *user_data) { 16 | union event_data *ev = event_data_new(EVENT_MIDI); 17 | 18 | ev->midi.nbytes = n; 19 | 20 | for (uint8_t i = 0; i < n; i++) { 21 | ev->midi.data[i] = message[i]; 22 | } 23 | 24 | event_post(ev); 25 | } 26 | 27 | void midi_send(const uint8_t *message, size_t n) { 28 | rtmidi_out_send_message(midi_out, message, n); 29 | } 30 | 31 | void init_midi() { 32 | midi_in = rtmidi_in_create_default(); 33 | rtmidi_open_virtual_port(midi_in, "isms"); 34 | rtmidi_in_set_callback(midi_in, midi_in_callback, NULL); 35 | 36 | midi_out = rtmidi_out_create_default(); 37 | rtmidi_open_virtual_port(midi_out, "isms"); 38 | } 39 | -------------------------------------------------------------------------------- /src/midi.h: -------------------------------------------------------------------------------- 1 | void init_midi(); 2 | void midi_send(const uint8_t *message, size_t n); 3 | -------------------------------------------------------------------------------- /src/monome.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "event.h" 10 | 11 | #define MAX_MONOME_DEVICES 8 12 | 13 | static int port = 11110; // internal port for serialosc communication 14 | 15 | static lo_server_thread st; 16 | static lo_address sosc; 17 | 18 | typedef struct { 19 | bool assigned; 20 | bool connected; 21 | char serial[10]; 22 | char name[32]; 23 | int port; 24 | lo_address addr; 25 | char quad[4][64]; 26 | bool dirty[4]; 27 | char mappath[32]; 28 | } monome_device_t; 29 | 30 | static monome_device_t device[MAX_MONOME_DEVICES]; 31 | 32 | static int osc_serialosc(const char *path, const char *types, lo_arg **argv, 33 | int argc, lo_message msg, void *user_data); 34 | static void lo_error_handler(int num, const char *m, const char *path); 35 | 36 | void init_monome() { 37 | for (int i = 0; i < MAX_MONOME_DEVICES; i++) 38 | device[i].assigned = false; 39 | 40 | char serialosc_port[6]; 41 | do { 42 | sprintf(serialosc_port, "%d", port); 43 | st = lo_server_thread_new(serialosc_port, lo_error_handler); 44 | if (!st) 45 | port++; 46 | } while (!st); 47 | 48 | printf("serialosc\t%s\n", serialosc_port); 49 | 50 | lo_server_thread_add_method(st, NULL, NULL, osc_serialosc, NULL); 51 | lo_server_thread_start(st); 52 | sosc = lo_address_new(NULL, "12002"); 53 | lo_send(sosc, "/serialosc/notify", "si", "localhost", port); 54 | lo_send(sosc, "/serialosc/list", "si", "localhost", port); 55 | } 56 | 57 | void deinit_monome() { lo_server_thread_free(st); } 58 | 59 | int rlookup(char *s) { 60 | int i; 61 | for (i = 0; i < MAX_MONOME_DEVICES; i++) { 62 | if (device[i].assigned && strcmp(device[i].serial, s) == 0) { 63 | return i; 64 | } 65 | } 66 | return -1; 67 | } 68 | 69 | void focus(int i) { 70 | lo_send(device[i].addr, "/sys/port", "i", port); 71 | lo_send(device[i].addr, "/sys/prefix", "s", device[i].serial); 72 | printf("## grid focus [%d]\n", i); 73 | } 74 | 75 | int osc_serialosc(const char *path, const char *types, lo_arg **argv, int argc, 76 | lo_message msg, void *user_data) { 77 | (void)user_data; 78 | // assuming properly formed OSC here 79 | // manually matching because liblo's wildcards don't seem to work 80 | if (strstr(path, "serialosc") != NULL) { 81 | if (strstr(path, "device") != NULL || strstr(path, "add") != NULL) { 82 | int pos = rlookup(&argv[0]->s); 83 | if (pos < 0) { // ADD NEW 84 | int i; 85 | for (i = 0; i < MAX_MONOME_DEVICES; i++) 86 | if (!device[i].assigned) 87 | break; 88 | if (i < MAX_MONOME_DEVICES) { 89 | device[i].assigned = true; 90 | device[i].connected = true; 91 | device[i].port = argv[2]->i; 92 | strcpy(device[i].serial, &argv[0]->s); 93 | strcpy(device[i].name, &argv[1]->s); 94 | sprintf(device[i].mappath, "/%s/grid/led/level/map", &argv[0]->s); 95 | char p[6]; 96 | sprintf(p, "%d", argv[2]->i); 97 | device[i].addr = lo_address_new(NULL, p); 98 | printf("## grid new/add [%d] %s '%s'\n", i, device[i].serial, 99 | device[i].name); 100 | focus(i); 101 | union event_data *ev = event_data_new(EVENT_GRID_ADD); 102 | ev->grid_add.id = i; 103 | ev->grid_add.serial = device[i].serial; 104 | ev->grid_add.name = device[i].name; 105 | event_post(ev); 106 | } 107 | } else { // RECONNECT 108 | if (!device[pos].connected) { // filter duplicate serialosc notification 109 | device[pos].connected = true; 110 | printf("## grid add [%d] %s\n", pos, device[pos].serial); 111 | union event_data *ev = event_data_new(EVENT_GRID_ADD); 112 | ev->grid_add.id = pos; 113 | ev->grid_add.serial = device[pos].serial; 114 | ev->grid_add.name = device[pos].name; 115 | event_post(ev); 116 | } 117 | } 118 | } else if (strstr(path, "remove") != NULL) { 119 | int pos = rlookup(&argv[0]->s); 120 | if (device[pos].connected) { // filter duplicate serialosc notifications 121 | device[pos].connected = false; 122 | printf("## grid remove [%d] %s\n", pos, device[pos].serial); 123 | union event_data *ev = event_data_new(EVENT_GRID_REMOVE); 124 | ev->grid_remove.id = pos; 125 | event_post(ev); 126 | } 127 | } 128 | lo_send(sosc, "/serialosc/notify", "si", "localhost", port); 129 | } else { 130 | for (int i = 0; i < MAX_MONOME_DEVICES; i++) { 131 | if (device[i].connected) { 132 | if (strstr(path, device[i].serial) != NULL) { 133 | if (strstr(path, "key") != 0) { 134 | // printf("keypress: %d %d %d\n",argv[0]->i, argv[1]->i, 135 | // argv[2]->i); 136 | union event_data *ev = event_data_new(EVENT_GRID); 137 | ev->grid.id = i; 138 | ev->grid.x = argv[0]->i; 139 | ev->grid.y = argv[1]->i; 140 | ev->grid.state = argv[2]->i; 141 | event_post(ev); 142 | } 143 | } 144 | } 145 | } 146 | } 147 | 148 | /* 149 | printf("serialosc: %s ",path); 150 | for (int i = 0; i < argc; i++) { 151 | switch (types[i]) { 152 | case LO_INT32: 153 | printf("%d ", argv[i]->i); 154 | break; 155 | case LO_FLOAT: 156 | printf("%f ", argv[i]->f); 157 | break; 158 | case LO_STRING: 159 | printf("'%s' ",&argv[i]->s); 160 | break; 161 | default: 162 | printf("(junk)"); 163 | break; 164 | } 165 | } 166 | printf("\n"); 167 | */ 168 | return 0; 169 | } 170 | 171 | void lo_error_handler(int num, const char *m, const char *path) { 172 | // printf("#### liblo error %d in path %s: %s\n", num, path, m); 173 | } 174 | 175 | void monome_all(int index, int z) { 176 | if (device[index].connected) { 177 | memset(device[index].quad, z, sizeof(char) * 256); 178 | for (int i = 0; i < 4; i++) 179 | device[index].dirty[i] = true; 180 | } 181 | } 182 | 183 | void monome_led(int index, int x, int y, int z) { 184 | if (device[index].connected) { 185 | int quad = (x / 8) + ((y / 8) * 2); 186 | device[index].quad[quad][(x % 8) + (y % 8) * 8] = z; 187 | device[index].dirty[quad] = true; 188 | } 189 | } 190 | 191 | void monome_redraw(int index) { 192 | if (device[index].connected) { 193 | static int offx[] = {0, 8, 0, 8}; // could do this with some bits instead 194 | static int offy[] = {0, 0, 8, 8}; 195 | for (int i = 0; i < 4; i++) { 196 | if (device[index].dirty[i]) { 197 | // printf("dirty: %d\n",i); 198 | lo_message msg = lo_message_new(); 199 | lo_message_add_int32(msg, offx[i]); 200 | lo_message_add_int32(msg, offy[i]); 201 | for (int d = 0; d < 64; d++) 202 | lo_message_add_int32(msg, device[index].quad[i][d]); 203 | // printf("%s : ",device[index].mappath); 204 | // lo_message_pp(msg); 205 | lo_send_message(device[index].addr, device[index].mappath, msg); 206 | lo_message_free(msg); 207 | device[index].dirty[i] = false; 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/monome.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern char *monome_port; 6 | 7 | void init_monome(); 8 | void register_monome(void); 9 | void deinit_monome(void); 10 | 11 | void handle_monome(char *from_host, char *from_port, char *path, 12 | lo_message msg); 13 | 14 | void monome_all(int index, int z); 15 | void monome_led(int index, int x, int y, int z); 16 | void monome_redraw(int index); 17 | -------------------------------------------------------------------------------- /src/osc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "event.h" 10 | 11 | char osc_port[6]; 12 | 13 | static lo_server_thread st; 14 | 15 | static int osc_receive(const char *path, const char *types, lo_arg **argv, 16 | int argc, lo_message msg, void *user_data); 17 | static void lo_error_handler(int num, const char *m, const char *path); 18 | 19 | void init_osc(int port) { 20 | do { 21 | sprintf(osc_port, "%d", port); 22 | st = lo_server_thread_new(osc_port, lo_error_handler); 23 | if (!st) 24 | port++; 25 | } while (!st); 26 | 27 | printf("osc\t\t%s\n", osc_port); 28 | 29 | lo_server_thread_add_method(st, NULL, NULL, osc_receive, NULL); 30 | lo_server_thread_start(st); 31 | } 32 | 33 | void deinit_osc() { lo_server_thread_free(st); } 34 | 35 | int osc_receive(const char *path, const char *types, lo_arg **argv, int argc, 36 | lo_message msg, void *user_data) { 37 | (void)types; 38 | (void)argv; 39 | (void)argc; 40 | (void)user_data; 41 | 42 | union event_data *ev = event_data_new(EVENT_OSC); 43 | 44 | ev->osc.path = strdup(path); 45 | ev->osc.msg = lo_message_clone(msg); 46 | 47 | lo_address source = lo_message_get_source(msg); 48 | const char *host = lo_address_get_hostname(source); 49 | const char *port = lo_address_get_port(source); 50 | 51 | ev->osc.from_host = strdup(host); 52 | ev->osc.from_port = strdup(port); 53 | 54 | event_post(ev); 55 | 56 | return 0; 57 | } 58 | 59 | void lo_error_handler(int num, const char *m, const char *path) { 60 | // printf("#### liblo error %d in path %s: %s\n", num, path, m); 61 | } 62 | -------------------------------------------------------------------------------- /src/osc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern char *osc_port; 6 | 7 | void init_osc(int); 8 | void register_osc(void); 9 | void deinit_osc(void); 10 | 11 | void handle_osc(char *from_host, char *from_port, char *path, lo_message msg); 12 | -------------------------------------------------------------------------------- /src/platform_clock.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "platform_clock.h" 4 | 5 | #ifdef __APPLE__ 6 | #include 7 | #define IV_1E9 1000000000 8 | 9 | static int emulated_clock_nanosleep(clockid_t clock_id, int flags, 10 | const struct timespec *rqtp, 11 | struct timespec *rmtp) { 12 | struct timespec timespec_init; 13 | 14 | clock_gettime(clock_id, ×pec_init); 15 | 16 | switch (clock_id) { 17 | case CLOCK_REALTIME: 18 | case CLOCK_MONOTONIC: { 19 | uint64_t nanos = rqtp->tv_sec * IV_1E9 + rqtp->tv_nsec; 20 | int success; 21 | 22 | if ((flags & TIMER_ABSTIME)) { 23 | uint64_t back = timespec_init.tv_sec * IV_1E9 + timespec_init.tv_nsec; 24 | nanos = nanos > back ? nanos - back : 0; 25 | } 26 | 27 | success = mach_wait_until(mach_absolute_time() + nanos) == KERN_SUCCESS; 28 | 29 | if (rmtp != NULL) { 30 | rmtp->tv_sec = 0; 31 | rmtp->tv_nsec = 0; 32 | } 33 | 34 | return success; 35 | } 36 | default: 37 | return -1; 38 | break; 39 | } 40 | } 41 | #endif // __APPLE__ 42 | 43 | int platform_clock_nanosleep(clockid_t clock_id, int flags, 44 | const struct timespec *rqtp, 45 | struct timespec *rmtp) { 46 | #ifdef __APPLE__ 47 | emulated_clock_nanosleep(clock_id, flags, rqtp, rmtp); 48 | #else 49 | clock_nanosleep(clock_id, flags, rqtp, rmtp); 50 | #endif 51 | return 0; // eh 52 | } 53 | -------------------------------------------------------------------------------- /src/platform_clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef TIMER_ABSTIME 4 | #define TIMER_ABSTIME 0x01 5 | #endif 6 | 7 | extern int platform_clock_nanosleep(clockid_t clock_id, int flags, 8 | const struct timespec *rqtp, 9 | struct timespec *rmtp); 10 | -------------------------------------------------------------------------------- /src/sdl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "event.h" 7 | #include "sdl.h" 8 | 9 | static pthread_t p; 10 | 11 | SDL_Window *window; 12 | SDL_Surface *surface; 13 | SDL_Surface *screen; 14 | SDL_Rect rect; 15 | 16 | uint32_t *pixels; 17 | 18 | int WIDTH = 256; 19 | int HEIGHT = 128; 20 | int ZOOM = 4; 21 | 22 | static int active = 0; 23 | int sdl_active() { return active; } 24 | 25 | void *sdl_loop(void *); 26 | 27 | void sdl_check(); 28 | 29 | int error(char *msg, const char *err) { 30 | printf("Error %s: %s\n", msg, err); 31 | return 0; 32 | } 33 | 34 | void window_rect() { 35 | int xsize, ysize, xzoom, yzoom; 36 | SDL_GetWindowSize(window, &xsize, &ysize); 37 | for (xzoom = 1; ((1 + xzoom) * WIDTH) <= xsize; xzoom++) 38 | ; 39 | for (yzoom = 1; ((1 + yzoom) * HEIGHT) <= ysize; yzoom++) 40 | ; 41 | ZOOM = xzoom < yzoom ? xzoom : yzoom; 42 | rect.x = (xsize - (WIDTH * ZOOM)) / 2; 43 | rect.y = (ysize - (HEIGHT * ZOOM)) / 2; 44 | rect.w = WIDTH * ZOOM; 45 | rect.h = HEIGHT * ZOOM; 46 | SDL_FillRect(screen, NULL, 0); 47 | } 48 | 49 | void sdl_clear(uint32_t *dst) { 50 | int v, h; 51 | for (v = 0; v < HEIGHT; v++) 52 | for (h = 0; h < WIDTH; h++) 53 | dst[v * WIDTH + h] = 0; 54 | } 55 | 56 | void sdl_pixel(uint32_t *dst, int x, int y, int color) { 57 | if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) 58 | dst[y * WIDTH + x] = color; 59 | } 60 | 61 | void sdl_line(uint32_t *dst, int ax, int ay, int bx, int by, int color) { 62 | int dx = abs(bx - ax), sx = ax < bx ? 1 : -1; 63 | int dy = -abs(by - ay), sy = ay < by ? 1 : -1; 64 | int err = dx + dy, e2; 65 | for (;;) { 66 | sdl_pixel(dst, ax, ay, color); 67 | if (ax == bx && ay == by) 68 | break; 69 | e2 = 2 * err; 70 | if (e2 >= dy) { 71 | err += dy; 72 | ax += sx; 73 | } 74 | if (e2 <= dx) { 75 | err += dx; 76 | ay += sy; 77 | } 78 | } 79 | } 80 | 81 | void sdl_redraw(uint32_t *dst) { 82 | SDL_BlitScaled(surface, NULL, screen, &rect); 83 | SDL_UpdateWindowSurface(window); 84 | } 85 | 86 | int init_sdl(int x, int y) { 87 | // printf(">> SDL: init\n"); 88 | if (active) 89 | return 0; // already initialized 90 | 91 | WIDTH = x; 92 | HEIGHT = y; 93 | 94 | if (SDL_Init(SDL_INIT_VIDEO) < 0) 95 | return error("Init", SDL_GetError()); 96 | window = SDL_CreateWindow( 97 | "isms", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH * ZOOM, 98 | HEIGHT * ZOOM, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 99 | if (window == NULL) 100 | return error("Window", SDL_GetError()); 101 | SDL_SetWindowMinimumSize(window, WIDTH, HEIGHT); 102 | 103 | surface = SDL_CreateRGBSurface(0, WIDTH, HEIGHT, 32, 0, 0, 0, 0); 104 | screen = SDL_GetWindowSurface(window); 105 | window_rect(); 106 | 107 | sdl_clear(surface->pixels); 108 | 109 | // start event check timer 110 | if (pthread_create(&p, NULL, sdl_loop, 0)) { 111 | return error("SDL", "pthread failed"); 112 | } 113 | #ifdef _GNU_SOURCE 114 | pthread_setname_np(p, "sdl"); 115 | #endif 116 | 117 | active = 1; 118 | return 1; 119 | } 120 | 121 | void deinit_sdl(void) { 122 | if (active == 0) 123 | return; 124 | // printf(">> SDL: deinit\n"); 125 | pthread_cancel(p); 126 | SDL_DestroyWindow(window); 127 | window = NULL; 128 | SDL_FreeSurface(surface); 129 | SDL_Quit(); 130 | } 131 | 132 | void sdl_check() { 133 | union event_data *ev; 134 | SDL_Event event; 135 | int ctrl; 136 | while (SDL_PollEvent(&event) != 0) { 137 | switch (event.type) { 138 | // case SDL_MOUSEBUTTONUP: 139 | // case SDL_MOUSEBUTTONDOWN: 140 | // case SDL_MOUSEMOTION: //domouse(&event); break; 141 | case SDL_KEYDOWN: 142 | // shift = SDL_GetModState() & KMOD_LSHIFT || SDL_GetModState() & 143 | // KMOD_RSHIFT; 144 | ctrl = SDL_GetModState() & KMOD_LCTRL || SDL_GetModState() & KMOD_RCTRL; 145 | if (ctrl) { 146 | switch (event.key.keysym.sym) { 147 | case SDLK_q: 148 | ev = event_data_new(EVENT_QUIT); 149 | event_post(ev); 150 | break; 151 | case SDLK_r: 152 | ev = event_data_new(EVENT_RESET); 153 | event_post(ev); 154 | break; 155 | } 156 | } 157 | ev = event_data_new(EVENT_KEY); 158 | ev->key.scancode = event.key.keysym.sym; 159 | event_post(ev); 160 | break; 161 | case SDL_QUIT: 162 | ev = event_data_new(EVENT_QUIT); 163 | event_post(ev); 164 | break; 165 | case SDL_WINDOWEVENT: 166 | if (event.window.event == SDL_WINDOWEVENT_EXPOSED) 167 | sdl_redraw(pixels); 168 | if (event.window.event == SDL_WINDOWEVENT_RESIZED) { 169 | screen = SDL_GetWindowSurface(window); 170 | window_rect(); 171 | sdl_redraw(pixels); 172 | } 173 | } 174 | } 175 | } 176 | 177 | // FIXME: this is high-cpu 178 | void *sdl_loop(void *x) { 179 | (void)x; 180 | union event_data *ev; 181 | while (1) { 182 | ev = event_data_new(EVENT_SDL_CHECK); 183 | event_post(ev); 184 | sleep(0.005); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/sdl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define HOR 32 6 | #define VER 16 7 | #define PAD 2 8 | 9 | extern SDL_Surface *surface; 10 | extern uint32_t *pixels; 11 | 12 | int init_sdl(int x, int y); 13 | void deinit_sdl(void); 14 | 15 | int sdl_active(void); 16 | void sdl_check(void); 17 | 18 | void sdl_redraw(uint32_t *dst); 19 | void sdl_clear(uint32_t *dst); 20 | void sdl_pixel(uint32_t *dst, int x, int y, int color); 21 | void sdl_line(uint32_t *dst, int ax, int ay, int bx, int by, int color); 22 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "event.h" 12 | 13 | #define SOCKET_LEN 1024 14 | 15 | static pthread_t p; 16 | 17 | static int sockfd; 18 | static char buffer[SOCKET_LEN]; 19 | static struct sockaddr_in addr_here, addr_there; 20 | 21 | void *socket_loop(void *x) { 22 | (void)x; 23 | uint32_t len; 24 | int n; 25 | 26 | while (1) { 27 | len = sizeof(addr_there); 28 | n = recvfrom(sockfd, (char *)buffer, SOCKET_LEN, MSG_WAITALL, 29 | (struct sockaddr *)&addr_there, &len); 30 | if (n == -1) { 31 | printf(">> SOCKET: receive fail\n"); 32 | } else { 33 | buffer[n] = '\0'; 34 | printf("\033[0;31m"); 35 | printf("%s", buffer); // assuming incoming linebreak? 36 | printf("\033[0m"); 37 | char *line = malloc((n + 1) * sizeof(char)); 38 | strncpy(line, buffer, n); 39 | line[n] = '\0'; 40 | union event_data *ev = event_data_new(EVENT_EXEC_CODE_LINE); 41 | ev->exec_code_line.line = line; 42 | event_post(ev); 43 | } 44 | } 45 | } 46 | 47 | void init_socket(int port) { 48 | int ret; 49 | 50 | if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 51 | printf(">> SOCKET: init fail\n"); 52 | exit(1); 53 | } 54 | 55 | do { 56 | memset(&addr_here, 0, sizeof(addr_here)); 57 | addr_here.sin_family = AF_INET; 58 | addr_here.sin_addr.s_addr = INADDR_ANY; 59 | addr_here.sin_port = htons(port); 60 | ret = bind(sockfd, (const struct sockaddr *)&addr_here, sizeof(addr_here)) < 61 | 0; 62 | if (ret) 63 | port++; 64 | } while (ret); 65 | 66 | if (pthread_create(&p, NULL, socket_loop, 0)) { 67 | printf(">> SOCKET: init fail pthread\n"); 68 | exit(1); 69 | } 70 | #ifdef _GNU_SOURCE 71 | pthread_setname_np(p, "socket"); 72 | #endif 73 | 74 | printf("socket\t\t%d\n", port); 75 | } 76 | -------------------------------------------------------------------------------- /src/socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern void init_socket(int); 4 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | require('math') 2 | print(math.random(3)) 3 | print('test.lua') 4 | 5 | -------------------------------------------------------------------------------- /waf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tehn/isms/88ae4095b155ad0199bbe01351774ceabc8e53e7/waf -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | import shutil 2 | from pathlib import Path 3 | from waflib.Build import BuildContext 4 | 5 | class install_lib_class(BuildContext): 6 | cmd = 'install-lib' 7 | fun = 'install_lib' 8 | 9 | def options(opt): 10 | opt.load('compiler_c compiler_cxx') 11 | 12 | def configure(conf): 13 | conf.load('compiler_c compiler_cxx') 14 | 15 | conf.env.append_value('CXXFLAGS', '-std=c++11') 16 | 17 | # extra include path for brew-installed SDL 18 | if conf.env.DEST_OS == "darwin": 19 | conf.env.append_value('CFLAGS', '-I/opt/homebrew/include') 20 | 21 | try: 22 | conf.check_cfg(package='lua54', args='--cflags --libs', uselib_store='LUA') 23 | except conf.errors.ConfigurationError: 24 | conf.check_cfg(package='lua', args='lua >= 5.4 --cflags --libs', uselib_store='LUA') 25 | 26 | conf.check_cfg(msg='Checking for SDL2', 27 | package='', 28 | path='sdl2-config', 29 | args='--cflags --libs', 30 | uselib_store='SDL2') 31 | 32 | conf.check_cfg(package='liblo', args='--cflags --libs', uselib_store='LO') 33 | conf.check_cc(lib='pthread', uselib_store='PTHREAD') 34 | conf.check_cc(lib='m', uselib_store='M') 35 | 36 | if conf.env.DEST_OS == "linux": 37 | try: 38 | conf.check_cfg(package='alsa', args='--cflags --libs', uselib_store='ALSA') 39 | conf.env.append_value('RTMIDI_DEFINES', '__LINUX_ALSA__') 40 | conf.env.append_value('RTMIDI_USE', 'ALSA') 41 | except conf.errors.ConfigurationError: 42 | pass 43 | elif conf.env.DEST_OS == "darwin": 44 | conf.env.append_value('RTMIDI_DEFINES', '__MACOSX_CORE__') 45 | conf.env.append_value('RTMIDI_FRAMEWORKS', ['CoreServices', 'CoreAudio', 'CoreMIDI', 'CoreFoundation']) 46 | 47 | def build(ctx): 48 | ctx.stlib(features='cxx', 49 | source=['rtmidi/rtmidi_c.cpp', 'rtmidi/RtMidi.cpp'], 50 | defines=ctx.env.RTMIDI_DEFINES, 51 | target='rtmidi') 52 | 53 | ctx.program(features='cxx', 54 | source=ctx.path.ant_glob('src/**/*.c'), 55 | includes=['src', 'src/clock', 'rtmidi'], 56 | target='isms', 57 | framework=ctx.env.RTMIDI_FRAMEWORKS, 58 | use=['LO', 'LUA', 'SDL2', 'PTHREAD', 'M', 'rtmidi'] + ctx.env.RTMIDI_USE) 59 | 60 | def install_lib(ctx): 61 | src = Path(ctx.path.abspath()) / 'lua' 62 | dst = Path.home() / '.local' / 'share' / 'isms' / 'system' 63 | 64 | dst.mkdir(parents=True, exist_ok=True) 65 | shutil.copytree(src, dst, dirs_exist_ok=True) 66 | --------------------------------------------------------------------------------