aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2016-01-05 14:20:16 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2016-01-05 14:20:16 +0100
commit97df9bd48cb6d4847896377d4eefaead9e2cd75f (patch)
treed41ab27628cc389e12b8bc21bb5a122826398515
parent7d222a52c70cc78e80ad0bab2f7f77dd03ad12b8 (diff)
downloadapplause2-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--Makefile4
-rw-r--r--applause.c147
-rw-r--r--applause.lua83
3 files changed, 117 insertions, 117 deletions
diff --git a/Makefile b/Makefile
index 999fac3..5102469 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/applause.c b/applause.c
index 28210c9..4a5f453 100644
--- a/applause.c
+++ b/applause.c
@@ -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