Remove the concept of sheets.

looper
Ben Niemann 5 years ago
parent f8a545d94d
commit 8417b55f06

@ -2,7 +2,7 @@
* Rework how time signatures are managed :BUG:
- Currently duration is a property of Measure. It uses the time signature of the measure in the
sheet property track at the same index.
property track at the same index.
- it references the property measure with its index within the measure_heap, which is basically
a random number. Can cause crashes when pasting a sequence of measures.
- the same measure could be used at different positions with different time signatures.
@ -73,19 +73,6 @@
- select, cut, copy, paste arbitrary regions
- automatically insert control points/split samples at selection boundary
- selecting measures is just a special case of this
* Remove the sheet layer :CLEANUP:
- multiple sheets per project are of little use and just complicate the code.
- make tracks etc. direct children of the project object.
- simplify UI code, merge SheetView logic into ProjectView.
- instead think about ways to have different regions in time within the project, e.g. for
experiments, etc.
- tracks can be discontinuous, i.e. measures don't need to line up
- each measure tracks its position in time
- set regions in the time line.
- inserting measures only shifts measures to the right within the current region
- if the end goes past the region, extend the region and move all following regions (across
all tracks) to the right to make room.
* Common testdata directory with some audio files :CLEANUP:
- possibly also lv2, ladspa, ... plugins (built from source)?
- replace all uses of system files in tests.
@ -118,6 +105,15 @@
* Control values :FR:
- at beginning of block, take current list, apply CVs by writing values to buffers
* Section on the timeline :FR:
- have different regions in time within the project, e.g. for experiments, etc.
- tracks can be discontinuous, i.e. measures don't need to line up
- each measure tracks its position in time
- set regions in the time line.
- inserting measures only shifts measures to the right within the current region
- if the end goes past the region, extend the region and move all following regions (across
all tracks) to the right to make room.
* Finish VM-based pipeline engine :FR:
- port parameters
- volume, mute, bypass, dry_wet
@ -311,7 +307,7 @@ http://pyxdg.readthedocs.io/en/latest/recentfiles.html
- could crash in ServerProtocol.command_complete, if Server instance has already been cleaned up
- does it need to lameduck?
* SheetEditor: show/hide tracks does work anymore :BUG:
* Editor: show/hide tracks does work anymore :BUG:
* ScoreEditorTrackItem: Improve rendering :FR:
** ghost notes should be closer to real insert position
** squeeze notes into measure, if duration is exceeded

@ -24,8 +24,7 @@ using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("noisicaa::capnp");
enum Key {
sheetId @0;
trackId @1;
trackId @0;
}
struct Label {

@ -28,10 +28,10 @@ from . import message
class BuildMessageTest(unittest.TestCase):
def test_build_message(self):
msg = message.build_message(
{message.MessageKey.sheetId: '123'},
{message.MessageKey.trackId: '123'},
message.MessageType.atom, b'abcd')
self.assertEqual(len(msg.labelset.labels), 1)
self.assertEqual(msg.labelset.labels[0].key, message.MessageKey.sheetId)
self.assertEqual(msg.labelset.labels[0].key, message.MessageKey.trackId)
self.assertEqual(msg.labelset.labels[0].value, '123')
self.assertEqual(msg.type, message.MessageType.atom)
self.assertEqual(msg.data, b'abcd')

@ -47,9 +47,7 @@ add_python_package(
sample_track.py
score_track.py
score_track_test.py
sheet_property_track.py
sheet.py
sheet_test.py
property_track.py
state.py
state_test.py
time_mapper.py

@ -39,8 +39,8 @@ from .score_track import (
ScoreMeasure, ScoreTrack,
Note,
)
from .sheet_property_track import (
SheetPropertyMeasure, SheetPropertyTrack,
from .property_track import (
PropertyMeasure, PropertyTrack,
)
from .time_mapper import (
TimeMapper, TimeOutOfRange

@ -88,7 +88,7 @@ class ReparentTrack(commands.Command):
assert not track.is_master_group
new_parent = track.root.get_object(self.new_parent)
assert new_parent.is_child_of(track.sheet)
assert new_parent.is_child_of(track.project)
assert isinstance(new_parent, model.TrackGroup)
assert 0 <= self.index <= len(new_parent.tracks)
@ -194,19 +194,19 @@ class Track(model.Track, state.StateBase):
parent_mixer_node.graph_pos
+ self.relative_position_to_parent_mixer),
track=self)
self.sheet.add_pipeline_graph_node(mixer_node)
self.project.add_pipeline_graph_node(mixer_node)
self.mixer_id = mixer_node.id
conn = pipeline_graph.PipelineGraphConnection(
mixer_node, 'out:left', parent_mixer_node, 'in:left')
self.sheet.add_pipeline_graph_connection(conn)
self.project.add_pipeline_graph_connection(conn)
conn = pipeline_graph.PipelineGraphConnection(
mixer_node, 'out:right', parent_mixer_node, 'in:right')
self.sheet.add_pipeline_graph_connection(conn)
self.project.add_pipeline_graph_connection(conn)
def remove_pipeline_nodes(self):
self.sheet.remove_pipeline_graph_node(self.mixer_node)
self.project.remove_pipeline_graph_node(self.mixer_node)
self.mixer_id = None
@ -232,7 +232,7 @@ state.StateBase.register_class(MeasureReference)
class BufferSource(object):
def __init__(self, track):
self._track = track
self._sheet = track.sheet
self._project = track.project
def close(self):
pass
@ -247,7 +247,7 @@ class EventSetBufferSource(BufferSource):
self.__event_set = event_set.EventSet()
self.__active_notes = set()
self.__connector = self._create_connector(track, self.__event_set)
self.__time_mapper = time_mapper.TimeMapper(track.sheet)
self.__time_mapper = time_mapper.TimeMapper(track.project)
def _create_connector(self, track, event_set):
raise NotImplementedError

@ -244,13 +244,13 @@ class BeatTrack(model.BeatTrack, base_track.MeasuredTrack):
name="Track Instrument",
graph_pos=mixer_node.graph_pos - misc.Pos2F(200, 0),
track=self)
self.sheet.add_pipeline_graph_node(instrument_node)
self.project.add_pipeline_graph_node(instrument_node)
self.instrument_id = instrument_node.id
self.sheet.add_pipeline_graph_connection(
self.project.add_pipeline_graph_connection(
pipeline_graph.PipelineGraphConnection(
instrument_node, 'out:left', self.mixer_node, 'in:left'))
self.sheet.add_pipeline_graph_connection(
self.project.add_pipeline_graph_connection(
pipeline_graph.PipelineGraphConnection(
instrument_node, 'out:right', self.mixer_node, 'in:right'))
@ -258,17 +258,17 @@ class BeatTrack(model.BeatTrack, base_track.MeasuredTrack):
name="Track Events",
graph_pos=instrument_node.graph_pos - misc.Pos2F(200, 0),
track=self)
self.sheet.add_pipeline_graph_node(event_source_node)
self.project.add_pipeline_graph_node(event_source_node)
self.event_source_id = event_source_node.id
self.sheet.add_pipeline_graph_connection(
self.project.add_pipeline_graph_connection(
pipeline_graph.PipelineGraphConnection(
event_source_node, 'out', instrument_node, 'in'))
def remove_pipeline_nodes(self):
self.sheet.remove_pipeline_graph_node(self.event_source_node)
self.project.remove_pipeline_graph_node(self.event_source_node)
self.event_source_id = None
self.sheet.remove_pipeline_graph_node(self.instrument_node)
self.project.remove_pipeline_graph_node(self.instrument_node)
self.instrument_id = None
super().remove_pipeline_nodes()

@ -143,7 +143,7 @@ class ControlBufferSource(base_track.BufferSource):
def __init__(self, track):
super().__init__(track)
self.time_mapper = time_mapper.TimeMapper(self._sheet)
self.time_mapper = time_mapper.TimeMapper(self._project)
def get_buffers(self, ctxt):
output = numpy.zeros(shape=ctxt.block_size, dtype=numpy.float32)
@ -213,7 +213,7 @@ class ControlTrack(model.ControlTrack, base_track.Track):
@property
def control_source_node(self):
for node in self.sheet.pipeline_graph_nodes:
for node in self.project.pipeline_graph_nodes:
if (isinstance(node, pipeline_graph.ControlSourcePipelineGraphNode)
and node.track is self):
return node
@ -225,10 +225,10 @@ class ControlTrack(model.ControlTrack, base_track.Track):
name="Control Value",
graph_pos=self.parent_mixer_node.graph_pos - misc.Pos2F(200, 0),
track=self)
self.sheet.add_pipeline_graph_node(control_source_node)
self.project.add_pipeline_graph_node(control_source_node)
def remove_pipeline_nodes(self):
self.sheet.remove_pipeline_graph_node(self.control_source_node)
self.project.remove_pipeline_graph_node(self.control_source_node)
state.StateBase.register_class(ControlTrack)

@ -33,7 +33,13 @@ from . import time_signature
from . import misc
class Track(core.ObjectBase):
class ProjectChild(core.ObjectBase):
@property
def project(self):
return self.root
class Track(ProjectChild):
name = core.Property(str)
visible = core.Property(bool, default=True)
@ -43,14 +49,6 @@ class Track(core.ObjectBase):
mixer_id = core.Property(str, allow_none=True)
@property
def project(self):
return self.sheet.project
@property
def sheet(self):
return self.parent.sheet
@property
def is_master_group(self):
return False
@ -60,22 +58,18 @@ class Track(core.ObjectBase):
yield self
class Measure(core.ObjectBase):
class Measure(ProjectChild):
@property
def track(self):
return self.parent
@property
def sheet(self):
return self.track.sheet
@property
def duration(self):
time_signature = self.sheet.get_time_signature(self.index)
time_signature = self.project.get_time_signature(self.index)
return time.Duration(time_signature.upper, time_signature.lower)
class MeasureReference(core.ObjectBase):
class MeasureReference(ProjectChild):
measure_id = core.Property(str)
@property
@ -86,17 +80,13 @@ class MeasureReference(core.ObjectBase):
def track(self):
return self.parent
@property
def sheet(self):
return self.track.sheet
class MeasuredTrack(Track):
measure_list = core.ObjectListProperty(cls=MeasureReference)
measure_heap = core.ObjectListProperty(cls=Measure)
class Note(core.ObjectBase):
class Note(ProjectChild):
pitches = core.ListProperty(pitch.Pitch)
base_duration = core.Property(time.Duration, default=time.Duration(1, 4))
dots = core.Property(int, default=0)
@ -158,7 +148,7 @@ class ScoreMeasure(Measure):
@property
def time_signature(self):
return self.sheet.get_time_signature(self.index)
return self.project.get_time_signature(self.index)
class ScoreTrack(MeasuredTrack):
@ -169,7 +159,7 @@ class ScoreTrack(MeasuredTrack):
event_source_id = core.Property(str, allow_none=True)
class Beat(core.ObjectBase):
class Beat(ProjectChild):
timepos = core.Property(time.Duration)
velocity = core.Property(int)
@ -183,7 +173,7 @@ class BeatMeasure(Measure):
@property
def time_signature(self):
return self.sheet.get_time_signature(self.index)
return self.project.get_time_signature(self.index)
class BeatTrack(MeasuredTrack):
@ -193,17 +183,18 @@ class BeatTrack(MeasuredTrack):
instrument_id = core.Property(str, allow_none=True)
event_source_id = core.Property(str, allow_none=True)
class SheetPropertyMeasure(Measure):
class PropertyMeasure(Measure):
time_signature = core.Property(
time_signature.TimeSignature,
default=time_signature.TimeSignature(4, 4))
class SheetPropertyTrack(MeasuredTrack):
class PropertyTrack(MeasuredTrack):
pass
class ControlPoint(core.ObjectBase):
class ControlPoint(ProjectChild):
timepos = core.Property(time.Duration)
value = core.Property(float)
@ -212,7 +203,7 @@ class ControlTrack(Track):
points = core.ObjectListProperty(ControlPoint)
class SampleRef(core.ObjectBase):
class SampleRef(ProjectChild):
timepos = core.Property(time.Duration)
sample_id = core.Property(str)
@ -222,37 +213,29 @@ class SampleTrack(Track):
audio_source_id = core.Property(str, allow_none=True)
class PipelineGraphNodeParameterValue(core.ObjectBase):
class PipelineGraphNodeParameterValue(ProjectChild):
name = core.Property(str)
value = core.Property((str, float, int))
class PipelineGraphControlValue(core.ObjectBase):
class PipelineGraphControlValue(ProjectChild):
name = core.Property(str)
value = core.Property(float)
class PipelineGraphPortPropertyValue(core.ObjectBase):
class PipelineGraphPortPropertyValue(ProjectChild):
port_name = core.Property(str)
name = core.Property(str)
value = core.Property((str, float, int, bool))
class BasePipelineGraphNode(core.ObjectBase):
class BasePipelineGraphNode(ProjectChild):
name = core.Property(str)
graph_pos = core.Property(misc.Pos2F)
parameter_values = core.ObjectListProperty(PipelineGraphNodeParameterValue)
control_values = core.ObjectListProperty(PipelineGraphControlValue)
port_property_values = core.ObjectListProperty(PipelineGraphPortPropertyValue)
@property
def sheet(self):
return self.parent
@property
def project(self):
return self.sheet.project
@property
def removable(self):
raise NotImplementedError
@ -285,7 +268,15 @@ class AudioOutPipelineGraphNode(BasePipelineGraphNode):
class TrackMixerPipelineGraphNode(BasePipelineGraphNode):
track = core.ObjectReferenceProperty()
track_id = core.Property(str)
@property
def track(self):
return self.root.get_object(self.track_id)
@track.setter
def track(self, obj):
self.track_id = obj.id
@property
def removable(self):
@ -297,7 +288,15 @@ class TrackMixerPipelineGraphNode(BasePipelineGraphNode):
class EventSourcePipelineGraphNode(BasePipelineGraphNode):
track = core.ObjectReferenceProperty()
track_id = core.Property(str)
@property
def track(self):
return self.root.get_object(self.track_id)
@track.setter
def track(self, obj):
self.track_id = obj.id
@property
def removable(self):
@ -309,7 +308,15 @@ class EventSourcePipelineGraphNode(BasePipelineGraphNode):
class ControlSourcePipelineGraphNode(BasePipelineGraphNode):
track = core.ObjectReferenceProperty()
track_id = core.Property(str)
@property
def track(self):
return self.root.get_object(self.track_id)
@track.setter
def track(self, obj):
self.track_id = obj.id
@property
def removable(self):
@ -321,7 +328,15 @@ class ControlSourcePipelineGraphNode(BasePipelineGraphNode):
class AudioSourcePipelineGraphNode(BasePipelineGraphNode):
track = core.ObjectReferenceProperty()
track_id = core.Property(str)
@property
def track(self):
return self.root.get_object(self.track_id)
@track.setter
def track(self, obj):
self.track_id = obj.id
@property
def removable(self):
@ -333,7 +348,15 @@ class AudioSourcePipelineGraphNode(BasePipelineGraphNode):
class InstrumentPipelineGraphNode(BasePipelineGraphNode):
track = core.ObjectReferenceProperty()
track_id = core.Property(str)
@property
def track(self):
return self.root.get_object(self.track_id)
@track.setter
def track(self, obj):
self.track_id = obj.id
@property
def removable(self):
@ -345,39 +368,49 @@ class InstrumentPipelineGraphNode(BasePipelineGraphNode):
return self.project.get_node_description(node_uri)
class PipelineGraphConnection(core.ObjectBase):
source_node = core.ObjectReferenceProperty()
class PipelineGraphConnection(ProjectChild):
source_node_id = core.Property(str)
source_port = core.Property(str)
dest_node = core.ObjectReferenceProperty()
dest_node_id = core.Property(str)
dest_port = core.Property(str)
@property
def source_node(self):
return self.root.get_object(self.source_node_id)
class Sample(core.ObjectBase):
path = core.Property(str)
@source_node.setter
def source_node(self, obj):
self.source_node_id = obj.id
@property
def sheet(self):
return self.parent
def dest_node(self):
return self.root.get_object(self.dest_node_id)
@dest_node.setter
def dest_node(self, obj):
self.dest_node_id = obj.id
class Sample(ProjectChild):
path = core.Property(str)
class Sheet(core.ObjectBase):
name = core.Property(str, default="Sheet")
class Metadata(ProjectChild):
author = core.Property(str, allow_none=True)
license = core.Property(str, allow_none=True)
copyright = core.Property(str, allow_none=True)
created = core.Property(int, allow_none=True)
class Project(core.ObjectBase):
metadata = core.ObjectProperty(cls=Metadata)
master_group = core.ObjectProperty(TrackGroup)
property_track = core.ObjectProperty(SheetPropertyTrack)
property_track = core.ObjectProperty(PropertyTrack)
pipeline_graph_nodes = core.ObjectListProperty(PipelineGraphNode)
pipeline_graph_connections = core.ObjectListProperty(
PipelineGraphConnection)
pipeline_graph_connections = core.ObjectListProperty(PipelineGraphConnection)
samples = core.ObjectListProperty(Sample)
bpm = core.Property(int, default=120)
@property
def sheet(self):
return self
@property
def project(self):
return self.parent
@property
def all_tracks(self):
return ([self.property_track]
@ -393,32 +426,5 @@ class Sheet(core.ObjectBase):
return self.property_track.measure_list[0].measure.time_signature
class Metadata(core.ObjectBase):
author = core.Property(str, allow_none=True)
license = core.Property(str, allow_none=True)
copyright = core.Property(str, allow_none=True)
created = core.Property(int, allow_none=True)
class Project(core.ObjectBase):
sheets = core.ObjectListProperty(cls=Sheet)
current_sheet = core.Property(int, default=0)
metadata = core.ObjectProperty(cls=Metadata)
def get_current_sheet(self):
return self.sheets[self.current_sheet]
def get_sheet(self, name):
for sheet in self.sheets:
if sheet.name == name:
return sheet
raise ValueError("No sheet %r" % name)
def get_sheet_index(self, name):
for idx, sheet in enumerate(self.sheets):
if sheet.name == name:
return idx
raise ValueError("No sheet %r" % name)
def get_node_description(self, uri):
raise NotImplementedError

@ -215,14 +215,6 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
self.name = name
self.graph_pos = graph_pos
@property
def sheet(self):
return self.parent
@property
def project(self):
return self.sheet.project
@property
def pipeline_node_id(self):
raise NotImplementedError
@ -244,14 +236,14 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
parameter.name, parameter.default)
if params:
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.SetNodeParameter(self.pipeline_node_id, **params))
for port in self.description.ports:
if (port.direction == node_db.PortDirection.Input
and port.port_type == node_db.PortType.KRateControl):
for cv in self.control_values:
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.SetControlValue(
'%s:%s' % (self.pipeline_node_id, cv.name), cv.value))
@ -261,7 +253,7 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
if p.port_name == port.name)
if port_property_values:
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.SetPortProperty(
self.pipeline_node_id, port.name,
**port_property_values))
@ -282,7 +274,7 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
self.parameter_values.append(PipelineGraphNodeParameterValue(
name=name, value=value))
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.SetNodeParameter(
self.pipeline_node_id, **parameters))
@ -302,7 +294,7 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
PipelineGraphPortPropertyValue(
port_name=port_name, name=prop_name, value=value))
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.SetPortProperty(
self.pipeline_node_id, port_name,
bypass=bypass, drywet=drywet))
@ -316,7 +308,7 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
self.control_values.append(PipelineGraphControlValue(
name=port_name, value=value))
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.SetControlValue('%s:%s' % (self.pipeline_node_id, port_name), value))
@ -383,7 +375,7 @@ class PipelineGraphNode(model.PipelineGraphNode, BasePipelineGraphNode):
return self.id
def add_to_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.AddNode(
description=self.description,
id=self.pipeline_node_id,
@ -392,7 +384,7 @@ class PipelineGraphNode(model.PipelineGraphNode, BasePipelineGraphNode):
self.set_initial_parameters()
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.RemoveNode(self.pipeline_node_id))
state.StateBase.register_class(PipelineGraphNode)
@ -431,7 +423,7 @@ class TrackMixerPipelineGraphNode(
return self.track.mixer_name
def add_to_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.AddNode(
description=self.description,
id=self.pipeline_node_id,
@ -440,7 +432,7 @@ class TrackMixerPipelineGraphNode(
self.set_initial_parameters()
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.RemoveNode(self.pipeline_node_id))
state.StateBase.register_class(TrackMixerPipelineGraphNode)
@ -459,7 +451,7 @@ class ControlSourcePipelineGraphNode(
return self.track.control_source_name
def add_to_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.AddNode(
description=self.description,
id=self.pipeline_node_id,
@ -469,7 +461,7 @@ class ControlSourcePipelineGraphNode(
self.set_initial_parameters()
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.RemoveNode(self.pipeline_node_id))
state.StateBase.register_class(ControlSourcePipelineGraphNode)
@ -488,7 +480,7 @@ class AudioSourcePipelineGraphNode(
return self.track.audio_source_name
def add_to_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.AddNode(
description=self.description,
id=self.pipeline_node_id,
@ -498,7 +490,7 @@ class AudioSourcePipelineGraphNode(
self.set_initial_parameters()
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.RemoveNode(self.pipeline_node_id))
state.StateBase.register_class(AudioSourcePipelineGraphNode)
@ -517,7 +509,7 @@ class EventSourcePipelineGraphNode(
return self.track.event_source_name
def add_to_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.AddNode(
description=self.description,
id=self.pipeline_node_id,
@ -527,7 +519,7 @@ class EventSourcePipelineGraphNode(
self.set_initial_parameters()
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.RemoveNode(self.pipeline_node_id))
state.StateBase.register_class(EventSourcePipelineGraphNode)
@ -547,7 +539,7 @@ class InstrumentPipelineGraphNode(
def update_pipeline(self):
connections = []
for connection in self.sheet.pipeline_graph_connections:
for connection in self.project.pipeline_graph_connections:
if connection.source_node is self or connection.dest_node is self:
connections.append(connection)
@ -560,7 +552,7 @@ class InstrumentPipelineGraphNode(
def add_to_pipeline(self):
node_uri, node_params = instrument_db.parse_uri(self.track.instrument)
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.AddNode(
description=self.project.get_node_description(node_uri),
id=self.pipeline_node_id,
@ -570,7 +562,7 @@ class InstrumentPipelineGraphNode(
self.set_initial_parameters()
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.RemoveNode(self.pipeline_node_id))
state.StateBase.register_class(InstrumentPipelineGraphNode)
@ -587,22 +579,14 @@ class PipelineGraphConnection(
self.dest_node = dest_node
self.dest_port = dest_port
@property
def sheet(self):
return self.parent
@property
def project(self):
return self.sheet.project
def add_to_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.ConnectPorts(
self.source_node.pipeline_node_id, self.source_port,
self.dest_node.pipeline_node_id, self.dest_port))
def remove_from_pipeline(self):
self.sheet.handle_pipeline_mutation(
self.project.handle_pipeline_mutation(
audioproc.DisconnectPorts(
self.source_node.pipeline_node_id, self.source_port,
self.dest_node.pipeline_node_id, self.dest_port))

@ -277,7 +277,7 @@ class AudioStreamProxy(object):
settings = project_client.PlayerSettings()
settings.state = 'stopped'
settings.sample_pos = 0
tmap = time_mapper.TimeMapper(self._player.sheet)
tmap = time_mapper.TimeMapper(self._player.project)
logger.info("Player proxy started.")
try:
@ -447,8 +447,8 @@ class AudioStreamProxy(object):
class Player(object):
def __init__(self, sheet, callback_address, manager, event_loop):
self.sheet = sheet
def __init__(self, project, callback_address, manager, event_loop):
self.project = project
self.manager = manager
self.callback_address = callback_address
self.event_loop = event_loop
@ -499,7 +499,7 @@ class Player(object):
self.proxy = AudioStreamProxy(self)
self.proxy.setup()
self.mutation_listener = self.sheet.listeners.add(
self.mutation_listener = self.project.listeners.add(
'pipeline_mutations', self.handle_pipeline_mutation)
self.audioproc_shm = posix_ipc.SharedMemory(
@ -517,7 +517,7 @@ class Player(object):
# TODO: with timeout
await self.audioproc_ready.wait()
self.add_track(self.sheet.master_group)
self.add_track(self.project.master_group)
logger.info("Player instance %s setup complete.", self.id)
@ -603,7 +603,7 @@ class Player(object):
async def audioproc_started(self):
self.pending_pipeline_mutations = []
self.sheet.add_to_pipeline()
self.project.add_to_pipeline()
pipeline_mutations = self.pending_pipeline_mutations[:]
self.pending_pipeline_mutations = None

@ -50,7 +50,6 @@ from noisicaa.node_db.private import db as node_db
from noisidev import perf_stats
from . import project
from . import sheet
from . import player
from . import project_client
@ -114,7 +113,6 @@ class PlayerTest(asynctest.TestCase):
await self.node_db.setup()
self.project = project.BaseProject.make_demo(demo='complex', node_db=self.node_db)
self.sheet = self.project.sheets[0]
self.callback_server = CallbackServer(self.loop)
await self.callback_server.setup()
@ -200,7 +198,7 @@ class PlayerTest(asynctest.TestCase):
@unittest.skip("TODO: async status updates are flaky")
async def test_playback_demo(self):
logger.info("Yo!")
p = player.Player(self.sheet, self.callback_server.address, self.mock_manager, self.loop)
p = player.Player(self.project, self.callback_server.address, self.mock_manager, self.loop)
try:
logger.info("Setup player...")
await p.setup()
@ -254,7 +252,7 @@ class PlayerTest(asynctest.TestCase):
@unittest.skip("TODO: async status updates are flaky")
async def test_send_message(self):
p = player.Player(self.sheet, self.callback_server.address, self.mock_manager, self.loop)
p = player.Player(self.project, self.callback_server.address, self.mock_manager, self.loop)
try:
await p.setup()
@ -280,7 +278,7 @@ class PlayerTest(asynctest.TestCase):
logger.info("Send messsage...")
p.send_message(core.build_message(
{core.MessageKey.trackId: self.sheet.master_group.tracks[0].id},
{core.MessageKey.trackId: self.project.master_group.tracks[0].id},
core.MessageType.atom,
lv2.AtomForge.build_midi_noteon(0, 65, 127)).to_bytes())

@ -38,7 +38,6 @@ from noisicaa.core import ipc
from noisicaa.ui import model
from . import project
from . import sheet
from . import player
logger = logging.getLogger(__name__)
@ -69,6 +68,9 @@ class MockAudioProcClient(object):
async def disconnect(self, shutdown=False):
logger.info("Disconnect audioproc client (shutdown=%s).", shutdown)
async def pipeline_mutation(self, mutation):
pass
async def set_backend(self, backend, **settings):
logger.info("Set to audioproc backend to %s.", backend)
if backend == 'ipc':
@ -105,8 +107,6 @@ class MockAudioProcClient(object):
class PlayerTest(asynctest.TestCase):
async def setUp(self):
self.project = project.BaseProject()
self.sheet = sheet.Sheet(name='Test')
self.project.sheets.append(self.sheet)
self.player_status_calls = asyncio.Queue()
self.callback_server = ipc.Server(self.loop, 'callback')
@ -168,7 +168,7 @@ class PlayerTest(asynctest.TestCase):
client.cleanup()
async def test_audio_stream_fails(self):
p = player.Player(self.sheet, self.callback_server.address, self.mock_manager, self.loop)
p = player.Player(self.project, self.callback_server.address, self.mock_manager, self.loop)
try:
with mock.patch('noisicaa.music.player.AudioProcClient', MockAudioProcClient):
await p.setup()

<
@ -24,6 +24,7 @@ import base64
import email.parser
import email.policy
import email.message
import itertools
import logging
import os.path
import time
@ -38,129 +39,340 @@ from .clef import Clef
from .key_signature import KeySignature
from .time_signature import TimeSignature
from .time import Duration
from . import base_track
from . import beat_track
from . import score_track
from . import sample_track
from . import commands
from . import control_track
from . import pipeline_graph
from . import misc
from . import model
from . import mutations
from . import pipeline_graph
from . import property_track
from . import sample_track
from . import score_track
from . import state
from . import commands
from . import sheet
from . import misc
from . import track_group
logger = logging.getLogger(__name__)
class AddSheet(commands.Command):
name = core.Property(str, allow_none=True)
class UpdateProjectProperties(commands.Command):
bpm = core.Property(int, allow_none=True)
def __init__(self, bpm=None, state=None):
super().__init__(state=state)
if state is None:
self.bpm = bpm
def run(self, project):
assert isinstance(project, BaseProject)
if self.bpm is not None:
project.bpm = self.bpm
commands.Command.register_command(UpdateProjectProperties)
def __init__(self, name=None, state=None):
class AddTrack(commands.Command):
track_type = core.Property(str)
parent_group_id = core.Property(str)
insert_index = core.Property(int)
def __init__(self, track_type=None, parent_group_id=None, insert_index=-1, state=None):
super().__init__(state=state)
if state is None:
self.name = name
self.track_type = track_type
self.parent_group_id = parent_group_id
self.insert_index = insert_index
def run(self, project):
assert isinstance(project, BaseProject)
if self.name is not None:
name = self.name
parent_group = project.get_object(self.parent_group_id)
assert parent_group.is_child_of(project)
assert isinstance(parent_group, track_group.TrackGroup)
if self.insert_index == -1:
insert_index = len(parent_group.tracks)
else:
idx = 1
while True:
name = 'Sheet %d' % idx
if name not in [sheet.name for sheet in project.sheets]:
break
idx += 1
insert_index = self.insert_index
assert 0 <= insert_index <= len(parent_group.tracks)
track_name = "Track %d" % (len(parent_group.tracks) + 1)
track_cls_map = {
'score': score_track.ScoreTrack,
'beat': beat_track.BeatTrack,
'control': control_track.ControlTrack,
'sample': sample_track.SampleTrack,
'group': track_group.TrackGroup,
}
track_cls = track_cls_map[self.track_type]
kwargs = {}
if issubclass(track_cls, model.MeasuredTrack):
num_measures = 1
for track in parent_group.walk_tracks():
if isinstance(track, model.MeasuredTrack):
num_measures = max(num_measures, len(track.measure_list))
kwargs['num_measures'] = num_measures
track = track_cls(name=track_name, **kwargs)
if name in [s.name for s in project.sheets]:
raise ValueError("Sheet %s already exists" % name)
s = sheet.Sheet(name)
project.add_sheet(s)
project.add_track(parent_group, insert_index, track)
commands.Command.register_command(AddSheet)
return insert_index
commands.Command.register_command(AddTrack)
class ClearSheet(commands.Command):
name = core.Property(str)
def __init__(self, name=None, state=None):
class RemoveTrack(commands.Command):
track_id = core.Property(str)
def __init__(self, track_id=None, state=None):
super().__init__(state=state)
if state is None:
self.name = name
self.track_id = track_id
def run(self, project):
assert isinstance(project, BaseProject)
project.get_sheet(self.name).clear()
track = project.get_object(self.track_id)
assert track.is_child_of(project)
parent_group = track.parent
project.remove_track(parent_group, track)
commands.Command.register_command(ClearSheet)
commands.Command.register_command(RemoveTrack)
class DeleteSheet(commands.Command):
name = core.Property(str)
class InsertMeasure(commands.Command):
tracks = core.ListProperty(str)
pos = core.Property(int)
def __init__(self, name=None, state=None):
def __init__(self, tracks=None, pos=None, state=None):
super().__init__(state=state)
if state is None:
self.name = name
self.tracks.extend(tracks)
self.pos = pos
def run(self, project):
assert isinstance(project, BaseProject)
assert len(project.sheets) > 1
for idx, sheet in enumerate(project.sheets):
if sheet.name == self.name:
sheet.remove_from_pipeline()
del project.sheets[idx]
project.current_sheet = min(
project.current_sheet, len(project.sheets) - 1)
return
if not self.tracks:
project.property_track.insert_measure(self.pos)
else:
project.property_track.append_measure()
raise ValueError("No sheet %r" % self.name)
for track in project.master_group.walk_tracks():
if not isinstance(track, model.MeasuredTrack):
continue
commands.Command.register_command(DeleteSheet)
if not self.tracks or track.id in self.tracks:
track.insert_measure(self.pos)
else:
track.append_measure()
commands.Command.register_command(InsertMeasure)
class RenameSheet(commands.Command):
name = core.Property(str)
new_name = core.Property(str)
class RemoveMeasure(commands.Command):
tracks = core.ListProperty(int)
pos = core.Property(int)
def __init__(self, name=None, new_name=None, state=None):
def __init__(self, tracks=None, pos=None, state=None):
super().__init__(state=state)
if state is None:
self.name = name
self.new_name = new_name
self.tracks.extend(tracks)
self.pos = pos
def run(self, project):
assert isinstance(project, BaseProject)
if self.name == self.new_name:
return
if not self.tracks:
project.property_track.remove_measure(self.pos)
for idx, track in enumerate(project.master_group.tracks):
if not self.tracks or idx in self.tracks:
track.remove_measure(self.pos)
if self.tracks:
track.append_measure()
commands.Command.register_command(RemoveMeasure)
class ClearMeasures(commands.Command):
measure_ids = core.ListProperty(str)
def __init__(self, measure_ids=None, state=None):
super().__init__(state=state)
if state is None:
self.measure_ids.extend(measure_ids)
def run(self, project):
assert isinstance(project, BaseProject)
measure_references = [project.get_object(obj_id) for obj_id in self.measure_ids]
assert all(isinstance(obj, base_track.MeasureReference) for obj in measure_references)
if self.new_name in [s.name for s in project.sheets]:
raise ValueError("Sheet %s already exists" % self.new_name)
affected_track_ids = set(obj.track.id for obj in measure_references)
sheet = project.get_sheet(self.name)
sheet.name = self.new_name
for mref in measure_references:
track = mref.track
measure = track.create_empty_measure(mref.measure)
track.measure_heap.append(measure)
mref.measure_id = measure.id
commands.Command.register_command(RenameSheet)
for track_id in affected_track_ids:
project.get_object(track_id).garbage_collect_measures()
commands.Command.register_command(ClearMeasures)
class SetCurrentSheet(commands.Command):
name = core.Property(str)
def __init__(self, name=None, state=None):
class PasteMeasures(commands.Command):
mode = core.Property(str)
src_objs = core.ListProperty(bytes)
target_ids = core.ListProperty(str)
def __init__(self, mode=None, src_objs=None, target_ids=None, state=None):
super().__init__(state=state)
if state is None:
self.name = name
self.mode = mode
self.src_objs.extend(src_objs)
self.target_ids.extend(target_ids)
def run(self, project):
assert isinstance(project, BaseProject)
project.current_sheet = project.get_sheet_index(self.name)
src_measures = [project.deserialize_object(obj) for obj in self.src_objs]
assert all(isinstance(obj, base_track.Measure) for obj in src_measures)
target_measures = [project.get_object(obj_id) for obj_id in self.target_ids]
assert all(isinstance(obj, base_track.MeasureReference) for obj in target_measures)
affected_track_ids = set(obj.track.id for obj in target_measures)
assert len(affected_track_ids) == 1
if self.mode == 'link':
for target, src in zip(target_measures, itertools.cycle(src_measures)):
assert(any(src.id == m.id for m in target.track.measure_heap))
target.measure_id = src.id
commands.Command.register_command(SetCurrentSheet)
elif self.mode == 'overwrite':
measure_map = {}
for target, src in zip(target_measures, itertools.cycle(src_measures)):
try:
measure = measure_map[src.id]
except KeyError:
measure = measure_map[src.id] = src.clone()
target.track.measure_heap.append(measure)
target.measure_id = measure.id
else:
raise ValueError(mode)
for track_id in affected_track_ids:
project.get_object(track_id).garbage_collect_measures()
commands.Command.register_command(PasteMeasures)
class AddPipelineGraphNode(commands.Command):
uri = core.Property(str)
graph_pos = core.Property(misc.Pos2F)
def __init__(self, uri=None, graph_pos=None, state=None):
super().__init__(state=state)
if state is None:
self.uri = uri
self.graph_pos = graph_pos
def run(self, project):
assert isinstance(project, BaseProject)
node_desc = project.project.get_node_description(self.uri)
node = pipeline_graph.PipelineGraphNode(
name=node_desc.display_name,
node_uri=self.uri,
graph_pos=self.graph_pos)
project.add_pipeline_graph_node(node)
return node.id
commands.Command.register_command(AddPipelineGraphNode)
class RemovePipelineGraphNode(commands.Command):
node_id = core.Property(str)
def __init__(self, node_id=None, state=None):
super().__init__(state=state)
if state is None:
self.node_id = node_id