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:
Mister-Lemon
2019-02-09 23:45:27 +02:00
committed by Oskar Wallgren
parent 3d17200925
commit 29c210128a
15 changed files with 987 additions and 40 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

View File

@@ -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;
};

View File

@@ -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
View 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

View 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

View File

@@ -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
View 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;
}

View File

@@ -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
)

View File

@@ -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");
}

View File

@@ -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"));
}
}

View File

@@ -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 )
{

View 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)
}