Reorganize code to make creating new node types easier.

Adding new node types required adding bits and pieces in many places, which was tedious and failure
prone.
Now it's just cloning the directory of one of the existing nodes and adding a few lines to
'registries' (all in one directory).

Also:
- Reanimate custom csound node.
- All nodes can be muted.
looper
Ben Niemann 4 years ago
parent aa380e9c07
commit 21ec283822

@ -8716,6 +8716,7 @@ class QTabWidget(QWidget):
class QTextEdit(QAbstractScrollArea):
textChanged = ... # type: PYQT_SIGNAL
class AutoFormattingFlag(int): ...
AutoNone = ... # type: 'QTextEdit.AutoFormattingFlag'
@ -8797,7 +8798,6 @@ class QTextEdit(QAbstractScrollArea):
def currentCharFormatChanged(self, format: QtGui.QTextCharFormat) -> None: ...
def redoAvailable(self, b: bool) -> None: ...
def undoAvailable(self, b: bool) -> None: ...
def textChanged(self) -> None: ...
def zoomOut(self, range: int = ...) -> None: ...
def zoomIn(self, range: int = ...) -> None: ...
def undo(self) -> None: ...

@ -41,3 +41,4 @@ add_subdirectory(model)
add_subdirectory(music)
add_subdirectory(node_db)
add_subdirectory(ui)
add_subdirectory(builtin_nodes)

@ -84,7 +84,7 @@ class AudioProcClientTest(
),
],
processor=node_db.ProcessorDescription(
type=node_db.ProcessorDescription.NULLPROC,
type='builtin://null',
),
)

@ -39,13 +39,8 @@ add_python_package(
processor.pyi
processor_test.py
processor_csound_test.py
processor_cvgenerator_test.py
processor_pianoroll_test.py
processor_plugin_test.py
processor_instrument_test.py
processor_sample_script_test.py
processor_sound_file_test.py
processor_mixer_test.py
profile.pyi
realm.pyi
realm_test.py
@ -80,14 +75,8 @@ set(LIB_SRCS
processor_null.cpp
processor_csound_base.cpp
processor_csound.cpp
processor_custom_csound.cpp
processor_plugin.cpp
processor_instrument.cpp
processor_sound_file.cpp
processor_mixer.cpp
processor_pianoroll.cpp
processor_cvgenerator.cpp
processor_sample_script.cpp
profile.cpp
realtime.cpp
spec.cpp
@ -118,6 +107,7 @@ target_link_libraries(noisicaa-audioproc-engine PRIVATE pthread)
target_link_libraries(noisicaa-audioproc-engine PRIVATE rt)
target_link_libraries(noisicaa-audioproc-engine PRIVATE profiler)
target_link_libraries(noisicaa-audioproc-engine PRIVATE rtcheck)
target_link_libraries(noisicaa-audioproc-engine PRIVATE noisicaa-builtin_nodes-processors)
target_include_directories(noisicaa-audioproc-engine PUBLIC ${LIBLILV_INCLUDE_DIRS})
target_link_libraries(noisicaa-audioproc-engine PRIVATE ${LIBLILV_LIBRARIES})
target_include_directories(noisicaa-audioproc-engine PRIVATE ${LIBCSOUND_INCLUDE_DIRS})

@ -31,9 +31,10 @@
namespace noisicaa {
CSoundUtil::CSoundUtil(HostSystem* host_system)
CSoundUtil::CSoundUtil(HostSystem* host_system, function<void(LogLevel, const char*)> log_func)
: _logger(LoggerRegistry::get_logger("noisicaa.audioproc.engine.csound_util")),
_host_system(host_system) {}
_host_system(host_system),
_log_func(log_func) {}
CSoundUtil::~CSoundUtil() {
if (_csnd != nullptr) {
@ -75,7 +76,7 @@ void CSoundUtil::_log_cb(int attr, const char* fmt, va_list args) {
}
*eol = 0;
_logger->log(level, "%s", _log_buf);
_log_func(level, _log_buf);
memmove(_log_buf, eol + 1, strlen(eol + 1) + 1);
}
@ -246,6 +247,7 @@ Status CSoundUtil::process_block(
/* p5: velocity */ (MYFLT)midi[2],
};
//_logger->info("i %f %f %f %f %f", p[0], p[1], p[2], p[3], p[4]);
RTUnsafe rtu; // csound might do RT unsafe stuff internally.
int rc = csoundScoreEvent(_csnd, 'i', p, 5);
if (rc < 0) {
return ERROR_STATUS("csoundScoreEvent failed (code %d).", rc);
@ -257,6 +259,7 @@ Status CSoundUtil::process_block(
/* p3: duration */ 0.0,
};
//_logger->info("i %f %f %f", p[0], p[1], p[2]);
RTUnsafe rtu; // csound might do RT unsafe stuff internally.
int rc = csoundScoreEvent(_csnd, 'i', p, 3);
if (rc < 0) {
return ERROR_STATUS("csoundScoreEvent failed (code %d).", rc);

@ -26,12 +26,14 @@
#define _NOISICAA_AUDIOPROC_ENGINE_CSOUND_UTIL_H
#include <atomic>
#include <functional>
#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/logging.h"
#include "noisicaa/core/status.h"
#include "noisicaa/node_db/node_description.pb.h"
#include "noisicaa/audioproc/engine/buffers.h"
@ -40,14 +42,13 @@ namespace noisicaa {
using namespace std;
class Logger;
class HostSystem;
class BlockContext;
class TimeMapper;
class CSoundUtil {
public:
CSoundUtil(HostSystem* host_system);
CSoundUtil(HostSystem* host_system, function<void(LogLevel, const char*)> log_func);
~CSoundUtil();
struct PortSpec {
@ -62,6 +63,7 @@ public:
private:
Logger* _logger;
HostSystem* _host_system;
function<void(LogLevel, const char*)> _log_func;
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);

@ -241,12 +241,8 @@ cdef class PyEngine(object):
player=player,
callback_address=callback_address)
self.__realms[name] = realm
self.__realm_listeners['%s:node_state_changed' % name] = realm.node_state_changed.add(
lambda node_id, state: self.notifications.call(engine_notification_pb2.EngineNotification(
node_state_changes=[engine_notification_pb2.NodeStateChange(
realm=name,
node_id=node_id,
state=state)])))
self.__realm_listeners['%s:notifications' % name] = realm.notifications.add(
self.notifications.call)
if parent is None:
self.__root_realm = realm
@ -263,7 +259,7 @@ cdef class PyEngine(object):
if realm.parent is not None:
realm.parent.child_realms.pop(name)
del self.__realms[name]
self.__realm_listeners.pop('%s:node_state_changed' % name).remove()
self.__realm_listeners.pop('%s:notifications' % name).remove()
await realm.cleanup()
def get_buffer(self, name, type):

@ -367,18 +367,14 @@ class Node(object):
spec.append_opcode('MIX', upstream_port.buf_name, port.buf_name)
def add_to_spec_post(self, spec: spec_lib.PySpec) -> None:
for port_idx, port in enumerate(self.ports):
if isinstance(port, AudioOutputPort):
spec.append_opcode('POST_RMS', self.id, port_idx, port.buf_name)
elif isinstance(port, EventOutputPort):
spec.append_opcode('LOG_ATOM', port.buf_name)
pass
class ProcessorNode(Node):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.__processor = processor_lib.PyProcessor(self.id, self._host_system, self.description)
self.__processor = None # type: processor_lib.PyProcessor
@property
def processor(self) -> processor_lib.PyProcessor:
@ -388,6 +384,8 @@ class ProcessorNode(Node):
async def setup(self) -> None:
await super().setup()
self.__processor = processor_lib.PyProcessor(
self.realm.name, self.id, self._host_system, self.description)
self.__processor.setup()
self.realm.add_active_processor(self.__processor)
@ -464,11 +462,6 @@ class RealmSinkNode(Node):
def __init__(self, **kwargs: Any) -> None:
super().__init__(id='sink', **kwargs)
def add_to_spec_pre(self, spec: spec_lib.PySpec) -> None:
super().add_to_spec_pre(spec)
spec.append_opcode('POST_RMS', self.id, 0, self.inputs['in:left'].buf_name)
spec.append_opcode('POST_RMS', self.id, 1, self.inputs['in:right'].buf_name)
class ChildRealmNode(Node):
def __init__(self, *, child_realm: str, **kwargs: Any) -> None:

@ -119,18 +119,22 @@ Status run_POST_RMS(BlockContext* ctxt, ProgramState* state, const vector<OpArg>
float rms = sqrtf(sum / state->host_system->block_size());
uint8_t atom[100];
uint8_t atom[200];
LV2_Atom_Forge forge;
lv2_atom_forge_init(&forge, &state->host_system->lv2->urid_map);
lv2_atom_forge_set_buffer(&forge, atom, sizeof(atom));
LV2_Atom_Forge_Frame frame;
lv2_atom_forge_push(
&forge, &frame,
lv2_atom_forge_atom(&forge, 0, state->host_system->lv2->urid.core_portrms));
LV2_Atom_Forge_Frame oframe;
lv2_atom_forge_object(&forge, &oframe, state->host_system->lv2->urid.core_nodemsg, 0);
lv2_atom_forge_key(&forge, state->host_system->lv2->urid.core_portrms);
LV2_Atom_Forge_Frame tframe;
lv2_atom_forge_tuple(&forge, &tframe);
lv2_atom_forge_int(&forge, port_index);
lv2_atom_forge_float(&forge, rms);
lv2_atom_forge_pop(&forge, &frame);
lv2_atom_forge_pop(&forge, &tframe);
lv2_atom_forge_pop(&forge, &oframe);
NodeMessage::push(ctxt->out_messages, node_id, (LV2_Atom*)atom);

@ -27,36 +27,36 @@
#include "noisicaa/core/slots.inl.h"
#include "noisicaa/host_system/host_system.h"
#include "noisicaa/audioproc/public/engine_notification.pb.h"
#include "noisicaa/audioproc/engine/rtcheck.h"
#include "noisicaa/audioproc/public/processor_message.pb.h"
#include "noisicaa/audioproc/engine/processor.pb.h"
#include "noisicaa/audioproc/engine/processor.h"
#include "noisicaa/audioproc/engine/processor_null.h"
#include "noisicaa/audioproc/engine/processor_csound.h"
#include "noisicaa/audioproc/engine/processor_custom_csound.h"
#include "noisicaa/audioproc/engine/processor_instrument.h"
#include "noisicaa/audioproc/engine/processor_plugin.h"
#include "noisicaa/audioproc/engine/processor_sound_file.h"
#include "noisicaa/audioproc/engine/processor_mixer.h"
#include "noisicaa/audioproc/engine/processor_pianoroll.h"
#include "noisicaa/audioproc/engine/processor_cvgenerator.h"
#include "noisicaa/audioproc/engine/processor_sample_script.h"
#include "noisicaa/builtin_nodes/processor_registry.h"
namespace noisicaa {
Processor::Processor(
const string& node_id, const char* logger_name, HostSystem* host_system,
const pb::NodeDescription& desc)
const string& realm_name, const string& node_id, const char* logger_name,
HostSystem* host_system, const pb::NodeDescription& desc)
: _logger(LoggerRegistry::get_logger(logger_name)),
_host_system(host_system),
_id(Processor::new_id()),
_realm_name(realm_name),
_node_id(node_id),
_desc(desc),
_muted(false),
_state(ProcessorState::INACTIVE) {}
Processor::~Processor() {}
StatusOr<Processor*> Processor::create(
const string& node_id, HostSystem* host_system, const string& desc_serialized) {
const string& realm_name, const string& node_id, HostSystem* host_system,
const string& desc_serialized) {
pb::NodeDescription desc;
if (!desc.ParseFromString(desc_serialized)) {
return ERROR_STATUS("Failed to parse NodeDescription proto.");
@ -64,40 +64,20 @@ StatusOr<Processor*> Processor::create(
assert(desc.has_processor());
switch (desc.processor().type()) {
case pb::ProcessorDescription::NULLPROC:
if (desc.processor().type() == "builtin://null") {
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorNull(node_id, host_system, desc);
case pb::ProcessorDescription::CSOUND:
return new ProcessorNull(realm_name, node_id, host_system, desc);
} else if (desc.processor().type() == "builtin://csound") {
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorCSound(node_id, host_system, desc);
case pb::ProcessorDescription::CUSTOM_CSOUND:
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorCustomCSound(node_id, host_system, desc);
case pb::ProcessorDescription::PLUGIN:
return new ProcessorCSound(realm_name, node_id, host_system, desc);
} else if (desc.processor().type() == "builtin://plugin") {
assert(desc.type() == pb::NodeDescription::PLUGIN);
return new ProcessorPlugin(node_id, host_system, desc);
case pb::ProcessorDescription::INSTRUMENT:
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorInstrument(node_id, host_system, desc);
case pb::ProcessorDescription::SOUND_FILE:
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorSoundFile(node_id, host_system, desc);
case pb::ProcessorDescription::MIXER:
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorMixer(node_id, host_system, desc);
case pb::ProcessorDescription::PIANOROLL:
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorPianoRoll(node_id, host_system, desc);
case pb::ProcessorDescription::CV_GENERATOR:
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorCVGenerator(node_id, host_system, desc);
case pb::ProcessorDescription::SAMPLE_SCRIPT:
return new ProcessorPlugin(realm_name, node_id, host_system, desc);
} else if (desc.processor().type() == "builtin://sound-file") {
assert(desc.type() == pb::NodeDescription::PROCESSOR);
return new ProcessorSampleScript(node_id, host_system, desc);
default:
return ERROR_STATUS("Invalid processor type %d", desc.processor().type());
return new ProcessorSoundFile(realm_name, node_id, host_system, desc);
} else {
return create_processor(realm_name, node_id, host_system, desc);
}
}
@ -124,7 +104,19 @@ void Processor::set_state(ProcessorState state) {
_logger->info("Processor %llx: State %s -> %s", id(), state_name(_state), state_name(state));
_state = state;
state_changed.emit(_state);
pb::EngineNotification notification;
auto nsc = notification.add_node_state_changes();
nsc->set_realm(_realm_name);
nsc->set_node_id(_node_id);
switch (_state) {
case ProcessorState::INACTIVE: nsc->set_state(pb::NodeStateChange::INACTIVE); break;
case ProcessorState::SETUP: nsc->set_state(pb::NodeStateChange::SETUP); break;
case ProcessorState::RUNNING: nsc->set_state(pb::NodeStateChange::RUNNING); break;
case ProcessorState::BROKEN: nsc->set_state(pb::NodeStateChange::BROKEN); break;
case ProcessorState::CLEANUP: nsc->set_state(pb::NodeStateChange::CLEANUP); break;
}
notifications.emit(notification);
}
Status Processor::setup() {
@ -169,7 +161,13 @@ Status Processor::handle_message(const string& msg_serialized) {
Status Processor::handle_message_internal(pb::ProcessorMessage* msg) {
unique_ptr<pb::ProcessorMessage> msg_ptr(msg);
return ERROR_STATUS("Processor %llx: Unhandled message.", id());
if (msg->has_mute_node()) {
_muted.exchange(msg->mute_node().muted());
return Status::Ok();
}
return ERROR_STATUS("Processor %llx: Unhandled message.", id());
}
Status Processor::set_parameters(const string& parameters_serialized) {
@ -194,6 +192,7 @@ void Processor::connect_port(BlockContext* ctxt, uint32_t port_idx, BufferPtr bu
if (status.is_error()) {
_logger->error(
"Processor %llx: connect_port(%u) failed: %s", id(), port_idx, status.message());
RTUnsafe rtu; // We just crashed... doesn't matter we're now calling unsafe callbacks.
set_state(ProcessorState::BROKEN);
}
}
@ -204,14 +203,28 @@ void Processor::process_block(BlockContext* ctxt, TimeMapper* time_mapper) {
Status status = process_block_internal(ctxt, time_mapper);
if (status.is_error()) {
_logger->error("Processor %llx: process_block() failed: %s", id(), status.message());
RTUnsafe rtu; // We just crashed... doesn't matter we're now calling unsafe callbacks.
set_state(ProcessorState::BROKEN);
}
}
if (state() != ProcessorState::RUNNING) {
// Processor is broken, just clear all outputs.
if (state() != ProcessorState::RUNNING || _muted.load()) {
// Processor is muted or broken, just clear all outputs.
clear_all_outputs();
}
if (state() == ProcessorState::RUNNING) {
Status status = post_process_block_internal(ctxt, time_mapper);
if (status.is_error()) {
_logger->error("Processor %llx: post_process_block() failed: %s", id(), status.message());
RTUnsafe rtu; // We just crashed... doesn't matter we're now calling unsafe callbacks.
set_state(ProcessorState::BROKEN);
}
}
}
Status Processor::post_process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper) {
return Status::Ok();
}
void Processor::clear_all_outputs() {

@ -25,6 +25,7 @@
#ifndef _NOISICAA_AUDIOPROC_ENGINE_PROCESSOR_H
#define _NOISICAA_AUDIOPROC_ENGINE_PROCESSOR_H
#include <atomic>
#include <map>
#include <memory>
#include <string>
@ -47,6 +48,7 @@ class HostSystem;
class NotificationQueue;
class TimeMapper;
namespace pb {
class EngineNotification;
class ProcessorMessage;
class ProcessorParameters;
}
@ -63,14 +65,17 @@ enum ProcessorState {
class Processor : public RefCounted {
public:
Processor(
const string& node_id, const char* logger_name, HostSystem* host_system,
const string& realm_name, const string& node_id, const char* logger_name,
HostSystem* host_system,
const pb::NodeDescription& desc);
virtual ~Processor();
static StatusOr<Processor*> create(
const string& node_id, HostSystem* host_system, const string& desc_serialized);
const string& realm_name, const string& node_id, HostSystem* host_system,
const string& desc_serialized);
uint64_t id() const { return _id; }
const string& realm_name() const { return _realm_name; }
const string& node_id() const { return _node_id; }
ProcessorState state() const { return _state; }
static const char* state_name(ProcessorState state);
@ -84,7 +89,7 @@ public:
void connect_port(BlockContext* ctxt, uint32_t port_idx, BufferPtr buf);
void process_block(BlockContext* ctxt, TimeMapper* time_mapper);
Slot<ProcessorState> state_changed;
Slot<pb::EngineNotification> notifications;
protected:
void set_state(ProcessorState state);
@ -96,14 +101,17 @@ protected:
virtual Status set_parameters_internal(const pb::ProcessorParameters& parameters);
virtual Status connect_port_internal(BlockContext* ctxt, uint32_t port_idx, BufferPtr buf) = 0;
virtual Status process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper) = 0;
virtual Status post_process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper);
void clear_all_outputs();
Logger* _logger;
HostSystem* _host_system;
uint64_t _id;
string _realm_name;
string _node_id;
pb::NodeDescription _desc;
atomic<bool> _muted;
private:
static uint64_t new_id();

@ -42,7 +42,8 @@ cdef extern from "noisicaa/audioproc/engine/processor.h" namespace "noisicaa" no
cppclass Processor(RefCounted):
@staticmethod
StatusOr[Processor*] create(
const string& node_id, HostSystem* host_system, const string& desc_serialized)
const string& realm_name, const string& node_id, HostSystem* host_system,
const string& desc_serialized)
uint64_t id() const
const string& node_id() const

@ -37,7 +37,7 @@ class State(enum.Enum):
class PyProcessor(object):
def __init__(
self, node_id: str, host_system: host_system_lib.HostSystem,
self, realm_name: str, node_id: str, host_system: host_system_lib.HostSystem,
node_description: node_db.NodeDescription) -> None: ...
@property
def id(self) -> int: ...

@ -40,7 +40,9 @@ class State(enum.Enum):
cdef class PyProcessor(object):
def __init__(self, node_id, PyHostSystem host_system, node_description):
def __init__(self, realm_name, node_id, PyHostSystem host_system, node_description):
if isinstance(realm_name, str):
realm_name = realm_name.encode('utf-8')
if isinstance(node_id, str):
node_id = node_id.encode('utf-8')
assert isinstance(node_id, bytes)
@ -48,7 +50,7 @@ cdef class PyProcessor(object):
self.__desc = node_description
cdef StatusOr[Processor*] stor_processor = Processor.create(
node_id, host_system.get(), self.__desc.SerializeToString())
realm_name, node_id, host_system.get(), self.__desc.SerializeToString())
check(stor_processor)
self.__processor = stor_processor.result()
self.__processor.incref()
@ -104,7 +106,8 @@ cdef class PyProcessor(object):
self.__processor.process_block(ctxt.get(), c_time_mapper)
finally:
rtcheck.enable_rt_checker(0)
assert rtcheck.rt_checker_violations() == 0
assert rtcheck.rt_checker_violations() == 0, \
"%d RT violation(s) seen" % rtcheck.rt_checker_violations()
def handle_message(self, msg):
cdef string msg_serialized = msg.SerializeToString()

@ -25,8 +25,10 @@
namespace noisicaa {
ProcessorCSound::ProcessorCSound(
const string& node_id, HostSystem *host_system, const pb::NodeDescription& desc)
: ProcessorCSoundBase(node_id, "noisicaa.audioproc.engine.processor.csound", host_system, desc) {}
const string& realm_name, const string& node_id, HostSystem *host_system,
const pb::NodeDescription& desc)
: ProcessorCSoundBase(
realm_name, node_id, "noisicaa.audioproc.engine.processor.csound", host_system, desc) {}
Status ProcessorCSound::setup_internal() {
RETURN_IF_ERROR(ProcessorCSoundBase::setup_internal());

@ -36,7 +36,9 @@ class HostSystem;
class ProcessorCSound : public ProcessorCSoundBase {
public:
ProcessorCSound(const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc);
ProcessorCSound(
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc);
protected:
Status setup_internal() override;

@ -32,9 +32,9 @@
namespace noisicaa {
ProcessorCSoundBase::ProcessorCSoundBase(
const string& node_id, const char* logger_name, HostSystem* host_system,
const pb::NodeDescription& desc)
: Processor(node_id, logger_name, host_system, desc),
const string& realm_name, const string& node_id, const char* logger_name,
HostSystem* host_system, const pb::NodeDescription& desc)
: Processor(realm_name, node_id, logger_name, host_system, desc),
_next_instance(nullptr),
_current_instance(nullptr),
_old_instance(nullptr) {}
@ -55,7 +55,10 @@ Status ProcessorCSoundBase::set_code(const string& orchestra, const string& scor
}
// Create the next instance.
unique_ptr<CSoundUtil> instance(new CSoundUtil(_host_system));
unique_ptr<CSoundUtil> instance(
new CSoundUtil(
_host_system,
bind(&ProcessorCSoundBase::handle_csound_log, this, placeholders::_1, placeholders::_2)));
vector<CSoundUtil::PortSpec> ports;
for (const auto& port : _desc.ports()) {
@ -136,4 +139,8 @@ Status ProcessorCSoundBase::process_block_internal(BlockContext* ctxt, TimeMappe
return instance->process_block(ctxt, time_mapper, _buffers);
}
void ProcessorCSoundBase::handle_csound_log(LogLevel level, const char* msg) {
_logger->log(level, "%s", msg);
}
}

@ -43,8 +43,8 @@ class CSoundUtil;
class ProcessorCSoundBase : public Processor {
public:
ProcessorCSoundBase(
const string& node_id, const char* logger_name, HostSystem* host_system,
const pb::NodeDescription& desc);
const string& realm_name, const string& node_id, const char* logger_name,
HostSystem* host_system, const pb::NodeDescription& desc);
~ProcessorCSoundBase() override;
protected:
@ -52,6 +52,7 @@ protected:
void cleanup_internal() override;
Status connect_port_internal(BlockContext* ctxt, uint32_t port_idx, BufferPtr buf) override;
Status process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper) override;
virtual void handle_csound_log(LogLevel log_level, const char* msg);
Status set_code(const string& orchestra, const string& score);

@ -56,7 +56,7 @@ class ProcessorCsoundTestMixin(
),
],
processor=node_db.ProcessorDescription(
type=node_db.ProcessorDescription.CSOUND,
type='builtin://csound',
),
csound=node_db.CSoundDescription(
orchestra=textwrap.dedent("""\
@ -77,7 +77,7 @@ class ProcessorCsoundTestMixin(
),
)
proc = processor.PyProcessor('test_node', self.host_system, node_description)
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_description)
proc.setup()
buffer_mgr = unittest_engine_utils.BufferManager(self.host_system)
@ -120,7 +120,7 @@ class ProcessorCsoundTestMixin(
),
],
processor=node_db.ProcessorDescription(
type=node_db.ProcessorDescription.CSOUND,
type='builtin://csound',
),
csound=node_db.CSoundDescription(
orchestra=textwrap.dedent("""\
@ -145,7 +145,7 @@ class ProcessorCsoundTestMixin(
),
)
proc = processor.PyProcessor('test_node', self.host_system, node_description)
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_description)
proc.setup()
buffer_mgr = unittest_engine_utils.BufferManager(self.host_system)

@ -1,112 +0,0 @@
/*
* @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 <string>
#include <ctype.h>
#include <stdint.h>
#include "noisicaa/core/status.h"
#include "noisicaa/audioproc/engine/misc.h"
#include "noisicaa/audioproc/engine/processor_custom_csound.h"
namespace {
using namespace std;
string port_name_to_csound_label(const string &port_name) {
string result;
bool was_alpha = false;
for (const auto& c : port_name) {
bool is_alpha = isalpha(c);
if (is_alpha && !was_alpha) {
result += toupper(c);
} else if (is_alpha) {
result += c;
}
was_alpha = is_alpha;
}
return result;
}
} // namespace
namespace noisicaa {
ProcessorCustomCSound::ProcessorCustomCSound(
const string& node_id, HostSystem *host_system, const pb::NodeDescription& desc)
: ProcessorCSoundBase(
node_id, "noisicaa.audioproc.engine.processor.custom_csound", host_system, desc) {}
Status ProcessorCustomCSound::setup_internal() {
RETURN_IF_ERROR(ProcessorCSoundBase::setup_internal());
if (!_desc.has_csound()) {
return ERROR_STATUS("NodeDescription misses csound field.");
}
string orchestra_preamble = "0dbfs = 1.0\nksmps = 32\nnchnls = 2\n";
for (int i = 0 ; i < _desc.ports_size() ; ++i) {
const auto& port = _desc.ports(i);
if (port.type() == pb::PortDescription::AUDIO
&& port.direction() == pb::PortDescription::INPUT) {
orchestra_preamble += sprintf(
"ga%s chnexport \"%s\", 1\n",
port_name_to_csound_label(port.name()).c_str(),
port.name().c_str());
} else if (port.type() == pb::PortDescription::AUDIO
&& port.direction() == pb::PortDescription::OUTPUT) {
orchestra_preamble += sprintf(
"ga%s chnexport \"%s\", 2\n",
port_name_to_csound_label(port.name()).c_str(),
port.name().c_str());
} else if (port.type() == pb::PortDescription::ARATE_CONTROL
&& port.direction() == pb::PortDescription::INPUT) {
orchestra_preamble += sprintf(
"ga%s chnexport \"%s\", 1\n",
port_name_to_csound_label(port.name()).c_str(),
port.name().c_str());
} else if (port.type() == pb::PortDescription::ARATE_CONTROL
&& port.direction() == pb::PortDescription::OUTPUT) {
orchestra_preamble += sprintf(
"ga%s chnexport \"%s\", 2\n",
port_name_to_csound_label(port.name()).c_str(),
port.name().c_str());
} else if (port.type() == pb::PortDescription::EVENTS
&& port.direction() == pb::PortDescription::INPUT) {
} else {
return ERROR_STATUS("Port %s not supported", port.name().c_str());
}
}
RETURN_IF_ERROR(
set_code(orchestra_preamble + _desc.csound().orchestra(), _desc.csound().score()));
return Status::Ok();
}
void ProcessorCustomCSound::cleanup_internal() {
ProcessorCSoundBase::cleanup_internal();
}
}

@ -25,8 +25,10 @@
namespace noisicaa {
ProcessorNull::ProcessorNull(
const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc)
: Processor(node_id, "noisicaa.audioproc.engine.processor.null", host_system, desc) {}
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc)
: Processor(
realm_name, node_id, "noisicaa.audioproc.engine.processor.null", host_system, desc) {}
ProcessorNull::~ProcessorNull() {}
Status ProcessorNull::setup_internal() {

@ -37,7 +37,9 @@ class HostSystem;
class ProcessorNull : public Processor {
public:
ProcessorNull(const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc);
ProcessorNull(
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc);
~ProcessorNull() override;
protected:

@ -39,8 +39,10 @@
namespace noisicaa {
ProcessorPlugin::ProcessorPlugin(
const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc)
: Processor(node_id, "noisicaa.audioproc.engine.processor.plugin", host_system, desc) {}
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc)
: Processor(
realm_name, node_id, "noisicaa.audioproc.engine.processor.plugin", host_system, desc) {}
Status ProcessorPlugin::setup_internal() {
return Processor::setup_internal();

@ -42,7 +42,9 @@ class HostSystem;
class ProcessorPlugin : public Processor {
public:
ProcessorPlugin(const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc);
ProcessorPlugin(
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc);
protected:
Status setup_internal() override;

@ -86,7 +86,7 @@ class ProcessorPluginTest(
plugin_spec.node_description.CopyFrom(node_desc)
pipe_address = await plugin_host.call('CREATE_PLUGIN', plugin_spec)
proc = processor.PyProcessor('test_node', self.host_system, node_desc)
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
proc.setup()
proc.set_parameters(
processor_pb2.ProcessorParameters(
@ -155,7 +155,7 @@ class ProcessorPluginTest(
try:
fake_host_ready.wait()
proc = processor.PyProcessor('test_node', self.host_system, node_desc)
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
proc.setup()
proc.set_parameters(
processor_pb2.ProcessorParameters(
@ -219,7 +219,7 @@ class ProcessorPluginTest(
try:
fake_host_ready.wait()
proc = processor.PyProcessor('test_node', self.host_system, node_desc)
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
proc.setup()
proc.set_parameters(
processor_pb2.ProcessorParameters(

@ -39,8 +39,10 @@ extern "C" {
namespace noisicaa {
ProcessorSoundFile::ProcessorSoundFile(
const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc)
: Processor(node_id, "noisicaa.audioproc.engine.processor.sound_file", host_system, desc) {}
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc)
: Processor(
realm_name, node_id, "noisicaa.audioproc.engine.processor.sound_file", host_system, desc) {}
ProcessorSoundFile::~ProcessorSoundFile() {}

@ -46,7 +46,8 @@ class AudioFile;
class ProcessorSoundFile : public Processor {
public:
ProcessorSoundFile(
const string& node_id, HostSystem* host_system, const pb::NodeDescription& desc);
const string& realm_name, const string& node_id, HostSystem* host_system,
const pb::NodeDescription& desc);
~ProcessorSoundFile() override;
protected:

@ -41,7 +41,7 @@ class ProcessorSoundFileTestMixin(
node_desc.sound_file.sound_file_path = os.fsencode(
os.path.join(unittest.TESTDATA_DIR, 'snare.wav'))
proc = processor.PyProcessor('test_node', self.host_system, node_desc)
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
proc.setup()
buffer_mgr = unittest_engine_utils.BufferManager(self.host_system)

@ -34,11 +34,11 @@ class ProcessorTest(
type=node_db.NodeDescription.PROCESSOR,
ports=[],
processor=node_db.ProcessorDescription(
type=node_db.ProcessorDescription.NULLPROC,
type='builtin://null',
),
)
proc1 = processor.PyProcessor('test_node_1', self.host_system, node_description)
proc2 = processor.PyProcessor('test_node_2', self.host_system, node_description)
proc1 = processor.PyProcessor('realm', 'test_node_1', self.host_system, node_description)
proc2 = processor.PyProcessor('realm', 'test_node_2', self.host_system, node_description)
self.assertNotEqual(proc1.id, proc2.id)

@ -32,6 +32,7 @@
#include "noisicaa/core/perf_stats.h"
#include "noisicaa/core/status.h"
#include "noisicaa/host_system/host_system.h"
#include "noisicaa/audioproc/public/engine_notification.pb.h"
#include "noisicaa/audioproc/public/time_mapper.h"
#include "noisicaa/audioproc/engine/opcodes.h"
#include "noisicaa/audioproc/engine/block_context.h"
@ -83,20 +84,33 @@ Status Program::setup(Realm* realm, HostSystem* host_system, const Spec* s) {
return Status::Ok();
}
ActiveProcessor::ActiveProcessor(Processor* processor, Slot<ProcessorState>::Callback state_callback)
ActiveProcessor::ActiveProcessor(
Processor* processor, Slot<pb::EngineNotification>::Callback notification_callback)
: processor(processor),
state_changed_listener(processor->state_changed.connect(state_callback)),
notification_listener(processor->notifications.connect(notification_callback)),
ref_count(0) {
processor->incref();
// Emit correct state.
// TODO: When we have async processor setup, remove this, as the notifications will come from
// the background thread.
state_callback(processor->state());
pb::EngineNotification notification;
auto nsc = notification.add_node_state_changes();
nsc->set_realm(processor->realm_name());
nsc->set_node_id(processor->node_id());
switch (processor->state()) {
case ProcessorState::INACTIVE: nsc->set_state(pb::NodeStateChange::INACTIVE); break;
case ProcessorState::SETUP: nsc->set_state(pb::NodeStateChange::SETUP); break;
case ProcessorState::RUNNING: nsc->set_state(pb::NodeStateChange::RUNNING); break;
case ProcessorState::BROKEN: nsc->set_state(pb::NodeStateChange::BROKEN); break;
case ProcessorState::CLEANUP: nsc->set_state(pb::NodeStateChange::CLEANUP); break;
}
notification_callback(notification);
}
ActiveProcessor::~ActiveProcessor() {
processor->decref();
processor->state_changed.disconnect(state_changed_listener);
processor->notifications.disconnect(notification_listener);
}
ActiveControlValue::ActiveControlValue(ControlValue* cv)
@ -195,23 +209,24 @@ Status Realm::add_processor(Processor* processor) {
ActiveProcessor* active_processor = new ActiveProcessor(
processor,
std::bind(&Realm::processor_state_changed_proxy, this, processor, placeholders::_1));
std::bind(&Realm::notification_proxy, this, placeholders::_1));
_processors.emplace(processor->id(), unique_ptr<ActiveProcessor>(active_processor));
return Status::Ok();
}
void Realm::set_processor_state_changed_callback(
void (*callback)(void*, const string&, ProcessorState), void* userdata) {
assert(_processor_state_changed_callback == nullptr);
_processor_state_changed_callback = callback;
_processor_state_changed_userdata = userdata;
void Realm::set_notification_callback(
void (*callback)(void*, const string&), void* userdata) {
assert(_notification_callback == nullptr);
_notification_callback = callback;
_notification_userdata = userdata;
}
void Realm::processor_state_changed_proxy(Processor* processor, ProcessorState state) {
_logger->info("Processor %llx state change: %s", processor->id(), Processor::state_name(state));
if (_processor_state_changed_callback != nullptr) {
_processor_state_changed_callback(_processor_state_changed_userdata, processor->node_id(), state);
void Realm::notification_proxy(const pb::EngineNotification& notification) {
if (_notification_callback != nullptr) {
string notification_serialized;
assert(notification.SerializeToString(&notification_serialized));
_notification_callback(_notification_userdata, notification_serialized);
}
}

@ -51,6 +51,10 @@ class TimeMapper;
class BufferArena;
class Realm;
namespace pb {
class EngineNotification;
}
class Program {
public:
Program(Logger* logger, uint32_t version);
@ -78,11 +82,11 @@ struct ProgramState {
};
struct ActiveProcessor {
ActiveProcessor(Processor* processor, Slot<ProcessorState>::Callback state_callback);
ActiveProcessor(Processor* processor, Slot<pb::EngineNotification>::Callback notification_callback);
~ActiveProcessor();
Processor* processor;
Slot<ProcessorState>::Listener state_changed_listener;
Slot<pb::EngineNotification>::Listener notification_listener;
int ref_count;
};
@ -113,8 +117,8 @@ public:
void clear_programs();
void set_processor_state_changed_callback(
void (*callback)(void*, const string&, ProcessorState), void* userdata);
void set_notification_callback(
void (*callback)(void*, const string&), void* userdata);
Status add_processor(Processor* processor);
Status add_control_value(ControlValue* cv);
@ -140,9 +144,9 @@ private:
void activate_program(Program* program);
void deactivate_program(Program* program);
void processor_state_changed_proxy(Processor* processor, ProcessorState state);
void (*_processor_state_changed_callback)(void*, const string&, ProcessorState) = nullptr;
void* _processor_state_changed_userdata = nullptr;
void notification_proxy(const pb::EngineNotification& notification);
void (*_notification_callback)(void*, const string&) = nullptr;
void* _notification_userdata = nullptr;
string _name;
Logger* _logger = nullptr;

@ -45,8 +45,8 @@ cdef extern from "noisicaa/audioproc/engine/realm.h" namespace "noisicaa" nogil:
Status setup()
void cleanup()
void clear_programs()
void set_processor_state_changed_callback(
void (*callback)(void*, const string&, ProcessorState), void* userdata);
void set_notification_callback(
void (*callback)(void*, const string&), void* userdata);
Status add_processor(Processor* processor)
Status add_control_value(ControlValue* cv)
Status add_child_realm(Realm* cv)
@ -76,5 +76,5 @@ cdef class PyRealm(object):
cdef Realm* get(self) nogil
@staticmethod
cdef void _processor_state_changed_callback(
void* c_self, const string& node_id, ProcessorState state) with gil
cdef void __notification_callback(
void* c_self, const string& notification_serialized) with gil

@ -25,6 +25,7 @@ from noisicaa.core import ipc
from noisicaa import audioproc
from noisicaa import node_db
from noisicaa import host_system as host_system_lib
from noisicaa.audioproc.public import engine_notification_pb2
from . import player as player_lib
from . import spec as spec_lib
from . import processor
@ -45,7 +46,7 @@ class PyProgram(object):
class PyRealm(object):
node_state_changed = ... # type: core.Callback[str]
notifications = ... # type: core.Callback[engine_notification_pb2.EngineNotification]
child_realms = ... # type: Dict[str, PyRealm]
def __init__(

@ -30,6 +30,7 @@ from noisicaa import audioproc
from noisicaa import node_db
from noisicaa.core.status cimport check
from noisicaa.host_system.host_system cimport PyHostSystem
from noisicaa.audioproc.public import engine_notification_pb2
from .player cimport PyPlayer
from .spec cimport PySpec
from .processor cimport PyProcessor
@ -59,7 +60,7 @@ cdef class PyRealm(object):
PyHostSystem host_system,
PyPlayer player,
str callback_address):
self.node_state_changed = core.Callback()
self.notifications = core.Callback()
self.__engine = engine
self.__name = name
@ -81,8 +82,8 @@ cdef class PyRealm(object):
self.__realm = new Realm(c_name, host_system.get(), c_player)
self.__realm.incref()
self.__realm.set_processor_state_changed_callback(
self._processor_state_changed_callback, <PyObject*>self)
self.__realm.set_notification_callback(
self.__notification_callback, <PyObject*>self)
self.__sink = graph.Node.create(
host_system=self.__host_system,
@ -258,8 +259,8 @@ cdef class PyRealm(object):
check(self.__realm.run_maintenance())
@staticmethod
cdef void _processor_state_changed_callback(
void* c_self, const string& c_node_id, ProcessorState c_state) with gil:
cdef void __notification_callback(
void* c_self, const string& notification_serialized) with gil:
cdef PyRealm self = <object><PyObject*>c_self
# Have to stash away any active exception, because otherwise exception handling