Browse Source

Allow nodes to have dynamic list of ports.

- Currently only used for CustomCSound nodes.
- Bring back node parameters
- Add a ObjectListEditor widget.
looper
Ben Niemann 4 years ago
parent
commit
c3e5c3a88d
  1. 2
      3rdparty/protoc/setup.py
  2. 12
      3rdparty/typeshed/PyQt5/QtWidgets.pyi
  3. 6
      CMakeLists.txt
  4. 20
      listdeps
  5. 3
      noisicaa/audioproc/__init__.py
  6. 13
      noisicaa/audioproc/audioproc.proto
  7. 13
      noisicaa/audioproc/audioproc_process.py
  8. 6
      noisicaa/audioproc/engine/CMakeLists.txt
  9. 15
      noisicaa/audioproc/engine/csound_util.cpp
  10. 1
      noisicaa/audioproc/engine/csound_util.h
  11. 195
      noisicaa/audioproc/engine/graph.py
  12. 3
      noisicaa/audioproc/engine/plugin_host_process.py
  13. 19
      noisicaa/audioproc/engine/processor.cpp
  14. 7
      noisicaa/audioproc/engine/processor.h
  15. 1
      noisicaa/audioproc/engine/processor.pxd
  16. 5
      noisicaa/audioproc/engine/processor.pyi
  17. 5
      noisicaa/audioproc/engine/processor.pyx
  18. 8
      noisicaa/audioproc/engine/processor_csound_base.cpp
  19. 12
      noisicaa/audioproc/engine/processor_plugin.cpp
  20. 2
      noisicaa/audioproc/engine/processor_plugin.h
  21. 8
      noisicaa/audioproc/engine/processor_plugin.proto
  22. 24
      noisicaa/audioproc/engine/processor_plugin_test.py
  23. 3
      noisicaa/audioproc/public/CMakeLists.txt
  24. 3
      noisicaa/audioproc/public/__init__.py
  25. 5
      noisicaa/audioproc/public/node_parameters.proto
  26. 1
      noisicaa/builtin_nodes/CMakeLists.txt
  27. 3
      noisicaa/builtin_nodes/client_registry.py
  28. 3
      noisicaa/builtin_nodes/commands_registry.proto
  29. 12
      noisicaa/builtin_nodes/custom_csound/CMakeLists.txt
  30. 18
      noisicaa/builtin_nodes/custom_csound/client_impl.py
  31. 22
      noisicaa/builtin_nodes/custom_csound/client_impl_test.py
  32. 15
      noisicaa/builtin_nodes/custom_csound/commands.proto
  33. 35
      noisicaa/builtin_nodes/custom_csound/commands.py
  34. 5
      noisicaa/builtin_nodes/custom_csound/model.proto
  35. 149
      noisicaa/builtin_nodes/custom_csound/model.py
  36. 47
      noisicaa/builtin_nodes/custom_csound/node_description.py
  37. 314
      noisicaa/builtin_nodes/custom_csound/node_ui.py
  38. 249
      noisicaa/builtin_nodes/custom_csound/node_ui_test.py
  39. 103
      noisicaa/builtin_nodes/custom_csound/processor.cpp
  40. 3
      noisicaa/builtin_nodes/custom_csound/processor.h
  41. 36
      noisicaa/builtin_nodes/custom_csound/processor.proto
  42. 32
      noisicaa/builtin_nodes/custom_csound/processor_messages.py
  43. 108
      noisicaa/builtin_nodes/custom_csound/processor_test.py
  44. 129
      noisicaa/builtin_nodes/custom_csound/server_impl.py
  45. 39
      noisicaa/builtin_nodes/custom_csound/server_impl_test.py
  46. 9
      noisicaa/builtin_nodes/instrument/processor.cpp
  47. 1
      noisicaa/builtin_nodes/model_registry.proto
  48. 4
      noisicaa/builtin_nodes/processor_message_registry.proto
  49. 4
      noisicaa/builtin_nodes/server_registry.py
  50. 1
      noisicaa/core/callbacks.py
  51. 1
      noisicaa/model/__init__.py
  52. 10
      noisicaa/model/project.proto
  53. 71
      noisicaa/model/project.py
  54. 2
      noisicaa/music/__init__.py
  55. 12
      noisicaa/music/commands.proto
  56. 67
      noisicaa/music/graph.py
  57. 2
      noisicaa/music/mutations.proto
  58. 5
      noisicaa/music/mutations.py
  59. 43
      noisicaa/music/pmodel.py
  60. 1
      noisicaa/music/project.py
  61. 22
      noisicaa/music/project_client.py
  62. 4
      noisicaa/music/project_client_model.py
  63. 2
      noisicaa/node_db/node_description.proto
  64. 2
      noisicaa/node_db/private/csound_scanner.py
  65. 1
      noisicaa/ui/CMakeLists.txt
  66. 34
      noisicaa/ui/control_value_dial.py
  67. 2
      noisicaa/ui/graph/__init__.py
  68. 49
      noisicaa/ui/graph/base_node.py
  69. 34
      noisicaa/ui/graph/generic_node.py
  70. 317
      noisicaa/ui/object_list_editor.py
  71. 4
      noisicaa/ui/project_view.py
  72. 2
      noisicaa/ui/track_list/toolbox.py
  73. 24
      noisicaa/ui/ui_base.py
  74. 14
      noisidev/uitest.py

2
3rdparty/protoc/setup.py vendored

@ -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

12
3rdparty/typeshed/PyQt5/QtWidgets.pyi vendored

@ -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: ...

6
CMakeLists.txt

@ -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

@ -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'),

3
noisicaa/audioproc/__init__.py

@ -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,
)

13
noisicaa/audioproc/audioproc.proto

@ -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;
}
}

13
noisicaa/audioproc/audioproc_process.py

@ -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)

6
noisicaa/audioproc/engine/CMakeLists.txt

@ -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)

15
noisicaa/audioproc/engine/csound_util.cpp

@ -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);

1
noisicaa/audioproc/engine/csound_util.h

@ -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);

195
noisicaa/audioproc/engine/graph.py

@ -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('csound_instr'):
kwargs['csound_instr'] = port_desc.csound_instr
port = port_cls(description=port_desc, **kwargs)
port.owner = self
self.ports.append(port)
if port_desc.direction == node_db.PortDescription.INPUT:
self.inputs[port.name] = port
else:
self.outputs[port.name] = 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
port = port_cls(description=port_desc, **kwargs)
port.owner = self
return 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)

3
noisicaa/audioproc/engine/plugin_host_process.py

@ -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())

19
noisicaa/audioproc/engine/processor.cpp

@ -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();
}

7
noisicaa/audioproc/engine/processor.h

@ -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:

1
noisicaa/audioproc/engine/processor.pxd

@ -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)

5
noisicaa/audioproc/engine/processor.pyi

@ -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: ...

5
noisicaa/audioproc/engine/processor.pyx

@ -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))

8
noisicaa/audioproc/engine/processor_csound_base.cpp

@ -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));

12
noisicaa/audioproc/engine/processor_plugin.cpp

@ -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();

2
noisicaa/audioproc/engine/processor_plugin.h

@ -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;

8
noisicaa/audioproc/engine/processor.proto → noisicaa/audioproc/engine/processor_plugin.proto

@ -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;
}

24
noisicaa/audioproc/engine/processor_plugin_test.py

@ -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

3
noisicaa/audioproc/public/CMakeLists.txt

@ -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)

3
noisicaa/audioproc/public/__init__.py

@ -66,3 +66,6 @@ from .host_parameters_pb2 import (
from .node_port_properties_pb2 import (
NodePortProperties,
)
from .node_parameters_pb2 import (
NodeParameters,
)

5
noisicaa/builtin_nodes/custom_csound/processor_messages.proto → noisicaa/audioproc/public/node_parameters.proto

@ -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;
}

1
noisicaa/builtin_nodes/CMakeLists.txt

@ -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)

3
noisicaa/builtin_nodes/client_registry.py

@ -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)

3
noisicaa/builtin_nodes/commands_registry.proto

@ -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;

12
noisicaa/builtin_nodes/custom_csound/CMakeLists.txt

@ -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)

18
noisicaa/builtin_nodes/custom_csound/client_impl.py

@ -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

22
noisicaa/builtin_nodes/custom_csound/client_impl_test.py

@ -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)

15
noisicaa/builtin_nodes/custom_csound/commands.proto

@ -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;
}

35
noisicaa/builtin_nodes/custom_csound/commands.py

@ -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

5
noisicaa/builtin_nodes/custom_csound/model.proto

@ -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;
}

149
noisicaa/builtin_nodes/custom_csound/model.py

@ -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