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.

1032 lines
39KB

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