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.

888 lines
37KB

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