Refactor to move positionChanged signal to Timeline (#7454)
Previously, this PR simply added a new signal to the Timeline class and had it emitted by the TimeLineWidget class in order to solve #7351. However, as messmerd pointed out in his review comments, that was quite a hacky solution, and ideally the positionChanged signal would be solely managed by the backend Timeline class, while the frontend TimeLineWidget class would connect to those signals, instead of the other way around. This PR is no longer a simple bugfix, but instead a refactoring of the Timeline/TimeLineWidget signal/slots. Changes - The positionChanged signal and updatePosition slot were removed from TimeLineWidget and moved to Timeline. - Removed PlayPos, and instead store the timeline position in a TimePos along with a separate frame offset counter variable. The functions to set the ticks/timepos in Timeline emit the positionChanged signal. (Also, may emit positionJumped signal if the ticks were forcefully set--see below) - The pos() method and PlayPos m_pos were removed from TimeLineWidget; - The constructor for TimeLineWidget no longer requires a PlayPos reference. - Since each TimeLineWidget stores a reference to its Timeline, a new method was added, timeline(), for other classes to access it. - Removed array of PlayPos'es in Song. Now each Timeline holds their respective TimePos. - Song's methods for getPlayPos were changed to return the TimePos of the respective Timeline. The non-const versions of the methods were removed because Timeline does not expose its TimePos to write. - All of the places where Timelines are used were updated, along with their calls to access/modify the PlayPos. For example, occurrences of m_timeline->pos() were replaced with m_timeline->timeline()->pos(), and calls to m_timeline->pos().setTicks(ticks) were changed to m_timeline->timeline()->setTicks(ticks). - ALSO: Removed m_elapsedMilliseconds, m_elapsedBars, and m_elapsedTicks from Song. The elapsed milliseconds is now handled individually by each Timeline. - NEW: The m_jumped variable has been removed from Timeline. Now jumped events emit Timeline::positionJumped automatically whenever the ticks are forcefully set. This means it is no longer necessary to call timeline->setJumped(true) or timeline->setFrameOffset(0) whenever the playhead position is forcefully set. Many places in the codebase were already missing these calls, so this may fix some unknown bugs. --------- Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com> Co-authored-by: saker <sakertooth@gmail.com> Co-authored-by: Alex <allejok96@gmail.com>
This commit is contained in:
@@ -66,8 +66,8 @@ LfoController::LfoController( Model * _parent ) :
|
||||
|
||||
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
|
||||
this, SLOT(updatePhase()));
|
||||
connect( Engine::getSong(), SIGNAL(playbackPositionChanged()),
|
||||
this, SLOT(updatePhase()));
|
||||
connect(Engine::getSong(), &Song::playbackPositionJumped,
|
||||
this, &LfoController::updatePhase);
|
||||
|
||||
updateDuration();
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying)
|
||||
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
//care about loops and jumps
|
||||
connect( Engine::getSong(), SIGNAL(updateSampleTracks()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
connect(Engine::getSong(), &Song::playbackPositionJumped,
|
||||
this, &SampleClip::playbackPositionChanged, Qt::DirectConnection);
|
||||
//care about mute Clips
|
||||
connect( this, SIGNAL(dataChanged()), this, SLOT(playbackPositionChanged()));
|
||||
//care about mute track
|
||||
@@ -94,8 +94,8 @@ SampleClip::SampleClip(const SampleClip& orig) :
|
||||
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
//care about loops and jumps
|
||||
connect( Engine::getSong(), SIGNAL(updateSampleTracks()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
connect(Engine::getSong(), &Song::playbackPositionJumped,
|
||||
this, &SampleClip::playbackPositionChanged, Qt::DirectConnection);
|
||||
//care about mute Clips
|
||||
connect( this, SIGNAL(dataChanged()), this, SLOT(playbackPositionChanged()));
|
||||
//care about mute track
|
||||
|
||||
@@ -92,13 +92,10 @@ Song::Song() :
|
||||
m_length( 0 ),
|
||||
m_midiClipToPlay( nullptr ),
|
||||
m_loopMidiClip( false ),
|
||||
m_elapsedTicks( 0 ),
|
||||
m_elapsedBars( 0 ),
|
||||
m_loopRenderCount(1),
|
||||
m_loopRenderRemaining(1),
|
||||
m_oldAutomatedValues()
|
||||
{
|
||||
for (double& millisecondsElapsed : m_elapsedMilliSeconds) { millisecondsElapsed = 0; }
|
||||
connect( &m_tempoModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(setTempo()), Qt::DirectConnection );
|
||||
connect( &m_tempoModel, SIGNAL(dataUnchanged()),
|
||||
@@ -120,6 +117,19 @@ Song::Song() :
|
||||
|
||||
for (auto& scale : m_scales) {scale = std::make_shared<Scale>();}
|
||||
for (auto& keymap : m_keymaps) {keymap = std::make_shared<Keymap>();}
|
||||
|
||||
// Aggregate the `positionJumped` signals from all the timelines into a single `playbackPositionJumped` signal for other objects (sample tracks, LFOs, etc) to use.
|
||||
for (auto& timeline : m_timelines)
|
||||
{
|
||||
connect(&timeline, &Timeline::positionJumped, this, [this](){
|
||||
// Only emit the signal when the song is actually playing
|
||||
// This prevents LFOs from changing phase when the user drags the timeline while paused
|
||||
if (isPlaying()) { emit playbackPositionJumped(); }
|
||||
}, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
// Inform VST plugins if the user moved the play head
|
||||
connect(this, &Song::playbackPositionJumped, this, [this](){ m_vstSyncController.setPlaybackJumped(true); }, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
|
||||
@@ -193,8 +203,6 @@ void Song::savePlayStartPosition()
|
||||
|
||||
void Song::processNextBuffer()
|
||||
{
|
||||
m_vstSyncController.setPlaybackJumped(false);
|
||||
|
||||
// If nothing is playing, there is nothing to do
|
||||
if (!m_playing) { return; }
|
||||
|
||||
@@ -234,6 +242,8 @@ void Song::processNextBuffer()
|
||||
return;
|
||||
}
|
||||
|
||||
auto& timeline = getTimeline();
|
||||
|
||||
// If the playback position is outside of the range [begin, end), move it to
|
||||
// begin and inform interested parties.
|
||||
// Returns true if the playback position was moved, else false.
|
||||
@@ -241,28 +251,17 @@ void Song::processNextBuffer()
|
||||
{
|
||||
if (getPlayPos() < begin || getPlayPos() >= end)
|
||||
{
|
||||
setToTime(begin);
|
||||
m_vstSyncController.setPlaybackJumped(true);
|
||||
emit updateSampleTracks();
|
||||
getTimeline().setTicks(begin.getTicks());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto& timeline = getTimeline();
|
||||
const auto loopEnabled = !m_exporting && timeline.loopEnabled();
|
||||
|
||||
// Ensure playback begins within the loop if it is enabled
|
||||
if (loopEnabled) { enforceLoop(timeline.loopBegin(), timeline.loopEnd()); }
|
||||
|
||||
// Inform VST plugins and sample tracks if the user moved the play head
|
||||
if (getPlayPos().jumped())
|
||||
{
|
||||
m_vstSyncController.setPlaybackJumped(true);
|
||||
emit updateSampleTracks();
|
||||
getPlayPos().setJumped(false);
|
||||
}
|
||||
|
||||
const auto framesPerTick = Engine::framesPerTick();
|
||||
const auto framesPerPeriod = Engine::audioEngine()->framesPerPeriod();
|
||||
|
||||
@@ -270,16 +269,16 @@ void Song::processNextBuffer()
|
||||
|
||||
while (frameOffsetInPeriod < framesPerPeriod)
|
||||
{
|
||||
auto frameOffsetInTick = getPlayPos().currentFrame();
|
||||
auto frameOffsetInTick = timeline.frameOffset();
|
||||
|
||||
// If a whole tick has elapsed, update the frame and tick count, and check any loops
|
||||
if (frameOffsetInTick >= framesPerTick)
|
||||
{
|
||||
// Transfer any whole ticks from the frame count to the tick count
|
||||
const auto elapsedTicks = static_cast<int>(frameOffsetInTick / framesPerTick);
|
||||
getPlayPos().setTicks(getPlayPos().getTicks() + elapsedTicks);
|
||||
frameOffsetInTick -= elapsedTicks * framesPerTick;
|
||||
getPlayPos().setCurrentFrame(frameOffsetInTick);
|
||||
timeline.incrementTicks(elapsedTicks);
|
||||
timeline.setFrameOffset(frameOffsetInTick);
|
||||
|
||||
// If we are playing a pattern track, or a MIDI clip with no loop enabled,
|
||||
// loop back to the beginning when we reach the end
|
||||
@@ -323,7 +322,7 @@ void Song::processNextBuffer()
|
||||
// This must be done after we've corrected the frame/tick count,
|
||||
// but before actually playing any frames.
|
||||
m_vstSyncController.setAbsolutePosition(getPlayPos().getTicks()
|
||||
+ getPlayPos().currentFrame() / static_cast<double>(framesPerTick));
|
||||
+ timeline.frameOffset() / static_cast<double>(framesPerTick));
|
||||
m_vstSyncController.update();
|
||||
}
|
||||
|
||||
@@ -342,11 +341,11 @@ void Song::processNextBuffer()
|
||||
// Update frame counters
|
||||
frameOffsetInPeriod += framesToPlay;
|
||||
frameOffsetInTick += framesToPlay;
|
||||
getPlayPos().setCurrentFrame(frameOffsetInTick);
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] += TimePos::ticksToMilliseconds(framesToPlay / framesPerTick, getTempo());
|
||||
m_elapsedBars = getPlayPos(PlayMode::Song).getBar();
|
||||
m_elapsedTicks = (getPlayPos(PlayMode::Song).getTicks() % ticksPerBar()) / 48;
|
||||
timeline.setFrameOffset(frameOffsetInTick);
|
||||
}
|
||||
|
||||
// Reset the jumped state after processing this buffer, since presumably it has now been handled.
|
||||
m_vstSyncController.setPlaybackJumped(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -603,26 +602,6 @@ void Song::updateLength()
|
||||
|
||||
|
||||
|
||||
void Song::setPlayPos( tick_t ticks, PlayMode playMode )
|
||||
{
|
||||
tick_t ticksFromPlayMode = getPlayPos(playMode).getTicks();
|
||||
m_elapsedTicks += ticksFromPlayMode - ticks;
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(playMode)] += TimePos::ticksToMilliseconds( ticks - ticksFromPlayMode, getTempo() );
|
||||
getPlayPos(playMode).setTicks( ticks );
|
||||
getPlayPos(playMode).setCurrentFrame( 0.0f );
|
||||
getPlayPos(playMode).setJumped( true );
|
||||
|
||||
// send a signal if playposition changes during playback
|
||||
if( isPlaying() )
|
||||
{
|
||||
emit playbackPositionChanged();
|
||||
emit updateSampleTracks();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Song::togglePause()
|
||||
{
|
||||
// Pause/unpause only works when something is actually playing
|
||||
@@ -674,20 +653,18 @@ void Song::stop()
|
||||
case Timeline::StopBehaviour::BackToZero:
|
||||
if (m_playMode == PlayMode::MidiClip)
|
||||
{
|
||||
getPlayPos().setTicks(std::max(0, -m_midiClipToPlay->startTimeOffset()));
|
||||
timeline.setTicks(std::max(0, -m_midiClipToPlay->startTimeOffset()));
|
||||
}
|
||||
else
|
||||
{
|
||||
getPlayPos().setTicks(0);
|
||||
timeline.setTicks(0);
|
||||
}
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
|
||||
break;
|
||||
|
||||
case Timeline::StopBehaviour::BackToStart:
|
||||
if (timeline.playStartPosition() >= 0)
|
||||
{
|
||||
getPlayPos().setTicks(timeline.playStartPosition().getTicks());
|
||||
setToTime(timeline.playStartPosition());
|
||||
timeline.setTicks(timeline.playStartPosition().getTicks());
|
||||
|
||||
timeline.setPlayStartPosition(-1);
|
||||
}
|
||||
@@ -697,15 +674,12 @@ void Song::stop()
|
||||
break;
|
||||
}
|
||||
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(PlayMode::None)] = m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)];
|
||||
getPlayPos(PlayMode::None).setTicks(getPlayPos().getTicks());
|
||||
|
||||
getPlayPos().setCurrentFrame( 0 );
|
||||
getTimeline(PlayMode::None).setTicks(getPlayPos().getTicks());
|
||||
|
||||
m_vstSyncController.setPlaybackState( m_exporting );
|
||||
m_vstSyncController.setAbsolutePosition(
|
||||
getPlayPos().getTicks()
|
||||
+ getPlayPos().currentFrame()
|
||||
+ timeline.frameOffset()
|
||||
/ (double) Engine::framesPerTick() );
|
||||
|
||||
// remove all note-play-handles that are active
|
||||
@@ -738,14 +712,14 @@ void Song::startExport()
|
||||
m_exporting = true;
|
||||
updateLength();
|
||||
|
||||
const auto& timeline = getTimeline(PlayMode::Song);
|
||||
auto& timeline = getTimeline(PlayMode::Song);
|
||||
|
||||
if (m_renderBetweenMarkers)
|
||||
{
|
||||
m_exportSongBegin = m_exportLoopBegin = timeline.loopBegin();
|
||||
m_exportSongEnd = m_exportLoopEnd = timeline.loopEnd();
|
||||
|
||||
getPlayPos(PlayMode::Song).setTicks(timeline.loopBegin().getTicks());
|
||||
timeline.setTicks(timeline.loopBegin().getTicks());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -768,7 +742,7 @@ void Song::startExport()
|
||||
? timeline.loopEnd()
|
||||
: TimePos{0};
|
||||
|
||||
getPlayPos(PlayMode::Song).setTicks( 0 );
|
||||
getTimeline(PlayMode::Song).setTicks(0);
|
||||
}
|
||||
|
||||
m_exportEffectiveLength = (m_exportLoopBegin - m_exportSongBegin) + (m_exportLoopEnd - m_exportLoopBegin)
|
||||
|
||||
@@ -127,13 +127,10 @@ AutomationEditor::AutomationEditor() :
|
||||
|
||||
// add time-line
|
||||
m_timeLine = new TimeLineWidget(VALUES_WIDTH, 0, m_ppb,
|
||||
Engine::getSong()->getPlayPos(Song::PlayMode::AutomationClip),
|
||||
Engine::getSong()->getTimeline(Song::PlayMode::AutomationClip),
|
||||
m_currentPosition, Song::PlayMode::AutomationClip, this
|
||||
m_currentPosition, this
|
||||
);
|
||||
connect(this, &AutomationEditor::positionChanged, m_timeLine, &TimeLineWidget::updatePosition);
|
||||
connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ),
|
||||
this, SLOT( updatePosition( const lmms::TimePos& ) ) );
|
||||
connect(m_timeLine->timeline(), &Timeline::positionChanged, this, &AutomationEditor::updatePosition);
|
||||
|
||||
// init scrollbars
|
||||
m_leftRightScroll = new QScrollBar( Qt::Horizontal, this );
|
||||
@@ -269,23 +266,17 @@ void AutomationEditor::keyPressEvent(QKeyEvent * ke )
|
||||
break;
|
||||
|
||||
case Qt::Key_Left:
|
||||
if( ( m_timeLine->pos() -= 16 ) < 0 )
|
||||
{
|
||||
m_timeLine->pos().setTicks( 0 );
|
||||
}
|
||||
m_timeLine->updatePosition();
|
||||
m_timeLine->timeline()->setTicks(std::max(0, m_timeLine->timeline()->ticks() - 16));
|
||||
ke->accept();
|
||||
break;
|
||||
|
||||
case Qt::Key_Right:
|
||||
m_timeLine->pos() += 16;
|
||||
m_timeLine->updatePosition();
|
||||
m_timeLine->timeline()->setTicks(m_timeLine->timeline()->ticks() + 16);
|
||||
ke->accept();
|
||||
break;
|
||||
|
||||
case Qt::Key_Home:
|
||||
m_timeLine->pos().setTicks( 0 );
|
||||
m_timeLine->updatePosition();
|
||||
m_timeLine->timeline()->setTicks(0);
|
||||
ke->accept();
|
||||
break;
|
||||
|
||||
@@ -1719,7 +1710,7 @@ void AutomationEditor::stop()
|
||||
void AutomationEditor::horScrolled(int new_pos )
|
||||
{
|
||||
m_currentPosition = new_pos;
|
||||
emit positionChanged( m_currentPosition );
|
||||
m_timeLine->update();
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -1788,8 +1779,9 @@ void AutomationEditor::setTension()
|
||||
|
||||
|
||||
|
||||
void AutomationEditor::updatePosition(const TimePos & t )
|
||||
void AutomationEditor::updatePosition()
|
||||
{
|
||||
const TimePos& t = m_timeLine->timeline()->pos();
|
||||
if( ( Engine::getSong()->isPlaying() &&
|
||||
Engine::getSong()->playMode() ==
|
||||
Song::PlayMode::AutomationClip ) ||
|
||||
|
||||
@@ -57,11 +57,10 @@ PatternEditor::PatternEditor(PatternStore* ps) :
|
||||
setModel(ps);
|
||||
|
||||
m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(),
|
||||
Engine::getSong()->getPlayPos(Song::PlayMode::Pattern),
|
||||
Engine::getSong()->getTimeline(Song::PlayMode::Pattern),
|
||||
m_currentPosition, Song::PlayMode::Pattern, this
|
||||
m_currentPosition, this
|
||||
);
|
||||
connect(m_timeLine, &TimeLineWidget::positionChanged, this, &PatternEditor::updatePosition);
|
||||
connect(m_timeLine->timeline(), &Timeline::positionChanged, this, &PatternEditor::updatePosition);
|
||||
static_cast<QVBoxLayout*>(layout())->insertWidget(0, m_timeLine);
|
||||
|
||||
connect(m_ps, &PatternStore::trackUpdated,
|
||||
@@ -199,7 +198,6 @@ void PatternEditor::updatePosition()
|
||||
{
|
||||
trackView->update();
|
||||
}
|
||||
emit positionChanged( m_currentPosition );
|
||||
}
|
||||
|
||||
void PatternEditor::updatePixelsPerBar()
|
||||
|
||||
@@ -276,23 +276,22 @@ PianoRoll::PianoRoll() :
|
||||
|
||||
// add time-line
|
||||
m_timeLine = new TimeLineWidget(m_whiteKeyWidth, 0, m_ppb,
|
||||
Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip),
|
||||
Engine::getSong()->getTimeline(Song::PlayMode::MidiClip),
|
||||
m_currentPosition, Song::PlayMode::MidiClip, this
|
||||
m_currentPosition, this
|
||||
);
|
||||
connect(this, &PianoRoll::positionChanged, m_timeLine, &TimeLineWidget::updatePosition);
|
||||
connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ),
|
||||
this, SLOT( updatePosition( const lmms::TimePos& ) ) );
|
||||
connect(m_timeLine->timeline(), &Timeline::positionChanged, this, &PianoRoll::updatePosition);
|
||||
|
||||
// white position line follows timeline marker
|
||||
m_positionLine = new PositionLine(this, Song::PlayMode::MidiClip);
|
||||
|
||||
connect(Engine::getSong(), &Song::playbackStateChanged, m_positionLine, qOverload<>(&QWidget::update));
|
||||
|
||||
//update timeline when in step-recording mode
|
||||
connect( &m_stepRecorderWidget, SIGNAL( positionChanged( const lmms::TimePos& ) ),
|
||||
this, SLOT( updatePositionStepRecording( const lmms::TimePos& ) ) );
|
||||
|
||||
// update timeline when in record-accompany mode
|
||||
connect(m_timeLine, &TimeLineWidget::positionChanged, this, &PianoRoll::updatePositionAccompany);
|
||||
connect(&Engine::getSong()->getTimeline(Song::PlayMode::Song), &Timeline::positionChanged, this, &PianoRoll::updatePositionAccompany);
|
||||
// TODO
|
||||
/* connect( engine::getSong()->getPlayPos( Song::PlayMode::Pattern ).m_timeLine,
|
||||
SIGNAL( positionChanged( const lmms::TimePos& ) ),
|
||||
@@ -882,8 +881,8 @@ void PianoRoll::setCurrentMidiClip( MidiClip* newMidiClip )
|
||||
}
|
||||
|
||||
// Make sure the playhead position isn't out of the clip bounds.
|
||||
Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip).setTicks(std::clamp(
|
||||
Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip).getTicks(),
|
||||
m_timeLine->timeline()->setTicks(std::clamp(
|
||||
m_timeLine->timeline()->ticks(),
|
||||
std::max(0, -m_midiClip->startTimeOffset()),
|
||||
m_midiClip->length() - m_midiClip->startTimeOffset()
|
||||
));
|
||||
@@ -1456,8 +1455,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
|
||||
break;
|
||||
|
||||
case Qt::Key_Home:
|
||||
m_timeLine->pos().setTicks( 0 );
|
||||
m_timeLine->updatePosition();
|
||||
m_timeLine->timeline()->setTicks(0);
|
||||
ke->accept();
|
||||
break;
|
||||
|
||||
@@ -4316,7 +4314,8 @@ void PianoRoll::horScrolled(int new_pos )
|
||||
{
|
||||
m_currentPosition = new_pos;
|
||||
m_stepRecorderWidget.setCurrentPosition(m_currentPosition);
|
||||
emit positionChanged( m_currentPosition );
|
||||
m_timeLine->update();
|
||||
updatePositionLinePos();
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -4589,7 +4588,7 @@ void PianoRoll::pasteNotes()
|
||||
// create the note
|
||||
Note cur_note;
|
||||
cur_note.restoreState( list.item( i ).toElement() );
|
||||
cur_note.setPos( cur_note.pos() + Note::quantized( m_timeLine->pos(), quantization() ) );
|
||||
cur_note.setPos(cur_note.pos() + Note::quantized(m_timeLine->timeline()->pos(), quantization()));
|
||||
|
||||
// select it
|
||||
cur_note.setSelected( true );
|
||||
@@ -4655,8 +4654,9 @@ void PianoRoll::autoScroll( const TimePos & t )
|
||||
|
||||
|
||||
|
||||
void PianoRoll::updatePosition(const TimePos & t)
|
||||
void PianoRoll::updatePosition()
|
||||
{
|
||||
const TimePos& t = m_timeLine->timeline()->pos();
|
||||
if ((Engine::getSong()->isPlaying()
|
||||
&& Engine::getSong()->playMode() == Song::PlayMode::MidiClip
|
||||
&& m_timeLine->autoScroll() != TimeLineWidget::AutoScrollState::Disabled
|
||||
@@ -4664,10 +4664,15 @@ void PianoRoll::updatePosition(const TimePos & t)
|
||||
{
|
||||
autoScroll(t);
|
||||
}
|
||||
updatePositionLinePos();
|
||||
}
|
||||
|
||||
void PianoRoll::updatePositionLinePos()
|
||||
{
|
||||
// ticks relative to m_currentPosition
|
||||
// < 0 = outside viewport left
|
||||
// > width = outside viewport right
|
||||
const int pos = (static_cast<int>(m_timeLine->pos()) - m_currentPosition) * m_ppb / TimePos::ticksPerBar();
|
||||
const int pos = (static_cast<int>(m_timeLine->timeline()->pos()) - m_currentPosition) * m_ppb / TimePos::ticksPerBar();
|
||||
// if pos is within visible range, show it
|
||||
if (hasValidMidiClip() && pos >= 0 && pos <= width() - m_whiteKeyWidth)
|
||||
{
|
||||
@@ -4690,8 +4695,9 @@ void PianoRoll::updatePositionLineHeight()
|
||||
|
||||
|
||||
|
||||
void PianoRoll::updatePositionAccompany( const TimePos & t )
|
||||
void PianoRoll::updatePositionAccompany()
|
||||
{
|
||||
const TimePos& t = Engine::getSong()->getPlayPos(Song::PlayMode::Song);
|
||||
Song * s = Engine::getSong();
|
||||
|
||||
if( m_recording && hasValidMidiClip() &&
|
||||
@@ -4700,11 +4706,11 @@ void PianoRoll::updatePositionAccompany( const TimePos & t )
|
||||
TimePos pos = t;
|
||||
if (s->playMode() != Song::PlayMode::Pattern)
|
||||
{
|
||||
pos -= m_midiClip->startPosition();
|
||||
pos -= m_midiClip->startPosition() + m_midiClip->startTimeOffset();
|
||||
}
|
||||
if( (int) pos > 0 )
|
||||
{
|
||||
s->getPlayPos( Song::PlayMode::MidiClip ).setTicks( pos );
|
||||
m_timeLine->timeline()->setTicks(pos);
|
||||
autoScroll( pos );
|
||||
}
|
||||
}
|
||||
@@ -4729,6 +4735,7 @@ void PianoRoll::zoomingChanged()
|
||||
m_timeLine->setPixelsPerBar( m_ppb );
|
||||
m_stepRecorderWidget.setPixelsPerBar( m_ppb );
|
||||
m_positionLine->zoomChange(m_zoomLevels[m_zoomingModel.value()]);
|
||||
updatePositionLinePos();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -96,13 +96,11 @@ SongEditor::SongEditor( Song * song ) :
|
||||
{
|
||||
// Set up timeline
|
||||
m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(),
|
||||
m_song->getPlayPos(Song::PlayMode::Song),
|
||||
m_song->getTimeline(Song::PlayMode::Song),
|
||||
m_currentPosition, Song::PlayMode::Song, this
|
||||
m_currentPosition, this
|
||||
);
|
||||
connect(this, &TrackContainerView::positionChanged, m_timeLine, &TimeLineWidget::updatePosition);
|
||||
connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ),
|
||||
this, SLOT( updatePosition( const lmms::TimePos& ) ) );
|
||||
connect(this, &TrackContainerView::positionChanged, m_timeLine, qOverload<>(&QWidget::update));
|
||||
connect(m_timeLine->timeline(), &Timeline::positionChanged, this, &SongEditor::updatePosition);
|
||||
connect( m_timeLine, SIGNAL(regionSelectedFromPixels(int,int)),
|
||||
this, SLOT(selectRegionFromPixels(int,int)));
|
||||
connect( m_timeLine, SIGNAL(selectionFinished()),
|
||||
@@ -120,7 +118,10 @@ SongEditor::SongEditor( Song * song ) :
|
||||
// When zoom changes, update position line
|
||||
// But we must convert pixels per bar to a zoom factor where 1.0 is 100%
|
||||
connect(this, &SongEditor::pixelsPerBarChanged, m_positionLine,
|
||||
[this]() { m_positionLine->zoomChange(pixelsPerBar() / float(DEFAULT_PIXELS_PER_BAR)); });
|
||||
[this]() {
|
||||
m_positionLine->zoomChange(pixelsPerBar() / float(DEFAULT_PIXELS_PER_BAR));
|
||||
updatePositionLine();
|
||||
});
|
||||
|
||||
// Ensure loop markers snap to same increments as clips. Zoom & proportional
|
||||
// snap changes are handled in zoomingChanged() and toggleProportionalSnap()
|
||||
@@ -329,6 +330,7 @@ void SongEditor::scrolled( int new_pos )
|
||||
{
|
||||
update();
|
||||
emit positionChanged(m_currentPosition = TimePos(new_pos));
|
||||
updatePositionLine();
|
||||
}
|
||||
|
||||
|
||||
@@ -472,7 +474,7 @@ void SongEditor::keyPressEvent( QKeyEvent * ke )
|
||||
}
|
||||
else if( ke->key() == Qt::Key_Left )
|
||||
{
|
||||
tick_t t = m_song->currentTick() - TimePos::ticksPerBar();
|
||||
tick_t t = m_song->getPlayPos(Song::PlayMode::Song).getTicks() - TimePos::ticksPerBar();
|
||||
if( t >= 0 )
|
||||
{
|
||||
m_song->setPlayPos( t, Song::PlayMode::Song );
|
||||
@@ -480,7 +482,7 @@ void SongEditor::keyPressEvent( QKeyEvent * ke )
|
||||
}
|
||||
else if( ke->key() == Qt::Key_Right )
|
||||
{
|
||||
tick_t t = m_song->currentTick() + TimePos::ticksPerBar();
|
||||
tick_t t = m_song->getPlayPos(Song::PlayMode::Song).getTicks() + TimePos::ticksPerBar();
|
||||
if( t < MaxSongLength )
|
||||
{
|
||||
m_song->setPlayPos( t, Song::PlayMode::Song );
|
||||
@@ -760,8 +762,9 @@ static inline void animateScroll( QScrollBar *scrollBar, int newVal, bool smooth
|
||||
|
||||
|
||||
|
||||
void SongEditor::updatePosition( const TimePos & t )
|
||||
void SongEditor::updatePosition()
|
||||
{
|
||||
const TimePos& t = m_timeLine->timeline()->pos();
|
||||
const bool compactTrackButtons = ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt();
|
||||
const auto widgetWidth = compactTrackButtons ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT : DEFAULT_SETTINGS_WIDGET_WIDTH;
|
||||
const auto trackOpWidth = compactTrackButtons ? TRACK_OP_WIDTH_COMPACT : TRACK_OP_WIDTH;
|
||||
@@ -789,7 +792,18 @@ void SongEditor::updatePosition( const TimePos & t )
|
||||
m_scrollBack = false;
|
||||
}
|
||||
|
||||
const int x = m_timeLine->markerX(t);
|
||||
updatePositionLine();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SongEditor::updatePositionLine()
|
||||
{
|
||||
const bool compactTrackButtons = ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt();
|
||||
const auto widgetWidth = compactTrackButtons ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT : DEFAULT_SETTINGS_WIDGET_WIDTH;
|
||||
const auto trackOpWidth = compactTrackButtons ? TRACK_OP_WIDTH_COMPACT : TRACK_OP_WIDTH;
|
||||
const int x = m_timeLine->markerX(m_timeLine->timeline()->pos());
|
||||
if( x >= trackOpWidth + widgetWidth -1 )
|
||||
{
|
||||
m_positionLine->show();
|
||||
@@ -800,14 +814,6 @@ void SongEditor::updatePosition( const TimePos & t )
|
||||
m_positionLine->hide();
|
||||
}
|
||||
|
||||
updatePositionLine();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SongEditor::updatePositionLine()
|
||||
{
|
||||
m_positionLine->setFixedHeight(totalHeightOfTracks());
|
||||
}
|
||||
|
||||
|
||||
@@ -46,25 +46,21 @@ namespace
|
||||
constexpr int MIN_BAR_LABEL_DISTANCE = 35;
|
||||
}
|
||||
|
||||
TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, Song::PlayPos& pos, Timeline& timeline,
|
||||
const TimePos& begin, Song::PlayMode mode, QWidget* parent) :
|
||||
TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, Timeline& timeline,
|
||||
const TimePos& begin, QWidget* parent) :
|
||||
QWidget{parent},
|
||||
m_xOffset{xoff},
|
||||
m_ppb{ppb},
|
||||
m_pos{pos},
|
||||
m_timeline{&timeline},
|
||||
m_begin{begin},
|
||||
m_mode{mode}
|
||||
m_begin{begin}
|
||||
{
|
||||
move( 0, yoff );
|
||||
|
||||
setMouseTracking(true);
|
||||
|
||||
auto updateTimer = new QTimer(this);
|
||||
connect(updateTimer, &QTimer::timeout, this, &TimeLineWidget::updatePosition);
|
||||
updateTimer->start( 1000 / 60 ); // 60 fps
|
||||
connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)),
|
||||
this, SLOT(update()));
|
||||
connect(m_timeline, &Timeline::positionChanged, this, qOverload<>(&QWidget::update));
|
||||
}
|
||||
|
||||
|
||||
@@ -129,12 +125,6 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar )
|
||||
_tool_bar->addWidget( behaviourAtStop );
|
||||
}
|
||||
|
||||
void TimeLineWidget::updatePosition()
|
||||
{
|
||||
emit positionChanged(m_pos);
|
||||
update();
|
||||
}
|
||||
|
||||
void TimeLineWidget::toggleAutoScroll( int _n )
|
||||
{
|
||||
m_autoScroll = static_cast<AutoScrollState>( _n );
|
||||
@@ -223,12 +213,12 @@ void TimeLineWidget::paintEvent( QPaintEvent * )
|
||||
const QPixmap& marker = !m_isRecording ? m_posMarkerPixmap : m_recordingPosMarkerPixmap;
|
||||
|
||||
// Only draw the position marker if the position line is in view
|
||||
if (m_isPlayheadVisible && markerX(m_pos) >= m_xOffset && markerX(m_pos) < width() - marker.width() / 2)
|
||||
if (m_isPlayheadVisible && markerX(m_timeline->pos()) >= m_xOffset && markerX(m_timeline->pos()) < width() - marker.width() / 2)
|
||||
{
|
||||
// Let the position marker extrude to the left
|
||||
p.setClipping(false);
|
||||
p.setOpacity(0.6);
|
||||
p.drawPixmap(markerX(m_pos) - (marker.width() / 2),
|
||||
p.drawPixmap(markerX(m_timeline->pos()) - (marker.width() / 2),
|
||||
height() - marker.height(), marker);
|
||||
}
|
||||
}
|
||||
@@ -341,16 +331,13 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event )
|
||||
switch( m_action )
|
||||
{
|
||||
case Action::MovePositionMarker:
|
||||
m_pos.setTicks(timeAtCursor.getTicks());
|
||||
Engine::getSong()->setToTime(timeAtCursor, m_mode);
|
||||
m_timeline->setTicks(timeAtCursor.getTicks());
|
||||
if (!( Engine::getSong()->isPlaying()))
|
||||
{
|
||||
//Song::PlayMode::None is used when nothing is being played.
|
||||
Engine::getSong()->setToTime(timeAtCursor, Song::PlayMode::None);
|
||||
Engine::getSong()->getTimeline(Song::PlayMode::None).setTicks(timeAtCursor.getTicks());
|
||||
}
|
||||
m_pos.setCurrentFrame( 0 );
|
||||
m_pos.setJumped( true );
|
||||
updatePosition();
|
||||
update();
|
||||
break;
|
||||
|
||||
case Action::MoveLoopBegin:
|
||||
|
||||
Reference in New Issue
Block a user