Skip to content
This repository has been archived by the owner on Dec 27, 2021. It is now read-only.

Commit

Permalink
New copy&paste system.
Browse files Browse the repository at this point in the history
  • Loading branch information
odahoda committed Sep 22, 2019
1 parent c4e63e2 commit f4c583e
Show file tree
Hide file tree
Showing 32 changed files with 1,284 additions and 471 deletions.
2 changes: 1 addition & 1 deletion 3rdparty/typeshed/PyQt5/QtGui.pyi
Expand Up @@ -556,6 +556,7 @@ class QConicalGradient(QGradient):


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

class Mode(int): ...
Clipboard = ... # type: 'QClipboard.Mode'
Expand All @@ -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: ...
Expand Down
2 changes: 1 addition & 1 deletion 3rdparty/typeshed/PyQt5/QtWidgets.pyi
Expand Up @@ -1053,6 +1053,7 @@ class QActionGroup(QtCore.QObject):


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

class ColorSpec(int): ...
NormalColor = ... # type: 'QApplication.ColorSpec'
Expand All @@ -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: ...
Expand Down
1 change: 1 addition & 0 deletions noisicaa/builtin_nodes/beat_track/track_ui.py
Expand Up @@ -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:
Expand Down
40 changes: 40 additions & 0 deletions noisicaa/builtin_nodes/pianoroll_track/clipboard.proto
@@ -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;
}
84 changes: 83 additions & 1 deletion noisicaa/builtin_nodes/pianoroll_track/model.py
Expand Up @@ -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
Expand All @@ -34,6 +35,7 @@
from . import node_description
from . import processor_messages
from . import _model
from . import clipboard_pb2

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
91 changes: 91 additions & 0 deletions noisicaa/builtin_nodes/pianoroll_track/model_test.py
Expand Up @@ -103,6 +103,97 @@ async def test_split_segment(self):
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()

Expand Down

0 comments on commit f4c583e

Please sign in to comment.