Step Recording feature (#4544)
(Addresses #1421) **Behaviour description:** * Toggle step-recording mode using the dedicated icon. * This mode is mutually exclusive with other recoding modes (record/record accompany). * Step-Recording while song is playing is allowed (and fun! :) ). * When start recording, the start recording-position will be set where the timeline curser points (quantized backwards using PianoRoll's current quantization). If step-recording is started while the pattern is playing the start recording-position is set to the beginning of the pattern. * Step length is determined by the Piano Roll's current note-length (can be changed dynamically during step-recording). * The record-position can be moved forward/backward using the right/left keys. * When notes are pressed on keyboard/midi-device, they will be added temporarily ("recorded") with a length of a step. while still pressed, user can adjust the length by steps resolution using the arrow keys (e.g. moving right once will make the note's length 2-steps, another right press will make the length 3-steps etc.). * When all pressed-keys are released, the actual recording happen and the notes are added. * If the user press multiple notes, and release some of them for some time which indicates it is intentional i.e. he didn't want to do a full release to record the step but rather just change what will be recorded (I set the "intentional release threshold" to 70 milliseconds) - these note will be removed from current step-recording. e.g. * Added notes are not quantized, making the addition simpler and WYSIWYG * Similiarly to adding notes using mouse clicks, an undo-checkpoint is added per added step and not for the whole recording as in other record modes.
This commit is contained in:
committed by
Oskar Wallgren
parent
3d17200925
commit
29c210128a
BIN
data/themes/classic/record_step_off.png
Normal file
BIN
data/themes/classic/record_step_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 443 B |
BIN
data/themes/classic/record_step_on.png
Normal file
BIN
data/themes/classic/record_step_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 596 B |
BIN
data/themes/default/record_step_off.png
Normal file
BIN
data/themes/default/record_step_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 443 B |
BIN
data/themes/default/record_step_on.png
Normal file
BIN
data/themes/default/record_step_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 596 B |
@@ -51,6 +51,7 @@ protected slots:
|
||||
virtual void play() {}
|
||||
virtual void record() {}
|
||||
virtual void recordAccompany() {}
|
||||
virtual void toggleStepRecording() {}
|
||||
virtual void stop() {}
|
||||
|
||||
private slots:
|
||||
@@ -64,7 +65,7 @@ protected:
|
||||
///
|
||||
/// \param record If set true, the editor's toolbar will contain record
|
||||
/// buttons in addition to the play and stop buttons.
|
||||
Editor(bool record = false);
|
||||
Editor(bool record = false, bool record_step = false);
|
||||
virtual ~Editor();
|
||||
|
||||
|
||||
@@ -73,6 +74,7 @@ protected:
|
||||
QAction* m_playAction;
|
||||
QAction* m_recordAction;
|
||||
QAction* m_recordAccompanyAction;
|
||||
QAction* m_toggleStepRecordingAction;
|
||||
QAction* m_stopAction;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include "lmms_basics.h"
|
||||
#include "Song.h"
|
||||
#include "ToolTip.h"
|
||||
#include "StepRecorder.h"
|
||||
#include "StepRecorderWidget.h"
|
||||
|
||||
class QPainter;
|
||||
class QPixmap;
|
||||
@@ -104,6 +106,11 @@ public:
|
||||
return m_recording;
|
||||
}
|
||||
|
||||
inline bool isStepRecording() const
|
||||
{
|
||||
return m_stepRecorder.isRecording();
|
||||
}
|
||||
|
||||
const Pattern* currentPattern() const
|
||||
{
|
||||
return m_pattern;
|
||||
@@ -189,6 +196,7 @@ protected slots:
|
||||
void play();
|
||||
void record();
|
||||
void recordAccompany();
|
||||
bool toggleStepRecording();
|
||||
void stop();
|
||||
|
||||
void startRecordNote( const Note & n );
|
||||
@@ -206,9 +214,11 @@ protected slots:
|
||||
|
||||
void updatePosition(const MidiTime & t );
|
||||
void updatePositionAccompany(const MidiTime & t );
|
||||
void updatePositionStepRecording(const MidiTime & t );
|
||||
|
||||
void zoomingChanged();
|
||||
void quantizeChanged();
|
||||
void noteLengthChanged();
|
||||
void quantizeNotes();
|
||||
|
||||
void updateSemiToneMarkerMenu();
|
||||
@@ -405,6 +415,9 @@ private:
|
||||
|
||||
friend class PianoRollWindow;
|
||||
|
||||
StepRecorderWidget m_stepRecorderWidget;
|
||||
StepRecorder m_stepRecorder;
|
||||
|
||||
// qproperty fields
|
||||
QColor m_barLineColor;
|
||||
QColor m_beatLineColor;
|
||||
@@ -449,6 +462,7 @@ public:
|
||||
void stop();
|
||||
void record();
|
||||
void recordAccompany();
|
||||
void toggleStepRecording();
|
||||
void stopRecording();
|
||||
|
||||
bool isRecording() const;
|
||||
@@ -473,11 +487,14 @@ signals:
|
||||
|
||||
|
||||
private slots:
|
||||
void patternRenamed();
|
||||
void updateAfterPatternChange();
|
||||
void ghostPatternSet( bool state );
|
||||
|
||||
private:
|
||||
void patternRenamed();
|
||||
void focusInEvent(QFocusEvent * event);
|
||||
void stopStepRecording();
|
||||
void updateStepRecordingIcon();
|
||||
|
||||
PianoRoll* m_editor;
|
||||
|
||||
|
||||
143
include/StepRecorder.h
Normal file
143
include/StepRecorder.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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_RECORDER_H
|
||||
#define STEP_RECORDER_H
|
||||
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#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);
|
||||
|
||||
QVector<Note*> getCurStepNotes();
|
||||
|
||||
bool isRecording() const
|
||||
{
|
||||
return m_isRecording;
|
||||
}
|
||||
|
||||
QColor curStepNoteColor() const
|
||||
{
|
||||
return QColor(245,3,139); // radiant pink
|
||||
}
|
||||
|
||||
private slots:
|
||||
void removeNotesReleasedForTooLong();
|
||||
|
||||
private:
|
||||
void stepForwards();
|
||||
void stepBackwards();
|
||||
|
||||
void applyStep();
|
||||
void dismissStep();
|
||||
void prepareNewStep();
|
||||
|
||||
MidiTime getCurStepEndPos();
|
||||
|
||||
void updateCurStepNotes();
|
||||
void updateWidget();
|
||||
|
||||
bool allCurStepNotesReleased();
|
||||
|
||||
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 StepNote
|
||||
{
|
||||
public:
|
||||
StepNote(const Note & note) : m_note(note), m_pressed(true) {};
|
||||
|
||||
void setPressed()
|
||||
{
|
||||
m_pressed = true;
|
||||
}
|
||||
|
||||
void setReleased()
|
||||
{
|
||||
m_pressed = false;
|
||||
releasedTimer.start();
|
||||
}
|
||||
|
||||
int timeSinceReleased()
|
||||
{
|
||||
return releasedTimer.elapsed();
|
||||
}
|
||||
|
||||
bool isPressed() const
|
||||
{
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
bool isReleased() const
|
||||
{
|
||||
return !m_pressed;
|
||||
}
|
||||
|
||||
Note m_note;
|
||||
|
||||
private:
|
||||
bool m_pressed;
|
||||
QTime releasedTimer;
|
||||
} ;
|
||||
|
||||
QVector<StepNote*> 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;
|
||||
};
|
||||
|
||||
#endif //STEP_RECORDER_H
|
||||
92
include/StepRecorderWidget.h
Normal file
92
include/StepRecorderWidget.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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_H
|
||||
#define STEP_RECOREDER_WIDGET_H
|
||||
|
||||
#include "lmms_basics.h"
|
||||
#include "Note.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QColor>
|
||||
#include <QPainter>
|
||||
|
||||
class StepRecorderWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StepRecorderWidget(
|
||||
QWidget * parent,
|
||||
const int ppt,
|
||||
const int marginTop,
|
||||
const int marginBottom,
|
||||
const int marginLeft,
|
||||
const int marginRight);
|
||||
|
||||
//API used by PianoRoll
|
||||
void setPixelsPerTact(int ppt);
|
||||
void setCurrentPosition(MidiTime currentPosition);
|
||||
void setBottomMargin(const int marginBottom);
|
||||
|
||||
//API used by StepRecorder
|
||||
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
|
||||
|
||||
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_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);
|
||||
} ;
|
||||
|
||||
#endif //STEP_RECOREDER_WIDGET_H
|
||||
@@ -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
|
||||
|
||||
366
src/core/StepRecorder.cpp
Normal file
366
src/core/StepRecorder.cpp
Normal file
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* 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 <QPainter>
|
||||
|
||||
#include <climits>
|
||||
using std::min;
|
||||
using std::max;
|
||||
|
||||
const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70;
|
||||
|
||||
StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget):
|
||||
m_pianoRoll(pianoRoll),
|
||||
m_stepRecorderWidget(stepRecorderWidget)
|
||||
{
|
||||
m_stepRecorderWidget.hide();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
m_stepRecorderWidget.showHint();
|
||||
|
||||
prepareNewStep();
|
||||
}
|
||||
|
||||
void StepRecorder::stop()
|
||||
{
|
||||
m_stepRecorderWidget.hide();
|
||||
m_isRecording = false;
|
||||
}
|
||||
|
||||
void StepRecorder::notePressed(const Note & n)
|
||||
{
|
||||
//if this is the first pressed note in step, advance position
|
||||
if(!m_isStepInProgress)
|
||||
{
|
||||
m_isStepInProgress = true;
|
||||
|
||||
//move curser one step forwards
|
||||
stepForwards();
|
||||
}
|
||||
|
||||
StepNote* stepNote = findCurStepNote(n.key());
|
||||
if(stepNote == nullptr)
|
||||
{
|
||||
m_curStepNotes.append(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning())));
|
||||
m_pianoRoll.update();
|
||||
}
|
||||
else if (stepNote->isReleased())
|
||||
{
|
||||
stepNote->setPressed();
|
||||
}
|
||||
}
|
||||
|
||||
void StepRecorder::noteReleased(const Note & n)
|
||||
{
|
||||
StepNote* stepNote = findCurStepNote(n.key());
|
||||
|
||||
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 released notes)
|
||||
if(!m_updateReleasedTimer.isActive())
|
||||
{
|
||||
m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS);
|
||||
}
|
||||
|
||||
//check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step
|
||||
if(allCurStepNotesReleased())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
QVector<Note*> StepRecorder::getCurStepNotes()
|
||||
{
|
||||
QVector<Note*> notes;
|
||||
|
||||
if(m_isStepInProgress)
|
||||
{
|
||||
for(StepNote* stepNote: m_curStepNotes)
|
||||
{
|
||||
notes.append(&stepNote->m_note);
|
||||
}
|
||||
}
|
||||
|
||||
return notes;
|
||||
}
|
||||
|
||||
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::applyStep()
|
||||
{
|
||||
m_pattern->addJournalCheckPoint();
|
||||
|
||||
for (const StepNote* stepNote : m_curStepNotes)
|
||||
{
|
||||
m_pattern->addNote(stepNote->m_note, false);
|
||||
}
|
||||
|
||||
m_pattern->rearrangeAllNotes();
|
||||
m_pattern->updateLength();
|
||||
m_pattern->dataChanged();
|
||||
Engine::getSong()->setModified();
|
||||
|
||||
prepareNewStep();
|
||||
}
|
||||
|
||||
void StepRecorder::dismissStep()
|
||||
{
|
||||
if(!m_isStepInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
prepareNewStep();
|
||||
}
|
||||
|
||||
void StepRecorder::prepareNewStep()
|
||||
{
|
||||
for(StepNote* stepNote : m_curStepNotes)
|
||||
{
|
||||
delete stepNote;
|
||||
}
|
||||
m_curStepNotes.clear();
|
||||
|
||||
m_isStepInProgress = false;
|
||||
|
||||
m_curStepStartPos = getCurStepEndPos();
|
||||
m_curStepLength = 0;
|
||||
|
||||
updateWidget();
|
||||
}
|
||||
|
||||
void StepRecorder::setCurrentPattern( Pattern* newPattern )
|
||||
{
|
||||
if(m_pattern != NULL && m_pattern != newPattern)
|
||||
{
|
||||
dismissStep();
|
||||
}
|
||||
|
||||
m_pattern = newPattern;
|
||||
}
|
||||
|
||||
void StepRecorder::removeNotesReleasedForTooLong()
|
||||
{
|
||||
int nextTimout = std::numeric_limits<int>::max();
|
||||
bool notesRemoved = false;
|
||||
|
||||
QMutableVectorIterator<StepNote*> itr(m_curStepNotes);
|
||||
while (itr.hasNext())
|
||||
{
|
||||
StepNote* stepNote = itr.next();
|
||||
|
||||
if(stepNote->isReleased())
|
||||
{
|
||||
const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout
|
||||
if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS)
|
||||
{
|
||||
delete stepNote;
|
||||
itr.remove();
|
||||
notesRemoved = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextTimout = min(nextTimout, REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS - timeSinceReleased);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(notesRemoved)
|
||||
{
|
||||
m_pianoRoll.update();
|
||||
}
|
||||
|
||||
if(nextTimout != std::numeric_limits<int>::max())
|
||||
{
|
||||
m_updateReleasedTimer.start(nextTimout);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no released note found for next timout, stop timer
|
||||
m_updateReleasedTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
MidiTime StepRecorder::getCurStepEndPos()
|
||||
{
|
||||
return m_curStepStartPos + m_curStepLength;
|
||||
}
|
||||
|
||||
void StepRecorder::updateCurStepNotes()
|
||||
{
|
||||
for (StepNote* stepNote : m_curStepNotes)
|
||||
{
|
||||
stepNote->m_note.setLength(m_curStepLength);
|
||||
stepNote->m_note.setPos(m_curStepStartPos);
|
||||
}
|
||||
}
|
||||
|
||||
void StepRecorder::updateWidget()
|
||||
{
|
||||
m_stepRecorderWidget.setStartPosition(m_curStepStartPos);
|
||||
m_stepRecorderWidget.setEndPosition(getCurStepEndPos());
|
||||
m_stepRecorderWidget.setStepsLength(m_stepsLength);
|
||||
}
|
||||
|
||||
bool StepRecorder::allCurStepNotesReleased()
|
||||
{
|
||||
for (const StepNote* stepNote : m_curStepNotes)
|
||||
{
|
||||
if(stepNote->isPressed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key)
|
||||
{
|
||||
for (StepNote* stepNote : m_curStepNotes)
|
||||
{
|
||||
if(stepNote->m_note.key() == key)
|
||||
{
|
||||
return stepNote;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -87,6 +87,7 @@ SET(LMMS_SRCS
|
||||
gui/widgets/TrackLabelButton.cpp
|
||||
gui/widgets/TrackRenameLineEdit.cpp
|
||||
gui/widgets/VisualizationWidget.cpp
|
||||
gui/widgets/StepRecorderWidget.cpp
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -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"));
|
||||
@@ -93,11 +94,13 @@ Editor::Editor(bool record) :
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ),
|
||||
@@ -323,6 +326,10 @@ PianoRoll::PianoRoll() :
|
||||
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,
|
||||
SIGNAL( positionChanged( const MidiTime & ) ),
|
||||
@@ -395,7 +402,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 +451,8 @@ PianoRoll::PianoRoll() :
|
||||
//connection for selecion from timeline
|
||||
connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
|
||||
this, SLOT( selectRegionFromPixels( int, int ) ) );
|
||||
|
||||
m_stepRecorder.initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -660,12 +669,19 @@ 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;
|
||||
|
||||
m_stepRecorder.setCurrentPattern(newPattern);
|
||||
|
||||
if( ! hasValidPattern() )
|
||||
{
|
||||
//resizeEvent( NULL );
|
||||
@@ -1153,8 +1169,19 @@ 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)
|
||||
{
|
||||
ke->accept();
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if( hasValidPattern() && ke->modifiers() == Qt::NoModifier )
|
||||
{
|
||||
const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave;
|
||||
@@ -1903,7 +1930,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 );
|
||||
|
||||
@@ -2136,6 +2163,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;
|
||||
}
|
||||
@@ -3226,6 +3255,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 );
|
||||
|
||||
@@ -3625,6 +3689,34 @@ void PianoRoll::recordAccompany()
|
||||
|
||||
|
||||
|
||||
bool PianoRoll::toggleStepRecording()
|
||||
{
|
||||
if(m_stepRecorder.isRecording())
|
||||
{
|
||||
m_stepRecorder.stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(hasValidPattern())
|
||||
{
|
||||
if(Engine::getSong()->isPlaying())
|
||||
{
|
||||
m_stepRecorder.start(0, newNoteLen());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stepRecorder.start(
|
||||
Engine::getSong()->getPlayPos(
|
||||
Song::Mode_PlayPattern), newNoteLen());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_stepRecorder.isRecording();;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PianoRoll::stop()
|
||||
{
|
||||
@@ -3638,22 +3730,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3663,28 +3762,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 ) )
|
||||
if(hasValidPattern())
|
||||
{
|
||||
for( QList<Note>::Iterator it = m_recordingNotes.begin();
|
||||
it != m_recordingNotes.end(); ++it )
|
||||
if( m_recording &&
|
||||
Engine::getSong()->isPlaying() &&
|
||||
( Engine::getSong()->playMode() ==
|
||||
desiredPlayModeForAccompany() ||
|
||||
Engine::getSong()->playMode() ==
|
||||
Song::Mode_PlayPattern ) )
|
||||
{
|
||||
if( it->key() == n.key() )
|
||||
for( QList<Note>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3694,6 +3800,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();
|
||||
}
|
||||
@@ -4064,6 +4171,13 @@ void PianoRoll::updatePositionAccompany( const MidiTime & t )
|
||||
}
|
||||
|
||||
|
||||
void PianoRoll::updatePositionStepRecording( const MidiTime & t )
|
||||
{
|
||||
if( m_stepRecorder.isRecording() )
|
||||
{
|
||||
autoScroll( t );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PianoRoll::zoomingChanged()
|
||||
@@ -4073,6 +4187,8 @@ void PianoRoll::zoomingChanged()
|
||||
assert( m_ppt > 0 );
|
||||
|
||||
m_timeLine->setPixelsPerTact( m_ppt );
|
||||
m_stepRecorderWidget.setPixelsPerTact( m_ppt );
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -4084,7 +4200,11 @@ void PianoRoll::quantizeChanged()
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
void PianoRoll::noteLengthChanged()
|
||||
{
|
||||
m_stepRecorder.setStepsLength(newNoteLen());
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
int PianoRoll::quantization() const
|
||||
@@ -4221,7 +4341,7 @@ Note * PianoRoll::noteUnderMouse()
|
||||
|
||||
|
||||
PianoRollWindow::PianoRollWindow() :
|
||||
Editor(true),
|
||||
Editor(true, true),
|
||||
m_editor(new PianoRoll())
|
||||
{
|
||||
setCentralWidget( m_editor );
|
||||
@@ -4229,6 +4349,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" ) );
|
||||
@@ -4375,7 +4496,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() ) );
|
||||
}
|
||||
|
||||
|
||||
@@ -4404,8 +4525,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
|
||||
{
|
||||
@@ -4450,6 +4571,8 @@ void PianoRollWindow::stop()
|
||||
|
||||
void PianoRollWindow::record()
|
||||
{
|
||||
stopStepRecording(); //step recording mode is mutually exclusive with other record modes
|
||||
|
||||
m_editor->record();
|
||||
}
|
||||
|
||||
@@ -4458,11 +4581,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()
|
||||
{
|
||||
@@ -4520,6 +4657,11 @@ QSize PianoRollWindow::sizeHint() const
|
||||
|
||||
|
||||
|
||||
void PianoRollWindow::updateAfterPatternChange()
|
||||
{
|
||||
patternRenamed();
|
||||
updateStepRecordingIcon(); //pattern change turn step recording OFF - update icon accordingly
|
||||
}
|
||||
|
||||
void PianoRollWindow::patternRenamed()
|
||||
{
|
||||
@@ -4549,3 +4691,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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
155
src/gui/widgets/StepRecorderWidget.cpp
Normal file
155
src/gui/widgets/StepRecorderWidget.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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"
|
||||
#include "TextFloat.h"
|
||||
#include "embed.h"
|
||||
|
||||
StepRecorderWidget::StepRecorderWidget(
|
||||
QWidget * parent,
|
||||
const int ppt,
|
||||
const int marginTop,
|
||||
const int marginBottom,
|
||||
const int marginLeft,
|
||||
const int marginRight) :
|
||||
QWidget(parent),
|
||||
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);
|
||||
m_colorLineStart = baseColor.darker(120);
|
||||
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setPixelsPerTact(ppt);
|
||||
|
||||
m_top = m_marginTop;
|
||||
m_left = m_marginLeft;
|
||||
}
|
||||
|
||||
void StepRecorderWidget::setPixelsPerTact(int ppt)
|
||||
{
|
||||
m_ppt = ppt;
|
||||
}
|
||||
|
||||
void StepRecorderWidget::setCurrentPosition(MidiTime currentPosition)
|
||||
{
|
||||
m_currentPosition = currentPosition;
|
||||
}
|
||||
|
||||
void StepRecorderWidget::setBottomMargin(const int marginBottom)
|
||||
{
|
||||
m_marginBottom = marginBottom;
|
||||
}
|
||||
|
||||
void StepRecorderWidget::setStartPosition(MidiTime pos)
|
||||
{
|
||||
m_curStepStartPos = pos;
|
||||
}
|
||||
|
||||
void StepRecorderWidget::setEndPosition(MidiTime pos)
|
||||
{
|
||||
m_curStepEndPos = pos;
|
||||
emit positionChanged(m_curStepEndPos);
|
||||
}
|
||||
|
||||
void StepRecorderWidget::showHint()
|
||||
{
|
||||
TextFloat::displayMessage(tr( "Hint" ), tr("Move recording curser using <Left/Right> arrows"),
|
||||
embed::getIconPixmap("hint"));
|
||||
}
|
||||
|
||||
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_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_marginLeft && x <= (width() - m_marginRight))
|
||||
{
|
||||
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_marginBottom;
|
||||
m_right = width() - m_marginTop;
|
||||
|
||||
//(no need to change top and left as they are static)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user