summaryrefslogtreecommitdiffstats
path: root/src/server/PortAudioDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/PortAudioDriver.cpp')
-rw-r--r--src/server/PortAudioDriver.cpp297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/server/PortAudioDriver.cpp b/src/server/PortAudioDriver.cpp
new file mode 100644
index 00000000..f67a62bb
--- /dev/null
+++ b/src/server/PortAudioDriver.cpp
@@ -0,0 +1,297 @@
+/*
+ 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/atom/util.h"
+
+#include "Buffer.hpp"
+#include "DuplexPort.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "PortAudioDriver.hpp"
+#include "PortImpl.hpp"
+#include "FrameTimer.hpp"
+#include "ThreadManager.hpp"
+#include "util.hpp"
+
+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)
+ , _stream(nullptr)
+ , _seq_size(4096)
+ , _block_length(engine.world()->conf().option("buffer-size").get<int32_t>())
+ , _sample_rate(48000)
+ , _n_inputs(0)
+ , _n_outputs(0)
+ , _flag(false)
+ , _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);
+
+ /* TODO: It looks like it is somehow actually impossible to request the
+ best/native buffer size then retrieve what it actually is with
+ PortAudio. How such a glaring useless flaw exists in such a widespread
+ library is beyond me... */
+
+ _sample_rate = in_dev->defaultSampleRate;
+
+ _timer = std::unique_ptr<FrameTimer>(
+ new FrameTimer(_block_length, _sample_rate));
+
+ 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 = nullptr;
+ _outputParameters.sampleFormat = paFloat32|paNonInterleaved;
+ _outputParameters.suggestedLatency = out_dev->defaultLowOutputLatency;
+ _outputParameters.hostApiSpecificStreamInfo = nullptr;
+
+ // Open stream
+ PaError st = paNoError;
+ if ((st = Pa_OpenStream(
+ &_stream,
+ _inputParameters.channelCount ? &_inputParameters : nullptr,
+ _outputParameters.channelCount ? &_outputParameters : nullptr,
+ 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 _timer->frame_time(_engine.current_time()) + _engine.block_length();
+}
+
+EnginePort*
+PortAudioDriver::get_port(const Raul::Path& path)
+{
+ for (auto& p : _ports) {
+ if (p.graph_port()->path() == path) {
+ return &p;
+ }
+ }
+
+ return nullptr;
+}
+
+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 URI& uri,
+ const Atom& value)
+{
+}
+
+EnginePort*
+PortAudioDriver::create_port(DuplexPort* graph_port)
+{
+ EnginePort* eport = nullptr;
+ 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);
+ _timer->update(_engine.current_time(), _engine.run_context().start());
+
+ // 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