Skip to content

Commit

Permalink
Recursive buffer rendering implementation (#538)
Browse files Browse the repository at this point in the history
Allows for playing very short audio samples in continuous loop modes such as single.endless mode.
  • Loading branch information
gvnnz committed Jan 27, 2024
1 parent f0dcd78 commit 77746e1
Showing 1 changed file with 30 additions and 33 deletions.
63 changes: 30 additions & 33 deletions src/core/rendering/sampleRendering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ ReadResult readCopy_(const Wave& wave, mcl::AudioBuffer& dest, Frame start,
Things to do when the last frame has been reached. 'natural' == true if the
rendering has ended because the end of the sample has ben reached.
'natural' == false if the rendering has been manually interrupted (by a
RenderInfo::Mode::STOP type). */
RenderInfo::Mode::STOP type). Returns whether the sample should loop or not. */

void onSampleEnd_(const Channel& ch, bool seqIsRunning, bool natural)
bool onSampleEnd_(const Channel& ch, bool seqIsRunning, bool natural)
{
ChannelShared& shared = *ch.shared;
const bool isLoop = ch.sampleChannel->isAnyLoopMode();
const bool isLoopOnce = ch.sampleChannel->isAnyLoopOnceMode();
const bool isSingleNoLoop = ch.sampleChannel->isAnyNonLoopingSingleMode();
bool shouldLoop = false;

switch (shared.playStatus.load())
{
Expand All @@ -92,15 +93,20 @@ void onSampleEnd_(const Channel& ch, bool seqIsRunning, bool natural)
shared.playStatus.store(ChannelStatus::OFF);
else if (isLoopOnce)
shared.playStatus.store(ChannelStatus::WAIT);
else
shouldLoop = true;
break;

case ChannelStatus::ENDING:
shared.playStatus.store(ChannelStatus::OFF);
break;

default:
shouldLoop = true;
break;
}

return shouldLoop;
}

/* -------------------------------------------------------------------------- */
Expand All @@ -114,43 +120,34 @@ void stop_(const Channel& ch, mcl::AudioBuffer& buf, Frame offset, bool seqIsRun

/* -------------------------------------------------------------------------- */

bool shouldLoop_(SamplePlayerMode mode, ChannelStatus status)
{
return (mode == SamplePlayerMode::LOOP_BASIC ||
mode == SamplePlayerMode::LOOP_REPEAT ||
mode == SamplePlayerMode::SINGLE_ENDLESS) &&
status == ChannelStatus::PLAY; // Don't loop if ENDING
}

/* -------------------------------------------------------------------------- */

Frame render_(const Channel& ch, mcl::AudioBuffer& buf, Frame tracker, Frame offset, bool seqIsRunning)
{
const ChannelStatus status = ch.shared->playStatus.load();
const Frame begin = ch.sampleChannel->begin;
const Frame end = ch.sampleChannel->end;
const float pitch = ch.sampleChannel->pitch;
const SamplePlayerMode mode = ch.sampleChannel->mode;
const Wave& wave = *ch.sampleChannel->getWave();
const Resampler& resampler = ch.shared->resampler.value();

/* First pass rendering. */
const Frame begin = ch.sampleChannel->begin;
const Frame end = ch.sampleChannel->end;
const float pitch = ch.sampleChannel->pitch;
const Wave& wave = *ch.sampleChannel->getWave();
const Resampler& resampler = ch.shared->resampler.value();

rendering::ReadResult res = rendering::readWave(wave, buf, tracker, end, offset, pitch, resampler);
tracker += res.used;
while (true)
{
ReadResult res = readWave(wave, buf, tracker, end, offset, pitch, resampler);
tracker += res.used;
offset += res.generated;

/* Second pass rendering: if tracker has looped, special care is needed. If
the channel is in loop mode, fill the second part of the buffer with data
coming from the sample's head, starting at 'res.generated' offset. */
/* Break here if the buffer has been filled completely: there's nothing
else do to. */

if (tracker >= end)
{
tracker = begin;
ch.shared->resampler->last();
onSampleEnd_(ch, seqIsRunning, /*natural=*/true);
if (offset >= buf.countFrames())
break;

if (shouldLoop_(mode, status) && res.generated < buf.countFrames())
tracker += rendering::readWave(wave, buf, tracker, end, res.generated, pitch, resampler).used;
if (tracker >= end)
{
tracker = begin;
ch.shared->resampler->last();
const bool shouldLoop = onSampleEnd_(ch, seqIsRunning, /*natural=*/true);
if (!shouldLoop)
break;
}
}

return tracker;
Expand Down

0 comments on commit 77746e1

Please sign in to comment.