Signed-off-by: falkTX <falktx@falktx.com>tags/v2.3.0-RC1
@@ -2,7 +2,7 @@ | |||
# -*- coding: utf-8 -*- | |||
# A piano roll viewer/editor | |||
# Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
# Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com> | |||
# Copyright (C) 2014-2015 Perry Nguyen | |||
# | |||
# This program is free software; you can redistribute it and/or | |||
@@ -69,6 +69,7 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||
self.ui.act_edit_select_all.triggered.connect(self.slot_editSelectAll) | |||
self.ui.piano.midievent.connect(self.sendMsg) | |||
self.ui.piano.noteclicked.connect(self.sendTemporaryNote) | |||
self.ui.piano.measureupdate.connect(self.updateMeasureBox) | |||
self.ui.piano.modeupdate.connect(self.ui.modeIndicator.changeMode) | |||
self.ui.piano.modeupdate.connect(self.slot_modeChanged) | |||
@@ -237,6 +238,9 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||
self.send([msg, note_start, 3, MIDI_STATUS_NOTE_ON, note, vel]) | |||
self.send([msg, note_stop, 3, MIDI_STATUS_NOTE_OFF, note, vel]) | |||
def sendTemporaryNote(self, note, on): | |||
self.send(["midi-note", note, on]) | |||
def msgCallback(self, msg): | |||
msg = charPtrToString(msg) | |||
@@ -246,15 +250,15 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||
elif msg == "midievent-add": | |||
# adds single midi event | |||
time = int(self.readlineblock()) | |||
size = int(self.readlineblock()) | |||
data = tuple(int(self.readlineblock()) for x in range(size)) | |||
time = self.readlineblock_int() | |||
size = self.readlineblock_int() | |||
data = tuple(self.readlineblock_int() for x in range(size)) | |||
self.handleMidiEvent(time, size, data) | |||
elif msg == "transport": | |||
playing, frame, bar, beat, tick = tuple(int(i) for i in self.readlineblock().split(":")) | |||
bpm = float(self.readlineblock()) | |||
bpm = self.readlineblock_float() | |||
playing = bool(int(playing)) | |||
old_frame = self.fTransportInfo['frame'] | |||
@@ -326,7 +330,7 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||
if __name__ == '__main__': | |||
import resources_rc | |||
pathBinaries, pathResources = getPaths() | |||
pathBinaries, _ = getPaths() | |||
gCarla.utils = CarlaUtils(os.path.join(pathBinaries, "libcarla_utils." + DLL_EXTENSION)) | |||
gCarla.utils.set_process_name("MidiPattern") | |||
@@ -2,7 +2,7 @@ | |||
# -*- coding: utf-8 -*- | |||
# A piano roll viewer/editor | |||
# Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
# Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com> | |||
# Copyright (C) 2014-2015 Perry Nguyen | |||
# | |||
# This program is free software; you can redistribute it and/or | |||
@@ -21,7 +21,7 @@ | |||
# Imports (Global) | |||
from PyQt5.QtCore import Qt, QRectF, QPointF, pyqtSignal | |||
from PyQt5.QtGui import QColor, QFont, QPen, QPainter | |||
from PyQt5.QtGui import QColor, QFont, QPen, QPainter, QTransform | |||
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsOpacityEffect, QGraphicsRectItem, QGraphicsSimpleTextItem | |||
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView | |||
from PyQt5.QtWidgets import QWidget, QLabel, QComboBox, QHBoxLayout, QVBoxLayout, QStyle | |||
@@ -246,11 +246,12 @@ class NoteItem(QGraphicsRectItem): | |||
class PianoKeyItem(QGraphicsRectItem): | |||
def __init__(self, width, height, parent): | |||
def __init__(self, width, height, note, parent): | |||
QGraphicsRectItem.__init__(self, 0, 0, width, height, parent) | |||
self.setPen(QPen(QColor(0,0,0,80))) | |||
self.width = width | |||
self.height = height | |||
self.note = note | |||
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |||
self.setAcceptHoverEvents(True) | |||
self.hover_brush = QColor(200, 0, 0) | |||
@@ -285,6 +286,7 @@ class PianoKeyItem(QGraphicsRectItem): | |||
class PianoRoll(QGraphicsScene): | |||
'''the piano roll''' | |||
noteclicked = pyqtSignal(int,bool) | |||
midievent = pyqtSignal(list) | |||
measureupdate = pyqtSignal(int) | |||
modeupdate = pyqtSignal(str) | |||
@@ -342,6 +344,7 @@ class PianoRoll(QGraphicsScene): | |||
self.piano = None | |||
self.header = None | |||
self.play_head = None | |||
self.last_piano_note = None | |||
self.setGridDiv() | |||
self.default_length = 1. / self.grid_div | |||
@@ -454,6 +457,14 @@ class PianoRoll(QGraphicsScene): | |||
def mousePressEvent(self, event): | |||
QGraphicsScene.mousePressEvent(self, event) | |||
if self.piano.contains(event.scenePos()): | |||
item = self.itemAt(event.scenePos(), QTransform()) | |||
if isinstance(item, PianoKeyItem) and item.note is not None: | |||
self.last_piano_note = item.note | |||
self.noteclicked.emit(self.last_piano_note, True) | |||
return | |||
if not (any(key.pressed for key in self.piano_keys) | |||
or any(note.pressed for note in self.notes)): | |||
for note in self.selected_notes: | |||
@@ -469,6 +480,7 @@ class PianoRoll(QGraphicsScene): | |||
self.marquee = QGraphicsRectItem(self.marquee_rect) | |||
self.marquee.setBrush(QColor(255, 255, 255, 100)) | |||
self.addItem(self.marquee) | |||
else: | |||
for s_note in self.notes: | |||
if s_note.pressed and s_note in self.selected_notes: | |||
@@ -484,6 +496,10 @@ class PianoRoll(QGraphicsScene): | |||
def mouseMoveEvent(self, event): | |||
QGraphicsScene.mouseMoveEvent(self, event) | |||
if self.last_piano_note is not None: | |||
return | |||
self.mousePos = event.scenePos() | |||
if not (any((key.pressed for key in self.piano_keys))): | |||
m_pos = event.scenePos() | |||
@@ -544,6 +560,13 @@ class PianoRoll(QGraphicsScene): | |||
note.moveEvent(event) | |||
def mouseReleaseEvent(self, event): | |||
QGraphicsScene.mouseReleaseEvent | |||
if self.last_piano_note is not None: | |||
self.noteclicked.emit(self.last_piano_note, False) | |||
self.last_piano_note = None | |||
return | |||
if not (any((key.pressed for key in self.piano_keys)) or any((note.pressed for note in self.notes))): | |||
if event.button() == Qt.LeftButton: | |||
if self.place_ghost and self.insert_mode: | |||
@@ -557,6 +580,7 @@ class PianoRoll(QGraphicsScene): | |||
elif self.marquee_select: | |||
self.marquee_select = False | |||
self.removeItem(self.marquee) | |||
elif not self.marquee_select: | |||
for note in self.selected_notes: | |||
old_info = note.note[:] | |||
@@ -586,32 +610,35 @@ class PianoRoll(QGraphicsScene): | |||
self.piano.setPos(0, self.header_height) | |||
self.addItem(self.piano) | |||
key = PianoKeyItem(piano_keys_width, self.note_height, self.piano) | |||
label = QGraphicsSimpleTextItem('C8', key) | |||
key = PianoKeyItem(piano_keys_width, self.note_height, 78, self.piano) | |||
label = QGraphicsSimpleTextItem('C9', key) | |||
label.setPos(18, 1) | |||
label.setFont(piano_label) | |||
key.setBrush(QColor(255, 255, 255)) | |||
for i in range(self.end_octave - self.start_octave, self.start_octave - self.start_octave, -1): | |||
for i in range(self.end_octave - self.start_octave, 0, -1): | |||
for j in range(self.notes_in_octave, 0, -1): | |||
note = (self.end_octave - i + 3) * 12 - j | |||
if j in black_notes: | |||
key = PianoKeyItem(piano_keys_width/1.4, self.note_height, self.piano) | |||
key = PianoKeyItem(piano_keys_width/1.4, self.note_height, note, self.piano) | |||
key.setBrush(QColor(0, 0, 0)) | |||
key.setZValue(1.0) | |||
key.setPos(0, self.note_height * j + self.octave_height * (i - 1)) | |||
elif (j - 1) and (j + 1) in black_notes: | |||
key = PianoKeyItem(piano_keys_width, self.note_height * 2, self.piano) | |||
key = PianoKeyItem(piano_keys_width, self.note_height * 2, note, self.piano) | |||
key.setBrush(QColor(255, 255, 255)) | |||
key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.) | |||
elif (j - 1) in black_notes: | |||
key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, self.piano) | |||
key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano) | |||
key.setBrush(QColor(255, 255, 255)) | |||
key.setPos(0, self.note_height * j + self.octave_height * (i - 1) - self.note_height/2.) | |||
elif (j + 1) in black_notes: | |||
key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, self.piano) | |||
key = PianoKeyItem(piano_keys_width, self.note_height * 3./2, note, self.piano) | |||
key.setBrush(QColor(255, 255, 255)) | |||
key.setPos(0, self.note_height * j + self.octave_height * (i - 1)) | |||
if j == 12: | |||
label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1], self.end_octave - i), key ) | |||
label = QGraphicsSimpleTextItem('{}{}'.format(labels[j - 1], | |||
self.end_octave - i + 1), | |||
key) | |||
label.setPos(18, 6) | |||
label.setFont(piano_label) | |||
self.piano_keys.append(key) | |||
@@ -1,6 +1,6 @@ | |||
/* | |||
* Carla Native Plugins | |||
* Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com> | |||
* Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com> | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU General Public License as | |||
@@ -59,9 +59,18 @@ public: | |||
fStartTime(0), | |||
fReadMutex(), | |||
fWriteMutex(), | |||
fData() | |||
fData(), | |||
fTemporary() | |||
{ | |||
CARLA_SAFE_ASSERT(kPlayer != nullptr); | |||
carla_zeroStructs(fTemporary, 2); | |||
fTemporary[0].data[0] = MIDI_STATUS_NOTE_OFF; | |||
fTemporary[1].data[0] = MIDI_STATUS_NOTE_ON; | |||
fTemporary[1].data[2] = 100; | |||
fTemporary[0].size = fTemporary[1].size = 3; | |||
} | |||
~MidiPattern() noexcept | |||
@@ -183,6 +192,12 @@ public: | |||
appendSorted(rawEvent); | |||
} | |||
void flagTemporaryNote(const uint8_t note, const bool on) | |||
{ | |||
fTemporary[on ? 1 : 0].time = 1; | |||
fTemporary[on ? 1 : 0].data[1] = note; | |||
} | |||
// ------------------------------------------------------------------- | |||
// remove data | |||
@@ -240,6 +255,8 @@ public: | |||
{ | |||
long double ldtime; | |||
playTemporary(); | |||
const CarlaMutexTryLocker cmtl(fReadMutex); | |||
if (cmtl.wasNotLocked()) | |||
@@ -266,6 +283,21 @@ public: | |||
return true; | |||
} | |||
void playTemporary() | |||
{ | |||
if (fTemporary[0].time != 0) | |||
{ | |||
fTemporary[0].time = 0; | |||
kPlayer->writeMidiEvent(fMidiPort, 0.0, &fTemporary[0]); | |||
} | |||
if (fTemporary[1].time != 0) | |||
{ | |||
fTemporary[1].time = 0; | |||
kPlayer->writeMidiEvent(fMidiPort, 0.0, &fTemporary[1]); | |||
} | |||
} | |||
// ------------------------------------------------------------------- | |||
// configure | |||
@@ -303,12 +335,15 @@ public: | |||
const CarlaMutexLocker cmlw(fWriteMutex); | |||
if (fData.count() == 0) | |||
return nullptr; | |||
char* const data((char*)std::calloc(1, fData.count()*maxMsgSize+1)); | |||
char* const data((char*)std::calloc(1, fData.count() * maxMsgSize + 1)); | |||
CARLA_SAFE_ASSERT_RETURN(data != nullptr, nullptr); | |||
if (fData.count() == 0) | |||
{ | |||
*data = '\0'; | |||
return data; | |||
} | |||
char* dataWrtn = data; | |||
int wrtn; | |||
@@ -453,6 +488,8 @@ private: | |||
CarlaMutex fWriteMutex; | |||
LinkedList<const RawMidiEvent*> fData; | |||
RawMidiEvent fTemporary[2]; | |||
void appendSorted(const RawMidiEvent* const event) | |||
{ | |||
const CarlaMutexLocker cmlw(fWriteMutex); | |||
@@ -1,6 +1,6 @@ | |||
/* | |||
* Carla Native Plugins | |||
* Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
* Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com> | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU General Public License as | |||
@@ -281,6 +281,10 @@ protected: | |||
fLastFrame = fTimeInfo.frame; | |||
fLastPosition = static_cast<float>(playPos); | |||
} | |||
else | |||
{ | |||
fMidiOut.playTemporary(); | |||
} | |||
} | |||
// ------------------------------------------------------------------- | |||
@@ -389,6 +393,17 @@ protected: | |||
return true; | |||
} | |||
if (std::strcmp(msg, "midi-note") == 0) | |||
{ | |||
uint8_t note; | |||
bool on; | |||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true); | |||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(on), true); | |||
fMidiOut.flagTemporaryNote(note, on); | |||
return true; | |||
} | |||
if (std::strcmp(msg, "midievent-add") == 0) | |||
{ | |||
uint64_t time; | |||
@@ -406,8 +421,7 @@ protected: | |||
data[i] = dvalue; | |||
} | |||
fMidiOut.addRaw(time /* * TICKS_PER_BEAT */, data, size); | |||
fMidiOut.addRaw(time, data, size); | |||
return true; | |||
} | |||
@@ -428,8 +442,7 @@ protected: | |||
data[i] = dvalue; | |||
} | |||
fMidiOut.removeRaw(time /* * TICKS_PER_BEAT */, data, size); | |||
fMidiOut.removeRaw(time, data, size); | |||
return true; | |||
} | |||