Skip to content

Commit

Permalink
Rework Song::processNextBuffer (LMMS#5723)
Browse files Browse the repository at this point in the history
  • Loading branch information
DomClark authored Nov 27, 2020
1 parent 7c9b9e3 commit fd934a9
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 155 deletions.
4 changes: 4 additions & 0 deletions include/Song.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ class LMMS_EXPORT Song : public TrackContainer
{
return m_playPos[pm];
}
inline PlayPos & getPlayPos()
{
return getPlayPos(m_playMode);
}
inline const PlayPos & getPlayPos() const
{
return getPlayPos(m_playMode);
Expand Down
245 changes: 90 additions & 155 deletions src/core/Song.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <QFileInfo>
#include <QMessageBox>

#include <algorithm>
#include <cmath>
#include <functional>

#include "AutomationTrack.h"
Expand Down Expand Up @@ -191,226 +193,159 @@ void Song::savePos()

void Song::processNextBuffer()
{
m_vstSyncController.setPlaybackJumped( false );
m_vstSyncController.setPlaybackJumped(false);

// if not playing, nothing to do
if( m_playing == false )
// If nothing is playing, there is nothing to do
if (!m_playing) { return; }

// At the beginning of the song, we have to reset the LFOs
if (m_playMode == Mode_PlaySong && getPlayPos() == 0)
{
return;
EnvelopeAndLfoParameters::instances()->reset();
}

TrackList trackList;
int tcoNum = -1; // track content object number
int clipNum = -1; // The number of the clip that will be played

// determine the list of tracks to play and the track content object
// (TCO) number
switch( m_playMode )
// Determine the list of tracks to play and the clip number
switch (m_playMode)
{
case Mode_PlaySong:
trackList = tracks();
// at song-start we have to reset the LFOs
if( m_playPos[Mode_PlaySong] == 0 )
{
EnvelopeAndLfoParameters::instances()->reset();
}
break;

case Mode_PlayBB:
if( Engine::getBBTrackContainer()->numOfBBs() > 0 )
if (Engine::getBBTrackContainer()->numOfBBs() > 0)
{
tcoNum = Engine::getBBTrackContainer()->
currentBB();
trackList.push_back( BBTrack::findBBTrack(
tcoNum ) );
clipNum = Engine::getBBTrackContainer()->currentBB();
trackList.push_back(BBTrack::findBBTrack(clipNum));
}
break;

case Mode_PlayPattern:
if( m_patternToPlay != NULL )
if (m_patternToPlay)
{
tcoNum = m_patternToPlay->getTrack()->
getTCONum( m_patternToPlay );
trackList.push_back(
m_patternToPlay->getTrack() );
clipNum = m_patternToPlay->getTrack()->getTCONum(m_patternToPlay);
trackList.push_back(m_patternToPlay->getTrack());
}
break;

default:
return;

}

// if we have no tracks to play, nothing to do
if( trackList.empty() == true )
{
return;
}

// check for looping-mode and act if necessary
TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine;
bool checkLoop =
tl != NULL && m_exporting == false && tl->loopPointsEnabled();
// If we have no tracks to play, there is nothing to do
if (trackList.empty()) { return; }

if( checkLoop )
// 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.
const auto enforceLoop = [this](const MidiTime& begin, const MidiTime& end)
{
// if looping-mode is enabled and we are outside of the looping
// range, go to the beginning of the range
if( m_playPos[m_playMode] < tl->loopBegin() ||
m_playPos[m_playMode] >= tl->loopEnd() )
if (getPlayPos() < begin || getPlayPos() >= end)
{
setToTime(tl->loopBegin());
m_playPos[m_playMode].setTicks(
tl->loopBegin().getTicks() );

m_vstSyncController.setPlaybackJumped( true );

setToTime(begin);
m_vstSyncController.setPlaybackJumped(true);
emit updateSampleTracks();
return true;
}
}
return false;
};

if( m_playPos[m_playMode].jumped() )
const auto timeline = getPlayPos().m_timeLine;
const auto loopEnabled = !m_exporting && timeline && timeline->loopPointsEnabled();

// Ensure playback begins within the loop if it is enabled
if (loopEnabled) { enforceLoop(timeline->loopBegin(), timeline->loopEnd()); }

// Inform VST plugins if the user moved the play head
if (getPlayPos().jumped())
{
m_vstSyncController.setPlaybackJumped( true );
m_playPos[m_playMode].setJumped( false );
m_vstSyncController.setPlaybackJumped(true);
getPlayPos().setJumped(false);
}

f_cnt_t framesPlayed = 0;
const float framesPerTick = Engine::framesPerTick();
const auto framesPerTick = Engine::framesPerTick();
const auto framesPerPeriod = Engine::mixer()->framesPerPeriod();

f_cnt_t frameOffsetInPeriod = 0;

while( framesPlayed < Engine::mixer()->framesPerPeriod() )
while (frameOffsetInPeriod < framesPerPeriod)
{
m_vstSyncController.update();
auto frameOffsetInTick = getPlayPos().currentFrame();

float currentFrame = m_playPos[m_playMode].currentFrame();
// did we play a tick?
if( currentFrame >= framesPerTick )
// If a whole tick has elapsed, update the frame and tick count, and check any loops
if (frameOffsetInTick >= framesPerTick)
{
int ticks = m_playPos[m_playMode].getTicks() +
( int )( currentFrame / framesPerTick );

// did we play a whole bar?
if( ticks >= MidiTime::ticksPerBar() )
// 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);

// If we are playing a BB track, or a pattern with no loop enabled,
// loop back to the beginning when we reach the end
if (m_playMode == Mode_PlayBB)
{
// per default we just continue playing even if
// there's no more stuff to play
// (song-play-mode)
int maxBar = m_playPos[m_playMode].getBar()
+ 2;

// then decide whether to go over to next bar
// or to loop back to first bar
if( m_playMode == Mode_PlayBB )
{
maxBar = Engine::getBBTrackContainer()
->lengthOfCurrentBB();
}
else if( m_playMode == Mode_PlayPattern &&
m_loopPattern == true &&
tl != NULL &&
tl->loopPointsEnabled() == false )
{
maxBar = m_patternToPlay->length()
.getBar();
}

// end of played object reached?
if( m_playPos[m_playMode].getBar() + 1
>= maxBar )
{
// then start from beginning and keep
// offset
ticks %= ( maxBar * MidiTime::ticksPerBar() );

// wrap milli second counter
setToTimeByTicks(ticks);

m_vstSyncController.setPlaybackJumped( true );
}
enforceLoop(MidiTime{0}, MidiTime{Engine::getBBTrackContainer()->lengthOfCurrentBB(), 0});
}
else if (m_playMode == Mode_PlayPattern && m_loopPattern && !loopEnabled)
{
enforceLoop(MidiTime{0}, m_patternToPlay->length());
}
m_playPos[m_playMode].setTicks( ticks );

if (checkLoop || m_loopRenderRemaining > 1)
// Handle loop points, and inform VST plugins of the loop status
if (loopEnabled || m_loopRenderRemaining > 1)
{
m_vstSyncController.startCycle(
tl->loopBegin().getTicks(), tl->loopEnd().getTicks() );
timeline->loopBegin().getTicks(), timeline->loopEnd().getTicks());

// if looping-mode is enabled and we have got
// past the looping range, return to the
// beginning of the range
if( m_playPos[m_playMode] >= tl->loopEnd() )
// Loop if necessary, and decrement the remaining loops if we did
if (enforceLoop(timeline->loopBegin(), timeline->loopEnd())
&& m_loopRenderRemaining > 1)
{
if (m_loopRenderRemaining > 1)
m_loopRenderRemaining--;
ticks = tl->loopBegin().getTicks();
m_playPos[m_playMode].setTicks( ticks );
setToTime(tl->loopBegin());

m_vstSyncController.setPlaybackJumped( true );

emit updateSampleTracks();
m_loopRenderRemaining--;
}
}
else
{
m_vstSyncController.stopCycle();
}

currentFrame = fmodf( currentFrame, framesPerTick );
m_playPos[m_playMode].setCurrentFrame( currentFrame );
}

if( framesPlayed == 0 )
{
// update VST sync position after we've corrected frame/
// tick count but before actually playing any frames
m_vstSyncController.setAbsolutePosition(
m_playPos[m_playMode].getTicks()
+ m_playPos[m_playMode].currentFrame()
/ (double) framesPerTick );
}
const f_cnt_t framesUntilNextPeriod = framesPerPeriod - frameOffsetInPeriod;
const f_cnt_t framesUntilNextTick = static_cast<f_cnt_t>(std::ceil(framesPerTick - frameOffsetInTick));

f_cnt_t framesToPlay =
Engine::mixer()->framesPerPeriod() - framesPlayed;
// We want to proceed to the next buffer or tick, whichever is closer
const auto framesToPlay = std::min(framesUntilNextPeriod, framesUntilNextTick);

f_cnt_t framesLeft = ( f_cnt_t )framesPerTick -
( f_cnt_t )currentFrame;
// skip last frame fraction
if( framesLeft == 0 )
if (frameOffsetInPeriod == 0)
{
++framesPlayed;
m_playPos[m_playMode].setCurrentFrame( currentFrame
+ 1.0f );
continue;
}
// do we have samples left in this tick but these are less
// than samples we have to play?
if( framesLeft < framesToPlay )
{
// then set framesToPlay to remaining samples, the
// rest will be played in next loop
framesToPlay = framesLeft;
// First frame of buffer: update VST sync position.
// 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));
m_vstSyncController.update();
}

if( ( f_cnt_t ) currentFrame == 0 )
if (static_cast<f_cnt_t>(frameOffsetInTick) == 0)
{
processAutomations(trackList, m_playPos[m_playMode], framesToPlay);

// loop through all tracks and play them
for( int i = 0; i < trackList.size(); ++i )
// First frame of tick: process automation and play tracks
processAutomations(trackList, getPlayPos(), framesToPlay);
for (const auto track : trackList)
{
trackList[i]->play( m_playPos[m_playMode],
framesToPlay,
framesPlayed, tcoNum );
track->play(getPlayPos(), framesToPlay, frameOffsetInPeriod, clipNum);
}
}

// update frame-counters
framesPlayed += framesToPlay;
m_playPos[m_playMode].setCurrentFrame( framesToPlay +
currentFrame );
// Update frame counters
frameOffsetInPeriod += framesToPlay;
frameOffsetInTick += framesToPlay;
getPlayPos().setCurrentFrame(frameOffsetInTick);
m_elapsedMilliSeconds[m_playMode] += MidiTime::ticksToMilliseconds(framesToPlay / framesPerTick, getTempo());
m_elapsedBars = m_playPos[Mode_PlaySong].getBar();
m_elapsedTicks = ( m_playPos[Mode_PlaySong].getTicks() % ticksPerBar() ) / 48;
m_elapsedTicks = (m_playPos[Mode_PlaySong].getTicks() % ticksPerBar()) / 48;
}
}

Expand Down

0 comments on commit fd934a9

Please sign in to comment.