Skip to content

Commit

Permalink
Step Recording feature (#4544)
Browse files Browse the repository at this point in the history
(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.
  • Loading branch information
Mister-Lemon authored and zonkmachine committed Feb 9, 2019
1 parent 3d17200 commit 29c2101
Show file tree
Hide file tree
Showing 15 changed files with 988 additions and 41 deletions.
Binary file added data/themes/classic/record_step_off.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/classic/record_step_on.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/default/record_step_off.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/default/record_step_on.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion include/Editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected slots:
virtual void play() {}
virtual void record() {}
virtual void recordAccompany() {}
virtual void toggleStepRecording() {}
virtual void stop() {}

private slots:
Expand All @@ -64,7 +65,7 @@ private slots:
///
/// \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();


Expand All @@ -73,6 +74,7 @@ private slots:
QAction* m_playAction;
QAction* m_recordAction;
QAction* m_recordAccompanyAction;
QAction* m_toggleStepRecordingAction;
QAction* m_stopAction;
};

Expand Down
19 changes: 18 additions & 1 deletion include/PianoRoll.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include "lmms_basics.h"
#include "Song.h"
#include "ToolTip.h"
#include "StepRecorder.h"
#include "StepRecorderWidget.h"

class QPainter;
class QPixmap;
Expand Down Expand Up @@ -104,6 +106,11 @@ class PianoRoll : public QWidget
return m_recording;
}

inline bool isStepRecording() const
{
return m_stepRecorder.isRecording();
}

const Pattern* currentPattern() const
{
return m_pattern;
Expand Down Expand Up @@ -189,6 +196,7 @@ protected slots:
void play();
void record();
void recordAccompany();
bool toggleStepRecording();
void stop();

void startRecordNote( const Note & n );
Expand All @@ -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();
Expand Down Expand Up @@ -405,6 +415,9 @@ protected slots:

friend class PianoRollWindow;

StepRecorderWidget m_stepRecorderWidget;
StepRecorder m_stepRecorder;

// qproperty fields
QColor m_barLineColor;
QColor m_beatLineColor;
Expand Down Expand Up @@ -449,6 +462,7 @@ class PianoRollWindow : public Editor, SerializingObject
void stop();
void record();
void recordAccompany();
void toggleStepRecording();
void stopRecording();

bool isRecording() const;
Expand All @@ -473,11 +487,14 @@ class PianoRollWindow : public Editor, SerializingObject


private slots:
void patternRenamed();
void updateAfterPatternChange();
void ghostPatternSet( bool state );

private:
void patternRenamed();
void focusInEvent(QFocusEvent * event);
void stopStepRecording();
void updateStepRecordingIcon();

PianoRoll* m_editor;

Expand Down
143 changes: 143 additions & 0 deletions include/StepRecorder.h
Original file line number Diff line number Diff line change
@@ -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 changes: 92 additions & 0 deletions include/StepRecorderWidget.h
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 29c2101

Please sign in to comment.