Browse Source
- Merge the base/client/server model class trees into a single tree. - Move the model into the UI process. - Autogenerate model boilerplate code. - Replace commands by arbitrary mutations directly from the UI. - Assorted other cleanups.looper
215 changed files with 6481 additions and 11383 deletions
@ -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,48 +0,0 @@
|
||||
/* |
||||
* @begin:license |
||||
* |
||||
* Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de> |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
* @end:license |
||||
*/ |
||||
|
||||
syntax = "proto2"; |
||||
|
||||
import "noisicaa/audioproc/public/musical_time.proto"; |
||||
import "noisicaa/model/project.proto"; |
||||
|
||||
package noisicaa.pb; |
||||
|
||||
message UpdateBeatTrack { |
||||
required uint64 track_id = 1; |
||||
optional Pitch set_pitch = 2; |
||||
} |
||||
|
||||
message CreateBeat { |
||||
required uint64 measure_id = 1; |
||||
optional MusicalDuration time = 2; |
||||
optional uint32 velocity = 3; |
||||
} |
||||
|
||||
message UpdateBeat { |
||||
required uint64 beat_id = 1; |
||||
optional uint32 set_velocity = 2; |
||||
} |
||||
|
||||
message DeleteBeat { |
||||
optional uint64 beat_id = 1; |
||||
} |
@ -1,65 +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 noisicaa import audioproc |
||||
from noisicaa import model |
||||
from noisicaa import music |
||||
from noisicaa.builtin_nodes import commands_registry_pb2 |
||||
from . import client_impl |
||||
|
||||
|
||||
def update( |
||||
track: client_impl.BeatTrack, *, set_pitch: model.Pitch = None) -> music.Command: |
||||
cmd = music.Command(command='update_beat_track') |
||||
pb = cmd.Extensions[commands_registry_pb2.update_beat_track] |
||||
pb.track_id = track.id |
||||
if set_pitch is not None: |
||||
pb.set_pitch.CopyFrom(set_pitch.to_proto()) |
||||
return cmd |
||||
|
||||
def create_beat( |
||||
measure: client_impl.BeatMeasure, *, |
||||
time: audioproc.MusicalTime, |
||||
velocity: int = None |
||||
) -> music.Command: |
||||
cmd = music.Command(command='create_beat') |
||||
pb = cmd.Extensions[commands_registry_pb2.create_beat] |
||||
pb.measure_id = measure.id |
||||
pb.time.CopyFrom(time.to_proto()) |
||||
if velocity is not None: |
||||
pb.velocity = velocity |
||||
return cmd |
||||
|
||||
def update_beat( |
||||
beat: client_impl.Beat, *, set_velocity: int = None) -> music.Command: |
||||
cmd = music.Command(command='update_beat') |
||||
pb = cmd.Extensions[commands_registry_pb2.update_beat] |
||||
pb.beat_id = beat.id |
||||
if set_velocity is not None: |
||||
pb.set_velocity = set_velocity |
||||
return cmd |
||||
|
||||
def delete_beat(beat: client_impl.Beat) -> music.Command: |
||||
cmd = music.Command(command='delete_beat') |
||||
pb = cmd.Extensions[commands_registry_pb2.delete_beat] |
||||
pb.beat_id = beat.id |
||||
return cmd |
@ -1,41 +0,0 @@
|
||||
/* |
||||
* @begin:license |
||||
* |
||||
* Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de> |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
* @end:license |
||||
*/ |
||||
|
||||
syntax = "proto2"; |
||||
|
||||
import "noisicaa/audioproc/public/musical_time.proto"; |
||||
import "noisicaa/model/project.proto"; |
||||
|
||||
package noisicaa.pb; |
||||
|
||||
message Beat { |
||||
optional MusicalDuration time = 1; |
||||
optional uint32 velocity = 2; |
||||
} |
||||
|
||||
message BeatMeasure { |
||||
repeated uint64 beats = 1; |
||||
} |
||||
|
||||
message BeatTrack { |
||||
optional Pitch pitch = 1; |
||||
} |
@ -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,63 +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 noisicaa import audioproc |
||||
from noisicaa import model |
||||
from noisicaa.music import pmodel_test |
||||
from . import server_impl |
||||
|
||||
T0 = audioproc.MusicalDuration(0, 4) |
||||
|
||||
|
||||
class BeatTrackTest(pmodel_test.MeasuredTrackMixin, pmodel_test.ModelTest): |
||||
cls = server_impl.BeatTrack |
||||
create_args = {'name': 'test'} |
||||
measure_cls = server_impl.BeatMeasure |
||||
|
||||
def test_pitch(self): |
||||
track = self.pool.create(self.cls, **self.create_args) |
||||
|
||||
track.pitch = model.Pitch('F4') |
||||
self.assertEqual(track.pitch, model.Pitch('F4')) |
||||
|
||||
|
||||
class BeatMeasureTest(pmodel_test.ModelTest): |
||||
def test_beats(self): |
||||
measure = self.pool.create(server_impl.BeatMeasure) |
||||
|
||||
beat = self.pool.create(server_impl.Beat, time=T0, velocity=100) |
||||
measure.beats.append(beat) |
||||
self.assertIs(measure.beats[0], beat) |
||||
|
||||
|
||||
class BeatTest(pmodel_test.ModelTest): |
||||
def test_time(self): |
||||
beat = self.pool.create(server_impl.Beat, time=T0, velocity=100) |
||||
|
||||
beat.time = audioproc.MusicalDuration(1, 4) |
||||
self.assertEqual(beat.time, audioproc.MusicalDuration(1, 4)) |
||||
|
||||
def test_velocity(self): |
||||
beat = self.pool.create(server_impl.Beat, time=T0, velocity=100) |
||||
|
||||
beat.velocity = 120 |
||||
self.assertEqual(beat.velocity, 120) |