From 2f8d7b18519dca5d8963d421a933e3aee7d9b0cd Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 12 Mar 2025 12:58:49 -0400 Subject: [PATCH 01/17] Initial implementation --- include/Clip.h | 39 ++++++++++ include/ClipView.h | 5 ++ include/SamplePlayHandle.h | 1 + src/core/Clip.cpp | 88 +++++++++++++++++++++ src/core/SamplePlayHandle.cpp | 31 ++++++++ src/gui/clips/ClipView.cpp | 130 ++++++++++++++++++++++++++++++- src/gui/clips/SampleClipView.cpp | 5 ++ 7 files changed, 296 insertions(+), 3 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index a520ad4e470..452efd88208 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -134,6 +134,38 @@ class LMMS_EXPORT Clip : public Model, public JournallingObject TimePos startTimeOffset() const; void setStartTimeOffset( const TimePos &startTimeOffset ); + TimePos startCrossfadeLength() const + { + return m_startCrossfadeLength; + } + void setStartCrossfadeLength(const TimePos &startCrossfadeLength) + { + m_startCrossfadeLength = std::max(TimePos{0}, startCrossfadeLength); + } + Clip* leftCrossfadeClip() { return m_leftCrossfadeClip; } + + TimePos endCrossfadeLength() const + { + return m_endCrossfadeLength; + } + void setEndCrossfadeLength(const TimePos &endCrossfadeLength) + { + m_endCrossfadeLength = std::max(TimePos{0}, endCrossfadeLength); + } + Clip* rightCrossfadeClip() { return m_rightCrossfadeClip; } + + bool autoCrossfade() const + { + return m_autoCrossfade; + } + void setAutoCrossfade(bool autoCrossfade) + { + m_autoCrossfade = autoCrossfade; + } + + void updateCrossfades(); + void deleteCrossfades(); + // Will copy the state of a clip to another clip static void copyStateTo( Clip *src, Clip *dst ); @@ -144,6 +176,7 @@ public slots: signals: void lengthChanged(); void positionChanged(); + void crossfadesChanged(); void destroyedClip(); void colorChanged(); @@ -156,6 +189,12 @@ public slots: TimePos m_length; TimePos m_startTimeOffset; + TimePos m_startCrossfadeLength; + TimePos m_endCrossfadeLength; + Clip* m_leftCrossfadeClip = nullptr; + Clip* m_rightCrossfadeClip = nullptr; + bool m_autoCrossfade; + BoolModel m_mutedModel; BoolModel m_soloModel; bool m_autoResize; diff --git a/include/ClipView.h b/include/ClipView.h index 14898db65b4..f5d9fbe5adf 100644 --- a/include/ClipView.h +++ b/include/ClipView.h @@ -184,6 +184,7 @@ public slots: DataFile createClipDataFiles(const QVector & clips) const; virtual void paintTextLabel(QString const & text, QPainter & painter); + virtual void drawCrossfade(QPainter& painter, QRect rect); auto hasCustomColor() const -> bool; @@ -200,6 +201,8 @@ protected slots: MoveSelection, Resize, ResizeLeft, + EditStartCrossfade, + EditEndCrossfade, Split, CopySelection, ToggleSelected @@ -229,6 +232,8 @@ protected slots: QCursor m_cursorHand; QCursor m_cursorKnife; bool m_cursorSetYet; + bool m_mouseOverStartCrossfadeHandle; + bool m_mouseOverEndCrossfadeHandle; bool m_needsUpdate; inline void setInitialPos( QPoint pos ) diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index b3fddd503bc..85ef4a0dd3a 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -93,6 +93,7 @@ class LMMS_EXPORT SamplePlayHandle : public PlayHandle FloatModel m_defaultVolumeModel; FloatModel * m_volumeModel; Track * m_track; + SampleClip * m_clip; PatternTrack* m_patternTrack; diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index b18391df169..c26e56ec22b 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -47,6 +47,9 @@ Clip::Clip( Track * track ) : m_track( track ), m_startPosition(), m_length(), + m_startCrossfadeLength(0), + m_endCrossfadeLength(0), + m_autoCrossfade{true}, m_mutedModel( false, this, tr( "Mute" ) ), m_selectViewOnCreate{false} { @@ -76,6 +79,7 @@ Clip::~Clip() { getTrack()->removeClip( this ); } + deleteCrossfades(); } @@ -99,9 +103,92 @@ void Clip::movePosition( const TimePos & pos ) Engine::getSong()->updateLength(); emit positionChanged(); } + updateCrossfades(); } +void Clip::updateCrossfades() +{ + if (m_autoCrossfade) + { + // If the left or right crossfade isn't valid anymore, remove them + if (m_leftCrossfadeClip && ( + m_leftCrossfadeClip->endPosition() <= startPosition() + || m_leftCrossfadeClip->startPosition() >= startPosition() + || !m_leftCrossfadeClip->autoCrossfade())) + { + m_leftCrossfadeClip->setEndCrossfadeLength(0); + m_leftCrossfadeClip->m_rightCrossfadeClip = nullptr; + setStartCrossfadeLength(0); + m_leftCrossfadeClip = nullptr; + } + if (m_rightCrossfadeClip && ( + m_rightCrossfadeClip->startPosition() >= endPosition() + || m_rightCrossfadeClip->endPosition() <= endPosition() + || !m_rightCrossfadeClip->autoCrossfade())) + { + m_rightCrossfadeClip->setStartCrossfadeLength(0); + m_rightCrossfadeClip->m_leftCrossfadeClip = nullptr; + setEndCrossfadeLength(0); + m_rightCrossfadeClip = nullptr; + } + // If there isn't a left or right crossfade yet, add them + if (!m_rightCrossfadeClip || !m_leftCrossfadeClip) + { + for (Clip* clip: m_track->getClips()) + { + if (clip && clip != this && clip->autoCrossfade()) + { + if (clip->startPosition() <= startPosition() && clip->endPosition() >= endPosition()) { continue; } + if (clip->startPosition() >= startPosition() && clip->endPosition() <= endPosition()) { continue; } + if (clip->startPosition() < endPosition() && clip->endPosition() > endPosition() && !m_rightCrossfadeClip && !clip->m_leftCrossfadeClip) + { + clip->m_leftCrossfadeClip = this; + m_rightCrossfadeClip = clip; + } + else if (clip->endPosition() > startPosition() && clip->startPosition() < startPosition() && !m_leftCrossfadeClip && !clip->m_rightCrossfadeClip) + { + clip->m_rightCrossfadeClip = this; + m_leftCrossfadeClip = clip; + } + } + } + } + // If we do have a left/right crossfade, update the length. + if (m_leftCrossfadeClip) + { + const TimePos overlap = m_leftCrossfadeClip->endPosition() - startPosition(); + m_leftCrossfadeClip->setEndCrossfadeLength(overlap); + setStartCrossfadeLength(overlap); + emit m_leftCrossfadeClip->crossfadesChanged(); + } + if (m_rightCrossfadeClip) + { + const TimePos overlap = endPosition() - m_rightCrossfadeClip->startPosition(); + m_rightCrossfadeClip->setStartCrossfadeLength(overlap); + setEndCrossfadeLength(overlap); + emit m_rightCrossfadeClip->crossfadesChanged(); + } + } + emit crossfadesChanged(); +} + +void Clip::deleteCrossfades() +{ + if (m_leftCrossfadeClip) + { + m_leftCrossfadeClip->m_rightCrossfadeClip = nullptr; + m_leftCrossfadeClip->setEndCrossfadeLength(0); + m_leftCrossfadeClip = nullptr; + } + if (m_rightCrossfadeClip) + { + m_rightCrossfadeClip->m_leftCrossfadeClip = nullptr; + m_leftCrossfadeClip->setStartCrossfadeLength(0); + m_rightCrossfadeClip = nullptr; + } +} + /*! \brief Change the length of this Clip @@ -114,6 +201,7 @@ void Clip::movePosition( const TimePos & pos ) void Clip::changeLength( const TimePos & length ) { m_length = length; + updateCrossfades(); Engine::getSong()->updateLength(); emit lengthChanged(); } diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index eed727fc12e..dc9ef03e514 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -26,11 +26,14 @@ #include "AudioEngine.h" #include "AudioBusHandle.h" #include "Engine.h" +#include "MixHelpers.h" #include "Note.h" #include "PatternTrack.h" #include "SampleClip.h" #include "SampleTrack.h" +#include + namespace lmms { @@ -44,6 +47,7 @@ SamplePlayHandle::SamplePlayHandle(Sample* sample, bool ownAudioBusHandle) : m_defaultVolumeModel(DefaultVolume, MinVolume, MaxVolume, 1), m_volumeModel(&m_defaultVolumeModel), m_track(nullptr), + m_clip(nullptr), m_patternTrack(nullptr) { if (ownAudioBusHandle) @@ -66,6 +70,7 @@ SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) : SamplePlayHandle(&clip->sample(), false) { + m_clip = clip; m_track = clip->getTrack(); setAudioBusHandle(((SampleTrack *)clip->getTrack())->audioBusHandle()); } @@ -106,6 +111,28 @@ void SamplePlayHandle::play( SampleFrame* buffer ) frames -= offset(); } + float crossfadeAmount = 1.0f; + bool inCrossfade = false; + if (m_clip) + { + const f_cnt_t startCrossfadeFrames = m_clip->startCrossfadeLength() * Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); + const f_cnt_t endCrossfadeFrames = m_clip->endCrossfadeLength() * Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); + const f_cnt_t framesPerTick = Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); + const int framesRelativeToClipStart = m_state.frameIndex() + m_clip->startTimeOffset() * framesPerTick; + const int framesRelativeToClipEnd = m_clip->length() * framesPerTick - framesRelativeToClipStart; + + if (framesRelativeToClipStart < static_cast(startCrossfadeFrames)) + { + crossfadeAmount *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)); + inCrossfade = true; + } + if (framesRelativeToClipEnd < static_cast(endCrossfadeFrames)) + { + crossfadeAmount *= std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + inCrossfade = true; + } + } + if( !( m_track && m_track->isMuted() ) && !(m_patternTrack && m_patternTrack->isMuted())) { @@ -118,6 +145,10 @@ void SamplePlayHandle::play( SampleFrame* buffer ) { zeroSampleFrames(workingBuffer, frames); } + if (inCrossfade) + { + MixHelpers::multiply(workingBuffer, crossfadeAmount, frames); + } } m_frame += frames; diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index f98351f377d..d6afbf68fae 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -28,8 +28,10 @@ #include #include +#include #include #include +#include #include "AutomationClip.h" #include "Clipboard.h" @@ -64,6 +66,11 @@ namespace lmms::gui */ const int RESIZE_GRIP_WIDTH = 4; +/*! The square radius (half width/height) of the crossfade handle in pixels + */ +const int CROSSFADE_GRIP_RADIUS = 3; +const int CROSSFADE_GRIP_ACTIVE_RADIUS = 5; + /*! A pointer for that text bubble used when moving segments, etc. * @@ -106,6 +113,8 @@ ClipView::ClipView( Clip * clip, m_cursorHand( QCursor( embed::getIconPixmap( "hand" ) ) ), m_cursorKnife( QCursor( embed::getIconPixmap( "cursor_knife" ) ) ), m_cursorSetYet( false ), + m_mouseOverStartCrossfadeHandle(false), + m_mouseOverEndCrossfadeHandle(false), m_needsUpdate( true ) { if( s_textFloat == nullptr ) @@ -132,6 +141,7 @@ ClipView::ClipView( Clip * clip, connect( m_clip, SIGNAL(destroyedClip()), this, SLOT(close())); setModel( m_clip ); connect(m_clip, SIGNAL(colorChanged()), this, SLOT(update())); + connect(m_clip, SIGNAL(crossfadesChanged()), this, SLOT(update())); connect(m_trackView->getTrack(), &Track::colorChanged, this, [this] { @@ -602,6 +612,73 @@ void ClipView::paintTextLabel(QString const & text, QPainter & painter) painter.drawText( textLeft, finalTextTop, elidedClipName ); } +void ClipView::drawCrossfade(QPainter& painter, QRect rect) +{ + float nom = Engine::getSong()->getTimeSigModel().getNumerator(); + float den = Engine::getSong()->getTimeSigModel().getDenominator(); + float ticksPerBar = DefaultTicksPerBar * nom / den; + int startCrossfadeLength = m_clip->startCrossfadeLength() / ticksPerBar * pixelsPerBar(); + int endCrossfadeLength = m_clip->endCrossfadeLength() / ticksPerBar * pixelsPerBar(); + + if (m_clip->startCrossfadeLength() > 0) + { + QPainterPath startPath; + startPath.moveTo(QPoint(0,0)); + startPath.lineTo(QPoint(0, rect.height())); + float lastValue = 0; + for (int i = 0; i < startCrossfadeLength; ++i) + { + float nextValue = std::sqrt(static_cast(i) / static_cast(startCrossfadeLength)); + startPath.lineTo(QPoint(i + 1, (1.0f - nextValue) * rect.height())); + painter.drawLine(i, (1.0f - lastValue) * rect.height(), i + 1, (1.0f - nextValue) * rect.height()); + if (m_clip->leftCrossfadeClip()) + { + painter.drawLine(startCrossfadeLength - i, (1.0f - lastValue) * rect.height(), startCrossfadeLength - i - 1, (1.0f - nextValue) * rect.height()); + } + lastValue = nextValue; + } + startPath.lineTo(QPoint(startCrossfadeLength, 0)); + painter.fillPath(startPath, QColor(0,0,0,200)); + } + if (m_mouseOverStartCrossfadeHandle || m_action == Action::EditStartCrossfade) + { + painter.drawEllipse(std::max(0, startCrossfadeLength - CROSSFADE_GRIP_ACTIVE_RADIUS), 0, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); + } + else if (m_clip->startCrossfadeLength() > 0) + { + painter.drawEllipse(std::max(0, startCrossfadeLength - CROSSFADE_GRIP_RADIUS), 0, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); + } + + if (m_clip->endCrossfadeLength() > 0) + { + QPainterPath endPath; + endPath.moveTo(QPoint(rect.width(),0)); + endPath.lineTo(QPoint(rect.width(), rect.height())); + float lastValue = 0; + for (int i = 1; i < endCrossfadeLength; ++i) + { + float nextValue = std::sqrt(static_cast(i) / static_cast(endCrossfadeLength)); + endPath.lineTo(QPoint(rect.width() - i - 1, (1.0f - nextValue) * rect.height())); + painter.drawLine(rect.width() - i, (1.0f - lastValue) * rect.height(), rect.width() - i - 1, (1.0f - nextValue) * rect.height()); + if (m_clip->rightCrossfadeClip()) + { + painter.drawLine(rect.width() - endCrossfadeLength + i, (1.0f - lastValue) * rect.height(), rect.width() - endCrossfadeLength + i + 1, (1.0f - nextValue) * rect.height()); + } + lastValue = nextValue; + } + endPath.lineTo(QPoint(rect.width() - endCrossfadeLength, 0)); + painter.fillPath(endPath, QColor(0,0,0,200)); + } + if (m_mouseOverEndCrossfadeHandle || m_action == Action::EditEndCrossfade) + { + painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_ACTIVE_RADIUS * 2, rect.width() - endCrossfadeLength - CROSSFADE_GRIP_ACTIVE_RADIUS), 0, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); + } + else if (m_clip->endCrossfadeLength() > 0) + { + painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_RADIUS * 2, rect.width() - endCrossfadeLength - CROSSFADE_GRIP_RADIUS), 0, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); + } +} + /*! \brief Handle a mouse press on this ClipView. * * Handles the various ways in which a ClipView can be @@ -655,9 +732,9 @@ void ClipView::mousePressEvent( QMouseEvent * me ) getGUI()->songEditor()->m_editor->selectAllClips( false ); m_clip->addJournalCheckPoint(); - // Action::Move, Action::Resize and Action::ResizeLeft + // Action::Move, Action::Resize, Action::ResizeLeft, Action::EditStartCrossfade, and Action::EditEndCrossfade // Action::Split action doesn't disable Clip journalling - if (m_action == Action::Move || m_action == Action::Resize || m_action == Action::ResizeLeft) + if (m_action == Action::Move || m_action == Action::Resize || m_action == Action::ResizeLeft || m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) { m_clip->setJournalling(false); } @@ -668,7 +745,21 @@ void ClipView::mousePressEvent( QMouseEvent * me ) if( m_clip->getAutoResize() ) { // Always move clips that can't be manually resized m_action = Action::Move; - setCursor( Qt::SizeAllCursor ); + setCursor(Qt::SizeAllCursor); + } + else if (m_mouseOverStartCrossfadeHandle) + { + m_action = Action::EditStartCrossfade; + m_clip->setAutoCrossfade(false); + m_clip->deleteCrossfades(); + setCursor(Qt::SizeHorCursor); + } + else if (m_mouseOverEndCrossfadeHandle) + { + m_action = Action::EditEndCrossfade; + m_clip->setAutoCrossfade(false); + m_clip->deleteCrossfades(); + setCursor(Qt::SizeHorCursor); } else if( me->x() >= width() - RESIZE_GRIP_WIDTH ) { @@ -982,6 +1073,18 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) TimePos::ticksPerBar() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); } + else if (m_action == Action::EditStartCrossfade) + { + TimePos pos = static_cast( me->x() * TimePos::ticksPerBar() / ppb ); + m_clip->setStartCrossfadeLength(std::max(0, static_cast(pos))); + update(); + } + else if (m_action == Action::EditEndCrossfade) + { + TimePos pos = static_cast( me->x() * TimePos::ticksPerBar() / ppb ); + m_clip->setEndCrossfadeLength(std::max(0, static_cast(m_clip->length() - pos))); + update(); + } else if( m_action == Action::Split ) { auto sClip = dynamic_cast(m_clip); @@ -993,6 +1096,23 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } // None of the actions above, we will just handle the cursor else { updateCursor(me); } + + bool oldStartState = m_mouseOverStartCrossfadeHandle; + bool oldEndState = m_mouseOverEndCrossfadeHandle; + + m_mouseOverStartCrossfadeHandle = + (std::abs(me->x() - m_clip->startCrossfadeLength() * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS + || me->x() < CROSSFADE_GRIP_ACTIVE_RADIUS * 2) + && me->y() <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; + m_mouseOverEndCrossfadeHandle = + (std::abs(me->x() - (m_clip->length() - m_clip->endCrossfadeLength()) * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS + || m_clip->length() * ppb / TimePos::ticksPerBar() - me->x() < CROSSFADE_GRIP_ACTIVE_RADIUS * 2) + && me->y() <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; + + if (oldStartState != m_mouseOverStartCrossfadeHandle || oldEndState != m_mouseOverEndCrossfadeHandle) + { + update(); + } } @@ -1021,6 +1141,10 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) // TODO: Fix m_clip->setJournalling() consistency m_clip->setJournalling( true ); } + else if(m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) + { + update(); + } else if( m_action == Action::Split ) { const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index d5cfb211ee8..978917cfab4 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -286,6 +286,11 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) m_sampleThumbnail.visualize(param, p); } + + p.setBrush(clipColor); + p.setPen(QColor(0,0,0)); + drawCrossfade(p, rect()); + p.setBrush(QColor(0,0,0,0)); QString name = PathUtil::cleanName(m_clip->m_sample.sampleFile()); paintTextLabel(name, p); From 41c472a2830d4c4f27a2048e0287c3a9b976a07d Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 21 May 2025 21:07:44 -0400 Subject: [PATCH 02/17] Remove auto-crossfading, and make fading sample-exact --- include/Clip.h | 18 ------- include/ClipView.h | 1 + src/core/Clip.cpp | 86 -------------------------------- src/core/SampleClip.cpp | 4 ++ src/core/SamplePlayHandle.cpp | 52 +++++++++---------- src/gui/clips/ClipView.cpp | 28 ++++------- src/gui/clips/SampleClipView.cpp | 2 +- 7 files changed, 43 insertions(+), 148 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index 29aeb841789..29b9bf6d1ea 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -159,7 +159,6 @@ class LMMS_EXPORT Clip : public Model, public JournallingObject { m_startCrossfadeLength = std::max(TimePos{0}, startCrossfadeLength); } - Clip* leftCrossfadeClip() { return m_leftCrossfadeClip; } TimePos endCrossfadeLength() const { @@ -169,19 +168,6 @@ class LMMS_EXPORT Clip : public Model, public JournallingObject { m_endCrossfadeLength = std::max(TimePos{0}, endCrossfadeLength); } - Clip* rightCrossfadeClip() { return m_rightCrossfadeClip; } - - bool autoCrossfade() const - { - return m_autoCrossfade; - } - void setAutoCrossfade(bool autoCrossfade) - { - m_autoCrossfade = autoCrossfade; - } - - void updateCrossfades(); - void deleteCrossfades(); // Will copy the state of a clip to another clip static void copyStateTo( Clip *src, Clip *dst ); @@ -199,7 +185,6 @@ public slots: signals: void lengthChanged(); void positionChanged(); - void crossfadesChanged(); void destroyedClip(); void colorChanged(); @@ -216,9 +201,6 @@ public slots: TimePos m_startCrossfadeLength; TimePos m_endCrossfadeLength; - Clip* m_leftCrossfadeClip = nullptr; - Clip* m_rightCrossfadeClip = nullptr; - bool m_autoCrossfade; BoolModel m_mutedModel; BoolModel m_soloModel; diff --git a/include/ClipView.h b/include/ClipView.h index 59d05acd718..ed99b7c95c1 100644 --- a/include/ClipView.h +++ b/include/ClipView.h @@ -169,6 +169,7 @@ public slots: void mousePressEvent( QMouseEvent * me ) override; void mouseMoveEvent( QMouseEvent * me ) override; void mouseReleaseEvent( QMouseEvent * me ) override; + void leaveEvent(QEvent * e) override; void resizeEvent( QResizeEvent * re ) override { m_needsUpdate = true; diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index 28f9e5de491..0db8f1a43a2 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -49,7 +49,6 @@ Clip::Clip( Track * track ) : m_length(), m_startCrossfadeLength(0), m_endCrossfadeLength(0), - m_autoCrossfade{true}, m_mutedModel( false, this, tr( "Mute" ) ), m_selectViewOnCreate{false} { @@ -102,7 +101,6 @@ Clip::~Clip() { getTrack()->removeClip( this ); } - deleteCrossfades(); } @@ -126,92 +124,9 @@ void Clip::movePosition( const TimePos & pos ) Engine::getSong()->updateLength(); emit positionChanged(); } - updateCrossfades(); } -void Clip::updateCrossfades() -{ - if (m_autoCrossfade) - { - // If the left or right crossfade isn't valid anymore, remove them - if (m_leftCrossfadeClip && ( - m_leftCrossfadeClip->endPosition() <= startPosition() - || m_leftCrossfadeClip->startPosition() >= startPosition() - || !m_leftCrossfadeClip->autoCrossfade())) - { - m_leftCrossfadeClip->setEndCrossfadeLength(0); - m_leftCrossfadeClip->m_rightCrossfadeClip = nullptr; - setStartCrossfadeLength(0); - m_leftCrossfadeClip = nullptr; - } - if (m_rightCrossfadeClip && ( - m_rightCrossfadeClip->startPosition() >= endPosition() - || m_rightCrossfadeClip->endPosition() <= endPosition() - || !m_rightCrossfadeClip->autoCrossfade())) - { - m_rightCrossfadeClip->setStartCrossfadeLength(0); - m_rightCrossfadeClip->m_leftCrossfadeClip = nullptr; - setEndCrossfadeLength(0); - m_rightCrossfadeClip = nullptr; - } - // If there isn't a left or right crossfade yet, add them - if (!m_rightCrossfadeClip || !m_leftCrossfadeClip) - { - for (Clip* clip: m_track->getClips()) - { - if (clip && clip != this && clip->autoCrossfade()) - { - if (clip->startPosition() <= startPosition() && clip->endPosition() >= endPosition()) { continue; } - if (clip->startPosition() >= startPosition() && clip->endPosition() <= endPosition()) { continue; } - if (clip->startPosition() < endPosition() && clip->endPosition() > endPosition() && !m_rightCrossfadeClip && !clip->m_leftCrossfadeClip) - { - clip->m_leftCrossfadeClip = this; - m_rightCrossfadeClip = clip; - } - else if (clip->endPosition() > startPosition() && clip->startPosition() < startPosition() && !m_leftCrossfadeClip && !clip->m_rightCrossfadeClip) - { - clip->m_rightCrossfadeClip = this; - m_leftCrossfadeClip = clip; - } - } - } - } - // If we do have a left/right crossfade, update the length. - if (m_leftCrossfadeClip) - { - const TimePos overlap = m_leftCrossfadeClip->endPosition() - startPosition(); - m_leftCrossfadeClip->setEndCrossfadeLength(overlap); - setStartCrossfadeLength(overlap); - emit m_leftCrossfadeClip->crossfadesChanged(); - } - if (m_rightCrossfadeClip) - { - const TimePos overlap = endPosition() - m_rightCrossfadeClip->startPosition(); - m_rightCrossfadeClip->setStartCrossfadeLength(overlap); - setEndCrossfadeLength(overlap); - emit m_rightCrossfadeClip->crossfadesChanged(); - } - } - emit crossfadesChanged(); -} - -void Clip::deleteCrossfades() -{ - if (m_leftCrossfadeClip) - { - m_leftCrossfadeClip->m_rightCrossfadeClip = nullptr; - m_leftCrossfadeClip->setEndCrossfadeLength(0); - m_leftCrossfadeClip = nullptr; - } - if (m_rightCrossfadeClip) - { - m_rightCrossfadeClip->m_leftCrossfadeClip = nullptr; - m_leftCrossfadeClip->setStartCrossfadeLength(0); - m_rightCrossfadeClip = nullptr; - } -} - /*! \brief Change the length of this Clip @@ -224,7 +139,6 @@ void Clip::deleteCrossfades() void Clip::changeLength( const TimePos & length ) { m_length = length; - updateCrossfades(); Engine::getSong()->updateLength(); emit lengthChanged(); } diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 5c04287c42e..59a094e5440 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -308,6 +308,8 @@ void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "src", sampleFile() ); _this.setAttribute( "off", startTimeOffset() ); _this.setAttribute("autoresize", QString::number(getAutoResize())); + _this.setAttribute("startfadelength", QString::number(startCrossfadeLength())); + _this.setAttribute("endfadelength", QString::number(endCrossfadeLength())); if( sampleFile() == "" ) { QString s; @@ -357,6 +359,8 @@ void SampleClip::loadSettings( const QDomElement & _this ) setMuted( _this.attribute( "muted" ).toInt() ); setStartTimeOffset( _this.attribute( "off" ).toInt() ); setAutoResize(_this.attribute("autoresize", "1").toInt()); + setStartCrossfadeLength(_this.attribute("startfadelength").toInt()); + setEndCrossfadeLength(_this.attribute("endfadelength").toInt()); if (_this.hasAttribute("color")) { diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index dc9ef03e514..1469a8efe73 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -32,8 +32,6 @@ #include "SampleClip.h" #include "SampleTrack.h" -#include - namespace lmms { @@ -111,27 +109,7 @@ void SamplePlayHandle::play( SampleFrame* buffer ) frames -= offset(); } - float crossfadeAmount = 1.0f; - bool inCrossfade = false; - if (m_clip) - { - const f_cnt_t startCrossfadeFrames = m_clip->startCrossfadeLength() * Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); - const f_cnt_t endCrossfadeFrames = m_clip->endCrossfadeLength() * Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); - const f_cnt_t framesPerTick = Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); - const int framesRelativeToClipStart = m_state.frameIndex() + m_clip->startTimeOffset() * framesPerTick; - const int framesRelativeToClipEnd = m_clip->length() * framesPerTick - framesRelativeToClipStart; - - if (framesRelativeToClipStart < static_cast(startCrossfadeFrames)) - { - crossfadeAmount *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)); - inCrossfade = true; - } - if (framesRelativeToClipEnd < static_cast(endCrossfadeFrames)) - { - crossfadeAmount *= std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); - inCrossfade = true; - } - } + const f_cnt_t initialFrameIndex = m_state.frameIndex(); if( !( m_track && m_track->isMuted() ) && !(m_patternTrack && m_patternTrack->isMuted())) @@ -145,9 +123,33 @@ void SamplePlayHandle::play( SampleFrame* buffer ) { zeroSampleFrames(workingBuffer, frames); } - if (inCrossfade) + + // Apply crossfade + const int framesPerTick = Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); + const int startCrossfadeFrames = m_clip->startCrossfadeLength() * framesPerTick; + const int endCrossfadeFrames = m_clip->endCrossfadeLength() * framesPerTick; + const int startOffsetFrames = m_clip->startTimeOffset() * framesPerTick; + const int lengthFrames = m_clip->length() * framesPerTick; + for (f_cnt_t f = 0; f < frames; ++f) { - MixHelpers::multiply(workingBuffer, crossfadeAmount, frames); + const f_cnt_t frameIndex = initialFrameIndex + f; + const int framesRelativeToClipStart = frameIndex + startOffsetFrames; + const int framesRelativeToClipEnd = lengthFrames - framesRelativeToClipStart; + if (framesRelativeToClipStart < 0 || framesRelativeToClipEnd < 0) { workingBuffer[f] = SampleFrame(); continue; } + + if (framesRelativeToClipStart < startCrossfadeFrames && framesRelativeToClipEnd < endCrossfadeFrames) + { + workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)) + * std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + } + else if (framesRelativeToClipStart < startCrossfadeFrames) + { + workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)); + } + else if (framesRelativeToClipEnd < endCrossfadeFrames) + { + workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + } } } diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 4739f18b5cc..00148d344cf 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -142,7 +141,6 @@ ClipView::ClipView( Clip * clip, connect( m_clip, SIGNAL(destroyedClip()), this, SLOT(close())); setModel( m_clip ); connect(m_clip, SIGNAL(colorChanged()), this, SLOT(update())); - connect(m_clip, SIGNAL(crossfadesChanged()), this, SLOT(update())); connect(m_trackView->getTrack(), &Track::colorChanged, this, [this] { @@ -637,10 +635,6 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) float nextValue = std::sqrt(static_cast(i) / static_cast(startCrossfadeLength)); startPath.lineTo(QPoint(i + 1, (1.0f - nextValue) * rect.height())); painter.drawLine(i, (1.0f - lastValue) * rect.height(), i + 1, (1.0f - nextValue) * rect.height()); - if (m_clip->leftCrossfadeClip()) - { - painter.drawLine(startCrossfadeLength - i, (1.0f - lastValue) * rect.height(), startCrossfadeLength - i - 1, (1.0f - nextValue) * rect.height()); - } lastValue = nextValue; } startPath.lineTo(QPoint(startCrossfadeLength, 0)); @@ -666,10 +660,6 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) float nextValue = std::sqrt(static_cast(i) / static_cast(endCrossfadeLength)); endPath.lineTo(QPoint(rect.width() - i - 1, (1.0f - nextValue) * rect.height())); painter.drawLine(rect.width() - i, (1.0f - lastValue) * rect.height(), rect.width() - i - 1, (1.0f - nextValue) * rect.height()); - if (m_clip->rightCrossfadeClip()) - { - painter.drawLine(rect.width() - endCrossfadeLength + i, (1.0f - lastValue) * rect.height(), rect.width() - endCrossfadeLength + i + 1, (1.0f - nextValue) * rect.height()); - } lastValue = nextValue; } endPath.lineTo(QPoint(rect.width() - endCrossfadeLength, 0)); @@ -685,6 +675,13 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) } } +void ClipView::leaveEvent(QEvent* e) +{ + m_mouseOverStartCrossfadeHandle = false; + m_mouseOverEndCrossfadeHandle = false; + update(); +} + /*! \brief Handle a mouse press on this ClipView. * * Handles the various ways in which a ClipView can be @@ -736,9 +733,8 @@ void ClipView::mousePressEvent( QMouseEvent * me ) getGUI()->songEditor()->m_editor->selectAllClips( false ); m_clip->addJournalCheckPoint(); - // Action::Move, Action::Resize, Action::ResizeLeft, Action::EditStartCrossfade, and Action::EditEndCrossfade // Action::Split action doesn't disable Clip journalling - if (m_action == Action::Move || m_action == Action::Resize || m_action == Action::ResizeLeft || m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) + if (m_action != Action::Split) { m_clip->setJournalling(false); } @@ -749,20 +745,16 @@ void ClipView::mousePressEvent( QMouseEvent * me ) if (!m_clip->getResizable() && !knifeMode) { // Always move clips that can't be manually resized m_action = Action::Move; - setCursor(Qt::SizeAllCursor); + setCursor( Qt::SizeAllCursor ); } else if (m_mouseOverStartCrossfadeHandle) { m_action = Action::EditStartCrossfade; - m_clip->setAutoCrossfade(false); - m_clip->deleteCrossfades(); setCursor(Qt::SizeHorCursor); } else if (m_mouseOverEndCrossfadeHandle) { m_action = Action::EditEndCrossfade; - m_clip->setAutoCrossfade(false); - m_clip->deleteCrossfades(); setCursor(Qt::SizeHorCursor); } else if( me->x() >= width() - RESIZE_GRIP_WIDTH ) @@ -1156,7 +1148,7 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) // TODO: Fix m_clip->setJournalling() consistency m_clip->setJournalling( true ); } - else if(m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) + else if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) { update(); } diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index bd24315aeaa..70ad391025f 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -300,7 +300,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) m_sampleThumbnail.visualize(param, p); } - + p.setBrush(clipColor); p.setPen(QColor(0,0,0)); drawCrossfade(p, rect()); From f8d0e6804b45ad7688c5d5e21d002a2627688f92 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 21 May 2025 21:36:16 -0400 Subject: [PATCH 03/17] Fix journalling bug, add bool for whether crossfades are supported, and clamp fade length --- include/Clip.h | 1 + include/SampleClip.h | 2 ++ src/gui/clips/ClipView.cpp | 17 +++++++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index 29b9bf6d1ea..73741b0d0ee 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -168,6 +168,7 @@ class LMMS_EXPORT Clip : public Model, public JournallingObject { m_endCrossfadeLength = std::max(TimePos{0}, endCrossfadeLength); } + virtual bool isCrossfadeable() const { return false; } // Will copy the state of a clip to another clip static void copyStateTo( Clip *src, Clip *dst ); diff --git a/include/SampleClip.h b/include/SampleClip.h index cbd3ac5d5df..02cdc588915 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -80,6 +80,8 @@ class SampleClip : public Clip void setIsPlaying(bool isPlaying); void setSampleBuffer(std::shared_ptr sb); + bool isCrossfadeable() const override { return true; } + SampleClip* clone() override { return new SampleClip(*this); diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 00148d344cf..8dc11cba7f6 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -747,12 +747,12 @@ void ClipView::mousePressEvent( QMouseEvent * me ) m_action = Action::Move; setCursor( Qt::SizeAllCursor ); } - else if (m_mouseOverStartCrossfadeHandle) + else if (m_mouseOverStartCrossfadeHandle && m_clip->isCrossfadeable()) { m_action = Action::EditStartCrossfade; setCursor(Qt::SizeHorCursor); } - else if (m_mouseOverEndCrossfadeHandle) + else if (m_mouseOverEndCrossfadeHandle && m_clip->isCrossfadeable()) { m_action = Action::EditEndCrossfade; setCursor(Qt::SizeHorCursor); @@ -1085,14 +1085,14 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } else if (m_action == Action::EditStartCrossfade) { - TimePos pos = static_cast( me->x() * TimePos::ticksPerBar() / ppb ); - m_clip->setStartCrossfadeLength(std::max(0, static_cast(pos))); + TimePos pos = static_cast(me->x() * TimePos::ticksPerBar() / ppb); + m_clip->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(m_clip->length()))); update(); } else if (m_action == Action::EditEndCrossfade) { - TimePos pos = static_cast( me->x() * TimePos::ticksPerBar() / ppb ); - m_clip->setEndCrossfadeLength(std::max(0, static_cast(m_clip->length() - pos))); + TimePos pos = static_cast(me->x() * TimePos::ticksPerBar() / ppb); + m_clip->setEndCrossfadeLength(std::clamp(static_cast(m_clip->length() - pos), 0, static_cast(m_clip->length()))); update(); } else if( m_action == Action::Split ) @@ -1143,12 +1143,13 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) { setSelected( !isSelected() ); } - else if( m_action == Action::Move || m_action == Action::Resize || m_action == Action::ResizeLeft ) + else if (m_action != Action::Split) { // TODO: Fix m_clip->setJournalling() consistency m_clip->setJournalling( true ); } - else if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) + + if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) { update(); } From 5d0bd0d055225358b71cf4219aa3c7a696c59d6f Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 21 May 2025 21:53:15 -0400 Subject: [PATCH 04/17] Add comments and fix bug --- src/gui/clips/ClipView.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 8dc11cba7f6..d77f64b29c5 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -1108,12 +1108,21 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) bool oldEndState = m_mouseOverEndCrossfadeHandle; m_mouseOverStartCrossfadeHandle = + // Is the x coordinate within the radius of the handle? (std::abs(me->x() - m_clip->startCrossfadeLength() * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS - || me->x() < CROSSFADE_GRIP_ACTIVE_RADIUS * 2) + // Or, if there is no current fade, is the x coordinate close to the clip start? + || (m_clip->startCrossfadeLength() == 0 + && me->x() < CROSSFADE_GRIP_ACTIVE_RADIUS * 2)) + // And, is the y coordinate up by the top of the clip? && me->y() <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; + m_mouseOverEndCrossfadeHandle = + // Is the x coordinate within the radius of the handle? (std::abs(me->x() - (m_clip->length() - m_clip->endCrossfadeLength()) * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS - || m_clip->length() * ppb / TimePos::ticksPerBar() - me->x() < CROSSFADE_GRIP_ACTIVE_RADIUS * 2) + // Or, if there is no current fade, is the x coordinate close to the clip end? + || (m_clip->endCrossfadeLength() == 0 + && m_clip->length() * ppb / TimePos::ticksPerBar() - me->x() < CROSSFADE_GRIP_ACTIVE_RADIUS * 2)) + // And, is the y coordinate up by the top of the clip? && me->y() <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; if (oldStartState != m_mouseOverStartCrossfadeHandle || oldEndState != m_mouseOverEndCrossfadeHandle) From cabc8119c87a770e20792a91f10cd36dc18e8881 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 21 May 2025 22:18:02 -0400 Subject: [PATCH 05/17] Allow fading selections of clips --- src/gui/clips/ClipView.cpp | 50 +++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index d77f64b29c5..4a7314130b4 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -726,7 +726,20 @@ void ClipView::mousePressEvent( QMouseEvent * me ) { if( isSelected() ) { - m_action = Action::MoveSelection; + if (m_mouseOverStartCrossfadeHandle && m_clip->isCrossfadeable()) + { + m_action = Action::EditStartCrossfade; + setCursor(Qt::SizeHorCursor); + } + else if (m_mouseOverEndCrossfadeHandle && m_clip->isCrossfadeable()) + { + m_action = Action::EditEndCrossfade; + setCursor(Qt::SizeHorCursor); + } + else + { + m_action = Action::MoveSelection; + } } else { @@ -1083,17 +1096,36 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) TimePos::ticksPerBar() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); } - else if (m_action == Action::EditStartCrossfade) - { - TimePos pos = static_cast(me->x() * TimePos::ticksPerBar() / ppb); - m_clip->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(m_clip->length()))); - update(); - } - else if (m_action == Action::EditEndCrossfade) + else if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) { TimePos pos = static_cast(me->x() * TimePos::ticksPerBar() / ppb); - m_clip->setEndCrossfadeLength(std::clamp(static_cast(m_clip->length() - pos), 0, static_cast(m_clip->length()))); + + if (m_action == Action::EditStartCrossfade) + { + m_clip->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(m_clip->length()))); + } + else if (m_action == Action::EditEndCrossfade) + { + m_clip->setEndCrossfadeLength(std::clamp(static_cast(m_clip->length() - pos), 0, static_cast(m_clip->length()))); + } update(); + + // If multiple clips are selected, also set their crossfades + for (auto clipv: getClickedClips()) + { + if (clipv && clipv->getClip()->isCrossfadeable()) + { + if (m_action == Action::EditStartCrossfade) + { + clipv->getClip()->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(m_clip->length()))); + } + else if (m_action == Action::EditEndCrossfade) + { + clipv->getClip()->setEndCrossfadeLength(std::clamp(static_cast(m_clip->length() - pos), 0, static_cast(m_clip->length()))); + } + clipv->update(); + } + } } else if( m_action == Action::Split ) { From dd36f351d06ec5875f7f70f0be35b644793270c3 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 21 May 2025 22:47:33 -0400 Subject: [PATCH 06/17] Add checkpoint --- src/gui/clips/ClipView.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 4a7314130b4..22c29b4b580 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -726,6 +726,8 @@ void ClipView::mousePressEvent( QMouseEvent * me ) { if( isSelected() ) { + for (auto clipv: getClickedClips()) { clipv->getClip()->addJournalCheckPoint(); } + if (m_mouseOverStartCrossfadeHandle && m_clip->isCrossfadeable()) { m_action = Action::EditStartCrossfade; From e4ecad0e0645423f785838b96379e2ea4e9b9647 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Wed, 21 May 2025 23:11:43 -0400 Subject: [PATCH 07/17] Fix journalling issue, and other minor fix --- src/gui/clips/ClipView.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 22c29b4b580..7e894d47587 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -748,12 +748,6 @@ void ClipView::mousePressEvent( QMouseEvent * me ) getGUI()->songEditor()->m_editor->selectAllClips( false ); m_clip->addJournalCheckPoint(); - // Action::Split action doesn't disable Clip journalling - if (m_action != Action::Split) - { - m_clip->setJournalling(false); - } - setInitialPos( me->pos() ); setInitialOffsets(); @@ -1119,11 +1113,11 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) { if (m_action == Action::EditStartCrossfade) { - clipv->getClip()->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(m_clip->length()))); + clipv->getClip()->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(clipv->getClip()->length()))); } else if (m_action == Action::EditEndCrossfade) { - clipv->getClip()->setEndCrossfadeLength(std::clamp(static_cast(m_clip->length() - pos), 0, static_cast(m_clip->length()))); + clipv->getClip()->setEndCrossfadeLength(std::clamp(static_cast(clipv->getClip()->length() - pos), 0, static_cast(clipv->getClip()->length()))); } clipv->update(); } @@ -1186,11 +1180,6 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) { setSelected( !isSelected() ); } - else if (m_action != Action::Split) - { - // TODO: Fix m_clip->setJournalling() consistency - m_clip->setJournalling( true ); - } if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) { From 23d6b352b07343ee76542b2ae24a9da6c2882e3b Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 12:00:09 -0400 Subject: [PATCH 08/17] Fix crash when sample play handle has no clip --- src/core/SamplePlayHandle.cpp | 49 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 1469a8efe73..2058b4a6f36 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -124,31 +124,34 @@ void SamplePlayHandle::play( SampleFrame* buffer ) zeroSampleFrames(workingBuffer, frames); } - // Apply crossfade - const int framesPerTick = Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); - const int startCrossfadeFrames = m_clip->startCrossfadeLength() * framesPerTick; - const int endCrossfadeFrames = m_clip->endCrossfadeLength() * framesPerTick; - const int startOffsetFrames = m_clip->startTimeOffset() * framesPerTick; - const int lengthFrames = m_clip->length() * framesPerTick; - for (f_cnt_t f = 0; f < frames; ++f) + // Apply crossfade if this play handle is for a clip + if (m_clip && (m_clip->startCrossfadeLength() != 0 || m_clip->endCrossfadeLength() != 0)) { - const f_cnt_t frameIndex = initialFrameIndex + f; - const int framesRelativeToClipStart = frameIndex + startOffsetFrames; - const int framesRelativeToClipEnd = lengthFrames - framesRelativeToClipStart; - if (framesRelativeToClipStart < 0 || framesRelativeToClipEnd < 0) { workingBuffer[f] = SampleFrame(); continue; } - - if (framesRelativeToClipStart < startCrossfadeFrames && framesRelativeToClipEnd < endCrossfadeFrames) - { - workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)) - * std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); - } - else if (framesRelativeToClipStart < startCrossfadeFrames) - { - workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)); - } - else if (framesRelativeToClipEnd < endCrossfadeFrames) + const int framesPerTick = Engine::framesPerTick(Engine::audioEngine()->outputSampleRate()); + const int startCrossfadeFrames = m_clip->startCrossfadeLength() * framesPerTick; + const int endCrossfadeFrames = m_clip->endCrossfadeLength() * framesPerTick; + const int startOffsetFrames = m_clip->startTimeOffset() * framesPerTick; + const int lengthFrames = m_clip->length() * framesPerTick; + for (f_cnt_t f = 0; f < frames; ++f) { - workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + const f_cnt_t frameIndex = initialFrameIndex + f; + const int framesRelativeToClipStart = frameIndex + startOffsetFrames; + const int framesRelativeToClipEnd = lengthFrames - framesRelativeToClipStart; + if (framesRelativeToClipStart < 0 || framesRelativeToClipEnd < 0) { workingBuffer[f] = SampleFrame(); continue; } + + if (framesRelativeToClipStart < startCrossfadeFrames && framesRelativeToClipEnd < endCrossfadeFrames) + { + workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)) + * std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + } + else if (framesRelativeToClipStart < startCrossfadeFrames) + { + workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)); + } + else if (framesRelativeToClipEnd < endCrossfadeFrames) + { + workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + } } } } From 0c0e20b0095f2191dbe6d769f7b1265f0593a123 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 12:35:16 -0400 Subject: [PATCH 09/17] Fix TextFloat, Add quantized fading, and fix bug when resizing clips --- src/core/Clip.cpp | 2 ++ src/gui/clips/ClipView.cpp | 49 ++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index 0db8f1a43a2..27ca025eb8c 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -139,6 +139,8 @@ void Clip::movePosition( const TimePos & pos ) void Clip::changeLength( const TimePos & length ) { m_length = length; + m_startCrossfadeLength = std::min(m_startCrossfadeLength, m_length); + m_endCrossfadeLength = std::min(m_endCrossfadeLength, m_length); Engine::getSong()->updateLength(); emit lengthChanged(); } diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 7e894d47587..775e6d30129 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -812,6 +812,22 @@ void ClipView::mousePressEvent( QMouseEvent * me ) arg( m_clip->endPosition().getTicks() % TimePos::ticksPerBar() ) ); } + else if (m_action == Action::EditStartCrossfade) + { + s_textFloat->setTitle(tr("Fade length")); + s_textFloat->setText(QString("%1:%2"). + arg(m_clip->startCrossfadeLength().getBar() + 1). + arg(m_clip->startCrossfadeLength().getTicks() % + TimePos::ticksPerBar())); + } + else if (m_action == Action::EditEndCrossfade) + { + s_textFloat->setTitle(tr("Fade length")); + s_textFloat->setText(QString("%1:%2"). + arg(m_clip->endCrossfadeLength().getBar() + 1). + arg(m_clip->endCrossfadeLength().getTicks() % + TimePos::ticksPerBar())); + } // s_textFloat->reparent( this ); // setup text-float as if Clip was already moved/resized s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); @@ -830,10 +846,14 @@ void ClipView::mousePressEvent( QMouseEvent * me ) ? tr("Press <%1> or for unquantized splitting.\nPress for destructive splitting.") : tr("Press <%1> or for unquantized splitting."); } - else + else if (m_action == Action::Resize || m_action == Action::ResizeLeft) { hint = tr("Press <%1> or for unquantized resizing."); } + else if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) + { + hint = tr("Press <%1> or for quantized fading."); + } m_hint = TextFloat::displayMessage( tr( "Hint" ), hint.arg(UI_COPY_KEY), embed::getIconPixmap( "hint" ), 0 ); } @@ -1094,15 +1114,34 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } else if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) { + const float snapSize = getGUI()->songEditor()->m_editor->getSnapSize(); + TimePos pos = static_cast(me->x() * TimePos::ticksPerBar() / ppb); + TimePos length; if (m_action == Action::EditStartCrossfade) { - m_clip->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(m_clip->length()))); + // If alt or ctrl is held, quantize the length + length = unquantizedModHeld(me) + ? pos.quantize(snapSize) + : pos; + m_clip->setStartCrossfadeLength(std::clamp(static_cast(length), 0, static_cast(m_clip->length()))); + s_textFloat->setText(QString("%1:%2"). + arg(m_clip->startCrossfadeLength().getBar() + 1). + arg(m_clip->startCrossfadeLength().getTicks() % + TimePos::ticksPerBar())); } else if (m_action == Action::EditEndCrossfade) { - m_clip->setEndCrossfadeLength(std::clamp(static_cast(m_clip->length() - pos), 0, static_cast(m_clip->length()))); + // If alt or ctrl is held, quantize the length + length = unquantizedModHeld(me) + ? TimePos(m_clip->length() - pos).quantize(snapSize) + : TimePos(m_clip->length() - pos); + m_clip->setEndCrossfadeLength(std::clamp(static_cast(length), 0, static_cast(m_clip->length()))); + s_textFloat->setText(QString("%1:%2"). + arg(m_clip->endCrossfadeLength().getBar() + 1). + arg(m_clip->endCrossfadeLength().getTicks() % + TimePos::ticksPerBar())); } update(); @@ -1113,11 +1152,11 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) { if (m_action == Action::EditStartCrossfade) { - clipv->getClip()->setStartCrossfadeLength(std::clamp(static_cast(pos), 0, static_cast(clipv->getClip()->length()))); + clipv->getClip()->setStartCrossfadeLength(std::clamp(static_cast(length), 0, static_cast(clipv->getClip()->length()))); } else if (m_action == Action::EditEndCrossfade) { - clipv->getClip()->setEndCrossfadeLength(std::clamp(static_cast(clipv->getClip()->length() - pos), 0, static_cast(clipv->getClip()->length()))); + clipv->getClip()->setEndCrossfadeLength(std::clamp(static_cast(length), 0, static_cast(clipv->getClip()->length()))); } clipv->update(); } From 96225d10e852ad94d029b77db50bf290533374c5 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 17:47:55 -0400 Subject: [PATCH 10/17] Add tension --- include/Clip.h | 23 ++++--- include/ClipView.h | 5 ++ src/core/Clip.cpp | 10 +++- src/core/SampleClip.cpp | 4 ++ src/core/SamplePlayHandle.cpp | 13 ++-- src/gui/clips/ClipView.cpp | 109 +++++++++++++++++++++++++++++++--- 6 files changed, 142 insertions(+), 22 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index 73741b0d0ee..3de44ad77c1 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -151,23 +151,28 @@ class LMMS_EXPORT Clip : public Model, public JournallingObject TimePos startTimeOffset() const; void setStartTimeOffset( const TimePos &startTimeOffset ); - TimePos startCrossfadeLength() const + TimePos startCrossfadeLength() const { return m_startCrossfadeLength; } + void setStartCrossfadeLength(const TimePos& startCrossfadeLength) { - return m_startCrossfadeLength; + m_startCrossfadeLength = std::max(TimePos{0}, startCrossfadeLength); } - void setStartCrossfadeLength(const TimePos &startCrossfadeLength) + TimePos endCrossfadeLength() const { return m_endCrossfadeLength; } + void setEndCrossfadeLength(const TimePos &endCrossfadeLength) { - m_startCrossfadeLength = std::max(TimePos{0}, startCrossfadeLength); + m_endCrossfadeLength = std::max(TimePos{0}, endCrossfadeLength); } - TimePos endCrossfadeLength() const + float startCrossfadeTension() const { return m_startCrossfadeTension; } + void setStartCrossfadeTension(float startCrossfadeTension) { - return m_endCrossfadeLength; + m_startCrossfadeTension = std::clamp(startCrossfadeTension, 0.0f, 1.0f); } - void setEndCrossfadeLength(const TimePos &endCrossfadeLength) + float endCrossfadeTension() const { return m_endCrossfadeTension; } + void setEndCrossfadeTension(float endCrossfadeTension) { - m_endCrossfadeLength = std::max(TimePos{0}, endCrossfadeLength); + m_endCrossfadeTension = std::clamp(endCrossfadeTension, 0.0f, 1.0f); } + virtual bool isCrossfadeable() const { return false; } // Will copy the state of a clip to another clip @@ -202,6 +207,8 @@ public slots: TimePos m_startCrossfadeLength; TimePos m_endCrossfadeLength; + float m_startCrossfadeTension; + float m_endCrossfadeTension; BoolModel m_mutedModel; BoolModel m_soloModel; diff --git a/include/ClipView.h b/include/ClipView.h index ed99b7c95c1..58b8d4f2e5e 100644 --- a/include/ClipView.h +++ b/include/ClipView.h @@ -204,6 +204,8 @@ protected slots: ResizeLeft, EditStartCrossfade, EditEndCrossfade, + EditStartCrossfadeTension, + EditEndCrossfadeTension, Split, CopySelection, ToggleSelected @@ -234,8 +236,11 @@ protected slots: QCursor m_cursorHand; QCursor m_cursorKnife; bool m_cursorSetYet; + bool m_mouseOverStartCrossfadeHandle; + bool m_mouseOverStartCrossfadeTensionHandle; bool m_mouseOverEndCrossfadeHandle; + bool m_mouseOverEndCrossfadeTensionHandle; bool m_needsUpdate; inline void setInitialPos( QPoint pos ) diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index 27ca025eb8c..75d4454c3f3 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -49,6 +49,8 @@ Clip::Clip( Track * track ) : m_length(), m_startCrossfadeLength(0), m_endCrossfadeLength(0), + m_startCrossfadeTension(1.0f - std::numbers::sqrt2 / 2), + m_endCrossfadeTension(1.0f - std::numbers::sqrt2 / 2), m_mutedModel( false, this, tr( "Mute" ) ), m_selectViewOnCreate{false} { @@ -76,6 +78,10 @@ Clip::Clip(const Clip& other): m_startPosition(other.m_startPosition), m_length(other.m_length), m_startTimeOffset(other.m_startTimeOffset), + m_startCrossfadeLength(other.m_startCrossfadeLength), + m_endCrossfadeLength(other.m_endCrossfadeLength), + m_startCrossfadeTension(other.m_startCrossfadeTension), + m_endCrossfadeTension(other.m_endCrossfadeTension), m_mutedModel(other.m_mutedModel.value(), this, tr( "Mute" )), m_resizable(other.m_resizable), m_autoResize(other.m_autoResize), @@ -139,8 +145,8 @@ void Clip::movePosition( const TimePos & pos ) void Clip::changeLength( const TimePos & length ) { m_length = length; - m_startCrossfadeLength = std::min(m_startCrossfadeLength, m_length); - m_endCrossfadeLength = std::min(m_endCrossfadeLength, m_length); + setStartCrossfadeLength(std::min(m_startCrossfadeLength, m_length)); + setEndCrossfadeLength(std::min(m_endCrossfadeLength, m_length)); Engine::getSong()->updateLength(); emit lengthChanged(); } diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 59a094e5440..cc54044e4bd 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -310,6 +310,8 @@ void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute("autoresize", QString::number(getAutoResize())); _this.setAttribute("startfadelength", QString::number(startCrossfadeLength())); _this.setAttribute("endfadelength", QString::number(endCrossfadeLength())); + _this.setAttribute("startfadetension", QString::number(startCrossfadeTension())); + _this.setAttribute("endfadetension", QString::number(endCrossfadeTension())); if( sampleFile() == "" ) { QString s; @@ -361,6 +363,8 @@ void SampleClip::loadSettings( const QDomElement & _this ) setAutoResize(_this.attribute("autoresize", "1").toInt()); setStartCrossfadeLength(_this.attribute("startfadelength").toInt()); setEndCrossfadeLength(_this.attribute("endfadelength").toInt()); + setStartCrossfadeTension(_this.attribute("startfadetension", QString::number(1.0f - std::numbers::sqrt2 / 2)).toFloat()); + setEndCrossfadeTension(_this.attribute("endfadetension", QString::number(1.0f - std::numbers::sqrt2 / 2)).toFloat()); if (_this.hasAttribute("color")) { diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 2058b4a6f36..78d6b632878 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -26,6 +26,7 @@ #include "AudioEngine.h" #include "AudioBusHandle.h" #include "Engine.h" +#include "lmms_math.h" #include "MixHelpers.h" #include "Note.h" #include "PatternTrack.h" @@ -132,6 +133,9 @@ void SamplePlayHandle::play( SampleFrame* buffer ) const int endCrossfadeFrames = m_clip->endCrossfadeLength() * framesPerTick; const int startOffsetFrames = m_clip->startTimeOffset() * framesPerTick; const int lengthFrames = m_clip->length() * framesPerTick; + const float startPower = std::log(1.0f - m_clip->startCrossfadeTension()) / -std::numbers::ln2; + const float endPower = std::log(1.0f - m_clip->endCrossfadeTension()) / -std::numbers::ln2; + for (f_cnt_t f = 0; f < frames; ++f) { const f_cnt_t frameIndex = initialFrameIndex + f; @@ -141,16 +145,17 @@ void SamplePlayHandle::play( SampleFrame* buffer ) if (framesRelativeToClipStart < startCrossfadeFrames && framesRelativeToClipEnd < endCrossfadeFrames) { - workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)) - * std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + workingBuffer[f] *= + fastPow(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames), startPower) + * fastPow(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames), endPower); } else if (framesRelativeToClipStart < startCrossfadeFrames) { - workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames)); + workingBuffer[f] *= fastPow(static_cast(framesRelativeToClipStart) / static_cast(startCrossfadeFrames), startPower); } else if (framesRelativeToClipEnd < endCrossfadeFrames) { - workingBuffer[f] *= std::sqrt(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames)); + workingBuffer[f] *= fastPow(static_cast(framesRelativeToClipEnd) / static_cast(endCrossfadeFrames), endPower); } } } diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 775e6d30129..85a62203ba8 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -43,6 +43,7 @@ #include "InstrumentTrack.h" #include "InstrumentTrackView.h" #include "KeyboardShortcuts.h" +#include "lmms_math.h" #include "MidiClip.h" #include "MidiClipView.h" #include "Note.h" @@ -114,7 +115,9 @@ ClipView::ClipView( Clip * clip, m_cursorKnife( QCursor( embed::getIconPixmap( "cursor_knife" ) ) ), m_cursorSetYet( false ), m_mouseOverStartCrossfadeHandle(false), + m_mouseOverStartCrossfadeTensionHandle(false), m_mouseOverEndCrossfadeHandle(false), + m_mouseOverEndCrossfadeTensionHandle(false), m_needsUpdate( true ) { if( s_textFloat == nullptr ) @@ -623,6 +626,8 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) float ticksPerBar = DefaultTicksPerBar * nom / den; int startCrossfadeLength = m_clip->startCrossfadeLength() / ticksPerBar * pixelsPerBar(); int endCrossfadeLength = m_clip->endCrossfadeLength() / ticksPerBar * pixelsPerBar(); + float startCrossfadeTension = m_clip->startCrossfadeTension(); + float endCrossfadeTension = m_clip->endCrossfadeTension(); if (m_clip->startCrossfadeLength() > 0) { @@ -632,7 +637,7 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) float lastValue = 0; for (int i = 0; i < startCrossfadeLength; ++i) { - float nextValue = std::sqrt(static_cast(i) / static_cast(startCrossfadeLength)); + float nextValue = fastPow(static_cast(i) / static_cast(startCrossfadeLength), std::log(1.0f - startCrossfadeTension) / -std::numbers::ln2); startPath.lineTo(QPoint(i + 1, (1.0f - nextValue) * rect.height())); painter.drawLine(i, (1.0f - lastValue) * rect.height(), i + 1, (1.0f - nextValue) * rect.height()); lastValue = nextValue; @@ -649,6 +654,15 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) painter.drawEllipse(std::max(0, startCrossfadeLength - CROSSFADE_GRIP_RADIUS), 0, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); } + if ((m_mouseOverStartCrossfadeTensionHandle || m_action == Action::EditStartCrossfadeTension) && m_clip->startCrossfadeLength() > 0) + { + painter.drawEllipse(std::max(0, startCrossfadeLength / 2 - CROSSFADE_GRIP_ACTIVE_RADIUS), startCrossfadeTension * rect.height() - CROSSFADE_GRIP_ACTIVE_RADIUS, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); + } + else if (m_clip->startCrossfadeLength() > 0) + { + painter.drawEllipse(std::max(0, startCrossfadeLength / 2 - CROSSFADE_GRIP_RADIUS), startCrossfadeTension * rect.height() - CROSSFADE_GRIP_RADIUS, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); + } + if (m_clip->endCrossfadeLength() > 0) { QPainterPath endPath; @@ -657,7 +671,7 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) float lastValue = 0; for (int i = 1; i < endCrossfadeLength; ++i) { - float nextValue = std::sqrt(static_cast(i) / static_cast(endCrossfadeLength)); + float nextValue = fastPow(static_cast(i) / static_cast(endCrossfadeLength), std::log(1.0f - endCrossfadeTension) / -std::numbers::ln2); endPath.lineTo(QPoint(rect.width() - i - 1, (1.0f - nextValue) * rect.height())); painter.drawLine(rect.width() - i, (1.0f - lastValue) * rect.height(), rect.width() - i - 1, (1.0f - nextValue) * rect.height()); lastValue = nextValue; @@ -665,6 +679,7 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) endPath.lineTo(QPoint(rect.width() - endCrossfadeLength, 0)); painter.fillPath(endPath, QColor(0,0,0,200)); } + if (m_mouseOverEndCrossfadeHandle || m_action == Action::EditEndCrossfade) { painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_ACTIVE_RADIUS * 2, rect.width() - endCrossfadeLength - CROSSFADE_GRIP_ACTIVE_RADIUS), 0, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); @@ -673,12 +688,23 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) { painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_RADIUS * 2, rect.width() - endCrossfadeLength - CROSSFADE_GRIP_RADIUS), 0, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); } + + if ((m_mouseOverEndCrossfadeTensionHandle || m_action == Action::EditEndCrossfadeTension) && m_clip->endCrossfadeLength() > 0) + { + painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_ACTIVE_RADIUS * 2, rect.width() - endCrossfadeLength / 2 - CROSSFADE_GRIP_ACTIVE_RADIUS), endCrossfadeTension * rect.height() - CROSSFADE_GRIP_ACTIVE_RADIUS, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); + } + else if (m_clip->endCrossfadeLength() > 0) + { + painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_RADIUS * 2, rect.width() - endCrossfadeLength / 2 - CROSSFADE_GRIP_RADIUS), endCrossfadeTension * rect.height() - CROSSFADE_GRIP_RADIUS, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); + } } void ClipView::leaveEvent(QEvent* e) { m_mouseOverStartCrossfadeHandle = false; m_mouseOverEndCrossfadeHandle = false; + m_mouseOverStartCrossfadeTensionHandle = false; + m_mouseOverEndCrossfadeTensionHandle = false; update(); } @@ -738,6 +764,16 @@ void ClipView::mousePressEvent( QMouseEvent * me ) m_action = Action::EditEndCrossfade; setCursor(Qt::SizeHorCursor); } + else if (m_mouseOverStartCrossfadeTensionHandle && m_clip->startCrossfadeLength() > 0 && m_clip->isCrossfadeable()) + { + m_action = Action::EditStartCrossfadeTension; + setCursor(Qt::SizeVerCursor); + } + else if (m_mouseOverEndCrossfadeTensionHandle && m_clip->endCrossfadeLength() > 0 && m_clip->isCrossfadeable()) + { + m_action = Action::EditEndCrossfadeTension; + setCursor(Qt::SizeVerCursor); + } else { m_action = Action::MoveSelection; @@ -766,6 +802,16 @@ void ClipView::mousePressEvent( QMouseEvent * me ) m_action = Action::EditEndCrossfade; setCursor(Qt::SizeHorCursor); } + else if (m_mouseOverStartCrossfadeTensionHandle && m_clip->startCrossfadeLength() > 0 && m_clip->isCrossfadeable()) + { + m_action = Action::EditStartCrossfadeTension; + setCursor(Qt::SizeVerCursor); + } + else if (m_mouseOverEndCrossfadeTensionHandle && m_clip->endCrossfadeLength() > 0 && m_clip->isCrossfadeable()) + { + m_action = Action::EditEndCrossfadeTension; + setCursor(Qt::SizeVerCursor); + } else if( me->x() >= width() - RESIZE_GRIP_WIDTH ) { m_action = Action::Resize; @@ -854,8 +900,11 @@ void ClipView::mousePressEvent( QMouseEvent * me ) { hint = tr("Press <%1> or for quantized fading."); } - m_hint = TextFloat::displayMessage( tr( "Hint" ), hint.arg(UI_COPY_KEY), - embed::getIconPixmap( "hint" ), 0 ); + if (hint != "") + { + m_hint = TextFloat::displayMessage(tr("Hint"), hint.arg(UI_COPY_KEY), + embed::getIconPixmap("hint"), 0); + } } } else if( me->button() == Qt::RightButton ) @@ -1148,7 +1197,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) // If multiple clips are selected, also set their crossfades for (auto clipv: getClickedClips()) { - if (clipv && clipv->getClip()->isCrossfadeable()) + if (clipv->getClip()->isCrossfadeable()) { if (m_action == Action::EditStartCrossfade) { @@ -1162,6 +1211,30 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } } } + else if (m_action == Action::EditStartCrossfadeTension || m_action == Action::EditEndCrossfadeTension) + { + float tension = static_cast(me->y()) / height(); + if (m_action == Action::EditStartCrossfadeTension) { m_clip->setStartCrossfadeTension(tension); } + if (m_action == Action::EditEndCrossfadeTension) { m_clip->setEndCrossfadeTension(tension); } + update(); + + // If multiple clips are selected, also set their tensions + for (auto clipv: getClickedClips()) + { + if (clipv->getClip()->isCrossfadeable()) + { + if (m_action == Action::EditStartCrossfadeTension) + { + clipv->getClip()->setStartCrossfadeTension(tension); + } + else if (m_action == Action::EditEndCrossfadeTension) + { + clipv->getClip()->setEndCrossfadeTension(tension); + } + clipv->update(); + } + } + } else if( m_action == Action::Split ) { setCursor(m_cursorKnife); @@ -1173,6 +1246,8 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) bool oldStartState = m_mouseOverStartCrossfadeHandle; bool oldEndState = m_mouseOverEndCrossfadeHandle; + bool oldStartTensionState = m_mouseOverStartCrossfadeTensionHandle; + bool oldEndTensionState = m_mouseOverEndCrossfadeTensionHandle; m_mouseOverStartCrossfadeHandle = // Is the x coordinate within the radius of the handle? @@ -1184,7 +1259,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) && me->y() <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; m_mouseOverEndCrossfadeHandle = - // Is the x coordinate within the radius of the handle? + // Is the x coordinate within the radius of the handle? (std::abs(me->x() - (m_clip->length() - m_clip->endCrossfadeLength()) * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS // Or, if there is no current fade, is the x coordinate close to the clip end? || (m_clip->endCrossfadeLength() == 0 @@ -1192,7 +1267,22 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) // And, is the y coordinate up by the top of the clip? && me->y() <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; - if (oldStartState != m_mouseOverStartCrossfadeHandle || oldEndState != m_mouseOverEndCrossfadeHandle) + m_mouseOverStartCrossfadeTensionHandle = + // Is the x coordinate within the radius of the handle? + std::abs(me->x() - m_clip->startCrossfadeLength() / 2 * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS + // And, is the y coordinate at the height of the tension? + && std::abs(me->y() - m_clip->startCrossfadeTension() * height()) <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; + + m_mouseOverEndCrossfadeTensionHandle = + // Is the x coordinate within the radius of the handle? + std::abs(me->x() - (m_clip->length() - m_clip->endCrossfadeLength() / 2) * ppb / TimePos::ticksPerBar()) <= CROSSFADE_GRIP_ACTIVE_RADIUS + // And, is the y coordinate at the height of the tension? + && std::abs(me->y() - m_clip->endCrossfadeTension() * height()) <= CROSSFADE_GRIP_ACTIVE_RADIUS * 2; + + if (oldStartState != m_mouseOverStartCrossfadeHandle + || oldEndState != m_mouseOverEndCrossfadeHandle + || oldStartTensionState != m_mouseOverStartCrossfadeTensionHandle + || oldEndTensionState != m_mouseOverEndCrossfadeTensionHandle) { update(); } @@ -1220,7 +1310,10 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) setSelected( !isSelected() ); } - if (m_action == Action::EditStartCrossfade || m_action == Action::EditEndCrossfade) + if (m_action == Action::EditStartCrossfade + || m_action == Action::EditEndCrossfade + || m_action == Action::EditStartCrossfadeTension + || m_action == Action::EditEndCrossfadeTension) { update(); } From 33bd6b5ce33717f256f34190a38c687a9d33e11a Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 18:22:21 -0400 Subject: [PATCH 11/17] Clean up drawCrossfade() --- src/gui/clips/ClipView.cpp | 58 +++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 85a62203ba8..d47e13fb6e0 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -629,6 +629,17 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) float startCrossfadeTension = m_clip->startCrossfadeTension(); float endCrossfadeTension = m_clip->endCrossfadeTension(); + auto drawHandle = [&](int x, int y, int radius) + { + painter.drawEllipse( + std::clamp(x - radius, 0, rect.width() - radius * 2), + std::clamp(y - radius, 0, rect.height() - radius * 2), + radius * 2, + radius * 2 + ); + }; + + // Draw fade in curve if (m_clip->startCrossfadeLength() > 0) { QPainterPath startPath; @@ -644,25 +655,9 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) } startPath.lineTo(QPoint(startCrossfadeLength, 0)); painter.fillPath(startPath, QColor(0,0,0,200)); - } - if (m_mouseOverStartCrossfadeHandle || m_action == Action::EditStartCrossfade) - { - painter.drawEllipse(std::max(0, startCrossfadeLength - CROSSFADE_GRIP_ACTIVE_RADIUS), 0, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); - } - else if (m_clip->startCrossfadeLength() > 0) - { - painter.drawEllipse(std::max(0, startCrossfadeLength - CROSSFADE_GRIP_RADIUS), 0, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); - } - - if ((m_mouseOverStartCrossfadeTensionHandle || m_action == Action::EditStartCrossfadeTension) && m_clip->startCrossfadeLength() > 0) - { - painter.drawEllipse(std::max(0, startCrossfadeLength / 2 - CROSSFADE_GRIP_ACTIVE_RADIUS), startCrossfadeTension * rect.height() - CROSSFADE_GRIP_ACTIVE_RADIUS, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); - } - else if (m_clip->startCrossfadeLength() > 0) - { - painter.drawEllipse(std::max(0, startCrossfadeLength / 2 - CROSSFADE_GRIP_RADIUS), startCrossfadeTension * rect.height() - CROSSFADE_GRIP_RADIUS, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); - } + }; + // Draw fade out curve if (m_clip->endCrossfadeLength() > 0) { QPainterPath endPath; @@ -680,22 +675,41 @@ void ClipView::drawCrossfade(QPainter& painter, QRect rect) painter.fillPath(endPath, QColor(0,0,0,200)); } + // Draw handles + if (m_mouseOverStartCrossfadeHandle || m_action == Action::EditStartCrossfade) + { + drawHandle(startCrossfadeLength, 0, CROSSFADE_GRIP_ACTIVE_RADIUS); + } + else if (m_clip->startCrossfadeLength() > 0) + { + drawHandle(startCrossfadeLength, 0, CROSSFADE_GRIP_RADIUS); + } + if (m_mouseOverEndCrossfadeHandle || m_action == Action::EditEndCrossfade) { - painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_ACTIVE_RADIUS * 2, rect.width() - endCrossfadeLength - CROSSFADE_GRIP_ACTIVE_RADIUS), 0, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); + drawHandle(rect.width() - endCrossfadeLength, 0, CROSSFADE_GRIP_ACTIVE_RADIUS); } else if (m_clip->endCrossfadeLength() > 0) { - painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_RADIUS * 2, rect.width() - endCrossfadeLength - CROSSFADE_GRIP_RADIUS), 0, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); + drawHandle(rect.width() - endCrossfadeLength, 0, CROSSFADE_GRIP_RADIUS); + } + + if ((m_mouseOverStartCrossfadeTensionHandle || m_action == Action::EditStartCrossfadeTension) && m_clip->startCrossfadeLength() > 0) + { + drawHandle(startCrossfadeLength / 2, startCrossfadeTension * rect.height(), CROSSFADE_GRIP_ACTIVE_RADIUS); + } + else if (m_clip->startCrossfadeLength() > 0) + { + drawHandle(startCrossfadeLength / 2, startCrossfadeTension * rect.height(), CROSSFADE_GRIP_RADIUS); } if ((m_mouseOverEndCrossfadeTensionHandle || m_action == Action::EditEndCrossfadeTension) && m_clip->endCrossfadeLength() > 0) { - painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_ACTIVE_RADIUS * 2, rect.width() - endCrossfadeLength / 2 - CROSSFADE_GRIP_ACTIVE_RADIUS), endCrossfadeTension * rect.height() - CROSSFADE_GRIP_ACTIVE_RADIUS, CROSSFADE_GRIP_ACTIVE_RADIUS*2, CROSSFADE_GRIP_ACTIVE_RADIUS*2); + drawHandle(rect.width() - endCrossfadeLength / 2, endCrossfadeTension * rect.height(), CROSSFADE_GRIP_ACTIVE_RADIUS); } else if (m_clip->endCrossfadeLength() > 0) { - painter.drawEllipse(std::min(rect.width() - CROSSFADE_GRIP_RADIUS * 2, rect.width() - endCrossfadeLength / 2 - CROSSFADE_GRIP_RADIUS), endCrossfadeTension * rect.height() - CROSSFADE_GRIP_RADIUS, CROSSFADE_GRIP_RADIUS*2, CROSSFADE_GRIP_RADIUS*2); + drawHandle(rect.width() - endCrossfadeLength / 2, endCrossfadeTension * rect.height(), CROSSFADE_GRIP_RADIUS); } } From 6ae62167caae183e407203b5a36b08b6fea09626 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 18:23:46 -0400 Subject: [PATCH 12/17] Fix warning --- include/SampleClip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SampleClip.h b/include/SampleClip.h index 02cdc588915..3a73ed0b412 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -89,7 +89,7 @@ class SampleClip : public Clip public slots: void setSampleFile(const QString& sf); - void updateLength(); + void updateLength() override; void toggleRecord(); void playbackPositionChanged(); void updateTrackClips(); From 2b1e664a301012b0880eed4bd9b5da5d50fa334e Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 18:24:33 -0400 Subject: [PATCH 13/17] Fix other warning --- src/core/SampleClip.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index cc54044e4bd..8474cf1423a 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -26,6 +26,7 @@ #include #include +#include #include "PathUtil.h" #include "SampleBuffer.h" From 6bb07ff09f86d8f2c0decb6bd5c3f62e43a4eafa Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Thu, 22 May 2025 19:55:41 -0400 Subject: [PATCH 14/17] Attempt to resolve std::numbers warning on macos, msvc, and windows arm --- src/core/Clip.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index 75d4454c3f3..2ae459c85ed 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -49,8 +49,8 @@ Clip::Clip( Track * track ) : m_length(), m_startCrossfadeLength(0), m_endCrossfadeLength(0), - m_startCrossfadeTension(1.0f - std::numbers::sqrt2 / 2), - m_endCrossfadeTension(1.0f - std::numbers::sqrt2 / 2), + m_startCrossfadeTension(1.0f - std::sqrt(2) / 2), + m_endCrossfadeTension(1.0f - std::sqrt(2) / 2), m_mutedModel( false, this, tr( "Mute" ) ), m_selectViewOnCreate{false} { From de95c56956de2896148abe46d65ca11b5cc0164c Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Mon, 26 May 2025 17:55:55 -0400 Subject: [PATCH 15/17] Add alt/ctrl snap for linear and sqrt fading tension --- src/gui/clips/ClipView.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index d47e13fb6e0..4ff4c747703 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -1228,6 +1228,13 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) else if (m_action == Action::EditStartCrossfadeTension || m_action == Action::EditEndCrossfadeTension) { float tension = static_cast(me->y()) / height(); + // If alt or ctrl is held, snap to 0.5 or 1-sqrt(2)/2 + if (unquantizedModHeld(me)) + { + if (std::abs(tension - 0.5) <= std::abs(tension - (1.0f - std::numbers::sqrt2 / 2))) { tension = 0.5; } + else { tension = 1.0f - std::numbers::sqrt2 / 2; } + } + if (m_action == Action::EditStartCrossfadeTension) { m_clip->setStartCrossfadeTension(tension); } if (m_action == Action::EditEndCrossfadeTension) { m_clip->setEndCrossfadeTension(tension); } update(); From d7459b4cdad6a8a0cbc7a1f905b4403567e84038 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Mon, 26 May 2025 19:38:32 -0400 Subject: [PATCH 16/17] Fix TextFloats and fix mvsc warning --- src/gui/clips/ClipView.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 4ff4c747703..542344fb117 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -888,6 +888,16 @@ void ClipView::mousePressEvent( QMouseEvent * me ) arg(m_clip->endCrossfadeLength().getTicks() % TimePos::ticksPerBar())); } + else if (m_action == Action::EditStartCrossfadeTension) + { + s_textFloat->setTitle(tr("Tension")); + s_textFloat->setText(QString("%1").arg(m_clip->startCrossfadeTension())); + } + else if (m_action == Action::EditEndCrossfadeTension) + { + s_textFloat->setTitle(tr("Tension")); + s_textFloat->setText(QString("%1").arg(m_clip->endCrossfadeTension())); + } // s_textFloat->reparent( this ); // setup text-float as if Clip was already moved/resized s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); @@ -914,6 +924,10 @@ void ClipView::mousePressEvent( QMouseEvent * me ) { hint = tr("Press <%1> or for quantized fading."); } + else if (m_action == Action::EditStartCrossfadeTension || m_action == Action::EditEndCrossfadeTension) + { + hint = tr("Press <%1> or to snap tension."); + } if (hint != "") { m_hint = TextFloat::displayMessage(tr("Hint"), hint.arg(UI_COPY_KEY), @@ -1231,8 +1245,8 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) // If alt or ctrl is held, snap to 0.5 or 1-sqrt(2)/2 if (unquantizedModHeld(me)) { - if (std::abs(tension - 0.5) <= std::abs(tension - (1.0f - std::numbers::sqrt2 / 2))) { tension = 0.5; } - else { tension = 1.0f - std::numbers::sqrt2 / 2; } + if (std::abs(tension - 0.5f) <= std::abs(tension - (1.0f - 0.5f * std::numbers::sqrt2))) { tension = 0.5f; } + else { tension = 1.0f - 0.5f * std::numbers::sqrt2; } } if (m_action == Action::EditStartCrossfadeTension) { m_clip->setStartCrossfadeTension(tension); } From 840b2ad063b8dbffedb29bfb2dabda587bd98ea8 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Mon, 26 May 2025 20:07:11 -0400 Subject: [PATCH 17/17] Attempt to fix msvc error --- src/gui/clips/ClipView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 542344fb117..bc0bb6d412d 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -1245,8 +1245,8 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) // If alt or ctrl is held, snap to 0.5 or 1-sqrt(2)/2 if (unquantizedModHeld(me)) { - if (std::abs(tension - 0.5f) <= std::abs(tension - (1.0f - 0.5f * std::numbers::sqrt2))) { tension = 0.5f; } - else { tension = 1.0f - 0.5f * std::numbers::sqrt2; } + if (std::abs(tension - 0.5f) <= std::abs(tension - (1.0f - 0.5f * static_cast(std::numbers::sqrt2)))) { tension = 0.5f; } + else { tension = 1.0f - 0.5f * static_cast(std::numbers::sqrt2); } } if (m_action == Action::EditStartCrossfadeTension) { m_clip->setStartCrossfadeTension(tension); }