Allow nodes to have dynamic list of ports.
- Currently only used for CustomCSound nodes. - Bring back node parameters - Add a ObjectListEditor widget.looper
parent
9f4dbd2732
commit
c3e5c3a88d
|
@ -29,7 +29,7 @@ import sys
|
|||
import time
|
||||
import zipfile
|
||||
|
||||
VERSION = '3.0.2'
|
||||
VERSION = '3.7.1'
|
||||
FILENAME = 'v%s.zip' % VERSION
|
||||
DOWNLOAD_URL = 'https://github.com/google/protobuf/archive/%s' % FILENAME
|
||||
|
||||
|
|
|
@ -444,6 +444,7 @@ class QAbstractButton(QWidget):
|
|||
|
||||
|
||||
class QAbstractItemDelegate(QtCore.QObject):
|
||||
commitData = ... # type: PYQT_SIGNAL
|
||||
|
||||
class EndEditHint(int): ...
|
||||
NoHint = ... # type: 'QAbstractItemDelegate.EndEditHint'
|
||||
|
@ -456,7 +457,7 @@ class QAbstractItemDelegate(QtCore.QObject):
|
|||
|
||||
def sizeHintChanged(self, a0: QtCore.QModelIndex) -> None: ...
|
||||
def closeEditor(self, editor: QWidget, hint: 'QAbstractItemDelegate.EndEditHint' = ...) -> None: ...
|
||||
def commitData(self, editor: QWidget) -> None: ...
|
||||
#def commitData(self, editor: QWidget) -> None: ...
|
||||
def helpEvent(self, event: QtGui.QHelpEvent, view: 'QAbstractItemView', option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> bool: ...
|
||||
def editorEvent(self, event: QtCore.QEvent, model: QtCore.QAbstractItemModel, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> bool: ...
|
||||
def destroyEditor(self, editor: QWidget, index: QtCore.QModelIndex) -> None: ...
|
||||
|
@ -1581,6 +1582,7 @@ class QColumnView(QAbstractItemView):
|
|||
|
||||
class QComboBox(QWidget):
|
||||
currentIndexChanged = ... # type: PYQT_SIGNAL
|
||||
activated = ... # type: PYQT_SIGNAL
|
||||
|
||||
class SizeAdjustPolicy(int): ...
|
||||
AdjustToContents = ... # type: 'QComboBox.SizeAdjustPolicy'
|
||||
|
@ -1624,10 +1626,10 @@ class QComboBox(QWidget):
|
|||
@typing.overload
|
||||
def highlighted(self, a0: str) -> None: ...
|
||||
def currentTextChanged(self, a0: str) -> None: ...
|
||||
@typing.overload
|
||||
def activated(self, index: int) -> None: ...
|
||||
@typing.overload
|
||||
def activated(self, a0: str) -> None: ...
|
||||
# @typing.overload
|
||||
# def activated(self, index: int) -> None: ...
|
||||
# @typing.overload
|
||||
# def activated(self, a0: str) -> None: ...
|
||||
def editTextChanged(self, a0: str) -> None: ...
|
||||
def setCurrentText(self, text: str) -> None: ...
|
||||
def setEditText(self, text: str) -> None: ...
|
||||
|
|
|
@ -41,7 +41,7 @@ pkg_check_modules(LIBSNDFILE REQUIRED sndfile)
|
|||
pkg_check_modules(LIBFLUIDSYNTH REQUIRED fluidsynth>=1.1.6)
|
||||
pkg_check_modules(LIBAVUTIL REQUIRED libavutil)
|
||||
pkg_check_modules(LIBSWRESAMPLE REQUIRED libswresample)
|
||||
pkg_check_modules(LIBPROTOBUF REQUIRED protobuf>=3.0)
|
||||
pkg_check_modules(LIBPROTOBUF REQUIRED protobuf>=3.7)
|
||||
pkg_check_modules(LIBPORTAUDIO REQUIRED portaudio-2.0>=19)
|
||||
pkg_check_modules(LIBUNWIND REQUIRED libunwind-generic>=1.1)
|
||||
|
||||
|
@ -105,7 +105,7 @@ macro(py_proto src)
|
|||
string(REGEX REPLACE "\\.proto$" "" base ${src})
|
||||
add_custom_command(
|
||||
OUTPUT ${base}_pb2.py
|
||||
COMMAND LD_LIBRARY_PATH=$ENV{VIRTUAL_ENV}/lib protoc --python_out=${CMAKE_BINARY_DIR} --mypy_out=${CMAKE_BINARY_DIR} --proto_path=${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}/${src}
|
||||
COMMAND LD_LIBRARY_PATH=$ENV{VIRTUAL_ENV}/lib $ENV{VIRTUAL_ENV}/bin/protoc --python_out=${CMAKE_BINARY_DIR} --mypy_out=${CMAKE_BINARY_DIR} --proto_path=${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}/${src}
|
||||
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${src}
|
||||
)
|
||||
file(RELATIVE_PATH pkg_path ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
@ -117,7 +117,7 @@ macro(cpp_proto src)
|
|||
string(REGEX REPLACE "\\.proto$" "" base ${src})
|
||||
add_custom_command(
|
||||
OUTPUT ${base}.pb.cc ${base}.pb.h
|
||||
COMMAND LD_LIBRARY_PATH=$ENV{VIRTUAL_ENV}/lib protoc --cpp_out=${CMAKE_BINARY_DIR} --proto_path=${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}/${src}
|
||||
COMMAND LD_LIBRARY_PATH=$ENV{VIRTUAL_ENV}/lib $ENV{VIRTUAL_ENV}/bin/protoc --cpp_out=${CMAKE_BINARY_DIR} --proto_path=${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}/${src}
|
||||
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${src}
|
||||
)
|
||||
endmacro(cpp_proto)
|
||||
|
|
20
listdeps
20
listdeps
|
@ -57,7 +57,7 @@ PIP_DEPS = {
|
|||
PKG('numpy'),
|
||||
PKG('portalocker'),
|
||||
PKG('posix_ipc'),
|
||||
PKG('protobuf'),
|
||||
PKG('protobuf==3.7.1'),
|
||||
PKG('psutil'),
|
||||
PKG('pyaudio'),
|
||||
PKG('quamash'),
|
||||
|
@ -65,7 +65,7 @@ PIP_DEPS = {
|
|||
PKG('urwid'),
|
||||
],
|
||||
'build': [
|
||||
PKG('./3rdparty/protoc/', 'ubuntu', '<17.10'),
|
||||
PKG('./3rdparty/protoc/'),
|
||||
PKG('cssutils'),
|
||||
PKG('cython'),
|
||||
PKG('pkgconfig'),
|
||||
|
@ -138,15 +138,13 @@ SYS_DEPS = {
|
|||
PKG('libcsound64-dev', 'ubuntu', '>=17.10'),
|
||||
|
||||
# protocol buffers
|
||||
PKG('autoconf', 'ubuntu', '<17.10'),
|
||||
PKG('automake', 'ubuntu', '<17.10'),
|
||||
PKG('libtool', 'ubuntu', '<17.10'),
|
||||
PKG('curl', 'ubuntu', '<17.10'),
|
||||
PKG('make', 'ubuntu', '<17.10'),
|
||||
PKG('g++', 'ubuntu', '<17.10'),
|
||||
PKG('unzip', 'ubuntu', '<17.10'),
|
||||
PKG('libprotobuf-dev', 'ubuntu', '>=17.10'),
|
||||
PKG('protobuf-compiler', 'ubuntu', '>=17.10'),
|
||||
PKG('autoconf'),
|
||||
PKG('automake'),
|
||||
PKG('libtool'),
|
||||
PKG('curl'),
|
||||
PKG('make'),
|
||||
PKG('g++'),
|
||||
PKG('unzip'),
|
||||
|
||||
# libswresample
|
||||
PKG('libswresample-dev'),
|
||||
|
|
|
@ -29,6 +29,8 @@ from .audioproc_pb2 import (
|
|||
SetControlValue,
|
||||
SetPluginState,
|
||||
SetNodePortProperties,
|
||||
SetNodeDescription,
|
||||
SetNodeParameters,
|
||||
)
|
||||
from .audioproc_client import (
|
||||
AbstractAudioProcClient,
|
||||
|
@ -57,4 +59,5 @@ from .public import (
|
|||
BackendSettings,
|
||||
HostParameters,
|
||||
NodePortProperties,
|
||||
NodeParameters,
|
||||
)
|
||||
|
|
|
@ -27,6 +27,7 @@ import "noisicaa/node_db/node_description.proto";
|
|||
import "noisicaa/audioproc/public/backend_settings.proto";
|
||||
import "noisicaa/audioproc/public/control_value.proto";
|
||||
import "noisicaa/audioproc/public/host_parameters.proto";
|
||||
import "noisicaa/audioproc/public/node_parameters.proto";
|
||||
import "noisicaa/audioproc/public/node_port_properties.proto";
|
||||
import "noisicaa/audioproc/public/player_state.proto";
|
||||
import "noisicaa/audioproc/public/plugin_state.proto";
|
||||
|
@ -77,6 +78,16 @@ message SetNodePortProperties {
|
|||
required noisicaa.pb.NodePortProperties port_properties = 2;
|
||||
}
|
||||
|
||||
message SetNodeDescription {
|
||||
required string node_id = 1;
|
||||
required noisicaa.pb.NodeDescription description = 2;
|
||||
}
|
||||
|
||||
message SetNodeParameters {
|
||||
required string node_id = 1;
|
||||
required noisicaa.pb.NodeParameters parameters = 2;
|
||||
}
|
||||
|
||||
message Mutation {
|
||||
oneof type {
|
||||
AddNode add_node = 1;
|
||||
|
@ -86,6 +97,8 @@ message Mutation {
|
|||
SetControlValue set_control_value = 5;
|
||||
SetPluginState set_plugin_state = 6;
|
||||
SetNodePortProperties set_node_port_properties = 7;
|
||||
SetNodeDescription set_node_description = 8;
|
||||
SetNodeParameters set_node_parameters = 9;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -270,6 +270,8 @@ class AudioProcProcess(core.ProcessBase):
|
|||
request: audioproc_pb2.PipelineMutationRequest,
|
||||
response: empty_message_pb2.EmptyMessage
|
||||
) -> None:
|
||||
logging.info("Pipeline mutation:\n%s", request)
|
||||
|
||||
realm = self.__engine.get_realm(request.realm)
|
||||
graph = realm.graph
|
||||
|
||||
|
@ -350,6 +352,17 @@ class AudioProcProcess(core.ProcessBase):
|
|||
node.set_port_properties(set_node_port_properties.port_properties)
|
||||
realm.update_spec()
|
||||
|
||||
elif mutation_type == 'set_node_description':
|
||||
set_node_description = request.mutation.set_node_description
|
||||
node = graph.find_node(set_node_description.node_id)
|
||||
if await node.set_description(set_node_description.description):
|
||||
realm.update_spec()
|
||||
|
||||
elif mutation_type == 'set_node_parameters':
|
||||
set_node_parameters = request.mutation.set_node_parameters
|
||||
node = graph.find_node(set_node_parameters.node_id)
|
||||
node.set_parameters(set_node_parameters.parameters)
|
||||
|
||||
else:
|
||||
raise ValueError(request.mutation)
|
||||
|
||||
|
|
|
@ -72,11 +72,11 @@ set(LIB_SRCS
|
|||
plugin_ui_host.cpp
|
||||
plugin_ui_host_lv2.cpp
|
||||
processor.cpp
|
||||
processor.pb.cc
|
||||
processor_null.cpp
|
||||
processor_csound_base.cpp
|
||||
processor_csound.cpp
|
||||
processor_plugin.cpp
|
||||
processor_plugin.pb.cc
|
||||
processor_sound_file.cpp
|
||||
profile.cpp
|
||||
realtime.cpp
|
||||
|
@ -94,8 +94,8 @@ set(TEST_SRCS
|
|||
py_proto(plugin_host.proto)
|
||||
cpp_proto(plugin_host.proto)
|
||||
|
||||
py_proto(processor.proto)
|
||||
cpp_proto(processor.proto)
|
||||
py_proto(processor_plugin.proto)
|
||||
cpp_proto(processor_plugin.proto)
|
||||
|
||||
add_library(noisicaa-audioproc-engine SHARED ${LIB_SRCS})
|
||||
target_compile_options(noisicaa-audioproc-engine PRIVATE -fPIC -std=c++11 -Wall -Werror -pedantic -DHAVE_PTHREAD_SPIN_LOCK)
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "noisicaa/core/logging.h"
|
||||
#include "noisicaa/core/perf_stats.h"
|
||||
|
@ -188,8 +189,16 @@ Status CSoundUtil::process_block(
|
|||
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
|
||||
|
||||
char *e = nullptr;
|
||||
int instr = strtol(port.csound_name.c_str(), &e, 10);
|
||||
if (e == port.csound_name.c_str()) {
|
||||
_logger->error("Invalid instrument name '%s'", port.csound_name.c_str());
|
||||
instr = 1;
|
||||
}
|
||||
|
||||
_event_input_ports[port_idx] = {seq, event, instr};
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +259,7 @@ Status CSoundUtil::process_block(
|
|||
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);
|
||||
_logger->warning("csoundScoreEvent failed (code %d).", rc);
|
||||
}
|
||||
} else if ((midi[0] & 0xf0) == 0x80) {
|
||||
MYFLT p[3] = {
|
||||
|
@ -262,7 +271,7 @@ Status CSoundUtil::process_block(
|
|||
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);
|
||||
_logger->warning("csoundScoreEvent failed (code %d).", rc);
|
||||
}
|
||||
} else {
|
||||
_logger->warning("Ignoring unsupported midi event %d.", midi[0] & 0xf0);
|
||||
|
|
|
@ -55,6 +55,7 @@ public:
|
|||
string name;
|
||||
pb::PortDescription::Type type;
|
||||
pb::PortDescription::Direction direction;
|
||||
string csound_name;
|
||||
};
|
||||
|
||||
Status setup(const string& orchestra, const string& score, const vector<PortSpec>& ports);
|
||||
|
|
|
@ -31,10 +31,11 @@ from noisicaa import host_system as host_system_lib
|
|||
from noisicaa.core import ipc
|
||||
from noisicaa.core import session_data_pb2
|
||||
from noisicaa.audioproc.public import node_port_properties_pb2
|
||||
from noisicaa.audioproc.public import node_parameters_pb2
|
||||
from noisicaa.audioproc.public import processor_message_pb2
|
||||
from . import control_value
|
||||
from . import processor as processor_lib
|
||||
from . import processor_pb2
|
||||
from . import processor_plugin_pb2
|
||||
from . import plugin_host_pb2
|
||||
from . import buffers
|
||||
from . import spec as spec_lib
|
||||
|
@ -202,10 +203,6 @@ class KRateControlOutputPort(KRateControlPortMixin, OutputPortMixin, Port):
|
|||
|
||||
|
||||
class EventInputPort(EventPortMixin, InputPortMixin, Port):
|
||||
def __init__(self, *, csound_instr: str = '1', **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.csound_instr = csound_instr
|
||||
|
||||
def check_port(self, port: Port) -> None:
|
||||
super().check_port(port)
|
||||
if not isinstance(port, EventOutputPort):
|
||||
|
@ -216,9 +213,27 @@ class EventOutputPort(EventPortMixin, OutputPortMixin, Port):
|
|||
pass
|
||||
|
||||
|
||||
class Node(object):
|
||||
init_ports_from_description = True
|
||||
port_cls_map = {
|
||||
(node_db.PortDescription.AUDIO,
|
||||
node_db.PortDescription.INPUT): AudioInputPort,
|
||||
(node_db.PortDescription.AUDIO,
|
||||
node_db.PortDescription.OUTPUT): AudioOutputPort,
|
||||
(node_db.PortDescription.ARATE_CONTROL,
|
||||
node_db.PortDescription.INPUT): ARateControlInputPort,
|
||||
(node_db.PortDescription.ARATE_CONTROL,
|
||||
node_db.PortDescription.OUTPUT): ARateControlOutputPort,
|
||||
(node_db.PortDescription.KRATE_CONTROL,
|
||||
node_db.PortDescription.INPUT): KRateControlInputPort,
|
||||
(node_db.PortDescription.KRATE_CONTROL,
|
||||
node_db.PortDescription.OUTPUT): KRateControlOutputPort,
|
||||
(node_db.PortDescription.EVENTS,
|
||||
node_db.PortDescription.INPUT): EventInputPort,
|
||||
(node_db.PortDescription.EVENTS,
|
||||
node_db.PortDescription.OUTPUT): EventOutputPort,
|
||||
}
|
||||
|
||||
|
||||
class Node(object):
|
||||
def __init__(
|
||||
self, *,
|
||||
host_system: host_system_lib.HostSystem, description: node_db.NodeDescription,
|
||||
|
@ -228,7 +243,8 @@ class Node(object):
|
|||
assert isinstance(description, node_db.NodeDescription), description
|
||||
|
||||
self._host_system = host_system
|
||||
self.description = description
|
||||
self.description = node_db.NodeDescription()
|
||||
self.description.CopyFrom(description)
|
||||
self.name = name or type(self).__name__
|
||||
self.id = id
|
||||
self.initial_state = initial_state
|
||||
|
@ -241,9 +257,10 @@ class Node(object):
|
|||
|
||||
self.__control_values = {} # type: Dict[str, control_value.PyControlValue]
|
||||
self.__port_properties = {} # type: Dict[str, node_port_properties_pb2.NodePortProperties]
|
||||
self.__parameters = node_parameters_pb2.NodeParameters()
|
||||
|
||||
if self.init_ports_from_description:
|
||||
self.init_ports()
|
||||
for port_desc in self.description.ports:
|
||||
self.__add_port(self.__create_port(port_desc))
|
||||
|
||||
@classmethod
|
||||
def create(cls, *, description: node_db.NodeDescription, **kwargs: Any) -> 'Node':
|
||||
|
@ -276,46 +293,28 @@ class Node(object):
|
|||
def is_owned_by(self, realm: realm_lib.PyRealm) -> bool:
|
||||
return self.__realm is realm
|
||||
|
||||
def init_ports(self) -> None:
|
||||
port_cls_map = {
|
||||
(node_db.PortDescription.AUDIO,
|
||||
node_db.PortDescription.INPUT): AudioInputPort,
|
||||
(node_db.PortDescription.AUDIO,
|
||||
node_db.PortDescription.OUTPUT): AudioOutputPort,
|
||||
(node_db.PortDescription.ARATE_CONTROL,
|
||||
node_db.PortDescription.INPUT): ARateControlInputPort,
|
||||
(node_db.PortDescription.ARATE_CONTROL,
|
||||
node_db.PortDescription.OUTPUT): ARateControlOutputPort,
|
||||
(node_db.PortDescription.KRATE_CONTROL,
|
||||
node_db.PortDescription.INPUT): KRateControlInputPort,
|
||||
(node_db.PortDescription.KRATE_CONTROL,
|
||||
node_db.PortDescription.OUTPUT): KRateControlOutputPort,
|
||||
(node_db.PortDescription.EVENTS,
|
||||
node_db.PortDescription.INPUT): EventInputPort,
|
||||
(node_db.PortDescription.EVENTS,
|
||||
node_db.PortDescription.OUTPUT): EventOutputPort,
|
||||
}
|
||||
def __create_port(self, port_desc: node_db.PortDescription) -> Port:
|
||||
port_cls = port_cls_map[
|
||||
(port_desc.type, port_desc.direction)]
|
||||
kwargs = {}
|
||||
|
||||
for port_desc in self.description.ports:
|
||||
port_cls = port_cls_map[
|
||||
(port_desc.type, port_desc.direction)]
|
||||
kwargs = {}
|
||||
if port_desc.HasField('bypass_port'):
|
||||
kwargs['bypass_port'] = port_desc.bypass_port
|
||||
if port_desc.HasField('drywet_port'):
|
||||
kwargs['drywet_port'] = port_desc.drywet_port
|
||||
|
||||
if port_desc.HasField('bypass_port'):
|
||||
kwargs['bypass_port'] = port_desc.bypass_port
|
||||
if port_desc.HasField('drywet_port'):
|
||||
kwargs['drywet_port'] = port_desc.drywet_port
|
||||
if port_desc.HasField('csound_instr'):
|
||||
kwargs['csound_instr'] = port_desc.csound_instr
|
||||
port = port_cls(description=port_desc, **kwargs)
|
||||
port.owner = self
|
||||
|
||||
port = port_cls(description=port_desc, **kwargs)
|
||||
port.owner = self
|
||||
return port
|
||||
|
||||
self.ports.append(port)
|
||||
if port_desc.direction == node_db.PortDescription.INPUT:
|
||||
self.inputs[port.name] = port
|
||||
else:
|
||||
self.outputs[port.name] = port
|
||||
def __add_port(self, port: Port) -> None:
|
||||
self.ports.append(port)
|
||||
if isinstance(port, InputPortMixin):
|
||||
self.inputs[port.name] = port
|
||||
else:
|
||||
assert isinstance(port, OutputPortMixin)
|
||||
self.outputs[port.name] = port
|
||||
|
||||
@property
|
||||
def parent_nodes(self) -> List['Node']:
|
||||
|
@ -334,11 +333,12 @@ class Node(object):
|
|||
|
||||
for port in self.ports:
|
||||
if isinstance(port, (KRateControlInputPort, ARateControlInputPort)):
|
||||
logger.info("Float control value '%s'", port.buf_name)
|
||||
cv = control_value.PyFloatControlValue(
|
||||
port.buf_name, port.description.float_value.default, 1)
|
||||
self.__control_values[port.buf_name] = cv
|
||||
self.realm.add_active_control_value(cv)
|
||||
if port.buf_name not in self.__control_values:
|
||||
logger.info("New float control value '%s'", port.buf_name)
|
||||
cv = control_value.PyFloatControlValue(
|
||||
port.buf_name, port.description.float_value.default, 1)
|
||||
self.__control_values[port.buf_name] = cv
|
||||
self.realm.add_active_control_value(cv)
|
||||
|
||||
async def cleanup(self, deref: bool = False) -> None:
|
||||
"""Clean up the node.
|
||||
|
@ -360,6 +360,67 @@ class Node(object):
|
|||
self, port_properties: node_port_properties_pb2.NodePortProperties) -> None:
|
||||
self.__port_properties[port_properties.name] = port_properties
|
||||
|
||||
async def set_description(self, description: node_db.NodeDescription) -> bool:
|
||||
logger.info("%s: set description:\n%s", self.id, description)
|
||||
|
||||
old = {port_desc.name: port_desc for port_desc in self.description.ports}
|
||||
new = {port_desc.name: port_desc for port_desc in description.ports}
|
||||
|
||||
added = {
|
||||
name for name, port_desc in new.items()
|
||||
if name not in old
|
||||
}
|
||||
removed = {
|
||||
name for name, port_desc in old.items()
|
||||
if name not in new
|
||||
}
|
||||
changed = {
|
||||
name for name, port_desc in new.items()
|
||||
if (name in old
|
||||
and (port_desc.type != old[name].type
|
||||
or port_desc.direction != old[name].direction))
|
||||
}
|
||||
|
||||
if not added and not removed and not changed:
|
||||
self.description.CopyFrom(description)
|
||||
return False
|
||||
|
||||
await self.cleanup()
|
||||
|
||||
logger.info(
|
||||
"%s: Ports changed: added=[%s] changed=[%s] removed=[%s]",
|
||||
self.id,
|
||||
", ".join(sorted(added)),
|
||||
", ".join(sorted(changed)),
|
||||
", ".join(sorted(removed)))
|
||||
|
||||
existing_ports = {port.name: port for port in self.ports}
|
||||
self.ports.clear()
|
||||
self.inputs.clear()
|
||||
self.outputs.clear()
|
||||
for port_desc in description.ports:
|
||||
if port_desc.name in added or port_desc.name in changed:
|
||||
port = self.__create_port(port_desc)
|
||||
else:
|
||||
port = existing_ports[port_desc.name]
|
||||
self.__add_port(port)
|
||||
|
||||
self.description.CopyFrom(description)
|
||||
|
||||
await self.setup()
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def parameters(self) -> node_parameters_pb2.NodeParameters:
|
||||
params = node_parameters_pb2.NodeParameters()
|
||||
params.CopyFrom(self.__parameters)
|
||||
return params
|
||||
|
||||
def set_parameters(self, parameters: node_parameters_pb2.NodeParameters) -> None:
|
||||
logger.info("%s: set parameters:\n%s", self.id, parameters)
|
||||
self.__parameters.MergeFrom(parameters)
|
||||
|
||||
@property
|
||||
def control_values(self) -> List[control_value.PyControlValue]:
|
||||
return [v for _, v in sorted(self.__control_values.items())]
|
||||
|
@ -409,6 +470,7 @@ class ProcessorNode(Node):
|
|||
|
||||
self.__processor = processor_lib.PyProcessor(
|
||||
self.realm.name, self.id, self._host_system, self.description)
|
||||
self.__processor.set_parameters(self.parameters)
|
||||
self.__processor.setup()
|
||||
self.realm.add_active_processor(self.__processor)
|
||||
|
||||
|
@ -421,6 +483,11 @@ class ProcessorNode(Node):
|
|||
|
||||
await super().cleanup(deref)
|
||||
|
||||
def set_parameters(self, parameters: node_parameters_pb2.NodeParameters) -> None:
|
||||
super().set_parameters(parameters)
|
||||
if self.__processor is not None:
|
||||
self.__processor.set_parameters(parameters)
|
||||
|
||||
def set_session_value(self, key: str, value: session_data_pb2.SessionValue) -> None:
|
||||
if key == 'muted':
|
||||
assert value.WhichOneof('type') == 'bool_value', value
|
||||
|
@ -430,6 +497,12 @@ class ProcessorNode(Node):
|
|||
|
||||
super().set_session_value(key, value)
|
||||
|
||||
async def set_description(self, description: node_db.NodeDescription) -> bool:
|
||||
if not await super().set_description(description):
|
||||
self.__processor.set_description(description)
|
||||
return False
|
||||
return True
|
||||
|
||||
def add_to_spec_pre(self, spec: spec_lib.PySpec) -> None:
|
||||
super().add_to_spec_pre(spec)
|
||||
|
||||
|
@ -449,6 +522,13 @@ class PluginNode(ProcessorNode):
|
|||
self.__plugin_pipe_path = None # type: str
|
||||
|
||||
async def setup(self) -> None:
|
||||
# Make sure the processor is started without a plugin_pipe_path (i.e. not getting
|
||||
# initialized with an old path from the previous incarnation.
|
||||
params = node_parameters_pb2.NodeParameters()
|
||||
plugin_params = params.Extensions[processor_plugin_pb2.processor_plugin_parameters]
|
||||
plugin_params.plugin_pipe_path = ''
|
||||
self.set_parameters(params)
|
||||
|
||||
await super().setup()
|
||||
|
||||
self.__plugin_host = await self.realm.get_plugin_host()
|
||||
|
@ -468,9 +548,10 @@ class PluginNode(ProcessorNode):
|
|||
'CREATE_PLUGIN', create_plugin_request, create_plugin_response)
|
||||
self.__plugin_pipe_path = create_plugin_response.pipe_path
|
||||
|
||||
self.processor.set_parameters(
|
||||
processor_pb2.ProcessorParameters(
|
||||
plugin_pipe_path=self.__plugin_pipe_path))
|
||||
params = node_parameters_pb2.NodeParameters()
|
||||
plugin_params = params.Extensions[processor_plugin_pb2.processor_plugin_parameters]
|
||||
plugin_params.plugin_pipe_path = self.__plugin_pipe_path
|
||||
self.set_parameters(params)
|
||||
|
||||
async def cleanup(self, deref: bool = False) -> None:
|
||||
await super().cleanup(deref)
|
||||
|
|
|
@ -199,6 +199,9 @@ class PluginHost(plugin_host.PyPluginHost):
|
|||
realm=self.__realm, node_id=self.__node_id, state=self.__state)),
|
||||
loop=self.__event_loop)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
except: # pylint: disable=bare-except
|
||||
logger.error("Exception in state fetcher:\n%s", traceback.format_exc())
|
||||
|
||||
|
|
|
@ -28,9 +28,9 @@
|
|||
#include "noisicaa/core/slots.inl.h"
|
||||
#include "noisicaa/host_system/host_system.h"
|
||||
#include "noisicaa/audioproc/public/engine_notification.pb.h"
|
||||
#include "noisicaa/audioproc/public/node_parameters.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"
|
||||
|
@ -171,13 +171,26 @@ Status Processor::handle_message_internal(pb::ProcessorMessage* msg) {
|
|||
}
|
||||
|
||||
Status Processor::set_parameters(const string& parameters_serialized) {
|
||||
pb::ProcessorParameters parameters;
|
||||
pb::NodeParameters parameters;
|
||||
assert(parameters.ParseFromString(parameters_serialized));
|
||||
_logger->info("Processor %llx: Set parameters:\n%s", id(), parameters.DebugString().c_str());
|
||||
return set_parameters_internal(parameters);
|
||||
}
|
||||
|
||||
Status Processor::set_parameters_internal(const pb::ProcessorParameters& parameters) {
|
||||
Status Processor::set_parameters_internal(const pb::NodeParameters& parameters) {
|
||||
_params.MergeFrom(parameters);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status Processor::set_description(const string& desc_serialized) {
|
||||
pb::NodeDescription desc;
|
||||
assert(desc.ParseFromString(desc_serialized));
|
||||
_logger->info("Processor %llx: Set description:\n%s", id(), desc.DebugString().c_str());
|
||||
return set_description_internal(desc);
|
||||
}
|
||||
|
||||
Status Processor::set_description_internal(const pb::NodeDescription& desc) {
|
||||
_desc.CopyFrom(desc);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "noisicaa/core/status.h"
|
||||
#include "noisicaa/node_db/node_description.pb.h"
|
||||
#include "noisicaa/audioproc/public/musical_time.h"
|
||||
#include "noisicaa/audioproc/public/node_parameters.pb.h"
|
||||
#include "noisicaa/audioproc/engine/buffers.h"
|
||||
#include "noisicaa/audioproc/engine/block_context.h"
|
||||
#include "noisicaa/audioproc/engine/misc.h"
|
||||
|
@ -50,7 +51,6 @@ class TimeMapper;
|
|||
namespace pb {
|
||||
class EngineNotification;
|
||||
class ProcessorMessage;
|
||||
class ProcessorParameters;
|
||||
}
|
||||
|
||||
// Keep this in sync with engine_notification.proto > NodeStateChange
|
||||
|
@ -85,6 +85,7 @@ public:
|
|||
|
||||
Status handle_message(const string& msg_serialized);
|
||||
Status set_parameters(const string& parameters_serialized);
|
||||
Status set_description(const string& description_serialized);
|
||||
|
||||
void connect_port(BlockContext* ctxt, uint32_t port_idx, BufferPtr buf);
|
||||
void process_block(BlockContext* ctxt, TimeMapper* time_mapper);
|
||||
|
@ -98,7 +99,8 @@ protected:
|
|||
virtual void cleanup_internal();
|
||||
|
||||
virtual Status handle_message_internal(pb::ProcessorMessage* msg);
|
||||
virtual Status set_parameters_internal(const pb::ProcessorParameters& parameters);
|
||||
virtual Status set_parameters_internal(const pb::NodeParameters& parameters);
|
||||
virtual Status set_description_internal(const pb::NodeDescription& description);
|
||||
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);
|
||||
|
@ -111,6 +113,7 @@ protected:
|
|||
string _realm_name;
|
||||
string _node_id;
|
||||
pb::NodeDescription _desc;
|
||||
pb::NodeParameters _params;
|
||||
atomic<bool> _muted;
|
||||
|
||||
private:
|
||||
|
|
|
@ -53,6 +53,7 @@ cdef extern from "noisicaa/audioproc/engine/processor.h" namespace "noisicaa" no
|
|||
|
||||
Status handle_message(const string& msg)
|
||||
Status set_parameters(const string& msg)
|
||||
Status set_description(const string& msg)
|
||||
|
||||
void connect_port(BlockContext* ctxt, uint32_t port_idx, BufferPtr buf)
|
||||
void process_block(BlockContext* ctxt, TimeMapper* time_mapper)
|
||||
|
|
|
@ -23,8 +23,8 @@ import enum
|
|||
from noisicaa import node_db
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import host_system as host_system_lib
|
||||
from noisicaa.audioproc.public import node_parameters_pb2
|
||||
from . import block_context
|
||||
from . import processor_pb2
|
||||
|
||||
|
||||
class State(enum.Enum):
|
||||
|
@ -50,4 +50,5 @@ class PyProcessor(object):
|
|||
def process_block(
|
||||
self, ctxt: block_context.PyBlockContext, time_mapper: audioproc.TimeMapper) -> None: ...
|
||||
def handle_message(self, msg: audioproc.ProcessorMessage) -> None: ...
|
||||
def set_parameters(self, parameters: processor_pb2.ProcessorParameters) -> None: ...
|
||||
def set_parameters(self, parameters: node_parameters_pb2.NodeParameters) -> None: ...
|
||||
def set_description(self, desc: node_db.NodeDescription) -> None: ...
|
||||
|
|
|
@ -118,3 +118,8 @@ cdef class PyProcessor(object):
|
|||
cdef string parameters_serialized = parameters.SerializeToString()
|
||||
with nogil:
|
||||
check(self.__processor.set_parameters(parameters_serialized))
|
||||
|
||||
def set_description(self, desc):
|
||||
cdef string desc_serialized = desc.SerializeToString()
|
||||
with nogil:
|
||||
check(self.__processor.set_description(desc_serialized))
|
||||
|
|
|
@ -62,7 +62,13 @@ Status ProcessorCSoundBase::set_code(const string& orchestra, const string& scor
|
|||
|
||||
vector<CSoundUtil::PortSpec> ports;
|
||||
for (const auto& port : _desc.ports()) {
|
||||
ports.emplace_back(CSoundUtil::PortSpec {port.name(), port.type(), port.direction()});
|
||||
ports.emplace_back(
|
||||
CSoundUtil::PortSpec {
|
||||
port.name(),
|
||||
port.type(),
|
||||
port.direction(),
|
||||
port.csound_name()
|
||||
});
|
||||
}
|
||||
|
||||
RETURN_IF_ERROR(instance->setup(orchestra, score, ports));
|
||||
|
|
|
@ -30,9 +30,10 @@
|
|||
#include <pthread.h>
|
||||
#include "noisicaa/core/perf_stats.h"
|
||||
#include "noisicaa/host_system/host_system.h"
|
||||
#include "noisicaa/audioproc/public/node_parameters.pb.h"
|
||||
#include "noisicaa/audioproc/engine/plugin_host.h"
|
||||
#include "noisicaa/audioproc/engine/buffer_arena.h"
|
||||
#include "noisicaa/audioproc/engine/processor.pb.h"
|
||||
#include "noisicaa/audioproc/engine/processor_plugin.pb.h"
|
||||
#include "noisicaa/audioproc/engine/processor_plugin.h"
|
||||
#include "noisicaa/audioproc/engine/rtcheck.h"
|
||||
|
||||
|
@ -55,10 +56,13 @@ void ProcessorPlugin::cleanup_internal() {
|
|||
Processor::cleanup_internal();
|
||||
}
|
||||
|
||||
Status ProcessorPlugin::set_parameters_internal(const pb::ProcessorParameters& parameters) {
|
||||
if (parameters.has_plugin_pipe_path()) {
|
||||
Status ProcessorPlugin::set_parameters_internal(const pb::NodeParameters& parameters) {
|
||||
if (parameters.HasExtension(pb::processor_plugin_parameters)) {
|
||||
const auto& p = parameters.GetExtension(pb::processor_plugin_parameters);
|
||||
pipe_close();
|
||||
RETURN_IF_ERROR(pipe_open(parameters.plugin_pipe_path()));
|
||||
if (!p.plugin_pipe_path().empty()) {
|
||||
RETURN_IF_ERROR(pipe_open(p.plugin_pipe_path()));
|
||||
}
|
||||
}
|
||||
|
||||
return Status::Ok();
|
||||
|
|
|
@ -49,7 +49,7 @@ public:
|
|||
protected:
|
||||
Status setup_internal() override;
|
||||
void cleanup_internal() override;
|
||||
Status set_parameters_internal(const pb::ProcessorParameters& parameters);
|
||||
Status set_parameters_internal(const pb::NodeParameters& parameters);
|
||||
Status connect_port_internal(BlockContext* ctxt, uint32_t port_idx, BufferPtr buf) override;
|
||||
Status process_block_internal(BlockContext* ctxt, TimeMapper* time_mapper) override;
|
||||
|
||||
|
|
|
@ -24,9 +24,13 @@ syntax = "proto2";
|
|||
|
||||
package noisicaa.pb;
|
||||
|
||||
message ProcessorParameters {
|
||||
// ProcessorPlugin
|
||||
import "noisicaa/audioproc/public/node_parameters.proto";
|
||||
|
||||
message ProcessorPluginParameters {
|
||||
// Must only be set while the audio thread is not running.
|
||||
optional string plugin_pipe_path = 1;
|
||||
}
|
||||
|
||||
extend NodeParameters {
|
||||
optional ProcessorPluginParameters processor_plugin_parameters = 100000;
|
||||
}
|
|
@ -35,11 +35,12 @@ from noisidev import unittest_engine_mixins
|
|||
from noisidev import unittest_engine_utils
|
||||
from noisicaa.constants import TEST_OPTS
|
||||
from noisicaa.core import ipc
|
||||
from noisicaa.audioproc.public import node_parameters_pb2
|
||||
from . import plugin_host_pb2
|
||||
from . import block_context
|
||||
from . import buffers
|
||||
from . import processor
|
||||
from . import processor_pb2
|
||||
from . import processor_plugin_pb2
|
||||
from . import buffer_arena
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -91,9 +92,10 @@ class ProcessorPluginTest(
|
|||
|
||||
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
|
||||
proc.setup()
|
||||
proc.set_parameters(
|
||||
processor_pb2.ProcessorParameters(
|
||||
plugin_pipe_path=pipe_address))
|
||||
params = node_parameters_pb2.NodeParameters()
|
||||
plugin_params = params.Extensions[processor_plugin_pb2.processor_plugin_parameters]
|
||||
plugin_params.plugin_pipe_path = pipe_address
|
||||
proc.set_parameters(params)
|
||||
|
||||
arena = buffer_arena.PyBufferArena(2**20)
|
||||
buffer_mgr = unittest_engine_utils.BufferManager(self.host_system, arena)
|
||||
|
@ -163,9 +165,10 @@ class ProcessorPluginTest(
|
|||
|
||||
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
|
||||
proc.setup()
|
||||
proc.set_parameters(
|
||||
processor_pb2.ProcessorParameters(
|
||||
plugin_pipe_path=pipe_address))
|
||||
params = node_parameters_pb2.NodeParameters()
|
||||
plugin_params = params.Extensions[processor_plugin_pb2.processor_plugin_parameters]
|
||||
plugin_params.plugin_pipe_path = pipe_address
|
||||
proc.set_parameters(params)
|
||||
|
||||
ctxt = block_context.PyBlockContext(buffer_arena=arena)
|
||||
ctxt.sample_pos = 1024
|
||||
|
@ -227,9 +230,10 @@ class ProcessorPluginTest(
|
|||
|
||||
proc = processor.PyProcessor('realm', 'test_node', self.host_system, node_desc)
|
||||
proc.setup()
|
||||
proc.set_parameters(
|
||||
processor_pb2.ProcessorParameters(
|
||||
plugin_pipe_path=pipe_address))
|
||||
params = node_parameters_pb2.NodeParameters()
|
||||
plugin_params = params.Extensions[processor_plugin_pb2.processor_plugin_parameters]
|
||||
plugin_params.plugin_pipe_path = pipe_address
|
||||
proc.set_parameters(params)
|
||||
|
||||
ctxt = block_context.PyBlockContext(buffer_arena=arena)
|
||||
ctxt.sample_pos = 1024
|
||||
|
|
|
@ -29,6 +29,7 @@ set(LIB_SRCS
|
|||
instrument_spec.pb.cc
|
||||
musical_time.cpp
|
||||
musical_time.pb.cc
|
||||
node_parameters.pb.cc
|
||||
node_port_properties.pb.cc
|
||||
player_state.pb.cc
|
||||
plugin_state.pb.cc
|
||||
|
@ -66,6 +67,8 @@ cpp_proto(project_properties.proto)
|
|||
py_proto(project_properties.proto)
|
||||
cpp_proto(node_port_properties.proto)
|
||||
py_proto(node_port_properties.proto)
|
||||
cpp_proto(node_parameters.proto)
|
||||
py_proto(node_parameters.proto)
|
||||
|
||||
add_library(noisicaa-audioproc-public SHARED ${LIB_SRCS})
|
||||
target_compile_options(noisicaa-audioproc-public PRIVATE -fPIC -std=c++11 -Wall -Werror -pedantic -DHAVE_PTHREAD_SPIN_LOCK)
|
||||
|
|
|
@ -66,3 +66,6 @@ from .host_parameters_pb2 import (
|
|||
from .node_port_properties_pb2 import (
|
||||
NodePortProperties,
|
||||
)
|
||||
from .node_parameters_pb2 import (
|
||||
NodeParameters,
|
||||
)
|
||||
|
|
|
@ -24,7 +24,6 @@ syntax = "proto2";
|
|||
|
||||
package noisicaa.pb;
|
||||
|
||||
message CustomCSoundSetScript {
|
||||
required string orchestra = 1;
|
||||
required string score = 2;
|
||||
message NodeParameters {
|
||||
extensions 100000 to max;
|
||||
}
|
|
@ -36,7 +36,6 @@ target_link_libraries(noisicaa-builtin_nodes-processor_message_registry PRIVATE
|
|||
target_link_libraries(noisicaa-builtin_nodes-processor_message_registry PRIVATE noisicaa-builtin_nodes-sample_track-processor_messages)
|
||||
target_link_libraries(noisicaa-builtin_nodes-processor_message_registry PRIVATE noisicaa-builtin_nodes-instrument-processor_messages)
|
||||
target_link_libraries(noisicaa-builtin_nodes-processor_message_registry PRIVATE noisicaa-builtin_nodes-pianoroll-processor_messages)
|
||||
target_link_libraries(noisicaa-builtin_nodes-processor_message_registry PRIVATE noisicaa-builtin_nodes-custom_csound-processor_messages)
|
||||
target_link_libraries(noisicaa-builtin_nodes-processor_message_registry PRIVATE noisicaa-builtin_nodes-midi_source-processor_messages)
|
||||
|
||||
add_library(noisicaa-builtin_nodes-processors SHARED processor_registry.cpp)
|
||||
|
|
|
@ -26,7 +26,7 @@ from .beat_track.client_impl import Beat, BeatMeasure, BeatTrack
|
|||
from .control_track.client_impl import ControlPoint, ControlTrack
|
||||
from .sample_track.client_impl import SampleRef, SampleTrack
|
||||
from .instrument.client_impl import Instrument
|
||||
from .custom_csound.client_impl import CustomCSound
|
||||
from .custom_csound.client_impl import CustomCSound, CustomCSoundPort
|
||||
from .midi_source.client_impl import MidiSource
|
||||
|
||||
|
||||
|
@ -42,5 +42,6 @@ def register_classes(pool: model.AbstractPool) -> None:
|
|||
pool.register_class(SampleRef)
|
||||
pool.register_class(SampleTrack)
|
||||
pool.register_class(Instrument)
|
||||
pool.register_class(CustomCSoundPort)
|
||||
pool.register_class(CustomCSound)
|
||||
pool.register_class(MidiSource)
|
||||
|
|
|
@ -62,6 +62,9 @@ extend Command {
|
|||
|
||||
// Custom CSound (407xxx)
|
||||
optional UpdateCustomCSound update_custom_csound = 407000;
|
||||
optional CreateCustomCSoundPort create_custom_csound_port = 407001;
|
||||
optional UpdateCustomCSoundPort update_custom_csound_port = 407002;
|
||||
optional DeleteCustomCSoundPort delete_custom_csound_port = 407003;
|
||||
|
||||
// MIDI source (408xxx)
|
||||
optional UpdateMidiSource update_midi_source = 408000;
|
||||
|
|
|
@ -27,21 +27,17 @@ add_python_package(
|
|||
server_impl.py
|
||||
server_impl_test.py
|
||||
node_ui.py
|
||||
node_ui_test.py
|
||||
processor_test.py
|
||||
processor_messages.py
|
||||
)
|
||||
|
||||
py_proto(model.proto)
|
||||
py_proto(commands.proto)
|
||||
py_proto(processor_messages.proto)
|
||||
cpp_proto(processor_messages.proto)
|
||||
py_proto(processor.proto)
|
||||
cpp_proto(processor.proto)
|
||||
|
||||
add_library(noisicaa-builtin_nodes-custom_csound-processor_messages SHARED processor_messages.pb.cc)
|
||||
target_link_libraries(noisicaa-builtin_nodes-custom_csound-processor_messages PRIVATE noisicaa-audioproc-public)
|
||||
|
||||
add_library(noisicaa-builtin_nodes-custom_csound-processor SHARED processor.cpp)
|
||||
add_library(noisicaa-builtin_nodes-custom_csound-processor SHARED processor.cpp processor.pb.cc)
|
||||
target_compile_options(noisicaa-builtin_nodes-custom_csound-processor PRIVATE -fPIC -std=c++11 -Wall -Werror -pedantic -DHAVE_PTHREAD_SPIN_LOCK)
|
||||
target_link_libraries(noisicaa-builtin_nodes-custom_csound-processor PRIVATE noisicaa-audioproc-public)
|
||||
target_link_libraries(noisicaa-builtin_nodes-custom_csound-processor PRIVATE noisicaa-host_system)
|
||||
target_link_libraries(noisicaa-builtin_nodes-custom_csound-processor PRIVATE noisicaa-builtin_nodes-processor_message_registry)
|
||||
target_link_libraries(noisicaa-builtin_nodes-custom_csound-processor PRIVATE noisicaa-builtin_nodes-custom_csound-processor_messages)
|
||||
|
|
|
@ -20,18 +20,22 @@
|
|||
#
|
||||
# @end:license
|
||||
|
||||
from noisicaa.core.typing_extra import down_cast
|
||||
from noisicaa.music import project_client_model
|
||||
from . import model
|
||||
|
||||
|
||||
class CustomCSoundPort(
|
||||
project_client_model.Port,
|
||||
model.CustomCSoundPort,
|
||||
project_client_model.ObjectBase):
|
||||
@property
|
||||
def node(self) -> 'CustomCSound':
|
||||
return down_cast(CustomCSound, self.parent)
|
||||
|
||||
|
||||
class CustomCSound(
|
||||
project_client_model.BaseNode,
|
||||
model.CustomCSound,
|
||||
project_client_model.ObjectBase):
|
||||
@property
|
||||
def orchestra(self) -> str:
|
||||
return self.get_property_value('orchestra')
|
||||
|
||||
@property
|
||||
def score(self) -> str:
|
||||
return self.get_property_value('score')
|
||||
pass
|
||||
|
|
|
@ -53,3 +53,25 @@ class CustomCSoundTest(commands_test.CommandsTestMixin, unittest.AsyncTestCase):
|
|||
await self.client.send_command(commands.update(
|
||||
node, set_score='blabla'))
|
||||
self.assertEqual(node.score, 'blabla')
|
||||
|
||||
async def test_create_port(self):
|
||||
node = await self._add_node()
|
||||
|
||||
await self.client.send_command(commands.create_port(node, name='foo'))
|
||||
self.assertEqual(len(node.ports), 1)
|
||||
|
||||
async def test_update_port(self):
|
||||
node = await self._add_node()
|
||||
await self.client.send_command(commands.create_port(node, name='foo'))
|
||||
port = node.ports[-1]
|
||||
|
||||
await self.client.send_command(commands.update_port(port, set_csound_name='foo'))
|
||||
self.assertEqual(port.csound_name, 'foo')
|
||||
|
||||
async def test_delete_port(self):
|
||||
node = await self._add_node()
|
||||
await self.client.send_command(commands.create_port(node, name='foo'))
|
||||
port = node.ports[-1]
|
||||
|
||||
await self.client.send_command(commands.delete_port(port))
|
||||
self.assertEqual(len(node.ports), 0)
|
||||
|
|
|
@ -29,3 +29,18 @@ message UpdateCustomCSound {
|
|||
optional string set_orchestra = 2;
|
||||
optional string set_score = 3;
|
||||
}
|
||||
|
||||
message CreateCustomCSoundPort {
|
||||
required uint64 node_id = 1;
|
||||
required string name = 2;
|
||||
optional uint32 index = 3;
|
||||
}
|
||||
|
||||
message UpdateCustomCSoundPort {
|
||||
required uint64 port_id = 1;
|
||||
optional string set_csound_name = 2;
|
||||
}
|
||||
|
||||
message DeleteCustomCSoundPort {
|
||||
required uint64 port_id = 1;
|
||||
}
|
||||
|
|
|
@ -38,3 +38,38 @@ def update(
|
|||
if set_score is not None:
|
||||
pb.set_score = set_score
|
||||
return cmd
|
||||
|
||||
|
||||
def create_port(
|
||||
node: client_impl.CustomCSound,
|
||||
name: str,
|
||||
index: int = None,
|
||||
) -> music.Command:
|
||||
cmd = music.Command(command='create_custom_csound_port')
|
||||
pb = cmd.Extensions[commands_registry_pb2.create_custom_csound_port]
|
||||
pb.node_id = node.id
|
||||
pb.name = name
|
||||
if index is not None:
|
||||
pb.index = index
|
||||
return cmd
|
||||
|
||||
|
||||
def update_port(
|
||||
port: client_impl.CustomCSoundPort, *,
|
||||
set_csound_name: str = None,
|
||||
) -> music.Command:
|
||||
cmd = music.Command(command='update_custom_csound_port')
|
||||
pb = cmd.Extensions[commands_registry_pb2.update_custom_csound_port]
|
||||
pb.port_id = port.id
|
||||
if set_csound_name is not None:
|
||||
pb.set_csound_name = set_csound_name
|
||||
return cmd
|
||||
|
||||
|
||||
def delete_port(
|
||||
port: client_impl.CustomCSoundPort,
|
||||
) -> music.Command:
|
||||
cmd = music.Command(command='delete_custom_csound_port')
|
||||
pb = cmd.Extensions[commands_registry_pb2.delete_custom_csound_port]
|
||||
pb.port_id = port.id
|
||||
return cmd
|
||||
|
|
|
@ -24,7 +24,12 @@ syntax = "proto2";
|
|||
|
||||
package noisicaa.pb;
|
||||
|
||||
message CustomCSoundPort {
|
||||
optional string csound_name = 1;
|
||||
}
|
||||
|
||||
message CustomCSound {
|
||||
optional string orchestra = 1;
|
||||
optional string score = 2;
|
||||
repeated uint64 ports = 3;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
#
|
||||
# @end:license
|
||||
|
||||
from typing import Any
|
||||
import logging
|
||||
from typing import Any, Dict, Sequence
|
||||
|
||||
from noisicaa import core
|
||||
from noisicaa import node_db
|
||||
|
@ -28,6 +29,51 @@ from noisicaa import model
|
|||
from noisicaa.builtin_nodes import model_registry_pb2
|
||||
from . import node_description
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomCSoundPort(model.Port):
|
||||
class CustomCSoundPortSpec(model.ObjectSpec):
|
||||
proto_type = 'custom_csound_port'
|
||||
proto_ext = model_registry_pb2.custom_csound_port
|
||||
|
||||
csound_name = model.Property(str, allow_none=True)
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.csound_name_changed = core.Callback[model.PropertyChange[str]]()
|
||||
|
||||
@property
|
||||
def csound_name(self) -> str:
|
||||
return self.get_property_value('csound_name')
|
||||
|
||||
def csound_name_prefix(self, *, type: node_db.PortDescription.Type = None) -> str: # pylint: disable=redefined-builtin
|
||||
if type is None:
|
||||
type = self.type
|
||||
|
||||
if type == node_db.PortDescription.KRATE_CONTROL:
|
||||
return 'gk'
|
||||
elif type in (node_db.PortDescription.ARATE_CONTROL,
|
||||
node_db.PortDescription.AUDIO):
|
||||
return 'ga'
|
||||
else:
|
||||
assert type == node_db.PortDescription.EVENTS
|
||||
return ''
|
||||
|
||||
def csound_name_default(
|
||||
self, *, name: str = None, type: node_db.PortDescription.Type = None) -> str: # pylint: disable=redefined-builtin
|
||||
if name is None:
|
||||
name = self.name
|
||||
|
||||
if type is None:
|
||||
type = self.type
|
||||
|
||||
if type == node_db.PortDescription.EVENTS:
|
||||
return '1'
|
||||
else:
|