diff options
-rw-r--r-- | applause.c | 169 | ||||
-rw-r--r-- | applause.h | 2 | ||||
-rw-r--r-- | applause.lua | 45 |
3 files changed, 203 insertions, 13 deletions
@@ -51,25 +51,31 @@ static jack_client_t *client = NULL; #define DEFAULT_NUMBER_OF_OUTPUT_PORTS 1 +#define DEFAULT_NUMBER_OF_INPUT_PORTS 0 #define DEFAULT_BUFFER_SIZE 100 /* milliseconds */ -typedef struct applause_output_port { +typedef struct applause_io_port { jack_port_t *jack_port; jack_ringbuffer_t *buffer; int buffer_sem; /** - * True if a buffer underrun occurs. + * True if a buffer underrun/overrun occurs. * FIXME: sig_atomic_t is probably the wrong type. * Perhaps use the Mintomic types. */ sig_atomic_t buffer_xrun; -} applause_output_port; +} applause_io_port; /** List of Applause output ports */ -static applause_output_port *output_ports = NULL; +static applause_io_port *output_ports = NULL; /** Number of Applause output ports */ static int output_ports_count = DEFAULT_NUMBER_OF_OUTPUT_PORTS; +/** List of Applause input ports */ +static applause_io_port *input_ports = NULL; +/** Number of Applause input ports */ +static int input_ports_count = DEFAULT_NUMBER_OF_INPUT_PORTS; + static lua_State *L_global = NULL; static volatile sig_atomic_t interrupted = 0; @@ -213,13 +219,18 @@ jack_process(jack_nframes_t nframes, void *arg) void *midi_in; jack_nframes_t midi_events; + /* + * Fill the buffers of all output ports. + * Does not block when there are insufficient samples + * in the ring buffer (buffer underrun). + * See also applause_push_sample(). + */ for (int i = 0; i < output_ports_count; i++) { - applause_output_port *port = output_ports + i; + applause_io_port *port = output_ports + i; jack_default_audio_sample_t *out; size_t r; - out = (jack_default_audio_sample_t *) - jack_port_get_buffer(port->jack_port, nframes); + out = jack_port_get_buffer(port->jack_port, nframes); r = jack_ringbuffer_read(port->buffer, (char *)out, len); @@ -238,7 +249,35 @@ jack_process(jack_nframes_t nframes, void *arg) * It might not be on every UNIX!? */ memset((char *)out + r, 0, len - r); - port->buffer_xrun |= len - r > 0; + port->buffer_xrun |= (r < len); + } + + /* + * Retrieve the data of all input ports. + * It does not block when the ring buffer overflows. + * See also applause_pull_sample(). + */ + for (int i = 0; i < input_ports_count; i++) { + applause_io_port *port = input_ports + i; + jack_default_audio_sample_t *in; + size_t r; + + in = jack_port_get_buffer(port->jack_port, nframes); + + r = jack_ringbuffer_write(port->buffer, (const char *)in, len); + + /* + * The semaphore value corresponds with the number of readable + * bytes in the buffer. + * This operation should never block. + */ + if (r > 0) + svsem_op(port->buffer_sem, r); + + /* + * Record buffer overruns. + */ + port->buffer_xrun |= (r < len); } /* @@ -346,7 +385,7 @@ init_audio(int buffer_size) output_ports = calloc(output_ports_count, sizeof(*output_ports)); for (int i = 0; i < output_ports_count; i++) { - applause_output_port *port = output_ports + i; + applause_io_port *port = output_ports + i; char name[256]; /* @@ -383,6 +422,49 @@ init_audio(int buffer_size) } /* + * Create input ports + */ + free(input_ports); + input_ports = calloc(input_ports_count, sizeof(*input_ports)); + + for (int i = 0; i < input_ports_count; i++) { + applause_io_port *port = input_ports + i; + char name[256]; + + /* + * NOTE: Port names must be unique. + * FIXME: Make the names configurable. + */ + snprintf(name, sizeof(name), "input_%d", i+1); + + port->jack_port = jack_port_register(client, name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + if (!port->jack_port) { + fprintf(stderr, "No more JACK ports available\n"); + return 1; + } + + /* + * Initialize ring buffer of samples. + * The semaphore is initialized with the same size + * since it represents the bytes written to the ring + * buffer. + */ + port->buffer = jack_ringbuffer_create(buffer_bytes); + if (!port->buffer) { + fprintf(stderr, "cannot create ringbuffer\n"); + return 1; + } + + port->buffer_sem = svsem_init(0); + if (port->buffer_sem < 0) { + fprintf(stderr, "error initializing semaphore\n"); + return 1; + } + } + + /* * Create MIDI input port */ midi_port.jack_port = jack_port_register(client, "midi_input", @@ -459,7 +541,7 @@ init_audio(int buffer_size) enum applause_audio_state applause_push_sample(int output_port_id, double sample_double) { - applause_output_port *port; + applause_io_port *port; jack_default_audio_sample_t sample = (jack_default_audio_sample_t)sample_double; @@ -493,6 +575,62 @@ applause_push_sample(int output_port_id, double sample_double) } /** + * Pull one Jack sample from the ring buffer. + * + * This function should be called from the InputStream:gtick() + * implementation, which is faster than calling Lua functions + * from a C implementation of InputStream:gtick() using the Lua C API. + * + * @param input_port_id The number of the input port. + * The first one being 1. + * @param sample_double The audio sample to write 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_pull_sample(int input_port_id, double *sample_double) +{ + applause_io_port *port; + jack_default_audio_sample_t sample; + + /* + * NOTE: The alternative to reporting invalid port Ids here + * would be exporting output_ports_count, so the Lua code can + * check it and assert()ing here instead. + */ + if (unlikely(input_port_id < 1 || input_port_id > input_ports_count)) + return APPLAUSE_AUDIO_INVALID_PORT; + port = input_ports + input_port_id - 1; + + /* + * We are about to "consume" one sample from the buffer. + * This can block when the buffer is empty. + * After this operation, we have __at least__ one sample available + * for reading since jack_process() will only write to the + * buffer. + */ + svsem_op(port->buffer_sem, -(int)sizeof(sample)); + + /* + * NOTE: We cannot directly read into sample_double since it + * may have a different storage size - jack_default_audio_sample_t + * is usually a float. + */ + jack_ringbuffer_read(port->buffer, (char *)&sample, + sizeof(sample)); + *sample_double = sample; + + if (unlikely(port->buffer_xrun)) { + port->buffer_xrun = 0; + return APPLAUSE_AUDIO_XRUN; + } + + return APPLAUSE_AUDIO_OK; +} + +/** * Pull one MIDI sample from the ring buffer. * * This can be called from MIDIStream's tick function. @@ -865,11 +1003,13 @@ static const NativeMethod native_methods[] = { static void usage(const char *program) { - printf("%s [-h] [-o OUTPUT] [-b SIZE] [SCRIPT [ARGS]]\n" + printf("%s [-h] [-o OUTPUT] [-i INPUT] [-b SIZE] [SCRIPT [ARGS]]\n" "\tOUTPUT\tNumber of output ports to reserve (default: %d)\n" + "\tINPUT\tNumber of input ports to reserve (default: %d)\n" "\tSIZE\tMinimum size of the output buffer in milliseconds (default: %dms)\n", program, - DEFAULT_NUMBER_OF_OUTPUT_PORTS, DEFAULT_BUFFER_SIZE); + DEFAULT_NUMBER_OF_OUTPUT_PORTS, DEFAULT_NUMBER_OF_INPUT_PORTS, + DEFAULT_BUFFER_SIZE); } int @@ -883,7 +1023,7 @@ main(int argc, char **argv) pthread_t command_server_thread; - while ((opt = getopt(argc, argv, "ho:b:")) >= 0) { + while ((opt = getopt(argc, argv, "ho:i:b:")) >= 0) { switch (opt) { case '?': case 'h': /* get help */ @@ -892,6 +1032,9 @@ main(int argc, char **argv) case 'o': /* output ports */ output_ports_count = atoi(optarg); break; + case 'i': /* input ports */ + input_ports_count = atoi(optarg); + break; case 'b': /* buffer size */ buffer_size = atoi(optarg); break; @@ -8,5 +8,7 @@ enum applause_audio_state { enum applause_audio_state applause_push_sample(int output_port_id, double sample_double); +enum applause_audio_state applause_pull_sample(int input_port_id, + double *sample_double); int applause_is_interrupted(void); diff --git a/applause.lua b/applause.lua index 5d1ba5a..fc5c31b 100644 --- a/applause.lua +++ b/applause.lua @@ -1422,6 +1422,51 @@ end -- If given, this value will be returned by the @{Stream:new} method instead. -- @see Stream:ctor +-- +-- A stream for retrieving data from Jack input ports. +-- This will block until data is available so constructs +-- like InputStream():save("foo.wav") are possible. +-- +InputStream = DeriveClass(Stream) + +function InputStream:ctor(first_port, len) + first_port = first_port or 1 + len = len or 1 + assert(len >= 1, "Invalid length specified") + + if len > 1 then + -- Since MuxStream is the only allowed multi-channel stream, + -- automatically mux for multichannel input streams. + local streams = {} + for i = 1, len do + streams[i] = InputStream:new(first_port+i-1) + end + return MuxStream:new(unpack(streams)) + end + + -- Single channel InputStream + self.port = first_port +end + +function InputStream:gtick() + local port = self.port + local sample_buffer = ffi.new("double[1]") + + return function() + local state = C.applause_pull_sample(port, sample_buffer) + + -- React to buffer overruns. + -- 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 overrun detected\n") + end + + return tonumber(sample_buffer[0]) + end +end + --- Class for caching streams. -- Cached streams are calculated only once per tick. -- @type CachedStream |