| 
							- #!/usr/bin/env python3
 - # -*- coding: utf-8 -*-
 - 
 - # A piano roll viewer/editor
 - # Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
 - # Copyright (C) 2014-2015 Perry Nguyen
 - #
 - # 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 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.
 - #
 - # For a full copy of the GNU General Public License see the doc/GPL.txt file.
 - 
 - # ------------------------------------------------------------------------------------------------------------
 - # Imports (Global)
 - 
 - from PyQt5.QtCore import Qt, QRectF, QPointF, pyqtSignal
 - from PyQt5.QtGui import QColor, QCursor, QFont, QPen, QPainter
 - from PyQt5.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, QGraphicsSimpleTextItem
 - from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView
 - from PyQt5.QtWidgets import QApplication, QStyle, QWidget
 - 
 - # ------------------------------------------------------------------------------------------------------------
 - # Imports (Custom)
 - 
 - #from carla_shared import *
 - 
 - # ------------------------------------------------------------------------------------------------------------
 - # MIDI definitions, copied from CarlaMIDI.h
 - 
 - MAX_MIDI_CHANNELS = 16
 - MAX_MIDI_NOTE     = 128
 - MAX_MIDI_VALUE    = 128
 - MAX_MIDI_CONTROL  = 120 # 0x77
 - 
 - MIDI_STATUS_BIT  = 0xF0
 - MIDI_CHANNEL_BIT = 0x0F
 - 
 - # MIDI Messages List
 - MIDI_STATUS_NOTE_OFF              = 0x80 # note (0-127), velocity (0-127)
 - MIDI_STATUS_NOTE_ON               = 0x90 # note (0-127), velocity (0-127)
 - MIDI_STATUS_POLYPHONIC_AFTERTOUCH = 0xA0 # note (0-127), pressure (0-127)
 - MIDI_STATUS_CONTROL_CHANGE        = 0xB0 # see 'Control Change Messages List'
 - MIDI_STATUS_PROGRAM_CHANGE        = 0xC0 # program (0-127), none
 - MIDI_STATUS_CHANNEL_PRESSURE      = 0xD0 # pressure (0-127), none
 - MIDI_STATUS_PITCH_WHEEL_CONTROL   = 0xE0 # LSB (0-127), MSB (0-127)
 - 
 - # MIDI Message type
 - def MIDI_IS_CHANNEL_MESSAGE(status): return status >= MIDI_STATUS_NOTE_OFF and status <  MIDI_STATUS_BIT
 - def MIDI_IS_SYSTEM_MESSAGE(status):  return status >= MIDI_STATUS_BIT      and status <= 0xFF
 - def MIDI_IS_OSC_MESSAGE(status):     return status == '/'                   or status == '#'
 - 
 - # MIDI Channel message type
 - def MIDI_IS_STATUS_NOTE_OFF(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_OFF
 - def MIDI_IS_STATUS_NOTE_ON(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_ON
 - def MIDI_IS_STATUS_POLYPHONIC_AFTERTOUCH(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_POLYPHONIC_AFTERTOUCH
 - def MIDI_IS_STATUS_CONTROL_CHANGE(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CONTROL_CHANGE
 - def MIDI_IS_STATUS_PROGRAM_CHANGE(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PROGRAM_CHANGE
 - def MIDI_IS_STATUS_CHANNEL_PRESSURE(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CHANNEL_PRESSURE
 - def MIDI_IS_STATUS_PITCH_WHEEL_CONTROL(status):
 -     return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PITCH_WHEEL_CONTROL
 - 
 - # MIDI Utils
 - def MIDI_GET_STATUS_FROM_DATA(data):
 -     return data[0] & MIDI_STATUS_BIT  if MIDI_IS_CHANNEL_MESSAGE(data[0]) else data[0]
 - def MIDI_GET_CHANNEL_FROM_DATA(data):
 -     return data[0] & MIDI_CHANNEL_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else 0
 - 
 - # ---------------------------------------------------------------------------------------------------------------------
 - # Graphics Items
 - 
 - class NoteExpander(QGraphicsRectItem):
 -     def __init__(self, length, height, parent):
 -         QGraphicsRectItem.__init__(self, 0, 0, length, height, parent)
 -         self.parent = parent
 -         self.orig_brush = QColor(0, 0, 0, 0)
 -         self.hover_brush = QColor(200, 200, 200)
 -         self.stretch = False
 - 
 -         self.setAcceptHoverEvents(True)
 -         self.setFlag(QGraphicsItem.ItemIsSelectable)
 -         self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
 -         self.setPen(QPen(QColor(0,0,0,0)))
 - 
 -     def paint(self, painter, option, widget=None):
 -         paint_option = option
 -         paint_option.state &= ~QStyle.State_Selected
 -         QGraphicsRectItem.paint(self, painter, paint_option, widget)
 - 
 -     def mousePressEvent(self, event):
 -         QGraphicsRectItem.mousePressEvent(self, event)
 -         self.stretch = True
 - 
 -     def mouseReleaseEvent(self, event):
 -         QGraphicsRectItem.mouseReleaseEvent(self, event)
 -         self.stretch = False
 - 
 -     def hoverEnterEvent(self, event):
 -         QGraphicsRectItem.hoverEnterEvent(self, event)
 -         self.setCursor(QCursor(Qt.SizeHorCursor))
 -         self.setBrush(self.hover_brush)
 - 
 -     def hoverLeaveEvent(self, event):
 -         QGraphicsRectItem.hoverLeaveEvent(self, event)
 -         self.unsetCursor()
 -         self.setBrush(self.orig_brush)
 - 
 - # ---------------------------------------------------------------------------------------------------------------------
 - 
 - class NoteItem(QGraphicsRectItem):
 -     '''a note on the pianoroll sequencer'''
 -     def __init__(self, height, length, note_info):
 -         QGraphicsRectItem.__init__(self, 0, 0, length, height)
 - 
 -         self.orig_brush = QColor(note_info[3], 0, 0)
 -         self.hover_brush = QColor(note_info[3] + 98, 200, 100)
 -         self.select_brush = QColor(note_info[3] + 98, 100, 100)
 - 
 -         self.note = note_info
 -         self.length = length
 -         self.piano = self.scene
 - 
 -         self.pressed = False
 -         self.hovering = False
 -         self.moving_diff = (0,0)
 -         self.expand_diff = 0
 - 
 -         self.setAcceptHoverEvents(True)
 -         self.setFlag(QGraphicsItem.ItemIsMovable)
 -         self.setFlag(QGraphicsItem.ItemIsSelectable)
 -         self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
 -         self.setPen(QPen(QColor(0,0,0,0)))
 -         self.setBrush(self.orig_brush)
 - 
 -         l = 5
 -         self.front = NoteExpander(l, height, self)
 -         self.back = NoteExpander(l, height, self)
 -         self.back.setPos(length - l, 0)
 - 
 -     def paint(self, painter, option, widget=None):
 -         paint_option = option
 -         paint_option.state &= ~QStyle.State_Selected
 -         if self.isSelected():
 -             self.setBrush(self.select_brush)
 -         elif self.hovering:
 -             self.setBrush(self.hover_brush)
 -         else:
 -             self.setBrush(self.orig_brush)
 -         QGraphicsRectItem.paint(self, painter, paint_option, widget)
 - 
 -     def hoverEnterEvent(self, event):
 -         QGraphicsRectItem.hoverEnterEvent(self, event)
 -         self.hovering = True
 -         self.update()
 -         self.setCursor(QCursor(Qt.OpenHandCursor))
 - 
 -     def hoverLeaveEvent(self, event):
 -         QGraphicsRectItem.hoverLeaveEvent(self, event)
 -         self.hovering = False
 -         self.unsetCursor()
 -         self.update()
 - 
 -     def mousePressEvent(self, event):
 -         QGraphicsRectItem.mousePressEvent(self, event)
 -         self.pressed = True
 -         self.moving_diff = (0,0)
 -         self.expand_diff = 0
 -         self.setCursor(QCursor(Qt.ClosedHandCursor))
 -         self.setSelected(True)
 - 
 -     def mouseMoveEvent(self, event):
 -         event.ignore()
 - 
 -     def mouseReleaseEvent(self, event):
 -         QGraphicsRectItem.mouseReleaseEvent(self, event)
 -         self.pressed = False
 -         self.moving_diff = (0,0)
 -         self.expand_diff = 0
 -         self.setCursor(QCursor(Qt.OpenHandCursor))
 - 
 -     def moveEvent(self, event):
 -         offset = event.scenePos() - event.lastScenePos()
 - 
 -         if self.back.stretch:
 -             self.expand(self.back, offset)
 -             self.updateNoteInfo(self.scenePos().x(), self.scenePos().y())
 -             return
 - 
 -         if self.front.stretch:
 -             self.expand(self.front, offset)
 -             self.updateNoteInfo(self.scenePos().x(), self.scenePos().y())
 -             return
 - 
 -         piano = self.piano()
 - 
 -         pos = self.scenePos() + offset + QPointF(self.moving_diff[0],self.moving_diff[1])
 -         pos = piano.enforce_bounds(pos)
 -         pos_x = pos.x()
 -         pos_y = pos.y()
 -         width = self.rect().width()
 -         if pos_x + width > piano.grid_width + piano.piano_width:
 -             pos_x = piano.grid_width + piano.piano_width - width
 -         pos_sx, pos_sy = piano.snap(pos_x, pos_y)
 - 
 -         if pos_sx + width > piano.grid_width + piano.piano_width:
 -             self.moving_diff = (0,0)
 -             self.expand_diff = 0
 -             return
 - 
 -         self.moving_diff = (pos_x-pos_sx, pos_y-pos_sy)
 -         self.setPos(pos_sx, pos_sy)
 - 
 -         self.updateNoteInfo(pos_sx, pos_sy)
 - 
 -     def expand(self, rectItem, offset):
 -         rect = self.rect()
 -         piano = self.piano()
 -         width = rect.right() + self.expand_diff
 - 
 -         if rectItem == self.back:
 -             width += offset.x()
 -             max_x = piano.grid_width + piano.piano_width
 -             if width + self.scenePos().x() >= max_x:
 -                 width = max_x - self.scenePos().x() - 1
 -             elif piano.snap_value and width < piano.snap_value:
 -                 width = piano.snap_value
 -             elif width < 10:
 -                 width = 10
 -             new_w = piano.snap(width) - 2.75
 -             if new_w + self.scenePos().x() >= max_x:
 -                 self.moving_diff = (0,0)
 -                 self.expand_diff = 0
 -                 return
 - 
 -         else:
 -             width -= offset.x()
 -             new_w = piano.snap(width+2.75) - 2.75
 -             if new_w <= 0:
 -                 new_w = piano.snap_value
 -                 self.moving_diff = (0,0)
 -                 self.expand_diff = 0
 -                 return
 -             diff = rect.right() - new_w
 -             if diff: # >= piano.snap_value:
 -                 new_x = self.scenePos().x() + diff
 -                 if new_x < piano.piano_width:
 -                     new_x = piano.piano_width
 -                     self.moving_diff = (0,0)
 -                     self.expand_diff = 0
 -                     return
 -                 print(new_x, new_w, diff)
 -                 self.setX(new_x)
 - 
 -         self.expand_diff = width - new_w
 -         self.back.setPos(new_w - 5, 0)
 -         rect.setRight(new_w)
 -         self.setRect(rect)
 - 
 -     def updateNoteInfo(self, pos_x, pos_y):
 -         note_info = (self.piano().get_note_num_from_y(pos_y),
 -                      self.piano().get_note_start_from_x(pos_x),
 -                      self.piano().get_note_length_from_x(self.rect().width()),
 -                      self.note[3])
 -         if self.note != note_info:
 -             self.piano().move_note(self.note, note_info)
 -             self.note = note_info
 - 
 -     def updateVelocity(self, event):
 -         offset = event.scenePos().x() - event.lastScenePos().x()
 -         offset = int(offset/5)
 - 
 -         note_info = self.note[:]
 -         note_info[3] += offset
 -         if note_info[3] > 127:
 -             note_info[3] = 127
 -         elif note_info[3] < 0:
 -             note_info[3] = 0
 -         if self.note != note_info:
 -             self.orig_brush = QColor(note_info[3], 0, 0)
 -             self.hover_brush = QColor(note_info[3] + 98, 200, 100)
 -             self.select_brush = QColor(note_info[3] + 98, 100, 100)
 -             self.update()
 -             self.piano().move_note(self.note, note_info)
 -             self.note = note_info
 - 
 - # ---------------------------------------------------------------------------------------------------------------------
 - 
 - class PianoKeyItem(QGraphicsRectItem):
 -     def __init__(self, width, height, note, parent):
 -         QGraphicsRectItem.__init__(self, 0, 0, width, height, parent)
 - 
 -         self.width = width
 -         self.height = height
 -         self.note = note
 -         self.piano = self.scene
 -         self.hovered = False
 -         self.pressed = False
 - 
 -         self.click_brush = QColor(255, 100, 100)
 -         self.hover_brush = QColor(200, 0, 0)
 -         self.orig_brush = None
 - 
 -         self.setAcceptHoverEvents(True)
 -         self.setFlag(QGraphicsItem.ItemIsSelectable)
 -         self.setPen(QPen(QColor(0,0,0,80)))
 - 
 -     def paint(self, painter, option, widget=None):
 -         paint_option = option
 -         paint_option.state &= ~QStyle.State_Selected
 -         QGraphicsRectItem.paint(self, painter, paint_option, widget)
 - 
 -     def hoverEnterEvent(self, event):
 -         QGraphicsRectItem.hoverEnterEvent(self, event)
 -         self.hovered = True
 -         self.orig_brush = self.brush()
 -         self.setBrush(self.hover_brush)
 - 
 -     def hoverLeaveEvent(self, event):
 -         QGraphicsRectItem.hoverLeaveEvent(self, event)
 -         self.hovered = False
 -         self.setBrush(self.click_brush if self.pressed else self.orig_brush)
 - 
 -     def mousePressEvent(self, event):
 -         QGraphicsRectItem.mousePressEvent(self, event)
 -         self.pressed = True
 -         self.setBrush(self.click_brush)
 -         self.piano().noteclicked.emit(self.note, True)
 - 
 -     def mouseReleaseEvent(self, event):
 -         QGraphicsRectItem.mouseReleaseEvent(self, event)
 -         self.pressed = False
 -         self.setBrush(self.hover_brush if self.hovered else self.orig_brush)
 -         self.piano().noteclicked.emit(self.note, False)
 - 
 - # ---------------------------------------------------------------------------------------------------------------------
 - 
 - class PianoRoll(QGraphicsScene):
 -     '''the piano roll'''
 - 
 -     noteclicked = pyqtSignal(int,bool)
 -     midievent = pyqtSignal(list)
 -     measureupdate = pyqtSignal(int)
 -     modeupdate = pyqtSignal(str)
 - 
 -     default_ghost_vel = 100
 - 
 -     def __init__(self, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
 -         QGraphicsScene.__init__(self)
 -         self.setBackgroundBrush(QColor(50, 50, 50))
 - 
 -         self.notes = []
 -         self.removed_notes = []
 -         self.selected_notes = []
 -         self.piano_keys = []
 - 
 -         self.marquee_select = False
 -         self.marquee_rect = None
 -         self.marquee = None
 - 
 -         self.ghost_note = None
 -         self.ghost_rect = None
 -         self.ghost_rect_orig_width = None
 -         self.ghost_vel = self.default_ghost_vel
 - 
 -         self.ignore_mouse_events = False
 -         self.insert_mode = False
 -         self.velocity_mode = False
 -         self.place_ghost = False
 -         self.last_mouse_pos = QPointF()
 - 
 -         ## dimensions
 -         self.padding = 2
 - 
 -         ## piano dimensions
 -         self.note_height = 10
 -         self.start_octave = -2
 -         self.end_octave = 8
 -         self.notes_in_octave = 12
 -         self.total_notes = (self.end_octave - self.start_octave) * self.notes_in_octave + 1
 -         self.piano_height = self.note_height * self.total_notes
 -         self.octave_height = self.notes_in_octave * self.note_height
 - 
 -         self.piano_width = 34
 - 
 -         ## height
 -         self.header_height = 20
 -         self.total_height = self.piano_height - self.note_height + self.header_height
 -         #not sure why note_height is subtracted
 - 
 -         ## width
 -         self.full_note_width = 250 # i.e. a 4/4 note
 -         self.snap_value = None
 -         self.quantize_val = quantize_val
 - 
 -         ### dummy vars that will be changed
 -         self.time_sig = (0,0)
 -         self.measure_width = 0
 -         self.num_measures = 0
 -         self.max_note_length = 0
 -         self.grid_width = 0
 -         self.value_width = 0
 -         self.grid_div = 0
 -         self.piano = None
 -         self.header = None
 -         self.play_head = None
 - 
 -         self.setGridDiv()
 -         self.default_length = 1. / self.grid_div
 - 
 - 
 -     # -------------------------------------------------------------------------
 -     # Callbacks
 - 
 -     def movePlayHead(self, transportInfo):
 -         ticksPerBeat = transportInfo['ticksPerBeat']
 -         max_ticks = ticksPerBeat * self.time_sig[0] * self.num_measures
 -         cur_tick = ticksPerBeat * self.time_sig[0] * transportInfo['bar'] + ticksPerBeat * transportInfo['beat'] + transportInfo['tick']
 -         frac = (cur_tick % max_ticks) / max_ticks
 -         self.play_head.setPos(QPointF(frac * self.grid_width, 0))
 - 
 -     def setTimeSig(self, time_sig):
 -         self.time_sig = time_sig
 -         self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
 -         self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
 -         self.grid_width = self.measure_width * self.num_measures
 -         self.setGridDiv()
 - 
 -     def setMeasures(self, measures):
 -         #try:
 -         self.num_measures = float(measures)
 -         self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
 -         self.grid_width = self.measure_width * self.num_measures
 -         self.refreshScene()
 -         #except:
 -             #pass
 - 
 -     def setDefaultLength(self, length):
 -         v = list(map(float, length.split('/')))
 -         if len(v) < 3:
 -             self.default_length = v[0] if len(v) == 1 else v[0] / v[1]
 -             pos = self.enforce_bounds(self.last_mouse_pos)
 -             if self.insert_mode:
 -                 self.makeGhostNote(pos.x(), pos.y())
 - 
 -     def setGridDiv(self, div=None):
 -         if not div: div = self.quantize_val
 -         try:
 -             val = list(map(int, div.split('/')))
 -             if len(val) < 3:
 -                 self.quantize_val = div
 -                 self.grid_div = val[0] if len(val)==1 else val[1]
 -                 self.value_width = self.full_note_width / float(self.grid_div) if self.grid_div else None
 -                 self.setQuantize(div)
 - 
 -                 self.refreshScene()
 -         except ValueError:
 -             pass
 - 
 -     def setQuantize(self, value):
 -         val = list(map(float, value.split('/')))
 -         if len(val) == 1:
 -             self.quantize(val[0])
 -             self.quantize_val = value
 -         elif len(val) == 2:
 -             self.quantize(val[0] / val[1])
 -             self.quantize_val = value
 - 
 -     # -------------------------------------------------------------------------
 -     # Event Callbacks
 - 
 -     def keyPressEvent(self, event):
 -         QGraphicsScene.keyPressEvent(self, event)
 - 
 -         if event.key() == Qt.Key_Escape:
 -             QApplication.instance().closeAllWindows()
 -             return
 - 
 -         if event.key() == Qt.Key_F:
 -             if not self.insert_mode:
 -                 # turn off velocity mode
 -                 self.velocity_mode = False
 -                 # enable insert mode
 -                 self.insert_mode = True
 -                 self.place_ghost = False
 -                 self.makeGhostNote(self.last_mouse_pos.x(), self.last_mouse_pos.y())
 -                 self.modeupdate.emit('insert_mode')
 -             else:
 -                 # turn off insert mode
 -                 self.insert_mode = False
 -                 self.place_ghost = False
 -                 if self.ghost_note is not None:
 -                     self.removeItem(self.ghost_note)
 -                     self.ghost_note = None
 -                 self.modeupdate.emit('')
 - 
 -         elif event.key() == Qt.Key_D:
 -             if not self.velocity_mode:
 -                 # turn off insert mode
 -                 self.insert_mode = False
 -                 self.place_ghost = False
 -                 if self.ghost_note is not None:
 -                     self.removeItem(self.ghost_note)
 -                     self.ghost_note = None
 -                 # enable velocity mode
 -                 self.velocity_mode = True
 -                 self.modeupdate.emit('velocity_mode')
 -             else:
 -                 # turn off velocity mode
 -                 self.velocity_mode = False
 -                 self.modeupdate.emit('')
 - 
 -         elif event.key() == Qt.Key_A:
 -             for note in self.notes:
 -                 if not note.isSelected():
 -                     has_unselected = True
 -                     break
 -             else:
 -                 has_unselected = False
 - 
 -             # select all notes
 -             if has_unselected:
 -                 for note in self.notes:
 -                     note.setSelected(True)
 -                 self.selected_notes = self.notes[:]
 -             # unselect all
 -             else:
 -                 for note in self.notes:
 -                     note.setSelected(False)
 -                 self.selected_notes = []
 - 
 -         elif event.key() in (Qt.Key_Delete, Qt.Key_Backspace):
 -             # remove selected notes from our notes list
 -             self.notes = [note for note in self.notes if note not in self.selected_notes]
 -             # delete the selected notes
 -             for note in self.selected_notes:
 -                 self.removeItem(note)
 -                 self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
 -                 del note
 -             self.selected_notes = []
 - 
 -     def mousePressEvent(self, event):
 -         QGraphicsScene.mousePressEvent(self, event)
 - 
 -         # mouse click on left-side piano area
 -         if self.piano.contains(event.scenePos()):
 -             self.ignore_mouse_events = True
 -             return
 - 
 -         clicked_notes = []
 - 
 -         for note in self.notes:
 -             if note.pressed or note.back.stretch or note.front.stretch:
 -                 clicked_notes.append(note)
 - 
 -         # mouse click on existing notes
 -         if clicked_notes:
 -             keep_selection = all(note in self.selected_notes for note in clicked_notes)
 -             if keep_selection:
 -                 for note in self.selected_notes:
 -                     note.setSelected(True)
 -                 return
 - 
 -             for note in self.selected_notes:
 -                 if note not in clicked_notes:
 -                     note.setSelected(False)
 -             for note in clicked_notes:
 -                 if note not in self.selected_notes:
 -                     note.setSelected(True)
 - 
 -             self.selected_notes = clicked_notes
 -             return
 - 
 -         # mouse click on empty area (no note selected)
 -         for note in self.selected_notes:
 -             note.setSelected(False)
 -         self.selected_notes = []
 - 
 -         if event.button() != Qt.LeftButton:
 -             return
 - 
 -         if self.insert_mode:
 -             self.place_ghost = True
 -         else:
 -             self.marquee_select = True
 -             self.marquee_rect = QRectF(event.scenePos().x(), event.scenePos().y(), 1, 1)
 -             self.marquee = QGraphicsRectItem(self.marquee_rect)
 -             self.marquee.setBrush(QColor(255, 255, 255, 100))
 -             self.addItem(self.marquee)
 - 
 -     def mouseMoveEvent(self, event):
 -         QGraphicsScene.mouseMoveEvent(self, event)
 - 
 -         self.last_mouse_pos = event.scenePos()
 - 
 -         if self.ignore_mouse_events:
 -             return
 - 
 -         pos = self.enforce_bounds(self.last_mouse_pos)
 - 
 -         if self.insert_mode:
 -             if self.ghost_note is None:
 -                 self.makeGhostNote(pos.x(), pos.y())
 -             max_x = self.grid_width + self.piano_width
 - 
 -             # placing note, only width needs updating
 -             if self.place_ghost:
 -                 pos_x = pos.x()
 -                 min_x = self.ghost_rect.x() + self.ghost_rect_orig_width
 -                 if pos_x < min_x:
 -                     pos_x = min_x
 -                 new_x = self.snap(pos_x)
 -                 self.ghost_rect.setRight(new_x)
 -                 self.ghost_note.setRect(self.ghost_rect)
 -                 #self.adjust_note_vel(event)
 - 
 -             # ghostnote following mouse around
 -             else:
 -                 pos_x = pos.x()
 -                 if pos_x + self.ghost_rect.width() >= max_x:
 -                     pos_x = max_x - self.ghost_rect.width()
 -                 elif pos_x > self.piano_width + self.ghost_rect.width()*3/4:
 -                     pos_x -= self.ghost_rect.width()/2
 -                 new_x, new_y = self.snap(pos_x, pos.y())
 -                 self.ghost_rect.moveTo(new_x, new_y)
 -                 self.ghost_note.setRect(self.ghost_rect)
 -             return
 - 
 -         if self.marquee_select:
 -             marquee_orig_pos = event.buttonDownScenePos(Qt.LeftButton)
 -             if marquee_orig_pos.x() < pos.x() and marquee_orig_pos.y() < pos.y():
 -                 self.marquee_rect.setBottomRight(pos)
 -             elif marquee_orig_pos.x() < pos.x() and marquee_orig_pos.y() > pos.y():
 -                 self.marquee_rect.setTopRight(pos)
 -             elif marquee_orig_pos.x() > pos.x() and marquee_orig_pos.y() < pos.y():
 -                 self.marquee_rect.setBottomLeft(pos)
 -             elif marquee_orig_pos.x() > pos.x() and marquee_orig_pos.y() > pos.y():
 -                 self.marquee_rect.setTopLeft(pos)
 -             self.marquee.setRect(self.marquee_rect)
 - 
 -             for note in self.selected_notes:
 -                 note.setSelected(False)
 -             self.selected_notes = []
 - 
 -             for item in self.collidingItems(self.marquee):
 -                 if item in self.notes:
 -                     item.setSelected(True)
 -                     self.selected_notes.append(item)
 -             return
 - 
 -         if event.buttons() != Qt.LeftButton:
 -             return
 - 
 -         if self.velocity_mode:
 -             for note in self.selected_notes:
 -                 note.updateVelocity(event)
 -             return
 - 
 -         x = y = False
 -         for note in self.selected_notes:
 -             if note.back.stretch:
 -                 x = True
 -                 break
 -         for note in self.selected_notes:
 -             if note.front.stretch:
 -                 y = True
 -                 break
 -         for note in self.selected_notes:
 -             note.back.stretch = x
 -             note.front.stretch = y
 -             note.moveEvent(event)
 - 
 -     def mouseReleaseEvent(self, event):
 -         QGraphicsScene.mouseReleaseEvent(self, event)
 - 
 -         if self.ignore_mouse_events:
 -             self.ignore_mouse_events = False
 -             return
 - 
 -         if self.marquee_select:
 -             self.marquee_select = False
 -             self.removeItem(self.marquee)
 -             self.marquee = None
 - 
 -         if self.insert_mode and self.place_ghost:
 -             self.place_ghost = False
 -             note_start = self.get_note_start_from_x(self.ghost_rect.x())
 -             note_num = self.get_note_num_from_y(self.ghost_rect.y())
 -             note_length = self.get_note_length_from_x(self.ghost_rect.width())
 -             note = self.drawNote(note_num, note_start, note_length, self.ghost_vel)
 -             note.setSelected(True)
 -             self.selected_notes.append(note)
 -             self.midievent.emit(["midievent-add", note_num, note_start, note_length, self.ghost_vel])
 -             pos = self.enforce_bounds(self.last_mouse_pos)
 -             pos_x = pos.x()
 -             if pos_x > self.piano_width + self.ghost_rect.width()*3/4:
 -                 pos_x -= self.ghost_rect.width()/2
 -             self.makeGhostNote(pos_x, pos.y())
 - 
 -         for note in self.selected_notes:
 -             note.back.stretch = False
 -             note.front.stretch = False
 - 
 -     # -------------------------------------------------------------------------
 -     # Internal Functions
 - 
 -     def drawHeader(self):
 -         self.header = QGraphicsRectItem(0, 0, self.grid_width, self.header_height)
 -         #self.header.setZValue(1.0)
 -         self.header.setPos(self.piano_width, 0)
 -         self.addItem(self.header)
 - 
 -     def drawPiano(self):
 -         piano_keys_width = self.piano_width - self.padding
 -         labels = ('B','Bb','A','Ab','G','Gb','F','E','Eb','D','Db','C')
 -         black_notes = (2,4,6,9,11)
 -         piano_label = QFont()
 -         piano_label.setPointSize(6)
 -         self.piano = QGraphicsRectItem(0, 0, piano_keys_width, self.piano_height)
 -         self.piano.setPos(0, self.header_height)
 -         self.addItem(self.piano)
 - 
 -         key = PianoKeyItem(piano_keys_width, self.note_height, 78, self.piano)
 -         label = QGraphicsSimpleTextItem('C9', key)
 -         label.setPos(18, 1)
 -         label.setFont(piano_label)
 -         key.setBrush(QColor(255, 255, 255))
 -         for i in range(self.end_octave - self.start_octave, 0, -1):
 -             for j in range(self.notes_in_octave, 0, -1):
 -                 note = (self.end_octave - i + 3) * 12 - j
 -                 if j in black_notes:
 -                     key = PianoKeyItem(piano_keys_width/1.4, self.note_height, note, self.piano)
 -                     key.setBrush(QColor(0, 0, 0))
 -                     key.setZValue(1.0)
 -                     key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
 -                 elif (j - 1) and (j + 1) in black_notes:
 -                     key = PianoKeyItem(piano_keys_width, self.note_height * 2, note, self.piano)
 -                     key.setBrush(QColor(255, 255, 255))
 -                     key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
 -                 elif (j - 1) in black_notes:
 -                     key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano)
 -                     key.setBrush(QColor(255, 255, 255))
 -                     key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
 -                 elif (j + 1) in black_notes:
 -                     key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano)
 -                     key.setBrush(QColor(255, 255, 255))
 -                     key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
 -                 if j == 12:
 -                     label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1], self.end_octave - i + 1), key)
 -                     label.setPos(18, 6)
 -                     label.setFont(piano_label)
 -                 self.piano_keys.append(key)
 - 
 -     def drawGrid(self):
 -         black_notes = [2,4,6,9,11]
 -         scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
 -         scale_bar.setPos(self.piano_width, 0)
 -         scale_bar.setBrush(QColor(100,100,100))
 -         clearpen = QPen(QColor(0,0,0,0))
 -         for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
 -             for j in range(self.notes_in_octave, 0, -1):
 -                 scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
 -                 scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
 -                 scale_bar.setPen(clearpen)
 -                 if j not in black_notes:
 -                     scale_bar.setBrush(QColor(120,120,120))
 -                 else:
 -                     scale_bar.setBrush(QColor(100,100,100))
 - 
 -         measure_pen = QPen(QColor(0, 0, 0, 120), 3)
 -         half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
 -         line_pen = QPen(QColor(0, 0, 0, 40))
 -         for i in range(0, int(self.num_measures) + 1):
 -             measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
 -             measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
 -             measure.setPen(measure_pen)
 -             if i < self.num_measures:
 -                 number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
 -                 number.setPos(self.measure_width * i + 5, 2)
 -                 number.setBrush(Qt.white)
 -                 for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
 -                     line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
 -                     line.setZValue(1.0)
 -                     line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
 -                     if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
 -                         line.setPen(half_measure_pen)
 -                     else:
 -                         line.setPen(line_pen)
 - 
 -     def drawPlayHead(self):
 -         self.play_head = QGraphicsLineItem(self.piano_width, self.header_height, self.piano_width, self.total_height)
 -         self.play_head.setPen(QPen(QColor(255,255,255,50), 2))
 -         self.play_head.setZValue(1.)
 -         self.addItem(self.play_head)
 - 
 -     def refreshScene(self):
 -         list(map(self.removeItem, self.notes))
 -         self.selected_notes = []
 -         self.piano_keys = []
 -         self.place_ghost = False
 -         if self.ghost_note is not None:
 -             self.removeItem(self.ghost_note)
 -             self.ghost_note = None
 -         self.clear()
 -         self.drawPiano()
 -         self.drawHeader()
 -         self.drawGrid()
 -         self.drawPlayHead()
 - 
 -         for note in self.notes[:]:
 -             if note.note[1] >= (self.num_measures * self.time_sig[0]):
 -                 self.notes.remove(note)
 -                 self.removed_notes.append(note)
 -                 #self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
 -             elif note.note[2] > self.max_note_length:
 -                 new_note = note.note[:]
 -                 new_note[2] = self.max_note_length
 -                 self.notes.remove(note)
 -                 self.drawNote(new_note[0], new_note[1], self.max_note_length, new_note[3], False)
 -                 self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
 -                 self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])
 - 
 -         for note in self.removed_notes[:]:
 -             if note.note[1] < (self.num_measures * self.time_sig[0]):
 -                 self.removed_notes.remove(note)
 -                 self.notes.append(note)
 - 
 -         list(map(self.addItem, self.notes))
 -         if self.views():
 -             self.views()[0].setSceneRect(self.itemsBoundingRect())
 - 
 -     def clearNotes(self):
 -         self.clear()
 -         self.notes = []
 -         self.removed_notes = []
 -         self.selected_notes = []
 -         self.drawPiano()
 -         self.drawHeader()
 -         self.drawGrid()
 - 
 -     def makeGhostNote(self, pos_x, pos_y):
 -         """creates the ghostnote that is placed on the scene before the real one is."""
 -         if self.ghost_note is not None:
 -             self.removeItem(self.ghost_note)
 -         length = self.full_note_width * self.default_length
 -         pos_x, pos_y = self.snap(pos_x, pos_y)
 -         self.ghost_vel = self.default_ghost_vel
 -         self.ghost_rect = QRectF(pos_x, pos_y, length, self.note_height)
 -         self.ghost_rect_orig_width = self.ghost_rect.width()
 -         self.ghost_note = QGraphicsRectItem(self.ghost_rect)
 -         self.ghost_note.setBrush(QColor(230, 221, 45, 100))
 -         self.addItem(self.ghost_note)
 - 
 -     def drawNote(self, note_num, note_start, note_length, note_velocity, add=True):
 -         """
 -         note_num: midi number, 0 - 127
 -         note_start: 0 - (num_measures * time_sig[0]) so this is in beats
 -         note_length: 0 - (num_measures  * time_sig[0]/time_sig[1]) this is in measures
 -         note_velocity: 0 - 127
 -         """
 - 
 -         info = [note_num, note_start, note_length, note_velocity]
 - 
 -         if not note_start % (self.num_measures * self.time_sig[0]) == note_start:
 -             #self.midievent.emit(["midievent-remove", note_num, note_start, note_length, note_velocity])
 -             while not note_start % (self.num_measures * self.time_sig[0]) == note_start:
 -                 self.setMeasures(self.num_measures+1)
 -             self.measureupdate.emit(self.num_measures)
 -             self.refreshScene()
 - 
 -         x_start = self.get_note_x_start(note_start)
 -         if note_length > self.max_note_length:
 -             note_length = self.max_note_length + 0.25
 -         x_length = self.get_note_x_length(note_length)
 -         y_pos = self.get_note_y_pos(note_num)
 - 
 -         note = NoteItem(self.note_height, x_length, info)
 -         note.setPos(x_start, y_pos)
 - 
 -         self.notes.append(note)
 - 
 -         if add:
 -             self.addItem(note)
 - 
 -         return note
 - 
 -     # -------------------------------------------------------------------------
 -     # Helper Functions
 - 
 -     def frange(self, x, y, t):
 -         while x < y:
 -             yield x
 -             x += t
 - 
 -     def quantize(self, value):
 -         self.snap_value = float(self.full_note_width) * value if value else None
 - 
 -     def snap(self, pos_x, pos_y = None):
 -         if self.snap_value:
 -             pos_x = int(round((pos_x - self.piano_width) / self.snap_value)) * self.snap_value + self.piano_width
 -         if pos_y is not None:
 -             pos_y = int((pos_y - self.header_height) / self.note_height) * self.note_height + self.header_height
 -         return (pos_x, pos_y) if pos_y is not None else pos_x
 - 
 -     def adjust_note_vel(self, event):
 -         m_pos = event.scenePos()
 -         #bind velocity to vertical mouse movement
 -         self.ghost_vel += (event.lastScenePos().y() - m_pos.y())/10
 -         if self.ghost_vel < 0:
 -             self.ghost_vel = 0
 -         elif self.ghost_vel > 127:
 -             self.ghost_vel = 127
 - 
 -         m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
 -         if m_pos.x() < m_width:
 -             m_pos.setX(m_width)
 -         m_new_x = self.snap(m_pos.x())
 -         self.ghost_rect.setRight(m_new_x)
 -         self.ghost_note.setRect(self.ghost_rect)
 - 
 -     def enforce_bounds(self, pos):
 -         pos = QPointF(pos)
 -         if pos.x() < self.piano_width:
 -             pos.setX(self.piano_width)
 -         elif pos.x() >= self.grid_width + self.piano_width:
 -             pos.setX(self.grid_width + self.piano_width - 1)
 -         if pos.y() < self.header_height + self.padding:
 -             pos.setY(self.header_height + self.padding)
 -         return pos
 - 
 -     def get_note_start_from_x(self, note_x):
 -         return (note_x - self.piano_width) / (self.grid_width / self.num_measures / self.time_sig[0])
 - 
 -     def get_note_x_start(self, note_start):
 -         return self.piano_width + (self.grid_width / self.num_measures / self.time_sig[0]) * note_start
 - 
 -     def get_note_x_length(self, note_length):
 -         return float(self.time_sig[1]) / self.time_sig[0] * note_length * self.grid_width / self.num_measures
 - 
 -     def get_note_length_from_x(self, note_x):
 -         return float(self.time_sig[0]) / self.time_sig[1] * self.num_measures / self.grid_width * note_x
 - 
 -     def get_note_y_pos(self, note_num):
 -         return self.header_height + self.note_height * (self.total_notes - note_num - 1)
 - 
 -     def get_note_num_from_y(self, note_y_pos):
 -         return -(int((note_y_pos - self.header_height) / self.note_height) - self.total_notes + 1)
 - 
 -     def move_note(self, old_note, new_note):
 -         self.midievent.emit(["midievent-remove", old_note[0], old_note[1], old_note[2], old_note[3]])
 -         self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])
 - 
 - # ------------------------------------------------------------------------------------------------------------
 - 
 - class PianoRollView(QGraphicsView):
 -     def __init__(self, parent, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
 -         QGraphicsView.__init__(self, parent)
 -         self.piano = PianoRoll(time_sig, num_measures, quantize_val)
 -         self.setScene(self.piano)
 -         #self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
 - 
 -         x = 0   * self.sceneRect().width() + self.sceneRect().left()
 -         y = 0.4 * self.sceneRect().height() + self.sceneRect().top()
 -         self.centerOn(x, y)
 - 
 -         self.setAlignment(Qt.AlignLeft)
 -         self.o_transform = self.transform()
 -         self.zoom_x = 1
 -         self.zoom_y = 1
 - 
 -     def setZoomX(self, scale_x):
 -         self.setTransform(self.o_transform)
 -         self.zoom_x = 1 + scale_x / float(99) * 2
 -         self.scale(self.zoom_x, self.zoom_y)
 - 
 -     def setZoomY(self, scale_y):
 -         self.setTransform(self.o_transform)
 -         self.zoom_y = 1 + scale_y / float(99)
 -         self.scale(self.zoom_x, self.zoom_y)
 - 
 - # ------------------------------------------------------------------------------------------------------------
 - 
 - class ModeIndicator(QWidget):
 -     def __init__(self, parent):
 -         QWidget.__init__(self, parent)
 -         #self.setGeometry(0, 0, 30, 20)
 -         self.mode = None
 -         self.setFixedSize(30,20)
 - 
 -     def paintEvent(self, event):
 -         event.accept()
 - 
 -         painter = QPainter(self)
 -         painter.setPen(QPen(QColor(0, 0, 0, 0)))
 - 
 -         if self.mode == 'velocity_mode':
 -             painter.setBrush(QColor(127, 0, 0))
 -         elif self.mode == 'insert_mode':
 -             painter.setBrush(QColor(0, 100, 127))
 -         else:
 -             painter.setBrush(QColor(0, 0, 0, 0))
 - 
 -         painter.drawRect(0, 0, 30, 20)
 - 
 -     def changeMode(self, new_mode):
 -         self.mode = new_mode
 -         self.update()
 - 
 - # ------------------------------------------------------------------------------------------------------------
 
 
  |