From 594dcf0a847465e7516433faa1d4e0ba4ec7a549 Mon Sep 17 00:00:00 2001 From: nomzu Date: Mon, 20 Aug 2018 00:05:01 +0300 Subject: [PATCH 01/11] Step Recording inital commit includes all basic functionallty. Added two new classes: StepRecorder(logic) StepRecorderWidget(gui). still missing: Ignoring mouse edit events during step recording --- data/themes/classic/record_step_off.png | Bin 0 -> 443 bytes data/themes/classic/record_step_on.png | Bin 0 -> 596 bytes data/themes/default/record_step_off.png | Bin 0 -> 443 bytes data/themes/default/record_step_on.png | Bin 0 -> 596 bytes include/Editor.h | 4 +- include/PianoRoll.h | 18 +- include/StepRecorder.h | 120 ++++++ include/StepRecorderWidget.h | 84 +++++ src/core/CMakeLists.txt | 3 +- src/core/StepRecorder.cpp | 465 ++++++++++++++++++++++++ src/gui/CMakeLists.txt | 3 +- src/gui/editors/Editor.cpp | 11 +- src/gui/editors/PianoRoll.cpp | 233 ++++++++---- src/gui/editors/SongEditor.cpp | 2 +- src/gui/widgets/StepRecorderWidget.cpp | 145 ++++++++ 15 files changed, 1014 insertions(+), 74 deletions(-) create mode 100644 data/themes/classic/record_step_off.png create mode 100644 data/themes/classic/record_step_on.png create mode 100644 data/themes/default/record_step_off.png create mode 100644 data/themes/default/record_step_on.png create mode 100644 include/StepRecorder.h create mode 100644 include/StepRecorderWidget.h create mode 100644 src/core/StepRecorder.cpp create mode 100644 src/gui/widgets/StepRecorderWidget.cpp diff --git a/data/themes/classic/record_step_off.png b/data/themes/classic/record_step_off.png new file mode 100644 index 0000000000000000000000000000000000000000..8da17a91009f9ee65a28f29d4e8d2ce3ea073eb0 GIT binary patch literal 443 zcmV;s0Yv_ZP)!40j=$PgAl~tMmtNfu&@zHqfcQ9pFjkQ zAVLZWOd!b2d^QtVH$S_DN-iu6?7ionyZ76X<~_``7uMQpsZ=^N#?*~5+mZx40-fP- zcvCKy-*b0y&NaOEJ*I^BzUQ24;reBq^VA= zwbj&^%~{Qp<6CR1liamZ6IEj21JDOPffwKk*h(fk=UOA3;`h3Zsi%4@N$N{_i|2x* z&G;PsKxMpE3L$h;4zGbibXfs*fn(q)y_^t2H;*8|GjIy*0V`4502i5p-ure2B>}jN zqN^!<+9REX#MU~g9yLjql3pb_NrU(;70;ScJC02TG6$L1OaS{nLc9hPfn}fuoB$u0 lVE@Ul_ZyeP|6E^l6<_1QUov$MTMqyL002ovPDHLkV1mZ_vFiW; literal 0 HcmV?d00001 diff --git a/data/themes/classic/record_step_on.png b/data/themes/classic/record_step_on.png new file mode 100644 index 0000000000000000000000000000000000000000..700ba97f3056a189e4b3667da25885dfcf6e2034 GIT binary patch literal 596 zcmV-a0;~OrP))TL%$N3 zqw6pG0bKyr^%tI$ZNt!grP4v3Z&U18><>25UfMXbG zf2s7TXU|+TS5_W-G5MGNSw63|?+4nOplRj?YPGRW#^mH107)cvWq7zQfk0E@@t?A^ z^jbDH?sWiawXvXS=Gtd}hkK{YNekKs8V%rfcE6&Nu1NQcwP_N(iH1>6U iy=xy1|7U#NsQ3$`99L@!40j=$PgAl~tMmtNfu&@zHqfcQ9pFjkQ zAVLZWOd!b2d^QtVH$S_DN-iu6?7ionyZ76X<~_``7uMQpsZ=^N#?*~5+mZx40-fP- zcvCKy-*b0y&NaOEJ*I^BzUQ24;reBq^VA= zwbj&^%~{Qp<6CR1liamZ6IEj21JDOPffwKk*h(fk=UOA3;`h3Zsi%4@N$N{_i|2x* z&G;PsKxMpE3L$h;4zGbibXfs*fn(q)y_^t2H;*8|GjIy*0V`4502i5p-ure2B>}jN zqN^!<+9REX#MU~g9yLjql3pb_NrU(;70;ScJC02TG6$L1OaS{nLc9hPfn}fuoB$u0 lVE@Ul_ZyeP|6E^l6<_1QUov$MTMqyL002ovPDHLkV1mZ_vFiW; literal 0 HcmV?d00001 diff --git a/data/themes/default/record_step_on.png b/data/themes/default/record_step_on.png new file mode 100644 index 0000000000000000000000000000000000000000..700ba97f3056a189e4b3667da25885dfcf6e2034 GIT binary patch literal 596 zcmV-a0;~OrP))TL%$N3 zqw6pG0bKyr^%tI$ZNt!grP4v3Z&U18><>25UfMXbG zf2s7TXU|+TS5_W-G5MGNSw63|?+4nOplRj?YPGRW#^mH107)cvWq7zQfk0E@@t?A^ z^jbDH?sWiawXvXS=Gtd}hkK{YNekKs8V%rfcE6&Nu1NQcwP_N(iH1>6U iy=xy1|7U#NsQ3$`99L@ +#include +#include +#include + +#include "Note.h" +#include "lmms_basics.h" +#include "Pattern.h" + +class PianoRoll; +class StepRecorderWidget; + +class StepRecorder : public QObject +{ + Q_OBJECT + + public: + StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget); + + void initialize(); + void start(const MidiTime& currentPosition,const MidiTime& stepLength); + void stop(); + void notePressed(const Note & n); + void noteReleased(const Note & n); + bool keyPressEvent(QKeyEvent* ke); + bool mousePressEvent(QMouseEvent* ke); + void setCurrentPattern(Pattern* newPattern); + void setStepsLength(const MidiTime& newLength); + + inline bool isRecording() const + { + return m_isRecording; + } + + private slots: + void removeNotesReleasedForTooLong(); + + private: + void stepForwards(); + void stepBackwards(); + + void removeCurStepNotesFromPattern(QList* removedNotesCopy); + + void applyStep(); + void dismissStep(); + void prepareNewStep(); + + MidiTime getCurStepEndPos(); + + void updateCurStepNotes(); + void updateWidget(); + + bool isKeyEventDisallowedDuringStepRecording(QKeyEvent* ke); + + PianoRoll& m_pianoRoll; + StepRecorderWidget& m_stepRecorderWidget; + + bool m_isRecording = false; + MidiTime m_curStepStartPos = 0; + MidiTime m_curStepEndPos = 0; + + MidiTime m_stepsLength; + MidiTime m_curStepLength; // current step length refers to the step currently recorded. it may defer from m_stepsLength + // since the user can make current step larger + + QTimer m_updateReleasedTimer; + + Pattern* m_pattern; + + class ReleasedNote + { + public: + ReleasedNote() {}; + + void setNote(Note* m_note) + { + this->m_note = m_note; + timer.start(); + } + + int timeSinceReleased() + { + return timer.elapsed(); + } + + Note* m_note; + + private: + QTime timer; + } ; + QHash m_pressedNotes; + QHash m_releasedNotes; + + bool m_isStepInProgress = false; +}; + +#endif //STEP_RECORDER_H \ No newline at end of file diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h new file mode 100644 index 00000000000..fe9db4a41d4 --- /dev/null +++ b/include/StepRecorderWidget.h @@ -0,0 +1,84 @@ +/* + * StepRecorderWidget.h - widget that provide gui markers for step recording + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of"the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ +#ifndef _STEP_RECOREDER_WIDGET +#define _STEP_RECOREDER_WIDGET + +#include "lmms_basics.h" +#include "Note.h" + +#include +#include +#include + +class StepRecorderWidget : public QWidget +{ +public: + StepRecorderWidget( + QWidget * parent, + const int _ppt, + const int margin_top, + const int margin_bottom, + const int margin_left, + const int margin_right); + + //API used by PianoRoll + void setPixelsPerTact(int _ppt); + void setCurrentPosition(MidiTime currentPosition); + void setBottomMargin(const int margin_bottom); + + //API used by StepRecorder + void setStepsLength(MidiTime stepsLength); + void setStartPosition(MidiTime pos); + void setEndPosition(MidiTime pos); +private: + virtual void paintEvent(QPaintEvent * pe); + + int xCoordOfTick(int tick); + + void drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom); + void drawVerLine(QPainter* painter, const MidiTime& pos, const QColor& color, int top, int bottom); + + void updateBoundaries(); + + MidiTime m_stepsLength; + MidiTime m_curStepStartPos; + MidiTime m_curStepEndPos; + + int m_ppt; // pixels per tact + MidiTime m_currentPosition; // current position showed by on PianoRoll + + QColor m_colorLineStart; + QColor m_colorLineEnd; + + // boundaries within piano roll window + int m_top; + int m_bottom; + int m_left; + int m_right; + + const int m_margin_top; + int m_margin_bottom; // not const since can change on resize of edit-note area + const int m_margin_left; + const int m_margin_right; +} ; + +#endif //_STEP_RECOREDER_WIDGET diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 85a00780b10..a1e66b0a01d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -67,6 +67,7 @@ set(LMMS_SRCS core/TrackContainer.cpp core/ValueBuffer.cpp core/VstSyncController.cpp + core/StepRecorder.cpp core/audio/AudioAlsa.cpp core/audio/AudioDevice.cpp @@ -96,6 +97,6 @@ set(LMMS_SRCS core/midi/MidiPort.cpp core/midi/MidiTime.cpp core/midi/MidiWinMM.cpp - + PARENT_SCOPE ) diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp new file mode 100644 index 00000000000..87bbafe2b93 --- /dev/null +++ b/src/core/StepRecorder.cpp @@ -0,0 +1,465 @@ +/* + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "StepRecorder.h" +#include "StepRecorderWidget.h" +#include "PianoRoll.h" + +#include + +#include +using std::min; +using std::max; + +//for debugging: uncomment to create a win32 console, and enable DBG_PRINT +//#define DEBUG_STEP_RECORDER + +#ifdef DEBUG_STEP_RECORDER + #define DEBUG_CREATE_WIN32_CONSOLE + #define DRBUG_ENABLE_PRINTS +#endif //DEBUG_STEP_RECORDER + +#ifdef DEBUG_CREATE_WIN32_CONSOLE +#include +#endif + +#ifdef DRBUG_ENABLE_PRINTS +#include + #define DBG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else + #define DBG_PRINT(fmt, ...) +#endif + +const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70; + + +StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget): + m_pianoRoll(pianoRoll), + m_stepRecorderWidget(stepRecorderWidget) +{ + m_stepRecorderWidget.hide(); + +#if defined(DEBUG_CREATE_WIN32_CONSOLE) && defined(_WIN32) + //create win32 console and attach it for output + if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) + { + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif +} + +void StepRecorder::initialize() +{ + connect(&m_updateReleasedTimer, SIGNAL( timeout() ), this, SLOT( removeNotesReleasedForTooLong() ) ); +} + +void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLength) +{ + m_isRecording = true; + + setStepsLength(stepLength); + + // quantize current position to get start recording position + const int q = m_pianoRoll.quantization(); + const int curPosTicks = currentPosition.getTicks(); + const int QuantizedPosTicks = (curPosTicks / q) * q; + const MidiTime& QuantizedPos = MidiTime(QuantizedPosTicks); + + m_curStepStartPos = QuantizedPos; + m_curStepLength = 0; + + m_stepRecorderWidget.show(); + + prepareNewStep(); +} + +void StepRecorder::stop() +{ + m_stepRecorderWidget.hide(); + m_isRecording = false; +} + +void StepRecorder::notePressed(const Note & n) +{ + const int key = n.key(); + + if(m_pressedNotes.contains(key)) + { + //if note already pressed, return. this should not happen and this condition added just for robustness. + //in case this turns to be a valid case in the future, we should update note's velocity instead + return ; + } + + //if this is the first pressed note in step, advance position + if(!m_isStepInProgress) + { + m_isStepInProgress = true; + + //move curser one step forwards + stepForwards(); + } + + Note noteToAdd(m_stepsLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning() ); + + Note* newNote = m_pattern->addNote( noteToAdd, false); + m_pianoRoll.update(); + + //remove note from released if it is in there + if(m_releasedNotes.contains(key)) + { + ReleasedNote& rn = m_releasedNotes[key]; + m_pattern->removeNote(rn.m_note); + m_releasedNotes.remove(key); + } + + //add note to pressed list + m_pressedNotes[key] = newNote; +} +void StepRecorder::noteReleased(const Note & n) +{ + DBG_PRINT("%s: key[%d]... \n", __FUNCTION__, n.key()); + + const int key = n.key(); + if(m_pressedNotes.contains(key)) + { + //move note from pressed list to released list + Note* note = m_pressedNotes[key]; + m_pressedNotes.remove(key); + + ReleasedNote& rn = m_releasedNotes[key]; + rn.setNote(note); + + //if m_updateReleasedTimer is not already active, activate it + //(when activated, the timer will re-set itself as long as there are notes in m_releasedNotes + if(!m_updateReleasedTimer.isActive()) + { + m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS); + } + + DBG_PRINT("%s: key[%d] pressed->released \n", __FUNCTION__, key); + + //check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step + if(m_pressedNotes.count() == 0) + { + if(m_curStepLength > 0) + { + applyStep(); + } + else + { + dismissStep(); + } + } + } +} + +bool StepRecorder::keyPressEvent(QKeyEvent* ke) +{ + bool event_handled = false; + + switch(ke->key()) + { + case Qt::Key_Right: + { + if(!ke->isAutoRepeat()) + { + stepForwards(); + } + event_handled = true; + break; + } + + case Qt::Key_Left: + { + if(!ke->isAutoRepeat()) + { + stepBackwards(); + } + event_handled = true; + break; + } + + case Qt::Key_Escape: + { + if(m_isStepInProgress) + { + dismissStep(); + } + + event_handled = true; + break; + } + } + + if(!event_handled && isKeyEventDisallowedDuringStepRecording(ke)) + { + //TODO: display message to user that this key event is not allowed during step recording + event_handled = true; + } + return event_handled; +} + +void StepRecorder::setStepsLength(const MidiTime& newLength) +{ + if(m_isStepInProgress) + { + //update current step length by the new amount : (number_of_steps * newLength) + m_curStepLength = (m_curStepLength / m_stepsLength) * newLength; + + updateCurStepNotes(); + } + + m_stepsLength = newLength; + + updateWidget(); +} + +void StepRecorder::stepForwards() +{ + if(m_isStepInProgress) + { + m_curStepLength += m_stepsLength; + + updateCurStepNotes(); + } + else + { + m_curStepStartPos += m_stepsLength; + } + + updateWidget(); +} + +void StepRecorder::stepBackwards() +{ + if(m_isStepInProgress) + { + if(m_curStepLength > 0) + { + m_curStepLength = max(m_curStepLength - m_stepsLength, 0); + } + else + { + //if length is already zero - move starting position backwards + m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0); + } + + updateCurStepNotes(); + } + else + { + m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0); + } + + updateWidget(); +} + +void StepRecorder::removeCurStepNotesFromPattern(QList* removedNotesCopy) +{ + // Removing notes from pattern also deletes their instances. + // in order to recover them later, they a copy of them is added to an optional list + foreach (Note* note, m_pressedNotes) + { + if(removedNotesCopy != nullptr) + { + removedNotesCopy->append(*note); + } + + m_pattern->removeNote(note); + } + + foreach (const ReleasedNote& rn, m_releasedNotes) + { + if(removedNotesCopy != nullptr) + { + removedNotesCopy->append(*rn.m_note); + } + + m_pattern->removeNote(rn.m_note); + } +} + +void StepRecorder::applyStep() +{ + DBG_PRINT("%s\n", __FUNCTION__); + + // in order to allow "undo" of this step, we remove all notes from pattern, add checkpoint and re-add them + // (an alternative would be to add checkpoint in prepareNewStep(), but then, in case the step is dismissed + // the added checkpoint would be "empty" (i.e. not related to any modification)) + QList removedNotesCopy; + removeCurStepNotesFromPattern(&removedNotesCopy); + + m_pattern->addJournalCheckPoint(); + + foreach (const Note& note, removedNotesCopy) + { + m_pattern->addNote(note, false); + } + + m_pattern->rearrangeAllNotes(); + m_pattern->updateLength(); + m_pattern->dataChanged(); + Engine::getSong()->setModified(); + + prepareNewStep(); +} + +void StepRecorder::dismissStep() +{ + DBG_PRINT("%s\n", __FUNCTION__); + + if(!m_isStepInProgress) + { + return; + } + + removeCurStepNotesFromPattern(nullptr); + prepareNewStep(); +} + +void StepRecorder::prepareNewStep() +{ + DBG_PRINT("%s\n", __FUNCTION__); + + m_releasedNotes.clear(); + m_pressedNotes.clear(); + + m_isStepInProgress = false; + + m_curStepStartPos = getCurStepEndPos(); + m_curStepLength = 0; + + updateWidget(); +} + +void StepRecorder::setCurrentPattern( Pattern* newPattern ) +{ + DBG_PRINT("%s\n", __FUNCTION__); + + if(m_pattern != NULL && m_pattern != newPattern) + { + // remove any unsaved notes from old pattern + dismissStep(); + } + + m_pattern = newPattern; +} + +void StepRecorder::removeNotesReleasedForTooLong() +{ + DBG_PRINT("%s\n", __FUNCTION__); + + if(m_releasedNotes.count() == 0) + { + m_updateReleasedTimer.stop(); + } + else + { + int nextTimout = INT_MAX; + bool notesRemoved = false; + + QMutableHashIterator itr(m_releasedNotes); + while (itr.hasNext()) + { + itr.next(); + ReleasedNote& rn = itr.value(); + + DBG_PRINT("key[%d]: timeSinceReleased:[%d]\n", rn.m_note->key(), rn.timeSinceReleased()); + + const int timeSinceReleased = rn.timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout + if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS) + { + DBG_PRINT("removed...\n"); + + m_pattern->removeNote(rn.m_note); + itr.remove(); + notesRemoved = true; + } + else + { + nextTimout = min(nextTimout, REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS - timeSinceReleased); + } + } + + if(notesRemoved) + { + m_pianoRoll.update(); + } + + if(nextTimout != INT_MAX) + { + m_updateReleasedTimer.start(nextTimout); + } + } + +} + +MidiTime StepRecorder::getCurStepEndPos() +{ + return m_curStepStartPos + m_curStepLength; +} + +void StepRecorder::updateCurStepNotes() +{ + foreach (Note* note, m_pressedNotes) + { + note->setLength(m_curStepLength); + note->setPos(m_curStepStartPos); + } + + //need to update also released notes, since they might be recorded if not released for too long + foreach (const ReleasedNote& rn, m_releasedNotes) + { + rn.m_note->setLength(m_curStepLength); + rn.m_note->setPos(m_curStepStartPos); + } +} + +void StepRecorder::updateWidget() +{ + m_stepRecorderWidget.setStartPosition(m_curStepStartPos); + m_stepRecorderWidget.setEndPosition(getCurStepEndPos()); + m_stepRecorderWidget.setStepsLength(m_stepsLength); +} + +//list key pressed to be ignored during step recording (mostly - edit note actions) +//having this hardcoded is not very elegant nor flexible (consider adding new edot action to piano roll - developer must add to this list as well to be ignored) +//a better way would be to have an object to handle all "key to action" event and query it on runtime based (i.e. m_pianoRoll.keyEventHandler.getEditKeyEvents()); +bool StepRecorder::isKeyEventDisallowedDuringStepRecording(QKeyEvent* ke) +{ + int key = ke->key(); + + if((key == Qt::Key_Up || key == Qt::Key_Down) && (ke->modifiers() != Qt::NoModifier)) + { + //prevent editing but allow scrolling (no modifiers) + return true; + } + else if (key == Qt::Key_A && ke->modifiers() & Qt::ControlModifier) + { + return true; + } + else if (key == Qt::Key_Control || key == Qt::Key_Delete) + { + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5b4050bca70..7a617115265 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -87,7 +87,8 @@ SET(LMMS_SRCS gui/widgets/TrackLabelButton.cpp gui/widgets/TrackRenameLineEdit.cpp gui/widgets/VisualizationWidget.cpp - + gui/widgets/StepRecorderWidget.cpp + PARENT_SCOPE ) diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index bdc3e55d4bb..f97f8527079 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -73,11 +73,12 @@ void Editor::togglePlayStop() play(); } -Editor::Editor(bool record) : +Editor::Editor(bool record, bool stepRecord) : m_toolBar(new DropToolBar(this)), m_playAction(nullptr), m_recordAction(nullptr), m_recordAccompanyAction(nullptr), + m_toggleStepRecordingAction(nullptr), m_stopAction(nullptr) { m_toolBar = addDropToolBarToTop(tr("Transport controls")); @@ -90,14 +91,16 @@ Editor::Editor(bool record) : // Set up play and record actions m_playAction = new QAction(embed::getIconPixmap("play"), tr("Play (Space)"), this); m_stopAction = new QAction(embed::getIconPixmap("stop"), tr("Stop (Space)"), this); - + m_recordAction = new QAction(embed::getIconPixmap("record"), tr("Record"), this); m_recordAccompanyAction = new QAction(embed::getIconPixmap("record_accompany"), tr("Record while playing"), this); + m_toggleStepRecordingAction = new QAction(embed::getIconPixmap("record_step_off"), tr("Toggle Step Recording"), this); // Set up connections connect(m_playAction, SIGNAL(triggered()), this, SLOT(play())); connect(m_recordAction, SIGNAL(triggered()), this, SLOT(record())); connect(m_recordAccompanyAction, SIGNAL(triggered()), this, SLOT(recordAccompany())); + connect(m_toggleStepRecordingAction, SIGNAL(triggered()), this, SLOT(toggleStepRecording())); connect(m_stopAction, SIGNAL(triggered()), this, SLOT(stop())); new QShortcut(Qt::Key_Space, this, SLOT(togglePlayStop())); @@ -108,6 +111,10 @@ Editor::Editor(bool record) : addButton(m_recordAction, "recordButton"); addButton(m_recordAccompanyAction, "recordAccompanyButton"); } + if(stepRecord) + { + addButton(m_toggleStepRecordingAction, "stepRecordButton"); + } addButton(m_stopAction, "stopButton"); } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 07309ec1cc6..b1d94146748 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -62,6 +62,7 @@ #include "stdshims.h" #include "TextFloat.h" #include "TimeLineWidget.h" +#include "StepRecorderWidget.h" using std::move; @@ -177,6 +178,8 @@ PianoRoll::PianoRoll() : m_ctrlMode( ModeDraw ), m_mouseDownRight( false ), m_scrollBack( false ), + m_stepRecorderWidget(this, DEFAULT_PR_PPT, PR_TOP_MARGIN, PR_BOTTOM_MARGIN + m_notesEditHeight, WHITE_KEY_WIDTH, 0), + m_stepRecorder(*this, m_stepRecorderWidget), m_barLineColor( 0, 0, 0 ), m_beatLineColor( 0, 0, 0 ), m_lineColor( 0, 0, 0 ), @@ -395,7 +398,7 @@ PianoRoll::PianoRoll() : // Note length change can cause a redraw if Q is set to lock connect( &m_noteLenModel, SIGNAL( dataChanged() ), - this, SLOT( quantizeChanged() ) ); + this, SLOT( noteLengthChanged() ) ); // Set up scale model const InstrumentFunctionNoteStacking::ChordTable& chord_table = @@ -444,6 +447,8 @@ PianoRoll::PianoRoll() : //connection for selecion from timeline connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ), this, SLOT( selectRegionFromPixels( int, int ) ) ); + + m_stepRecorder.initialize(); } @@ -637,55 +642,56 @@ void PianoRoll::setCurrentPattern( Pattern* newPattern ) Engine::getSong()->playPattern( NULL ); } + if(m_stepRecorder.isRecording()) + { + m_stepRecorder.stop(); + } + // set new data m_pattern = newPattern; m_currentPosition = 0; m_currentNote = NULL; m_startKey = INITIAL_START_KEY; - if( ! hasValidPattern() ) + if(hasValidPattern()) { - //resizeEvent( NULL ); - - update(); - emit currentPatternChanged(); - return; - } - - m_leftRightScroll->setValue( 0 ); + m_leftRightScroll->setValue( 0 ); - // determine the central key so that we can scroll to it - int central_key = 0; - int total_notes = 0; - for( const Note *note : m_pattern->notes() ) - { - if( note->length() > 0 ) + // determine the central key so that we can scroll to it + int central_key = 0; + int total_notes = 0; + for( const Note *note : m_pattern->notes() ) { - central_key += note->key(); - ++total_notes; + if( note->length() > 0 ) + { + central_key += note->key(); + ++total_notes; + } } - } - if( total_notes > 0 ) - { - central_key = central_key / total_notes - - ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; - m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave ); - } + if( total_notes > 0 ) + { + central_key = central_key / total_notes - + ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; + m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave ); + } - // resizeEvent() does the rest for us (scrolling, range-checking - // of start-notes and so on...) - resizeEvent( NULL ); + // resizeEvent() does the rest for us (scrolling, range-checking + // of start-notes and so on...) + resizeEvent( NULL ); - // make sure to always get informed about the pattern being destroyed - connect( m_pattern, SIGNAL( destroyedPattern( Pattern* ) ), this, SLOT( hidePattern( Pattern* ) ) ); + // make sure to always get informed about the pattern being destroyed + connect( m_pattern, SIGNAL( destroyedPattern( Pattern* ) ), this, SLOT( hidePattern( Pattern* ) ) ); - connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const Note& ) ), this, SLOT( startRecordNote( const Note& ) ) ); - connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) ); - connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); + connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const Note& ) ), this, SLOT( startRecordNote( const Note& ) ) ); + connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) ); + connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); + } update(); emit currentPatternChanged(); + + m_stepRecorder.setCurrentPattern(newPattern); } @@ -1130,8 +1136,21 @@ int PianoRoll::selectionCount() const // how many notes are selected? -void PianoRoll::keyPressEvent(QKeyEvent* ke ) +void PianoRoll::keyPressEvent(QKeyEvent* ke) { + if(m_stepRecorder.isRecording()) + { + bool handled = m_stepRecorder.keyPressEvent(ke); + if(handled) + { + //return in case event was already handled by stepRecorder, + //this way we allow it to override/bypass other functionality that is not allowed while recording + ke->accept(); //TODO remove? <-- seems to be done already by caller ( PianoView::keyPressEvent) + update(); + return; + } + } + if( hasValidPattern() && ke->modifiers() == Qt::NoModifier ) { const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; @@ -2113,6 +2132,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) NOTE_EDIT_MIN_HEIGHT, height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR - PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT ); + + m_stepRecorderWidget.setBottomMargin(PR_BOTTOM_MARGIN + m_notesEditHeight); repaint(); return; } @@ -3599,8 +3620,22 @@ void PianoRoll::recordAccompany() } } +bool PianoRoll::toggleStepRecording() +{ + if(m_stepRecorder.isRecording()) + { + m_stepRecorder.stop(); + } + else + { + if(hasValidPattern()) + { + m_stepRecorder.start(Engine::getSong()->getPlayPos(Song::Mode_PlayPattern), newNoteLen()); + } + } - + return m_stepRecorder.isRecording();; +} void PianoRoll::stop() @@ -3615,22 +3650,29 @@ void PianoRoll::stop() void PianoRoll::startRecordNote(const Note & n ) { - if( m_recording && hasValidPattern() && + if(hasValidPattern()) + { + if( m_recording && Engine::getSong()->isPlaying() && (Engine::getSong()->playMode() == desiredPlayModeForAccompany() || - Engine::getSong()->playMode() == Song::Mode_PlayPattern )) - { - MidiTime sub; - if( Engine::getSong()->playMode() == Song::Mode_PlaySong ) + Engine::getSong()->playMode() == Song::Mode_PlayPattern )) { - sub = m_pattern->startPosition(); + MidiTime sub; + if( Engine::getSong()->playMode() == Song::Mode_PlaySong ) + { + sub = m_pattern->startPosition(); + } + Note n1( 1, Engine::getSong()->getPlayPos( + Engine::getSong()->playMode() ) - sub, + n.key(), n.getVolume(), n.getPanning() ); + if( n1.pos() >= 0 ) + { + m_recordingNotes << n1; + } } - Note n1( 1, Engine::getSong()->getPlayPos( - Engine::getSong()->playMode() ) - sub, - n.key(), n.getVolume(), n.getPanning() ); - if( n1.pos() >= 0 ) + else if (m_stepRecorder.isRecording()) { - m_recordingNotes << n1; + m_stepRecorder.notePressed(n); } } } @@ -3640,28 +3682,35 @@ void PianoRoll::startRecordNote(const Note & n ) void PianoRoll::finishRecordNote(const Note & n ) { - if( m_recording && hasValidPattern() && - Engine::getSong()->isPlaying() && - ( Engine::getSong()->playMode() == - desiredPlayModeForAccompany() || - Engine::getSong()->playMode() == - Song::Mode_PlayPattern ) ) - { - for( QList::Iterator it = m_recordingNotes.begin(); - it != m_recordingNotes.end(); ++it ) + if(hasValidPattern()) + { + if( m_recording && + Engine::getSong()->isPlaying() && + ( Engine::getSong()->playMode() == + desiredPlayModeForAccompany() || + Engine::getSong()->playMode() == + Song::Mode_PlayPattern ) ) { - if( it->key() == n.key() ) + for( QList::Iterator it = m_recordingNotes.begin(); + it != m_recordingNotes.end(); ++it ) { - Note n1( n.length(), it->pos(), - it->key(), it->getVolume(), - it->getPanning() ); - n1.quantizeLength( quantization() ); - m_pattern->addNote( n1 ); - update(); - m_recordingNotes.erase( it ); - break; + if( it->key() == n.key() ) + { + Note n1( n.length(), it->pos(), + it->key(), it->getVolume(), + it->getPanning() ); + n1.quantizeLength( quantization() ); + m_pattern->addNote( n1 ); + update(); + m_recordingNotes.erase( it ); + break; + } } } + else if (m_stepRecorder.isRecording()) + { + m_stepRecorder.noteReleased(n); + } } } @@ -3671,6 +3720,7 @@ void PianoRoll::finishRecordNote(const Note & n ) void PianoRoll::horScrolled(int new_pos ) { m_currentPosition = new_pos; + m_stepRecorderWidget.setCurrentPosition(m_currentPosition); emit positionChanged( m_currentPosition ); update(); } @@ -4050,6 +4100,8 @@ void PianoRoll::zoomingChanged() assert( m_ppt > 0 ); m_timeLine->setPixelsPerTact( m_ppt ); + m_stepRecorderWidget.setPixelsPerTact( m_ppt ); + update(); } @@ -4061,7 +4113,11 @@ void PianoRoll::quantizeChanged() update(); } - +void PianoRoll::noteLengthChanged() +{ + m_stepRecorder.setStepsLength(newNoteLen()); + update(); +} int PianoRoll::quantization() const @@ -4198,7 +4254,7 @@ Note * PianoRoll::noteUnderMouse() PianoRollWindow::PianoRollWindow() : - Editor(true), + Editor(true, true), m_editor(new PianoRoll()) { setCentralWidget( m_editor ); @@ -4206,6 +4262,7 @@ PianoRollWindow::PianoRollWindow() : m_playAction->setToolTip(tr( "Play/pause current pattern (Space)" ) ); m_recordAction->setToolTip(tr( "Record notes from MIDI-device/channel-piano" ) ); m_recordAccompanyAction->setToolTip( tr( "Record notes from MIDI-device/channel-piano while playing song or BB track" ) ); + m_toggleStepRecordingAction->setToolTip( tr( "Record notes from MIDI-device/channel-piano, one step at the time" ) ); m_stopAction->setToolTip( tr( "Stop playing of current pattern (Space)" ) ); DropToolBar *notesActionsToolBar = addDropToolBarToTop( tr( "Edit actions" ) ); @@ -4352,7 +4409,7 @@ PianoRollWindow::PianoRollWindow() : // Connections connect( m_editor, SIGNAL( currentPatternChanged() ), this, SIGNAL( currentPatternChanged() ) ); - connect( m_editor, SIGNAL( currentPatternChanged() ), this, SLOT( patternRenamed() ) ); + connect( m_editor, SIGNAL( currentPatternChanged() ), this, SLOT( updateAfterPatternChange() ) ); } @@ -4427,6 +4484,8 @@ void PianoRollWindow::stop() void PianoRollWindow::record() { + stopStepRecording(); //step recording mode is mutually exclusive with other record modes + m_editor->record(); } @@ -4435,11 +4494,25 @@ void PianoRollWindow::record() void PianoRollWindow::recordAccompany() { + stopStepRecording(); //step recording mode is mutually exclusive with other record modes + m_editor->recordAccompany(); } +void PianoRollWindow::toggleStepRecording() +{ + if(isRecording()) + { + // step recording mode is mutually exclusive with other record modes + // stop them before starting step recording + stop(); + } + + m_editor->toggleStepRecording(); + updateStepRecordingIcon(); +} void PianoRollWindow::stopRecording() { @@ -4480,6 +4553,11 @@ QSize PianoRollWindow::sizeHint() const +void PianoRollWindow::updateAfterPatternChange() +{ + patternRenamed(); + updateStepRecordingIcon(); //pattern change turn step recording OFF - update icon accordingly +} void PianoRollWindow::patternRenamed() { @@ -4509,3 +4587,24 @@ void PianoRollWindow::focusInEvent( QFocusEvent * event ) // when the window is given focus, also give focus to the actual piano roll m_editor->setFocus( event->reason() ); } + +void PianoRollWindow::stopStepRecording() +{ + if(m_editor->isStepRecording()) + { + m_editor->toggleStepRecording(); + updateStepRecordingIcon(); + } +} + +void PianoRollWindow::updateStepRecordingIcon() +{ + if(m_editor->isStepRecording()) + { + m_toggleStepRecordingAction->setIcon(embed::getIconPixmap("record_step_on")); + } + else + { + m_toggleStepRecordingAction->setIcon(embed::getIconPixmap("record_step_off")); + } +} diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index a2e52e200a6..92a5c5fa5b4 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -654,7 +654,7 @@ ComboBoxModel *SongEditor::zoomingModel() const SongEditorWindow::SongEditorWindow(Song* song) : - Editor(Engine::mixer()->audioDev()->supportsCapture()), + Editor(Engine::mixer()->audioDev()->supportsCapture(), false), m_editor(new SongEditor(song)), m_crtlAction( NULL ) { diff --git a/src/gui/widgets/StepRecorderWidget.cpp b/src/gui/widgets/StepRecorderWidget.cpp new file mode 100644 index 00000000000..06d4b5661dc --- /dev/null +++ b/src/gui/widgets/StepRecorderWidget.cpp @@ -0,0 +1,145 @@ +/* + * StepRecoderWidget.cpp - widget that provide gui markers for step recording + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "StepRecorderWidget.h" + +StepRecorderWidget::StepRecorderWidget( + QWidget * parent, + const int ppt, + const int margin_top, + const int margin_bottom, + const int margin_left, + const int margin_right) : + QWidget(parent), + m_margin_top(margin_top), + m_margin_bottom(margin_bottom), + m_margin_left(margin_left), + m_margin_right(margin_right) +{ + const QColor baseColor = QColor(255, 0, 0);// QColor(204, 163, 0); // Orange + m_colorLineEnd = baseColor.lighter(150); + m_colorLineStart = baseColor.darker(120); + + setAttribute(Qt::WA_NoSystemBackground, true); + setPixelsPerTact(ppt); + + m_top = m_margin_top; + m_left = m_margin_left; +} + +void StepRecorderWidget::setPixelsPerTact(int _ppt ) +{ + m_ppt = _ppt; +} + +void StepRecorderWidget::setCurrentPosition(MidiTime currentPosition) +{ + m_currentPosition = currentPosition; +} + +void StepRecorderWidget::setBottomMargin(const int margin_bottom) +{ + m_margin_bottom = margin_bottom; +} + +void StepRecorderWidget::setStartPosition(MidiTime pos) +{ + m_curStepStartPos = pos; +} +void StepRecorderWidget::setEndPosition(MidiTime pos) +{ + m_curStepEndPos = pos; +} + +void StepRecorderWidget::setStepsLength(MidiTime stepsLength) +{ + m_stepsLength = stepsLength; +} + +void StepRecorderWidget::paintEvent( QPaintEvent * pe ) +{ + QPainter painter( this ); + + updateBoundaries(); + + move(0, 0); + + //draw steps ruler + painter.setPen(m_colorLineEnd); + + MidiTime curPos = m_curStepEndPos; + int x = xCoordOfTick(curPos); + while(x <= m_right) + { + const int w = 2; + const int h = 4; + painter.drawRect(x - 1, m_top, w, h); + curPos += m_stepsLength; + x = xCoordOfTick(curPos); + } + + //draw current step start/end position lines + if(m_curStepStartPos != m_curStepEndPos) + { + drawVerLine(&painter, m_curStepStartPos, m_colorLineStart, m_top, m_bottom); + } + + drawVerLine(&painter, m_curStepEndPos, m_colorLineEnd, m_top, m_bottom); + + //if the line is adjacent to the keyboard at the left - it cannot be seen. + //add another line to make it clearer + if(m_curStepEndPos == 0) + { + drawVerLine(&painter, xCoordOfTick(m_curStepEndPos) + 1, m_colorLineEnd, m_top, m_bottom); + } +} + +int StepRecorderWidget::xCoordOfTick(int tick) +{ + return m_margin_left + ((tick - m_currentPosition) * m_ppt / MidiTime::ticksPerTact() ); +} + + +void StepRecorderWidget::drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom) +{ + if(x >= m_margin_left && x <= (width() - m_margin_right)) + { + painter->setPen(color); + painter->drawLine( x, top, x, bottom ); + } +} + +void StepRecorderWidget::drawVerLine(QPainter* painter, const MidiTime& pos, const QColor& color, int top, int bottom) +{ + drawVerLine(painter, xCoordOfTick(pos), color, top, bottom); +} + +void StepRecorderWidget::updateBoundaries() +{ + setFixedSize(parentWidget()->size()); + + m_bottom = height() - m_margin_bottom; + m_right = width() - m_margin_top; + + //(no need to change top and left - they are static) +} + From e72994f174b39aa3b04655a0753acf315d04cde7 Mon Sep 17 00:00:00 2001 From: nomzu Date: Fri, 24 Aug 2018 15:40:55 +0300 Subject: [PATCH 02/11] Do not add current recorded step-notes to pattern until released (allows editing while is step-recording mode) This change allows editing while is step-recording mode. The issue with editing was with manipulating just the current step being recorded (while pressing the notes), which might caused intervation to StepRecorder logic. The resolution in this commit is to keep the current step notes in a seperate list inside StepRecorder (instead inside Pattern). This way, these sepcific notes cannot be modified using the regular actions (e.g. right click to delete). Due to this change, PianoRoll needs to paint these notes expliclty besides the other notes in Pattern. (which also turned nicely since it made it easy to paint them in different color. This change also include various refactoring inside StepRecorder.cpp --- include/StepRecorder.h | 51 +++++--- src/core/StepRecorder.cpp | 216 +++++++++++++--------------------- src/gui/editors/PianoRoll.cpp | 35 ++++++ 3 files changed, 153 insertions(+), 149 deletions(-) diff --git a/include/StepRecorder.h b/include/StepRecorder.h index b52ddf0bbf7..3ac40bf1a32 100644 --- a/include/StepRecorder.h +++ b/include/StepRecorder.h @@ -49,12 +49,19 @@ class StepRecorder : public QObject bool mousePressEvent(QMouseEvent* ke); void setCurrentPattern(Pattern* newPattern); void setStepsLength(const MidiTime& newLength); + + QVector getCurStepNotes(); - inline bool isRecording() const + bool isRecording() const { return m_isRecording; } + QColor curStepNoteColor() const + { + return QColor(245,3,139); // radiant pink + } + private slots: void removeNotesReleasedForTooLong(); @@ -62,8 +69,6 @@ class StepRecorder : public QObject void stepForwards(); void stepBackwards(); - void removeCurStepNotesFromPattern(QList* removedNotesCopy); - void applyStep(); void dismissStep(); void prepareNewStep(); @@ -73,7 +78,7 @@ class StepRecorder : public QObject void updateCurStepNotes(); void updateWidget(); - bool isKeyEventDisallowedDuringStepRecording(QKeyEvent* ke); + bool allCurStepNotesReleased(); PianoRoll& m_pianoRoll; StepRecorderWidget& m_stepRecorderWidget; @@ -90,29 +95,47 @@ class StepRecorder : public QObject Pattern* m_pattern; - class ReleasedNote + class StepNote { public: - ReleasedNote() {}; + StepNote(const Note & note) : m_note(note), m_pressed(true) {}; - void setNote(Note* m_note) + void setPressed() { - this->m_note = m_note; - timer.start(); + m_pressed = true; + } + + void setReleased() + { + m_pressed = false; + releasedTimer.start(); } int timeSinceReleased() { - return timer.elapsed(); + return releasedTimer.elapsed(); } - Note* m_note; + bool isPressed() const + { + return m_pressed; + } + + bool isReleased() const + { + return !m_pressed; + } + + Note m_note; private: - QTime timer; + bool m_pressed; + QTime releasedTimer; } ; - QHash m_pressedNotes; - QHash m_releasedNotes; + + QVector m_curStepNotes; // contains the current recorded step notes (i.e. while user still press the notes; before they are applied to the pattern) + + StepNote* findCurStepNote(const int key); bool m_isStepInProgress = false; }; diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index 87bbafe2b93..164c29448d3 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -99,15 +99,6 @@ void StepRecorder::stop() void StepRecorder::notePressed(const Note & n) { - const int key = n.key(); - - if(m_pressedNotes.contains(key)) - { - //if note already pressed, return. this should not happen and this condition added just for robustness. - //in case this turns to be a valid case in the future, we should update note's velocity instead - return ; - } - //if this is the first pressed note in step, advance position if(!m_isStepInProgress) { @@ -117,38 +108,30 @@ void StepRecorder::notePressed(const Note & n) stepForwards(); } - Note noteToAdd(m_stepsLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning() ); - - Note* newNote = m_pattern->addNote( noteToAdd, false); - m_pianoRoll.update(); - - //remove note from released if it is in there - if(m_releasedNotes.contains(key)) + StepNote* stepNote = findCurStepNote(n.key()); + if(stepNote == nullptr) { - ReleasedNote& rn = m_releasedNotes[key]; - m_pattern->removeNote(rn.m_note); - m_releasedNotes.remove(key); + m_curStepNotes.append(new StepNote(Note(m_stepsLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning()))); + m_pianoRoll.update(); + } + else if (stepNote->isReleased()) + { + stepNote->setPressed(); } - - //add note to pressed list - m_pressedNotes[key] = newNote; } + void StepRecorder::noteReleased(const Note & n) { DBG_PRINT("%s: key[%d]... \n", __FUNCTION__, n.key()); - const int key = n.key(); - if(m_pressedNotes.contains(key)) - { - //move note from pressed list to released list - Note* note = m_pressedNotes[key]; - m_pressedNotes.remove(key); + StepNote* stepNote = findCurStepNote(n.key()); - ReleasedNote& rn = m_releasedNotes[key]; - rn.setNote(note); + if(stepNote != nullptr && stepNote->isPressed()) + { + stepNote->setReleased(); //if m_updateReleasedTimer is not already active, activate it - //(when activated, the timer will re-set itself as long as there are notes in m_releasedNotes + //(when activated, the timer will re-set itself as long as there are released notes) if(!m_updateReleasedTimer.isActive()) { m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS); @@ -157,7 +140,7 @@ void StepRecorder::noteReleased(const Note & n) DBG_PRINT("%s: key[%d] pressed->released \n", __FUNCTION__, key); //check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step - if(m_pressedNotes.count() == 0) + if(allCurStepNotesReleased()) { if(m_curStepLength > 0) { @@ -196,24 +179,8 @@ bool StepRecorder::keyPressEvent(QKeyEvent* ke) event_handled = true; break; } - - case Qt::Key_Escape: - { - if(m_isStepInProgress) - { - dismissStep(); - } - - event_handled = true; - break; - } } - if(!event_handled && isKeyEventDisallowedDuringStepRecording(ke)) - { - //TODO: display message to user that this key event is not allowed during step recording - event_handled = true; - } return event_handled; } @@ -232,6 +199,21 @@ void StepRecorder::setStepsLength(const MidiTime& newLength) updateWidget(); } +QVector StepRecorder::getCurStepNotes() +{ + QVector notes; + + if(m_isStepInProgress) + { + for(StepNote* stepNote: m_curStepNotes) + { + notes.append(&stepNote->m_note); + } + } + + return notes; +} + void StepRecorder::stepForwards() { if(m_isStepInProgress) @@ -272,46 +254,15 @@ void StepRecorder::stepBackwards() updateWidget(); } -void StepRecorder::removeCurStepNotesFromPattern(QList* removedNotesCopy) -{ - // Removing notes from pattern also deletes their instances. - // in order to recover them later, they a copy of them is added to an optional list - foreach (Note* note, m_pressedNotes) - { - if(removedNotesCopy != nullptr) - { - removedNotesCopy->append(*note); - } - - m_pattern->removeNote(note); - } - - foreach (const ReleasedNote& rn, m_releasedNotes) - { - if(removedNotesCopy != nullptr) - { - removedNotesCopy->append(*rn.m_note); - } - - m_pattern->removeNote(rn.m_note); - } -} - void StepRecorder::applyStep() { DBG_PRINT("%s\n", __FUNCTION__); - // in order to allow "undo" of this step, we remove all notes from pattern, add checkpoint and re-add them - // (an alternative would be to add checkpoint in prepareNewStep(), but then, in case the step is dismissed - // the added checkpoint would be "empty" (i.e. not related to any modification)) - QList removedNotesCopy; - removeCurStepNotesFromPattern(&removedNotesCopy); - m_pattern->addJournalCheckPoint(); - foreach (const Note& note, removedNotesCopy) + for (const StepNote* stepNote : m_curStepNotes) { - m_pattern->addNote(note, false); + m_pattern->addNote(stepNote->m_note, false); } m_pattern->rearrangeAllNotes(); @@ -331,7 +282,6 @@ void StepRecorder::dismissStep() return; } - removeCurStepNotesFromPattern(nullptr); prepareNewStep(); } @@ -339,8 +289,11 @@ void StepRecorder::prepareNewStep() { DBG_PRINT("%s\n", __FUNCTION__); - m_releasedNotes.clear(); - m_pressedNotes.clear(); + for(StepNote* stepNote : m_curStepNotes) + { + delete stepNote; + } + m_curStepNotes.clear(); m_isStepInProgress = false; @@ -367,29 +320,24 @@ void StepRecorder::removeNotesReleasedForTooLong() { DBG_PRINT("%s\n", __FUNCTION__); - if(m_releasedNotes.count() == 0) - { - m_updateReleasedTimer.stop(); - } - else + int nextTimout = INT_MAX; + bool notesRemoved = false; + + QMutableVectorIterator itr(m_curStepNotes); + while (itr.hasNext()) { - int nextTimout = INT_MAX; - bool notesRemoved = false; + StepNote* stepNote = itr.next(); - QMutableHashIterator itr(m_releasedNotes); - while (itr.hasNext()) + if(stepNote->isReleased()) { - itr.next(); - ReleasedNote& rn = itr.value(); - - DBG_PRINT("key[%d]: timeSinceReleased:[%d]\n", rn.m_note->key(), rn.timeSinceReleased()); + DBG_PRINT("key[%d]: timeSinceReleased:[%d]\n", stepNote->m_note.key(), stepNote->timeSinceReleased()); - const int timeSinceReleased = rn.timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout + const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS) { DBG_PRINT("removed...\n"); - m_pattern->removeNote(rn.m_note); + delete stepNote; itr.remove(); notesRemoved = true; } @@ -398,18 +346,22 @@ void StepRecorder::removeNotesReleasedForTooLong() nextTimout = min(nextTimout, REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS - timeSinceReleased); } } + } - if(notesRemoved) - { - m_pianoRoll.update(); - } - - if(nextTimout != INT_MAX) - { - m_updateReleasedTimer.start(nextTimout); - } + if(notesRemoved) + { + m_pianoRoll.update(); } + if(nextTimout != INT_MAX) + { + m_updateReleasedTimer.start(nextTimout); + } + else + { + // no released note found for next timout, stop timer + m_updateReleasedTimer.stop(); + } } MidiTime StepRecorder::getCurStepEndPos() @@ -419,17 +371,9 @@ MidiTime StepRecorder::getCurStepEndPos() void StepRecorder::updateCurStepNotes() { - foreach (Note* note, m_pressedNotes) + for (StepNote* stepNote : m_curStepNotes) { - note->setLength(m_curStepLength); - note->setPos(m_curStepStartPos); - } - - //need to update also released notes, since they might be recorded if not released for too long - foreach (const ReleasedNote& rn, m_releasedNotes) - { - rn.m_note->setLength(m_curStepLength); - rn.m_note->setPos(m_curStepStartPos); + stepNote->m_note.setLength(m_curStepLength); } } @@ -440,26 +384,28 @@ void StepRecorder::updateWidget() m_stepRecorderWidget.setStepsLength(m_stepsLength); } -//list key pressed to be ignored during step recording (mostly - edit note actions) -//having this hardcoded is not very elegant nor flexible (consider adding new edot action to piano roll - developer must add to this list as well to be ignored) -//a better way would be to have an object to handle all "key to action" event and query it on runtime based (i.e. m_pianoRoll.keyEventHandler.getEditKeyEvents()); -bool StepRecorder::isKeyEventDisallowedDuringStepRecording(QKeyEvent* ke) +bool StepRecorder::allCurStepNotesReleased() { - int key = ke->key(); - - if((key == Qt::Key_Up || key == Qt::Key_Down) && (ke->modifiers() != Qt::NoModifier)) - { - //prevent editing but allow scrolling (no modifiers) - return true; - } - else if (key == Qt::Key_A && ke->modifiers() & Qt::ControlModifier) + for (const StepNote* stepNote : m_curStepNotes) { - return true; + if(stepNote->isPressed()) + { + return false; + } } - else if (key == Qt::Key_Control || key == Qt::Key_Delete) + + return true; +} + +StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key) +{ + for (StepNote* stepNote : m_curStepNotes) { - return true; + if(stepNote->m_note.key() == key) + { + return stepNote; + } } - return false; -} \ No newline at end of file + return nullptr; +} diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index b1d94146748..aaa578dcd36 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -3224,6 +3224,41 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } } + //draw current step recording notes + for( const Note *note : m_stepRecorder.getCurStepNotes() ) + { + int len_ticks = note->length(); + + if( len_ticks == 0 ) + { + continue; + } + + const int key = note->key() - m_startKey + 1; + + int pos_ticks = note->pos(); + + int note_width = len_ticks * m_ppt / MidiTime::ticksPerTact(); + const int x = ( pos_ticks - m_currentPosition ) * + m_ppt / MidiTime::ticksPerTact(); + // skip this note if not in visible area at all + if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) ) + { + continue; + } + + // is the note in visible area? + if( key > 0 && key <= visible_keys ) + { + + // we've done and checked all, let's draw the note + drawNoteRect( p, x + WHITE_KEY_WIDTH, + y_base - key * KEY_LINE_HEIGHT, + note_width, note, m_stepRecorder.curStepNoteColor(), noteTextColor(), selectedNoteColor(), + noteOpacity(), noteBorders(), drawNoteNames ); + } + } + p.setPen( QPen( noteColor(), NOTE_EDIT_LINE_WIDTH + 2 ) ); p.drawPoints( editHandles ); From a6c4de43b4b3c4eaf5fd29fb91e271c015819b8b Mon Sep 17 00:00:00 2001 From: nomzu Date: Fri, 24 Aug 2018 16:25:44 +0300 Subject: [PATCH 03/11] cleanup - removal of debug prints --- src/core/StepRecorder.cpp | 47 --------------------------------------- 1 file changed, 47 deletions(-) diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index 164c29448d3..a4d31dd5c1b 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -28,42 +28,13 @@ using std::min; using std::max; -//for debugging: uncomment to create a win32 console, and enable DBG_PRINT -//#define DEBUG_STEP_RECORDER - -#ifdef DEBUG_STEP_RECORDER - #define DEBUG_CREATE_WIN32_CONSOLE - #define DRBUG_ENABLE_PRINTS -#endif //DEBUG_STEP_RECORDER - -#ifdef DEBUG_CREATE_WIN32_CONSOLE -#include -#endif - -#ifdef DRBUG_ENABLE_PRINTS -#include - #define DBG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) -#else - #define DBG_PRINT(fmt, ...) -#endif - const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70; - StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget): m_pianoRoll(pianoRoll), m_stepRecorderWidget(stepRecorderWidget) { m_stepRecorderWidget.hide(); - -#if defined(DEBUG_CREATE_WIN32_CONSOLE) && defined(_WIN32) - //create win32 console and attach it for output - if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) - { - freopen("CONOUT$", "w", stdout); - freopen("CONOUT$", "w", stderr); - } -#endif } void StepRecorder::initialize() @@ -122,8 +93,6 @@ void StepRecorder::notePressed(const Note & n) void StepRecorder::noteReleased(const Note & n) { - DBG_PRINT("%s: key[%d]... \n", __FUNCTION__, n.key()); - StepNote* stepNote = findCurStepNote(n.key()); if(stepNote != nullptr && stepNote->isPressed()) @@ -137,8 +106,6 @@ void StepRecorder::noteReleased(const Note & n) m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS); } - DBG_PRINT("%s: key[%d] pressed->released \n", __FUNCTION__, key); - //check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step if(allCurStepNotesReleased()) { @@ -256,8 +223,6 @@ void StepRecorder::stepBackwards() void StepRecorder::applyStep() { - DBG_PRINT("%s\n", __FUNCTION__); - m_pattern->addJournalCheckPoint(); for (const StepNote* stepNote : m_curStepNotes) @@ -275,8 +240,6 @@ void StepRecorder::applyStep() void StepRecorder::dismissStep() { - DBG_PRINT("%s\n", __FUNCTION__); - if(!m_isStepInProgress) { return; @@ -287,8 +250,6 @@ void StepRecorder::dismissStep() void StepRecorder::prepareNewStep() { - DBG_PRINT("%s\n", __FUNCTION__); - for(StepNote* stepNote : m_curStepNotes) { delete stepNote; @@ -305,8 +266,6 @@ void StepRecorder::prepareNewStep() void StepRecorder::setCurrentPattern( Pattern* newPattern ) { - DBG_PRINT("%s\n", __FUNCTION__); - if(m_pattern != NULL && m_pattern != newPattern) { // remove any unsaved notes from old pattern @@ -318,8 +277,6 @@ void StepRecorder::setCurrentPattern( Pattern* newPattern ) void StepRecorder::removeNotesReleasedForTooLong() { - DBG_PRINT("%s\n", __FUNCTION__); - int nextTimout = INT_MAX; bool notesRemoved = false; @@ -330,13 +287,9 @@ void StepRecorder::removeNotesReleasedForTooLong() if(stepNote->isReleased()) { - DBG_PRINT("key[%d]: timeSinceReleased:[%d]\n", stepNote->m_note.key(), stepNote->timeSinceReleased()); - const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS) { - DBG_PRINT("removed...\n"); - delete stepNote; itr.remove(); notesRemoved = true; From 8db9d3adf8a43098bdd2d0ac0a88d7ec4411dfb9 Mon Sep 17 00:00:00 2001 From: nomzu Date: Sat, 25 Aug 2018 22:08:11 +0300 Subject: [PATCH 04/11] 1. prevent playing note when added using mouse while step-recording 2. add usage-hint when toggling step-recoprding on 1. prevent playing note when added using mouse while step-recording This change is aligned to current behaviour during regular recording 2. add usage-hint when toggling step-recoprding on. will show "Move recording curser using arrows" on bottom of main window (3. change space indentation into tabs) --- include/StepRecorder.h | 132 ++++++++++++------------- include/StepRecorderWidget.h | 58 +++++------ src/core/StepRecorder.cpp | 18 ++-- src/gui/editors/PianoRoll.cpp | 2 +- src/gui/widgets/StepRecorderWidget.cpp | 28 ++++-- 5 files changed, 126 insertions(+), 112 deletions(-) diff --git a/include/StepRecorder.h b/include/StepRecorder.h index 3ac40bf1a32..504ee782c20 100644 --- a/include/StepRecorder.h +++ b/include/StepRecorder.h @@ -35,109 +35,109 @@ class StepRecorderWidget; class StepRecorder : public QObject { - Q_OBJECT - - public: - StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget); - - void initialize(); - void start(const MidiTime& currentPosition,const MidiTime& stepLength); - void stop(); - void notePressed(const Note & n); - void noteReleased(const Note & n); - bool keyPressEvent(QKeyEvent* ke); - bool mousePressEvent(QMouseEvent* ke); - void setCurrentPattern(Pattern* newPattern); - void setStepsLength(const MidiTime& newLength); - - QVector getCurStepNotes(); - - bool isRecording() const - { - return m_isRecording; - } + Q_OBJECT + + public: + StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget); + + void initialize(); + void start(const MidiTime& currentPosition,const MidiTime& stepLength); + void stop(); + void notePressed(const Note & n); + void noteReleased(const Note & n); + bool keyPressEvent(QKeyEvent* ke); + bool mousePressEvent(QMouseEvent* ke); + void setCurrentPattern(Pattern* newPattern); + void setStepsLength(const MidiTime& newLength); + + QVector getCurStepNotes(); + + bool isRecording() const + { + return m_isRecording; + } - QColor curStepNoteColor() const - { - return QColor(245,3,139); // radiant pink - } + QColor curStepNoteColor() const + { + return QColor(245,3,139); // radiant pink + } - private slots: - void removeNotesReleasedForTooLong(); + private slots: + void removeNotesReleasedForTooLong(); - private: - void stepForwards(); - void stepBackwards(); + private: + void stepForwards(); + void stepBackwards(); - void applyStep(); - void dismissStep(); - void prepareNewStep(); + void applyStep(); + void dismissStep(); + void prepareNewStep(); - MidiTime getCurStepEndPos(); + MidiTime getCurStepEndPos(); - void updateCurStepNotes(); - void updateWidget(); + void updateCurStepNotes(); + void updateWidget(); - bool allCurStepNotesReleased(); + bool allCurStepNotesReleased(); - PianoRoll& m_pianoRoll; + PianoRoll& m_pianoRoll; StepRecorderWidget& m_stepRecorderWidget; bool m_isRecording = false; MidiTime m_curStepStartPos = 0; MidiTime m_curStepEndPos = 0; - MidiTime m_stepsLength; - MidiTime m_curStepLength; // current step length refers to the step currently recorded. it may defer from m_stepsLength - // since the user can make current step larger - - QTimer m_updateReleasedTimer; - - Pattern* m_pattern; + MidiTime m_stepsLength; + MidiTime m_curStepLength; // current step length refers to the step currently recorded. it may defer from m_stepsLength + // since the user can make current step larger + + QTimer m_updateReleasedTimer; + + Pattern* m_pattern; class StepNote { public: - StepNote(const Note & note) : m_note(note), m_pressed(true) {}; + StepNote(const Note & note) : m_note(note), m_pressed(true) {}; - void setPressed() - { - m_pressed = true; - } + void setPressed() + { + m_pressed = true; + } - void setReleased() - { + void setReleased() + { m_pressed = false; releasedTimer.start(); - } + } int timeSinceReleased() { return releasedTimer.elapsed(); } - bool isPressed() const - { - return m_pressed; - } + bool isPressed() const + { + return m_pressed; + } - bool isReleased() const - { - return !m_pressed; - } + bool isReleased() const + { + return !m_pressed; + } - Note m_note; + Note m_note; private: - bool m_pressed; + bool m_pressed; QTime releasedTimer; } ; - QVector m_curStepNotes; // contains the current recorded step notes (i.e. while user still press the notes; before they are applied to the pattern) + QVector m_curStepNotes; // contains the current recorded step notes (i.e. while user still press the notes; before they are applied to the pattern) - StepNote* findCurStepNote(const int key); + StepNote* findCurStepNote(const int key); - bool m_isStepInProgress = false; + bool m_isStepInProgress = false; }; #endif //STEP_RECORDER_H \ No newline at end of file diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h index fe9db4a41d4..6e9280c8573 100644 --- a/include/StepRecorderWidget.h +++ b/include/StepRecorderWidget.h @@ -33,52 +33,54 @@ class StepRecorderWidget : public QWidget { public: StepRecorderWidget( - QWidget * parent, - const int _ppt, - const int margin_top, - const int margin_bottom, - const int margin_left, - const int margin_right); + QWidget * parent, + const int _ppt, + const int margin_top, + const int margin_bottom, + const int margin_left, + const int margin_right); //API used by PianoRoll void setPixelsPerTact(int _ppt); void setCurrentPosition(MidiTime currentPosition); - void setBottomMargin(const int margin_bottom); + void setBottomMargin(const int margin_bottom); //API used by StepRecorder - void setStepsLength(MidiTime stepsLength); + void setStepsLength(MidiTime stepsLength); void setStartPosition(MidiTime pos); - void setEndPosition(MidiTime pos); + void setEndPosition(MidiTime pos); + + void showHint(); private: virtual void paintEvent(QPaintEvent * pe); - - int xCoordOfTick(int tick); - - void drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom); - void drawVerLine(QPainter* painter, const MidiTime& pos, const QColor& color, int top, int bottom); - - void updateBoundaries(); + + int xCoordOfTick(int tick); + + void drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom); + void drawVerLine(QPainter* painter, const MidiTime& pos, const QColor& color, int top, int bottom); + + void updateBoundaries(); - MidiTime m_stepsLength; + MidiTime m_stepsLength; MidiTime m_curStepStartPos; - MidiTime m_curStepEndPos; + MidiTime m_curStepEndPos; int m_ppt; // pixels per tact MidiTime m_currentPosition; // current position showed by on PianoRoll QColor m_colorLineStart; - QColor m_colorLineEnd; + QColor m_colorLineEnd; - // boundaries within piano roll window - int m_top; - int m_bottom; - int m_left; - int m_right; + // boundaries within piano roll window + int m_top; + int m_bottom; + int m_left; + int m_right; - const int m_margin_top; - int m_margin_bottom; // not const since can change on resize of edit-note area - const int m_margin_left; - const int m_margin_right; + const int m_margin_top; + int m_margin_bottom; // not const since can change on resize of edit-note area + const int m_margin_left; + const int m_margin_right; } ; #endif //_STEP_RECOREDER_WIDGET diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index a4d31dd5c1b..c8d2a5f0eec 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -34,7 +34,7 @@ StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorde m_pianoRoll(pianoRoll), m_stepRecorderWidget(stepRecorderWidget) { - m_stepRecorderWidget.hide(); + m_stepRecorderWidget.hide(); } void StepRecorder::initialize() @@ -59,7 +59,9 @@ void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLe m_stepRecorderWidget.show(); - prepareNewStep(); + m_stepRecorderWidget.showHint(); + + prepareNewStep(); } void StepRecorder::stop() @@ -266,13 +268,13 @@ void StepRecorder::prepareNewStep() void StepRecorder::setCurrentPattern( Pattern* newPattern ) { - if(m_pattern != NULL && m_pattern != newPattern) - { - // remove any unsaved notes from old pattern - dismissStep(); - } + if(m_pattern != NULL && m_pattern != newPattern) + { + // remove any unsaved notes from old pattern + dismissStep(); + } - m_pattern = newPattern; + m_pattern = newPattern; } void StepRecorder::removeNotesReleasedForTooLong() diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index aaa578dcd36..69f090f3e31 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -1899,7 +1899,7 @@ void PianoRoll::testPlayNote( Note * n ) { m_lastKey = n->key(); - if( ! n->isPlaying() && ! m_recording ) + if( ! n->isPlaying() && ! m_recording && ! m_stepRecorder.isRecording()) { n->setIsPlaying( true ); diff --git a/src/gui/widgets/StepRecorderWidget.cpp b/src/gui/widgets/StepRecorderWidget.cpp index 06d4b5661dc..b312ff2fe5a 100644 --- a/src/gui/widgets/StepRecorderWidget.cpp +++ b/src/gui/widgets/StepRecorderWidget.cpp @@ -21,19 +21,21 @@ */ #include "StepRecorderWidget.h" +#include "TextFloat.h" +#include "embed.h" StepRecorderWidget::StepRecorderWidget( - QWidget * parent, - const int ppt, - const int margin_top, - const int margin_bottom, - const int margin_left, - const int margin_right) : + QWidget * parent, + const int ppt, + const int margin_top, + const int margin_bottom, + const int margin_left, + const int margin_right) : QWidget(parent), m_margin_top(margin_top), - m_margin_bottom(margin_bottom), - m_margin_left(margin_left), - m_margin_right(margin_right) + m_margin_bottom(margin_bottom), + m_margin_left(margin_left), + m_margin_right(margin_right) { const QColor baseColor = QColor(255, 0, 0);// QColor(204, 163, 0); // Orange m_colorLineEnd = baseColor.lighter(150); @@ -65,11 +67,19 @@ void StepRecorderWidget::setStartPosition(MidiTime pos) { m_curStepStartPos = pos; } + void StepRecorderWidget::setEndPosition(MidiTime pos) { m_curStepEndPos = pos; } +void StepRecorderWidget::showHint() +{ + TextFloat::displayMessage( tr( "Hint" ), + tr( "Move recording curser using arrows"), + embed::getIconPixmap( "hint" )); +} + void StepRecorderWidget::setStepsLength(MidiTime stepsLength) { m_stepsLength = stepsLength; From 056d9d7d74be13591fcb42b70b8535181cc68dfa Mon Sep 17 00:00:00 2001 From: nomzu Date: Wed, 29 Aug 2018 19:33:51 +0300 Subject: [PATCH 05/11] Auto-scroll to step-recording cursor position Auto-scroll to step-recording cursor position. (know limitation: only works if position is within pattern's current length (i.e. works fine for the usual use case)) --- include/PianoRoll.h | 1 + include/StepRecorderWidget.h | 6 ++++++ src/gui/editors/PianoRoll.cpp | 11 +++++++++++ src/gui/widgets/StepRecorderWidget.cpp | 1 + 4 files changed, 19 insertions(+) diff --git a/include/PianoRoll.h b/include/PianoRoll.h index a26f7a46342..f3b5440e7f6 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -213,6 +213,7 @@ protected slots: void updatePosition(const MidiTime & t ); void updatePositionAccompany(const MidiTime & t ); + void updatePositionStepRecording(const MidiTime & t ); void zoomingChanged(); void quantizeChanged(); diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h index 6e9280c8573..7f7123247eb 100644 --- a/include/StepRecorderWidget.h +++ b/include/StepRecorderWidget.h @@ -31,6 +31,8 @@ class StepRecorderWidget : public QWidget { + Q_OBJECT + public: StepRecorderWidget( QWidget * parent, @@ -51,6 +53,7 @@ class StepRecorderWidget : public QWidget void setEndPosition(MidiTime pos); void showHint(); + private: virtual void paintEvent(QPaintEvent * pe); @@ -81,6 +84,9 @@ class StepRecorderWidget : public QWidget int m_margin_bottom; // not const since can change on resize of edit-note area const int m_margin_left; const int m_margin_right; + +signals: + void positionChanged( const MidiTime & _t ); } ; #endif //_STEP_RECOREDER_WIDGET diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 69f090f3e31..9330be510b8 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -325,6 +325,10 @@ PianoRoll::PianoRoll() : m_timeLine, SLOT( updatePosition( const MidiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePosition( const MidiTime & ) ) ); + + //update timeline when in step-recording mode + connect( &m_stepRecorderWidget, SIGNAL( positionChanged( const MidiTime & ) ), + this, SLOT( updatePositionStepRecording( const MidiTime & ) ) ); // update timeline when in record-accompany mode connect( Engine::getSong()->getPlayPos( Song::Mode_PlaySong ).m_timeLine, @@ -4126,6 +4130,13 @@ void PianoRoll::updatePositionAccompany( const MidiTime & t ) } +void PianoRoll::updatePositionStepRecording( const MidiTime & t ) +{ + if( m_stepRecorder.isRecording() ) + { + autoScroll( t ); + } +} void PianoRoll::zoomingChanged() diff --git a/src/gui/widgets/StepRecorderWidget.cpp b/src/gui/widgets/StepRecorderWidget.cpp index b312ff2fe5a..0d554da797e 100644 --- a/src/gui/widgets/StepRecorderWidget.cpp +++ b/src/gui/widgets/StepRecorderWidget.cpp @@ -71,6 +71,7 @@ void StepRecorderWidget::setStartPosition(MidiTime pos) void StepRecorderWidget::setEndPosition(MidiTime pos) { m_curStepEndPos = pos; + emit positionChanged( m_curStepEndPos ); } void StepRecorderWidget::showHint() From 6840bd5fb33887f6ed3ee9e6d24dae0a67f1f734 Mon Sep 17 00:00:00 2001 From: nomzu Date: Fri, 31 Aug 2018 12:55:59 +0300 Subject: [PATCH 06/11] cleanup and formatting fixes according to code review --- include/StepRecorderWidget.h | 30 +++++------ src/core/CMakeLists.txt | 2 +- src/core/StepRecorder.cpp | 5 +- src/gui/editors/Editor.cpp | 2 +- src/gui/editors/PianoRoll.cpp | 72 ++++++++++++++------------ src/gui/widgets/StepRecorderWidget.cpp | 49 +++++++++--------- 6 files changed, 81 insertions(+), 79 deletions(-) diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h index 7f7123247eb..14255765a8a 100644 --- a/include/StepRecorderWidget.h +++ b/include/StepRecorderWidget.h @@ -19,8 +19,8 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef _STEP_RECOREDER_WIDGET -#define _STEP_RECOREDER_WIDGET +#ifndef STEP_RECOREDER_WIDGET_H +#define STEP_RECOREDER_WIDGET_H #include "lmms_basics.h" #include "Note.h" @@ -36,16 +36,16 @@ class StepRecorderWidget : public QWidget public: StepRecorderWidget( QWidget * parent, - const int _ppt, - const int margin_top, - const int margin_bottom, - const int margin_left, - const int margin_right); + const int ppt, + const int marginTop, + const int marginBottom, + const int marginLeft, + const int marginRight); //API used by PianoRoll - void setPixelsPerTact(int _ppt); + void setPixelsPerTact(int ppt); void setCurrentPosition(MidiTime currentPosition); - void setBottomMargin(const int margin_bottom); + void setBottomMargin(const int marginBottom); //API used by StepRecorder void setStepsLength(MidiTime stepsLength); @@ -80,13 +80,13 @@ class StepRecorderWidget : public QWidget int m_left; int m_right; - const int m_margin_top; - int m_margin_bottom; // not const since can change on resize of edit-note area - const int m_margin_left; - const int m_margin_right; + const int m_marginTop; + int m_marginBottom; // not const since can change on resize of edit-note area + const int m_marginLeft; + const int m_marginRight; signals: - void positionChanged( const MidiTime & _t ); + void positionChanged(const MidiTime & t); } ; -#endif //_STEP_RECOREDER_WIDGET +#endif //STEP_RECOREDER_WIDGET_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a1e66b0a01d..7870415f971 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -97,6 +97,6 @@ set(LMMS_SRCS core/midi/MidiPort.cpp core/midi/MidiTime.cpp core/midi/MidiWinMM.cpp - + PARENT_SCOPE ) diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index c8d2a5f0eec..765710ce0b8 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -39,7 +39,7 @@ StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorde void StepRecorder::initialize() { - connect(&m_updateReleasedTimer, SIGNAL( timeout() ), this, SLOT( removeNotesReleasedForTooLong() ) ); + connect(&m_updateReleasedTimer, SIGNAL(timeout()), this, SLOT(removeNotesReleasedForTooLong())); } void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLength) @@ -270,7 +270,6 @@ void StepRecorder::setCurrentPattern( Pattern* newPattern ) { if(m_pattern != NULL && m_pattern != newPattern) { - // remove any unsaved notes from old pattern dismissStep(); } @@ -282,7 +281,7 @@ void StepRecorder::removeNotesReleasedForTooLong() int nextTimout = INT_MAX; bool notesRemoved = false; - QMutableVectorIterator itr(m_curStepNotes); + QMutableVectorIterator itr(m_curStepNotes); while (itr.hasNext()) { StepNote* stepNote = itr.next(); diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index f97f8527079..b82453acf03 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -91,7 +91,7 @@ Editor::Editor(bool record, bool stepRecord) : // Set up play and record actions m_playAction = new QAction(embed::getIconPixmap("play"), tr("Play (Space)"), this); m_stopAction = new QAction(embed::getIconPixmap("stop"), tr("Stop (Space)"), this); - + m_recordAction = new QAction(embed::getIconPixmap("record"), tr("Record"), this); m_recordAccompanyAction = new QAction(embed::getIconPixmap("record_accompany"), tr("Record while playing"), this); m_toggleStepRecordingAction = new QAction(embed::getIconPixmap("record_step_off"), tr("Toggle Step Recording"), this); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 9330be510b8..f599c9fc064 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -325,7 +325,7 @@ PianoRoll::PianoRoll() : m_timeLine, SLOT( updatePosition( const MidiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePosition( const MidiTime & ) ) ); - + //update timeline when in step-recording mode connect( &m_stepRecorderWidget, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePositionStepRecording( const MidiTime & ) ) ); @@ -657,45 +657,51 @@ void PianoRoll::setCurrentPattern( Pattern* newPattern ) m_currentNote = NULL; m_startKey = INITIAL_START_KEY; - if(hasValidPattern()) + m_stepRecorder.setCurrentPattern(newPattern); + + if( ! hasValidPattern() ) { - m_leftRightScroll->setValue( 0 ); + //resizeEvent( NULL ); - // determine the central key so that we can scroll to it - int central_key = 0; - int total_notes = 0; - for( const Note *note : m_pattern->notes() ) - { - if( note->length() > 0 ) - { - central_key += note->key(); - ++total_notes; - } - } + update(); + emit currentPatternChanged(); + return; + } - if( total_notes > 0 ) + m_leftRightScroll->setValue( 0 ); + + // determine the central key so that we can scroll to it + int central_key = 0; + int total_notes = 0; + for( const Note *note : m_pattern->notes() ) + { + if( note->length() > 0 ) { - central_key = central_key / total_notes - - ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; - m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave ); + central_key += note->key(); + ++total_notes; } + } - // resizeEvent() does the rest for us (scrolling, range-checking - // of start-notes and so on...) - resizeEvent( NULL ); + if( total_notes > 0 ) + { + central_key = central_key / total_notes - + ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; + m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave ); + } - // make sure to always get informed about the pattern being destroyed - connect( m_pattern, SIGNAL( destroyedPattern( Pattern* ) ), this, SLOT( hidePattern( Pattern* ) ) ); + // resizeEvent() does the rest for us (scrolling, range-checking + // of start-notes and so on...) + resizeEvent( NULL ); - connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const Note& ) ), this, SLOT( startRecordNote( const Note& ) ) ); - connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) ); - connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); - } + // make sure to always get informed about the pattern being destroyed + connect( m_pattern, SIGNAL( destroyedPattern( Pattern* ) ), this, SLOT( hidePattern( Pattern* ) ) ); + + connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const Note& ) ), this, SLOT( startRecordNote( const Note& ) ) ); + connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) ); + connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); update(); emit currentPatternChanged(); - - m_stepRecorder.setCurrentPattern(newPattern); } @@ -1147,9 +1153,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) bool handled = m_stepRecorder.keyPressEvent(ke); if(handled) { - //return in case event was already handled by stepRecorder, - //this way we allow it to override/bypass other functionality that is not allowed while recording - ke->accept(); //TODO remove? <-- seems to be done already by caller ( PianoView::keyPressEvent) + ke->accept(); update(); return; } @@ -2136,7 +2140,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) NOTE_EDIT_MIN_HEIGHT, height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR - PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT ); - + m_stepRecorderWidget.setBottomMargin(PR_BOTTOM_MARGIN + m_notesEditHeight); repaint(); return; @@ -4602,7 +4606,7 @@ QSize PianoRollWindow::sizeHint() const void PianoRollWindow::updateAfterPatternChange() { patternRenamed(); - updateStepRecordingIcon(); //pattern change turn step recording OFF - update icon accordingly + updateStepRecordingIcon(); //pattern change turn step recording OFF - update icon accordingly } void PianoRollWindow::patternRenamed() diff --git a/src/gui/widgets/StepRecorderWidget.cpp b/src/gui/widgets/StepRecorderWidget.cpp index 0d554da797e..42a2c9a0b0f 100644 --- a/src/gui/widgets/StepRecorderWidget.cpp +++ b/src/gui/widgets/StepRecorderWidget.cpp @@ -27,15 +27,15 @@ StepRecorderWidget::StepRecorderWidget( QWidget * parent, const int ppt, - const int margin_top, - const int margin_bottom, - const int margin_left, - const int margin_right) : + const int marginTop, + const int marginBottom, + const int marginLeft, + const int marginRight) : QWidget(parent), - m_margin_top(margin_top), - m_margin_bottom(margin_bottom), - m_margin_left(margin_left), - m_margin_right(margin_right) + m_marginTop(marginTop), + m_marginBottom(marginBottom), + m_marginLeft(marginLeft), + m_marginRight(marginRight) { const QColor baseColor = QColor(255, 0, 0);// QColor(204, 163, 0); // Orange m_colorLineEnd = baseColor.lighter(150); @@ -44,13 +44,13 @@ StepRecorderWidget::StepRecorderWidget( setAttribute(Qt::WA_NoSystemBackground, true); setPixelsPerTact(ppt); - m_top = m_margin_top; - m_left = m_margin_left; + m_top = m_marginTop; + m_left = m_marginLeft; } -void StepRecorderWidget::setPixelsPerTact(int _ppt ) +void StepRecorderWidget::setPixelsPerTact(int ppt) { - m_ppt = _ppt; + m_ppt = ppt; } void StepRecorderWidget::setCurrentPosition(MidiTime currentPosition) @@ -58,9 +58,9 @@ void StepRecorderWidget::setCurrentPosition(MidiTime currentPosition) m_currentPosition = currentPosition; } -void StepRecorderWidget::setBottomMargin(const int margin_bottom) +void StepRecorderWidget::setBottomMargin(const int marginBottom) { - m_margin_bottom = margin_bottom; + m_marginBottom = marginBottom; } void StepRecorderWidget::setStartPosition(MidiTime pos) @@ -71,14 +71,13 @@ void StepRecorderWidget::setStartPosition(MidiTime pos) void StepRecorderWidget::setEndPosition(MidiTime pos) { m_curStepEndPos = pos; - emit positionChanged( m_curStepEndPos ); + emit positionChanged(m_curStepEndPos); } void StepRecorderWidget::showHint() { - TextFloat::displayMessage( tr( "Hint" ), - tr( "Move recording curser using arrows"), - embed::getIconPixmap( "hint" )); + TextFloat::displayMessage(tr( "Hint" ), tr("Move recording curser using arrows"), + embed::getIconPixmap("hint")); } void StepRecorderWidget::setStepsLength(MidiTime stepsLength) @@ -86,9 +85,9 @@ void StepRecorderWidget::setStepsLength(MidiTime stepsLength) m_stepsLength = stepsLength; } -void StepRecorderWidget::paintEvent( QPaintEvent * pe ) +void StepRecorderWidget::paintEvent(QPaintEvent * pe) { - QPainter painter( this ); + QPainter painter(this); updateBoundaries(); @@ -126,13 +125,13 @@ void StepRecorderWidget::paintEvent( QPaintEvent * pe ) int StepRecorderWidget::xCoordOfTick(int tick) { - return m_margin_left + ((tick - m_currentPosition) * m_ppt / MidiTime::ticksPerTact() ); + return m_marginLeft + ((tick - m_currentPosition) * m_ppt / MidiTime::ticksPerTact()); } void StepRecorderWidget::drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom) { - if(x >= m_margin_left && x <= (width() - m_margin_right)) + if(x >= m_marginLeft && x <= (width() - m_marginRight)) { painter->setPen(color); painter->drawLine( x, top, x, bottom ); @@ -148,9 +147,9 @@ void StepRecorderWidget::updateBoundaries() { setFixedSize(parentWidget()->size()); - m_bottom = height() - m_margin_bottom; - m_right = width() - m_margin_top; + m_bottom = height() - m_marginBottom; + m_right = width() - m_marginTop; - //(no need to change top and left - they are static) + //(no need to change top and left as they are static) } From 628835c4e051f3329ef0699f41e52abd6bd32ed8 Mon Sep 17 00:00:00 2001 From: nomzu Date: Tue, 4 Sep 2018 17:52:03 +0300 Subject: [PATCH 07/11] fix bug: SLOT function should have been changed from patternRenamed to updateAfterPatternChange In a former commit, the slot function "patternRenamed " was renamed to "updateAfterPatternChange" but two places in code were not changed accordingly by mistake. This issue now fixed. Also, removed trailing-spaces from files. --- include/StepRecorder.h | 18 +++++------ include/StepRecorderWidget.h | 22 +++++++------- src/core/StepRecorder.cpp | 42 +++++++++++++------------- src/gui/editors/PianoRoll.cpp | 4 +-- src/gui/widgets/StepRecorderWidget.cpp | 16 +++++----- 5 files changed, 51 insertions(+), 51 deletions(-) diff --git a/include/StepRecorder.h b/include/StepRecorder.h index 504ee782c20..b9653b1bbea 100644 --- a/include/StepRecorder.h +++ b/include/StepRecorder.h @@ -42,14 +42,14 @@ class StepRecorder : public QObject void initialize(); void start(const MidiTime& currentPosition,const MidiTime& stepLength); - void stop(); + void stop(); void notePressed(const Note & n); void noteReleased(const Note & n); bool keyPressEvent(QKeyEvent* ke); bool mousePressEvent(QMouseEvent* ke); void setCurrentPattern(Pattern* newPattern); void setStepsLength(const MidiTime& newLength); - + QVector getCurStepNotes(); bool isRecording() const @@ -58,16 +58,16 @@ class StepRecorder : public QObject } QColor curStepNoteColor() const - { + { return QColor(245,3,139); // radiant pink } - private slots: + private slots: void removeNotesReleasedForTooLong(); private: void stepForwards(); - void stepBackwards(); + void stepBackwards(); void applyStep(); void dismissStep(); @@ -90,9 +90,9 @@ class StepRecorder : public QObject MidiTime m_stepsLength; MidiTime m_curStepLength; // current step length refers to the step currently recorded. it may defer from m_stepsLength // since the user can make current step larger - - QTimer m_updateReleasedTimer; - + + QTimer m_updateReleasedTimer; + Pattern* m_pattern; class StepNote @@ -129,7 +129,7 @@ class StepRecorder : public QObject Note m_note; private: - bool m_pressed; + bool m_pressed; QTime releasedTimer; } ; diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h index 14255765a8a..0e45121698b 100644 --- a/include/StepRecorderWidget.h +++ b/include/StepRecorderWidget.h @@ -32,17 +32,17 @@ class StepRecorderWidget : public QWidget { Q_OBJECT - + public: StepRecorderWidget( - QWidget * parent, - const int ppt, + QWidget * parent, + const int ppt, const int marginTop, const int marginBottom, const int marginLeft, const int marginRight); - //API used by PianoRoll + //API used by PianoRoll void setPixelsPerTact(int ppt); void setCurrentPosition(MidiTime currentPosition); void setBottomMargin(const int marginBottom); @@ -51,25 +51,25 @@ class StepRecorderWidget : public QWidget void setStepsLength(MidiTime stepsLength); void setStartPosition(MidiTime pos); void setEndPosition(MidiTime pos); - + void showHint(); private: virtual void paintEvent(QPaintEvent * pe); - + int xCoordOfTick(int tick); - + void drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom); void drawVerLine(QPainter* painter, const MidiTime& pos, const QColor& color, int top, int bottom); - + void updateBoundaries(); MidiTime m_stepsLength; MidiTime m_curStepStartPos; MidiTime m_curStepEndPos; - int m_ppt; // pixels per tact - MidiTime m_currentPosition; // current position showed by on PianoRoll + int m_ppt; // pixels per tact + MidiTime m_currentPosition; // current position showed by on PianoRoll QColor m_colorLineStart; QColor m_colorLineEnd; @@ -86,7 +86,7 @@ class StepRecorderWidget : public QWidget const int m_marginRight; signals: - void positionChanged(const MidiTime & t); + void positionChanged(const MidiTime & t); } ; #endif //STEP_RECOREDER_WIDGET_H diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index 765710ce0b8..f6c57af0c42 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -28,13 +28,13 @@ using std::min; using std::max; -const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70; +const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70; StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget): m_pianoRoll(pianoRoll), m_stepRecorderWidget(stepRecorderWidget) { - m_stepRecorderWidget.hide(); + m_stepRecorderWidget.hide(); } void StepRecorder::initialize() @@ -45,7 +45,7 @@ void StepRecorder::initialize() void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLength) { m_isRecording = true; - + setStepsLength(stepLength); // quantize current position to get start recording position @@ -53,7 +53,7 @@ void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLe const int curPosTicks = currentPosition.getTicks(); const int QuantizedPosTicks = (curPosTicks / q) * q; const MidiTime& QuantizedPos = MidiTime(QuantizedPosTicks); - + m_curStepStartPos = QuantizedPos; m_curStepLength = 0; @@ -61,12 +61,12 @@ void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLe m_stepRecorderWidget.showHint(); - prepareNewStep(); + prepareNewStep(); } void StepRecorder::stop() { - m_stepRecorderWidget.hide(); + m_stepRecorderWidget.hide(); m_isRecording = false; } @@ -75,9 +75,9 @@ void StepRecorder::notePressed(const Note & n) //if this is the first pressed note in step, advance position if(!m_isStepInProgress) { - m_isStepInProgress = true; + m_isStepInProgress = true; - //move curser one step forwards + //move curser one step forwards stepForwards(); } @@ -85,7 +85,7 @@ void StepRecorder::notePressed(const Note & n) if(stepNote == nullptr) { m_curStepNotes.append(new StepNote(Note(m_stepsLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning()))); - m_pianoRoll.update(); + m_pianoRoll.update(); } else if (stepNote->isReleased()) { @@ -101,7 +101,7 @@ void StepRecorder::noteReleased(const Note & n) { stepNote->setReleased(); - //if m_updateReleasedTimer is not already active, activate it + //if m_updateReleasedTimer is not already active, activate it //(when activated, the timer will re-set itself as long as there are released notes) if(!m_updateReleasedTimer.isActive()) { @@ -115,12 +115,12 @@ void StepRecorder::noteReleased(const Note & n) { applyStep(); } - else + else { dismissStep(); } } - } + } } bool StepRecorder::keyPressEvent(QKeyEvent* ke) @@ -163,7 +163,7 @@ void StepRecorder::setStepsLength(const MidiTime& newLength) updateCurStepNotes(); } - m_stepsLength = newLength; + m_stepsLength = newLength; updateWidget(); } @@ -191,7 +191,7 @@ void StepRecorder::stepForwards() updateCurStepNotes(); } - else + else { m_curStepStartPos += m_stepsLength; } @@ -207,7 +207,7 @@ void StepRecorder::stepBackwards() { m_curStepLength = max(m_curStepLength - m_stepsLength, 0); } - else + else { //if length is already zero - move starting position backwards m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0); @@ -215,7 +215,7 @@ void StepRecorder::stepBackwards() updateCurStepNotes(); } - else + else { m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0); } @@ -257,7 +257,7 @@ void StepRecorder::prepareNewStep() delete stepNote; } m_curStepNotes.clear(); - + m_isStepInProgress = false; m_curStepStartPos = getCurStepEndPos(); @@ -282,7 +282,7 @@ void StepRecorder::removeNotesReleasedForTooLong() bool notesRemoved = false; QMutableVectorIterator itr(m_curStepNotes); - while (itr.hasNext()) + while (itr.hasNext()) { StepNote* stepNote = itr.next(); @@ -295,7 +295,7 @@ void StepRecorder::removeNotesReleasedForTooLong() itr.remove(); notesRemoved = true; } - else + else { nextTimout = min(nextTimout, REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS - timeSinceReleased); } @@ -311,7 +311,7 @@ void StepRecorder::removeNotesReleasedForTooLong() { m_updateReleasedTimer.start(nextTimout); } - else + else { // no released note found for next timout, stop timer m_updateReleasedTimer.stop(); @@ -348,7 +348,7 @@ bool StepRecorder::allCurStepNotesReleased() } } - return true; + return true; } StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index f599c9fc064..2005bef612b 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -4488,8 +4488,8 @@ void PianoRollWindow::setCurrentPattern( Pattern* pattern ) if ( pattern ) { setWindowTitle( tr( "Piano-Roll - %1" ).arg( pattern->name() ) ); - connect( pattern->instrumentTrack(), SIGNAL( nameChanged() ), this, SLOT( patternRenamed()) ); - connect( pattern, SIGNAL( dataChanged() ), this, SLOT( patternRenamed() ) ); + connect( pattern->instrumentTrack(), SIGNAL( nameChanged() ), this, SLOT( updateAfterPatternChange()) ); + connect( pattern, SIGNAL( dataChanged() ), this, SLOT( updateAfterPatternChange() ) ); } else { diff --git a/src/gui/widgets/StepRecorderWidget.cpp b/src/gui/widgets/StepRecorderWidget.cpp index 42a2c9a0b0f..f59e235fc80 100644 --- a/src/gui/widgets/StepRecorderWidget.cpp +++ b/src/gui/widgets/StepRecorderWidget.cpp @@ -24,13 +24,13 @@ #include "TextFloat.h" #include "embed.h" -StepRecorderWidget::StepRecorderWidget( - QWidget * parent, - const int ppt, +StepRecorderWidget::StepRecorderWidget( + QWidget * parent, + const int ppt, const int marginTop, const int marginBottom, const int marginLeft, - const int marginRight) : + const int marginRight) : QWidget(parent), m_marginTop(marginTop), m_marginBottom(marginBottom), @@ -65,18 +65,18 @@ void StepRecorderWidget::setBottomMargin(const int marginBottom) void StepRecorderWidget::setStartPosition(MidiTime pos) { - m_curStepStartPos = pos; + m_curStepStartPos = pos; } void StepRecorderWidget::setEndPosition(MidiTime pos) { - m_curStepEndPos = pos; + m_curStepEndPos = pos; emit positionChanged(m_curStepEndPos); } void StepRecorderWidget::showHint() { - TextFloat::displayMessage(tr( "Hint" ), tr("Move recording curser using arrows"), + TextFloat::displayMessage(tr( "Hint" ), tr("Move recording curser using arrows"), embed::getIconPixmap("hint")); } @@ -119,7 +119,7 @@ void StepRecorderWidget::paintEvent(QPaintEvent * pe) //add another line to make it clearer if(m_curStepEndPos == 0) { - drawVerLine(&painter, xCoordOfTick(m_curStepEndPos) + 1, m_colorLineEnd, m_top, m_bottom); + drawVerLine(&painter, xCoordOfTick(m_curStepEndPos) + 1, m_colorLineEnd, m_top, m_bottom); } } From 6c310671ab2cacb4baa0a2335e8774684145e9f6 Mon Sep 17 00:00:00 2001 From: nomzu Date: Mon, 17 Sep 2018 18:16:02 +0300 Subject: [PATCH 08/11] Two bug fixes (recorded notes position and length were wrong on specific cases) 1. length of new notes was wrong when current step was already in a size of multiply of steps. 2. recorded notes start-position was not updated correctly after moving cursor backwards --- src/core/StepRecorder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index f6c57af0c42..7f6f0654865 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -84,7 +84,7 @@ void StepRecorder::notePressed(const Note & n) StepNote* stepNote = findCurStepNote(n.key()); if(stepNote == nullptr) { - m_curStepNotes.append(new StepNote(Note(m_stepsLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning()))); + m_curStepNotes.append(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning()))); m_pianoRoll.update(); } else if (stepNote->isReleased()) @@ -328,6 +328,7 @@ void StepRecorder::updateCurStepNotes() for (StepNote* stepNote : m_curStepNotes) { stepNote->m_note.setLength(m_curStepLength); + stepNote->m_note.setPos(m_curStepStartPos); } } From d035e43d4511390b4ce655e9f2f7774043a7a123 Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Sat, 2 Feb 2019 19:13:02 +0100 Subject: [PATCH 09/11] Remove trailing whitespace --- include/Editor.h | 4 ++-- src/gui/CMakeLists.txt | 4 ++-- src/gui/editors/PianoRoll.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/Editor.h b/include/Editor.h index f1fdae630d3..ca4f7415e0b 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -51,7 +51,7 @@ protected slots: virtual void play() {} virtual void record() {} virtual void recordAccompany() {} - virtual void toggleStepRecording() {} + virtual void toggleStepRecording() {} virtual void stop() {} private slots: @@ -74,7 +74,7 @@ private slots: QAction* m_playAction; QAction* m_recordAction; QAction* m_recordAccompanyAction; - QAction* m_toggleStepRecordingAction; + QAction* m_toggleStepRecordingAction; QAction* m_stopAction; }; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7a617115265..d5ff6461237 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -87,8 +87,8 @@ SET(LMMS_SRCS gui/widgets/TrackLabelButton.cpp gui/widgets/TrackRenameLineEdit.cpp gui/widgets/VisualizationWidget.cpp - gui/widgets/StepRecorderWidget.cpp - + gui/widgets/StepRecorderWidget.cpp + PARENT_SCOPE ) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 2005bef612b..5d97337c850 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -1155,7 +1155,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) { ke->accept(); update(); - return; + return; } } @@ -3669,7 +3669,7 @@ bool PianoRoll::toggleStepRecording() { m_stepRecorder.stop(); } - else + else { if(hasValidPattern()) { @@ -4653,7 +4653,7 @@ void PianoRollWindow::updateStepRecordingIcon() { m_toggleStepRecordingAction->setIcon(embed::getIconPixmap("record_step_on")); } - else + else { m_toggleStepRecordingAction->setIcon(embed::getIconPixmap("record_step_off")); } From aebf056c5292c6deccdf6b4557eea824757fabe1 Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Mon, 4 Feb 2019 17:23:20 +0100 Subject: [PATCH 10/11] PlayPos 0 when toggling step record on while playing --- src/gui/editors/PianoRoll.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 5d97337c850..063f65c210b 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -3663,6 +3663,9 @@ void PianoRoll::recordAccompany() } } + + + bool PianoRoll::toggleStepRecording() { if(m_stepRecorder.isRecording()) @@ -3673,7 +3676,16 @@ bool PianoRoll::toggleStepRecording() { if(hasValidPattern()) { - m_stepRecorder.start(Engine::getSong()->getPlayPos(Song::Mode_PlayPattern), newNoteLen()); + if(Engine::getSong()->isPlaying()) + { + m_stepRecorder.start(0, newNoteLen()); + } + else + { + m_stepRecorder.start( + Engine::getSong()->getPlayPos( + Song::Mode_PlayPattern), newNoteLen()); + } } } @@ -3681,6 +3693,8 @@ bool PianoRoll::toggleStepRecording() } + + void PianoRoll::stop() { Engine::getSong()->stop(); From c6426f7d8e20b0560d74b105cc3e7b50541c6f3d Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Sat, 9 Feb 2019 15:54:38 +0100 Subject: [PATCH 11/11] Use std::numeric_limits::max() --- src/core/StepRecorder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index 7f6f0654865..7a63e88e26e 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -278,7 +278,7 @@ void StepRecorder::setCurrentPattern( Pattern* newPattern ) void StepRecorder::removeNotesReleasedForTooLong() { - int nextTimout = INT_MAX; + int nextTimout = std::numeric_limits::max(); bool notesRemoved = false; QMutableVectorIterator itr(m_curStepNotes); @@ -307,7 +307,7 @@ void StepRecorder::removeNotesReleasedForTooLong() m_pianoRoll.update(); } - if(nextTimout != INT_MAX) + if(nextTimout != std::numeric_limits::max()) { m_updateReleasedTimer.start(nextTimout); }