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.

858 lines
36KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # A piano roll viewer/editor
  4. # Copyright (C) 2012-2019 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
  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, 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.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
  216. self.setAcceptHoverEvents(True)
  217. self.hover_brush = QColor(200, 0, 0)
  218. self.click_brush = QColor(255, 100, 100)
  219. self.pressed = False
  220. def hoverEnterEvent(self, event):
  221. QGraphicsRectItem.hoverEnterEvent(self, event)
  222. self.orig_brush = self.brush()
  223. self.setBrush(self.hover_brush)
  224. def hoverLeaveEvent(self, event):
  225. if self.pressed:
  226. self.pressed = False
  227. self.setBrush(self.hover_brush)
  228. QGraphicsRectItem.hoverLeaveEvent(self, event)
  229. self.setBrush(self.orig_brush)
  230. #def mousePressEvent(self, event):
  231. # self.pressed = True
  232. # self.setBrush(self.click_brush)
  233. def mouseMoveEvent(self, event):
  234. """this may eventually do something"""
  235. pass
  236. def mouseReleaseEvent(self, event):
  237. self.pressed = False
  238. QGraphicsRectItem.mouseReleaseEvent(self, event)
  239. self.setBrush(self.hover_brush)
  240. class PianoRoll(QGraphicsScene):
  241. '''the piano roll'''
  242. midievent = pyqtSignal(list)
  243. measureupdate = pyqtSignal(int)
  244. modeupdate = pyqtSignal(str)
  245. def __init__(self, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
  246. QGraphicsScene.__init__(self)
  247. self.setBackgroundBrush(QColor(50, 50, 50))
  248. self.mousePos = QPointF()
  249. self.notes = []
  250. self.selected_notes = []
  251. self.piano_keys = []
  252. self.marquee_select = False
  253. self.insert_mode = False
  254. self.velocity_mode = False
  255. self.place_ghost = False
  256. self.ghost_note = None
  257. self.default_ghost_vel = 100
  258. self.ghost_vel = self.default_ghost_vel
  259. ## dimensions
  260. self.padding = 2
  261. ## piano dimensions
  262. self.note_height = 10
  263. self.start_octave = -2
  264. self.end_octave = 8
  265. self.notes_in_octave = 12
  266. self.total_notes = (self.end_octave - self.start_octave) \
  267. * self.notes_in_octave + 1
  268. self.piano_height = self.note_height * self.total_notes
  269. self.octave_height = self.notes_in_octave * self.note_height
  270. self.piano_width = 34
  271. ## height
  272. self.header_height = 20
  273. self.total_height = self.piano_height - self.note_height + self.header_height
  274. #not sure why note_height is subtracted
  275. ## width
  276. self.full_note_width = 250 # i.e. a 4/4 note
  277. self.snap_value = None
  278. self.quantize_val = quantize_val
  279. ### dummy vars that will be changed
  280. self.time_sig = (0,0)
  281. self.measure_width = 0
  282. self.num_measures = 0
  283. self.max_note_length = 0
  284. self.grid_width = 0
  285. self.value_width = 0
  286. self.grid_div = 0
  287. self.piano = None
  288. self.header = None
  289. self.play_head = None
  290. self.setGridDiv()
  291. self.default_length = 1. / self.grid_div
  292. # -------------------------------------------------------------------------
  293. # Callbacks
  294. def movePlayHead(self, transportInfo):
  295. ticksPerBeat = transportInfo['ticksPerBeat']
  296. max_ticks = ticksPerBeat * self.time_sig[0] * self.num_measures
  297. cur_tick = ticksPerBeat * self.time_sig[0] * transportInfo['bar'] + ticksPerBeat * transportInfo['beat'] + transportInfo['tick']
  298. frac = (cur_tick % max_ticks) / max_ticks
  299. self.play_head.setPos(QPointF(frac * self.grid_width, 0))
  300. def setTimeSig(self, time_sig):
  301. self.time_sig = time_sig
  302. self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
  303. self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
  304. self.grid_width = self.measure_width * self.num_measures
  305. self.setGridDiv()
  306. def setMeasures(self, measures):
  307. #try:
  308. self.num_measures = float(measures)
  309. self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
  310. self.grid_width = self.measure_width * self.num_measures
  311. self.refreshScene()
  312. #except:
  313. #pass
  314. def setDefaultLength(self, length):
  315. v = list(map(float, length.split('/')))
  316. if len(v) < 3:
  317. self.default_length = \
  318. v[0] if len(v)==1 else \
  319. v[0] / v[1]
  320. pos = self.enforce_bounds(self.mousePos)
  321. if self.insert_mode:
  322. self.makeGhostNote(pos.x(), pos.y())
  323. def setGridDiv(self, div=None):
  324. if not div: div = self.quantize_val
  325. try:
  326. val = list(map(int, div.split('/')))
  327. if len(val) < 3:
  328. self.quantize_val = div
  329. self.grid_div = val[0] if len(val)==1 else val[1]
  330. self.value_width = self.full_note_width / float(self.grid_div) if self.grid_div else None
  331. self.setQuantize(div)
  332. self.refreshScene()
  333. except ValueError:
  334. pass
  335. def setQuantize(self, value):
  336. val = list(map(float, value.split('/')))
  337. if len(val) == 1:
  338. self.quantize(val[0])
  339. self.quantize_val = value
  340. elif len(val) == 2:
  341. self.quantize(val[0] / val[1])
  342. self.quantize_val = value
  343. # -------------------------------------------------------------------------
  344. # Event Callbacks
  345. def keyPressEvent(self, event):
  346. QGraphicsScene.keyPressEvent(self, event)
  347. if event.key() == Qt.Key_F:
  348. if not self.insert_mode:
  349. self.velocity_mode = False
  350. self.insert_mode = True
  351. self.makeGhostNote(self.mousePos.x(), self.mousePos.y())
  352. self.modeupdate.emit('insert_mode')
  353. elif self.insert_mode:
  354. self.insert_mode = False
  355. if self.place_ghost: self.place_ghost = False
  356. self.removeItem(self.ghost_note)
  357. self.ghost_note = None
  358. self.modeupdate.emit('')
  359. elif event.key() == Qt.Key_D:
  360. if self.velocity_mode:
  361. self.velocity_mode = False
  362. self.modeupdate.emit('')
  363. else:
  364. if self.insert_mode:
  365. self.removeItem(self.ghost_note)
  366. self.ghost_note = None
  367. self.insert_mode = False
  368. self.place_ghost = False
  369. self.velocity_mode = True
  370. self.modeupdate.emit('velocity_mode')
  371. elif event.key() == Qt.Key_A:
  372. if all((note.isSelected() for note in self.notes)):
  373. for note in self.notes:
  374. note.setSelected(False)
  375. self.selected_notes = []
  376. else:
  377. for note in self.notes:
  378. note.setSelected(True)
  379. self.selected_notes = self.notes[:]
  380. elif event.key() in (Qt.Key_Delete, Qt.Key_Backspace):
  381. self.notes = [note for note in self.notes if note not in self.selected_notes]
  382. for note in self.selected_notes:
  383. self.removeItem(note)
  384. self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
  385. del note
  386. self.selected_notes = []
  387. def mousePressEvent(self, event):
  388. QGraphicsScene.mousePressEvent(self, event)
  389. if not (any(key.pressed for key in self.piano_keys)
  390. or any(note.pressed for note in self.notes)):
  391. for note in self.selected_notes:
  392. note.setSelected(False)
  393. self.selected_notes = []
  394. if event.button() == Qt.LeftButton:
  395. if self.insert_mode:
  396. self.place_ghost = True
  397. else:
  398. self.marquee_select = True
  399. self.marquee_rect = QRectF(event.scenePos().x(), event.scenePos().y(), 1, 1)
  400. self.marquee = QGraphicsRectItem(self.marquee_rect)
  401. self.marquee.setBrush(QColor(255, 255, 255, 100))
  402. self.addItem(self.marquee)
  403. else:
  404. for s_note in self.notes:
  405. if s_note.pressed and s_note in self.selected_notes:
  406. break
  407. elif s_note.pressed and s_note not in self.selected_notes:
  408. for note in self.selected_notes:
  409. note.setSelected(False)
  410. self.selected_notes = [s_note]
  411. break
  412. for note in self.selected_notes:
  413. if not self.velocity_mode:
  414. note.mousePressEvent(event)
  415. def mouseMoveEvent(self, event):
  416. QGraphicsScene.mouseMoveEvent(self, event)
  417. self.mousePos = event.scenePos()
  418. if not (any((key.pressed for key in self.piano_keys))):
  419. m_pos = event.scenePos()
  420. if self.insert_mode and self.place_ghost: #placing a note
  421. m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
  422. if m_pos.x() > m_width:
  423. m_new_x = self.snap(m_pos.x())
  424. self.ghost_rect.setRight(m_new_x)
  425. self.ghost_note.setRect(self.ghost_rect)
  426. #self.adjust_note_vel(event)
  427. else:
  428. m_pos = self.enforce_bounds(m_pos)
  429. if self.insert_mode: #ghostnote follows mouse around
  430. (m_new_x, m_new_y) = self.snap(m_pos.x(), m_pos.y())
  431. self.ghost_rect.moveTo(m_new_x, m_new_y)
  432. try:
  433. self.ghost_note.setRect(self.ghost_rect)
  434. except RuntimeError:
  435. self.ghost_note = None
  436. self.makeGhostNote(m_new_x, m_new_y)
  437. elif self.marquee_select:
  438. marquee_orig_pos = event.buttonDownScenePos(Qt.LeftButton)
  439. if marquee_orig_pos.x() < m_pos.x() and marquee_orig_pos.y() < m_pos.y():
  440. self.marquee_rect.setBottomRight(m_pos)
  441. elif marquee_orig_pos.x() < m_pos.x() and marquee_orig_pos.y() > m_pos.y():
  442. self.marquee_rect.setTopRight(m_pos)
  443. elif marquee_orig_pos.x() > m_pos.x() and marquee_orig_pos.y() < m_pos.y():
  444. self.marquee_rect.setBottomLeft(m_pos)
  445. elif marquee_orig_pos.x() > m_pos.x() and marquee_orig_pos.y() > m_pos.y():
  446. self.marquee_rect.setTopLeft(m_pos)
  447. self.marquee.setRect(self.marquee_rect)
  448. self.selected_notes = []
  449. for item in self.collidingItems(self.marquee):
  450. if item in self.notes:
  451. self.selected_notes.append(item)
  452. for note in self.notes:
  453. if note in self.selected_notes: note.setSelected(True)
  454. else: note.setSelected(False)
  455. elif self.velocity_mode:
  456. if Qt.LeftButton == event.buttons():
  457. for note in self.selected_notes:
  458. note.updateVelocity(event)
  459. elif not self.marquee_select: #move selected
  460. if Qt.LeftButton == event.buttons():
  461. x = y = False
  462. if any(note.back.stretch for note in self.selected_notes):
  463. x = True
  464. elif any(note.front.stretch for note in self.selected_notes):
  465. y = True
  466. for note in self.selected_notes:
  467. note.back.stretch = x
  468. note.front.stretch = y
  469. note.moveEvent(event)
  470. def mouseReleaseEvent(self, event):
  471. if not (any((key.pressed for key in self.piano_keys)) or any((note.pressed for note in self.notes))):
  472. if event.button() == Qt.LeftButton:
  473. if self.place_ghost and self.insert_mode:
  474. self.place_ghost = False
  475. note_start = self.get_note_start_from_x(self.ghost_rect.x())
  476. note_num = self.get_note_num_from_y(self.ghost_rect.y())
  477. note_length = self.get_note_length_from_x(self.ghost_rect.width())
  478. self.drawNote(note_num, note_start, note_length, self.ghost_vel)
  479. self.midievent.emit(["midievent-add", note_num, note_start, note_length, self.ghost_vel])
  480. self.makeGhostNote(self.mousePos.x(), self.mousePos.y())
  481. elif self.marquee_select:
  482. self.marquee_select = False
  483. self.removeItem(self.marquee)
  484. elif not self.marquee_select:
  485. for note in self.selected_notes:
  486. old_info = note.note[:]
  487. note.mouseReleaseEvent(event)
  488. if self.velocity_mode:
  489. note.setSelected(True)
  490. if not old_info == note.note:
  491. self.midievent.emit(["midievent-remove", old_info[0], old_info[1], old_info[2], old_info[3]])
  492. self.midievent.emit(["midievent-add", note.note[0], note.note[1], note.note[2], note.note[3]])
  493. # -------------------------------------------------------------------------
  494. # Internal Functions
  495. def drawHeader(self):
  496. self.header = QGraphicsRectItem(0, 0, self.grid_width, self.header_height)
  497. #self.header.setZValue(1.0)
  498. self.header.setPos(self.piano_width, 0)
  499. self.addItem(self.header)
  500. def drawPiano(self):
  501. piano_keys_width = self.piano_width - self.padding
  502. labels = ('B','Bb','A','Ab','G','Gb','F','E','Eb','D','Db','C')
  503. black_notes = (2,4,6,9,11)
  504. piano_label = QFont()
  505. piano_label.setPointSize(6)
  506. self.piano = QGraphicsRectItem(0, 0, piano_keys_width, self.piano_height)
  507. self.piano.setPos(0, self.header_height)
  508. self.addItem(self.piano)
  509. key = PianoKeyItem(piano_keys_width, self.note_height, self.piano)
  510. label = QGraphicsSimpleTextItem('C8', key)
  511. label.setPos(18, 1)
  512. label.setFont(piano_label)
  513. key.setBrush(QColor(255, 255, 255))
  514. for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
  515. for j in range(self.notes_in_octave, 0, -1):
  516. if j in black_notes:
  517. key = PianoKeyItem(piano_keys_width/1.4, self.note_height, self.piano)
  518. key.setBrush(QColor(0, 0, 0))
  519. key.setZValue(1.0)
  520. key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
  521. elif (j - 1) and (j + 1) in black_notes:
  522. key = PianoKeyItem(piano_keys_width, self.note_height * 2, self.piano)
  523. key.setBrush(QColor(255, 255, 255))
  524. key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
  525. elif (j - 1) in black_notes:
  526. key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, self.piano)
  527. key.setBrush(QColor(255, 255, 255))
  528. key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.)
  529. elif (j + 1) in black_notes:
  530. key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, self.piano)
  531. key.setBrush(QColor(255, 255, 255))
  532. key.setPos(0, self.note_height * j + self.octave_height * (i - 1))
  533. if j == 12:
  534. label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1], self.end_octave - i), key )
  535. label.setPos(18, 6)
  536. label.setFont(piano_label)
  537. self.piano_keys.append(key)
  538. def drawGrid(self):
  539. black_notes = [2,4,6,9,11]
  540. scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
  541. scale_bar.setPos(self.piano_width, 0)
  542. scale_bar.setBrush(QColor(100,100,100))
  543. clearpen = QPen(QColor(0,0,0,0))
  544. for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1):
  545. for j in range(self.notes_in_octave, 0, -1):
  546. scale_bar = QGraphicsRectItem(0, 0, self.grid_width, self.note_height, self.piano)
  547. scale_bar.setPos(self.piano_width, self.note_height * j + self.octave_height * (i - 1))
  548. scale_bar.setPen(clearpen)
  549. if j not in black_notes:
  550. scale_bar.setBrush(QColor(120,120,120))
  551. else:
  552. scale_bar.setBrush(QColor(100,100,100))
  553. measure_pen = QPen(QColor(0, 0, 0, 120), 3)
  554. half_measure_pen = QPen(QColor(0, 0, 0, 40), 2)
  555. line_pen = QPen(QColor(0, 0, 0, 40))
  556. for i in range(0, int(self.num_measures) + 1):
  557. measure = QGraphicsLineItem(0, 0, 0, self.piano_height + self.header_height - measure_pen.width(), self.header)
  558. measure.setPos(self.measure_width * i, 0.5 * measure_pen.width())
  559. measure.setPen(measure_pen)
  560. if i < self.num_measures:
  561. number = QGraphicsSimpleTextItem('%d' % (i + 1), self.header)
  562. number.setPos(self.measure_width * i + 5, 2)
  563. number.setBrush(Qt.white)
  564. for j in self.frange(0, self.time_sig[0]*self.grid_div/self.time_sig[1], 1.):
  565. line = QGraphicsLineItem(0, 0, 0, self.piano_height, self.header)
  566. line.setZValue(1.0)
  567. line.setPos(self.measure_width * i + self.value_width * j, self.header_height)
  568. if j == self.time_sig[0]*self.grid_div/self.time_sig[1] / 2.0:
  569. line.setPen(half_measure_pen)
  570. else:
  571. line.setPen(line_pen)
  572. def drawPlayHead(self):
  573. self.play_head = QGraphicsLineItem(self.piano_width, self.header_height, self.piano_width, self.total_height)
  574. self.play_head.setPen(QPen(QColor(255,255,255,50), 2))
  575. self.play_head.setZValue(1.)
  576. self.addItem(self.play_head)
  577. def refreshScene(self):
  578. list(map(self.removeItem, self.notes))
  579. self.selected_notes = []
  580. self.piano_keys = []
  581. self.clear()
  582. self.drawPiano()
  583. self.drawHeader()
  584. self.drawGrid()
  585. self.drawPlayHead()
  586. for note in self.notes[:]:
  587. if note.note[1] >= (self.num_measures * self.time_sig[0]):
  588. self.notes.remove(note)
  589. self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
  590. elif note.note[2] > self.max_note_length:
  591. new_note = note.note[:]
  592. new_note[2] = self.max_note_length
  593. self.notes.remove(note)
  594. self.drawNote(new_note[0], new_note[1], self.max_note_length, new_note[3], False)
  595. self.midievent.emit(["midievent-remove", note.note[0], note.note[1], note.note[2], note.note[3]])
  596. self.midievent.emit(["midievent-add", new_note[0], new_note[1], new_note[2], new_note[3]])
  597. list(map(self.addItem, self.notes))
  598. if self.views():
  599. self.views()[0].setSceneRect(self.itemsBoundingRect())
  600. def clearNotes(self):
  601. self.clear()
  602. self.notes = []
  603. self.selected_notes = []
  604. self.drawPiano()
  605. self.drawHeader()
  606. self.drawGrid()
  607. def makeGhostNote(self, pos_x, pos_y):
  608. """creates the ghostnote that is placed on the scene before the real one is."""
  609. if self.ghost_note:
  610. self.removeItem(self.ghost_note)
  611. length = self.full_note_width * self.default_length
  612. (start, note) = self.snap(pos_x, pos_y)
  613. self.ghost_vel = self.default_ghost_vel
  614. self.ghost_rect = QRectF(start, note, length, self.note_height)
  615. self.ghost_rect_orig_width = self.ghost_rect.width()
  616. self.ghost_note = QGraphicsRectItem(self.ghost_rect)
  617. self.ghost_note.setBrush(QColor(230, 221, 45, 100))
  618. self.addItem(self.ghost_note)
  619. def drawNote(self, note_num, note_start=None, note_length=None, note_velocity=None, add=True):
  620. """
  621. note_num: midi number, 0 - 127
  622. note_start: 0 - (num_measures * time_sig[0]) so this is in beats
  623. note_length: 0 - (num_measures * time_sig[0]/time_sig[1]) this is in measures
  624. note_velocity: 0 - 127
  625. """
  626. info = [note_num, note_start, note_length, note_velocity]
  627. if not note_start % (self.num_measures * self.time_sig[0]) == note_start:
  628. #self.midievent.emit(["midievent-remove", note_num, note_start, note_length, note_velocity])
  629. while not note_start % (self.num_measures * self.time_sig[0]) == note_start:
  630. self.setMeasures(self.num_measures+1)
  631. self.measureupdate.emit(self.num_measures)
  632. self.refreshScene()
  633. x_start = self.get_note_x_start(note_start)
  634. if note_length > self.max_note_length:
  635. note_length = self.max_note_length + 0.25
  636. x_length = self.get_note_x_length(note_length)
  637. y_pos = self.get_note_y_pos(note_num)
  638. note = NoteItem(self.note_height, x_length, info)
  639. note.setPos(x_start, y_pos)
  640. self.notes.append(note)
  641. if add:
  642. self.addItem(note)
  643. # -------------------------------------------------------------------------
  644. # Helper Functions
  645. def frange(self, x, y, t):
  646. while x < y:
  647. yield x
  648. x += t
  649. def quantize(self, value):
  650. self.snap_value = float(self.full_note_width) * value if value else None
  651. def snap(self, pos_x, pos_y = None):
  652. if self.snap_value:
  653. pos_x = int(round((pos_x - self.piano_width) / self.snap_value)) \
  654. * self.snap_value + self.piano_width
  655. if pos_y is not None:
  656. pos_y = int((pos_y - self.header_height) / self.note_height) \
  657. * self.note_height + self.header_height
  658. return (pos_x, pos_y) if pos_y is not None else pos_x
  659. def adjust_note_vel(self, event):
  660. m_pos = event.scenePos()
  661. #bind velocity to vertical mouse movement
  662. self.ghost_vel += (event.lastScenePos().y() - m_pos.y())/10
  663. if self.ghost_vel < 0:
  664. self.ghost_vel = 0
  665. elif self.ghost_vel > 127:
  666. self.ghost_vel = 127
  667. m_width = self.ghost_rect.x() + self.ghost_rect_orig_width
  668. if m_pos.x() < m_width:
  669. m_pos.setX(m_width)
  670. m_new_x = self.snap(m_pos.x())
  671. self.ghost_rect.setRight(m_new_x)
  672. self.ghost_note.setRect(self.ghost_rect)
  673. def enforce_bounds(self, pos):
  674. if pos.x() < self.piano_width:
  675. pos.setX(self.piano_width)
  676. elif pos.x() > self.grid_width + self.piano_width:
  677. pos.setX(self.grid_width + self.piano_width)
  678. if pos.y() < self.header_height + self.padding:
  679. pos.setY(self.header_height + self.padding)
  680. return pos
  681. def get_note_start_from_x(self, note_x):
  682. return (note_x - self.piano_width) / (self.grid_width / self.num_measures / self.time_sig[0])
  683. def get_note_x_start(self, note_start):
  684. return self.piano_width + \
  685. (self.grid_width / self.num_measures / self.time_sig[0]) * note_start
  686. def get_note_x_length(self, note_length):
  687. return float(self.time_sig[1]) / self.time_sig[0] * note_length * self.grid_width / self.num_measures
  688. def get_note_length_from_x(self, note_x):
  689. return float(self.time_sig[0]) / self.time_sig[1] * self.num_measures / self.grid_width \
  690. * note_x
  691. def get_note_y_pos(self, note_num):
  692. return self.header_height + self.note_height * (self.total_notes - note_num - 1)
  693. def get_note_num_from_y(self, note_y_pos):
  694. return -(((note_y_pos - self.header_height) / self.note_height) - self.total_notes + 1)
  695. class PianoRollView(QGraphicsView):
  696. def __init__(self, parent, time_sig = '4/4', num_measures = 4, quantize_val = '1/8'):
  697. QGraphicsView.__init__(self, parent)
  698. self.piano = PianoRoll(time_sig, num_measures, quantize_val)
  699. self.setScene(self.piano)
  700. #self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  701. x = 0 * self.sceneRect().width() + self.sceneRect().left()
  702. y = 0.4 * self.sceneRect().height() + self.sceneRect().top()
  703. self.centerOn(x, y)
  704. self.setAlignment(Qt.AlignLeft)
  705. self.o_transform = self.transform()
  706. self.zoom_x = 1
  707. self.zoom_y = 1
  708. def setZoomX(self, scale_x):
  709. self.setTransform(self.o_transform)
  710. self.zoom_x = 1 + scale_x / float(99) * 2
  711. self.scale(self.zoom_x, self.zoom_y)
  712. def setZoomY(self, scale_y):
  713. self.setTransform(self.o_transform)
  714. self.zoom_y = 1 + scale_y / float(99)
  715. self.scale(self.zoom_x, self.zoom_y)
  716. # ------------------------------------------------------------------------------------------------------------
  717. class ModeIndicator(QWidget):
  718. def __init__(self, parent):
  719. QWidget.__init__(self, parent)
  720. #self.setGeometry(0, 0, 30, 20)
  721. self.setFixedSize(30,20)
  722. self.mode = None
  723. def paintEvent(self, event):
  724. event.accept()
  725. painter = QPainter(self)
  726. painter.setPen(QPen(QColor(0, 0, 0, 0)))
  727. if self.mode == 'velocity_mode':
  728. painter.setBrush(QColor(127, 0, 0))
  729. elif self.mode == 'insert_mode':
  730. painter.setBrush(QColor(0, 100, 127))
  731. else:
  732. painter.setBrush(QColor(0, 0, 0, 0))
  733. painter.drawRect(0, 0, 30, 20)
  734. def changeMode(self, new_mode):
  735. self.mode = new_mode
  736. self.update()
  737. # ------------------------------------------------------------------------------------------------------------