diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-01-05 14:20:16 +0100 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-01-05 14:20:16 +0100 |
commit | 97df9bd48cb6d4847896377d4eefaead9e2cd75f (patch) | |
tree | d41ab27628cc389e12b8bc21bb5a122826398515 | |
parent | 7d222a52c70cc78e80ad0bab2f7f77dd03ad12b8 (diff) | |
download | applause2-97df9bd48cb6d4847896377d4eefaead9e2cd75f.tar.gz |
rewritten Stream:play() as a Lua function
* the low-level C part is now implemented in a normal
C function applause_push_sample() which is called using the FFI
API
* this is supposedly faster than the old Lua/C way, but the
speed improvement seems to be miniscule.
However changes like this are still good since they simplify
the C core.
* speed improvements will probably be larger for the
MIDI*Stream functions since here we call Lua/C functions at
sample rate.
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | applause.c | 147 | ||||
-rw-r--r-- | applause.lua | 83 |
3 files changed, 117 insertions, 117 deletions
@@ -15,6 +15,10 @@ CFLAGS += $(LUA_CFLAGS) $(READLINE_CFLAGS) $(JACK_CFLAGS) LDFLAGS += $(LUA_LDFLAGS) $(READLINE_LDFLAGS) $(JACK_LDFLAGS) \ -lpthread +# For exporting function from applause.c that can be called +# with the LuaJIT FFI interface: +LDFLAGS += -rdynamic + all : applause applause : applause.o @@ -394,6 +394,7 @@ init_audio(int buffer_size) return 0; } +/* FIXME: Rewrite as an exported FFI-callable function */ static int l_MIDIVelocityStream_getValue(lua_State *L) { @@ -429,6 +430,7 @@ l_MIDIVelocityStream_getValue(lua_State *L) return 1; } +/* FIXME: Rewrite as an exported FFI-callable function */ static int l_MIDINoteStream_getValue(lua_State *L) { @@ -461,6 +463,7 @@ l_MIDINoteStream_getValue(lua_State *L) return 1; } +/* FIXME: Rewrite as an exported FFI-callable function */ static int l_MIDICCStream_getValue(lua_State *L) { @@ -496,117 +499,54 @@ l_MIDICCStream_getValue(lua_State *L) return 1; } -static int -l_Stream_play(lua_State *L) -{ - int top = lua_gettop(L); - - luaL_argcheck(L, top == 1, top, "Stream object expected"); - luaL_checktype(L, 1, LUA_TTABLE); - - /* get reset() method */ - lua_getfield(L, 1, "reset"); - luaL_checktype(L, -1, LUA_TFUNCTION); - /* duplicate object table since we have to call self.reset(self) */ - lua_pushvalue(L, 1); - lua_call(L, 1, 0); - - /* get tick() method */ - lua_getfield(L, 1, "tick"); - luaL_checktype(L, -1, LUA_TFUNCTION); - /* duplicate object table since we have to call self.tick(self) */ - lua_pushvalue(L, 1); - lua_call(L, 1, 1); - /* the tick generator function should now be on top of the stack */ - luaL_checktype(L, -1, LUA_TFUNCTION); - - /* - * Make sure JIT compilation is turned on for the generator function - * and all subfunctions. - * This should not be necessary theoretically. - */ - luaJIT_setmode(L, -1, LUAJIT_MODE_ALLFUNC | LUAJIT_MODE_ON); +enum applause_audio_state { + APPLAUSE_AUDIO_OK = 0, + APPLAUSE_AUDIO_INTERRUPTED, + APPLAUSE_AUDIO_XRUN +}; - /* - * Get global cycleClock() function. It exists so we can - * conveniently advance the clock from the native C method - * without calling lua_setfield() and access a class table - * from generator methods at sample rate. - */ - lua_getglobal(L, "clockCycle"); +/** + * Push one Jack sample into the ring buffer. + * + * This function should be called from the Stream:play() + * implementation, which is faster than calling Lua functions + * from a C implementation of Stream:play() using the Lua C API + * (supposedly, I could not reproduce this). + * + * @param sample_double The audio sample as a double (compatible + * with lua_Number). + * This is speed relevant since otherwise + * LuaJIT tries to translate to Jack's default + * float type. + */ +enum applause_audio_state +applause_push_sample(double sample_double) +{ + jack_default_audio_sample_t sample = + (jack_default_audio_sample_t)sample_double; /* - * Perform garbage collection cycle and turn it off - * temporarily. This improves the realtime properties - * of the sample generation loop below. + * We are about to "consume" one free sample in the buffer. + * This can block when the buffer is full. + * After this operation, there is __at least__ one sample free + * in buffer since jack_process() will only read from the + * buffer. */ - lua_gc(L, LUA_GCCOLLECT, 0); - lua_gc(L, LUA_GCSTOP, 0); + svsem_op(buffer_sem, -(int)sizeof(sample)); - interrupted = 0; + jack_ringbuffer_write(buffer, (const char *)&sample, + sizeof(sample)); - while (!interrupted) { - jack_default_audio_sample_t sample; - - /* - * React to buffer underruns. - * This is done here instead of in the realtime thread - * even though it is already overloaded, so as not to - * affect other applications in the Jack graph. - */ - if (buffer_xrun) { - fprintf(stderr, "WARNING: Buffer underrun detected!\n"); - buffer_xrun = 0; - } - - /* - * Duplicate and call clockCycle() function. - * This is more efficient than calling lua_getglobal() - * at sample rate. - */ - lua_pushvalue(L, -1); - lua_call(L, 0, 0); - - /* duplicate generator function */ - lua_pushvalue(L, -2); - /* generate next sample */ - lua_call(L, 0, 1); - - if (lua_isnil(L, -1)) - /* stream has ended */ - break; - - /* copy sample into ring buffer */ - /* - * FIXME: What if sample isn't a number. This - * should be handled. But don't forget to restart - * the garbage collector - */ - sample = (jack_default_audio_sample_t)lua_tonumber(L, -1); - - /* - * We are about to "consume" one free sample in the buffer. - * This can block when the buffer is full. - * After this operation, there is __at least__ one sample free - * in buffer since jack_process() will only read from the - * buffer. - */ - svsem_op(buffer_sem, -(int)sizeof(sample)); - - jack_ringbuffer_write(buffer, (const char *)&sample, - sizeof(sample)); - - /* pop sample, the function dup has already been popped */ - lua_pop(L, 1); + if (interrupted) { + interrupted = 0; + return APPLAUSE_AUDIO_INTERRUPTED; + } + if (buffer_xrun) { + buffer_xrun = 0; + return APPLAUSE_AUDIO_XRUN; } - lua_gc(L, LUA_GCRESTART, 0); - - if (interrupted) - return luaL_error(L, "SIGINT received"); - - /* any remaining stack elements are automatically popped */ - return 0; + return APPLAUSE_AUDIO_OK; } static int @@ -879,7 +819,6 @@ typedef struct NativeMethod { } NativeMethod; static const NativeMethod native_methods[] = { - {"Stream", "play", l_Stream_play}, {"Stream", "fork", l_Stream_fork}, {"MIDIVelocityStream", "getValue", l_MIDIVelocityStream_getValue}, {"MIDINoteStream", "getValue", l_MIDINoteStream_getValue}, diff --git a/applause.lua b/applause.lua index 4f278b1..5fe77e4 100644 --- a/applause.lua +++ b/applause.lua @@ -1,6 +1,9 @@ local sndfile = require "sndfile" -local ffi = require "ffi" local bit = require "bit" +local ffi = require "ffi" +-- According to LuaJIT docs, it makes sense to cache +-- the FFI namespace +local C = ffi.C -- Make table.new() available (a LuaJIT extension) require "table.new" @@ -35,9 +38,9 @@ function benchmark(fnc) local t1 = ffi.new("struct timespec[1]") local t2 = ffi.new("struct timespec[1]") - ffi.C.clock_gettime("CLOCK_PROCESS_CPUTIME_ID", t1) + C.clock_gettime("CLOCK_PROCESS_CPUTIME_ID", t1) fnc() - ffi.C.clock_gettime("CLOCK_PROCESS_CPUTIME_ID", t2) + C.clock_gettime("CLOCK_PROCESS_CPUTIME_ID", t2) local t1_ms = t1[0].tv_sec*1000 + t1[0].tv_nsec/1000000 local t2_ms = t2[0].tv_sec*1000 + t2[0].tv_nsec/1000000 @@ -45,6 +48,20 @@ function benchmark(fnc) print("Elapsed CPU time: "..(t2_ms - t1_ms).."ms") end +-- +-- Define the Lua FFI part of Applause's C core. +-- These functions and types are defined in applause.c +-- +ffi.cdef[[ +enum applause_audio_state { + APPLAUSE_AUDIO_OK = 0, + APPLAUSE_AUDIO_INTERRUPTED, + APPLAUSE_AUDIO_XRUN +}; + +enum applause_audio_state applause_push_sample(double sample_double); +]] + -- Sample rate -- This is overwritten by the C core samplerate = 44100 @@ -62,10 +79,8 @@ function msec(x) return sec((x or 1)/1000) end -- A clock signal is necessary to "trigger" the recalculation -- of a stream's current sample. -- The clock signal is a boolean oscillating between true and --- false. The global function is for advancing the clock more --- or less efficiently from the play() method implemented in C. +-- false. local clock_signal = false -function clockCycle() clock_signal = not clock_signal end -- FIXME: Inconsistent naming. Use all-lower case for functions -- and methods? @@ -371,9 +386,49 @@ function Stream:len() return math.huge -- infinity end --- implemented in applause.c function Stream:play() - error("C function not registered!") + self:reset() + + local tick = self:tick() + + -- Make sure JIT compilation is turned on for the generator function + -- and all subfunctions. + -- This should not be necessary theoretically. + jit.on(true, true) + jit.on(tick, true) + + -- Perform garbage collection cycle and turn it off + -- temporarily. This improves the realtime properties + -- of the sample generation loop below. + collectgarbage("collect") + collectgarbage("stop") + + local state + repeat + -- Advance clock + clock_signal = not clock_signal + + local sample = tick() + if not sample then break end + + -- FIXME: What if the sample is not a number, + -- perhaps we should check that here + state = C.applause_push_sample(sample) + + -- React to buffer underruns. + -- This is done here instead of in the realtime thread + -- even though it is already overloaded, so as not to + -- affect other applications in the Jack graph. + if state == C.APPLAUSE_AUDIO_XRUN then + io.stderr:write("WARNING: Buffer underrun detected\n") + end + until state == C.APPLAUSE_AUDIO_INTERRUPTED + + collectgarbage("restart") + + if state == C.APPLAUSE_AUDIO_INTERRUPTED then + error("SIGINT received", 2) + end end -- implemented in applause.c @@ -394,11 +449,13 @@ function Stream:save(filename, format) local tick = self:tick() while true do - clockCycle() + clock_signal = not clock_signal local sample = tick() if not sample then break end + -- FIXME: What if the sample is not a number, + -- perhaps we should check that here hnd:write(sample) end @@ -416,7 +473,7 @@ function Stream:totable() local vector = table.new(self:len(), 0) while true do - clockCycle() + clock_signal = not clock_signal local value = tick() if not value then break end @@ -1554,15 +1611,15 @@ function Client:ctor(pid) end function Client:play() - ffi.C.kill(self.pid, 10); -- SIGUSR1 + C.kill(self.pid, 10); -- SIGUSR1 end function Client:stop() - ffi.C.kill(self.pid, 12); -- SIGUSR2 + C.kill(self.pid, 12); -- SIGUSR2 end function Client:kill() - ffi.C.kill(self.pid, 15); -- SIGTERM + C.kill(self.pid, 15); -- SIGTERM end Client.__gc = Client.kill |