Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix playback within Sample #7100

Merged
merged 32 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b4b2650
Remove Qt includes
sakertooth Feb 10, 2024
d9ff3ab
Simplify copyBufferForward and copyBufferBackward functions
sakertooth Feb 11, 2024
045a632
Remove Qt forward declarations
sakertooth Feb 11, 2024
7ce82c6
Rework sample playing
sakertooth Feb 11, 2024
079f2c4
Fix position of index when copying backwards
sakertooth Feb 11, 2024
f30e87e
Add playRaw function
sakertooth Feb 11, 2024
5f5e993
Combine forward and backward copying of the buffer
sakertooth Feb 11, 2024
923f6a9
Fix/simplify playRaw function and remove copyBuffer
sakertooth Feb 11, 2024
aa6d221
Handle backward non-looping case when playing beyond buffer
sakertooth Feb 11, 2024
62fd3f7
Only use std::abs when potentially looping in the negatives
sakertooth Feb 11, 2024
d5e13de
Simplify advance function
sakertooth Feb 11, 2024
0f0e45c
Consider reversed variable in playRaw
sakertooth Feb 11, 2024
2f107ad
Merge amplifySampleRange into play function
sakertooth Feb 11, 2024
3bc21ea
Make advance method const
sakertooth Feb 11, 2024
bf48d2e
Use type size_t for numFrames
sakertooth Feb 11, 2024
f5fe4a1
Fix advance function
sakertooth Feb 11, 2024
968635d
Make play function const
sakertooth Feb 11, 2024
78134b9
Use int instead of f_cnt_t
sakertooth Feb 11, 2024
e3ee48f
More simplification and style changes
sakertooth Feb 11, 2024
706423b
Style
sakertooth Feb 11, 2024
e2aaa07
Handle potential off by one access
sakertooth Feb 11, 2024
387757c
Update src/core/Sample.cpp
sakertooth Feb 11, 2024
94475d8
Reduce nesting within if guard
sakertooth Feb 11, 2024
267b5c1
Use std::fill_n
sakertooth Feb 11, 2024
8ac3aac
Use the correct size with std::fill_n
sakertooth Feb 11, 2024
7c79569
Move resampleResult.outputFramesGenerated into separate variable
sakertooth Feb 11, 2024
907f613
Add asserts in play function
sakertooth Feb 11, 2024
29a0b3d
Enforce start frame as lower bound for frame index
sakertooth Feb 12, 2024
0490ab1
Handle arithmetic exception when the loop distance is 0
sakertooth Feb 15, 2024
57b9e75
Do not check if backwards is true
sakertooth Feb 15, 2024
8a43151
Merge remote-tracking branch 'upstream/master' into fix-sample-playback
sakertooth Feb 16, 2024
a34ef79
Include cassert header
sakertooth Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions include/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@
#include "SampleBuffer.h"
#include "lmms_export.h"

class QPainter;
class QRect;

namespace lmms {
class LMMS_EXPORT Sample
{
Expand Down Expand Up @@ -63,25 +60,25 @@ class LMMS_EXPORT Sample
}

auto resampler() -> AudioResampler& { return m_resampler; }
auto frameIndex() const -> f_cnt_t { return m_frameIndex; }
auto frameIndex() const -> int { return m_frameIndex; }
auto varyingPitch() const -> bool { return m_varyingPitch; }
auto backwards() const -> bool { return m_backwards; }

void setFrameIndex(f_cnt_t frameIndex) { m_frameIndex = frameIndex; }
void setFrameIndex(int frameIndex) { m_frameIndex = frameIndex; }
void setVaryingPitch(bool varyingPitch) { m_varyingPitch = varyingPitch; }
void setBackwards(bool backwards) { m_backwards = backwards; }

private:
AudioResampler m_resampler;
f_cnt_t m_frameIndex = 0;
int m_frameIndex = 0;
bool m_varyingPitch = false;
bool m_backwards = false;
friend class Sample;
};

Sample() = default;
Sample(const QByteArray& base64, int sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const sampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const Sample& other);
Sample(Sample&& other);
explicit Sample(const QString& audioFile);
Expand All @@ -90,8 +87,8 @@ class LMMS_EXPORT Sample
auto operator=(const Sample&) -> Sample&;
auto operator=(Sample&&) -> Sample&;

auto play(sampleFrame* dst, PlaybackState* state, int numFrames, float desiredFrequency = DefaultBaseFreq,
Loop loopMode = Loop::Off) -> bool;
auto play(sampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency = DefaultBaseFreq,
Loop loopMode = Loop::Off) const -> bool;

auto sampleDuration() const -> std::chrono::milliseconds;
auto sampleFile() const -> const QString& { return m_buffer->audioFile(); }
Expand Down Expand Up @@ -120,10 +117,8 @@ class LMMS_EXPORT Sample
void setReversed(bool reversed) { m_reversed.store(reversed, std::memory_order_relaxed); }

private:
void playSampleRange(PlaybackState* state, sampleFrame* dst, size_t numFrames) const;
void amplifySampleRange(sampleFrame* src, int numFrames) const;
void copyBufferForward(sampleFrame* dst, int initialPosition, int advanceAmount) const;
void copyBufferBackward(sampleFrame* dst, int initialPosition, int advanceAmount) const;
void playRaw(sampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const;
void advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const;

private:
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
Expand Down
2 changes: 1 addition & 1 deletion include/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class LMMS_EXPORT SampleBuffer
SampleBuffer(const QString& base64, int sampleRate);
SampleBuffer(std::vector<sampleFrame> data, int sampleRate);
SampleBuffer(
const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate());
const sampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate());

friend void swap(SampleBuffer& first, SampleBuffer& second) noexcept;
auto toBase64() const -> QString;
Expand Down
160 changes: 86 additions & 74 deletions src/core/Sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@

#include "Sample.h"

#include <QPainter>
#include <QRect>

namespace lmms {

Sample::Sample(const QString& audioFile)
Expand All @@ -47,7 +44,7 @@
{
}

Sample::Sample(const sampleFrame* data, int numFrames, int sampleRate)
Sample::Sample(const sampleFrame* data, size_t numFrames, int sampleRate)
: m_buffer(std::make_shared<SampleBuffer>(data, numFrames, sampleRate))
, m_startFrame(0)
, m_endFrame(m_buffer->size())
Expand Down Expand Up @@ -117,58 +114,40 @@
return *this;
}

bool Sample::play(sampleFrame* dst, PlaybackState* state, int numFrames, float desiredFrequency, Loop loopMode)
bool Sample::play(sampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency, Loop loopMode) const
{
if (numFrames <= 0 || desiredFrequency <= 0) { return false; }
if (loopMode == Loop::Off && (state->m_frameIndex >= m_endFrame || (state->m_frameIndex < 0 && state->m_backwards)))
{
return false;
}

const auto outputSampleRate = Engine::audioEngine()->processingSampleRate() * m_frequency / desiredFrequency;
const auto inputSampleRate = m_buffer->sampleRate();
const auto resampleRatio = outputSampleRate / inputSampleRate;
const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()];

auto resampleRatio = static_cast<float>(Engine::audioEngine()->processingSampleRate()) / m_buffer->sampleRate();
resampleRatio *= frequency() / desiredFrequency;
auto playBuffer = std::vector<sampleFrame>(numFrames / resampleRatio + marginSize);
playRaw(playBuffer.data(), playBuffer.size(), state, loopMode);

auto playBuffer = std::vector<sampleFrame>(numFrames / resampleRatio);
if (!typeInfo<float>::isEqual(resampleRatio, 1.0f))
const auto resampleResult
= state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio);
advance(state, resampleResult.inputFramesUsed, loopMode);

if (resampleResult.outputFramesGenerated < numFrames)
{
playBuffer.resize(playBuffer.size() + s_interpolationMargins[state->resampler().interpolationMode()]);
std::fill(dst + resampleResult.outputFramesGenerated, dst + resampleResult.outputFramesGenerated + numFrames,
sampleFrame{0, 0});
}

const auto start = startFrame();
const auto end = endFrame();
const auto loopStart = loopStartFrame();
const auto loopEnd = loopEndFrame();

switch (loopMode)
if (!typeInfo<float>::isEqual(m_amplification, 1.0f))
{
case Loop::Off:
state->m_frameIndex = std::clamp(state->m_frameIndex, start, end);
if (state->m_frameIndex == end) { return false; }
break;
case Loop::On:
state->m_frameIndex = std::clamp(state->m_frameIndex, start, loopEnd);
if (state->m_frameIndex == loopEnd) { state->m_frameIndex = loopStart; }
break;
case Loop::PingPong:
state->m_frameIndex = std::clamp(state->m_frameIndex, start, loopEnd);
if (state->m_frameIndex == loopEnd)
for (int i = 0; i < numFrames; ++i)
{
state->m_frameIndex = loopEnd - 1;
state->m_backwards = true;
dst[i][0] *= m_amplification;
dst[i][1] *= m_amplification;
}
else if (state->m_frameIndex <= loopStart && state->m_backwards)
{
state->m_frameIndex = loopStart;
state->m_backwards = false;
}
break;
}

playSampleRange(state, playBuffer.data(), playBuffer.size());

const auto result
= state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio);
if (result.error != 0) { return false; }

state->m_frameIndex += (state->m_backwards ? -1 : 1) * result.inputFramesUsed;
amplifySampleRange(dst, result.outputFramesGenerated);

return true;
}

Expand All @@ -187,44 +166,77 @@
setLoopEndFrame(loopEndFrame);
}

void Sample::playSampleRange(PlaybackState* state, sampleFrame* dst, size_t numFrames) const
void Sample::playRaw(sampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const
{
auto framesToCopy = 0;
if (state->m_backwards)
{
framesToCopy = std::min<int>(state->m_frameIndex - startFrame(), numFrames);
copyBufferBackward(dst, state->m_frameIndex, framesToCopy);
}
else
auto index = state->m_frameIndex;
auto backwards = state->m_backwards;

for (size_t i = 0; i < numFrames; ++i)
{
framesToCopy = std::min<int>(endFrame() - state->m_frameIndex, numFrames);
copyBufferForward(dst, state->m_frameIndex, framesToCopy);
}
switch (loopMode)
{
case Loop::Off:
if (index > m_endFrame || (index < 0 && state->m_backwards)) { return; }
break;
case Loop::On:
if (index < m_loopStartFrame && backwards) { index = m_loopEndFrame - 1; }
else if (index >= m_loopEndFrame) { index = m_loopStartFrame; }
break;
case Loop::PingPong:
if (index < m_loopStartFrame && backwards)
{
index = m_loopStartFrame;
backwards = false;
}
else if (index >= m_loopEndFrame)
{
index = m_loopEndFrame - 1;
backwards = true;
}
break;
default:
break;
}

if (framesToCopy < numFrames) { std::fill_n(dst + framesToCopy, numFrames - framesToCopy, sampleFrame{0, 0}); }
dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index : index];
backwards ? --index : ++index;
}
}

Check notice on line 205 in src/core/Sample.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/core/Sample.cpp#L169-L205

Complex Method
void Sample::copyBufferForward(sampleFrame* dst, int initialPosition, int advanceAmount) const
void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const
{
reversed() ? std::copy_n(m_buffer->rbegin() + initialPosition, advanceAmount, dst)
: std::copy_n(m_buffer->begin() + initialPosition, advanceAmount, dst);
}
state->m_frameIndex += (state->m_backwards ? -1 : 1) * advanceAmount;

void Sample::copyBufferBackward(sampleFrame* dst, int initialPosition, int advanceAmount) const
{
reversed() ? std::reverse_copy(
m_buffer->rbegin() + initialPosition - advanceAmount, m_buffer->rbegin() + initialPosition, dst)
: std::reverse_copy(
m_buffer->begin() + initialPosition - advanceAmount, m_buffer->begin() + initialPosition, dst);
}
const auto distanceFromLoopStart = std::abs(state->m_frameIndex - m_loopStartFrame);
const auto distanceFromLoopEnd = std::abs(state->m_frameIndex - m_loopEndFrame);

void Sample::amplifySampleRange(sampleFrame* src, int numFrames) const
{
const auto amplification = m_amplification.load(std::memory_order_relaxed);
for (int i = 0; i < numFrames; ++i)
switch (loopMode)
{
src[i][0] *= amplification;
src[i][1] *= amplification;
case Loop::On:
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
{
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopStart % (m_loopEndFrame - m_loopStartFrame);
}
else if (state->m_frameIndex >= m_loopEndFrame)
{
state->m_frameIndex = m_loopStartFrame + distanceFromLoopEnd % (m_loopEndFrame - m_loopStartFrame);
}
break;
case Loop::PingPong:
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
{
state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % (m_loopEndFrame - m_loopStartFrame);
state->m_backwards = false;
}
else if (state->m_frameIndex >= m_loopEndFrame)
{
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % (m_loopEndFrame - m_loopStartFrame);
state->m_backwards = true;
}
break;
default:
break;
}
}

} // namespace lmms
2 changes: 1 addition & 1 deletion src/core/SampleBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

namespace lmms {

SampleBuffer::SampleBuffer(const sampleFrame* data, int numFrames, int sampleRate)
SampleBuffer::SampleBuffer(const sampleFrame* data, size_t numFrames, int sampleRate)
: m_data(data, data + numFrames)
, m_sampleRate(sampleRate)
{
Expand Down
Loading