Switch to libsamplerate's callback API in Sample (#7361)
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* AudioResampler.h - wrapper around libsamplerate
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LMMS_AUDIO_RESAMPLER_H
|
||||
#define LMMS_AUDIO_RESAMPLER_H
|
||||
|
||||
#include <samplerate.h>
|
||||
|
||||
#include "lmms_export.h"
|
||||
|
||||
namespace lmms {
|
||||
|
||||
class LMMS_EXPORT AudioResampler
|
||||
{
|
||||
public:
|
||||
struct ProcessResult
|
||||
{
|
||||
int error;
|
||||
long inputFramesUsed;
|
||||
long outputFramesGenerated;
|
||||
};
|
||||
|
||||
AudioResampler(int interpolationMode, int channels);
|
||||
AudioResampler(const AudioResampler&) = delete;
|
||||
AudioResampler(AudioResampler&&) = delete;
|
||||
~AudioResampler();
|
||||
|
||||
AudioResampler& operator=(const AudioResampler&) = delete;
|
||||
AudioResampler& operator=(AudioResampler&&) = delete;
|
||||
|
||||
auto resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio) -> ProcessResult;
|
||||
auto interpolationMode() const -> int { return m_interpolationMode; }
|
||||
auto channels() const -> int { return m_channels; }
|
||||
void setRatio(double ratio);
|
||||
|
||||
private:
|
||||
int m_interpolationMode = -1;
|
||||
int m_channels = 0;
|
||||
int m_error = 0;
|
||||
SRC_STATE* m_state = nullptr;
|
||||
};
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_AUDIO_RESAMPLER_H
|
||||
@@ -25,24 +25,18 @@
|
||||
#ifndef LMMS_SAMPLE_H
|
||||
#define LMMS_SAMPLE_H
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <samplerate.h>
|
||||
|
||||
#include "AudioResampler.h"
|
||||
#include "Note.h"
|
||||
#include "SampleBuffer.h"
|
||||
#include "lmms_basics.h"
|
||||
#include "lmms_export.h"
|
||||
|
||||
namespace lmms {
|
||||
class LMMS_EXPORT Sample
|
||||
{
|
||||
public:
|
||||
// values for buffer margins, used for various libsamplerate interpolation modes
|
||||
// the array positions correspond to the converter_type parameter values in libsamplerate
|
||||
// if there appears problems with playback on some interpolation mode, then the value for that mode
|
||||
// may need to be higher - conversely, to optimize, some may work with lower values
|
||||
static constexpr auto s_interpolationMargins = std::array<int, 5>{64, 64, 64, 4, 4};
|
||||
|
||||
enum class Loop
|
||||
{
|
||||
Off,
|
||||
@@ -50,30 +44,25 @@ public:
|
||||
PingPong
|
||||
};
|
||||
|
||||
class LMMS_EXPORT PlaybackState
|
||||
struct LMMS_EXPORT PlaybackState
|
||||
{
|
||||
public:
|
||||
PlaybackState(bool varyingPitch = false, int interpolationMode = SRC_LINEAR)
|
||||
: m_resampler(interpolationMode, DEFAULT_CHANNELS)
|
||||
, m_varyingPitch(varyingPitch)
|
||||
PlaybackState(int interpolationMode = SRC_LINEAR)
|
||||
: resampleState(src_callback_new(&Sample::render, interpolationMode, DEFAULT_CHANNELS, &error, this))
|
||||
{
|
||||
assert(resampleState && src_strerror(error));
|
||||
}
|
||||
|
||||
auto resampler() -> AudioResampler& { return m_resampler; }
|
||||
auto frameIndex() const -> int { return m_frameIndex; }
|
||||
auto varyingPitch() const -> bool { return m_varyingPitch; }
|
||||
auto backwards() const -> bool { return m_backwards; }
|
||||
~PlaybackState()
|
||||
{
|
||||
src_delete(resampleState);
|
||||
}
|
||||
|
||||
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;
|
||||
int m_frameIndex = 0;
|
||||
bool m_varyingPitch = false;
|
||||
bool m_backwards = false;
|
||||
friend class Sample;
|
||||
const Sample* sample = nullptr;
|
||||
Loop* loop = nullptr;
|
||||
SRC_STATE* resampleState = nullptr;
|
||||
int frameIndex = 0;
|
||||
int error = 0;
|
||||
bool backwards = false;
|
||||
};
|
||||
|
||||
Sample() = default;
|
||||
@@ -87,7 +76,7 @@ public:
|
||||
auto operator=(const Sample&) -> Sample&;
|
||||
auto operator=(Sample&&) -> Sample&;
|
||||
|
||||
auto play(SampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency = DefaultBaseFreq,
|
||||
auto play(SampleFrame* dst, PlaybackState* state, size_t numFrames, double frequency = DefaultBaseFreq,
|
||||
Loop loopMode = Loop::Off) const -> bool;
|
||||
|
||||
auto sampleDuration() const -> std::chrono::milliseconds;
|
||||
@@ -117,17 +106,14 @@ public:
|
||||
void setReversed(bool reversed) { m_reversed.store(reversed, std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
void playRaw(SampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const;
|
||||
void advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const;
|
||||
|
||||
private:
|
||||
static auto render(void* callbackData, float** data) -> long;
|
||||
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
|
||||
std::atomic<int> m_startFrame = 0;
|
||||
std::atomic<int> m_endFrame = 0;
|
||||
std::atomic<int> m_loopStartFrame = 0;
|
||||
std::atomic<int> m_loopEndFrame = 0;
|
||||
std::atomic<float> m_amplification = 1.0f;
|
||||
std::atomic<float> m_frequency = DefaultBaseFreq;
|
||||
std::atomic<double> m_frequency = DefaultBaseFreq;
|
||||
std::atomic<bool> m_reversed = false;
|
||||
};
|
||||
} // namespace lmms
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <samplerate.h>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -144,9 +144,9 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
srcmode = SRC_SINC_MEDIUM_QUALITY;
|
||||
break;
|
||||
}
|
||||
_n->m_pluginData = new Sample::PlaybackState(_n->hasDetuningInfo(), srcmode);
|
||||
static_cast<Sample::PlaybackState*>(_n->m_pluginData)->setFrameIndex(m_nextPlayStartPoint);
|
||||
static_cast<Sample::PlaybackState*>(_n->m_pluginData)->setBackwards(m_nextPlayBackwards);
|
||||
_n->m_pluginData = new Sample::PlaybackState(srcmode);
|
||||
static_cast<Sample::PlaybackState*>(_n->m_pluginData)->frameIndex = m_nextPlayStartPoint;
|
||||
static_cast<Sample::PlaybackState*>(_n->m_pluginData)->backwards = m_nextPlayBackwards;
|
||||
|
||||
// debug code
|
||||
/* qDebug( "frames %d", m_sample->frames() );
|
||||
@@ -162,7 +162,7 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
static_cast<Sample::Loop>(m_loopModel.value())))
|
||||
{
|
||||
applyRelease( _working_buffer, _n );
|
||||
emit isPlaying(static_cast<Sample::PlaybackState*>(_n->m_pluginData)->frameIndex());
|
||||
emit isPlaying(static_cast<Sample::PlaybackState*>(_n->m_pluginData)->frameIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,8 +176,8 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
}
|
||||
if( m_stutterModel.value() == true )
|
||||
{
|
||||
m_nextPlayStartPoint = static_cast<Sample::PlaybackState*>(_n->m_pluginData)->frameIndex();
|
||||
m_nextPlayBackwards = static_cast<Sample::PlaybackState*>(_n->m_pluginData)->backwards();
|
||||
m_nextPlayStartPoint = static_cast<Sample::PlaybackState*>(_n->m_pluginData)->frameIndex;
|
||||
m_nextPlayBackwards = static_cast<Sample::PlaybackState*>(_n->m_pluginData)->backwards;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@ void GigInstrument::play( SampleFrame* _working_buffer )
|
||||
if (sample.region->PitchTrack == true) { freq_factor *= sample.freqFactor; }
|
||||
|
||||
// We need a bit of margin so we don't get glitching
|
||||
samples = frames / freq_factor + Sample::s_interpolationMargins[m_interpolation];
|
||||
samples = frames / freq_factor + s_interpolationMargins[m_interpolation];
|
||||
}
|
||||
|
||||
// Load this note's data
|
||||
|
||||
@@ -240,6 +240,12 @@ class GigInstrument : public Instrument
|
||||
mapPropertyFromModel( int, getPatch, setPatch, m_patchNum );
|
||||
|
||||
public:
|
||||
// values for buffer margins, used for various libsamplerate interpolation modes
|
||||
// the array positions correspond to the converter_type parameter values in libsamplerate
|
||||
// if there appears problems with playback on some interpolation mode, then the value for that mode
|
||||
// may need to be higher - conversely, to optimize, some may work with lower values
|
||||
static constexpr auto s_interpolationMargins = std::array<int, 5>{64, 64, 64, 4, 4};
|
||||
|
||||
GigInstrument( InstrumentTrack * _instrument_track );
|
||||
~GigInstrument() override;
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* AudioResampler.cpp - wrapper for libsamplerate
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AudioResampler.h"
|
||||
|
||||
#include <samplerate.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
AudioResampler::AudioResampler(int interpolationMode, int channels)
|
||||
: m_interpolationMode(interpolationMode)
|
||||
, m_channels(channels)
|
||||
, m_state(src_new(interpolationMode, channels, &m_error))
|
||||
{
|
||||
if (!m_state)
|
||||
{
|
||||
const auto errorMessage = std::string{src_strerror(m_error)};
|
||||
const auto fullMessage = std::string{"Failed to create an AudioResampler: "} + errorMessage;
|
||||
throw std::runtime_error{fullMessage};
|
||||
}
|
||||
}
|
||||
|
||||
AudioResampler::~AudioResampler()
|
||||
{
|
||||
src_delete(m_state);
|
||||
}
|
||||
|
||||
auto AudioResampler::resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio)
|
||||
-> ProcessResult
|
||||
{
|
||||
auto data = SRC_DATA{};
|
||||
data.data_in = in;
|
||||
data.input_frames = inputFrames;
|
||||
data.data_out = out;
|
||||
data.output_frames = outputFrames;
|
||||
data.src_ratio = ratio;
|
||||
data.end_of_input = 0;
|
||||
return {src_process(m_state, &data), data.input_frames_used, data.output_frames_gen};
|
||||
}
|
||||
|
||||
void AudioResampler::setRatio(double ratio)
|
||||
{
|
||||
src_set_ratio(m_state, ratio);
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -4,7 +4,6 @@ set(LMMS_SRCS
|
||||
core/AudioEngine.cpp
|
||||
core/AudioEngineProfiler.cpp
|
||||
core/AudioEngineWorkerThread.cpp
|
||||
core/AudioResampler.cpp
|
||||
core/AutomatableModel.cpp
|
||||
core/AutomationClip.cpp
|
||||
core/AutomationNode.cpp
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "MixHelpers.h"
|
||||
|
||||
namespace lmms {
|
||||
|
||||
Sample::Sample(const QString& audioFile)
|
||||
@@ -116,43 +118,28 @@ auto Sample::operator=(Sample&& other) -> Sample&
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Sample::play(SampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency, Loop loopMode) const
|
||||
bool Sample::play(SampleFrame* dst, PlaybackState* state, size_t numFrames, double frequency, Loop loopMode) const
|
||||
{
|
||||
assert(numFrames > 0);
|
||||
assert(desiredFrequency > 0);
|
||||
assert(frequency > 0);
|
||||
if (m_buffer->empty()) { return false; }
|
||||
|
||||
const auto pastBounds = state->m_frameIndex >= m_endFrame || (state->m_frameIndex < 0 && state->m_backwards);
|
||||
if (loopMode == Loop::Off && pastBounds) { return false; }
|
||||
|
||||
const auto outputSampleRate = Engine::audioEngine()->outputSampleRate() * m_frequency / desiredFrequency;
|
||||
const auto outputSampleRate = Engine::audioEngine()->outputSampleRate() * m_frequency / frequency;
|
||||
const auto inputSampleRate = m_buffer->sampleRate();
|
||||
const auto resampleRatio = outputSampleRate / inputSampleRate;
|
||||
const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()];
|
||||
|
||||
state->m_frameIndex = std::max<int>(m_startFrame, state->m_frameIndex);
|
||||
state->frameIndex = std::max<int>(m_startFrame, state->frameIndex);
|
||||
state->sample = this;
|
||||
state->loop = &loopMode;
|
||||
|
||||
auto playBuffer = std::vector<SampleFrame>(numFrames / resampleRatio + marginSize);
|
||||
playRaw(playBuffer.data(), playBuffer.size(), state, loopMode);
|
||||
|
||||
state->resampler().setRatio(resampleRatio);
|
||||
|
||||
const auto resampleResult
|
||||
= state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio);
|
||||
advance(state, resampleResult.inputFramesUsed, loopMode);
|
||||
|
||||
const auto outputFrames = static_cast<f_cnt_t>(resampleResult.outputFramesGenerated);
|
||||
if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, SampleFrame{}); }
|
||||
|
||||
if (!typeInfo<float>::isEqual(m_amplification, 1.0f))
|
||||
src_set_ratio(state->resampleState, resampleRatio);
|
||||
if (src_callback_read(state->resampleState, resampleRatio, numFrames, &dst[0][0]) != 0)
|
||||
{
|
||||
for (auto i = std::size_t{0}; i < numFrames; ++i)
|
||||
{
|
||||
dst[i][0] *= m_amplification;
|
||||
dst[i][1] *= m_amplification;
|
||||
}
|
||||
MixHelpers::multiply(dst, m_amplification, numFrames);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Sample::sampleDuration() const -> std::chrono::milliseconds
|
||||
@@ -170,82 +157,43 @@ void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame,
|
||||
setLoopEndFrame(loopEndFrame);
|
||||
}
|
||||
|
||||
void Sample::playRaw(SampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const
|
||||
long Sample::render(void* callbackData, float** data)
|
||||
{
|
||||
if (m_buffer->size() < 1) { return; }
|
||||
const auto state = static_cast<PlaybackState*>(callbackData);
|
||||
const auto loop = *state->loop;
|
||||
const auto sample = state->sample;
|
||||
auto& index = state->frameIndex;
|
||||
auto& backwards = state->backwards;
|
||||
|
||||
auto index = state->m_frameIndex;
|
||||
auto backwards = state->m_backwards;
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i)
|
||||
{
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::Off:
|
||||
if (index < 0 || index >= m_endFrame) { 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;
|
||||
}
|
||||
|
||||
dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index - 1 : index];
|
||||
backwards ? --index : ++index;
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const
|
||||
{
|
||||
state->m_frameIndex += (state->m_backwards ? -1 : 1) * advanceAmount;
|
||||
if (loopMode == Loop::Off) { return; }
|
||||
|
||||
const auto distanceFromLoopStart = std::abs(state->m_frameIndex - m_loopStartFrame);
|
||||
const auto distanceFromLoopEnd = std::abs(state->m_frameIndex - m_loopEndFrame);
|
||||
const auto loopSize = m_loopEndFrame - m_loopStartFrame;
|
||||
if (loopSize == 0) { return; }
|
||||
|
||||
switch (loopMode)
|
||||
switch (loop)
|
||||
{
|
||||
case Loop::Off:
|
||||
if (index < 0 || index >= sample->m_endFrame) { return 0; }
|
||||
break;
|
||||
case Loop::On:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopStart % loopSize;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopEnd % loopSize;
|
||||
}
|
||||
if (index < sample->m_loopStartFrame && state->backwards) { index = sample->m_loopEndFrame - 1; }
|
||||
else if (index >= sample->m_loopEndFrame) { index = sample->m_loopStartFrame; }
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
if (index < sample->m_loopStartFrame && state->backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % loopSize;
|
||||
state->m_backwards = false;
|
||||
index = sample->m_loopStartFrame;
|
||||
backwards = false;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
else if (index >= sample->m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % loopSize;
|
||||
state->m_backwards = true;
|
||||
index = sample->m_loopEndFrame - 1;
|
||||
backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto srcIndex = sample->m_reversed ? sample->m_buffer->size() - index - 1 : index;
|
||||
*data = const_cast<float*>(&sample->m_buffer->data()[srcIndex][0]);
|
||||
backwards ? --index : ++index;
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
Reference in New Issue
Block a user