Add splitting (and resizing) to all types of clips (#7477)

Allow for splitting and resizing all types of clips (automation, MIDI, sample, pattern, etc) using the knife tool in the Song Editor.

---------

Co-authored-by: szeli1 <143485814+szeli1@users.noreply.github.com>
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
This commit is contained in:
regulus79
2025-04-29 17:56:13 -04:00
committed by GitHub
parent c91c81eee7
commit 679f9b848e
40 changed files with 868 additions and 422 deletions

View File

@@ -68,7 +68,6 @@ public:
using TimemapIterator = timeMap::const_iterator;
AutomationClip( AutomationTrack * _auto_track );
AutomationClip( const AutomationClip & _clip_to_copy );
~AutomationClip() override = default;
bool addObject( AutomatableModel * _obj, bool _search_dup = true );
@@ -90,7 +89,7 @@ public:
void setTension( QString _new_tension );
TimePos timeMapLength() const;
void updateLength();
void updateLength() override;
TimePos putValue(
const TimePos & time,
@@ -196,12 +195,22 @@ public:
static int quantization() { return s_quantization; }
static void setQuantization(int q) { s_quantization = q; }
AutomationClip* clone() override
{
return new AutomationClip(*this);
}
void clearObjects() { m_objects.clear(); }
public slots:
void clear();
void objectDestroyed( lmms::jo_id_t );
void flipY( int min, int max );
void flipY();
void flipX( int length = -1 );
void flipX(int start = -1, int end = -1);
protected:
AutomationClip( const AutomationClip & _clip_to_copy );
private:
void cleanObjects();

View File

@@ -75,6 +75,8 @@ private:
QStaticText m_staticTextName;
void scaleTimemapToFit( float oldMin, float oldMax );
bool isResizableBeforeStart() override { return false; }
} ;

View File

@@ -74,6 +74,7 @@ class AutomationEditor : public QWidget, public JournallingObject
Q_PROPERTY(QColor ghostNoteColor MEMBER m_ghostNoteColor)
Q_PROPERTY(QColor detuningNoteColor MEMBER m_detuningNoteColor)
Q_PROPERTY(QColor ghostSampleColor MEMBER m_ghostSampleColor)
Q_PROPERTY(QColor outOfBoundsShade MEMBER m_outOfBoundsShade)
public:
void setCurrentClip(AutomationClip * new_clip);
void setGhostMidiClip(MidiClip* newMidiClip);
@@ -291,6 +292,7 @@ private:
QColor m_ghostNoteColor;
QColor m_detuningNoteColor;
QColor m_ghostSampleColor;
QColor m_outOfBoundsShade;
SampleThumbnail m_sampleThumbnail;

View File

@@ -100,12 +100,28 @@ public:
* resized by clicking and dragging its edge.
*
*/
inline void setAutoResize( const bool r )
inline void setResizable( const bool r )
{
m_resizable = r;
}
inline const bool getResizable() const
{
return m_resizable;
}
/*! \brief Set whether a clip has been resized yet by the user or the knife tool.
*
* If a clip has been resized previously, it will not automatically
* resize when editing it.
*
*/
void setAutoResize(const bool r)
{
m_autoResize = r;
}
inline const bool getAutoResize() const
bool getAutoResize() const
{
return m_autoResize;
}
@@ -115,6 +131,7 @@ public:
virtual void movePosition( const TimePos & pos );
virtual void changeLength( const TimePos & length );
virtual void updateLength() {};
virtual gui::ClipView * createView( gui::TrackView * tv ) = 0;
@@ -137,6 +154,12 @@ public:
// Will copy the state of a clip to another clip
static void copyStateTo( Clip *src, Clip *dst );
/**
* Creates a copy of this clip
* @return pointer to the new clip object
*/
virtual Clip* clone() = 0;
public slots:
void toggleMute();
@@ -147,6 +170,8 @@ signals:
void destroyedClip();
void colorChanged();
protected:
Clip(const Clip& other);
private:
Track * m_track;
@@ -158,7 +183,8 @@ private:
BoolModel m_mutedModel;
BoolModel m_soloModel;
bool m_autoResize;
bool m_resizable = true;
bool m_autoResize = true;
bool m_selectViewOnCreate;

View File

@@ -63,6 +63,7 @@ class ClipView : public selectableObject, public ModelView
Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor )
Q_PROPERTY( QColor patternClipBackground READ patternClipBackground WRITE setPatternClipBackground )
Q_PROPERTY( bool gradient READ gradient WRITE setGradient )
Q_PROPERTY(QColor markerColor READ markerColor WRITE setMarkerColor)
// We have to use a QSize here because using QPoint isn't supported.
// width -> x, height -> y
Q_PROPERTY( QSize mouseHotspotHand MEMBER m_mouseHotspotHand )
@@ -94,6 +95,7 @@ public:
QColor textBackgroundColor() const;
QColor textShadowColor() const;
QColor patternClipBackground() const;
QColor markerColor() const;
bool gradient() const;
void setMutedColor( const QColor & c );
void setMutedBackgroundColor( const QColor & c );
@@ -103,6 +105,7 @@ public:
void setTextShadowColor( const QColor & c );
void setPatternClipBackground(const QColor& c);
void setGradient( const bool & b );
void setMarkerColor(const QColor& c);
// access needsUpdate member variable
bool needsUpdate();
@@ -121,10 +124,8 @@ public:
// some metadata to be written to the clipboard.
static void remove( QVector<ClipView *> clipvs );
static void toggleMute( QVector<ClipView *> clipvs );
static void mergeClips(QVector<ClipView*> clipvs);
// Returns true if selection can be merged and false if not
static bool canMergeSelection(QVector<ClipView*> clipvs);
void toggleSelectedAutoResize();
QColor getColorForDisplay( QColor );
@@ -147,8 +148,7 @@ protected:
Cut,
Copy,
Paste,
Mute,
Merge
Mute
};
TrackView * m_trackView;
@@ -176,7 +176,7 @@ protected:
}
bool unquantizedModHeld( QMouseEvent * me );
TimePos quantizeSplitPos( TimePos, bool shiftMode );
TimePos quantizeSplitPos(TimePos);
float pixelsPerBar();
@@ -224,6 +224,7 @@ private:
QColor m_textShadowColor;
QColor m_patternClipBackground;
bool m_gradient;
QColor m_markerColor;
QSize m_mouseHotspotHand; // QSize must be used because QPoint
QSize m_mouseHotspotKnife; // isn't supported by property system
QCursor m_cursorHand;
@@ -244,8 +245,24 @@ private:
TimePos draggedClipPos( QMouseEvent * me );
int knifeMarkerPos( QMouseEvent * me );
void setColor(const std::optional<QColor>& color);
//! Return true iff the clip could be split. Currently only implemented for samples
virtual bool splitClip( const TimePos pos ){ return false; };
//! Returns whether the user can left-resize this clip so that the start of the clip bounds is before the start of the clip content.
virtual bool isResizableBeforeStart() { return true; };
/**
* Split this Clip into two clips
* @param pos the position of the split, relative to the start of the clip
* @return true if the clip could be split
*/
bool splitClip(const TimePos pos);
/**
* Destructively split this Clip into two clips. If the clip type does not implement this feature, it will default to normal splitting.
* @param pos the position of the split, relative to the start of the clip
* @return true if the clip could be split
*/
virtual bool destructiveSplitClip(const TimePos pos)
{
return splitClip(pos);
}
void updateCursor(QMouseEvent * me);
} ;

View File

@@ -39,6 +39,10 @@ public:
InlineAutomation()
{
}
DetuningHelper(const DetuningHelper& _copy) :
InlineAutomation(_copy)
{
}
~DetuningHelper() override = default;

View File

@@ -32,22 +32,24 @@
namespace lmms
{
class InlineAutomation : public FloatModel, public sharedObject
class InlineAutomation : public FloatModel
{
public:
InlineAutomation() :
FloatModel(),
sharedObject(),
m_autoClip( nullptr )
FloatModel()
{
}
InlineAutomation(const InlineAutomation& _copy) :
FloatModel(_copy.value(), _copy.minValue(), _copy.maxValue(), _copy.step<float>()),
m_autoClip(_copy.m_autoClip->clone())
{
m_autoClip->clearObjects();
m_autoClip->addObject(this);
}
~InlineAutomation() override
{
if( m_autoClip )
{
delete m_autoClip;
}
}
virtual float defaultValue() const = 0;
@@ -81,10 +83,10 @@ public:
{
if( m_autoClip == nullptr )
{
m_autoClip = new AutomationClip( nullptr );
m_autoClip = std::make_unique<AutomationClip>(nullptr);
m_autoClip->addObject( this );
}
return m_autoClip;
return m_autoClip.get();
}
void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override;
@@ -92,7 +94,7 @@ public:
private:
AutomationClip * m_autoClip;
std::unique_ptr<AutomationClip> m_autoClip;
} ;

View File

@@ -53,12 +53,11 @@ public:
} ;
MidiClip( InstrumentTrack* instrumentTrack );
MidiClip( const MidiClip& other );
~MidiClip() override;
void init();
void updateLength();
void updateLength() override;
// note management
Note * addNote( const Note & _new_note, const bool _quant_pos = true );
@@ -117,6 +116,11 @@ public:
gui::ClipView * createView( gui::TrackView * _tv ) override;
MidiClip* clone() override
{
return new MidiClip(*this);
}
using Model::dataChanged;
@@ -127,6 +131,7 @@ public slots:
void clear();
protected:
MidiClip( const MidiClip& other );
void updatePatternTrack();
protected slots:

View File

@@ -63,6 +63,11 @@ public:
QColor const & getMutedNoteBorderColor() const { return m_mutedNoteBorderColor; }
void setMutedNoteBorderColor(QColor const & color) { m_mutedNoteBorderColor = color; }
// Returns true if selection can be merged and false if not
static bool canMergeSelection(QVector<ClipView*> clipvs);
static void mergeClips(QVector<ClipView*> clipvs);
static void bulkClearNotesOutOfBounds(QVector<ClipView*> clipvs);
public slots:
lmms::MidiClip* getMidiClip();
void update() override;
@@ -76,6 +81,7 @@ protected slots:
void resetName();
void changeName();
void transposeSelection();
void clearNotesOutOfBounds();
protected:
@@ -103,6 +109,10 @@ private:
QStaticText m_staticTextName;
bool m_legacySEPattern;
bool isResizableBeforeStart() override { return false; }
bool destructiveSplitClip(const TimePos pos) override;
} ;

View File

@@ -26,6 +26,7 @@
#ifndef LMMS_NOTE_H
#define LMMS_NOTE_H
#include <memory>
#include <optional>
#include <vector>
@@ -107,6 +108,8 @@ public:
Note( const Note & note );
~Note() override;
Note& operator=(const Note& note);
// Note types
enum class Type
{
@@ -236,7 +239,7 @@ public:
DetuningHelper * detuning() const
{
return m_detuning;
return m_detuning.get();
}
bool hasDetuningInfo() const;
bool withinRange(int tickStart, int tickEnd) const;
@@ -262,7 +265,7 @@ private:
panning_t m_panning;
TimePos m_length;
TimePos m_pos;
DetuningHelper * m_detuning;
std::unique_ptr<DetuningHelper> m_detuning;
Type m_type = Type::Regular;
};

View File

@@ -51,6 +51,11 @@ public:
gui::ClipView * createView( gui::TrackView * _tv ) override;
PatternClip* clone() override
{
return new PatternClip(*this);
}
private:
friend class PatternClipView;
} ;

View File

@@ -90,6 +90,7 @@ class PianoRoll : public QWidget
Q_PROPERTY(int ghostNoteOpacity MEMBER m_ghostNoteOpacity)
Q_PROPERTY(bool ghostNoteBorders MEMBER m_ghostNoteBorders)
Q_PROPERTY(QColor backgroundShade MEMBER m_backgroundShade)
Q_PROPERTY(QColor outOfBoundsShade MEMBER m_outOfBoundsShade)
/* white key properties */
Q_PROPERTY(int whiteKeyWidth MEMBER m_whiteKeyWidth)
@@ -516,6 +517,7 @@ private:
bool m_noteBorders;
bool m_ghostNoteBorders;
QColor m_backgroundShade;
QColor m_outOfBoundsShade;
/* white key properties */
int m_whiteKeyWidth;
QColor m_whiteKeyActiveTextColor;

View File

@@ -49,7 +49,6 @@ class SampleClip : public Clip
public:
SampleClip(Track* track, Sample sample, bool isPlaying);
SampleClip(Track* track);
SampleClip( const SampleClip& orig );
~SampleClip() override;
SampleClip& operator=( const SampleClip& that ) = delete;
@@ -81,6 +80,11 @@ public:
void setIsPlaying(bool isPlaying);
void setSampleBuffer(std::shared_ptr<const SampleBuffer> sb);
SampleClip* clone() override
{
return new SampleClip(*this);
}
public slots:
void setSampleFile(const QString& sf);
void updateLength();
@@ -88,6 +92,8 @@ public slots:
void playbackPositionChanged();
void updateTrackClips();
protected:
SampleClip( const SampleClip& orig );
private:
Sample m_sample;

View File

@@ -68,7 +68,6 @@ private:
SampleThumbnail m_sampleThumbnail;
QPixmap m_paintPixmap;
long m_paintPixmapXPosition;
bool splitClip( const TimePos pos ) override;
} ;