aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2016-10-05 06:29:20 -0400
committerDavid Robillard <d@drobilla.net>2016-10-05 06:29:20 -0400
commite522a14f53ecb7ec50bb698755bacd706fc38aa2 (patch)
treeb16a47f9d4719b965c90621abf973bf6e7b982bf
parent227b462dc73ff4b1b99e26b5d4f2150d73417728 (diff)
downloadjalv-e522a14f53ecb7ec50bb698755bacd706fc38aa2.tar.gz
jalv-e522a14f53ecb7ec50bb698755bacd706fc38aa2.tar.bz2
jalv-e522a14f53ecb7ec50bb698755bacd706fc38aa2.zip
Add PortAudio backend
-rw-r--r--NEWS3
-rw-r--r--src/jalv.c4
-rw-r--r--src/portaudio.c223
-rw-r--r--wscript46
4 files changed, 257 insertions, 19 deletions
diff --git a/NEWS b/NEWS
index b6c9c5b..f278793 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ jalv (1.4.7) unstable;
* Support thread-safe state restoration
* Update UI when internal plugin state is changed during preset load
* Add generic Qt control UI from Amadeus Folego
+ * Add PortAudio backend (compile time option, audio only)
* Set Jack port order metadata
* Allow Jack client name to be set from command line (thanks Adam Avramov)
* Add command prompt to console version for changing controls
@@ -20,7 +21,7 @@ jalv (1.4.7) unstable;
* Use moc-qt4 if present for systems with multiple Qt versions
* Add Qt5 version
- -- David Robillard <d@drobilla.net> Fri, 16 Sep 2016 16:37:23 -0700
+ -- David Robillard <d@drobilla.net> Wed, 05 Oct 2016 06:15:57 -0400
jalv (1.4.6) stable;
diff --git a/src/jalv.c b/src/jalv.c
index 508c0fe..78901ca 100644
--- a/src/jalv.c
+++ b/src/jalv.c
@@ -771,8 +771,8 @@ main(int argc, char** argv)
Jalv jalv;
memset(&jalv, '\0', sizeof(Jalv));
jalv.prog_name = argv[0];
- jalv.block_length = 4096; /* Should be set by jack_buffer_size_cb */
- jalv.midi_buf_size = 1024; /* Should be set by jack_buffer_size_cb */
+ jalv.block_length = 4096; /* Should be set by backend */
+ jalv.midi_buf_size = 1024; /* Should be set by backend */
jalv.play_state = JALV_PAUSED;
jalv.bpm = 120.0f;
jalv.control_in = (uint32_t)-1;
diff --git a/src/portaudio.c b/src/portaudio.c
new file mode 100644
index 0000000..cb16c63
--- /dev/null
+++ b/src/portaudio.c
@@ -0,0 +1,223 @@
+/*
+ Copyright 2007-2016 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <portaudio.h>
+
+#include "jalv_internal.h"
+#include "worker.h"
+
+struct JalvBackend {
+ PaStream* stream;
+};
+
+static int
+pa_process_cb(const void* inputs,
+ void* outputs,
+ unsigned long nframes,
+ const PaStreamCallbackTimeInfo* time,
+ PaStreamCallbackFlags flags,
+ void* handle)
+{
+ Jalv* jalv = (Jalv*)handle;
+
+ /* Prepare port buffers */
+ uint32_t in_index = 0;
+ uint32_t out_index = 0;
+ for (uint32_t i = 0; i < jalv->num_ports; ++i) {
+ struct Port* port = &jalv->ports[i];
+ if (port->type == TYPE_AUDIO) {
+ if (port->flow == FLOW_INPUT) {
+ lilv_instance_connect_port(jalv->instance, i, ((float**)inputs)[in_index++]);
+ } else if (port->flow == FLOW_OUTPUT) {
+ lilv_instance_connect_port(jalv->instance, i, ((float**)outputs)[out_index++]);
+ }
+ } else if (port->type == TYPE_EVENT && port->flow == FLOW_INPUT) {
+ lv2_evbuf_reset(port->evbuf, true);
+
+ if (jalv->request_update) {
+ /* Plugin state has changed, request an update */
+ const LV2_Atom_Object get = {
+ { sizeof(LV2_Atom_Object_Body), jalv->urids.atom_Object },
+ { 0, jalv->urids.patch_Get } };
+ LV2_Evbuf_Iterator iter = lv2_evbuf_begin(port->evbuf);
+ lv2_evbuf_write(&iter, 0, 0,
+ get.atom.type, get.atom.size,
+ (const uint8_t*)LV2_ATOM_BODY(&get));
+ }
+ } else if (port->type == TYPE_EVENT) {
+ /* Clear event output for plugin to write to */
+ lv2_evbuf_reset(port->evbuf, false);
+ }
+ }
+ jalv->request_update = false;
+
+ /* Run plugin for this cycle */
+ const bool send_ui_updates = jalv_run(jalv, nframes);
+
+ /* Deliver UI events */
+ for (uint32_t p = 0; p < jalv->num_ports; ++p) {
+ struct Port* const port = &jalv->ports[p];
+ if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) {
+ for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(port->evbuf);
+ lv2_evbuf_is_valid(i);
+ i = lv2_evbuf_next(i)) {
+ // Get event from LV2 buffer
+ uint32_t frames, subframes, type, size;
+ uint8_t* body;
+ lv2_evbuf_get(i, &frames, &subframes, &type, &size, &body);
+
+ if (jalv->has_ui && !port->old_api) {
+ // Forward event to UI
+ jalv_send_to_ui(jalv, p, type, size, body);
+ }
+ }
+ } else if (send_ui_updates &&
+ port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL) {
+ char buf[sizeof(ControlChange) + sizeof(float)];
+ ControlChange* ev = (ControlChange*)buf;
+ ev->index = p;
+ ev->protocol = 0;
+ ev->size = sizeof(float);
+ *(float*)ev->body = port->control;
+ if (zix_ring_write(jalv->plugin_events, buf, sizeof(buf))
+ < sizeof(buf)) {
+ fprintf(stderr, "Plugin => UI buffer overflow!\n");
+ }
+ }
+ }
+
+ return paContinue;
+}
+
+static JalvBackend*
+pa_error(const char* msg, PaError err)
+{
+ fprintf(stderr, "error: %s (%s)\n", msg, Pa_GetErrorText(err));
+ Pa_Terminate();
+ return NULL;
+}
+
+JalvBackend*
+jalv_backend_init(Jalv* jalv)
+{
+ PaStreamParameters inputParameters;
+ PaStreamParameters outputParameters;
+ PaStream* stream = NULL;
+ PaError st = paNoError;
+
+ if ((st = Pa_Initialize())) {
+ return pa_error("Failed to initialize audio system", st);
+ }
+
+ // Get default input and output devices
+ inputParameters.device = Pa_GetDefaultInputDevice();
+ outputParameters.device = Pa_GetDefaultOutputDevice();
+ if (inputParameters.device == paNoDevice) {
+ return pa_error("No default input device", paDeviceUnavailable);
+ } else if (outputParameters.device == paNoDevice) {
+ return pa_error("No default output device", paDeviceUnavailable);
+ }
+
+ const PaDeviceInfo* in_dev = Pa_GetDeviceInfo(inputParameters.device);
+ const PaDeviceInfo* out_dev = Pa_GetDeviceInfo(outputParameters.device);
+
+ // Count number of input and output audio ports/channels
+ inputParameters.channelCount = 0;
+ outputParameters.channelCount = 0;
+ for (uint32_t i = 0; i < jalv->num_ports; ++i) {
+ if (jalv->ports[i].type == TYPE_AUDIO) {
+ if (jalv->ports[i].flow == FLOW_INPUT) {
+ ++inputParameters.channelCount;
+ } else if (jalv->ports[i].flow == FLOW_OUTPUT) {
+ ++outputParameters.channelCount;
+ }
+ }
+ }
+
+ // Configure audio format
+ inputParameters.sampleFormat = paFloat32|paNonInterleaved;
+ inputParameters.suggestedLatency = in_dev->defaultLowInputLatency;
+ inputParameters.hostApiSpecificStreamInfo = NULL;
+ outputParameters.sampleFormat = paFloat32|paNonInterleaved;
+ outputParameters.suggestedLatency = out_dev->defaultLowOutputLatency;
+ outputParameters.hostApiSpecificStreamInfo = NULL;
+
+ // Open stream
+ if ((st = Pa_OpenStream(
+ &stream,
+ inputParameters.channelCount ? &inputParameters : NULL,
+ outputParameters.channelCount ? &outputParameters : NULL,
+ in_dev->defaultSampleRate,
+ paFramesPerBufferUnspecified,
+ 0,
+ pa_process_cb,
+ jalv))) {
+ return pa_error("Failed to open audio stream", st);
+ }
+
+ // Set audio parameters
+ jalv->sample_rate = in_dev->defaultSampleRate;
+ // jalv->block_length = FIXME
+ jalv->midi_buf_size = 4096;
+
+ // Allocate and return opaque backend
+ JalvBackend* backend = (JalvBackend*)calloc(1, sizeof(JalvBackend));
+ backend->stream = stream;
+ return backend;
+}
+
+void
+jalv_backend_close(Jalv* jalv)
+{
+ Pa_Terminate();
+ free(jalv->backend);
+ jalv->backend = NULL;
+}
+
+void
+jalv_backend_activate(Jalv* jalv)
+{
+ const int st = Pa_StartStream(jalv->backend->stream);
+ if (st != paNoError) {
+ fprintf(stderr, "error: Error starting audio stream (%s)\n",
+ Pa_GetErrorText(st));
+ }
+}
+
+void
+jalv_backend_deactivate(Jalv* jalv)
+{
+ const int st = Pa_CloseStream(jalv->backend->stream);
+ if (st != paNoError) {
+ fprintf(stderr, "error: Error closing audio stream (%s)\n",
+ Pa_GetErrorText(st));
+ }
+}
+
+void
+jalv_backend_activate_port(Jalv* jalv, uint32_t port_index)
+{
+ struct Port* const port = &jalv->ports[port_index];
+ switch (port->type) {
+ case TYPE_CONTROL:
+ lilv_instance_connect_port(jalv->instance, port_index, &port->control);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/wscript b/wscript
index 8bf4720..24ec9db 100644
--- a/wscript
+++ b/wscript
@@ -19,6 +19,9 @@ def options(opt):
opt.load('compiler_c')
opt.load('compiler_cxx')
autowaf.set_options(opt)
+ opt.add_option('--portaudio', action='store_true', default=False,
+ dest='portaudio',
+ help='Use PortAudio backend, not JACK')
opt.add_option('--no-jack-session', action='store_true', default=False,
dest='no_jack_session',
help='Do not build JACK session support')
@@ -58,8 +61,12 @@ def configure(conf):
atleast_version='0.6.0', mandatory=True)
autowaf.check_pkg(conf, 'sratom-0', uselib_store='SRATOM',
atleast_version='0.5.1', mandatory=True)
- autowaf.check_pkg(conf, 'jack', uselib_store='JACK',
- atleast_version='0.120.0', mandatory=True)
+ if Options.options.portaudio:
+ autowaf.check_pkg(conf, 'portaudio-2.0', uselib_store='PORTAUDIO',
+ atleast_version='2.0.0', mandatory=False)
+ else:
+ autowaf.check_pkg(conf, 'jack', uselib_store='JACK',
+ atleast_version='0.120.0', mandatory=True)
if not Options.options.no_gtk:
if not Options.options.no_gtk2:
@@ -87,17 +94,18 @@ def configure(conf):
if not conf.find_program('moc-qt5', var='MOC5', mandatory=False):
conf.find_program('moc', var='MOC5')
- conf.check(function_name='jack_port_type_get_buffer_size',
- header_name='jack/jack.h',
- define_name='HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE',
- uselib='JACK',
- mandatory=False)
+ if conf.env.HAVE_JACK:
+ conf.check(function_name='jack_port_type_get_buffer_size',
+ header_name='jack/jack.h',
+ define_name='HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE',
+ uselib='JACK',
+ mandatory=False)
- conf.check(function_name='jack_set_property',
- header_name='jack/metadata.h',
- define_name='HAVE_JACK_METADATA',
- uselib='JACK',
- mandatory=False)
+ conf.check(function_name='jack_set_property',
+ header_name='jack/metadata.h',
+ define_name='HAVE_JACK_METADATA',
+ uselib='JACK',
+ mandatory=False)
defines = ['_POSIX_C_SOURCE=200809L']
@@ -130,8 +138,10 @@ def configure(conf):
conf.write_config_header('jalv_config.h', remove=False)
- autowaf.display_msg(conf, "Jack metadata support",
- conf.is_defined('HAVE_JACK_METADATA'))
+ autowaf.display_msg(conf, "Backend", "Jack" if conf.env.HAVE_JACK else "PortAudio")
+ if conf.env.HAVE_JACK:
+ autowaf.display_msg(conf, "Jack metadata support",
+ conf.is_defined('HAVE_JACK_METADATA'))
autowaf.display_msg(conf, "Gtk 2.0 support", bool(conf.env.HAVE_GTK2))
autowaf.display_msg(conf, "Gtk 3.0 support", bool(conf.env.HAVE_GTK3))
autowaf.display_msg(conf, "Gtkmm 2.0 support", bool(conf.env.HAVE_GTKMM2))
@@ -141,10 +151,9 @@ def configure(conf):
print('')
def build(bld):
- libs = 'LILV SUIL JACK SERD SORD SRATOM LV2'
+ libs = 'LILV SUIL JACK SERD SORD SRATOM LV2 PORTAUDIO'
source = '''
src/control.c
- src/jack.c
src/jalv.c
src/log.c
src/lv2_evbuf.c
@@ -154,6 +163,11 @@ def build(bld):
src/zix/ring.c
'''
+ if bld.env.HAVE_JACK:
+ source += 'src/jack.c'
+ elif bld.env.HAVE_PORTAUDIO:
+ source += 'src/portaudio.c'
+
# Non-GUI version
obj = bld(features = 'c cprogram',
source = source + ' src/jalv_console.c',