diff options
author | David Robillard <d@drobilla.net> | 2016-10-05 06:29:20 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2016-10-05 06:29:20 -0400 |
commit | e522a14f53ecb7ec50bb698755bacd706fc38aa2 (patch) | |
tree | b16a47f9d4719b965c90621abf973bf6e7b982bf | |
parent | 227b462dc73ff4b1b99e26b5d4f2150d73417728 (diff) | |
download | jalv-e522a14f53ecb7ec50bb698755bacd706fc38aa2.tar.gz jalv-e522a14f53ecb7ec50bb698755bacd706fc38aa2.tar.bz2 jalv-e522a14f53ecb7ec50bb698755bacd706fc38aa2.zip |
Add PortAudio backend
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | src/jalv.c | 4 | ||||
-rw-r--r-- | src/portaudio.c | 223 | ||||
-rw-r--r-- | wscript | 46 |
4 files changed, 257 insertions, 19 deletions
@@ -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; @@ -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; + } +} @@ -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', |