And various internal changes and cleanups to support this.looper
parent
45f67ea7f6
commit
aa380e9c07
@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# @end:license
|
||||
|
||||
if [ -z "$VIRTUAL_ENV" -o ! -d "$VIRTUAL_ENV" ]; then
|
||||
echo >&2 "Not running in a virtualenv. Please set that up first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASEDIR=$(readlink -f "$(dirname "$0")/..")
|
||||
LIBSDIR="$BASEDIR/libs"
|
||||
|
||||
LILV_DEPS="libserd-dev libsord-dev libsratom-dev lv2-examples mda-lv2"
|
||||
LADSPA_DEPS="ladspa-sdk swh-plugins"
|
||||
CSOUND_DEPS="libsndfile1-dev libsamplerate0-dev libboost-dev flex bison cmake"
|
||||
CAPNP_DEPS="capnproto libcapnp-0.5.3 libcapnp-dev"
|
||||
|
||||
PYVERSION=3.5
|
||||
|
||||
PACKAGES_QT5="python3-pyqt5 python3-pyqt5.qtsvg"
|
||||
|
||||
PACKAGES="python$PYVERSION python$PYVERSION-venv python$PYVERSION-dev libxml2-dev libxslt1-dev portaudio19-dev libavutil-dev libavutil-ffmpeg54 libswresample-dev libswresample-ffmpeg1 libfluidsynth1 libfluidsynth-dev inkscape timgm6mb-soundfont fluid-soundfont-gs fluid-soundfont-gm flac zlib1g-dev $PACKAGES_QT5 $CSOUND_DEPS $LADSPA_DEPS $LILV_DEPS $CAPNP_DEPS"
|
||||
|
||||
function pkg-status() {
|
||||
PKG="$1"
|
||||
(
|
||||
dpkg-query -W -f='${Status}\n' "$PKG" 2>/dev/null || echo "foo bar not-installed"
|
||||
) | awk '{print $3}'
|
||||
}
|
||||
|
||||
function main() {
|
||||
set -e
|
||||
|
||||
############################################################################
|
||||
# check prerequisites
|
||||
|
||||
declare -a MISSING
|
||||
for PKG in $PACKAGES; do
|
||||
STATUS=
|
||||
if [ "$(pkg-status "$PKG")" != "installed" ]; then
|
||||
MISSING+=( "$PKG" )
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||
echo >&2 "Missing some packages: ${MISSING[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
############################################################################
|
||||
# install libraries
|
||||
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade -r $BASEDIR/requirements.txt
|
||||
}
|
||||
|
||||
main
|
@ -0,0 +1,363 @@
|
||||
/*
|
||||
* @begin:license
|
||||
*
|
||||
* Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* @end:license
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "noisicaa/core/logging.h"
|
||||
#include "noisicaa/core/perf_stats.h"
|
||||
#include "noisicaa/host_system/host_system.h"
|
||||
#include "noisicaa/audioproc/engine/csound_util.h"
|
||||
#include "noisicaa/audioproc/engine/rtcheck.h"
|
||||
|
||||
namespace noisicaa {
|
||||
|
||||
CSoundUtil::CSoundUtil(HostSystem* host_system)
|
||||
: _logger(LoggerRegistry::get_logger("noisicaa.audioproc.engine.csound_util")),
|
||||
_host_system(host_system) {}
|
||||
|
||||
CSoundUtil::~CSoundUtil() {
|
||||
if (_csnd != nullptr) {
|
||||
_logger->info("Destroying csound instance %p", _csnd);
|
||||
csoundDestroy(_csnd);
|
||||
}
|
||||
_event_input_ports.clear();
|
||||
}
|
||||
|
||||
void CSoundUtil::_log_cb(CSOUND* csnd, int attr, const char* fmt, va_list args) {
|
||||
CSoundUtil* proc = (CSoundUtil*)csoundGetHostData(csnd);
|
||||
assert(proc != nullptr);
|
||||
proc->_log_cb(attr, fmt, args);
|
||||
}
|
||||
|
||||
void CSoundUtil::_log_cb(int attr, const char* fmt, va_list args) {
|
||||
LogLevel level = LogLevel::INFO;
|
||||
switch (attr & CSOUNDMSG_TYPE_MASK) {
|
||||
case CSOUNDMSG_ORCH:
|
||||
case CSOUNDMSG_REALTIME:
|
||||
case CSOUNDMSG_DEFAULT:
|
||||
level = LogLevel::INFO;
|
||||
break;
|
||||
case CSOUNDMSG_WARNING:
|
||||
level = LogLevel::WARNING;
|
||||
break;
|
||||
case CSOUNDMSG_ERROR:
|
||||
level = LogLevel::ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t bytes_used = strlen(_log_buf);
|
||||
vsnprintf(_log_buf + bytes_used, sizeof(_log_buf) - bytes_used, fmt, args);
|
||||
|
||||
while (_log_buf[0]) {
|
||||
char *eol = strchr(_log_buf, '\n');
|
||||
if (eol == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
*eol = 0;
|
||||
_logger->log(level, "%s", _log_buf);
|
||||
|
||||
memmove(_log_buf, eol + 1, strlen(eol + 1) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Status CSoundUtil::setup(
|
||||
const string& orchestra, const string& score, const vector<PortSpec>& ports) {
|
||||
_ports = ports;
|
||||
|
||||
memset(_log_buf, 0, sizeof(_log_buf));
|
||||
_event_input_ports.resize(_ports.size());
|
||||
|
||||
_csnd = csoundCreate(this);
|
||||
if (_csnd == nullptr) {
|
||||
return ERROR_STATUS("Failed to create Csound instance.");
|
||||
}
|
||||
_logger->info("Created csound instance %p", _csnd);
|
||||
|
||||
csoundSetMessageCallback(_csnd, CSoundUtil::_log_cb);
|
||||
|
||||
int rc = csoundSetOption(_csnd, "-n");
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("Failed to set Csound options (code %d)", rc);
|
||||
}
|
||||
|
||||
_logger->info("csound orchestra:\n%s", orchestra.c_str());
|
||||
rc = csoundCompileOrc(_csnd, orchestra.c_str());
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("Failed to compile Csound orchestra (code %d)", rc);
|
||||
}
|
||||
|
||||
double zerodbfs = csoundGet0dBFS(_csnd);
|
||||
if (zerodbfs != 1.0) {
|
||||
return ERROR_STATUS("Csound orchestra must set 0dbfs=1.0 (found %f)", zerodbfs);
|
||||
}
|
||||
|
||||
rc = csoundStart(_csnd);
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("Failed to start Csound (code %d)", rc);
|
||||
}
|
||||
|
||||
_logger->info("csound score:\n%s", score.c_str());
|
||||
rc = csoundReadScore(_csnd, score.c_str());
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("Failed to read Csound score (code %d)", rc);
|
||||
}
|
||||
|
||||
_channel_ptr.resize(_ports.size());
|
||||
_channel_lock.resize(_ports.size());
|
||||
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
|
||||
const auto& port = _ports[port_idx];
|
||||
|
||||
if (port.type == pb::PortDescription::EVENTS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MYFLT* channel_ptr;
|
||||
int type = csoundGetChannelPtr(_csnd, &channel_ptr, port.name.c_str(), 0);
|
||||
if (type < 0) {
|
||||
return ERROR_STATUS("Orchestra does not define the channel '%s'", port.name.c_str());
|
||||
}
|
||||
|
||||
if (port.direction == pb::PortDescription::OUTPUT
|
||||
&& !(type & CSOUND_OUTPUT_CHANNEL)) {
|
||||
return ERROR_STATUS("Channel '%s' is not an output channel", port.name.c_str());
|
||||
}
|
||||
|
||||
if (port.direction == pb::PortDescription::INPUT
|
||||
&& !(type & CSOUND_INPUT_CHANNEL)) {
|
||||
return ERROR_STATUS("Channel '%s' is not an input channel", port.name.c_str());
|
||||
}
|
||||
|
||||
if (port.type == pb::PortDescription::AUDIO
|
||||
|| port.type == pb::PortDescription::ARATE_CONTROL) {
|
||||
if ((type & CSOUND_CHANNEL_TYPE_MASK) != CSOUND_AUDIO_CHANNEL) {
|
||||
return ERROR_STATUS("Channel '%s' is not an audio channel", port.name.c_str());
|
||||
}
|
||||
} else if (port.type == pb::PortDescription::KRATE_CONTROL) {
|
||||
if ((type & CSOUND_CHANNEL_TYPE_MASK) != CSOUND_CONTROL_CHANNEL) {
|
||||
return ERROR_STATUS("Channel '%s' is not an control channel", port.name.c_str());
|
||||
}
|
||||
} else {
|
||||
return ERROR_STATUS("Internal error, channel '%s' type %d", port.name.c_str(), port.type);
|
||||
}
|
||||
|
||||
int rc = csoundGetChannelPtr(_csnd, &channel_ptr, port.name.c_str(), type);
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("Failed to get channel pointer for port '%s'", port.name.c_str());
|
||||
}
|
||||
assert(channel_ptr != nullptr);
|
||||
|
||||
_channel_ptr[port_idx] = channel_ptr;
|
||||
_channel_lock[port_idx] = csoundGetChannelLock(_csnd, port.name.c_str());
|
||||
}
|
||||
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status CSoundUtil::process_block(
|
||||
BlockContext* ctxt, TimeMapper* time_mapper, vector<BufferPtr>& buffers) {
|
||||
assert(buffers.size() == (size_t)_ports.size());
|
||||
|
||||
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
|
||||
const auto& port = _ports[port_idx];
|
||||
if (port.direction == pb::PortDescription::INPUT
|
||||
&& port.type == pb::PortDescription::EVENTS) {
|
||||
LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)buffers[port_idx];
|
||||
if (seq->atom.type != _host_system->lv2->urid.atom_sequence) {
|
||||
return ERROR_STATUS(
|
||||
"Excepted sequence in port '%s', got %d.", port.name.c_str(), seq->atom.type);
|
||||
}
|
||||
LV2_Atom_Event* event = lv2_atom_sequence_begin(&seq->body);
|
||||
int instr = 1; // TODO: use port.csound_instr
|
||||
_event_input_ports[port_idx] = {seq, event, instr};
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t pos = 0;
|
||||
uint32_t ksmps = csoundGetKsmps(_csnd);
|
||||
while (pos < _host_system->block_size()) {
|
||||
// Copy input ports into Csound channels.
|
||||
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
|
||||
const auto& port = _ports[port_idx];
|
||||
if (port.direction == pb::PortDescription::INPUT) {
|
||||
switch (port.type) {
|
||||
case pb::PortDescription::AUDIO:
|
||||
case pb::PortDescription::ARATE_CONTROL: {
|
||||
float* buf = (float*)buffers[port_idx];
|
||||
buf += pos;
|
||||
|
||||
MYFLT* channel_ptr = _channel_ptr[port_idx];
|
||||
int *lock = _channel_lock[port_idx];
|
||||
csoundSpinLock(lock);
|
||||
for (uint32_t i = 0 ; i < ksmps ; ++i) {
|
||||
*channel_ptr++ = *buf++;
|
||||
}
|
||||
csoundSpinUnLock(lock);
|
||||
break;
|
||||
}
|
||||
|
||||
case pb::PortDescription::KRATE_CONTROL: {
|
||||
float* buf = (float*)buffers[port_idx];
|
||||
|
||||
MYFLT* channel_ptr = _channel_ptr[port_idx];
|
||||
int *lock = _channel_lock[port_idx];
|
||||
csoundSpinLock(lock);
|
||||
*channel_ptr = *buf;
|
||||
csoundSpinUnLock(lock);
|
||||
break;
|
||||
}
|
||||
|
||||
case pb::PortDescription::EVENTS: {
|
||||
EventInputPort &ep = _event_input_ports[port_idx];
|
||||
|
||||
// TODO: is instrument started with one ksmps delay? needs further testing.
|
||||
while (!lv2_atom_sequence_is_end(
|
||||
&ep.seq->body, ep.seq->atom.size, ep.event)
|
||||
&& ep.event->time.frames < pos + ksmps) {
|
||||
LV2_Atom& atom = ep.event->body;
|
||||
if (atom.type == _host_system->lv2->urid.midi_event) {
|
||||
uint8_t* midi = (uint8_t*)LV2_ATOM_CONTENTS(LV2_Atom, &atom);
|
||||
if ((midi[0] & 0xf0) == 0x90) {
|
||||
MYFLT p[5] = {
|
||||
/* p1: instr */ (MYFLT)ep.instr + (MYFLT)midi[1] / 1000.0,
|
||||
/* p2: time */ 0.0,
|
||||
/* p3: duration */ -1.0,
|
||||
/* p4: pitch */ (MYFLT)midi[1],
|
||||
/* p5: velocity */ (MYFLT)midi[2],
|
||||
};
|
||||
//_logger->info("i %f %f %f %f %f", p[0], p[1], p[2], p[3], p[4]);
|
||||
int rc = csoundScoreEvent(_csnd, 'i', p, 5);
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("csoundScoreEvent failed (code %d).", rc);
|
||||
}
|
||||
} else if ((midi[0] & 0xf0) == 0x80) {
|
||||
MYFLT p[3] = {
|
||||
/* p1: instr */ -((MYFLT)ep.instr + (MYFLT)midi[1] / 1000.0),
|
||||
/* p2: time */ 0.0,
|
||||
/* p3: duration */ 0.0,
|
||||
};
|
||||
//_logger->info("i %f %f %f", p[0], p[1], p[2]);
|
||||
int rc = csoundScoreEvent(_csnd, 'i', p, 3);
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("csoundScoreEvent failed (code %d).", rc);
|
||||
}
|
||||
} else {
|
||||
_logger->warning("Ignoring unsupported midi event %d.", midi[0] & 0xf0);
|
||||
}
|
||||
} else {
|
||||
_logger->warning("Ignoring event %d in sequence.", atom.type);
|
||||
}
|
||||
ep.event = lv2_atom_sequence_next(ep.event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return ERROR_STATUS("Port %s has unsupported type %d", port.name.c_str(), port.type);
|
||||
}
|
||||
} else {
|
||||
assert(port.direction == pb::PortDescription::OUTPUT);
|
||||
|
||||
switch (port.type) {
|
||||
case pb::PortDescription::AUDIO:
|
||||
case pb::PortDescription::ARATE_CONTROL: {
|
||||
MYFLT* channel_ptr = _channel_ptr[port_idx];
|
||||
int *lock = _channel_lock[port_idx];
|
||||
csoundSpinLock(lock);
|
||||
for (uint32_t i = 0 ; i < ksmps ; ++i) {
|
||||
*channel_ptr++ = 0.0;
|
||||
}
|
||||
csoundSpinUnLock(lock);
|
||||
break;
|
||||
}
|
||||
|
||||
case pb::PortDescription::KRATE_CONTROL: {
|
||||
MYFLT* channel_ptr = _channel_ptr[port_idx];
|
||||
int *lock = _channel_lock[port_idx];
|
||||
csoundSpinLock(lock);
|
||||
*channel_ptr = 0.0;
|
||||
csoundSpinUnLock(lock);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return ERROR_STATUS("Port %s has unsupported type %d", port.name.c_str(), port.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rc;
|
||||
{
|
||||
RTUnsafe rtu; // csound might do RT unsafe stuff internally.
|
||||
rc = csoundPerformKsmps(_csnd);
|
||||
}
|
||||
if (rc < 0) {
|
||||
return ERROR_STATUS("Csound performance failed (code %d)", rc);
|
||||
}
|
||||
|
||||
// Copy channel data from Csound into output ports.
|
||||
for (size_t port_idx = 0 ; port_idx < _ports.size() ; ++port_idx) {
|
||||
const auto& port = _ports[port_idx];
|
||||
if (port.direction == pb::PortDescription::OUTPUT) {
|
||||
switch (port.type) {
|
||||
case pb::PortDescription::AUDIO:
|
||||
case pb::PortDescription::ARATE_CONTROL: {
|
||||
float* buf = (float*)buffers[port_idx];
|
||||
buf += pos;
|
||||
|
||||
MYFLT* channel_ptr = _channel_ptr[port_idx];
|
||||
int *lock = _channel_lock[port_idx];
|
||||
csoundSpinLock(lock);
|
||||
for (uint32_t i = 0 ; i < ksmps ; ++i) {
|
||||
*buf++ = *channel_ptr++;
|
||||
}
|
||||
csoundSpinUnLock(lock);
|
||||
break;
|
||||
}
|
||||
|
||||
case pb::PortDescription::KRATE_CONTROL: {
|
||||
float* buf = (float*)buffers[port_idx];
|
||||
|
||||
MYFLT* channel_ptr = _channel_ptr[port_idx];
|
||||
int *lock = _channel_lock[port_idx];
|
||||
csoundSpinLock(lock);
|
||||
*buf = *channel_ptr;
|
||||
csoundSpinUnLock(lock);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return ERROR_STATUS("Port %s has unsupported type %d", port.name.c_str(), port.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += ksmps;
|
||||
}
|
||||
|
||||
assert(pos == _host_system->block_size());
|
||||
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
// -*- mode: c++ -*-
|
||||
|
||||
/*
|
||||
* @begin:license
|
||||
*
|
||||
* Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* @end:license
|
||||
*/
|
||||
|
||||
#ifndef _NOISICAA_AUDIOPROC_ENGINE_CSOUND_UTIL_H
|
||||
#define _NOISICAA_AUDIOPROC_ENGINE_CSOUND_UTIL_H
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include "csound/csound.h"
|
||||
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
|
||||
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
|
||||
#include "noisicaa/core/status.h"
|
||||
#include "noisicaa/node_db/node_description.pb.h"
|
||||
#include "noisicaa/audioproc/engine/buffers.h"
|
||||
|
||||
namespace noisicaa {
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Logger;
|
||||
class HostSystem;
|
||||
class BlockContext;
|
||||
class TimeMapper;
|
||||
|
||||
class CSoundUtil {
|
||||
public:
|
||||
CSoundUtil(HostSystem* host_system);
|
||||
~CSoundUtil();
|
||||
|
||||
struct PortSpec {
|
||||
string name;
|
||||
pb::PortDescription::Type type;
|
||||
pb::PortDescription::Direction direction;
|
||||
};
|
||||
|
||||
Status setup(const string& orchestra, const string& score, const vector<PortSpec>& ports);
|
||||
Status process_block(BlockContext* ctxt, TimeMapper* time_mapper, vector<BufferPtr>& buffers);
|
||||
|
||||
private:
|
||||
Logger* _logger;
|
||||
HostSystem* _host_system;
|
||||
|
||||
static void _log_cb(CSOUND* csnd, int attr, const char* fmt, va_list args);
|
||||
void _log_cb(int attr, const char* fmt, va_list args);
|
||||
char _log_buf[10240];
|
||||
|
||||
CSOUND* _csnd = nullptr;
|
||||
vector<MYFLT*> _channel_ptr;
|
||||
vector<int*> _channel_lock;
|
||||
|
||||
vector<PortSpec> _ports;
|
||||
|
||||
struct EventInputPort {
|
||||
LV2_Atom_Sequence* seq;
|
||||
LV2_Atom_Event* event;
|
||||
int instr;
|
||||
};
|
||||
vector<EventInputPort> _event_input_ports;
|
||||
};
|
||||
|
||||
} // namespace noisicaa
|
||||
|
||||
#endif
|