summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2017-02-18 18:32:02 +0100
committerDavid Robillard <d@drobilla.net>2017-02-18 19:38:13 +0100
commitf240b08a1799b3aa6a29701c90388d51c0c1b2ce (patch)
tree28ec7429aa10f28710023fdcc43d07a4f0b4ebc9
parent6141b832946006a6ec3c1a3a70bd729c0404771e (diff)
downloadingen-f240b08a1799b3aa6a29701c90388d51c0c1b2ce.tar.gz
ingen-f240b08a1799b3aa6a29701c90388d51c0c1b2ce.tar.bz2
ingen-f240b08a1799b3aa6a29701c90388d51c0c1b2ce.zip
Add experimental PortAudio driver
-rw-r--r--ingen/EngineBase.hpp18
-rw-r--r--ingen/ingen.h10
-rw-r--r--src/ingen/ingen.cpp15
-rw-r--r--src/server/DirectDriver.hpp4
-rw-r--r--src/server/Driver.hpp9
-rw-r--r--src/server/Engine.cpp88
-rw-r--r--src/server/Engine.hpp12
-rw-r--r--src/server/EnginePort.hpp16
-rw-r--r--src/server/JackDriver.cpp12
-rw-r--r--src/server/JackDriver.hpp6
-rw-r--r--src/server/PortAudioDriver.cpp289
-rw-r--r--src/server/PortAudioDriver.hpp127
-rw-r--r--src/server/events/CreateGraph.cpp9
-rw-r--r--src/server/events/CreatePort.cpp3
-rw-r--r--src/server/ingen_portaudio.cpp55
-rw-r--r--src/server/wscript12
-rw-r--r--tests/ingen_test.cpp29
-rw-r--r--wscript5
18 files changed, 627 insertions, 92 deletions
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 <http://drobilla.net/>
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
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 <stdint.h>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
-#include "raul/URI.hpp"
#include "ingen/ingen.h"
#include "ingen/types.hpp"
@@ -68,6 +69,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.
*/
virtual void locate(uint32_t start, uint32_t sample_count) = 0;
@@ -113,7 +124,6 @@ public:
Unregister a client.
*/
virtual bool unregister_client(SPtr<Interface> 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 <http://drobilla.net/>
+ Copyright 2014-2017 David Robillard <http://drobilla.net/>
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 <http://drobilla.net/>
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
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 <http://drobilla.net/>
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
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 <http://drobilla.net/>
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
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 <sys/mman.h>
#include <limits>
+#include <thread>
#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<int32_t>())
+ , _activated(false)
{
if (!world->store()) {
world->set_store(SPtr<Ingen::Store>(new Store()));
@@ -206,6 +208,14 @@ Engine::listen()
}
void
+Engine::advance(SampleCount nframes)
+{
+ for (RunContext* ctx : _run_contexts) {
+ ctx->locate(ctx->start() + nframes, block_length());
+ }
+}
+
+void
Engine::locate(FrameTime s, SampleCount nframes)
{
for (RunContext* ctx : _run_contexts) {
@@ -214,6 +224,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)
{
for (RunContext* ctx : _run_contexts) {
@@ -338,6 +367,11 @@ Engine::set_driver(SPtr<Driver> 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<Interface>(), -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<Interface>(), -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 <boost/utility.hpp>
+#include <chrono>
#include <condition_variable>
#include <mutex>
#include <random>
@@ -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<char>(),
world->conf().option("jack-name").ptr<char>(), 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<char>());
}
+ 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 <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen_config.h"
+
+#include <cstdlib>
+#include <string>
+
+#include <portaudio.h>
+
+#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 <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_ENGINE_PORTAUDIODRIVER_HPP
+#define INGEN_ENGINE_PORTAUDIODRIVER_HPP
+
+#include "ingen_config.h"
+
+#include <atomic>
+#include <portaudio.h>
+#include <string>
+
+#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<EnginePort> Ports;
+
+ Engine& _engine;
+ Ports _ports;
+ PaStreamParameters _inputParameters;
+ PaStreamParameters _outputParameters;
+ Raul::Semaphore _sem;
+ std::atomic<bool> _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<GraphImpl*>(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 <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+
+#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<Server::Driver>(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',