Fix audio resampling functionality (#7858)
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
This commit is contained in:
@@ -105,40 +105,6 @@ public:
|
||||
AudioEngine* m_audioEngine;
|
||||
};
|
||||
|
||||
struct qualitySettings
|
||||
{
|
||||
enum class Interpolation
|
||||
{
|
||||
Linear,
|
||||
SincFastest,
|
||||
SincMedium,
|
||||
SincBest
|
||||
} ;
|
||||
|
||||
Interpolation interpolation;
|
||||
|
||||
qualitySettings(Interpolation i) :
|
||||
interpolation(i)
|
||||
{
|
||||
}
|
||||
|
||||
int libsrcInterpolation() const
|
||||
{
|
||||
switch( interpolation )
|
||||
{
|
||||
case Interpolation::Linear:
|
||||
return SRC_ZERO_ORDER_HOLD;
|
||||
case Interpolation::SincFastest:
|
||||
return SRC_SINC_FASTEST;
|
||||
case Interpolation::SincMedium:
|
||||
return SRC_SINC_MEDIUM_QUALITY;
|
||||
case Interpolation::SincBest:
|
||||
return SRC_SINC_BEST_QUALITY;
|
||||
}
|
||||
return SRC_LINEAR;
|
||||
}
|
||||
} ;
|
||||
|
||||
void initDevices();
|
||||
void clear();
|
||||
void clearNewPlayHandles();
|
||||
@@ -160,10 +126,7 @@ public:
|
||||
|
||||
//! Set new audio device. Old device will be deleted,
|
||||
//! unless it's stored using storeAudioDevice
|
||||
void setAudioDevice( AudioDevice * _dev,
|
||||
const struct qualitySettings & _qs,
|
||||
bool _needs_fifo,
|
||||
bool startNow );
|
||||
void setAudioDevice(AudioDevice* _dev, bool _needs_fifo, bool startNow);
|
||||
void storeAudioDevice();
|
||||
void restoreAudioDevice();
|
||||
inline AudioDevice * audioDev()
|
||||
@@ -230,12 +193,6 @@ public:
|
||||
return m_profiler.detailLoad(type);
|
||||
}
|
||||
|
||||
const qualitySettings & currentQualitySettings() const
|
||||
{
|
||||
return m_qualitySettings;
|
||||
}
|
||||
|
||||
|
||||
sample_rate_t baseSampleRate() const { return m_baseSampleRate; }
|
||||
|
||||
|
||||
@@ -300,8 +257,6 @@ public:
|
||||
return hasFifoWriter() ? m_fifo->read() : renderNextBuffer();
|
||||
}
|
||||
|
||||
void changeQuality(const struct qualitySettings & qs);
|
||||
|
||||
//! Block until a change in model can be done (i.e. wait for audio thread)
|
||||
void requestChangeInModel();
|
||||
void doneChangeInModel();
|
||||
@@ -390,8 +345,6 @@ private:
|
||||
LocklessList<PlayHandle *> m_newPlayHandles;
|
||||
ConstPlayHandleList m_playHandlesToRemove;
|
||||
|
||||
|
||||
struct qualitySettings m_qualitySettings;
|
||||
float m_masterGain;
|
||||
|
||||
// audio device stuff
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* AudioResampler.h - wrapper around libsamplerate
|
||||
* AudioResampler.h
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
* Copyright (c) 2025 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
@@ -25,41 +25,104 @@
|
||||
#ifndef LMMS_AUDIO_RESAMPLER_H
|
||||
#define LMMS_AUDIO_RESAMPLER_H
|
||||
|
||||
#include <samplerate.h>
|
||||
|
||||
#include <memory>
|
||||
#include "AudioBufferView.h"
|
||||
#include "lmms_export.h"
|
||||
|
||||
namespace lmms {
|
||||
|
||||
/**
|
||||
* @class AudioResampler
|
||||
* @brief A utility class for resampling interleaved audio buffers using various resampling algorithms.
|
||||
*
|
||||
* This class provides support for zero-order hold, linear, and several levels of sinc-based resampling.
|
||||
*/
|
||||
class LMMS_EXPORT AudioResampler
|
||||
{
|
||||
public:
|
||||
struct ProcessResult
|
||||
/**
|
||||
* @enum Mode
|
||||
* @brief Defines the resampling method to use.
|
||||
*/
|
||||
enum class Mode
|
||||
{
|
||||
int error;
|
||||
long inputFramesUsed;
|
||||
long outputFramesGenerated;
|
||||
ZOH, //!< Zero Order Hold (nearest-neighbor) interpolation.
|
||||
Linear, //!< Linear interpolation.
|
||||
SincFastest, //!< Fastest sinc-based resampling.
|
||||
SincMedium, //!< Medium quality sinc-based resampling.
|
||||
SincBest //!< Highest quality sinc-based resampling.
|
||||
};
|
||||
|
||||
AudioResampler(int interpolationMode, int channels);
|
||||
AudioResampler(const AudioResampler&) = delete;
|
||||
AudioResampler(AudioResampler&&) = delete;
|
||||
~AudioResampler();
|
||||
/**
|
||||
* @struct Result
|
||||
* @brief Result of a resampling operation.
|
||||
*/
|
||||
struct Result
|
||||
{
|
||||
f_cnt_t inputFramesUsed; //!< The number of input frames used during processing.
|
||||
f_cnt_t outputFramesGenerated; //!< The number of output frames generated during processing.
|
||||
};
|
||||
|
||||
AudioResampler& operator=(const AudioResampler&) = delete;
|
||||
AudioResampler& operator=(AudioResampler&&) = delete;
|
||||
/**
|
||||
* @brief Constructs an `AudioResampler` instance.
|
||||
* @param mode The resampling mode to use.
|
||||
* @param channels Number of audio channels. Defaults to `2` (stereo).
|
||||
*/
|
||||
AudioResampler(Mode mode, ch_cnt_t channels = 2);
|
||||
|
||||
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);
|
||||
/**
|
||||
* @brief Process a block of interleaved audio input from `input` and resample it into `output`.
|
||||
*
|
||||
* @param input The interleaved audio input.
|
||||
* @param output The interleaved audio output.
|
||||
*
|
||||
* @throws `std::invalid_argument` if a channel mismatch has been detected.
|
||||
* @throws `std::runtime_error` if the resampling process has failed.
|
||||
*
|
||||
* @remark This utility class does not cache the input and output buffers, making it stateless. In other words,
|
||||
* `input` is directly resampled into the `output`.
|
||||
*
|
||||
* @returns the result of the resampling process. See @ref Result for more details.
|
||||
*/
|
||||
[[nodiscard]] auto process(InterleavedBufferView<const float> input, InterleavedBufferView<float> output) -> Result;
|
||||
|
||||
/**
|
||||
* @brief Resets the internal resampler state.
|
||||
* Useful when working with unreleated pieces of audio.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief Sets the resampling ratio to `ratio`.
|
||||
* @param ratio Output sample rate divided by input sample rate.
|
||||
*/
|
||||
void setRatio(double ratio) { m_ratio = ratio; }
|
||||
|
||||
/**
|
||||
* @brief Sets the resampling ratio to `output / input`.
|
||||
* @param input Input sample rate.
|
||||
* @param output Output sample rate.
|
||||
*/
|
||||
void setRatio(sample_rate_t input, sample_rate_t output) { m_ratio = static_cast<double>(output) / input; }
|
||||
|
||||
//! @returns the resampling ratio.
|
||||
auto ratio() const -> double { return m_ratio; }
|
||||
|
||||
//! @returns the number of channels expected by the resampler.
|
||||
auto channels() const -> ch_cnt_t { return m_channels; }
|
||||
|
||||
//! @returns the interpolation mode used by this resampler.
|
||||
auto mode() const -> Mode { return m_mode; }
|
||||
|
||||
private:
|
||||
int m_interpolationMode = -1;
|
||||
int m_channels = 0;
|
||||
struct LMMS_EXPORT StateDeleter { void operator()(void* state); };
|
||||
std::unique_ptr<void, StateDeleter> m_state;
|
||||
Mode m_mode;
|
||||
ch_cnt_t m_channels = 0;
|
||||
double m_ratio = 1.0;
|
||||
int m_error = 0;
|
||||
SRC_STATE* m_state = nullptr;
|
||||
};
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_AUDIO_RESAMPLER_H
|
||||
|
||||
@@ -55,7 +55,6 @@ public:
|
||||
Effect( const Plugin::Descriptor * _desc,
|
||||
Model * _parent,
|
||||
const Descriptor::SubPluginFeatures::Key * _key );
|
||||
~Effect() override;
|
||||
|
||||
void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override;
|
||||
void loadSettings( const QDomElement & _this ) override;
|
||||
@@ -176,29 +175,6 @@ protected:
|
||||
|
||||
gui::PluginView* instantiateView( QWidget * ) override;
|
||||
|
||||
// some effects might not be capable of higher sample-rates so they can
|
||||
// sample it down before processing and back after processing
|
||||
inline void sampleDown( const SampleFrame* _src_buf,
|
||||
SampleFrame* _dst_buf,
|
||||
sample_rate_t _dst_sr )
|
||||
{
|
||||
resample( 0, _src_buf,
|
||||
Engine::audioEngine()->outputSampleRate(),
|
||||
_dst_buf, _dst_sr,
|
||||
Engine::audioEngine()->framesPerPeriod() );
|
||||
}
|
||||
|
||||
inline void sampleBack( const SampleFrame* _src_buf,
|
||||
SampleFrame* _dst_buf,
|
||||
sample_rate_t _src_sr )
|
||||
{
|
||||
resample( 1, _src_buf, _src_sr, _dst_buf,
|
||||
Engine::audioEngine()->outputSampleRate(),
|
||||
Engine::audioEngine()->framesPerPeriod() * _src_sr /
|
||||
Engine::audioEngine()->outputSampleRate() );
|
||||
}
|
||||
void reinitSRC();
|
||||
|
||||
virtual void onEnabledChanged() {}
|
||||
|
||||
|
||||
@@ -212,10 +188,6 @@ private:
|
||||
|
||||
|
||||
EffectChain * m_parent;
|
||||
void resample( int _i, const SampleFrame* _src_buf,
|
||||
sample_rate_t _src_sr,
|
||||
SampleFrame* _dst_buf, sample_rate_t _dst_sr,
|
||||
const f_cnt_t _frames );
|
||||
|
||||
bool m_okay;
|
||||
bool m_noRun;
|
||||
@@ -230,10 +202,6 @@ private:
|
||||
|
||||
bool m_autoQuitEnabled = false;
|
||||
|
||||
SRC_DATA m_srcData[2];
|
||||
SRC_STATE * m_srcState[2];
|
||||
|
||||
|
||||
friend class gui::EffectView;
|
||||
friend class EffectChain;
|
||||
|
||||
|
||||
@@ -59,11 +59,7 @@ public:
|
||||
AudioFileDeviceInstantiaton m_getDevInst;
|
||||
} ;
|
||||
|
||||
|
||||
ProjectRenderer( const AudioEngine::qualitySettings & _qs,
|
||||
const OutputSettings & _os,
|
||||
ExportFileFormat _file_format,
|
||||
const QString & _out_file );
|
||||
ProjectRenderer(const OutputSettings& _os, ExportFileFormat _file_format, const QString& _out_file);
|
||||
~ProjectRenderer() override = default;
|
||||
|
||||
bool isReady() const
|
||||
@@ -93,7 +89,6 @@ private:
|
||||
void run() override;
|
||||
|
||||
AudioFileDevice * m_fileDev;
|
||||
AudioEngine::qualitySettings m_qualitySettings;
|
||||
|
||||
volatile int m_progress;
|
||||
volatile bool m_abort;
|
||||
|
||||
@@ -40,11 +40,7 @@ class RenderManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenderManager(
|
||||
const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ProjectRenderer::ExportFileFormat fmt,
|
||||
QString outputPath);
|
||||
RenderManager(const OutputSettings& outputSettings, ProjectRenderer::ExportFileFormat fmt, QString outputPath);
|
||||
|
||||
~RenderManager() override;
|
||||
|
||||
@@ -70,8 +66,6 @@ private:
|
||||
|
||||
void render( QString outputPath );
|
||||
|
||||
const AudioEngine::qualitySettings m_qualitySettings;
|
||||
const AudioEngine::qualitySettings m_oldQualitySettings;
|
||||
const OutputSettings m_outputSettings;
|
||||
ProjectRenderer::ExportFileFormat m_format;
|
||||
QString m_outputPath;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Sample.h - State for container-class SampleBuffer
|
||||
* Sample.h
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
* Copyright (c) 2025 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
@@ -36,12 +36,6 @@ 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,
|
||||
@@ -52,42 +46,41 @@ public:
|
||||
class LMMS_EXPORT PlaybackState
|
||||
{
|
||||
public:
|
||||
PlaybackState(bool varyingPitch = false, int interpolationMode = SRC_LINEAR)
|
||||
: m_resampler(interpolationMode, DEFAULT_CHANNELS)
|
||||
, m_varyingPitch(varyingPitch)
|
||||
PlaybackState(AudioResampler::Mode interpolationMode = AudioResampler::Mode::Linear, int frameIndex = 0)
|
||||
: m_resampler(interpolationMode)
|
||||
, m_frameIndex(frameIndex)
|
||||
{
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
void setFrameIndex(int frameIndex) { m_frameIndex = frameIndex; }
|
||||
void setVaryingPitch(bool varyingPitch) { m_varyingPitch = varyingPitch; }
|
||||
void setFrameIndex(int index) { m_frameIndex = index; }
|
||||
void setBackwards(bool backwards) { m_backwards = backwards; }
|
||||
|
||||
private:
|
||||
AudioResampler m_resampler;
|
||||
std::array<SampleFrame, DEFAULT_BUFFER_SIZE> m_buffer;
|
||||
std::span<SampleFrame> m_bufferView;
|
||||
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()->outputSampleRate());
|
||||
Sample(const SampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate());
|
||||
Sample(const Sample& other);
|
||||
Sample(Sample&& other);
|
||||
Sample(Sample&& other) noexcept;
|
||||
explicit Sample(const QString& audioFile);
|
||||
explicit Sample(std::shared_ptr<const SampleBuffer> buffer);
|
||||
|
||||
auto operator=(const Sample&) -> Sample&;
|
||||
auto operator=(Sample&&) -> Sample&;
|
||||
auto operator=(Sample&&) noexcept -> Sample&;
|
||||
|
||||
auto play(SampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency = DefaultBaseFreq,
|
||||
Loop loopMode = Loop::Off) const -> bool;
|
||||
auto play(SampleFrame* dst, PlaybackState* state, size_t numFrames, Loop loopMode = Loop::Off,
|
||||
double ratio = 1.0) const -> bool;
|
||||
|
||||
auto sampleDuration() const -> std::chrono::milliseconds;
|
||||
auto sampleFile() const -> const QString& { return m_buffer->audioFile(); }
|
||||
@@ -116,10 +109,7 @@ 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:
|
||||
f_cnt_t render(SampleFrame* dst, f_cnt_t size, PlaybackState* state, Loop loop) const;
|
||||
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
|
||||
std::atomic<int> m_startFrame = 0;
|
||||
std::atomic<int> m_endFrame = 0;
|
||||
|
||||
Reference in New Issue
Block a user