diff --git a/source/frontend/midipattern-ui b/source/frontend/midipattern-ui index 28e676bca..cf26395a5 100755 --- a/source/frontend/midipattern-ui +++ b/source/frontend/midipattern-ui @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # A piano roll viewer/editor -# Copyright (C) 2012-2019 Filipe Coelho +# Copyright (C) 2012-2020 Filipe Coelho # 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") diff --git a/source/frontend/widgets/pianoroll.py b/source/frontend/widgets/pianoroll.py index 2ff1b7334..674dc5adb 100755 --- a/source/frontend/widgets/pianoroll.py +++ b/source/frontend/widgets/pianoroll.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # A piano roll viewer/editor -# Copyright (C) 2012-2019 Filipe Coelho +# Copyright (C) 2012-2020 Filipe Coelho # 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) diff --git a/source/native-plugins/midi-base.hpp b/source/native-plugins/midi-base.hpp index 3d57160e0..8133577d9 100644 --- a/source/native-plugins/midi-base.hpp +++ b/source/native-plugins/midi-base.hpp @@ -1,6 +1,6 @@ /* * Carla Native Plugins - * Copyright (C) 2012-2015 Filipe Coelho + * Copyright (C) 2012-2020 Filipe Coelho * * 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 fData; + RawMidiEvent fTemporary[2]; + void appendSorted(const RawMidiEvent* const event) { const CarlaMutexLocker cmlw(fWriteMutex); diff --git a/source/native-plugins/midi-pattern.cpp b/source/native-plugins/midi-pattern.cpp index 3b0ddbecf..b74aeefec 100644 --- a/source/native-plugins/midi-pattern.cpp +++ b/source/native-plugins/midi-pattern.cpp @@ -1,6 +1,6 @@ /* * Carla Native Plugins - * Copyright (C) 2012-2019 Filipe Coelho + * Copyright (C) 2012-2020 Filipe Coelho * * 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(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; }