From f240b08a1799b3aa6a29701c90388d51c0c1b2ce Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 18 Feb 2017 18:32:02 +0100 Subject: Add experimental PortAudio driver --- ingen/EngineBase.hpp | 18 ++- ingen/ingen.h | 10 +- src/ingen/ingen.cpp | 15 +- src/server/DirectDriver.hpp | 4 +- src/server/Driver.hpp | 9 +- src/server/Engine.cpp | 88 ++++++++---- src/server/Engine.hpp | 12 +- src/server/EnginePort.hpp | 16 ++- src/server/JackDriver.cpp | 12 +- src/server/JackDriver.hpp | 6 +- src/server/PortAudioDriver.cpp | 289 ++++++++++++++++++++++++++++++++++++++ src/server/PortAudioDriver.hpp | 127 +++++++++++++++++ src/server/events/CreateGraph.cpp | 9 +- src/server/events/CreatePort.cpp | 3 + src/server/ingen_portaudio.cpp | 55 ++++++++ src/server/wscript | 12 ++ tests/ingen_test.cpp | 29 +--- wscript | 5 + 18 files changed, 627 insertions(+), 92 deletions(-) create mode 100644 src/server/PortAudioDriver.cpp create mode 100644 src/server/PortAudioDriver.hpp create mode 100644 src/server/ingen_portaudio.cpp diff --git a/ingen/EngineBase.hpp b/ingen/EngineBase.hpp index 087189e4..a0a20cd9 100644 --- a/ingen/EngineBase.hpp +++ b/ingen/EngineBase.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2017 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -17,9 +17,10 @@ #ifndef INGEN_ENGINEBASE_HPP #define INGEN_ENGINEBASE_HPP -#include +#include +#include +#include -#include "raul/URI.hpp" #include "ingen/ingen.h" #include "ingen/types.hpp" @@ -67,6 +68,16 @@ public: */ virtual bool pending_events() = 0; + /** + Flush any pending events. + + This function is only safe to call in sequential contexts, and runs both + process thread and main iterations in lock-step. + + @param Interval in milliseconds to sleep between each block. + */ + virtual void flush_events(const std::chrono::milliseconds& sleep_ms) = 0; + /** Locate to a given cycle. */ @@ -113,7 +124,6 @@ public: Unregister a client. */ virtual bool unregister_client(SPtr client) = 0; - }; } // namespace Ingen diff --git a/ingen/ingen.h b/ingen/ingen.h index 11fd592a..ba56ae52 100644 --- a/ingen/ingen.h +++ b/ingen/ingen.h @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2014-2016 David Robillard + Copyright 2014-2017 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -34,14 +34,6 @@ # define INGEN_API #endif -#ifndef INGEN_WARN_UNUSED_RESULT -# if __GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ >= 4 -# define INGEN_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -# else -# define INGEN_WARN_UNUSED_RESULT -# endif -#endif - #define INGEN_NS "http://drobilla.net/ns/ingen#" #define INGEN__Arc INGEN_NS "Arc" diff --git a/src/ingen/ingen.cpp b/src/ingen/ingen.cpp index e6f781df..3d209d20 100644 --- a/src/ingen/ingen.cpp +++ b/src/ingen/ingen.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2015 David Robillard + Copyright 2007-2017 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -149,8 +149,11 @@ main(int argc, char** argv) // Activate the engine, if we have one if (world->engine()) { - ingen_try(world->load_module("jack"), "Failed to load jack module"); - world->engine()->activate(); + if (!world->load_module("jack") && !world->load_module("portaudio")) { + cerr << "ingen: error: Failed to load driver module" << endl; + delete world; + exit(EXIT_FAILURE); + } } // Load a graph @@ -213,6 +216,12 @@ main(int argc, char** argv) } } + // Activate the engine now that the graph is loaded + if (world->engine()) { + world->engine()->flush_events(std::chrono::milliseconds(10)); + world->engine()->activate(); + } + // Set up signal handlers that will set quit_flag on interrupt signal(SIGINT, ingen_interrupt); signal(SIGTERM, ingen_interrupt); diff --git a/src/server/DirectDriver.hpp b/src/server/DirectDriver.hpp index 186ea5f0..5bc7998f 100644 --- a/src/server/DirectDriver.hpp +++ b/src/server/DirectDriver.hpp @@ -39,9 +39,7 @@ public: _ports.clear_and_dispose([](EnginePort* p) { delete p; }); } - virtual void activate() {} - - virtual void deactivate() {} + bool dynamic_ports() const { return true; } virtual EnginePort* create_port(DuplexPort* graph_port) { return new EnginePort(graph_port); diff --git a/src/server/Driver.hpp b/src/server/Driver.hpp index 3e1a724f..eae90084 100644 --- a/src/server/Driver.hpp +++ b/src/server/Driver.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2016 David Robillard + Copyright 2007-2017 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -42,7 +42,7 @@ public: virtual ~Driver() {} /** Activate driver (begin processing graph and events). */ - virtual void activate() {} + virtual bool activate() { return true; } /** Deactivate driver (stop processing graph and events). */ virtual void deactivate() {} @@ -66,6 +66,9 @@ public: */ virtual void remove_port(RunContext& context, EnginePort* port) = 0; + /** Return true iff driver supports dynamic adding/removing of ports. */ + virtual bool dynamic_ports() const { return false; } + /** Register a system visible port. */ virtual void register_port(EnginePort& port) = 0; @@ -91,7 +94,7 @@ public: virtual SampleRate sample_rate() const = 0; /** Return the current frame time (running counter) */ - virtual SampleCount frame_time() const = 0; + virtual SampleCount frame_time() const = 0; /** Append time events for this cycle to `buffer`. */ virtual void append_time_events(RunContext& context, diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp index a460ac48..647fa6e6 100644 --- a/src/server/Engine.cpp +++ b/src/server/Engine.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2016 David Robillard + Copyright 2007-2017 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free @@ -19,6 +19,7 @@ #include #include +#include #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" @@ -91,6 +92,7 @@ Engine::Engine(Ingen::World* world) , _reset_load_flag(false) , _direct_driver(true) , _atomic_bundles(world->conf().option("atomic-bundles").get()) + , _activated(false) { if (!world->store()) { world->set_store(SPtr(new Store())); @@ -205,6 +207,14 @@ Engine::listen() #endif } +void +Engine::advance(SampleCount nframes) +{ + for (RunContext* ctx : _run_contexts) { + ctx->locate(ctx->start() + nframes, block_length()); + } +} + void Engine::locate(FrameTime s, SampleCount nframes) { @@ -213,6 +223,25 @@ Engine::locate(FrameTime s, SampleCount nframes) } } +void +Engine::flush_events(const std::chrono::milliseconds& sleep_ms) +{ + bool finished = !pending_events(); + while (!finished) { + // Run one audio block to execute prepared events + run(block_length()); + advance(block_length()); + + // Run one main iteration to post-process events + main_iteration(); + + // Sleep before continuing if there are still events to process + if (!(finished = !pending_events())) { + std::this_thread::sleep_for(sleep_ms); + } + } +} + void Engine::emit_notifications(FrameTime end) { @@ -338,6 +367,11 @@ Engine::set_driver(SPtr driver) ctx->set_priority(driver->real_time_priority()); ctx->set_rate(driver->sample_rate()); } + + _buffer_factory->set_block_length(driver->block_length()); + _options->set(sample_rate(), + block_length(), + buffer_factory()->default_size(_world->uris().atom_Sequence)); } SampleCount @@ -383,41 +417,31 @@ Engine::activate() ThreadManager::single_threaded = true; - _buffer_factory->set_block_length(_driver->block_length()); - _options->set(sample_rate(), - block_length(), - buffer_factory()->default_size(_world->uris().atom_Sequence)); - const Ingen::URIs& uris = world()->uris(); if (!_root_graph) { - // Create root graph - Properties graph_properties; - graph_properties.insert( - make_pair(uris.rdf_type, - Property(uris.ingen_Graph))); - graph_properties.insert( - make_pair(uris.ingen_polyphony, - Property(_world->forge().make(1), - Resource::Graph::INTERNAL))); - - Events::CreateGraph ev( - *this, SPtr(), -1, 0, Raul::Path("/"), graph_properties); - - // Execute in "fake" process context (we are single threaded) - PreProcessContext pctx; - RunContext rctx(run_context()); - ev.pre_process(pctx); - ev.execute(rctx); - ev.post_process(); - - _root_graph = ev.graph(); + // No root graph has been loaded, create an empty one + const Properties properties = { + {uris.rdf_type, uris.ingen_Graph}, + {uris.ingen_polyphony, + Property(_world->forge().make(1), + Resource::Graph::INTERNAL)}}; + + enqueue_event( + new Events::CreateGraph( + *this, SPtr(), -1, 0, Raul::Path("/"), properties)); + + flush_events(std::chrono::milliseconds(10)); + if (!_root_graph) { + return false; + } } _driver->activate(); _root_graph->enable(); ThreadManager::single_threaded = false; + _activated = true; return true; } @@ -434,6 +458,7 @@ Engine::deactivate() } ThreadManager::single_threaded = true; + _activated = false; } unsigned @@ -442,10 +467,6 @@ Engine::run(uint32_t sample_count) RunContext& ctx = run_context(); _cycle_start_time = current_time(ctx); - // Apply control bindings to input - control_bindings()->pre_process( - ctx, _root_graph->port_impl(0)->buffer(0).get()); - post_processor()->set_end_time(ctx.end()); // Process events that came in during the last cycle @@ -455,6 +476,11 @@ Engine::run(uint32_t sample_count) // Run root graph if (_root_graph) { + // Apply control bindings to input + control_bindings()->pre_process( + ctx, _root_graph->port_impl(0)->buffer(0).get()); + + // Run root graph for this cycle _root_graph->process(ctx); // Emit control binding feedback diff --git a/src/server/Engine.hpp b/src/server/Engine.hpp index de14b024..ea5a1402 100644 --- a/src/server/Engine.hpp +++ b/src/server/Engine.hpp @@ -17,7 +17,7 @@ #ifndef INGEN_ENGINE_ENGINE_HPP #define INGEN_ENGINE_ENGINE_HPP -#include +#include #include #include #include @@ -26,6 +26,7 @@ #include "ingen/Interface.hpp" #include "ingen/ingen.h" #include "ingen/types.hpp" +#include "raul/Noncopyable.hpp" #include "Clock.hpp" #include "Event.hpp" @@ -65,7 +66,7 @@ class Worker; @ingroup engine */ -class INGEN_API Engine : public boost::noncopyable, public EngineBase +class INGEN_API Engine : public Raul::Noncopyable, public EngineBase { public: explicit Engine(Ingen::World* world); @@ -141,6 +142,11 @@ public: RunContext& run_context() { return *_run_contexts[0]; } + void set_root_graph(GraphImpl* graph) { _root_graph = graph; } + + void flush_events(const std::chrono::milliseconds& sleep_ms); + + void advance(SampleCount nframes); void locate(FrameTime s, SampleCount nframes); void emit_notifications(FrameTime end); bool pending_notifications(); @@ -157,6 +163,7 @@ public: size_t n_threads() const { return _run_contexts.size(); } bool atomic_bundles() const { return _atomic_bundles; } + bool activated() const { return _activated; } private: Ingen::World* _world; @@ -226,6 +233,7 @@ private: bool _reset_load_flag; bool _direct_driver; bool _atomic_bundles; + bool _activated; }; } // namespace Server diff --git a/src/server/EnginePort.hpp b/src/server/EnginePort.hpp index f14243e7..ee00d4ed 100644 --- a/src/server/EnginePort.hpp +++ b/src/server/EnginePort.hpp @@ -40,20 +40,24 @@ public: : _graph_port(port) , _buffer(NULL) , _handle(NULL) + , _driver_index(0) {} - void set_buffer(void* buf) { _buffer = buf; } - void set_handle(void* buf) { _handle = buf; } + void set_buffer(void* buf) { _buffer = buf; } + void set_handle(void* buf) { _handle = buf; } + void set_driver_index(uint32_t index) { _driver_index = index; } - void* buffer() const { return _buffer; } - void* handle() const { return _handle; } - DuplexPort* graph_port() const { return _graph_port; } - bool is_input() const { return _graph_port->is_input(); } + void* buffer() const { return _buffer; } + void* handle() const { return _handle; } + uint32_t driver_index() const { return _driver_index; } + DuplexPort* graph_port() const { return _graph_port; } + bool is_input() const { return _graph_port->is_input(); } protected: DuplexPort* _graph_port; void* _buffer; void* _handle; + uint32_t _driver_index; }; } // namespace Server diff --git a/src/server/JackDriver.cpp b/src/server/JackDriver.cpp index 77eb62b3..b228faf7 100644 --- a/src/server/JackDriver.cpp +++ b/src/server/JackDriver.cpp @@ -140,31 +140,36 @@ JackDriver::attach(const std::string& server_name, return true; } -void +bool JackDriver::activate() { World* world = _engine.world(); if (_is_activated) { _engine.log().warn("Jack driver already activated\n"); - return; + return false; } if (!_client) attach(world->conf().option("jack-server").ptr(), world->conf().option("jack-name").ptr(), NULL); + if (!_client) { + return false; + } + jack_set_process_callback(_client, process_cb, this); _is_activated = true; if (jack_activate(_client)) { _engine.log().error("Could not activate Jack client, aborting\n"); - exit(EXIT_FAILURE); + return false; } else { _engine.log().info(fmt("Activated Jack client `%1%'\n") % world->conf().option("jack-name").ptr()); } + return true; } void @@ -479,6 +484,7 @@ JackDriver::_process_cb(jack_nframes_t nframes) pre_process_port(_engine.run_context(), &p); } + // Process _engine.run(nframes); // Write output diff --git a/src/server/JackDriver.hpp b/src/server/JackDriver.hpp index 2d50d892..49d3e4c2 100644 --- a/src/server/JackDriver.hpp +++ b/src/server/JackDriver.hpp @@ -65,10 +65,10 @@ public: const std::string& client_name, void* jack_client); - void activate(); + bool activate(); void deactivate(); - void enable(); - void disable(); + + bool dynamic_ports() const { return true; } EnginePort* create_port(DuplexPort* graph_port); EnginePort* get_port(const Raul::Path& path); diff --git a/src/server/PortAudioDriver.cpp b/src/server/PortAudioDriver.cpp new file mode 100644 index 00000000..396ec4d4 --- /dev/null +++ b/src/server/PortAudioDriver.cpp @@ -0,0 +1,289 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#include "ingen_config.h" + +#include +#include + +#include + +#include "ingen/Configuration.hpp" +#include "ingen/LV2Features.hpp" +#include "ingen/Log.hpp" +#include "ingen/URIMap.hpp" +#include "ingen/World.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" + +#include "Buffer.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "GraphImpl.hpp" +#include "PortAudioDriver.hpp" +#include "PortImpl.hpp" +#include "ThreadManager.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +static bool +pa_error(const char* msg, PaError err) +{ + fprintf(stderr, "error: %s (%s)\n", msg, Pa_GetErrorText(err)); + Pa_Terminate(); + return false; +} + +PortAudioDriver::PortAudioDriver(Engine& engine) + : _engine(engine) + , _sem(0) + , _flag(false) + , _seq_size(4096) + , _block_length(4096) + , _sample_rate(48000) + , _n_inputs(0) + , _n_outputs(0) + , _is_activated(false) +{ +} + +PortAudioDriver::~PortAudioDriver() +{ + deactivate(); + _ports.clear_and_dispose([](EnginePort* p) { delete p; }); +} + +bool +PortAudioDriver::attach() +{ + 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); + + _sample_rate = in_dev->defaultSampleRate; + _block_length = 4096; // FIXME + _seq_size = 4096; + return true; +} + +bool +PortAudioDriver::activate() +{ + 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 (const auto& port : _ports) { + if (port.graph_port()->is_a(PortType::AUDIO)) { + if (port.graph_port()->is_input()) { + ++_inputParameters.channelCount; + } else if (port.graph_port()->is_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 + PaError st = paNoError; + if ((st = Pa_OpenStream( + &_stream, + _inputParameters.channelCount ? &_inputParameters : NULL, + _outputParameters.channelCount ? &_outputParameters : NULL, + in_dev->defaultSampleRate, + _block_length, // paFramesPerBufferUnspecified, // FIXME: ? + 0, + pa_process_cb, + this))) { + return pa_error("Failed to open audio stream", st); + } + + _is_activated = true; + if ((st = Pa_StartStream(_stream))) { + return pa_error("Error starting audio stream", st); + } + + return true; +} + +void +PortAudioDriver::deactivate() +{ + Pa_Terminate(); +} + +SampleCount +PortAudioDriver::frame_time() const +{ + return _engine.run_context().start(); +} + +EnginePort* +PortAudioDriver::get_port(const Raul::Path& path) +{ + for (auto& p : _ports) { + if (p.graph_port()->path() == path) { + return &p; + } + } + + return NULL; +} + +void +PortAudioDriver::add_port(RunContext& context, EnginePort* port) +{ + _ports.push_back(*port); +} + +void +PortAudioDriver::remove_port(RunContext& context, EnginePort* port) +{ + _ports.erase(_ports.iterator_to(*port)); +} + +void +PortAudioDriver::register_port(EnginePort& port) +{ +} + +void +PortAudioDriver::unregister_port(EnginePort& port) +{ +} + +void +PortAudioDriver::rename_port(const Raul::Path& old_path, + const Raul::Path& new_path) +{ +} + +void +PortAudioDriver::port_property(const Raul::Path& path, + const Raul::URI& uri, + const Atom& value) +{ +} + +EnginePort* +PortAudioDriver::create_port(DuplexPort* graph_port) +{ + EnginePort* eport = NULL; + if (graph_port->is_a(PortType::AUDIO) || graph_port->is_a(PortType::CV)) { + // Audio buffer port, use Jack buffer directly + eport = new EnginePort(graph_port); + graph_port->set_is_driver_port(*_engine.buffer_factory()); + } else if (graph_port->is_a(PortType::ATOM) && + graph_port->buffer_type() == _engine.world()->uris().atom_Sequence) { + // Sequence port, make Jack port but use internal LV2 format buffer + eport = new EnginePort(graph_port); + } + + if (graph_port->is_a(PortType::AUDIO)) { + if (graph_port->is_input()) { + eport->set_driver_index(_n_inputs++); + } else { + eport->set_driver_index(_n_outputs++); + } + } + + if (eport) { + register_port(*eport); + } + + return eport; +} + +void +PortAudioDriver::pre_process_port(RunContext& context, + EnginePort* port, + const void* inputs, + void* outputs) +{ + if (!port->graph_port()->is_a(PortType::AUDIO)) { + return; + } + + if (port->is_input()) { + port->set_buffer(((float**)inputs)[port->driver_index()]); + } else { + port->set_buffer(((float**)outputs)[port->driver_index()]); + memset(port->buffer(), 0, _block_length * sizeof(float)); + } + + port->graph_port()->set_driver_buffer( + port->buffer(), _block_length * sizeof(float)); +} + +void +PortAudioDriver::post_process_port(RunContext& context, + EnginePort* port, + const void* inputs, + void* outputs) +{ +} + +int +PortAudioDriver::process_cb(const void* inputs, + void* outputs, + unsigned long nframes, + const PaStreamCallbackTimeInfo* time, + PaStreamCallbackFlags flags) +{ + _engine.advance(nframes); + + // Read input + for (auto& p : _ports) { + pre_process_port(_engine.run_context(), &p, inputs, outputs); + } + + // Process + _engine.run(nframes); + + // Write output + for (auto& p : _ports) { + post_process_port(_engine.run_context(), &p, inputs, outputs); + } + + return 0; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/PortAudioDriver.hpp b/src/server/PortAudioDriver.hpp new file mode 100644 index 00000000..5c5c2445 --- /dev/null +++ b/src/server/PortAudioDriver.hpp @@ -0,0 +1,127 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#ifndef INGEN_ENGINE_PORTAUDIODRIVER_HPP +#define INGEN_ENGINE_PORTAUDIODRIVER_HPP + +#include "ingen_config.h" + +#include +#include +#include + +#include "raul/Semaphore.hpp" + +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" + +#include "Driver.hpp" +#include "EnginePort.hpp" + +namespace Raul { class Path; } + +namespace Ingen { +namespace Server { + +class DuplexPort; +class Engine; +class GraphImpl; +class PortAudioDriver; +class PortImpl; + +class PortAudioDriver : public Driver +{ +public: + explicit PortAudioDriver(Engine& engine); + ~PortAudioDriver(); + + bool attach(); + + bool activate(); + void deactivate(); + + EnginePort* create_port(DuplexPort* graph_port); + EnginePort* get_port(const Raul::Path& path); + + void rename_port(const Raul::Path& old_path, const Raul::Path& new_path); + void port_property(const Raul::Path& path, const Raul::URI& uri, const Atom& value); + void add_port(RunContext& context, EnginePort* port); + void remove_port(RunContext& context, EnginePort* port); + void register_port(EnginePort& port); + void unregister_port(EnginePort& port); + + void append_time_events(RunContext& context, Buffer& buffer) {} + + SampleCount frame_time() const; + + int real_time_priority() { return 80; } + + SampleCount block_length() const { return _block_length; } + size_t seq_size() const { return _seq_size; } + SampleCount sample_rate() const { return _sample_rate; } + +private: + friend class PortAudioPort; + + inline static int + pa_process_cb(const void* inputs, + void* outputs, + unsigned long nframes, + const PaStreamCallbackTimeInfo* time, + PaStreamCallbackFlags flags, + void* handle) { + return ((PortAudioDriver*)handle)->process_cb( + inputs, outputs, nframes, time, flags); + } + + int + process_cb(const void* inputs, + void* outputs, + unsigned long nframes, + const PaStreamCallbackTimeInfo* time, + PaStreamCallbackFlags flags); + + void pre_process_port(RunContext& context, + EnginePort* port, + const void* inputs, + void* outputs); + + void post_process_port(RunContext& context, + EnginePort* port, + const void* inputs, + void* outputs); + +protected: + typedef boost::intrusive::list Ports; + + Engine& _engine; + Ports _ports; + PaStreamParameters _inputParameters; + PaStreamParameters _outputParameters; + Raul::Semaphore _sem; + std::atomic _flag; + PaStream* _stream; + size_t _seq_size; + uint32_t _block_length; + uint32_t _sample_rate; + uint32_t _n_inputs; + uint32_t _n_outputs; + bool _is_activated; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PORTAUDIODRIVER_HPP diff --git a/src/server/events/CreateGraph.cpp b/src/server/events/CreateGraph.cpp index 304656af..99b29b66 100644 --- a/src/server/events/CreateGraph.cpp +++ b/src/server/events/CreateGraph.cpp @@ -194,8 +194,13 @@ void CreateGraph::execute(RunContext& context) { if (_graph) { - if (_parent && _compiled_graph) { - _parent->set_compiled_graph(std::move(_compiled_graph)); + if (_parent) { + if (_compiled_graph) { + _parent->set_compiled_graph(std::move(_compiled_graph)); + } + } else { + _engine.set_root_graph(_graph); + _graph->enable(); } for (Event* ev : _child_events) { diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp index 4e34762a..3adf2f8b 100644 --- a/src/server/events/CreatePort.cpp +++ b/src/server/events/CreatePort.cpp @@ -102,6 +102,9 @@ CreatePort::pre_process(PreProcessContext& ctx) return Event::pre_process_done(Status::PARENT_NOT_FOUND, parent_path); } else if (!(_graph = dynamic_cast(parent))) { return Event::pre_process_done(Status::INVALID_PARENT, parent_path); + } else if (!_graph->parent() && _engine.activated() && + !_engine.driver()->dynamic_ports()) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); } const URIs& uris = _engine.world()->uris(); diff --git a/src/server/ingen_portaudio.cpp b/src/server/ingen_portaudio.cpp new file mode 100644 index 00000000..92ccaa15 --- /dev/null +++ b/src/server/ingen_portaudio.cpp @@ -0,0 +1,55 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#include + +#include "ingen/Atom.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/Log.hpp" +#include "ingen/Module.hpp" +#include "ingen/World.hpp" + +#include "PortAudioDriver.hpp" +#include "Engine.hpp" + +using namespace std; +using namespace Ingen; + +struct IngenPortAudioModule : public Ingen::Module { + void load(Ingen::World* world) { + if (((Server::Engine*)world->engine().get())->driver()) { + world->log().warn("Engine already has a driver\n"); + return; + } + + Server::PortAudioDriver* driver = new Server::PortAudioDriver( + *(Server::Engine*)world->engine().get()); + driver->attach(); + ((Server::Engine*)world->engine().get())->set_driver( + SPtr(driver)); + } +}; + +extern "C" { + +Ingen::Module* +ingen_module_load() +{ + return new IngenPortAudioModule(); +} + +} // extern "C" diff --git a/src/server/wscript b/src/server/wscript index 43ba162a..a8efd2f8 100644 --- a/src/server/wscript +++ b/src/server/wscript @@ -91,6 +91,18 @@ def build(bld): linkflags = bld.env.PTHREAD_LINKFLAGS) autowaf.use_lib(bld, obj, core_libs + ' JACK') + if bld.env.HAVE_PORTAUDIO: + obj = bld(features = 'cxx cxxshlib', + source = 'PortAudioDriver.cpp ingen_portaudio.cpp', + includes = ['.', '../..'], + name = 'libingen_portaudio', + target = 'ingen_portaudio', + install_path = '${LIBDIR}', + use = 'libingen_server', + cxxflags = bld.env.PTHREAD_CFLAGS, + linkflags = bld.env.PTHREAD_LINKFLAGS) + autowaf.use_lib(bld, obj, core_libs + ' PORTAUDIO') + # Ingen LV2 wrapper if bld.env.INGEN_BUILD_LV2: obj = bld(features = 'cxx cxxshlib', diff --git a/tests/ingen_test.cpp b/tests/ingen_test.cpp index 1ac2e383..f5ba45c6 100644 --- a/tests/ingen_test.cpp +++ b/tests/ingen_test.cpp @@ -133,28 +133,11 @@ ingen_try(bool cond, const char* msg) } } -static uint32_t -flush_events(Ingen::World* world, const uint32_t start) -{ - static const uint32_t block_length = 4096; - int count = 0; - uint32_t offset = start; - while (world->engine()->pending_events()) { - world->engine()->locate(offset, block_length); - world->engine()->run(block_length); - world->engine()->main_iteration(); - g_usleep(1000); - ++count; - offset += block_length; - } - return offset; -} - int main(int argc, char** argv) { Glib::thread_init(); - set_bundle_path_from_code((void*)&flush_events); + set_bundle_path_from_code((void*)&ingen_try); // Create world try { @@ -194,12 +177,12 @@ main(int argc, char** argv) world->engine()->init(48000.0, 4096, 4096); world->engine()->activate(); - // Load patch + // Load graph if (!world->parser()->parse_file(world, world->interface().get(), start_graph)) { cerr << "error: failed to load initial graph " << start_graph << endl; return EXIT_FAILURE; } - uint32_t time = flush_events(world, 0); + world->engine()->flush_events(std::chrono::milliseconds(20)); // Read commands @@ -262,7 +245,7 @@ main(int argc, char** argv) return EXIT_FAILURE; } - time = flush_events(world, time); + world->engine()->flush_events(std::chrono::milliseconds(20)); } delete cmds; @@ -277,7 +260,7 @@ main(int argc, char** argv) // Undo every event (should result in a graph identical to the original) for (int i = 0; i < n_events; ++i) { world->interface()->undo(); - time = flush_events(world, time); + world->engine()->flush_events(std::chrono::milliseconds(20)); } // Save completely undone graph @@ -289,7 +272,7 @@ main(int argc, char** argv) // Redo every event (should result in a graph identical to the pre-undo output) for (int i = 0; i < n_events; ++i) { world->interface()->redo(); - time = flush_events(world, time); + world->engine()->flush_events(std::chrono::milliseconds(20)); } // Save completely redone graph diff --git a/wscript b/wscript index 538e2b84..46f9b686 100644 --- a/wscript +++ b/wscript @@ -44,6 +44,9 @@ def options(opt): help='Do not build Socket interface') opt.add_option('--debug-urids', action='store_true', dest='debug_urids', help='Print a trace of URI mapping') + opt.add_option('--portaudio', action='store_true', default=False, + dest='portaudio', + help='Build PortAudio backend') def configure(conf): autowaf.display_header('Ingen Configuration') @@ -90,6 +93,8 @@ def configure(conf): atleast_version='0.18.0', mandatory=False) autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD', atleast_version='0.12.0', mandatory=False) + autowaf.check_pkg(conf, 'portaudio-2.0', uselib_store='PORTAUDIO', + atleast_version='2.0.0', mandatory=False) conf.check(function_name = 'posix_memalign', defines = '_POSIX_C_SOURCE=200809L', -- cgit v1.2.1