diff options
Diffstat (limited to 'applause.c')
-rw-r--r-- | applause.c | 169 |
1 files changed, 156 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; |