Skip to content
Permalink
Browse files

New copy&paste system.

  • Loading branch information...
odahoda committed Sep 22, 2019
1 parent c4e63e2 commit f4c583ee5b9da3f85cfabe6eaaa91bfaa8ff79cb
Showing with 1,284 additions and 471 deletions.
  1. +1 −1 3rdparty/typeshed/PyQt5/QtGui.pyi
  2. +1 −1 3rdparty/typeshed/PyQt5/QtWidgets.pyi
  3. +1 −0 noisicaa/builtin_nodes/beat_track/track_ui.py
  4. +40 −0 noisicaa/builtin_nodes/pianoroll_track/clipboard.proto
  5. +83 −1 noisicaa/builtin_nodes/pianoroll_track/model.py
  6. +91 −0 noisicaa/builtin_nodes/pianoroll_track/model_test.py
  7. +268 −99 noisicaa/builtin_nodes/pianoroll_track/track_ui.py
  8. +5 −20 noisicaa/builtin_nodes/pianoroll_track/track_ui_test.py
  9. +1 −0 noisicaa/builtin_nodes/pianoroll_track/wscript
  10. +0 −35 noisicaa/builtin_nodes/score_track/model_test.py
  11. +4 −3 noisicaa/builtin_nodes/score_track/track_ui.py
  12. +3 −0 noisicaa/music/__init__.py
  13. +40 −0 noisicaa/music/base_track.proto
  14. +55 −1 noisicaa/music/base_track.py
  15. +29 −0 noisicaa/music/clipboard.proto
  16. +1 −35 noisicaa/music/project.py
  17. +2 −0 noisicaa/music/wscript
  18. +271 −0 noisicaa/ui/clipboard.py
  19. +10 −11 noisicaa/ui/editor_app.py
  20. +4 −43 noisicaa/ui/editor_window.py
  21. +44 −0 noisicaa/ui/pianoroll.proto
  22. +160 −27 noisicaa/ui/pianoroll.py
  23. +0 −22 noisicaa/ui/project_view.py
  24. +0 −70 noisicaa/ui/selection_set.py
  25. +2 −0 noisicaa/ui/track_list/base_track_editor.py
  26. +1 −33 noisicaa/ui/track_list/editor.py
  27. +0 −2 noisicaa/ui/track_list/editor_test.py
  28. +139 −36 noisicaa/ui/track_list/measured_track_editor.py
  29. +0 −3 noisicaa/ui/track_list/view.py
  30. +3 −18 noisicaa/ui/ui_base.py
  31. +2 −1 noisicaa/ui/wscript
  32. +23 −9 noisidev/uitest.py
@@ -556,6 +556,7 @@ class QConicalGradient(QGradient):


class QClipboard(QtCore.QObject):
dataChanged = ... # type: PYQT_SIGNAL

class Mode(int): ...
Clipboard = ... # type: 'QClipboard.Mode'
@@ -564,7 +565,6 @@ class QClipboard(QtCore.QObject):

def selectionChanged(self) -> None: ...
def findBufferChanged(self) -> None: ...
def dataChanged(self) -> None: ...
def changed(self, mode: 'QClipboard.Mode') -> None: ...
def setPixmap(self, a0: QPixmap, mode: 'QClipboard.Mode' = ...) -> None: ...
def setImage(self, a0: 'QImage', mode: 'QClipboard.Mode' = ...) -> None: ...
@@ -1053,6 +1053,7 @@ class QActionGroup(QtCore.QObject):


class QApplication(QtGui.QGuiApplication):
focusChanged = ... # type: PYQT_SIGNAL

class ColorSpec(int): ...
NormalColor = ... # type: 'QApplication.ColorSpec'
@@ -1068,7 +1069,6 @@ class QApplication(QtGui.QGuiApplication):
def closeAllWindows() -> None: ...
@staticmethod
def aboutQt() -> None: ...
def focusChanged(self, old: QWidget, now: QWidget) -> None: ...
def styleSheet(self) -> str: ...
def autoSipEnabled(self) -> bool: ...
def notify(self, a0: QtCore.QObject, a1: QtCore.QEvent) -> bool: ...
@@ -230,6 +230,7 @@ def leaveEvent(self, evt: QtCore.QEvent) -> None:


class BeatTrackEditor(measured_track_editor.MeasuredTrackEditor):
measure_type = 'beat'
measure_editor_cls = BeatMeasureEditor

def __init__(self, **kwargs: Any) -> None:
@@ -0,0 +1,40 @@
/*
* @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/music/clipboard.proto";
import "noisicaa/music/model_base.proto";
import "noisicaa/builtin_nodes/pianoroll_track/model.proto";

package noisicaa.pb;

message PianoRollSegments {
required MusicalTime time = 1;
repeated ObjectTree segments = 2;
repeated PianoRollSegmentRef segment_refs = 3;
}

extend ClipboardContents {
optional PianoRollSegments pianoroll_segments = 417000;
}
@@ -23,8 +23,9 @@
import bisect
import functools
import logging
from typing import Any, Callable, Iterator, Dict, Tuple
from typing import Any, Callable, Iterator, Dict, List, Set, Tuple

from noisicaa.core.typing_extra import down_cast
from noisicaa import audioproc
from noisicaa import core
from noisicaa import music
@@ -34,6 +35,7 @@
from . import node_description
from . import processor_messages
from . import _model
from . import clipboard_pb2

logger = logging.getLogger(__name__)

@@ -313,6 +315,10 @@ def create(
assert segment is not None
self.segment = segment

@property
def end_time(self) -> audioproc.MusicalTime:
return self.time + self.segment.duration


class PianoRollTrack(_model.PianoRollTrack):
def create_node_connector(
@@ -400,3 +406,79 @@ def split_segment(
segment1.add_event(value_types.MidiEvent(
rel_split_time, interval.end_event.midi_event.midi))
segment1.remove_event(interval.end_event)

def copy_segments(
self, segment_refs: List[PianoRollSegmentRef]
) -> clipboard_pb2.PianoRollSegments:
data = clipboard_pb2.PianoRollSegments()

time = min(segment_ref.time for segment_ref in segment_refs)
data.time.CopyFrom(time.to_proto())

segment_ids = set() # type: Set[int]
for segment_ref in segment_refs:
segment = segment_ref.segment
if segment.id not in segment_ids:
serialized_segment = data.segments.add()
serialized_segment.CopyFrom(segment.serialize())
segment_ids.add(segment.id)

serialized_segment_ref = data.segment_refs.add()
serialized_segment_ref.time.CopyFrom(segment_ref.time.relative_to(time).to_proto())
serialized_segment_ref.segment = segment.id

return data

def cut_segments(
self, segment_refs: List[PianoRollSegmentRef]
) -> clipboard_pb2.PianoRollSegments:
data = self.copy_segments(segment_refs)

for segment_ref in segment_refs:
del self.segments[segment_ref.index]
self.__garbage_collect_segments()

return data

def paste_segments(
self, data: clipboard_pb2.PianoRollSegments, time: audioproc.MusicalTime
) -> List[PianoRollSegmentRef]:
segment_map = {} # type: Dict[int, PianoRollSegment]

for serialized_segment in data.segments:
segment = down_cast(PianoRollSegment, self._pool.clone_tree(serialized_segment))
self.segment_heap.append(segment)
segment_map[serialized_segment.root] = segment

segment_refs = [] # type: List[PianoRollSegmentRef]
for serialized_segment_ref in data.segment_refs:
ref_time = audioproc.MusicalTime.from_proto(serialized_segment_ref.time)
ref = self._pool.create(
PianoRollSegmentRef,
time=time + (ref_time - audioproc.MusicalTime(0, 1)),
segment=segment_map[serialized_segment_ref.segment])
self.segments.append(ref)
segment_refs.append(ref)

return segment_refs

def link_segments(
self, data: clipboard_pb2.PianoRollSegments, time: audioproc.MusicalTime
) -> List[PianoRollSegmentRef]:
segment_map = {} # type: Dict[int, PianoRollSegment]

for segment in self.segment_heap:
segment_map[segment.id] = segment

segment_refs = [] # type: List[PianoRollSegmentRef]
for serialized_segment_ref in data.segment_refs:
assert serialized_segment_ref.segment in segment_map
ref_time = audioproc.MusicalTime.from_proto(serialized_segment_ref.time)
ref = self._pool.create(
PianoRollSegmentRef,
time=time + (ref_time - audioproc.MusicalTime(0, 1)),
segment=segment_map[serialized_segment_ref.segment])
self.segments.append(ref)
segment_refs.append(ref)

return segment_refs
@@ -103,6 +103,97 @@ class PianoRollTrackTest(base_track_test.TrackTestMixin, unittest.AsyncTestCase)
MEVT(MT(2, 4), NOTE_OFF(0, 63)),
})

async def test_copy_segment(self):
track = await self._add_track()
with self.project.apply_mutations('test'):
segment_ref = track.create_segment(MT(0, 4), MD(4, 4))
segment = segment_ref.segment
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 70, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 70)))
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 60, 100)))
segment.add_event(MEVT(MT(1, 4), NOTE_OFF(0, 60)))
segment.add_event(MEVT(MT(1, 4), NOTE_ON(0, 61, 100)))
segment.add_event(MEVT(MT(2, 4), NOTE_OFF(0, 61)))
segment.add_event(MEVT(MT(2, 4), NOTE_ON(0, 62, 100)))
segment.add_event(MEVT(MT(3, 4), NOTE_OFF(0, 62)))
segment.add_event(MEVT(MT(3, 4), NOTE_ON(0, 63, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 63)))

data = track.copy_segments([segment_ref])
self.assertEqual(len(data.segments), 1)

async def test_cut_segment(self):
track = await self._add_track()
with self.project.apply_mutations('test'):
segment_ref = track.create_segment(MT(0, 4), MD(4, 4))
segment = segment_ref.segment
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 70, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 70)))
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 60, 100)))
segment.add_event(MEVT(MT(1, 4), NOTE_OFF(0, 60)))
segment.add_event(MEVT(MT(1, 4), NOTE_ON(0, 61, 100)))
segment.add_event(MEVT(MT(2, 4), NOTE_OFF(0, 61)))
segment.add_event(MEVT(MT(2, 4), NOTE_ON(0, 62, 100)))
segment.add_event(MEVT(MT(3, 4), NOTE_OFF(0, 62)))
segment.add_event(MEVT(MT(3, 4), NOTE_ON(0, 63, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 63)))

with self.project.apply_mutations('test'):
data = track.cut_segments([segment_ref])
self.assertEqual(len(data.segments), 1)
self.assertEqual(len(track.segments), 0)

async def test_paste_segment(self):
track = await self._add_track()
with self.project.apply_mutations('test'):
segment_ref = track.create_segment(MT(0, 4), MD(4, 4))
segment = segment_ref.segment
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 70, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 70)))
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 60, 100)))
segment.add_event(MEVT(MT(1, 4), NOTE_OFF(0, 60)))
segment.add_event(MEVT(MT(1, 4), NOTE_ON(0, 61, 100)))
segment.add_event(MEVT(MT(2, 4), NOTE_OFF(0, 61)))
segment.add_event(MEVT(MT(2, 4), NOTE_ON(0, 62, 100)))
segment.add_event(MEVT(MT(3, 4), NOTE_OFF(0, 62)))
segment.add_event(MEVT(MT(3, 4), NOTE_ON(0, 63, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 63)))

data = track.copy_segments([segment_ref])
with self.project.apply_mutations('test'):
segment_refs = track.paste_segments(data, MT(8, 4))

self.assertEqual(len(segment_refs), 1)
self.assertEqual(len(track.segments), 2)
self.assertEqual(len(track.segment_heap), 2)
self.assertEqual(segment_refs[0].time, MT(8, 4))

async def test_link_segment(self):
track = await self._add_track()
with self.project.apply_mutations('test'):
segment_ref = track.create_segment(MT(0, 4), MD(4, 4))
segment = segment_ref.segment
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 70, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 70)))
segment.add_event(MEVT(MT(0, 4), NOTE_ON(0, 60, 100)))
segment.add_event(MEVT(MT(1, 4), NOTE_OFF(0, 60)))
segment.add_event(MEVT(MT(1, 4), NOTE_ON(0, 61, 100)))
segment.add_event(MEVT(MT(2, 4), NOTE_OFF(0, 61)))
segment.add_event(MEVT(MT(2, 4), NOTE_ON(0, 62, 100)))
segment.add_event(MEVT(MT(3, 4), NOTE_OFF(0, 62)))
segment.add_event(MEVT(MT(3, 4), NOTE_ON(0, 63, 100)))
segment.add_event(MEVT(MT(4, 4), NOTE_OFF(0, 63)))

data = track.copy_segments([segment_ref])
with self.project.apply_mutations('test'):
segment_refs = track.link_segments(data, MT(8, 4))

self.assertEqual(len(segment_refs), 1)
self.assertEqual(len(track.segments), 2)
self.assertEqual(len(track.segment_heap), 1)
self.assertEqual(segment_refs[0].time, MT(8, 4))
self.assertIs(segment_refs[0].segment, segment)

async def test_connector(self):
track = await self._add_track()

0 comments on commit f4c583e

Please sign in to comment.
You can’t perform that action at this time.