diff --git a/include/AudioResampler.h b/include/AudioResampler.h deleted file mode 100644 index 6dd6fcc60..000000000 --- a/include/AudioResampler.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * AudioResampler.h - wrapper around libsamplerate - * - * Copyright (c) 2023 saker - * - * 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 - -#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 diff --git a/include/Sample.h b/include/Sample.h index 3fd5bc38e..754350368 100644 --- a/include/Sample.h +++ b/include/Sample.h @@ -25,24 +25,18 @@ #ifndef LMMS_SAMPLE_H #define LMMS_SAMPLE_H -#include #include +#include -#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{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 m_buffer = SampleBuffer::emptyBuffer(); std::atomic m_startFrame = 0; std::atomic m_endFrame = 0; std::atomic m_loopStartFrame = 0; std::atomic m_loopEndFrame = 0; std::atomic m_amplification = 1.0f; - std::atomic m_frequency = DefaultBaseFreq; + std::atomic m_frequency = DefaultBaseFreq; std::atomic m_reversed = false; }; } // namespace lmms diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 8ec6c5886..114634577 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 4cc14ba9c..2e63b5178 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -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(_n->m_pluginData)->setFrameIndex(m_nextPlayStartPoint); - static_cast(_n->m_pluginData)->setBackwards(m_nextPlayBackwards); + _n->m_pluginData = new Sample::PlaybackState(srcmode); + static_cast(_n->m_pluginData)->frameIndex = m_nextPlayStartPoint; + static_cast(_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(m_loopModel.value()))) { applyRelease( _working_buffer, _n ); - emit isPlaying(static_cast(_n->m_pluginData)->frameIndex()); + emit isPlaying(static_cast(_n->m_pluginData)->frameIndex); } else { @@ -176,8 +176,8 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, } if( m_stutterModel.value() == true ) { - m_nextPlayStartPoint = static_cast(_n->m_pluginData)->frameIndex(); - m_nextPlayBackwards = static_cast(_n->m_pluginData)->backwards(); + m_nextPlayStartPoint = static_cast(_n->m_pluginData)->frameIndex; + m_nextPlayBackwards = static_cast(_n->m_pluginData)->backwards; } } diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index b72e30b33..061df7bd5 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -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 diff --git a/plugins/GigPlayer/GigPlayer.h b/plugins/GigPlayer/GigPlayer.h index 685c7f546..117178e54 100644 --- a/plugins/GigPlayer/GigPlayer.h +++ b/plugins/GigPlayer/GigPlayer.h @@ -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{64, 64, 64, 4, 4}; + GigInstrument( InstrumentTrack * _instrument_track ); ~GigInstrument() override; diff --git a/src/core/AudioResampler.cpp b/src/core/AudioResampler.cpp deleted file mode 100644 index 8fb7d95a2..000000000 --- a/src/core/AudioResampler.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * AudioResampler.cpp - wrapper for libsamplerate - * - * Copyright (c) 2023 saker - * - * 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 -#include -#include - -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 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3608d2848..9eeb33904 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/Sample.cpp b/src/core/Sample.cpp index db99620c9..564e08201 100644 --- a/src/core/Sample.cpp +++ b/src/core/Sample.cpp @@ -26,6 +26,8 @@ #include +#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(m_startFrame, state->m_frameIndex); + state->frameIndex = std::max(m_startFrame, state->frameIndex); + state->sample = this; + state->loop = &loopMode; - auto playBuffer = std::vector(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(resampleResult.outputFramesGenerated); - if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, SampleFrame{}); } - - if (!typeInfo::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(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(&sample->m_buffer->data()[srcIndex][0]); + backwards ? --index : ++index; + return 1; } } // namespace lmms