parent
6b536e46e8
commit
ebcdd6af32
@ -0,0 +1,379 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import logging
|
||||
import enum
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from .misc import QGraphicsGroup
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Layer(enum.IntEnum):
|
||||
BG = 0
|
||||
MAIN = 1
|
||||
DEBUG = 2
|
||||
EDIT = 3
|
||||
MOUSE = 4
|
||||
EVENTS = 5
|
||||
|
||||
NUM_LAYERS = 6
|
||||
|
||||
|
||||
class MeasureLayout(object):
|
||||
def __init__(self):
|
||||
self.size = QtCore.QSize()
|
||||
self.baseline = 0
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return self.width > 0 and self.height > 0
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.size.width()
|
||||
|
||||
@width.setter
|
||||
def width(self, value):
|
||||
self.size.setWidth(value)
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.size.height()
|
||||
|
||||
@height.setter
|
||||
def height(self, value):
|
||||
self.size.setHeight(value)
|
||||
|
||||
@property
|
||||
def extend_above(self):
|
||||
return self.baseline
|
||||
|
||||
@property
|
||||
def extend_below(self):
|
||||
return self.height - self.baseline
|
||||
|
||||
def __eq__(self, other):
|
||||
assert isinstance(other, MeasureLayout)
|
||||
return (self.size == other.size) and (self.baseline == other.baseline)
|
||||
|
||||
|
||||
class MeasureItemImpl(QtWidgets.QGraphicsItem):
|
||||
def __init__(self, sheet_view, track_item, measure_reference, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._sheet_view = sheet_view
|
||||
self._track_item = track_item
|
||||
self._measure_reference = measure_reference
|
||||
if self._measure_reference is not None:
|
||||
self._measure = measure_reference.measure
|
||||
self._measure_listener = self._measure_reference.listeners.add(
|
||||
'measure_id', self.measureChanged)
|
||||
else:
|
||||
self._measure = None
|
||||
self._measure_listener = None
|
||||
|
||||
self._layout = None
|
||||
|
||||
self._layers = {}
|
||||
self._layers[Layer.BG] = QGraphicsGroup()
|
||||
|
||||
self._background = QtWidgets.QGraphicsRectItem(self._layers[Layer.BG])
|
||||
self._background.setPen(QtGui.QPen(Qt.NoPen))
|
||||
self._background.setBrush(QtGui.QColor(240, 240, 255))
|
||||
self._background.setVisible(False)
|
||||
|
||||
self._selected = False
|
||||
|
||||
@property
|
||||
def measure(self):
|
||||
return self._measure
|
||||
|
||||
@property
|
||||
def measure_reference(self):
|
||||
return self._measure_reference
|
||||
|
||||
@property
|
||||
def track_item(self):
|
||||
return self._track_item
|
||||
|
||||
def boundingRect(self):
|
||||
return QtCore.QRectF(0, 0, self._layout.width, self._layout.height)
|
||||
|
||||
def paint(self, painter, option, widget=None):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
if self._measure_listener is not None:
|
||||
self._measure_listener.remove()
|
||||
|
||||
def measureChanged(self, old_value, new_value):
|
||||
self._measure = self._measure_reference.measure
|
||||
self.recomputeLayout()
|
||||
|
||||
def recomputeLayout(self):
|
||||
layout = self.computeLayout()
|
||||
if layout != self._layout:
|
||||
self._sheet_view.updateSheet()
|
||||
else:
|
||||
self.updateMeasure()
|
||||
|
||||
def computeLayout(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def setLayout(self, layout):
|
||||
self._layout = layout
|
||||
|
||||
def updateMeasure(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def layers(self):
|
||||
return sorted(self._layers.keys())
|
||||
|
||||
def getLayer(self, layer_id):
|
||||
return self._layers.get(layer_id, None)
|
||||
|
||||
def width(self):
|
||||
return self._layout.width
|
||||
|
||||
def buildContextMenu(self, menu):
|
||||
insert_measure_action = QtWidgets.QAction(
|
||||
"Insert measure", menu,
|
||||
statusTip="Insert an empty measure at this point.",
|
||||
triggered=self.onInsertMeasure)
|
||||
menu.addAction(insert_measure_action)
|
||||
|
||||
remove_measure_action = QtWidgets.QAction(
|
||||
"Remove measure", menu,
|
||||
statusTip="Remove this measure.",
|
||||
triggered=self.onRemoveMeasure)
|
||||
menu.addAction(remove_measure_action)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = QtWidgets.QMenu()
|
||||
self._track_item.buildContextMenu(menu)
|
||||
self.buildContextMenu(menu)
|
||||
|
||||
menu.exec_(event.screenPos())
|
||||
event.accept()
|
||||
|
||||
def onInsertMeasure(self):
|
||||
self.send_command_async(
|
||||
self._sheet_view.sheet.id, 'InsertMeasure',
|
||||
tracks=[self._measure.track.index],
|
||||
pos=self._measure_reference.index)
|
||||
|
||||
def onRemoveMeasure(self):
|
||||
self.send_command_async(
|
||||
self._sheet_view.sheet.id, 'RemoveMeasure',
|
||||
tracks=[self._measure.track.index],
|
||||
pos=self._measure_reference.index)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.modifiers() == Qt.ControlModifier:
|
||||
if self.selected():
|
||||
self._sheet_view.removeFromSelection(self)
|
||||
else:
|
||||
self._sheet_view.clearSelection()
|
||||
self._sheet_view.addToSelection(self)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
return super().mousePressEvent(event)
|
||||
|
||||
def clearPlaybackPos(self):
|
||||
pass
|
||||
|
||||
def setPlaybackPos(
|
||||
self, sample_pos, num_samples, start_tick, end_tick, first):
|
||||
pass
|
||||
|
||||
def setSelected(self, selected):
|
||||
if selected != self._selected:
|
||||
self._selected = selected
|
||||
self._background.setVisible(self._selected)
|
||||
self.updateMeasure()
|
||||
|
||||
def selected(self):
|
||||
return self._selected
|
||||
|
||||
async def getCopy(self):
|
||||
return {'class': type(self._measure).__name__,
|
||||
'id': self._measure.id,
|
||||
'data': await self.project_client.serialize(self._measure.id) }
|
||||
|
||||
|
||||
class TrackItemImpl(object):
|
||||
measure_item_cls = None
|
||||
|
||||
def __init__(self, sheet_view, track, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._sheet_view = sheet_view
|
||||
self._track = track
|
||||
|
||||
self._instrument_selector = None
|
||||
|
||||
self._prev_note_highlight = None
|
||||
self._prev_playback_measures = set()
|
||||
|
||||
self._measures = []
|
||||
for mref in self._track.measure_list:
|
||||
measure_item = self.measure_item_cls( # pylint: disable=not-callable
|
||||
**self.context, sheet_view=self._sheet_view,
|
||||
track_item=self, measure_reference=mref)
|
||||
self._measures.append(measure_item)
|
||||
|
||||
self._ghost_measure_item = self.measure_item_cls( # pylint: disable=not-callable
|
||||
**self.context, sheet_view=self._sheet_view,
|
||||
track_item=self, measure_reference=None)
|
||||
|
||||
self._listeners = [
|
||||
self._track.listeners.add('name', self.onNameChanged),
|
||||
self._track.listeners.add(
|
||||
'measure_list', self.onMeasureListChanged),
|
||||
self._track.listeners.add('muted', self.onMutedChanged),
|
||||
self._track.listeners.add('volume', self.onVolumeChanged),
|
||||
self._track.listeners.add('visible', self.onVisibleChanged),
|
||||
]
|
||||
|
||||
def close(self):
|
||||
for listener in self._listeners:
|
||||
listener.remove()
|
||||
|
||||
while len(self._measures) > 0:
|
||||
measure = self._measures.pop(0)
|
||||
if measure.selected():
|
||||
self._sheet_view.removeFromSelection(
|
||||
measure, update_object=False)
|
||||
measure.close()
|
||||
self._ghost_measure_item.close()
|
||||
self._ghost_measure_item = None
|
||||
|
||||
@property
|
||||
def track(self):
|
||||
return self._track
|
||||
|
||||
@property
|
||||
def measures(self):
|
||||
return self._measures + [self._ghost_measure_item]
|
||||
|
||||
def removeMeasure(self, idx):
|
||||
measure = self._measures[idx]
|
||||
if measure.selected():
|
||||
self._sheet_view.removeFromSelection(measure, update_object=False)
|
||||
measure.close()
|
||||
del self._measures[idx]
|
||||
|
||||
def onMeasureListChanged(self, action, *args):
|
||||
if action == 'insert':
|
||||
idx, measure_reference = args
|
||||
measure_item = self.measure_item_cls( # pylint: disable=not-callable
|
||||
**self.context, sheet_view=self._sheet_view,
|
||||
track_item=self, measure_reference=measure_reference)
|
||||
self._measures.insert(idx, measure_item)
|
||||
self._sheet_view.updateSheet()
|
||||
|
||||
elif action == 'delete':
|
||||
idx, measure_reference = args
|
||||
self.removeMeasure(idx)
|
||||
self._sheet_view.updateSheet()
|
||||
|
||||
else:
|
||||
raise ValueError("Unknown action %r" % action)
|
||||
|
||||
def onNameChanged(self, old_name, new_name):
|
||||
# TODO: only update the first measure.
|
||||
self._sheet_view.updateSheet()
|
||||
|
||||
def onMutedChanged(self, old_value, new_value):
|
||||
pass # TODO
|
||||
|
||||
def onVolumeChanged(self, old_value, new_value):
|
||||
pass # TODO
|
||||
|
||||
def onVisibleChanged(self, old_value, new_value):
|
||||
self._sheet_view.updateSheet()
|
||||
|
||||
def buildContextMenu(self, menu):
|
||||
track_properties_action = QtWidgets.QAction(
|
||||
"Edit track properties...", menu,
|
||||
statusTip="Edit the properties of this track.",
|
||||
triggered=self.onTrackProperties)
|
||||
menu.addAction(track_properties_action)
|
||||
|
||||
remove_track_action = QtWidgets.QAction(
|
||||
"Remove track", menu,
|
||||
statusTip="Remove this track.",
|
||||
triggered=self.onRemoveTrack)
|
||||
menu.addAction(remove_track_action)
|
||||
|
||||
def onRemoveTrack(self):
|
||||
self.send_command_async(
|
||||
self._track.parent.id, 'RemoveTrack',
|
||||
track=self._track.index)
|
||||
|
||||
def onTrackProperties(self):
|
||||
dialog = QtWidgets.QDialog()
|
||||
dialog.setWindowTitle("Track Properties")
|
||||
|
||||
name = QtWidgets.QLineEdit(dialog)
|
||||
name.setText(self._track.name)
|
||||
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
form_layout.addRow("Name", name)
|
||||
|
||||
close = QtWidgets.QPushButton("Close")
|
||||
close.clicked.connect(dialog.close)
|
||||
|
||||
buttons = QtWidgets.QHBoxLayout()
|
||||
buttons.addStretch(1)
|
||||
buttons.addWidget(close)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addLayout(form_layout)
|
||||
layout.addLayout(buttons)
|
||||
dialog.setLayout(layout)
|
||||
|
||||
ret = dialog.exec_()
|
||||
|
||||
self.send_command_async(
|
||||
self._track.id, 'UpdateTrackProperties',
|
||||
name=name.text())
|
||||
|
||||
def setPlaybackPos(
|
||||
self, sample_pos, num_samples, start_measure_idx,
|
||||
start_measure_tick, end_measure_idx, end_measure_tick):
|
||||
playback_measures = set()
|
||||
|
||||
measure_idx = start_measure_idx
|
||||
first = True
|
||||
while True:
|
||||
measure_item = self.measures[measure_idx]
|
||||
if measure_idx == start_measure_idx:
|
||||
start_tick = start_measure_tick
|
||||
else:
|
||||
start_tick = 0
|
||||
if measure_idx == end_measure_idx:
|
||||
end_tick = end_measure_tick
|
||||
else:
|
||||
end_tick = measure_item.measure.duration.ticks
|
||||
measure_item.setPlaybackPos(
|
||||
sample_pos, num_samples, start_tick, end_tick, first)
|
||||
playback_measures.add(measure_idx)
|
||||
|
||||
if measure_idx == end_measure_idx:
|
||||
break
|
||||
|
||||
measure_idx = (measure_idx + 1) % len(self._track.measure_list)
|
||||
first = False
|
||||
|
||||
for measure_idx in self._prev_playback_measures - playback_measures:
|
||||
self.measures[measure_idx].clearPlaybackPos()
|
||||
self._prev_playback_measures = playback_measures
|
||||
|
||||
def onPasteMeasuresAsLink(self, data):
|
||||
print(data)
|
@ -0,0 +1,329 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import music
|
||||
from .misc import QGraphicsGroup
|
||||
from . import ui_base
|
||||
from . import base_track_item
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BeatMeasureLayout(base_track_item.MeasureLayout):
|
||||
pass
|
||||
|
||||
|
||||
class BeatMeasureItemImpl(base_track_item.MeasureItemImpl):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._ghost_beat = None
|
||||
|
||||
self._layers[base_track_item.Layer.MAIN] = QGraphicsGroup()
|
||||
self._layers[base_track_item.Layer.EVENTS] = QGraphicsGroup()
|
||||
self._layers[base_track_item.Layer.EDIT] = QGraphicsGroup()
|
||||
|
||||
self.setAcceptHoverEvents(True)
|
||||
|
||||
self._measure_listeners = []
|
||||
|
||||
if self._measure is not None:
|
||||
self.addMeasureListeners()
|
||||
|
||||
self.playback_pos = QtWidgets.QGraphicsLineItem(
|
||||
self._layers[base_track_item.Layer.EVENTS])
|
||||
self.playback_pos.setVisible(False)
|
||||
self.playback_pos.setLine(0, 0, 0, 20)
|
||||
pen = QtGui.QPen(Qt.black)
|
||||
pen.setWidth(3)
|
||||
self.playback_pos.setPen(pen)
|
||||
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
for listener in self._measure_listeners:
|
||||
listener.remove()
|
||||
self._measure_listeners.clear()
|
||||
|
||||
def addMeasureListeners(self):
|
||||
self._measure_listeners.append(self._measure.listeners.add(
|
||||
'beats-changed', lambda *args: self.recomputeLayout()))
|
||||
self._measure_listeners.append(self._measure.listeners.add(
|
||||
'beats', lambda *args: self.recomputeLayout()))
|
||||
|
||||
def measureChanged(self, old_value, new_value):
|
||||
super().measureChanged(old_value, new_value)
|
||||
|
||||
for listener in self._measure_listeners:
|
||||
listener.remove()
|
||||
self._measure_listeners.clear()
|
||||
|
||||
self.addMeasureListeners()
|
||||
|
||||
def computeLayout(self):
|
||||
width = 100
|
||||
height_above = 30
|
||||
height_below = 30
|
||||
|
||||
layout = BeatMeasureLayout()
|
||||
layout.size = QtCore.QSize(width, height_above + height_below)
|
||||
layout.baseline = height_above
|
||||
return layout
|
||||
|
||||
def updateMeasure(self):
|
||||
assert self._layout.width > 0 and self._layout.height > 0
|
||||
|
||||
self._background.setRect(0, 0, self._layout.width, self._layout.height)
|
||||
|
||||
layer = self._layers[base_track_item.Layer.MAIN]
|
||||
self._sheet_view.clearLayer(layer)
|
||||
|
||||
is_first = (
|
||||
self._measure_reference is not None
|
||||
and self._measure_reference.index == 0)
|
||||
|
||||
if self._measure is not None:
|
||||
black = Qt.black
|
||||
else:
|
||||
black = QtGui.QColor(200, 200, 200)
|
||||
|
||||
line = QtWidgets.QGraphicsLineItem(layer)
|
||||
line.setLine(0, self._layout.baseline,
|
||||
self._layout.width, self._layout.baseline)
|
||||
line.setPen(black)
|
||||
|
||||
if self._measure is not None:
|
||||
line = QtWidgets.QGraphicsLineItem(layer)
|
||||
line.setLine(0, 0, 0, self._layout.height)
|
||||
line.setPen(black)
|
||||
|
||||
if self._measure_reference.is_last:
|
||||
line = QtWidgets.QGraphicsLineItem(layer)
|
||||
line.setLine(
|
||||
self._layout.width, 0,
|
||||
self._layout.width, self._layout.height)
|
||||
line.setPen(black)
|
||||
|
||||
for i in range(1, 8 * self._measure.time_signature.upper):
|
||||
x = int(i * self._layout.width / (8 * self._measure.time_signature.upper))
|
||||
if i % 8 == 0:
|
||||
h = 10
|
||||
elif i % 4 == 0:
|
||||
h = 4
|
||||
else:
|
||||
h = 2
|
||||
|
||||
tick = QtWidgets.QGraphicsLineItem(layer)
|
||||
tick.setLine(x, self._layout.baseline - h,
|
||||
x, self._layout.baseline + h)
|
||||
tick.setPen(black)
|
||||
|
||||
line = QtWidgets.QGraphicsLineItem(layer)
|
||||
line.setLine(0, self._layout.baseline - 20,
|
||||
0, self._layout.baseline)
|
||||
line.setPen(black)
|
||||
|
||||
if is_first:
|
||||
text = QtWidgets.QGraphicsSimpleTextItem(
|
||||
layer)
|
||||
text.setText("> %s" % self._measure.track.name)
|
||||
text.setPos(0, 0)
|
||||
|
||||
for beat in self._measure.beats:
|
||||
assert 0 <= beat.timepos < self._measure.duration
|
||||
|
||||
pos = int(
|
||||
self._layout.width
|
||||
* beat.timepos
|
||||
/ self._measure.duration)
|
||||
|
||||
beat_vel = QtWidgets.QGraphicsRectItem(layer)
|
||||
beat_vel.setRect(0, 0, 4, 22 * beat.velocity / 127)
|
||||
beat_vel.setPen(QtGui.QPen(Qt.NoPen))
|
||||
beat_vel.setBrush(QtGui.QColor(255, 200, 200))
|
||||
beat_vel.setPos(pos + 2, self._layout.baseline + 8)
|
||||
|
||||
beat_path = QtGui.QPainterPath()
|
||||
beat_path.moveTo(0, -12)
|
||||
beat_path.lineTo(0, 12)
|
||||
beat_path.lineTo(8, 0)
|
||||
beat_path.closeSubpath()
|
||||
beat_item = QtWidgets.QGraphicsPathItem(layer)
|
||||
beat_item.setPath(beat_path)
|
||||
beat_item.setPen(QtGui.QPen(Qt.NoPen))
|
||||
beat_item.setBrush(black)
|
||||
beat_item.setPos(pos, self._layout.baseline)
|
||||
|
||||
|
||||
def setGhost(self):
|
||||
if self._ghost_beat is not None:
|
||||
return
|
||||
self.removeGhost()
|
||||
|
||||
layer = self._layers[base_track_item.Layer.EDIT]
|
||||
self._ghost_beat = QtWidgets.QGraphicsRectItem(layer)
|
||||
self._ghost_beat.setRect(0, -30, 8, 50)
|
||||
self._ghost_beat.setBrush(Qt.black)
|
||||
self._ghost_beat.setPen(QtGui.QPen(Qt.NoPen))
|
||||
self._ghost_beat.setOpacity(0.2)
|
||||
|
||||
def removeGhost(self):
|
||||
if self._ghost_beat is not None:
|
||||
self._ghost_beat.setParentItem(None)
|
||||
if self._ghost_beat.scene() is not None:
|
||||
self.scene().removeItem(self._ghost_beat)
|
||||
self._ghost_beat = None
|
||||
|
||||
def hoverEnterEvent(self, event):
|
||||
super().hoverEnterEvent(event)
|
||||
self.grabMouse()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
if not self.boundingRect().contains(event.pos()):
|
||||
self._track_item.playNoteOff()
|
||||
self._sheet_view.setInfoMessage('')
|
||||
self.removeGhost()
|
||||
self.ungrabMouse()
|
||||
return
|
||||
|
||||
if self._measure is None:
|
||||
return
|
||||
|
||||
self.setGhost()
|
||||
|
||||
ghost_timepos = music.Duration(
|
||||
int(8 * self._measure.time_signature.upper
|
||||
* event.pos().x() / self._layout.width),
|
||||
8 * self._measure.time_signature.upper)
|
||||
self._ghost_beat.setPos(
|
||||
int(self._layout.width * ghost_timepos / self._measure.duration),
|
||||
self._layout.baseline)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if (self._measure is None
|
||||
and event.button() == Qt.LeftButton
|
||||
and event.modifiers() == Qt.NoModifier):
|
||||
self.send_command_async(
|
||||
self._sheet_view.sheet.id,
|
||||
'InsertMeasure', tracks=[], pos=-1)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if self._layout.width <= 0 or self._layout.height <= 0:
|
||||
logger.warning("mousePressEvent without valid layout.")
|
||||
return
|
||||
|
||||
if (event.button() == Qt.LeftButton
|
||||
and event.modifiers() == Qt.NoModifier):
|
||||
click_timepos = music.Duration(
|
||||
int(8 * self._measure.time_signature.upper
|
||||
* event.pos().x() / self._layout.width),
|
||||
8 * self._measure.time_signature.upper)
|
||||
|
||||
for beat in self._measure.beats:
|
||||
if beat.timepos == click_timepos:
|
||||
self.send_command_async(
|
||||
self._measure.id, 'RemoveBeat', beat_id=beat.id)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
self.send_command_async(
|
||||
self._measure.id, 'AddBeat', timepos=click_timepos)
|
||||
self._track_item.playNoteOn(self._measure.track.pitch)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
return super().mousePressEvent(event)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if self._measure is not None:
|
||||
if self._layout.width <= 0 or self._layout.height <= 0:
|
||||
logger.warning("mousePressEvent without valid layout.")
|
||||
return
|
||||
|
||||
if event.modifiers() in (Qt.NoModifier, Qt.ShiftModifier):
|
||||
if event.modifiers() == Qt.ShiftModifier:
|
||||
vel_delta = (1 if event.delta() > 0 else -1)
|
||||
else:
|
||||
vel_delta = (10 if event.delta() > 0 else -10)
|
||||
|
||||
click_timepos = music.Duration(
|
||||
int(8 * self._measure.time_signature.upper
|
||||
* event.pos().x() / self._layout.width),
|
||||
8 * self._measure.time_signature.upper)
|
||||
|
||||
for beat in self._measure.beats:
|
||||
if beat.timepos == click_timepos:
|
||||
self.send_command_async(
|
||||
beat.id, 'SetBeatVelocity',
|
||||
velocity=max(0, min(127, beat.velocity + vel_delta)))
|
||||
event.accept()
|
||||
return
|
||||
|
||||
return super().wheelEvent(event)
|
||||
|
||||
def buildContextMenu(self, menu):
|
||||
super().buildContextMenu(menu)
|
||||
|
||||
def clearPlaybackPos(self):
|
||||
self.playback_pos.setVisible(False)
|
||||
|
||||
def setPlaybackPos(
|
||||
self, sample_pos, num_samples, start_tick, end_tick, first):
|
||||
if first:
|
||||
assert 0 <= start_tick < self._measure.duration.ticks
|
||||
assert self._layout.width > 0 and self._layout.height > 0
|
||||
|
||||
pos = (
|
||||
self._layout.width
|
||||
* start_tick
|
||||
/ self._measure.duration.ticks)
|
||||
self.playback_pos.setPos(pos, 0)
|
||||
self.playback_pos.setVisible(True)
|
||||
|
||||
|
||||
class BeatMeasureItem(ui_base.ProjectMixin, BeatMeasureItemImpl):
|
||||
pass
|
||||
|
||||
|
||||
class BeatTrackItemImpl(base_track_item.TrackItemImpl):
|
||||
measure_item_cls = BeatMeasureItem
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._play_last_pitch = None
|
||||
|
||||
def playNoteOn(self, pitch):
|
||||
self.playNoteOff()
|
||||
|
||||
self.call_async(
|
||||
self._sheet_view.audioproc_client.add_event(
|
||||
'sheet:%s/track:%s' % (
|
||||
self._sheet_view.sheet.id,
|
||||
self.track.id),
|
||||
audioproc.NoteOnEvent(-1, pitch)))
|
||||
|
||||
self._play_last_pitch = pitch
|
||||
|
||||
def playNoteOff(self):
|
||||
if self._play_last_pitch is not None:
|
||||
self.call_async(
|
||||
self._sheet_view.audioproc_client.add_event(
|
||||
'sheet:%s/track:%s' % (
|
||||
self._sheet_view.sheet.id,
|
||||
self.track.id),
|
||||
audioproc.NoteOffEvent(-1, self._play_last_pitch)))
|
||||
self._play_last_pitch = None
|
||||
|
||||
|
||||
class BeatTrackItem(ui_base.ProjectMixin, BeatTrackItemImpl):
|
||||
pass
|
@ -0,0 +1,805 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from noisicaa import audioproc
|
||||
from noisicaa import music
|
||||
from .instrument_library import InstrumentLibraryDialog
|
||||
from .svg_symbol import SymbolItem
|
||||
from .tool_dock import Tool
|
||||
from .misc import QGraphicsGroup
|
||||
from . import ui_base
|
||||
from . import base_track_item
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScoreMeasureLayout(base_track_item.MeasureLayout):
|
||||
pass
|
||||
|
||||
|
||||
class ScoreMeasureItemImpl(base_track_item.MeasureItemImpl):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._edit_areas = []
|
||||
self._notes = []
|
||||
self._ghost = None
|
||||
self._ghost_tool = None
|
||||
|
||||
self._layers[base_track_item.Layer.MAIN] = QGraphicsGroup()
|
||||
self._layers[base_track_item.Layer.EDIT] = QGraphicsGroup()
|
||||
self._layers[base_track_item.Layer.EVENTS] = QGraphicsGroup()
|
||||
|
||||
self.setAcceptHoverEvents(True)
|
||||
|
||||
self._measure_listeners = []
|
||||
if self._measure is not None:
|
||||
self.addMeasureListeners()
|
||||
|
||||
self.playback_pos = QtWidgets.QGraphicsLineItem(
|
||||
self._layers[base_track_item.Layer.EVENTS])
|
||||
self.playback_pos.setVisible(False)
|
||||
self.playback_pos.setLine(0, 0, 0, 20)
|
||||
pen = QtGui.QPen(Qt.black)
|
||||
pen.setWidth(3)
|
||||
self.playback_pos.setPen(pen)
|
||||
|
||||
_accidental_map = {
|
||||
'': 'accidental-natural',
|
||||
'#': 'accidental-sharp',
|
||||
'b': 'accidental-flat',
|
||||
'##': 'accidental-double-sharp',
|
||||
'bb': 'accidental-double-flat',
|
||||
}
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
for listener in self._measure_listeners:
|
||||
listener.remove()
|
||||
self._measure_listeners.clear()
|
||||
|
||||
def addMeasureListeners(self):
|
||||
self._measure_listeners.append(self._measure.listeners.add(
|
||||
'notes-changed', self.recomputeLayout))
|
||||
self._measure_listeners.append(self._measure.listeners.add(
|
||||
'clef', lambda *args: self.recomputeLayout()))
|
||||