/*
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 "PortAudioDriver.hpp"
#include "DuplexPort.hpp"
#include "Engine.hpp"
#include "FrameTimer.hpp"
#include "PortType.hpp"
#include "RunContext.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ingen {
class URI;
} // namespace ingen
namespace ingen::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)
, _inputParameters()
, _outputParameters()
, _block_length(engine.world().conf().option("buffer-size").get())
{}
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);
}
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::make_unique(_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&, EnginePort* port)
{
_ports.push_back(*port);
}
void
PortAudioDriver::remove_port(RunContext&, 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&,
EnginePort* port,
const void* inputs,
void* outputs)
{
if (!port->graph_port()->is_a(PortType::AUDIO)) {
return;
}
if (port->is_input()) {
const auto* const* const ins =
static_cast(inputs);
port->set_buffer(const_cast(ins[port->driver_index()]));
} else {
auto* const* const outs = static_cast(inputs);
port->set_buffer(outs[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&,
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 ingen::server