Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

894 lines
37KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # A piano roll viewer/editor
  4. # Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com>
  5. # Copyright (C) 2014-2015 Perry Nguyen
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License as
  9. # published by the Free Software Foundation; either version 2 of
  10. # the License, or any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  18. # ------------------------------------------------------------------------------------------------------------
  19. # Imports (Global)
  20. from PyQt5.QtCore import Qt, QRectF, QPointF, pyqtSignal
  21. from PyQt5.QtGui import QColor, QFont, QPen, QPainter, QTransform
  22. from PyQt5.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsOpacityEffect, QGraphicsRectItem, QGraphicsSimpleTextItem
  23. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView
  24. from PyQt5.QtWidgets import QWidget, QLabel, QComboBox, QHBoxLayout, QVBoxLayout, QStyle
  25. # ------------------------------------------------------------------------------------------------------------
  26. # Imports (Custom)
  27. from carla_shared import *
  28. # ------------------------------------------------------------------------------------------------------------
  29. # MIDI definitions, copied from CarlaMIDI.h
  30. MAX_MIDI_CHANNELS = 16
  31. MAX_MIDI_NOTE = 128
  32. MAX_MIDI_VALUE = 128
  33. MAX_MIDI_CONTROL = 120 # 0x77
  34. MIDI_STATUS_BIT = 0xF0
  35. MIDI_CHANNEL_BIT = 0x0F
  36. # MIDI Messages List
  37. MIDI_STATUS_NOTE_OFF = 0x80 # note (0-127), velocity (0-127)
  38. MIDI_STATUS_NOTE_ON = 0x90 # note (0-127), velocity (0-127)
  39. MIDI_STATUS_POLYPHONIC_AFTERTOUCH = 0xA0 # note (0-127), pressure (0-127)
  40. MIDI_STATUS_CONTROL_CHANGE = 0xB0 # see 'Control Change Messages List'
  41. MIDI_STATUS_PROGRAM_CHANGE = 0xC0 # program (0-127), none
  42. MIDI_STATUS_CHANNEL_PRESSURE = 0xD0 # pressure (0-127), none
  43. MIDI_STATUS_PITCH_WHEEL_CONTROL = 0xE0 # LSB (0-127), MSB (0-127)
  44. # MIDI Message type
  45. def MIDI_IS_CHANNEL_MESSAGE(status): return status >= MIDI_STATUS_NOTE_OFF and status < MIDI_STATUS_BIT
  46. def MIDI_IS_SYSTEM_MESSAGE(status): return status >= MIDI_STATUS_BIT and status <= 0xFF
  47. def MIDI_IS_OSC_MESSAGE(status): return status == '/' or status == '#'
  48. # MIDI Channel message type
  49. def MIDI_IS_STATUS_NOTE_OFF(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_OFF
  50. def MIDI_IS_STATUS_NOTE_ON(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_ON
  51. def MIDI_IS_STATUS_POLYPHONIC_AFTERTOUCH(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_POLYPHONIC_AFTERTOUCH
  52. def MIDI_IS_STATUS_CONTROL_CHANGE(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CONTROL_CHANGE
  53. def MIDI_IS_STATUS_PROGRAM_CHANGE(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PROGRAM_CHANGE
  54. def MIDI_IS_STATUS_CHANNEL_PRESSURE(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CHANNEL_PRESSURE
  55. def MIDI_IS_STATUS_PITCH_WHEEL_CONTROL(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PITCH_WHEEL_CONTROL
  56. # MIDI Utils
  57. def MIDI_GET_STATUS_FROM_DATA(data): return data[0] & MIDI_STATUS_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else data[0]
  58. def MIDI_GET_CHANNEL_FROM_DATA(data): return data[0] & MIDI_CHANNEL_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else 0
  59. # ------------------------------------------------------------------------------------------------------------
  60. # Graphics Items
  61. class NoteExpander(QGraphicsRectItem):
  62. def __init__(self, length, height, parent):
  63. QGraphicsRectItem.__init__(self, 0, 0, length, height, parent)
  64. self.parent = parent
  65. self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
  66. self.setAcceptHoverEvents(True)
  67. clearpen = QPen(QColor(0,0,0,0))
  68. self.setPen(clearpen)
  69. self.orig_brush = QColor(0, 0, 0, 0)
  70. self.hover_brush = QColor(200, 200, 200)
  71. self.stretch = False
  72. def mousePressEvent(self, event):
  73. QGraphicsRectItem.mousePressEvent(self, event)
  74. self.stretch = True
  75. def hoverEnterEvent(self, event):
  76. QGraphicsRectItem.hoverEnterEvent(self, event)
  77. if self.parent.isSelected():
  78. self.parent.setBrush(self.parent.select_brush)
  79. else:
  80. self.parent.setBrush(self.parent.orig_brush)
  81. self.setBrush(self.hover_brush)
  82. def hoverLeaveEvent(self, event):
  83. QGraphicsRectItem.hoverLeaveEvent(self, event)
  84. if self.parent.isSelected():
  85. self.parent.setBrush(self.parent.select_brush)
  86. elif self.parent.hovering:
  87. self.parent.setBrush(self.parent.hover_brush)
  88. else:
  89. self.parent.setBrush(self.parent.orig_brush)
  90. self.setBrush(self.orig_brush)
  91. class NoteItem(QGraphicsRectItem):
  92. '''a note on the pianoroll sequencer'''
  93. def __init__(self, height, length, note_info):
  94. QGraphicsRectItem.__init__(self, 0, 0, length, height)
  95. self.setFlag(QGraphicsItem.ItemIsMovable)
  96. self.setFlag(QGraphicsItem.ItemIsSelectable)
  97. self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
  98. self.setAcceptHoverEvents(True)
  99. clearpen = QPen(QColor(0,0,0,0))
  100. self.setPen(clearpen)
  101. self.orig_brush = QColor(note_info[3], 0, 0)
  102. self.hover_brush = QColor(note_info[3] + 100, 200, 100)
  103. self.select_brush = QColor(note_info[3] + 100, 100, 100)
  104. self.setBrush(self.orig_brush)
  105. self.note = note_info
  106. self.length = length
  107. self.piano = self.scene
  108. self.pressed = False
  109. self.hovering = False
  110. self.moving_diff = (0,0)
  111. self.expand_diff = 0
  112. l = 5
  113. self.front = NoteExpander(l, height, self)
  114. self.back = NoteExpander(l, height, self)
  115. self.back.setPos(length - l, 0)
  116. def paint(self, painter, option, widget=None):
  117. paint_option = option
  118. paint_option.state &= ~QStyle.State_Selected
  119. QGraphicsRectItem.paint(self, painter, paint_option, widget)
  120. def setSelected(self, boolean):
  121. QGraphicsRectItem.setSelected(self, boolean)
  122. if boolean: self.setBrush(self.select_brush)
  123. else: self.setBrush(self.orig_brush)
  124. def hoverEnterEvent(self, event):
  125. self.hovering = True
  126. QGraphicsRectItem.hoverEnterEvent(self, event)
  127. if not self.isSelected():
  128. self.setBrush(self.hover_brush)
  129. def hoverLeaveEvent(self, event):
  130. self.hovering = False
  131. QGraphicsRectItem.hoverLeaveEvent(self, event)
  132. if not self.isSelected():
  133. self.setBrush(self.orig_brush)
  134. elif self.isSelected():
  135. self.setBrush(self.select_brush)
  136. def mousePressEvent(self, event):
  137. QGraphicsRectItem.mousePressEvent(self, event)
  138. self.setSelected(True)
  139. self.pressed = True
  140. def mouseMoveEvent(self, event):
  141. pass
  142. def moveEvent(self, event):
  143. offset = event.scenePos() - event.lastScenePos()
  144. if self.back.stretch:
  145. self.expand(self.back, offset)
  146. else:
  147. self.move_pos = self.scenePos() + offset \
  148. + QPointF(self.moving_diff[0],self.moving_diff[1])
  149. pos = self.piano().enforce_bounds(self.move_pos)
  150. pos_x, pos_y = pos.x(), pos.y()
  151. pos_sx, pos_sy = self.piano().snap(pos_x, pos_y)
  152. self.moving_diff = (pos_x-pos_sx, pos_y-pos_sy)
  153. if self.front.stretch:
  154. right = self.rect().right() - offset.x() + self.expand_diff
  155. if (self.scenePos().x() == self.piano().piano_width and offset.x() < 0) \
  156. or right < 10:
  157. self.expand_diff = 0
  158. return
  159. self.expand(self.front, offset)
  160. self.setPos(pos_sx, self.scenePos().y())
  161. else:
  162. self.setPos(pos_sx, pos_sy)
  163. def expand(self, rectItem, offset):
  164. rect = self.rect()
  165. right = rect.right() + self.expand_diff
  166. if rectItem == self.back:
  167. right += offset.x()
  168. if right > self.piano().grid_width:
  169. right = self.piano().grid_width
  170. elif right < 10:
  171. right = 10
  172. new_x = self.piano().snap(right)
  173. else:
  174. right -= offset.x()
  175. new_x = self.piano().snap(right+2.75)
  176. if self.piano().snap_value: new_x -= 2.75 # where does this number come from?!
  177. self.expand_diff = right - new_x
  178. self.back.setPos(new_x - 5, 0)
  179. rect.setRight(new_x)
  180. self.setRect(rect)
  181. def updateNoteInfo(self, pos_x, pos_y):
  182. self.note[0] = self.piano().get_note_num_from_y(pos_y)
  183. self.note[1] = self.piano().get_note_start_from_x(pos_x)
  184. self.note[2] = self.piano().get_note_length_from_x(
  185. self.rect().right() - self.rect().left())
  186. #print("note: {}".format(self.note))
  187. def mouseReleaseEvent(self, event):
  188. QGraphicsRectItem.mouseReleaseEvent(self, event)
  189. self.pressed = False
  190. if event.button() == Qt.LeftButton:
  191. self.moving_diff = (0,0)
  192. self.expand_diff = 0
  193. self.back.stretch = False
  194. self.front.stretch = False
  195. (pos_x, pos_y,) = self.piano().snap(self.pos().x(), self.pos().y())
  196. self.setPos(pos_x, pos_y)
  197. self.updateNoteInfo(pos_x, pos_y)
  198. def updateVelocity(self, event):
  199. offset = event.scenePos().x() - event.lastScenePos().x()
  200. self.note[3] += int(offset/5)
  201. if self.note[3] > 127:
  202. self.note[3] = 127
  203. elif self.note[3] < 0:
  204. self.note[3] = 0
  205. print("new vel: {}".format(self.note[3]))
  206. self.orig_brush = QColor(self.note[3], 0, 0)
  207. self.select_brush = QColor(self.note[3] + 100, 100, 100)
  208. self.setBrush(self.orig_brush)
  209. class PianoKeyItem(QGraphicsRectItem):
  210. def __init__(self, width, height, note, parent):
  211. QGraphicsRectItem.__init__(self, 0, 0, width, height, parent)
  212. self.setPen(QPen(QColor(0,0,0,80)))
  213. self.width = width
  214. self.height = height
  215. self.note = note
  216. self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
  217. self.setAcceptHoverEvents(True)
  218. self.hover_brush = QColor(200, 0, 0)
  219. self.click_brush = QColor(255, 100, 100)
  220. self.pressed = False
  221. def hoverEnterEvent(self, event):
  222. QGraphicsRectItem.hoverEnterEvent(self, event)
  223. self.orig_brush = self.brush()
  224. self.setBrush(self.hover_brush)
  225. def hoverLeaveEvent(self, event):
  226. if self.pressed:
  227. self.pressed = False
  228. self.setBrush(self.hover_brush)
  229. QGraphicsRectItem.hoverLeaveEvent(self, event)
  230. self.setBrush(self.orig_brush)
  231. #def mousePressEvent(self, event):
  232. # self.pressed = True
  233. # self.setBrush(self.click_brush)
  234. def mouseMoveEvent(self, event):
  235. """this may eventually do something"""
  236. pass
  237. def mouseReleaseEvent(self, event):
  238. self.pressed = False
  239. QGraphicsRectItem.mouseReleaseEvent(self, event)
  240. self.setBrush(self.hover_brush)
  241. class PianoRoll(QGraphicsScene):
  242. '''the piano roll'''
  243. noteclicked = pyqtSignal(int,bool)
  244. midievent = pyqtSignal(list)
  245. measureupdate = pyqtSignal(int)
  246. modeupdate = pyqtSignal(str)
  247. def __init__(self, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
  248. QGraphicsScene.__init__(self)
  249. self.setBackgroundBrush(QColor(50, 50, 50))
  250. self.mousePos = QPointF()
  251. self.notes = []
  252. self.removed_notes = []
  253. self.selected_notes = []
  254. self.piano_keys = []
  255. self.marquee_select = False
  256. self.insert_mode = False
  257. self.velocity_mode = False
  258. self.place_ghost = False
  259. self.ghost_note = None
  260. self.default_ghost_vel = 100
  261. self.ghost_vel = self.default_ghost_vel
  262. ## dimensions
  263. self.padding = 2
  264. ## piano dimensions
  265. self.note_height = 10
  266. self.start_octave = -2
  267. self.end_octave = 8
  268. self.notes_in_octave = 12
  269. self.total_notes = (self.end_octave - self.start_octave) * self.notes_in_octave + 1
  270. self.piano_height = self.note_height * self.total_notes
  271. self.octave_height = self.notes_in_octave * self.note_height
  272. self.piano_width = 34
  273. ## height
  274. self.header_height = 20
  275. self.total_height = self.piano_height - self.note_height + self.header_height
  276. #not sure why note_height is subtracted
  277. ## width
  278. self.full_note_width = 250 # i.e. a 4/4 note
  279. self.snap_value = None
  280. self.quantize_val = quantize_val
  281. ### dummy vars that will be changed
  282. self.time_sig = (0,0)
  283. self.measure_width = 0
  284. self.num_measures = 0
  285. self.max_note_length = 0
  286. self.grid_width = 0
  287. self.value_width = 0
  288. self.grid_div = 0
  289. self.piano = None
  290. self.header = None
  291. self.play_head = None
  292. self.last_piano_note = None
  293. self.setGridDiv()
  294. self.default_length = 1. / self.grid_div
  295. # -------------------------------------------------------------------------
  296. # Callbacks
  297. def movePlayHead(self, transportInfo):
  298. ticksPerBeat = transportInfo['ticksPerBeat']
  299. max_ticks = ticksPerBeat * self.time_sig[0] * self.num_measures
  300. cur_tick = ticksPerBeat * self.time_sig[0] * transportInfo['bar'] + ticksPerBeat * transportInfo['beat'] + transportInfo['tick']
  301. frac = (cur_tick % max_ticks) / max_ticks
  302. self.play_head.setPos(QPointF(frac * self.grid_width, 0))
  303. def setTimeSig(self, time_sig):
  304. self.time_sig = time_sig
  305. self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
  306. self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
  307. self.grid_width = self.measure_width * self.num_measures
  308. self.setGridDiv()
  309. def setMeasures(self, measures):
  310. #try:
  311. self.num_measures = float(measures)
  312. self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
  313. self.grid_width = self.measure_width * self.num_measures
  314. self.refreshScene()
  315. #except:
  316. #pass
  317. def setDefaultLength(self, length):
  318. v = list(map(float, length.split('/')))
  319. if len(v) < 3:
  320. self.default_length = \
  321. v[0] if len(v)==1 else \
  322. v[0] / v[1]
  323. pos = self.enforce_bounds(self.mousePos)
  324. if self.insert_mode:
  325. self.makeGhostNote(pos.x(), pos.y())
  326. def setGridDiv(self, div=None):
  327. if not div: div = self.quantize_val
  328. try:
  329. val = list(map(int, div.split('/')))
  330. if len(val) < 3:
  331. self.quantize_val = div
  332. self.grid_div = val[0] if len(val)==1 else val[1]
  333. self.value_width = self.full_note_width / float(self.grid_div) if self.grid_div else None
  334. self.setQuantize(div)
  335. self.refreshScene()
  336. except ValueError:
  337. pass
  338. def setQuantize(self, value):
  339. val = list(map(float, value.split('/')))
  340. if len(val) == 1:
  341. self.quantize(val[0])
  342. self.quantize_val = value
  343. elif len(val) == 2:
  344. self.quantize(val[0] / val[1])
  345. self.quantize_val = value
  346. # -------------------------------------------------------------------------
  347. # Event Callbacks
  348. def keyPressEvent(self, event):
  349. QGraphicsScene.keyPressEvent(self, event)
  350. if event.key() == Qt.Key_F:
  351. if not self.insert_mode:
  352. self.velocity_mode = False
  353. self.insert_mode = True
  354. self.makeGhostNote(self.mousePos.x(), self.mousePos.y())
  355. self.modeupdate.emit('insert_mode')
  356. elif self.insert_mode:
  357. self.insert_mode = False
  358. if self.place_ghost: self.place_ghost = False
  359. self.removeItem(self.ghost_note)
  360. self.ghost_note = None
  361. self.modeupdate.emit('')
  362. elif event.key() == Qt.Key_D:
  363. if self.velocity_mode:
  364. self.velocity_mode = False
  365. self.modeupdate.emit('')
  366. else:
  367. if self.insert_mode:
  368. self.removeItem(self.ghost_note)
  369. self.ghost_note = None
  370. self.insert_mode = False
  371. self.place_ghost = False
  372. self.velocity_mode = True
  373. self.modeupdate.emit('velocity_mode')
  374. elif event.key() == Qt.Key_A:
  375. if all((note.isSelected() for note in self.notes)):
  376. for note in self.notes:
  377. note.setSelected(False)
  378. self.selected_notes = []
  379. else:
  380. for note in self.notes:
  381. note.setSelected(True)
  382. self.selected_notes = self.notes[:]
  383. elif event.key() in (Qt.Key_Delete, Qt.Key_Backspace):
  384. self.notes = [note for note in self.notes if note not in self.selected_notes]
  385. for note in self.selected_notes:
  386. self.removeItem(note)
  387. self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
  388. del note
  389. self.selected_notes = []
  390. def mousePressEvent(self, event):
  391. QGraphicsScene.mousePressEvent(self, event)
  392. if self.piano.contains(event.scenePos()):
  393. item = self.itemAt(event.scenePos(), QTransform())
  394. if isinstance(item, PianoKeyItem) and item.note is not None:
  395. self.last_piano_note = item.note
  396. self.noteclicked.emit(self.last_piano_note, True)
  397. return
  398. if not (any(key.pressed for key in self.piano_keys)
  399. or any(note.pressed for note in self.notes)):
  400. for note in self.selected_notes:
  401. note.setSelected(False)
  402. self.selected_notes = []
  403. if event.button() == Qt.LeftButton:
  404. if self.insert_mode:
  405. self.place_ghost = True
  406. else:
  407. self.marquee_select = True
  408. self.marquee_rect = QRectF(event.scenePos().x(), event.scenePos().y(), 1, 1)
  409. self.marquee = QGraphicsRectItem(self.marquee_rect)
  410. self.marquee.setBrush(QColor(255, 255, 255, 100))
  411. self.addItem(self.marquee)
  412. else:
  413. for s_note in self.notes:
  414. if s_note.pressed and s_note in self.selected_notes:
  415. break
  416. elif s_note.pressed and s_note not in self.selected_notes:
  417. for note in self.selected_notes:
  418. note.setSelected(False)
  419. self.selected_notes = [s_note]
  420. break
  421. for note in self.selected_notes:
  422. if not self.velocity_mode:
  423. note.mousePressEvent(event)
  424. def mouseMoveEvent(self, event):
  425. QGraphicsScene.mouseMoveEvent(self, event)
  426. if self.last_piano_note is not None:
  427. return
  428. self.mousePos = event.scenePos()
  429. if not (any((key.pressed for key in self.piano_keys))):
  430. m_pos = event.scenePos()
  431. if self.insert_mode and self.place_ghost: #placing a note
  432. m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
  433. if m_pos.x() > m_width:
  434. m_new_x = self.snap(m_pos.x())
  435. self.ghost_rect.setRight(m_new_x)
  436. self.ghost_note.setRect(self.ghost_rect)
  437. #self.adjust_note_vel(event)
  438. else:
  439. m_pos = self.enforce_bounds(m_pos)
  440. if self.insert_mode: #ghostnote follows mouse around
  441. (m_new_x, m_new_y) = self.snap(m_pos.x(), m_pos.y())
  442. self.ghost_rect.moveTo(m_new_x, m_new_y)
  443. try:
  444. self.ghost_note.setRect(self.ghost_rect)
  445. except RuntimeError:
  446. self.ghost_note = None
  447. self.makeGhostNote(m_new_x, m_new_y)
  448. elif self.marquee_select:
  449. marquee_orig_pos = event.buttonDownScenePos(Qt.LeftButton)
  450. if marquee_orig_pos.x() < m_pos.x() and marquee_orig_pos.y() < m_pos.y():
  451. self.marquee_rect.setBottomRight(m_pos)
  452. elif marquee_orig_pos.x() < m_pos.x() and marquee_orig_pos.y() > m_pos.y():
  453. self.marquee_rect.setTopRight(m_pos)
  454. elif marquee_orig_pos.x() > m_pos.x() and marquee_orig_pos.y() < m_pos.y():
  455. self.marquee_rect.setBottomLeft(m_pos)
  456. elif marquee_orig_pos.x() > m_pos.x() and marquee_orig_pos.y() > m_pos.y():
  457. self.marquee_rect.setTopLeft(m_pos)
  458. self.marquee.setRect(self.marquee_rect)
  459. self.selected_notes = []
  460. for item in self.collidingItems(self.marquee):
  461. if item in self.notes:
  462. self.selected_notes.append(item)
  463. for note in self.notes:
  464. if note in self.selected_notes: note.setSelected(True)
  465. else: note.setSelected(False)
  466. elif self.velocity_mode:
  467. if Qt.LeftButton == event.buttons():
  468. for note in self.selected_notes:
  469. note.updateVelocity(event)
  470. elif not self.marquee_select: #move selected
  471. if Qt.LeftButton == event.buttons():
  472. x = y = False
  473. if any(note.back.stretch for note in self.selected_notes):
  474. x = True
  475. elif any(note.front.stretch for note in self.selected_notes):
  476. y = True
  477. for note in self.selected_notes:
  478. note.back.stretch = x
  479. note.front.stretch = y
  480. note.moveEvent(event)
  481. def mouseReleaseEvent(self, event):
  482. QGraphicsScene.mouseReleaseEvent(self, event)
  483. if self.last_piano_note is not None:
  484. self.noteclicked.emit(self.last_piano_note, False)
  485. self.last_piano_note = None
  486. return
  487. if not (any((key.pressed for key in self.piano_keys)) or any((note.pressed for note in self.notes))):
  488. if event.button() == Qt.LeftButton:
  489. if self.place_ghost and self.insert_mode:
  490. self.place_ghost = False
  491. note_start = self.get_note_start_from_x(self.ghost_rect.x())
  492. note_num = self.get_note_num_from_y(self.ghost_rect.y())
  493. note_length = self.get_note_length_from_x(self.ghost_rect.width())
  494. self.drawNote(note_num, note_start, note_length, self.ghost_vel)
  495. self.midievent.emit(["midievent-add", note_num, note_start, note_length, self.ghost_vel])
  496. self.makeGhostNote(self.mousePos.x(), self.mousePos.y())
  497. elif self.marquee_select:
  498. self.marquee_select = False
  499. self.removeItem(self.marquee)
  500. elif not self.marquee_select:
  501. for note in self.selected_notes:
  502. old_info = note.note[:]
  503. note.mouseReleaseEvent(event)
  504. if self.velocity_mode:
  505. note.setSelected(True)
  506. if not old_info == note.note:
  507. self.midievent.emit(["midievent-remove", old_info[0], old_info[1], old_info[2], old_info[3]])
  508. self.midievent.emit(["midievent-add", note.note[0], note.note[1], note.note[2], note.note[3]])
  509. # -------------------------------------------------------------------------
  510. # Internal Functions
  511. def drawHeader(self):
  512. self.header = QGraphicsRectItem(0, 0, self.grid_width, self.header_height)
  513. #self.header.setZValue(1.0)
  514. self.header.setPos(self.piano_width, 0)
  515. self.addItem(self.header)
  516. def drawPiano(self):
  517. piano_keys_width = self.piano_width - self.padding
  518. labels = ('B','Bb','A','Ab','G','Gb','F','E','Eb','D','Db','C')
  519. black_notes = (2,4,6,9,11)
  520. piano_label = QFont()
  521. piano_label.setPointSize(6)
  522. self.piano = QGraphicsRectItem(0, 0, piano_keys_width, self.piano_height)
  523. self.piano.setPos(0, self.header_height)
  524. self.addItem(self.piano)
  525. key = PianoKeyItem(piano_keys_width, self.note_height, 78, self.piano)
  526. label = QGraphicsSimpleTextItem('C9', key)
  527. label.setPos(18, 1)
  528. label.setFont(piano_label)
  529. key.setBrush(QColor(255, 255, 255))
  530. for i in range(self.end_octave - self.start_octave, 0, -1):
  531. for j in range(self.notes_in_octave, 0, -1):
  532. note = (self.end_octave - i + 3) * 12 - j
  533. if j in black_notes:
  534. key = PianoKeyItem(piano_keys_width/1.4, self.note_height, note, self.piano)
  535. key.setBrush(QColor(0, 0, 0))
  536. key.setZValue(1.0)
  537. key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
  538. elif (j - 1) and (j + 1) in black_notes:
  539. key = PianoKeyItem(piano_keys_width, self.note_height * 2, note, self.piano)
  540. key.setBrush(QColor(255, 255, 255))
  541. key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
  542. elif (j - 1) in black_notes:
  543. key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano)
  544. key.setBrush(QColor(255, 255, 255))
  545. key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
  546. elif (j + 1) in black_notes:
  547. key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano)
  548. key.setBrush(QColor(255, 255, 255))
  549. key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
  550. if j == 12:
  551. label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1],
  552. self.end_octave - i + 1),
  553. key)
  554. label.setPos(18, 6)
  555. label.setFont(piano_label)
  556. self.piano_keys.append(key)
  557. def drawGrid(self):
  558. black_notes = [2,4,6,9,11]
  559. scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
  560. scale_bar.setPos(self.piano_width, 0)
  561. scale_bar.setBrush(QColor(100,100,100))
  562. clearpen = QPen(QColor(0,0,0,0))
  563. for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
  564. for j in range(self.notes_in_octave, 0, -1):
  565. scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
  566. scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
  567. scale_bar.setPen(clearpen)
  568. if j not in black_notes:
  569. scale_bar.setBrush(QColor(120,120,120))
  570. else:
  571. scale_bar.setBrush(QColor(100,100,100))
  572. measure_pen = QPen(QColor(0, 0, 0, 120), 3)
  573. half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
  574. line_pen = QPen(QColor(0, 0, 0, 40))
  575. for i in range(0, int(self.num_measures) + 1):
  576. measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
  577. measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
  578. measure.setPen(measure_pen)
  579. if i < self.num_measures:
  580. number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
  581. number.setPos(self.measure_width * i + 5, 2)
  582. number.setBrush(Qt.white)
  583. for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
  584. line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
  585. line.setZValue(1.0)
  586. line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
  587. if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
  588. line.setPen(half_measure_pen)
  589. else:
  590. line.setPen(line_pen)
  591. def drawPlayHead(self):
  592. self.play_head = QGraphicsLineItem(self.piano_width, self.header_height, self.piano_width, self.total_height)
  593. self.play_head.setPen(QPen(QColor(255,255,255,50), 2))
  594. self.play_head.setZValue(1.)
  595. self.addItem(self.play_head)
  596. def refreshScene(self):
  597. list(map(self.removeItem, self.notes))
  598. self.selected_notes = []
  599. self.piano_keys = []
  600. self.clear()
  601. self.drawPiano()
  602. self.drawHeader()
  603. self.drawGrid()
  604. self.drawPlayHead()
  605. for note in self.notes[:]:
  606. if note.note[1] >= (self.num_measures * self.time_sig[0]):
  607. self.notes.remove(note)
  608. self.removed_notes.append(note)
  609. #self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
  610. elif note.note[2] > self.max_note_length:
  611. new_note = note.note[:]
  612. new_note[2] = self.max_note_length
  613. self.notes.remove(note)
  614. self.drawNote(new_note[0], new_note[1], self.max_note_length, new_note[3], False)
  615. self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
  616. self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])
  617. for note in self.removed_notes[:]:
  618. if note.note[1] < (self.num_measures * self.time_sig[0]):
  619. self.removed_notes.remove(note)
  620. self.notes.append(note)
  621. list(map(self.addItem, self.notes))
  622. if self.views():
  623. self.views()[0].setSceneRect(self.itemsBoundingRect())
  624. def clearNotes(self):
  625. self.clear()
  626. self.notes = []
  627. self.removed_notes = []
  628. self.selected_notes = []
  629. self.drawPiano()
  630. self.drawHeader()
  631. self.drawGrid()
  632. def makeGhostNote(self, pos_x, pos_y):
  633. """creates the ghostnote that is placed on the scene before the real one is."""
  634. if self.ghost_note:
  635. self.removeItem(self.ghost_note)
  636. length = self.full_note_width * self.default_length
  637. (start, note) = self.snap(pos_x, pos_y)
  638. self.ghost_vel = self.default_ghost_vel
  639. self.ghost_rect = QRectF(start, note, length, self.note_height)
  640. self.ghost_rect_orig_width = self.ghost_rect.width()
  641. self.ghost_note = QGraphicsRectItem(self.ghost_rect)
  642. self.ghost_note.setBrush(QColor(230, 221, 45, 100))
  643. self.addItem(self.ghost_note)
  644. def drawNote(self, note_num, note_start=None, note_length=None, note_velocity=None, add=True):
  645. """
  646. note_num: midi number, 0 - 127
  647. note_start: 0 - (num_measures * time_sig[0]) so this is in beats
  648. note_length: 0 - (num_measures * time_sig[0]/time_sig[1]) this is in measures
  649. note_velocity: 0 - 127
  650. """
  651. info = [note_num, note_start, note_length, note_velocity]
  652. if not note_start % (self.num_measures * self.time_sig[0]) == note_start:
  653. #self.midievent.emit(["midievent-remove", note_num, note_start, note_length, note_velocity])
  654. while not note_start % (self.num_measures * self.time_sig[0]) == note_start:
  655. self.setMeasures(self.num_measures+1)
  656. self.measureupdate.emit(self.num_measures)
  657. self.refreshScene()
  658. x_start = self.get_note_x_start(note_start)
  659. if note_length > self.max_note_length:
  660. note_length = self.max_note_length + 0.25
  661. x_length = self.get_note_x_length(note_length)
  662. y_pos = self.get_note_y_pos(note_num)
  663. note = NoteItem(self.note_height, x_length, info)
  664. note.setPos(x_start, y_pos)
  665. self.notes.append(note)
  666. if add:
  667. self.addItem(note)
  668. # -------------------------------------------------------------------------
  669. # Helper Functions
  670. def frange(self, x, y, t):
  671. while x < y:
  672. yield x
  673. x += t
  674. def quantize(self, value):
  675. self.snap_value = float(self.full_note_width) * value if value else None
  676. def snap(self, pos_x, pos_y = None):
  677. if self.snap_value:
  678. pos_x = int(round((pos_x - self.piano_width) / self.snap_value)) \
  679. * self.snap_value + self.piano_width
  680. if pos_y is not None:
  681. pos_y = int((pos_y - self.header_height) / self.note_height) \
  682. * self.note_height + self.header_height
  683. return (pos_x, pos_y) if pos_y is not None else pos_x
  684. def adjust_note_vel(self, event):
  685. m_pos = event.scenePos()
  686. #bind velocity to vertical mouse movement
  687. self.ghost_vel += (event.lastScenePos().y() - m_pos.y())/10
  688. if self.ghost_vel < 0:
  689. self.ghost_vel = 0
  690. elif self.ghost_vel > 127:
  691. self.ghost_vel = 127
  692. m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
  693. if m_pos.x() < m_width:
  694. m_pos.setX(m_width)
  695. m_new_x = self.snap(m_pos.x())
  696. self.ghost_rect.setRight(m_new_x)
  697. self.ghost_note.setRect(self.ghost_rect)
  698. def enforce_bounds(self, pos):
  699. if pos.x() < self.piano_width:
  700. pos.setX(self.piano_width)
  701. elif pos.x() > self.grid_width + self.piano_width:
  702. pos.setX(self.grid_width + self.piano_width)
  703. if pos.y() < self.header_height + self.padding:
  704. pos.setY(self.header_height + self.padding)
  705. return pos
  706. def get_note_start_from_x(self, note_x):
  707. return (note_x - self.piano_width) / (self.grid_width / self.num_measures / self.time_sig[0])
  708. def get_note_x_start(self, note_start):
  709. return self.piano_width + \
  710. (self.grid_width / self.num_measures / self.time_sig[0]) * note_start
  711. def get_note_x_length(self, note_length):
  712. return float(self.time_sig[1]) / self.time_sig[0] * note_length * self.grid_width / self.num_measures
  713. def get_note_length_from_x(self, note_x):
  714. return float(self.time_sig[0]) / self.time_sig[1] * self.num_measures / self.grid_width \
  715. * note_x
  716. def get_note_y_pos(self, note_num):
  717. return self.header_height + self.note_height * (self.total_notes - note_num - 1)
  718. def get_note_num_from_y(self, note_y_pos):
  719. return -(((note_y_pos - self.header_height) / self.note_height) - self.total_notes + 1)
  720. class PianoRollView(QGraphicsView):
  721. def __init__(self, parent, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
  722. QGraphicsView.__init__(self, parent)
  723. self.piano = PianoRoll(time_sig, num_measures, quantize_val)
  724. self.setScene(self.piano)
  725. #self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  726. x = 0 * self.sceneRect().width() + self.sceneRect().left()
  727. y = 0.4 * self.sceneRect().height() + self.sceneRect().top()
  728. self.centerOn(x, y)
  729. self.setAlignment(Qt.AlignLeft)
  730. self.o_transform = self.transform()
  731. self.zoom_x = 1
  732. self.zoom_y = 1
  733. def setZoomX(self, scale_x):
  734. self.setTransform(self.o_transform)
  735. self.zoom_x = 1 + scale_x / float(99) * 2
  736. self.scale(self.zoom_x, self.zoom_y)
  737. def setZoomY(self, scale_y):
  738. self.setTransform(self.o_transform)
  739. self.zoom_y = 1 + scale_y / float(99)
  740. self.scale(self.zoom_x, self.zoom_y)
  741. # ------------------------------------------------------------------------------------------------------------
  742. class ModeIndicator(QWidget):
  743. def __init__(self, parent):
  744. QWidget.__init__(self, parent)
  745. #self.setGeometry(0, 0, 30, 20)
  746. self.setFixedSize(30,20)
  747. self.mode = None
  748. def paintEvent(self, event):
  749. event.accept()
  750. painter = QPainter(self)
  751. painter.setPen(QPen(QColor(0, 0, 0, 0)))
  752. if self.mode == 'velocity_mode':
  753. painter.setBrush(QColor(127, 0, 0))
  754. elif self.mode == 'insert_mode':
  755. painter.setBrush(QColor(0, 100, 127))
  756. else:
  757. painter.setBrush(QColor(0, 0, 0, 0))
  758. painter.drawRect(0, 0, 30, 20)
  759. def changeMode(self, new_mode):
  760. self.mode = new_mode
  761. self.update()
  762. # ------------------------------------------------------------------------------------------------------------