parent
1ab72dba20
commit
4d072f83f5
@ -1,64 +0,0 @@
|
||||
#!/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 typing import Sequence
|
||||
|
||||
from noisicaa.core.typing_extra import down_cast
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import model
|
||||
from noisicaa.music import project_client_model
|
||||
from . import model as beat_track_model
|
||||
|
||||
|
||||
class Beat(
|
||||
project_client_model.ProjectChild,
|
||||
beat_track_model.Beat,
|
||||
project_client_model.ObjectBase):
|
||||
@property
|
||||
def time(self) -> audioproc.MusicalDuration:
|
||||
return audioproc.MusicalDuration.from_proto(self.get_property_value('time'))
|
||||
|
||||
@property
|
||||
def velocity(self) -> int:
|
||||
return self.get_property_value('velocity')
|
||||
|
||||
@property
|
||||
def measure(self) -> 'BeatMeasure':
|
||||
return down_cast(BeatMeasure, super().measure)
|
||||
|
||||
|
||||
class BeatMeasure(
|
||||
project_client_model.Measure,
|
||||
beat_track_model.BeatMeasure,
|
||||
project_client_model.ObjectBase):
|
||||
@property
|
||||
def beats(self) -> Sequence[Beat]:
|
||||
return self.get_property_value('beats')
|
||||
|
||||
|
||||
class BeatTrack(
|
||||
project_client_model.MeasuredTrack,
|
||||
beat_track_model.BeatTrack,
|
||||
project_client_model.ObjectBase):
|
||||
@property
|
||||
def pitch(self) -> model.Pitch:
|
||||
return self.get_property_value('pitch')
|
@ -1,203 +0,0 @@
|
||||
#!/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
|
||||
|
||||
import logging
|
||||
from typing import Any, MutableSequence, Optional, Iterator, Callable
|
||||
|
||||
from noisicaa.core.typing_extra import down_cast
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import model
|
||||
from noisicaa.music import commands
|
||||
from noisicaa.music import pmodel
|
||||
from noisicaa.music import base_track
|
||||
from noisicaa.builtin_nodes import commands_registry_pb2
|
||||
from . import commands_pb2
|
||||
from . import model as beat_track_model
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateBeatTrack(commands.Command):
|
||||
proto_type = 'update_beat_track'
|
||||
proto_ext = commands_registry_pb2.update_beat_track
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.UpdateBeatTrack, self.pb)
|
||||
track = down_cast(BeatTrack, self.pool[pb.track_id])
|
||||
|
||||
if pb.HasField('set_pitch'):
|
||||
track.pitch = model.Pitch.from_proto(pb.set_pitch)
|
||||
|
||||
|
||||
class CreateBeat(commands.Command):
|
||||
proto_type = 'create_beat'
|
||||
proto_ext = commands_registry_pb2.create_beat
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.CreateBeat, self.pb)
|
||||
measure = down_cast(BeatMeasure, self.pool[pb.measure_id])
|
||||
|
||||
time = audioproc.MusicalDuration.from_proto(pb.time)
|
||||
assert audioproc.MusicalDuration(0, 1) <= time < measure.duration
|
||||
|
||||
if pb.HasField('velocity'):
|
||||
velocity = pb.velocity
|
||||
else:
|
||||
velocity = 100
|
||||
|
||||
beat = self.pool.create(
|
||||
Beat,
|
||||
time=time,
|
||||
velocity=velocity)
|
||||
measure.beats.append(beat)
|
||||
|
||||
|
||||
class UpdateBeat(commands.Command):
|
||||
proto_type = 'update_beat'
|
||||
proto_ext = commands_registry_pb2.update_beat
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.UpdateBeat, self.pb)
|
||||
beat = down_cast(Beat, self.pool[pb.beat_id])
|
||||
|
||||
if pb.HasField('set_velocity'):
|
||||
beat.velocity = pb.set_velocity
|
||||
|
||||
|
||||
class DeleteBeat(commands.Command):
|
||||
proto_type = 'delete_beat'
|
||||
proto_ext = commands_registry_pb2.delete_beat
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.DeleteBeat, self.pb)
|
||||
beat = down_cast(Beat, self.pool[pb.beat_id])
|
||||
|
||||
measure = beat.measure
|
||||
del measure.beats[beat.index]
|
||||
|
||||
|
||||
class Beat(pmodel.ProjectChild, beat_track_model.Beat, pmodel.ObjectBase):
|
||||
def create(
|
||||
self, *,
|
||||
time: Optional[audioproc.MusicalDuration] = None,
|
||||
velocity: Optional[int] = None,
|
||||
**kwargs: Any) -> None:
|
||||
super().create(**kwargs)
|
||||
|
||||
self.time = time
|
||||
self.velocity = velocity
|
||||
|
||||
@property
|
||||
def time(self) -> audioproc.MusicalDuration:
|
||||
return audioproc.MusicalDuration.from_proto(self.get_property_value('time'))
|
||||
|
||||
@time.setter
|
||||
def time(self, value: audioproc.MusicalDuration) -> None:
|
||||
self.set_property_value('time', value.to_proto())
|
||||
|
||||
@property
|
||||
def velocity(self) -> int:
|
||||
return self.get_property_value('velocity')
|
||||
|
||||
@velocity.setter
|
||||
def velocity(self, value: int) -> None:
|
||||
self.set_property_value('velocity', value)
|
||||
|
||||
@property
|
||||
def measure(self) -> 'BeatMeasure':
|
||||
return down_cast(BeatMeasure, super().measure)
|
||||
|
||||
|
||||
class BeatMeasure(base_track.Measure, beat_track_model.BeatMeasure, pmodel.ObjectBase):
|
||||
@property
|
||||
def beats(self) -> MutableSequence[Beat]:
|
||||
return self.get_property_value('beats')
|
||||
|
||||
@property
|
||||
def empty(self) -> bool:
|
||||
return len(self.beats) == 0
|
||||
|
||||
|
||||
class BeatTrackConnector(base_track.MeasuredTrackConnector):
|
||||
_node = None # type: BeatTrack
|
||||
|
||||
def _add_track_listeners(self) -> None:
|
||||
self._listeners['pitch'] = self._node.pitch_changed.add(self.__pitch_changed)
|
||||
|
||||
def _add_measure_listeners(self, mref: pmodel.MeasureReference) -> None:
|
||||
measure = down_cast(BeatMeasure, mref.measure)
|
||||
self._listeners['measure:%s:beats' % mref.id] = measure.content_changed.add(
|
||||
lambda _=None: self.__measure_beats_changed(mref)) # type: ignore
|
||||
|
||||
def _remove_measure_listeners(self, mref: pmodel.MeasureReference) -> None:
|
||||
self._listeners.pop('measure:%s:beats' % mref.id).remove()
|
||||
|
||||
def _create_events(
|
||||
self, time: audioproc.MusicalTime, measure: pmodel.Measure
|
||||
) -> Iterator[base_track.PianoRollInterval]:
|
||||
measure = down_cast(BeatMeasure, measure)
|
||||
for beat in measure.beats:
|
||||
beat_time = time + beat.time
|
||||
event = base_track.PianoRollInterval(
|
||||
beat_time, beat_time + audioproc.MusicalDuration(1, 4),
|
||||
self._node.pitch, 127)
|
||||
yield event
|
||||
|
||||
def __pitch_changed(self, change: model.PropertyChange) -> None:
|
||||
self._update_measure_range(0, len(self._node.measure_list))
|
||||
|
||||
def __measure_beats_changed(self, mref: pmodel.MeasureReference) -> None:
|
||||
self._update_measure_range(mref.index, mref.index + 1)
|
||||
|
||||
|
||||
class BeatTrack(base_track.MeasuredTrack, beat_track_model.BeatTrack, pmodel.ObjectBase):
|
||||
measure_cls = BeatMeasure
|
||||
|
||||
def create(
|
||||
self, *,
|
||||
pitch: Optional[model.Pitch] = None,
|
||||
num_measures: int = 1, **kwargs: Any) -> None:
|
||||
super().create(**kwargs)
|
||||
|
||||
if pitch is None:
|
||||
self.pitch = model.Pitch('B2')
|
||||
else:
|
||||
self.pitch = pitch
|
||||
|
||||
for _ in range(num_measures):
|
||||
self.append_measure()
|
||||
|
||||
@property
|
||||
def pitch(self) -> model.Pitch:
|
||||
return self.get_property_value('pitch')
|
||||
|
||||
@pitch.setter
|
||||
def pitch(self, value: model.Pitch) -> None:
|
||||
self.set_property_value('pitch', value)
|
||||
|
||||
def create_node_connector(
|
||||
self,
|
||||
message_cb: Callable[[audioproc.ProcessorMessage], None],
|
||||
audioproc_client: audioproc.AbstractAudioProcClient,
|
||||
) -> BeatTrackConnector:
|
||||
return BeatTrackConnector(
|
||||
node=self, message_cb=message_cb, audioproc_client=audioproc_client)
|
@ -1,44 +0,0 @@
|
||||
#!/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 typing import Sequence
|
||||
|
||||
from noisicaa import audioproc
|
||||
from noisicaa.music import project_client_model
|
||||
from . import model
|
||||
|
||||
|
||||
class ControlPoint(
|
||||
project_client_model.ProjectChild, model.ControlPoint, project_client_model.ObjectBase):
|
||||
@property
|
||||
def time(self) -> audioproc.MusicalTime:
|
||||
return self.get_property_value('time')
|
||||
|
||||
@property
|
||||
def value(self) -> float:
|
||||
return self.get_property_value('value')
|
||||
|
||||
|
||||
class ControlTrack(project_client_model.Track, model.ControlTrack):
|
||||
@property
|
||||
def points(self) -> Sequence[ControlPoint]:
|
||||
return self.get_property_value('points')
|
@ -1,214 +0,0 @@
|
||||
#!/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
|
||||
|
||||
import logging
|
||||
import random
|
||||
from typing import cast, Any, Dict, MutableSequence, Optional, Callable
|
||||
|
||||
from noisicaa.core.typing_extra import down_cast
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import model
|
||||
from noisicaa import core
|
||||
from noisicaa.music import commands
|
||||
from noisicaa.music import pmodel
|
||||
from noisicaa.music import node_connector
|
||||
from noisicaa.music import base_track
|
||||
from noisicaa.builtin_nodes import commands_registry_pb2
|
||||
from . import commands_pb2
|
||||
from . import model as control_track_model
|
||||
from . import processor_messages
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateControlPoint(commands.Command):
|
||||
proto_type = 'create_control_point'
|
||||
proto_ext = commands_registry_pb2.create_control_point
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.CreateControlPoint, self.pb)
|
||||
track = down_cast(ControlTrack, self.pool[pb.track_id])
|
||||
|
||||
insert_time = audioproc.MusicalTime.from_proto(pb.time)
|
||||
for insert_index, point in enumerate(track.points):
|
||||
if point.time == insert_time:
|
||||
raise ValueError("Duplicate control point")
|
||||
if point.time > insert_time:
|
||||
break
|
||||
else:
|
||||
insert_index = len(track.points)
|
||||
|
||||
control_point = self.pool.create(ControlPoint, time=insert_time, value=pb.value)
|
||||
track.points.insert(insert_index, control_point)
|
||||
|
||||
|
||||
class UpdateControlPoint(commands.Command):
|
||||
proto_type = 'update_control_point'
|
||||
proto_ext = commands_registry_pb2.update_control_point
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.UpdateControlPoint, self.pb)
|
||||
point = down_cast(ControlPoint, self.pool[pb.point_id])
|
||||
|
||||
if pb.HasField('set_time'):
|
||||
new_time = audioproc.MusicalTime.from_proto(pb.set_time)
|
||||
if not point.is_first:
|
||||
if new_time <= cast(ControlPoint, point.prev_sibling).time:
|
||||
raise ValueError("Control point out of order.")
|
||||
else:
|
||||
if new_time < audioproc.MusicalTime(0, 4):
|
||||
raise ValueError("Control point out of order.")
|
||||
|
||||
if not point.is_last:
|
||||
if new_time >= cast(ControlPoint, point.next_sibling).time:
|
||||
raise ValueError("Control point out of order.")
|
||||
|
||||
point.time = new_time
|
||||
|
||||
if pb.HasField('set_value'):
|
||||
# TODO: check that value is in valid range.
|
||||
point.value = pb.set_value
|
||||
|
||||
|
||||
class DeleteControlPoint(commands.Command):
|
||||
proto_type = 'delete_control_point'
|
||||
proto_ext = commands_registry_pb2.delete_control_point
|
||||
|
||||
def run(self) -> None:
|
||||
pb = down_cast(commands_pb2.DeleteControlPoint, self.pb)
|
||||
point = down_cast(ControlPoint, self.pool[pb.point_id])
|
||||
track = down_cast(ControlTrack, point.parent)
|
||||
|
||||
del track.points[point.index]
|
||||
|
||||
|
||||
class ControlTrackConnector(node_connector.NodeConnector):
|
||||
_node = None # type: ControlTrack
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__node_id = self._node.pipeline_node_id
|
||||
self.__listeners = {} # type: Dict[str, core.Listener]
|
||||
self.__point_ids = {} # type: Dict[int, int]
|
||||
|
||||
def _init_internal(self) -> None:
|
||||
for point in self._node.points:
|
||||
self.__add_point(point)
|
||||
|
||||
self.__listeners['points'] = self._node.points_changed.add(
|
||||
self.__points_list_changed)
|
||||
|
||||
def close(self) -> None:
|
||||
for listener in self.__listeners.values():
|
||||
listener.remove()
|
||||
self.__listeners.clear()
|
||||
|
||||
super().close()
|
||||
|
||||
def __points_list_changed(self, change: model.PropertyChange) -> None:
|
||||
if isinstance(change, model.PropertyListInsert):
|
||||
self.__add_point(change.new_value)
|
||||
|
||||
elif isinstance(change, model.PropertyListDelete):
|
||||
self.__remove_point(change.old_value)
|
||||
|
||||
else:
|
||||
raise TypeError("Unsupported change type %s" % type(change))
|
||||
|
||||
def __add_point(self, point: 'ControlPoint') -> None:
|
||||
point_id = self.__point_ids[point.id] = random.getrandbits(64)
|
||||