Change the primary metaphor to 'modular synth' instead of 'multitrack recorder'.

- tracks are now just special types of nodes in the pipeline graph.
- completely reimplemented the pipeline graph UI.
- greatly simplified the UI, got rid of all the docks.
- some functionality got lost along the way or hasn't been reimplemented yet.
looper
Ben Niemann 5 years ago
parent f898f8b922
commit 66d7f8c6af

@ -5,6 +5,9 @@
; - correct indentation for arg list continuation
((nil . (
; Projetile
(projectile-project-test-cmd . "bin/runtests")
; Uses spaces for indentation.
(indent-tabs-mode . nil)

@ -33,6 +33,7 @@ class pyqtSignal:
def disconnect(self, slot: typing.Optional[typing.Callable] = 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: ...
@ -6388,6 +6389,7 @@ class QRectF(sip.simplewrapper):
def left(self) -> float: ...
def normalized(self) -> 'QRectF': ...
def __repr__(self) -> str: ...
def __ior__(self, o: 'QRectF') -> 'QRectF': ...
class QRegExp(sip.simplewrapper):

@ -5664,6 +5664,7 @@ class QLineEdit(QWidget):
textChanged = ... # type: PYQT_SIGNAL
textEdited = ... # type: PYQT_SIGNAL
editingFinished = ... # type: PYQT_SIGNAL
returnPressed = ... # type: PYQT_SIGNAL
class ActionPosition(int): ...
LeadingPosition = ... # type: 'QLineEdit.ActionPosition'
@ -5723,7 +5724,6 @@ class QLineEdit(QWidget):
def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: ...
def initStyleOption(self, option: 'QStyleOptionFrame') -> None: ...
def selectionChanged(self) -> None: ...
def returnPressed(self) -> None: ...
def cursorPositionChanged(self, a0: int, a1: int) -> None: ...
def createStandardContextMenu(self) -> 'QMenu': ...
def insert(self, a0: str) -> None: ...
@ -5922,6 +5922,7 @@ class QListWidgetItem(sip.wrapper):
class QListWidget(QListView):
itemDoubleClicked = ... # type: PYQT_SIGNAL
def __init__(self, parent: typing.Optional[QWidget] = ...) -> None: ...
@ -5945,7 +5946,6 @@ class QListWidget(QListView):
def itemChanged(self, item: QListWidgetItem) -> None: ...
def itemEntered(self, item: QListWidgetItem) -> None: ...
def itemActivated(self, item: QListWidgetItem) -> None: ...
def itemDoubleClicked(self, item: QListWidgetItem) -> None: ...
def itemClicked(self, item: QListWidgetItem) -> None: ...
def itemPressed(self, item: QListWidgetItem) -> None: ...
def scrollToItem(self, item: QListWidgetItem, hint: QAbstractItemView.ScrollHint = ...) -> None: ...

@ -25,8 +25,10 @@
<csound>
<display-name>Butterworth Band-pass Filter</display-name>
<ports>
<port name="in" type="audio" direction="input"/>
<port name="out" type="audio" direction="output"/>
<port name="in/left" type="audio" direction="input"/>
<port name="in/right" type="audio" direction="input"/>
<port name="out/left" type="audio" direction="output"/>
<port name="out/right" type="audio" direction="output"/>
<port name="center" type="kratecontrol" direction="input">
<float-control min="0" max="20000" default="2000"/>
<display-name>Center frequency</display-name>

@ -25,8 +25,10 @@
<csound>
<display-name>Butterworth Band-reject Filter</display-name>
<ports>
<port name="in" type="audio" direction="input"/>
<port name="out" type="audio" direction="output"/>
<port name="in/left" type="audio" direction="input"/>
<port name="in/right" type="audio" direction="input"/>
<port name="out/left" type="audio" direction="output"/>
<port name="out/right" type="audio" direction="output"/>
<port name="center" type="kratecontrol" direction="input">
<float-control min="0" max="20000" default="2000"/>
<display-name>Center frequency</display-name>

@ -25,8 +25,10 @@
<csound>
<display-name>Butterworth High-pass Filter</display-name>
<ports>
<port name="in" type="audio" direction="input"/>
<port name="out" type="audio" direction="output"/>
<port name="in/left" type="audio" direction="input"/>
<port name="in/right" type="audio" direction="input"/>
<port name="out/left" type="audio" direction="output"/>
<port name="out/right" type="audio" direction="output"/>
<port name="cutoff" type="kratecontrol" direction="input">
<float-control min="0" max="20000" default="2000"/>
<display-name>Cutoff frequency</display-name>

@ -25,8 +25,10 @@
<csound>
<display-name>Butterworth Low-pass Filter</display-name>
<ports>
<port name="in" type="audio" direction="input"/>
<port name="out" type="audio" direction="output"/>
<port name="in/left" type="audio" direction="input"/>
<port name="in/right" type="audio" direction="input"/>
<port name="out/left" type="audio" direction="output"/>
<port name="out/right" type="audio" direction="output"/>
<port name="cutoff" type="kratecontrol" direction="input">
<float-control min="0" max="20000" default="2000"/>
<display-name>Cutoff frequency</display-name>

@ -81,8 +81,8 @@ PIP_DEPS = {
PKG('mox3'),
PKG('py-cpuinfo'),
PKG('pyfakefs'),
PKG('pylint'),
PKG('mypy'),
PKG('pylint==1.9.3'),
PKG('mypy==0.610'),
PKG('mypy-extensions'),
],
'vmtests': [
@ -127,13 +127,11 @@ SYS_DEPS = {
PKG('libboost-dev', 'ubuntu', '<17.10'),
PKG('flex', 'ubuntu', '<17.10'),
PKG('bison', 'ubuntu', '<17.10'),
PKG('cmake', 'ubuntu', '<17.10'),
PKG('csound', 'ubuntu', '>=17.10'),
PKG('libcsound64-dev', 'ubuntu', '>=17.10'),
# capnp
PKG('capnproto'),
PKG('libcapnp-0.5.3'),
PKG('libcapnp-dev'),
# protocol buffers

@ -49,7 +49,7 @@ class ProcessorCVGeneratorTest(
def setup_testcase(self):
self.host_system.set_block_size(4096)
plugin_uri = 'builtin://cvgenerator'
plugin_uri = 'builtin://control_track'
node_description = self.node_db[plugin_uri]
self.proc = processor.PyProcessor('test_node', self.host_system, node_description)

@ -47,7 +47,7 @@ class ProcessorPianoRollTestMixin(
def setup_testcase(self):
self.host_system.set_block_size(2 * 44100)
plugin_uri = 'builtin://pianoroll'
plugin_uri = 'builtin://score_track'
node_description = self.node_db[plugin_uri]
self.proc = processor.PyProcessor('test_node', self.host_system, node_description)

@ -55,7 +55,7 @@ class ProcessorSampleScriptTest(
def setup_testcase(self):
self.host_system.set_block_size(4096)
plugin_uri = 'builtin://sample_script'
plugin_uri = 'builtin://sample_track'
node_description = self.node_db[plugin_uri]
self.proc = processor.PyProcessor('test_node', self.host_system, node_description)

@ -22,11 +22,13 @@ add_python_package(
model_base.py
model_base_test.py
clef.py
color.py
key_signature.py
key_signature_test.py
pos2f.py
pitch.py
pitch_test.py
sizef.py
time_signature.py
time_signature_test.py
project.py

@ -39,6 +39,8 @@ from .time_signature import TimeSignature
from .clef import Clef
from .pitch import Pitch, NOTE_TO_MIDI
from .pos2f import Pos2F
from .sizef import SizeF
from .color import Color
from .project import (
ObjectBase,
ProjectChild,
@ -47,32 +49,24 @@ from .project import (
Beat,
BeatMeasure,
BeatTrack,
CVGeneratorPipelineGraphNode,
ControlPoint,
ControlTrack,
InstrumentPipelineGraphNode,
MasterTrackGroup,
Measure,
MeasureReference,
MeasuredTrack,
Metadata,
Note,
PianoRollPipelineGraphNode,
PipelineGraphConnection,
PipelineGraphControlValue,
PipelineGraphNode,
Project,
PropertyMeasure,
PropertyTrack,
Sample,
SampleRef,
SampleScriptPipelineGraphNode,
SampleTrack,
ScoreMeasure,
ScoreTrack,
Track,
TrackGroup,
TrackMixerPipelineGraphNode,
)
from .model_base_pb2 import (
ObjectTree,

@ -0,0 +1,75 @@
#!/usr/bin/python3
# @begin:license
#
# Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license
import decimal
from google.protobuf import message as protobuf
from . import project_pb2
from . import model_base
class Color(model_base.ProtoValue):
def __init__(self, r: float, g: float, b: float, a: float = 1.0) -> None:
self.__context = decimal.Context(prec=4)
self.__r = self.__context.create_decimal_from_float(r)
self.__g = self.__context.create_decimal_from_float(g)
self.__b = self.__context.create_decimal_from_float(b)
self.__a = self.__context.create_decimal_from_float(a)
def to_proto(self) -> project_pb2.Color:
return project_pb2.Color(r=self.__r, g=self.__g, b=self.__b, a=self.__a)
@classmethod
def from_proto(cls, pb: protobuf.Message) -> 'Color':
if not isinstance(pb, project_pb2.Color):
raise TypeError(type(pb).__name__)
return Color(pb.r, pb.g, pb.b, pb.a)
@property
def r(self) -> float:
return float(self.__r)
@property
def g(self) -> float:
return float(self.__g)
@property
def b(self) -> float:
return float(self.__b)
@property
def a(self) -> float:
return float(self.__a)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Color):
return False
return (
self.__r == other.__r
and self.__g == other.__g
and self.__b == other.__b
and self.__a == other.__a)
def __str__(self) -> str:
return 'Color(%s, %s, %s, %s)' % (self.__r, self.__g, self.__b, self.__a)
__repr__ = __str__

@ -28,7 +28,7 @@ message ObjectBase {
required string type = 1;
required uint64 id = 2;
extensions 1000 to max;
extensions 100000 to max;
}
message ObjectTree {

@ -654,19 +654,30 @@ class ObjectBase(object):
# Do not complain about 'id' arguments.
# pylint: disable=redefined-builtin
class Spec(ObjectSpec):
class ObjectBaseSpec(ObjectSpec):
id = Property(int)
@classmethod
def get_spec(cls) -> Type[ObjectSpec]:
for spec in cls.__dict__.values():
if isinstance(spec, type) and issubclass(spec, ObjectSpec):
return spec
return None
def __init__(self, *, pb: model_base_pb2.ObjectBase, pool: AbstractPool) -> None:
self.__proto = pb
self._pool = pool
self.__properties = {} # type: Dict[str, PropertyBase]
for cls in self.__class__.__mro__:
if not issubclass(cls, ObjectBase) or 'Spec' not in cls.__dict__:
if not issubclass(cls, ObjectBase):
continue # pragma: no coverage
spec = cls.__dict__['Spec']
assert isinstance(spec, type) and issubclass(spec, ObjectSpec)
spec = cast(Type[ObjectBase], cls).get_spec()
if spec is None:
continue
for prop_name, prop in spec.__dict__.items():
if isinstance(prop, PropertyBase):
self.__properties[prop_name] = prop
@ -699,7 +710,7 @@ class ObjectBase(object):
@property
def id(self) -> int:
return ObjectBase.Spec.id.get_value(self, self.__proto, self._pool)
return ObjectBase.ObjectBaseSpec.id.get_value(self, self.__proto, self._pool)
def __str__(self) -> str:
return '<%s id=%s>' % (type(self).__name__, self.id)
@ -851,10 +862,27 @@ class Pool(Generic[POOLOBJECTBASE], AbstractPool[POOLOBJECTBASE]):
self.__obj_map = {} # type: Dict[int, POOLOBJECTBASE]
self.__class_map = {} # type: Dict[str, Type[POOLOBJECTBASE]]
def __get_proto_type(self, cls: Type) -> str:
proto_type = None
for c in cls.__mro__:
if not issubclass(c, ObjectBase):
continue # pragma: no coverage
spec = cast(Type[ObjectBase], c).get_spec()
if spec is None:
continue
if spec.proto_type is not None:
assert proto_type is None, (cls.__name__, c.__name__)
proto_type = spec.proto_type
return proto_type
def register_class(self, cls: Type[POOLOBJECTBASE]) -> None:
assert cls.Spec.proto_type is not None
assert cls.Spec.proto_type not in self.__class_map
self.__class_map[cls.Spec.proto_type] = cls
proto_type = self.__get_proto_type(cls)
assert proto_type is not None, cls.__name__
assert proto_type not in self.__class_map
self.__class_map[proto_type] = cls
def object_added(self, obj: POOLOBJECTBASE) -> None:
pass
@ -902,10 +930,11 @@ class Pool(Generic[POOLOBJECTBASE], AbstractPool[POOLOBJECTBASE]):
def create(
self, cls: Type[OBJECT], id: Optional[int] = None, **kwargs: Any) -> OBJECT:
assert cls.Spec.proto_type in self.__class_map, cls.__name__
proto_type = self.__get_proto_type(cls)
assert proto_type in self.__class_map, cls.__name__
if id is None:
id = random.getrandbits(64)
pb = model_base_pb2.ObjectBase(id=id, type=cls.Spec.proto_type)
pb = model_base_pb2.ObjectBase(id=id, type=proto_type)
obj = cast(POOLOBJECTBASE, cls(pb=pb, pool=self))
self.__obj_map[id] = obj
obj.create(**kwargs) # type: ignore

@ -31,14 +31,14 @@ message Proto {
optional int32 b = 2;
}
message GrandChild {
}
message Child {
optional uint64 child = 1;
optional string value = 2;
}
message GrandChild {
}
message Root {
optional string string_value = 1;
optional int64 int_value = 2;
@ -57,6 +57,6 @@ message Root {
}
extend ObjectBase {
optional Root root = 2000;
optional Child child = 2001;
optional Root root = 1000000;
optional Child child = 1000001;
}

@ -66,12 +66,12 @@ class Proto(model_base.ProtoValue):
class GrandChild(model_base.ObjectBase):
class Spec(model_base.ObjectSpec):
class GrandChildSpec(model_base.ObjectSpec):
proto_type = 'grand_child'
class Child(model_base.ObjectBase):
class Spec(model_base.ObjectSpec):
class ChildSpec(model_base.ObjectSpec):
proto_type = 'child'
proto_ext = model_base_test_pb2.child # type: ignore
child = model_base.ObjectProperty(GrandChild)
@ -103,7 +103,7 @@ class Child(model_base.ObjectBase):
class Root(model_base.ObjectBase):
class Spec(model_base.ObjectSpec):
class RootSpec(model_base.ObjectSpec):
proto_type = 'root'
proto_ext = model_base_test_pb2.root # type: ignore
@ -280,8 +280,8 @@ class ObjectTest(unittest.TestCase):
obj.setup()
obj.setup_complete()
self.assertIs(obj.get_property('id'), model_base.ObjectBase.Spec.id)
self.assertIs(obj.get_property('string_value'), Root.Spec.string_value)
self.assertIs(obj.get_property('id'), model_base.ObjectBase.ObjectBaseSpec.id)
self.assertIs(obj.get_property('string_value'), Root.RootSpec.string_value)
with self.assertRaises(AttributeError):
obj.get_property('does_not_exist')

@ -29,17 +29,12 @@ import "noisicaa/audioproc/public/plugin_state.proto";
import "noisicaa/model/model_base.proto";
message Track {
optional string name = 1;
optional bool visible = 2;
optional bool muted = 3;
optional float gain = 4;
optional float pan = 5;
optional uint64 mixer_node = 6;
optional bool visible = 1;
optional uint32 list_position = 2;
}
message Measure {
optional TimeSignature time_signature = 1;
}
message MeasureReference {
@ -58,13 +53,6 @@ message Note {
optional uint32 tuplet = 4;
}
message TrackGroup {
repeated uint64 tracks = 1;
}
message MasterTrackGroup {
}
message ScoreMeasure {
optional Clef clef = 1;
optional KeySignature key_signature = 2;
@ -72,11 +60,7 @@ message ScoreMeasure {
}
message ScoreTrack {
optional string instrument = 1;
optional int32 transpose_octaves = 2;
optional uint64 instrument_node = 3;
optional uint64 event_source_node = 4;
optional int32 transpose_octaves = 1;
}
message Beat {
@ -89,18 +73,7 @@ message BeatMeasure {
}
message BeatTrack {
optional string instrument = 1;
optional Pitch pitch = 2;
optional uint64 instrument_node = 3;
optional uint64 event_source_node = 4;
}
message PropertyMeasure {
optional TimeSignature time_signature = 1;
}
message PropertyTrack {
optional Pitch pitch = 1;
}
message ControlPoint {
@ -110,8 +83,7 @@ message ControlPoint {
message ControlTrack {
repeated uint64 points = 1;
optional uint64 generator_node = 2;
}
}
message SampleRef {
optional MusicalTime time = 1;
@ -120,7 +92,6 @@ message SampleRef {
message SampleTrack {
repeated uint64 samples = 1;
optional uint64 sample_script_node = 2;
}
message PipelineGraphControlValue {
@ -131,6 +102,8 @@ message PipelineGraphControlValue {
message BasePipelineGraphNode {
optional string name = 1;
optional Pos2F graph_pos = 2;
optional SizeF graph_size = 5;
optional Color graph_color = 6;
repeated uint64 control_values = 3;
optional PluginState plugin_state = 4;
}
@ -142,24 +115,8 @@ message PipelineGraphNode {
message AudioOutPipelineGraphNode {
}
message TrackMixerPipelineGraphNode {
optional uint64 track = 1;
}
message PianoRollPipelineGraphNode {
optional uint64 track = 1;
}
message CVGeneratorPipelineGraphNode {
optional uint64 track = 1;
}
message SampleScriptPipelineGraphNode {
optional uint64 track = 1;
}
message InstrumentPipelineGraphNode {
optional uint64 track = 1;
optional string instrument_uri = 1;
}
message PipelineGraphConnection {
@ -171,7 +128,7 @@ message PipelineGraphConnection {
message Sample {
optional string path = 1;
}
}
message Metadata {
optional string author = 1;
@ -182,46 +139,52 @@ message Metadata {
message Project {
optional uint64 metadata = 1;
optional uint64 master_group = 2;
optional uint64 property_track = 3;
repeated uint64 pipeline_graph_nodes = 4;
repeated uint64 pipeline_graph_connections = 5;
repeated uint64 samples = 6;
optional uint32 bpm = 7;
optional uint32 bpm = 2;
repeated uint64 pipeline_graph_nodes = 3;
repeated uint64 pipeline_graph_connections = 4;
repeated uint64 samples = 5;
}
extend ObjectBase {
optional Track track = 1000;
optional Measure measure = 1001;
optional MeasureReference measure_reference = 1002;
optional MeasuredTrack measured_track = 1003;
optional Note note = 1004;
optional TrackGroup track_group = 1005;
optional MasterTrackGroup master_track_group = 1006;
optional ScoreMeasure score_measure = 1007;
optional ScoreTrack score_track = 1008;
optional Beat beat = 1009;
optional BeatMeasure beat_measure = 1010;
optional BeatTrack beat_track = 1011;
optional PropertyMeasure property_measure = 1012;
optional PropertyTrack property_track = 1013;
optional ControlPoint control_point = 1014;
optional ControlTrack control_track = 1015;
optional SampleRef sample_ref = 1016;
optional SampleTrack sample_track = 1017;
optional PipelineGraphControlValue pipeline_graph_control_value = 1018;
optional BasePipelineGraphNode base_pipeline_graph_node = 1019;
optional PipelineGraphNode pipeline_graph_node = 1020;
optional AudioOutPipelineGraphNode audio_out_pipeline_graph_node = 1021;
optional TrackMixerPipelineGraphNode track_mixer_pipeline_graph_node = 1022;
optional PianoRollPipelineGraphNode pianoroll_pipeline_graph_node = 1023;
optional CVGeneratorPipelineGraphNode cvgenerator_pipeline_graph_node = 1024;
optional SampleScriptPipelineGraphNode sample_script_pipeline_graph_node = 1025;
optional InstrumentPipelineGraphNode instrument_pipeline_graph_node = 1026;
optional PipelineGraphConnection pipeline_graph_connection = 1027;
optional Sample sample = 1028;
optional Metadata metadata = 1029;
optional Project project = 1030;
// Project (1xxxxx)
optional Project project = 100000;
optional Metadata metadata = 100001;
// Samples (2xxxxx)
optional Sample sample = 200000;
// Pipeline graph (3xxxxx)
optional PipelineGraphControlValue pipeline_graph_control_value = 300000;
optional PipelineGraphConnection pipeline_graph_connection = 300001;
optional BasePipelineGraphNode base_pipeline_graph_node = 301000;
optional PipelineGraphNode pipeline_graph_node = 302000;
optional AudioOutPipelineGraphNode audio_out_pipeline_graph_node = 303000;
optional InstrumentPipelineGraphNode instrument_pipeline_graph_node = 304000;
// Tracks (4xxxxx)
optional Track track = 400000;
optional MeasuredTrack measured_track = 400100;
optional Measure measure = 400101;
optional MeasureReference measure_reference = 400102;
// Score track (401xxx)
optional ScoreTrack score_track = 401000;
optional ScoreMeasure score_measure = 401001;
optional Note note = 401002;
// Beat track (402xxx)
optional BeatTrack beat_track = 402000;
optional BeatMeasure beat_measure = 402001;
optional Beat beat = 402002;
// Control track (403xxx)
optional ControlTrack control_track = 403000;
optional ControlPoint control_point = 403001;
// Sample track (404xxx)
optional SampleTrack sample_track = 404000;
optional SampleRef sample_ref = 404001;
}
message Pitch {
@ -258,6 +221,18 @@ message Pos2F {
required float y = 2;
}
message SizeF {
required float width = 1;
required float height = 2;
}
message Color {
required float r = 1;
required float g = 2;
required float b = 3;
required float a = 4 [default=1.0];
}
message ControlValue {
optional float value = 1;
optional uint64 generation = 2;

@ -22,7 +22,7 @@
import fractions
import logging
from typing import cast, Any, Dict, Iterator, Sequence, List, Union # pylint: disable=unused-import
from typing import cast, Any, Dict, Set, Iterator, Sequence, List, Union # pylint: disable=unused-import
from google.protobuf import message as protobuf # pylint: disable=unused-import
@ -37,6 +37,8 @@ from . import clef as clef_lib
from . import key_signature as key_signature_lib
from . import time_signature as time_signature_lib
from . import pos2f
from . import sizef
from . import color
from . import model_base
from . import project_pb2
@ -76,7 +78,7 @@ class ProjectChild(ObjectBase):
class Sample(ProjectChild):
class Spec(model_base.ObjectSpec):
class SampleSpec(model_base.ObjectSpec):
proto_type = 'sample'
proto_ext = project_pb2.sample # type: ignore
@ -89,7 +91,7 @@ class Sample(ProjectChild):
class PipelineGraphControlValue(ProjectChild):
class Spec(model_base.ObjectSpec):
class PipelineGraphControlValueSpec(model_base.ObjectSpec):
proto_type = 'pipeline_graph_control_value'
proto_ext = project_pb2.pipeline_graph_control_value # type: ignore
@ -104,11 +106,14 @@ class PipelineGraphControlValue(ProjectChild):
class BasePipelineGraphNode(ProjectChild):
class Spec(model_base.ObjectSpec):
class BasePipelineGraphNodeSpec(model_base.ObjectSpec):
proto_ext = project_pb2.base_pipeline_graph_node # type: ignore
name = model_base.Property(str)
graph_pos = model_base.WrappedProtoProperty(pos2f.Pos2F)
graph_size = model_base.WrappedProtoProperty(sizef.SizeF)
graph_color = model_base.WrappedProtoProperty(
color.Color, default=color.Color(0.8, 0.8, 0.8, 1.0))
control_values = model_base.ObjectListProperty(PipelineGraphControlValue)
plugin_state = model_base.ProtoProperty(audioproc.PluginState, allow_none=True)
@ -117,6 +122,8 @@ class BasePipelineGraphNode(ProjectChild):
self.name_changed = core.Callback[model_base.PropertyChange[str]]()
self.graph_pos_changed = core.Callback[model_base.PropertyChange[pos2f.Pos2F]]()
self.graph_size_changed = core.Callback[model_base.PropertyChange[sizef.SizeF]]()
self.graph_color_changed = core.Callback[model_base.PropertyChange[color.Color]]()
self.control_values_changed = \
core.Callback[model_base.PropertyListChange[PipelineGraphControlValue]]()
self.plugin_state_changed = \
@ -124,62 +131,140 @@ class BasePipelineGraphNode(ProjectChild):
@property
def removable(self) -> bool:
raise NotImplementedError
return True
@property
def description(self) -> node_db.NodeDescription:
raise NotImplementedError
def upstream_nodes(self) -> List['BasePipelineGraphNode']:
node_ids = set() # type: Set[int]
self.__upstream_nodes(node_ids)
return [self._pool[node_id] for node_id in sorted(node_ids)]
def __upstream_nodes(self, seen: Set[int]) -> None:
for connection in self.project.get_property_value('pipeline_graph_connections'):
if connection.dest_node is self and connection.source_node.id not in seen:
seen.add(connection.source_node.id)
connection.source_node.__upstream_nodes(seen)
class PipelineGraphConnection(ProjectChild):
class PipelineGraphConnectionSpec(model_base.ObjectSpec):
proto_type = 'pipeline_graph_connection'
proto_ext = project_pb2.pipeline_graph_connection # type: ignore
source_node = model_base.ObjectReferenceProperty(BasePipelineGraphNode)
source_port = model_base.Property(str)
dest_node = model_base.ObjectReferenceProperty(BasePipelineGraphNode)
dest_port = model_base.Property(str)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.source_node_changed = core.Callback[model_base.PropertyChange[BasePipelineGraphNode]]()
self.source_port_changed = core.Callback[model_base.PropertyChange[str]]()
self.dest_node_changed = core.Callback[model_base.PropertyChange[BasePipelineGraphNode]]()
self.dest_port_changed = core.Callback[model_base.PropertyChange[str]]()
class PipelineGraphNode(BasePipelineGraphNode):
class PipelineGraphNodeSpec(model_base.ObjectSpec):
proto_type = 'pipeline_graph_node'
proto_ext = project_pb2.pipeline_graph_node # type: ignore
node_uri = model_base.Property(str)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.node_uri_changed = core.Callback[model_base.PropertyChange[str]]()
@property
def node_uri(self) -> str:
return self.get_property_value('node_uri')
@property
def description(self) -> node_db.NodeDescription:
return self.project.get_node_description(self.node_uri)
class AudioOutPipelineGraphNode(BasePipelineGraphNode):
class AudioOutPipelineGraphNodeSpec(model_base.ObjectSpec):
proto_type = 'audio_out_pipeline_graph_node'
@property
def removable(self) -> bool:
return False
@property
def description(self) -> node_db.NodeDescription:
return node_db.Builtins.RealmSinkDescription
class Track(ProjectChild):
class Spec(model_base.ObjectSpec):
class InstrumentPipelineGraphNode(BasePipelineGraphNode):
class InstrumentPipelineGraphNodeSpec(model_base.ObjectSpec):
proto_type = 'instrument_pipeline_graph_node'
proto_ext = project_pb2.instrument_pipeline_graph_node # type: ignore
instrument_uri = model_base.Property(str)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.instrument_uri_changed = core.Callback[model_base.PropertyChange[str]]()
@property
def description(self) -> node_db.NodeDescription:
return instrument_db.parse_uri(
self.get_property_value('instrument_uri'), self.project.get_node_description)
class Track(BasePipelineGraphNode): # pylint: disable=abstract-method
class TrackSpec(model_base.ObjectSpec):
proto_ext = project_pb2.track # type: ignore
name = model_base.Property(str)
visible = model_base.Property(bool, default=True)
muted = model_base.Property(bool, default=False)
gain = model_base.Property(float, default=0.0)
pan = model_base.Property(float, default=0.0)
mixer_node = model_base.ObjectReferenceProperty(BasePipelineGraphNode, allow_none=True)
list_position = model_base.Property(int, default=0)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.name_changed = core.Callback[model_base.PropertyChange[str]]()
self.visible_changed = core.Callback[model_base.PropertyChange[bool]]()
self.muted_changed = core.Callback[model_base.PropertyChange[bool]]()
self.gain_changed = core.Callback[model_base.PropertyChange[float]]()
self.pan_changed = core.Callback[model_base.PropertyChange[float]]()
self.mixer_node_changed = core.Callback[model_base.PropertyChange[BasePipelineGraphNode]]()
self.list_position_changed = core.Callback[model_base.PropertyChange[int]]()
self.duration_changed = core.Callback[None]()
@property
def duration(self) -> audioproc.MusicalDuration:
return audioproc.MusicalDuration(1, 1)
@property
def is_master_group(self) -> bool:
return False
def walk_tracks(self, groups: bool = False, tracks: bool = True) -> Iterator['Track']:
if tracks:
yield self
class Measure(ProjectChild):
class MeasureSpec(model_base.ObjectSpec):
proto_ext = project_pb2.measure # type: ignore
time_signature = model_base.WrappedProtoProperty(
time_signature_lib.TimeSignature,
default=time_signature_lib.TimeSignature(4, 4))
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.time_signature_changed = \
core.Callback[model_base.PropertyChange[time_signature_lib.TimeSignature]]()
class Measure(ProjectChild):
@property
def track(self) -> Track:
return cast(Track, self.parent)
@property
def duration(self) -> audioproc.MusicalDuration:
time_signature = self.project.get_time_signature(self.index)
time_signature = self.get_property_value('time_signature')
return audioproc.MusicalDuration(time_signature.upper, time_signature.lower)
class MeasureReference(ProjectChild):
class Spec(model_base.ObjectSpec):
class MeasureReferenceSpec(model_base.ObjectSpec):
proto_type = 'measure_reference'
proto_ext = project_pb2.measure_reference # type: ignore
@ -207,8 +292,8 @@ class MeasureReference(ProjectChild):
return down_cast(MeasureReference, super().next_sibling)
class MeasuredTrack(Track):
class Spec(model_base.ObjectSpec):
class MeasuredTrack(Track): # pylint: disable=abstract-method
class MeasuredSpec(model_base.ObjectSpec):
proto_ext = project_pb2.measured_track # type: ignore
measure_list = model_base.ObjectListProperty(MeasureReference)
@ -263,7 +348,7 @@ class MeasuredTrack(Track):
class Note(ProjectChild):
class Spec(model_base.ObjectSpec):
class NoteSpec(model_base.ObjectSpec):
proto_type = 'note'
proto_ext = project_pb2.note # type: ignore
@ -338,205 +423,8 @@ class Note(ProjectChild):
self.measure.content_changed.call()
class TrackGroup(Track):
class Spec(model_base.ObjectSpec):
proto_type = 'track_group'
proto_ext = project_pb2.track_group # type: ignore
tracks = model_base.ObjectListProperty(Track)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.__listeners = {} # type: Dict[str, core.Listener]
self.tracks_changed = core.Callback[model_base.PropertyListChange[Track]]()
def setup(self) -> None:
super().setup()
for track in self.tracks:
self.__add_track(track)
self.tracks_changed.add(self.__tracks_changed)
def __tracks_changed(self, change: model_base.PropertyListChange) -> None:
if isinstance(change, model_base.PropertyListInsert):
self.__add_track(change.new_value)
elif isinstance(change, model_base.PropertyListDelete):
self.__remove_track(change.old_value)
else:
raise TypeError("Unsupported change type %s" % type(change))
def __add_track(self, track: Track) -> None:
self.__listeners['%s:duration_changed' % track.id] = track.duration_changed.add(
self.duration_changed.call)
self.duration_changed.call()
def __remove_track(self, track: Track) -> None:
self.__listeners.pop('%s:duration_changed' % track.id).remove()
self.duration_changed.call()
@property
def tracks(self) -> Sequence[Track]:
return self.get_property_value('tracks')
@property
def duration(self) -> audioproc.MusicalDuration:
duration = audioproc.MusicalDuration()
for track in self.tracks:
duration = max(duration, track.duration)
return duration
def walk_tracks(self, groups: bool = False, tracks: bool = True) -> Iterator[Track]:
if groups:
yield self
for track in self.tracks:
yield from track.walk_tracks(groups, tracks)
class MasterTrackGroup(TrackGroup):
class Spec(model_base.ObjectSpec):
proto_type = 'master_track_group'
@property
def is_master_group(self) -> bool:
return True
class PropertyMeasure(Measure):
class Spec(model_base.ObjectSpec):
proto_type = 'property_measure'
proto_ext = project_pb2.property_measure # type: ignore
time_signature = model_base.WrappedProtoProperty(
time_signature_lib.TimeSignature,
default=time_signature_lib.TimeSignature(4, 4))
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.time_signature_changed = \
core.Callback[model_base.PropertyChange[time_signature_lib.TimeSignature]]()
@property
def time_signature(self) -> time_signature_lib.TimeSignature:
return self.get_property_value('time_signature')
class PropertyTrack(MeasuredTrack):
class Spec(model_base.ObjectSpec):
proto_type = 'property_track'
class PipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):
proto_type = 'pipeline_graph_node'
proto_ext = project_pb2.pipeline_graph_node # type: ignore
node_uri = model_base.Property(str)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.node_uri_changed = core.Callback[model_base.PropertyChange[str]]()
@property
def node_uri(self) -> str:
return self.get_property_value('node_uri')
@property
def removable(self) -> bool:
return True
@property
def description(self) -> node_db.NodeDescription:
return self.project.get_node_description(self.node_uri)
class AudioOutPipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):
proto_type = 'audio_out_pipeline_graph_node'
@property
def removable(self) -> bool:
return False
@property
def description(self) -> node_db.NodeDescription:
return node_db.Builtins.RealmSinkDescription
class TrackMixerPipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):
proto_type = 'track_mixer_pipeline_graph_node'
proto_ext = project_pb2.track_mixer_pipeline_graph_node # type: ignore
track = model_base.ObjectReferenceProperty(Track)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.track_changed = core.Callback[model_base.PropertyChange[Track]]()
@property
def removable(self) -> bool:
return False
@property
def description(self) -> node_db.NodeDescription:
return node_db.Builtins.TrackMixerDescription
class InstrumentPipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):
proto_type = 'instrument_pipeline_graph_node'
proto_ext = project_pb2.instrument_pipeline_graph_node # type: ignore
track = model_base.ObjectReferenceProperty(Track)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.track_changed = core.Callback[model_base.PropertyChange[Track]]()
@property
def track(self) -> Track:
return self.get_property_value('track')
@property
def removable(self) -> bool:
return False
@property
def description(self) -> node_db.NodeDescription:
return instrument_db.parse_uri(
cast(Union[ScoreTrack, BeatTrack], self.track).instrument,
self.project.get_node_description)
class PianoRollPipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):
proto_type = 'pianoroll_pipeline_graph_node'
proto_ext = project_pb2.pianoroll_pipeline_graph_node # type: ignore
track = model_base.ObjectReferenceProperty(Track)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.track_changed = core.Callback[model_base.PropertyChange[Track]]()
@property
def removable(self) -> bool:
return False
@property
def description(self) -> node_db.NodeDescription:
return node_db.Builtins.PianoRollDescription
class ScoreMeasure(Measure):
class Spec(model_base.ObjectSpec):
class ScoreMeasureSpec(model_base.ObjectSpec):
proto_type = 'score_measure'
proto_ext = project_pb2.score_measure # type: ignore
@ -561,40 +449,26 @@ class ScoreMeasure(Measure):
self.notes_changed.add(lambda _: self.content_changed.call())
@property
def time_signature(self) -> time_signature_lib.TimeSignature:
return self.project.get_time_signature(self.index)
class ScoreTrack(MeasuredTrack):
class Spec(model_base.ObjectSpec):
class ScoreTrackSpec(model_base.ObjectSpec):
proto_type = 'score_track'
proto_ext = project_pb2.score_track # type: ignore
instrument = model_base.Property(str)
transpose_octaves = model_base.Property(int, default=0)
instrument_node = model_base.ObjectReferenceProperty(
InstrumentPipelineGraphNode, allow_none=True)
event_source_node = model_base.ObjectReferenceProperty(
PianoRollPipelineGraphNode, allow_none=True)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.instrument_changed = core.Callback[model_base.PropertyChange[str]]()
self.transpose_octaves_changed = core.Callback[model_base.PropertyChange[int]]()
self.instrument_node_changed = \
core.Callback[model_base.PropertyChange[InstrumentPipelineGraphNode]]()
self.event_source_node_changed = \
core.Callback[model_base.PropertyChange[PianoRollPipelineGraphNode]]()
@property
def instrument(self) -> str:
return self.get_property_value('instrument')
def description(self) -> node_db.NodeDescription:
return node_db.Builtins.ScoreTrackDescription
class Beat(ProjectChild):
class Spec(model_base.ObjectSpec):
class BeatSpec(model_base.ObjectSpec):
proto_type = 'beat'
proto_ext = project_pb2.beat # type: ignore
@ -619,7 +493,7 @@ class Beat(ProjectChild):
class BeatMeasure(Measure):
class Spec(model_base.ObjectSpec):
class BeatMeasureSpec(model_base.ObjectSpec):
proto_type = 'beat_measure'
proto_ext = project_pb2.beat_measure # type: ignore
@ -636,61 +510,26 @@ class BeatMeasure(Measure):
super().setup()
self.beats_changed.add(lambda _: self.content_changed.call())
@property
def time_signature(self) -> time_signature_lib.TimeSignature:
return self.project.get_time_signature(self.index)
class BeatTrack(MeasuredTrack):
class Spec(model_base.ObjectSpec):
class BeatTrackSpec(model_base.ObjectSpec):
proto_type = 'beat_track'
proto_ext = project_pb2.beat_track # type: ignore
instrument = model_base.Property(str)
pitch = model_base.WrappedProtoProperty(pitch_lib.Pitch)
instrument_node = model_base.ObjectReferenceProperty(
InstrumentPipelineGraphNode, allow_none=True)
event_source_node = model_base.ObjectReferenceProperty(
PianoRollPipelineGraphNode, allow_none=True)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.instrument_changed = core.Callback[model_base.PropertyChange[str]]()
self.pitch_changed = core.Callback[model_base.PropertyChange[pitch_lib.Pitch]]()
self.instrument_node_changed = \
core.Callback[model_base.PropertyChange[InstrumentPipelineGraphNode]]()
self.event_source_node_changed = \
core.Callback[model_base.PropertyChange[PianoRollPipelineGraphNode]]()
@property
def instrument(self) -> str:
return self.get_property_value('instrument')
class CVGeneratorPipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):
proto_type = 'cvgenerator_pipeline_graph_node'
proto_ext = project_pb2.cvgenerator_pipeline_graph_node # type: ignore
track = model_base.ObjectReferenceProperty(Track)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.track_changed = core.Callback[model_base.PropertyChange[Track]]()
@property
def removable(self) -> bool:
return False
@property
def description(self) -> node_db.NodeDescription:
return node_db.Builtins.CVGeneratorDescription
return node_db.Builtins.BeatTrackDescription
class ControlPoint(ProjectChild):
class Spec(model_base.ObjectSpec):
class ControlPointSpec(model_base.ObjectSpec):
proto_type = 'control_point'
proto_ext = project_pb2.control_point # type: ignore
@ -705,45 +544,24 @@ class ControlPoint(ProjectChild):
class ControlTrack(Track):
class Spec(model_base.ObjectSpec):
class ControlTrackSpec(model_base.ObjectSpec):
proto_type = 'control_track'
proto_ext = project_pb2.control_track # type: ignore
points = model_base.ObjectListProperty(ControlPoint)
generator_node = model_base.ObjectReferenceProperty(
CVGeneratorPipelineGraphNode, allow_none=True)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.points_changed = core.Callback[model_base.PropertyListChange[ControlPoint]]()
self.generator_node_changed = \
core.Callback[model_base.PropertyChange[CVGeneratorPipelineGraphNode]]()
class SampleScriptPipelineGraphNode(BasePipelineGraphNode):
class Spec(model_base.ObjectSpec):