Optionally "expose" control values as input ports, so they can be connected to other nodes.
Also: - Also add ControlValueDials for a-rate control ports (and make them exposeable). - Fix exception in UI when removing nodes with connections. - Fix exception in PluginHost when cleaning up some LV2 plugins. - Dump audio engine opcode list to log.looper
parent
901ed7e066
commit
6d52afb424
|
@ -27,17 +27,20 @@ import sip
|
|||
import datetime
|
||||
|
||||
# Support for new-style signals and slots.
|
||||
class pyqtConnection:
|
||||
pass
|
||||
|
||||
class pyqtSignal:
|
||||
def __init__(self, *types, name: str = ...) -> None: ...
|
||||
def connect(self, slot: typing.Callable) -> None: ...
|
||||
def disconnect(self, slot: typing.Optional[typing.Callable] = None) -> None: ...
|
||||
def connect(self, slot: typing.Callable) -> pyqtConnection: ...
|
||||
def disconnect(self, slot: typing.Optional[typing.Union[typing.Callable, pyqtConnection]] = None) -> None: ...
|
||||
def emit(self, *args: typing.Any) -> None: ...
|
||||
def __call__(self, *args: typing.Any) -> None: ...
|
||||
def __get__(self, instance: 'QObject', owner: typing.Type['QObject'] = None) -> 'pyqtBoundSignal': ...
|
||||
|
||||
class pyqtBoundSignal:
|
||||
def connect(self, slot: typing.Callable) -> None: ...
|
||||
def disconnect(self, slot: typing.Optional[typing.Callable] = None) -> None: ...
|
||||
def connect(self, slot: typing.Callable) -> pyqtConnection: ...
|
||||
def disconnect(self, slot: typing.Optional[typing.Union[typing.Callable, pyqtConnection]] = None) -> None: ...
|
||||
def emit(self, *args: typing.Any) -> None: ...
|
||||
def __call__(self, *args: typing.Any) -> None: ...
|
||||
|
||||
|
|
|
@ -26,5 +26,7 @@ install_files(
|
|||
butterhp.csnd
|
||||
butterlp.csnd
|
||||
delay.csnd
|
||||
lfo-arate.csnd
|
||||
lfo-krate.csnd
|
||||
reverb.csnd
|
||||
)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
@begin:license
|
||||
|
||||
Copyright (c) 2015-2019, 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
|
||||
-->
|
||||
|
||||
<csound>
|
||||
<display-name>LFO (a-rate)</display-name>
|
||||
<ports>
|
||||
<port name="out" type="aratecontrol" direction="output"/>
|
||||
<port name="freq" type="kratecontrol" direction="input">
|
||||
<float-control min="0" max="1000" default="1"/>
|
||||
<display-name>Frequency</display-name>
|
||||
</port>
|
||||
<port name="amp" type="kratecontrol" direction="input">
|
||||
<float-control min="0" max="1" default="1"/>
|
||||
<display-name>Amplitude</display-name>
|
||||
</port>
|
||||
</ports>
|
||||
<orchestra>
|
||||
0dbfs = 1.0
|
||||
ksmps = 32
|
||||
nchnls = 2
|
||||
|
||||
gaOut chnexport "out", 2
|
||||
gkFreq chnexport "freq", 1
|
||||
gkAmp chnexport "amp", 1
|
||||
|
||||
instr 1
|
||||
gaOut = lfo(gkAmp, gkFreq, 4)
|
||||
endin
|
||||
|
||||
</orchestra>
|
||||
<score>
|
||||
i1 0 -1
|
||||
</score>
|
||||
</csound>
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
@begin:license
|
||||
|
||||
Copyright (c) 2015-2019, 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
|
||||
-->
|
||||
|
||||
<csound>
|
||||
<display-name>LFO (k-rate)</display-name>
|
||||
<ports>
|
||||
<port name="out" type="kratecontrol" direction="output"/>
|
||||
<port name="freq" type="kratecontrol" direction="input">
|
||||
<float-control min="0" max="1000" default="1"/>
|
||||
<display-name>Frequency</display-name>
|
||||
</port>
|
||||
<port name="amp" type="kratecontrol" direction="input">
|
||||
<float-control min="0" max="1" default="1"/>
|
||||
<display-name>Amplitude</display-name>
|
||||
</port>
|
||||
</ports>
|
||||
<orchestra>
|
||||
0dbfs = 1.0
|
||||
ksmps = 32
|
||||
nchnls = 2
|
||||
|
||||
gkOut chnexport "out", 2
|
||||
gkFreq chnexport "freq", 1
|
||||
gkAmp chnexport "amp", 1
|
||||
|
||||
instr 1
|
||||
gkOut = lfo(gkAmp, gkFreq, 4)
|
||||
endin
|
||||
|
||||
</orchestra>
|
||||
<score>
|
||||
i1 0 -1
|
||||
</score>
|
||||
</csound>
|
|
@ -28,6 +28,7 @@ from .audioproc_pb2 import (
|
|||
DisconnectPorts,
|
||||
SetControlValue,
|
||||
SetPluginState,
|
||||
SetNodePortProperties,
|
||||
)
|
||||
from .audioproc_client import (
|
||||
AbstractAudioProcClient,
|
||||
|
@ -55,4 +56,5 @@ from .public import (
|
|||
ProjectProperties,
|
||||
BackendSettings,
|
||||
HostParameters,
|
||||
NodePortProperties,
|
||||
)
|
||||
|
|
|
@ -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_port_properties.proto";
|
||||
import "noisicaa/audioproc/public/player_state.proto";
|
||||
import "noisicaa/audioproc/public/plugin_state.proto";
|
||||
import "noisicaa/audioproc/public/processor_message.proto";
|
||||
|
@ -71,6 +72,11 @@ message SetPluginState {
|
|||
required noisicaa.pb.PluginState state = 2;
|
||||
}
|
||||
|
||||
message SetNodePortProperties {
|
||||
required string node_id = 1;
|
||||
required noisicaa.pb.NodePortProperties port_properties = 2;
|
||||
}
|
||||
|
||||
message Mutation {
|
||||
oneof type {
|
||||
AddNode add_node = 1;
|
||||
|
@ -79,6 +85,7 @@ message Mutation {
|
|||
DisconnectPorts disconnect_ports = 4;
|
||||
SetControlValue set_control_value = 5;
|
||||
SetPluginState set_plugin_state = 6;
|
||||
SetNodePortProperties set_node_port_properties = 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,9 @@ class AbstractAudioProcClient(object):
|
|||
async def profile_audio_thread(self, duration: int) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
async def dump(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AudioProcClient(AbstractAudioProcClient):
|
||||
def __init__(self, event_loop: asyncio.AbstractEventLoop, server: ipc.Server) -> None:
|
||||
|
@ -353,3 +356,6 @@ class AudioProcClient(AbstractAudioProcClient):
|
|||
audioproc_pb2.UpdateProjectPropertiesRequest(
|
||||
realm=realm,
|
||||
properties=properties))
|
||||
|
||||
async def dump(self) -> None:
|
||||
await self._stub.call('DUMP')
|
||||
|
|
|
@ -175,6 +175,9 @@ class AudioProcProcess(core.ProcessBase):
|
|||
self.__main_endpoint.add_handler(
|
||||
'PROFILE_AUDIO_THREAD', self.__handle_profile_audio_thread,
|
||||
audioproc_pb2.ProfileAudioThreadRequest, audioproc_pb2.ProfileAudioThreadResponse)
|
||||
self.__main_endpoint.add_handler(
|
||||
'DUMP', self.__handle_dump,
|
||||
empty_message_pb2.EmptyMessage, empty_message_pb2.EmptyMessage)
|
||||
await self.server.add_endpoint(self.__main_endpoint)
|
||||
|
||||
if self.shm_name is not None:
|
||||
|
@ -341,6 +344,12 @@ class AudioProcProcess(core.ProcessBase):
|
|||
set_plugin_state.node_id,
|
||||
set_plugin_state.state)
|
||||
|
||||
elif mutation_type == 'set_node_port_properties':
|
||||
set_node_port_properties = request.mutation.set_node_port_properties
|
||||
node = graph.find_node(set_node_port_properties.node_id)
|
||||
node.set_port_properties(set_node_port_properties.port_properties)
|
||||
realm.update_spec()
|
||||
|
||||
else:
|
||||
raise ValueError(request.mutation)
|
||||
|
||||
|
@ -498,6 +507,17 @@ class AudioProcProcess(core.ProcessBase):
|
|||
|
||||
response.svg = svg
|
||||
|
||||
async def __handle_dump(
|
||||
self,
|
||||
session: Session,
|
||||
request: empty_message_pb2.EmptyMessage,
|
||||
response: empty_message_pb2.EmptyMessage
|
||||
) -> None:
|
||||
if self.__engine is not None:
|
||||
logger.error("\n%s", self.__engine.dump())
|
||||
else:
|
||||
logger.error("No engine.")
|
||||
|
||||
|
||||
class AudioProcSubprocess(core.SubprocessMixin, AudioProcProcess):
|
||||
pass
|
||||
|
|
|
@ -202,6 +202,12 @@ cdef class PyEngine(object):
|
|||
|
||||
self.__engine_started = None
|
||||
|
||||
def dump(self):
|
||||
out = ""
|
||||
for _, realm in sorted(self.__realms.items()):
|
||||
out += realm.dump()
|
||||
return out
|
||||
|
||||
async def get_plugin_host(self):
|
||||
if self.__plugin_host is None:
|
||||
create_plugin_host_response = editor_main_pb2.CreateProcessResponse()
|
||||
|
|
|
@ -30,6 +30,7 @@ from noisicaa import node_db
|
|||
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 processor_message_pb2
|
||||
from . import control_value
|
||||
from . import processor as processor_lib
|
||||
|
@ -239,6 +240,7 @@ class Node(object):
|
|||
self.outputs = {} # type: Dict[str, OutputPortMixin]
|
||||
|
||||
self.__control_values = {} # type: Dict[str, control_value.PyControlValue]
|
||||
self.__port_properties = {} # type: Dict[str, node_port_properties_pb2.NodePortProperties]
|
||||
|
||||
if self.init_ports_from_description:
|
||||
self.init_ports()
|
||||
|
@ -331,7 +333,7 @@ class Node(object):
|
|||
logger.info("%s: setup()", self.name)
|
||||
|
||||
for port in self.ports:
|
||||
if isinstance(port, KRateControlInputPort):
|
||||
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)
|
||||
|
@ -348,6 +350,16 @@ class Node(object):
|
|||
def set_session_value(self, key: str, value: session_data_pb2.SessionValue) -> None:
|
||||
pass
|
||||
|
||||
def get_port_properties(self, port_name: str) -> node_port_properties_pb2.NodePortProperties:
|
||||
try:
|
||||
return self.__port_properties[port_name]
|
||||
except KeyError:
|
||||
return node_port_properties_pb2.NodePortProperties(name=port_name)
|
||||
|
||||
def set_port_properties(
|
||||
self, port_properties: node_port_properties_pb2.NodePortProperties) -> None:
|
||||
self.__port_properties[port_properties.name] = port_properties
|
||||
|
||||
@property
|
||||
def control_values(self) -> List[control_value.PyControlValue]:
|
||||
return [v for _, v in sorted(self.__control_values.items())]
|
||||
|
@ -357,11 +369,21 @@ class Node(object):
|
|||
spec.append_control_value(cv)
|
||||
|
||||
for port in self.ports:
|
||||
port_properties = self.get_port_properties(port.name)
|
||||
|
||||
spec.append_buffer(port.buf_name, port.get_buf_type())
|
||||
|
||||
if port.buf_name in self.__control_values:
|
||||
spec.append_opcode(
|
||||
'FETCH_CONTROL_VALUE', self.__control_values[port.buf_name], port.buf_name)
|
||||
if port.buf_name in self.__control_values and not port_properties.exposed:
|
||||
if isinstance(port, KRateControlPortMixin):
|
||||
spec.append_opcode(
|
||||
'FETCH_CONTROL_VALUE',
|
||||
self.__control_values[port.buf_name], port.buf_name)
|
||||
else:
|
||||
assert isinstance(port, ARateControlPortMixin)
|
||||
spec.append_opcode(
|
||||
'FETCH_CONTROL_VALUE_TO_AUDIO',
|
||||
self.__control_values[port.buf_name], port.buf_name)
|
||||
|
||||
elif isinstance(port, InputPortMixin):
|
||||
spec.append_opcode('CLEAR', port.buf_name)
|
||||
for upstream_port in port.inputs:
|
||||
|
|
|
@ -105,6 +105,29 @@ Status run_FETCH_CONTROL_VALUE(BlockContext* ctxt, ProgramState* state, const ve
|
|||
}
|
||||
}
|
||||
|
||||
Status run_FETCH_CONTROL_VALUE_TO_AUDIO(
|
||||
BlockContext* ctxt, ProgramState* state, const vector<OpArg>& args) {
|
||||
int cv_idx = args[0].int_value();
|
||||
int buf_idx = args[1].int_value();
|
||||
ControlValue* cv = state->program->spec->get_control_value(cv_idx);
|
||||
Buffer* buf = state->program->buffers[buf_idx].get();
|
||||
|
||||
switch (cv->type()) {
|
||||
case ControlValueType::FloatCV: {
|
||||
FloatControlValue* fcv = (FloatControlValue*)cv;
|
||||
float* data = (float*)buf->data();
|
||||
for (uint32_t i = 0 ; i < state->host_system->block_size() ; ++i) {
|
||||
*data++ = fcv->value();
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
case ControlValueType::IntCV:
|
||||
return ERROR_STATUS("IntControlValue not implemented yet.");
|
||||
default:
|
||||
return ERROR_STATUS("Invalid ControlValue type %d.", cv->type());
|
||||
}
|
||||
}
|
||||
|
||||
Status run_POST_RMS(BlockContext* ctxt, ProgramState* state, const vector<OpArg>& args) {
|
||||
const string& node_id = args[0].string_value();
|
||||
int port_index = args[1].int_value();
|
||||
|
@ -310,6 +333,7 @@ struct OpSpec opspecs[NUM_OPCODES] = {
|
|||
|
||||
// I/O
|
||||
{ OpCode::FETCH_CONTROL_VALUE, "FETCH_CONTROL_VALUE", "cb", nullptr, run_FETCH_CONTROL_VALUE },
|
||||
{ OpCode::FETCH_CONTROL_VALUE_TO_AUDIO, "FETCH_CONTROL_VALUE_TO_AUDIO", "cb", nullptr, run_FETCH_CONTROL_VALUE_TO_AUDIO },
|
||||
{ OpCode::POST_RMS, "POST_RMS", "sib", nullptr, run_POST_RMS },
|
||||
|
||||
// generators
|
||||
|
|
|
@ -52,6 +52,7 @@ enum OpCode {
|
|||
|
||||
// I/O
|
||||
FETCH_CONTROL_VALUE,
|
||||
FETCH_CONTROL_VALUE_TO_AUDIO,
|
||||
POST_RMS,
|
||||
|
||||
// generators
|
||||
|
|
|
@ -36,6 +36,7 @@ cdef extern from "noisicaa/audioproc/engine/opcodes.h" namespace "noisicaa" nogi
|
|||
MUL
|
||||
SET_FLOAT
|
||||
FETCH_CONTROL_VALUE
|
||||
FETCH_CONTROL_VALUE_TO_AUDIO
|
||||
POST_RMS
|
||||
NOISE
|
||||
SINE
|
||||
|
|
|
@ -26,6 +26,7 @@ import functools
|
|||
import logging
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
import typing
|
||||
from typing import Any, Dict, Tuple
|
||||
import uuid
|
||||
|
@ -183,19 +184,23 @@ class PluginHost(plugin_host.PyPluginHost):
|
|||
self.__thread_result.set_result(True)
|
||||
|
||||
async def __state_fetcher_main(self) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(1.0, loop=self.__event_loop)
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(1.0, loop=self.__event_loop)
|
||||
|
||||
state = self.get_state()
|
||||
if state != self.__state:
|
||||
self.__state = state
|
||||
logger.info("Plugin state for %s changed:\n%s", self.__node_id, self.__state)
|
||||
await asyncio.shield(
|
||||
self.__callback_stub.call(
|
||||
'PLUGIN_STATE_CHANGE',
|
||||
audioproc_pb2.PluginStateChange(
|
||||
realm=self.__realm, node_id=self.__node_id, state=self.__state)),
|
||||
loop=self.__event_loop)
|
||||
state = self.get_state()
|
||||
if state != self.__state:
|
||||
self.__state = state
|
||||
logger.info("Plugin state for %s changed:\n%s", self.__node_id, self.__state)
|
||||
await asyncio.shield(
|
||||
self.__callback_stub.call(
|
||||
'PLUGIN_STATE_CHANGE',
|
||||
audioproc_pb2.PluginStateChange(
|
||||
realm=self.__realm, node_id=self.__node_id, state=self.__state)),
|
||||
loop=self.__event_loop)
|
||||
|
||||
except: # pylint: disable=bare-except
|
||||
logger.error("Exception in state fetcher:\n%s", traceback.format_exc())
|
||||
|
||||
|
||||
class PluginHostProcess(core.ProcessBase):
|
||||
|
|
|
@ -178,6 +178,19 @@ void Realm::cleanup() {
|
|||
_block_context.reset();
|
||||
}
|
||||
|
||||
string Realm::dump() const {
|
||||
string out = sprintf("=== Realm %s:\n", _name.c_str());
|
||||
|
||||
Program* program = _current_program.load();
|
||||
if (program != nullptr) {
|
||||
out += program->spec->dump();
|
||||
} else {
|
||||
out += "No current program.\n";
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void Realm::clear_programs() {
|
||||
Program* program = _next_program.exchange(nullptr);
|
||||
if (program != nullptr) {
|
||||
|
|
|
@ -115,6 +115,7 @@ public:
|
|||
Status setup();
|
||||
void cleanup() override;
|
||||
|
||||
string dump() const;
|
||||
void clear_programs();
|
||||
|
||||
void set_notification_callback(
|
||||
|
|
|
@ -44,6 +44,7 @@ cdef extern from "noisicaa/audioproc/engine/realm.h" namespace "noisicaa" nogil:
|
|||
|
||||
Status setup()
|
||||
void cleanup()
|
||||
string dump()
|
||||
void clear_programs()
|
||||
void set_notification_callback(
|
||||
void (*callback)(void*, const string&), void* userdata);
|
||||
|
|
|
@ -24,6 +24,7 @@ from cpython.ref cimport PyObject
|
|||
from cpython.exc cimport PyErr_Fetch, PyErr_Restore
|
||||
from libc.stdint cimport uint8_t, uint32_t
|
||||
from libc.string cimport memmove
|
||||
from libcpp.string cimport string
|
||||
|
||||
from noisicaa import core
|
||||
from noisicaa import audioproc
|
||||
|
@ -147,6 +148,12 @@ cdef class PyRealm(object):
|
|||
|
||||
logger.info("Realm '%s' cleaned up.", self.name)
|
||||
|
||||
def dump(self):
|
||||
cdef string out
|
||||
with nogil:
|
||||
out = self.__realm.dump()
|
||||
return out.decode('ascii')
|
||||
|
||||
def clear_programs(self):
|
||||
with nogil:
|
||||
self.__realm.clear_programs()
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "noisicaa/core/logging.h"
|
||||
#include "noisicaa/audioproc/engine/spec.h"
|
||||
#include "noisicaa/audioproc/engine/control_value.h"
|
||||
#include "noisicaa/audioproc/engine/processor.h"
|
||||
|
@ -38,6 +39,55 @@ Spec::~Spec() {
|
|||
_buffer_map.clear();
|
||||
}
|
||||
|
||||
string Spec::dump() const {
|
||||
string out = "";
|
||||
|
||||
int i = 0;
|
||||
for (const auto& opcode : _opcodes) {
|
||||
const auto& opspec = opspecs[opcode.opcode];
|
||||
|
||||
string args = "";
|
||||
for (size_t a = 0 ; a < opcode.args.size() ; ++a) {
|
||||
const auto& arg = opcode.args[a];
|
||||
|
||||
if (a > 0) {
|
||||
args += ", ";
|
||||
}
|
||||
|
||||
switch (opspec.argspec[a]) {
|
||||
case 'i':
|
||||
args += sprintf("%ld", arg.int_value());
|
||||
break;
|
||||
case 'b': {
|
||||
args += sprintf("#BUF<%d>", arg.int_value());
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
Processor* processor = _processors[arg.int_value()];
|
||||
args += sprintf("#PROC<%016lx>", processor->id());
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
ControlValue* cv = _control_values[arg.int_value()];
|
||||
args += sprintf("#CV<%s>", cv->name().c_str());
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
args += sprintf("%f", arg.float_value());
|
||||
break;
|
||||
case 's':
|
||||
args += sprintf("\"%s\"", arg.string_value().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out += sprintf("% 3d %s(%s)\n", i, opspec.name, args.c_str());
|
||||
++i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Status Spec::append_opcode(OpCode opcode, ...) {
|
||||
vector<OpArg> args;
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ namespace noisicaa {
|
|||
|
||||
using namespace std;
|
||||
|
||||
class Logger;
|
||||
class Processor;
|
||||
class ControlValue;
|
||||
class BufferType;
|
||||
|
@ -55,6 +56,8 @@ public:
|
|||
Spec(const Spec&) = delete;
|
||||
Spec operator=(const Spec&) = delete;
|
||||
|
||||
string dump() const;
|
||||
|
||||
void set_bpm(uint32_t bpm) { _bpm = bpm; }
|
||||
uint32_t bpm() const { return _bpm; }
|
||||
|
||||
|
|
|
@ -32,23 +32,24 @@ from .realm cimport PyRealm
|
|||
|
||||
|
||||
opcode_map = {
|
||||
'NOOP': OpCode.NOOP,
|
||||
'END': OpCode.END,
|
||||
'CALL_CHILD_REALM': OpCode.CALL_CHILD_REALM,
|
||||
'COPY': OpCode.COPY,
|
||||
'CLEAR': OpCode.CLEAR,
|
||||
'MIX': OpCode.MIX,
|
||||
'MUL': OpCode.MUL,
|
||||
'SET_FLOAT': OpCode.SET_FLOAT,
|
||||
'FETCH_CONTROL_VALUE': OpCode.FETCH_CONTROL_VALUE,
|
||||
'POST_RMS': OpCode.POST_RMS,
|
||||
'NOISE': OpCode.NOISE,
|
||||
'SINE': OpCode.SINE,
|
||||
'MIDI_MONKEY': OpCode.MIDI_MONKEY,
|
||||
'CONNECT_PORT': OpCode.CONNECT_PORT,
|
||||
'CALL': OpCode.CALL,
|
||||
'LOG_RMS': OpCode.LOG_RMS,
|
||||
'LOG_ATOM': OpCode.LOG_ATOM,
|
||||
'NOOP': OpCode.NOOP,
|
||||
'END': OpCode.END,
|
||||
'CALL_CHILD_REALM': OpCode.CALL_CHILD_REALM,
|
||||
'COPY': OpCode.COPY,
|
||||
'CLEAR': OpCode.CLEAR,
|
||||
'MIX': OpCode.MIX,
|
||||
'MUL': OpCode.MUL,
|
||||
'SET_FLOAT': OpCode.SET_FLOAT,
|
||||
'FETCH_CONTROL_VALUE': OpCode.FETCH_CONTROL_VALUE,
|
||||
'FETCH_CONTROL_VALUE_TO_AUDIO': OpCode.FETCH_CONTROL_VALUE_TO_AUDIO,
|
||||
'POST_RMS': OpCode.POST_RMS,
|
||||
'NOISE': OpCode.NOISE,
|
||||
'SINE': OpCode.SINE,
|
||||
'MIDI_MONKEY': OpCode.MIDI_MONKEY,
|
||||
'CONNECT_PORT': OpCode.CONNECT_PORT,
|
||||
'CALL': OpCode.CALL,
|
||||
'LOG_RMS': OpCode.LOG_RMS,
|
||||
'LOG_ATOM': OpCode.LOG_ATOM,
|
||||
}
|
||||
|
||||
opname = {
|
||||
|
|
|
@ -29,6 +29,7 @@ set(LIB_SRCS
|
|||
instrument_spec.pb.cc
|
||||
musical_time.cpp
|
||||
musical_time.pb.cc
|
||||
node_port_properties.pb.cc
|
||||
player_state.pb.cc
|
||||
plugin_state.pb.cc
|
||||
processor_message.pb.cc
|
||||
|
@ -63,6 +64,8 @@ cpp_proto(host_parameters.proto)
|
|||
py_proto(host_parameters.proto)
|
||||
cpp_proto(project_properties.proto)
|
||||
py_proto(project_properties.proto)
|
||||
cpp_proto(node_port_properties.proto)
|
||||
py_proto(node_port_properties.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)
|
||||
|
|
|
@ -63,3 +63,6 @@ from .backend_settings_pb2 import (
|
|||
from .host_parameters_pb2 import (
|
||||
HostParameters,
|
||||
)
|
||||
from .node_port_properties_pb2 import (
|
||||
NodePortProperties,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* @begin:license
|
||||
*
|
||||
* Copyright (c) 2015-2019, 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
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package noisicaa.pb;
|
||||
|
||||
message NodePortProperties {
|
||||
optional string name = 1;
|
||||
optional bool exposed = 2;
|
||||
}
|
|
@ -45,8 +45,8 @@ CustomCSoundDescription = node_db.NodeDescription(
|
|||
type=node_db.PortDescription.AUDIO,
|
||||
),
|
||||
node_db.PortDescription(
|
||||
name='ctrl',
|
||||
display_name='ctrl',
|
||||
name='kctrl',
|
||||
display_name='ctrl (k-rate)',
|
||||
direction=node_db.PortDescription.INPUT,
|
||||
type=node_db.PortDescription.KRATE_CONTROL,
|
||||
float_value=node_db.FloatValueDescription(
|
||||
|
@ -54,6 +54,16 @@ CustomCSoundDescription = node_db.NodeDescription(
|
|||
max=1.0,
|
||||
default=0.0),
|
||||
),
|
||||
node_db.PortDescription(
|
||||
name='actrl',
|
||||
display_name='ctrl (a-rate)',
|
||||
direction=node_db.PortDescription.INPUT,
|
||||
type=node_db.PortDescription.ARATE_CONTROL,
|
||||
float_value=node_db.FloatValueDescription(
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=0.0),
|
||||
),
|
||||
node_db.PortDescription(
|
||||
name='ev',
|
||||
direction=node_db.PortDescription.INPUT,
|
||||
|
|
|
@ -49,7 +49,8 @@ class ProcessorCustomCSoundTest(
|
|||
|
||||
audio_l_in = self.buffer_mgr.allocate('in:left', buffers.PyFloatAudioBlockBuffer())
|
||||
audio_r_in = self.buffer_mgr.allocate('in:right', buffers.PyFloatAudioBlockBuffer())
|
||||
ctrl = self.buffer_mgr.allocate('ctrl', buffers.PyFloatControlValueBuffer())
|
||||
kctrl = self.buffer_mgr.allocate('kctrl', buffers.PyFloatControlValueBuffer())
|
||||
actrl = self.buffer_mgr.allocate('actrl', buffers.PyFloatAudioBlockBuffer())
|
||||
self.buffer_mgr.allocate('ev', buffers.PyAtomDataBuffer())
|
||||
audio_l_out = self.buffer_mgr.allocate('out:left', buffers.PyFloatAudioBlockBuffer())
|
||||
audio_r_out = self.buffer_mgr.allocate('out:right', buffers.PyFloatAudioBlockBuffer())
|
||||
|
@ -59,20 +60,22 @@ class ProcessorCustomCSoundTest(
|
|||
forge.set_buffer(self.buffer_mgr.data('ev'), 10240)
|
||||
with forge.sequence():
|
||||
pass
|
||||
ctrl[0] = 0.0
|
||||
kctrl[0] = 0.0
|
||||
for i in range(self.host_system.block_size):
|
||||
audio_l_in[i] = 0.0
|
||||
audio_r_in[i] = 0.0
|
||||
audio_l_out[i] = 0.0
|
||||
audio_r_out[i] = 0.0
|
||||
actrl[i] = 0.0
|
||||
|
||||
|
||||
proc.connect_port(self.ctxt, 0, self.buffer_mgr.data('in:left'))
|
||||
proc.connect_port(self.ctxt, 1, self.buffer_mgr.data('in:right'))
|
||||
proc.connect_port(self.ctxt, 2, self.buffer_mgr.data('ctrl'))
|
||||
proc.connect_port(self.ctxt, 3, self.buffer_mgr.data('ev'))
|
||||
proc.connect_port(self.ctxt, 4, self.buffer_mgr.data('out:left'))
|
||||
proc.connect_port(self.ctxt, 5, self.buffer_mgr.data('out:right'))
|
||||
proc.connect_port(self.ctxt, 2, self.buffer_mgr.data('kctrl'))
|
||||
proc.connect_port(self.ctxt, 3, self.buffer_mgr.data('actrl'))
|
||||
proc.connect_port(self.ctxt, 4, self.buffer_mgr.data('ev'))
|
||||
proc.connect_port(self.ctxt, 5, self.buffer_mgr.data('out:left'))
|
||||
proc.connect_port(self.ctxt, 6, self.buffer_mgr.data('out:right'))
|
||||
|
||||
return proc
|
||||
|
||||
|
@ -110,8 +113,8 @@ class ProcessorCustomCSoundTest(
|
|||
def test_filter(self):
|
||||
orchestra = textwrap.dedent('''\
|
||||
instr 1
|
||||
gaOutLeft = gkCtrl * gaInLeft
|
||||
gaOutRight = gkCtrl * gaInRight
|
||||
gaOutLeft = gkKctrl * gaInLeft
|
||||
gaOutRight = gkKctrl * gaInRight
|
||||
endin
|
||||
''')
|
||||
score = textwrap.dedent('''\
|
||||
|
@ -125,7 +128,7 @@ class ProcessorCustomCSoundTest(
|
|||
for i in range(self.host_system.block_size):
|
||||
audio_l_in[i] = 1.0
|
||||
audio_r_in[i] = 1.0
|
||||
self.buffer_mgr['ctrl'][0] = 0.5
|
||||
self.buffer_mgr['kctrl'][0] = 0.5
|
||||
|
||||
proc.process_block(self.ctxt, None) # TODO: pass time_mapper
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ add_python_package(
|
|||
control_value.py
|
||||
key_signature.py
|
||||
key_signature_test.py
|
||||
node_port_properties.py
|
||||
pos2f.py
|
||||
pitch.py
|
||||
pitch_test.py
|
||||
|
|
|
@ -46,6 +46,7 @@ from .pos2f import Pos2F
|
|||
from .sizef import SizeF
|
||||
from .color import Color
|
||||
from .control_value import ControlValue
|
||||
from .node_port_properties import NodePortProperties
|
||||
from .project import (
|
||||
ObjectBase,
|
||||
ProjectChild,
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# @begin:license
|
||||
#
|
||||
# Copyright (c) 2015-2019, 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
|
||||
|
||||
from google.protobuf import message as protobuf
|
||||
|
||||
from noisicaa import audioproc
|
||||
from . import model_base
|
||||
|
||||
|
||||
class NodePortProperties(model_base.ProtoValue):
|
||||
def __init__(self, name: str, *, exposed: bool = False) -> None:
|
||||
self.__name = name
|
||||
self.__exposed = exposed
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '<%s exposed=%s>' % (self.__name, self.__exposed)
|
||||
__repr__ = __str__
|
||||
|
||||
def to_proto(self) -> audioproc.NodePortProperties:
|
||||
return audioproc.NodePortProperties(
|
||||
name=self.__name,
|
||||
exposed=self.__exposed)
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, pb: protobuf.Message) -> 'NodePortProperties':
|
||||
if not isinstance(pb, audioproc.NodePortProperties):
|
||||
raise TypeError(type(pb).__name__)
|
||||
return NodePortProperties(
|
||||
name=pb.name,
|
||||
exposed=pb.exposed)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.__name
|
||||
|
||||
@property
|
||||
def exposed(self) -> bool:
|
||||
return self.__exposed
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, NodePortProperties):
|
||||
return False
|
||||
|
||||
return (
|
||||
self.__name == other.__name
|
||||
and self.__exposed == other.__exposed)
|
|
@ -26,6 +26,7 @@ package noisicaa.pb;
|
|||
|
||||
import "noisicaa/core/proto_types.proto";
|
||||
import "noisicaa/audioproc/public/control_value.proto";
|
||||
import "noisicaa/audioproc/public/node_port_properties.proto";
|
||||
import "noisicaa/audioproc/public/plugin_state.proto";
|
||||
import "noisicaa/model/model_base.proto";
|
||||
|
||||
|
@ -54,6 +55,7 @@ message BaseNode {
|
|||
optional Color graph_color = 6;
|
||||
repeated ControlValue control_values = 3;
|
||||
optional PluginState plugin_state = 4;
|
||||
repeated NodePortProperties port_properties = 7;
|
||||
}
|
||||
|
||||
message Node {
|
||||
|
|
|
@ -32,6 +32,7 @@ from . import pos2f
|
|||
from . import sizef
|
||||
from . import color
|
||||
from . import control_value
|
||||
from . import node_port_properties
|
||||
from . import model_base
|
||||
from . import project_pb2
|
||||
|
||||
|
@ -84,7 +85,6 @@ class ControlValueMap(object):
|
|||
|
||||
self.__initialized = False
|
||||
self.__control_values = {} # type: Dict[str, control_value.ControlValue]
|
||||
self.__control_value_listeners = [] # type: List[core.Listener]
|
||||
self.__control_values_listener = None # type: core.Listener
|
||||
|
||||
self.control_value_changed = core.CallbackMap[str, model_base.PropertyValueChange]()
|
||||
|
@ -101,7 +101,8 @@ class ControlValueMap(object):
|
|||
|
||||
for port in self.__node.description.ports:
|
||||
if (port.direction == node_db.PortDescription.INPUT
|
||||
and port.type == node_db.PortDescription.KRATE_CONTROL):
|
||||
and port.type in (node_db.PortDescription.KRATE_CONTROL,
|
||||
node_db.PortDescription.ARATE_CONTROL)):
|
||||
self.__control_values[port.name] = control_value.ControlValue(
|
||||
name=port.name, value=port.float_value.default, generation=1)
|
||||
|
||||
|
@ -158,6 +159,8 @@ class BaseNode(ProjectChild):
|
|||
color.Color, default=color.Color(0.8, 0.8, 0.8, 1.0))
|
||||
control_values = model_base.WrappedProtoListProperty(control_value.ControlValue)
|
||||
plugin_state = model_base.ProtoProperty(audioproc.PluginState, allow_none=True)
|
||||
port_properties = model_base.WrappedProtoListProperty(
|
||||
node_port_properties.NodePortProperties)
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
@ -170,6 +173,8 @@ class BaseNode(ProjectChild):
|
|||
core.Callback[model_base.PropertyListChange[control_value.ControlValue]]()
|
||||
self.plugin_state_changed = \
|
||||
core.Callback[model_base.PropertyChange[audioproc.PluginState]]()
|
||||
self.port_properties_changed = \
|
||||
core.Callback[model_base.PropertyListChange[node_port_properties.NodePortProperties]]()
|
||||
|
||||
self.control_value_map = ControlValueMap(self)
|
||||
|
||||
|
@ -177,6 +182,17 @@ class BaseNode(ProjectChild):
|
|||
def control_values(self) -> Sequence[control_value.ControlValue]:
|
||||
return self.get_property_value('control_values')
|
||||
|
||||
@property
|
||||
def port_properties(self) -> Sequence[node_port_properties.NodePortProperties]:
|
||||
return self.get_property_value('port_properties')
|
||||
|
||||
def get_port_properties(self, port_name: str) -> node_port_properties.NodePortProperties:
|
||||
for np in self.port_properties:
|
||||
if np.name == port_name:
|
||||
return np
|
||||
|
||||
return node_port_properties.NodePortProperties(port_name)
|
||||
|
||||
@property
|
||||
def pipeline_node_id(self) -> str:
|
||||
return '%016x' % self.id
|
||||
|
@ -189,6 +205,15 @@ class BaseNode(ProjectChild):
|
|||
def description(self) -> node_db.NodeDescription:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def connections(self) -> Sequence['NodeConnection']:
|
||||
result = []
|
||||
for conn in self.project.get_property_value('node_connections'):
|
||||
if conn.source_node is self or conn.dest_node is self:
|
||||
result.append(conn)
|
||||
|
||||
return result
|
||||
|
||||
def upstream_nodes(self) -> List['BaseNode']:
|
||||
node_ids = set() # type: Set[int]
|
||||
self.__upstream_nodes(node_ids)
|
||||
|
|
|
@ -25,6 +25,7 @@ syntax = "proto2";
|
|||
import "noisicaa/core/empty_message.proto";
|
||||
import "noisicaa/core/proto_types.proto";
|
||||
import "noisicaa/audioproc/public/control_value.proto";
|
||||
import "noisicaa/audioproc/public/node_port_properties.proto";
|
||||
import "noisicaa/audioproc/public/plugin_state.proto";
|
||||
import "noisicaa/model/model_base.proto";
|
||||
import "noisicaa/model/project.proto";
|
||||
|
@ -55,6 +56,7 @@ message UpdateNode {
|
|||
optional ControlValue set_control_value = 6;
|
||||
optional PluginState set_plugin_state = 7;
|
||||
optional bytes load_from_preset = 8;
|
||||
optional NodePortProperties set_port_properties = 9;
|
||||
}
|
||||
|
||||
message DeleteNode {
|
||||
|
|
|
@ -113,6 +113,20 @@ class DeleteNodeConnection(commands.Command):
|
|||
class UpdateNode(commands.Command):
|
||||
proto_type = 'update_node'
|
||||
|
||||
def validate(self) -> None:
|
||||
pb = down_cast(commands_pb2.UpdateNode, self.pb)
|
||||
|
||||
if pb.node_id not in self.pool:
|
||||
raise ValueError("Unknown node %016x" % pb.node_id)
|
||||
|
||||
node = down_cast(pmodel.BaseNode, self.pool[pb.node_id])
|
||||
|
||||
if pb.HasField('set_port_properties'):
|
||||
if not any(
|
||||
port_desc.name == pb.set_port_properties.name
|
||||
for port_desc in node.description.ports):
|
||||
raise ValueError("Invalid port name '%s'" % pb.set_port_properties.name)
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.UpdateNode, self.pb)
|
||||
node = down_cast(pmodel.BaseNode, self.pool[pb.node_id])
|
||||
|
@ -138,6 +152,9 @@ class UpdateNode(commands.Command):
|
|||
pb.set_control_value.value,
|
||||
pb.set_control_value.generation)
|
||||
|
||||
if pb.HasField('set_port_properties'):
|
||||
node.set_port_properties(pb.set_port_properties)
|
||||
|
||||
|
||||
# class NodeToPreset(commands.Command):
|
||||
# proto_type = 'node_to_preset'
|
||||
|
@ -196,9 +213,16 @@ class BaseNode(pmodel.BaseNode): # pylint: disable=abstract-method
|
|||
remove_node=audioproc.RemoveNode(id=self.pipeline_node_id))
|
||||
|
||||
def get_initial_parameter_mutations(self) -> Iterator[audioproc.Mutation]:
|
||||
for props in self.port_properties:
|
||||
yield audioproc.Mutation(
|
||||
set_node_port_properties=audioproc.SetNodePortProperties(
|
||||
node_id=self.pipeline_node_id,
|
||||
port_properties=props.to_proto()))
|
||||
|
||||
for port in self.description.ports:
|
||||
if (port.direction == node_db.PortDescription.INPUT
|
||||
and port.type == node_db.PortDescription.KRATE_CONTROL):
|
||||
and (port.type == node_db.PortDescription.KRATE_CONTROL,
|
||||
port.type == node_db.PortDescription.ARATE_CONTROL)):
|
||||
for cv in self.control_values:
|
||||
if cv.name == port.name:
|
||||
yield audioproc.Mutation(
|
||||
|
@ -237,6 +261,27 @@ class BaseNode(pmodel.BaseNode): # pylint: disable=abstract-method
|
|||
node_id=self.pipeline_node_id,
|
||||
state=plugin_state)))
|
||||
|
||||
def set_port_properties(self, port_properties: audioproc.NodePortProperties) -> None:
|
||||
new_props = None # type: audioproc.NodePortProperties
|
||||
|
||||
for idx, props in enumerate(self.port_properties):
|
||||
if props.name == port_properties.name:
|
||||
new_props = props.to_proto()
|
||||
new_props.MergeFrom(port_properties)
|
||||
self.port_properties[idx] = model.NodePortProperties.from_proto(new_props)
|
||||
break
|
||||
else:
|
||||
new_props = port_properties
|
||||
self.port_properties.append(
|
||||
model.NodePortProperties.from_proto(port_properties))
|
||||
|
||||
if self.attached_to_project:
|
||||
self.project.handle_pipeline_mutation(
|
||||
audioproc.Mutation(
|
||||
set_node_port_properties=audioproc.SetNodePortProperties(
|
||||
node_id=self.pipeline_node_id,
|
||||
port_properties=new_props)))
|
||||
|
||||
def create_node_connector(
|
||||
self, message_cb: Callable[[audioproc.ProcessorMessage], None]
|
||||
) -> node_connector.NodeConnector:
|
||||
|
|
|
@ -24,6 +24,7 @@ import logging
|
|||
from typing import List
|
||||
|
||||
from noisidev import unittest
|
||||
from noisicaa.core import ipc
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import model
|
||||
from . import commands_test
|
||||
|
@ -132,6 +133,27 @@ class GraphCommandsTest(commands_test.CommandsTestMixin, unittest.AsyncTestCase)
|
|||
set_plugin_state=plugin_state))
|
||||
self.assertEqual(node.plugin_state, plugin_state)
|
||||
|
||||
async def test_set_port_properties(self):
|
||||
await self.client.send_command(project_client.create_node(
|
||||
'builtin://csound/reverb',
|
||||
graph_pos=model.Pos2F(200, 100)))
|
||||
node = self.project.nodes[-1]
|
||||
|
||||
await self.client.send_command(project_client.update_node(
|
||||
node,
|
||||
set_port_properties=model.NodePortProperties('mix', exposed=True)))
|
||||
self.assertTrue(node.get_port_properties('mix').exposed)
|
||||
|
||||
await self.client.send_command(project_client.update_node(
|
||||
node,
|
||||
set_port_properties=model.NodePortProperties('mix', exposed=False)))
|
||||
self.assertFalse(node.get_port_properties('mix').exposed)
|
||||
|
||||
with self.assertRaises(ipc.RemoteException):
|
||||
await self.client.send_command(project_client.update_node(
|
||||
node,
|
||||
set_port_properties=model.NodePortProperties('holla')))
|
||||
|
||||
# @unittest.skip("Implementation broken")
|
||||
# async def test_node_to_preset(self):
|
||||
# await self.client.send_command(commands_pb2.Command(
|
||||
|
|
|
@ -25,6 +25,7 @@ syntax = "proto2";
|
|||
import "noisicaa/core/proto_types.proto";
|
||||
import "noisicaa/audioproc/public/musical_time.proto";
|
||||
import "noisicaa/audioproc/public/control_value.proto";
|
||||
import "noisicaa/audioproc/public/node_port_properties.proto";
|
||||
import "noisicaa/audioproc/public/plugin_state.proto";
|
||||
import "noisicaa/model/model_base.proto";
|
||||
import "noisicaa/model/project.proto";
|
||||
|
@ -103,6 +104,7 @@ message MutationList {
|
|||
SizeF sizef = 109;
|
||||
Color color = 110;
|
||||
ControlValue control_value = 108;
|
||||
NodePortProperties node_port_properties = 111;
|
||||
}
|
||||
}
|
||||
repeated Slot slots = 2;
|
||||
|
|
|
@ -89,6 +89,8 @@ class MutationList(object):
|
|||
return model.Color.from_proto(slot.color)
|
||||
elif vtype == 'control_value':
|
||||
return model.ControlValue.from_proto(slot.control_value)
|
||||
elif vtype == 'node_port_properties':
|
||||
return model.NodePortProperties.from_proto(slot.node_port_properties)
|
||||
|
||||
else:
|
||||
raise TypeError(vtype)
|
||||
|
@ -312,6 +314,8 @@ class MutationCollector(object):
|
|||
slot.color.CopyFrom(value.to_proto())
|
||||
elif isinstance(value, model.ControlValue):
|
||||
slot.control_value.CopyFrom(value.to_proto())
|
||||
elif isinstance(value, model.NodePortProperties):
|
||||
slot.node_port_properties.CopyFrom(value.to_proto())
|
||||
|
||||
else:
|
||||
raise TypeError(type(value))
|
||||
|
|
|
@ -94,6 +94,10 @@ class BaseNode(ProjectChild, model.BaseNode, ObjectBase):
|
|||
def plugin_state(self, value: audioproc.PluginState) -> None:
|
||||
self.set_property_value('plugin_state', value)
|
||||
|
||||
@property
|
||||
def port_properties(self) -> MutableSequence[model.NodePortProperties]:
|
||||
return self.get_property_value('port_properties')
|
||||
|
||||
def get_add_mutations(self) -> Iterator[audioproc.Mutation]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -109,6 +113,9 @@ class BaseNode(ProjectChild, model.BaseNode, ObjectBase):
|
|||
def set_plugin_state(self, plugin_state: audioproc.PluginState) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_port_properties(self, port_properties: audioproc.NodePortProperties) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def create_node_connector(
|
||||
self, message_cb: Callable[[audioproc.ProcessorMessage], None]) -> NodeConnector:
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -127,6 +127,7 @@ class BaseProject(pmodel.Project):
|
|||
self.dispatch_command_sequence(commands.CommandSequence.create(proto))
|
||||
|
||||
def dispatch_command_sequence(self, sequence: commands.CommandSequence) -> None:
|
||||
logger.info("Executing command sequence:\n%s", sequence)
|
||||
sequence.apply(self.command_registry, self._pool)
|
||||
logger.info(
|
||||
"Executed command sequence %s (%d operations)",
|
||||
|
@ -299,12 +300,12 @@ class Project(BaseProject):
|
|||
if self.__storage.logs_since_last_checkpoint > 1000:
|
||||
self.create_checkpoint()
|
||||
|
||||
def dispatch_command_sequence(self, sequence: commands.CommandSequence) -> Any:
|
||||
def dispatch_command_sequence(self, sequence: commands.CommandSequence) -> None:
|
||||
if self.closed:
|
||||
raise RuntimeError(
|
||||
"Command sequence %s executed on closed project." % sequence.command_names)
|
||||
|
||||