diff --git a/include/AudioEngine.h b/include/AudioEngine.h index 79f6eb281e2..3f736b7614f 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -105,40 +105,6 @@ class LMMS_EXPORT AudioEngine : public QObject 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 @@ class LMMS_EXPORT AudioEngine : public QObject //! 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 @@ class LMMS_EXPORT AudioEngine : public QObject return m_profiler.detailLoad(type); } - const qualitySettings & currentQualitySettings() const - { - return m_qualitySettings; - } - - sample_rate_t baseSampleRate() const { return m_baseSampleRate; } @@ -300,8 +257,6 @@ class LMMS_EXPORT AudioEngine : public QObject 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 @@ class LMMS_EXPORT AudioEngine : public QObject LocklessList m_newPlayHandles; ConstPlayHandleList m_playHandlesToRemove; - - struct qualitySettings m_qualitySettings; float m_masterGain; // audio device stuff diff --git a/include/AudioResampler.h b/include/AudioResampler.h index 6dd6fcc6039..d2b5e139335 100644 --- a/include/AudioResampler.h +++ b/include/AudioResampler.h @@ -1,7 +1,7 @@ /* - * AudioResampler.h - wrapper around libsamplerate + * AudioResampler.h * - * Copyright (c) 2023 saker + * Copyright (c) 2025 saker * * This file is part of LMMS - https://lmms.io * @@ -25,41 +25,104 @@ #ifndef LMMS_AUDIO_RESAMPLER_H #define LMMS_AUDIO_RESAMPLER_H -#include - +#include +#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 + { + 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. + }; + + /** + * @struct Result + * @brief Result of a resampling operation. + */ + struct Result { - int error; - long inputFramesUsed; - long outputFramesGenerated; + 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(int interpolationMode, int channels); - AudioResampler(const AudioResampler&) = delete; - AudioResampler(AudioResampler&&) = delete; - ~AudioResampler(); + /** + * @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); - AudioResampler& operator=(const AudioResampler&) = delete; - AudioResampler& operator=(AudioResampler&&) = delete; + /** + * @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 input, InterleavedBufferView output) -> Result; - 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 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(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 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 diff --git a/include/Effect.h b/include/Effect.h index 3c6a6e37d50..d6454b32be3 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -55,7 +55,6 @@ class LMMS_EXPORT Effect : public Plugin 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 @@ class LMMS_EXPORT Effect : public Plugin 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 @@ class LMMS_EXPORT Effect : public Plugin 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 @@ class LMMS_EXPORT Effect : public Plugin bool m_autoQuitEnabled = false; - SRC_DATA m_srcData[2]; - SRC_STATE * m_srcState[2]; - - friend class gui::EffectView; friend class EffectChain; diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 3183fc4788f..6363dd6c82d 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -59,11 +59,7 @@ class LMMS_EXPORT ProjectRenderer : public QThread 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 @@ public slots: void run() override; AudioFileDevice * m_fileDev; - AudioEngine::qualitySettings m_qualitySettings; volatile int m_progress; volatile bool m_abort; diff --git a/include/RenderManager.h b/include/RenderManager.h index 6865227789e..267e2dd4e27 100644 --- a/include/RenderManager.h +++ b/include/RenderManager.h @@ -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 slots: 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; diff --git a/include/Sample.h b/include/Sample.h index f5178d3a1de..b631e6d9e55 100644 --- a/include/Sample.h +++ b/include/Sample.h @@ -1,7 +1,7 @@ /* - * Sample.h - State for container-class SampleBuffer + * Sample.h * - * Copyright (c) 2023 saker + * Copyright (c) 2025 saker * * 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{64, 64, 64, 4, 4}; - enum class Loop { Off, @@ -52,42 +46,41 @@ class LMMS_EXPORT Sample 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 m_buffer; + std::span 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 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 @@ class LMMS_EXPORT Sample 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 m_buffer = SampleBuffer::emptyBuffer(); std::atomic m_startFrame = 0; std::atomic m_endFrame = 0; diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 6754624df5d..7cdb58d8f57 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -131,20 +131,21 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, m_nextPlayBackwards = false; } // set interpolation mode for libsamplerate - int srcmode = SRC_LINEAR; + auto interpolationMode = AudioResampler::Mode::Linear; switch( m_interpolationModel.value() ) { case 0: - srcmode = SRC_ZERO_ORDER_HOLD; + interpolationMode = AudioResampler::Mode::ZOH; break; case 1: - srcmode = SRC_LINEAR; + interpolationMode = AudioResampler::Mode::Linear; break; case 2: - srcmode = SRC_SINC_MEDIUM_QUALITY; + interpolationMode = AudioResampler::Mode::SincMedium; break; } - _n->m_pluginData = new Sample::PlaybackState(_n->hasDetuningInfo(), srcmode); + + _n->m_pluginData = new Sample::PlaybackState(interpolationMode); static_cast(_n->m_pluginData)->setFrameIndex(m_nextPlayStartPoint); static_cast(_n->m_pluginData)->setBackwards(m_nextPlayBackwards); @@ -158,8 +159,8 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, { if (m_sample.play(_working_buffer + offset, static_cast(_n->m_pluginData), - frames, _n->frequency(), - static_cast(m_loopModel.value()))) + frames, static_cast(m_loopModel.value()), + DefaultBaseFreq / _n->frequency())) { applyRelease( _working_buffer, _n ); emit isPlaying(static_cast(_n->m_pluginData)->frameIndex()); diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index c2d27f1e67a..859dd277398 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -44,6 +44,7 @@ #include "InstrumentTrack.h" #include "InstrumentPlayHandle.h" #include "Knob.h" +#include "MixHelpers.h" #include "NotePlayHandle.h" #include "PathUtil.h" #include "Sample.h" @@ -77,20 +78,16 @@ Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor = } - - - -GigInstrument::GigInstrument( InstrumentTrack * _instrument_track ) : - Instrument(_instrument_track, &gigplayer_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsNotBendable), - m_instance( nullptr ), - m_instrument( nullptr ), - m_filename( "" ), - m_bankNum( 0, 0, 999, this, tr( "Bank" ) ), - m_patchNum( 0, 0, 127, this, tr( "Patch" ) ), - m_gain( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Gain" ) ), - m_interpolation( SRC_LINEAR ), - m_RandomSeed( 0 ), - m_currentKeyDimension( 0 ) +GigInstrument::GigInstrument(InstrumentTrack* _instrument_track) + : Instrument(_instrument_track, &gigplayer_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsNotBendable) + , m_instance(nullptr) + , m_instrument(nullptr) + , m_filename("") + , m_bankNum(0, 0, 999, this, tr("Bank")) + , m_patchNum(0, 0, 127, this, tr("Patch")) + , m_gain(1.0f, 0.0f, 5.0f, 0.01f, this, tr("Gain")) + , m_RandomSeed(0) + , m_currentKeyDimension(0) { auto iph = new InstrumentPlayHandle(this, _instrument_track); Engine::audioEngine()->addPlayHandle( iph ); @@ -373,8 +370,7 @@ void GigInstrument::play( SampleFrame* _working_buffer ) } // Delete ended samples - for( QList::iterator sample = it->samples.begin(); - sample != it->samples.end(); ++sample ) + for (auto sample = it->samples.begin(); sample != it->samples.end(); ++sample) { // Delete if the ADSR for a sample is complete for normal // notes, or if a release sample, then if we've reached @@ -417,71 +413,67 @@ void GigInstrument::play( SampleFrame* _working_buffer ) { if (sample.sample == nullptr || sample.region == nullptr) { continue; } - // Will change if resampling - bool resample = false; - f_cnt_t samples = frames; // How many to grab - f_cnt_t used = frames; // How many we used - float freq_factor = 1.0; // How to resample + float freq_factor = 1.0; // How much to resample // Resample to be the correct pitch when the sample provided isn't // solely for this one note (e.g. one or two samples per octave) or // we are processing at a different sample rate if (sample.region->PitchTrack == true || rate != sample.sample->SamplesPerSecond) { - resample = true; - // Factor just for resampling freq_factor = 1.0 * rate / sample.sample->SamplesPerSecond; // Factor for pitch shifting as well as resampling 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]; } - // Load this note's data - SampleFrame sampleData[samples]; - loadSample(sample, sampleData, samples); - // Apply ADSR using a copy so if we don't use these samples when // resampling, the ADSR doesn't get messed up ADSR copy = sample.adsr; - for( f_cnt_t i = 0; i < samples; ++i ) - { - float amplitude = copy.value(); - sampleData[i][0] *= amplitude; - sampleData[i][1] *= amplitude; - } + sample.m_resampler.setRatio(freq_factor); - // Output the data resampling if needed - if( resample == true ) + // TODO: These kind of playback pipelines/graphs are repeated within other parts of the codebase that work + // with audio samples. We should find a way to unify this but the right abstraction is not so clear yet. + auto framesMixed = f_cnt_t{0}; + while (framesMixed < frames) { - SampleFrame convertBuf[frames]; - - // Only output if resampling is successful (note that "used" is output) - if (sample.convertSampleRate(*sampleData, *convertBuf, samples, frames, freq_factor, used)) + if (sample.m_sourceBufferView.empty()) { - for( f_cnt_t i = 0; i < frames; ++i ) + loadSample(sample, sample.m_sourceBuffer.data(), sample.m_sourceBuffer.size()); + + for (auto& frame : sample.m_sourceBuffer) { - _working_buffer[i][0] += convertBuf[i][0]; - _working_buffer[i][1] += convertBuf[i][1]; + frame *= copy.value(); } + + sample.pos += sample.m_sourceBuffer.size(); + sample.adsr.inc(sample.m_sourceBuffer.size()); + sample.m_sourceBufferView = sample.m_sourceBuffer; } - } - else - { - for( f_cnt_t i = 0; i < frames; ++i ) + + if (sample.m_mixBufferView.empty()) { sample.m_mixBufferView = sample.m_mixBuffer; } + + const auto [inputFramesUsed, outputFramesGenerated] = sample.m_resampler.process( + {&sample.m_sourceBufferView.data()[0][0], 2, sample.m_sourceBufferView.size()}, + {&sample.m_mixBufferView.data()[0][0], 2, sample.m_mixBufferView.size()}); + + if (inputFramesUsed == 0 && outputFramesGenerated == 0) { - _working_buffer[i][0] += sampleData[i][0]; - _working_buffer[i][1] += sampleData[i][1]; + std::fill_n(&_working_buffer[framesMixed], frames - framesMixed, SampleFrame{}); + break; } - } - // Update note position with how many samples we actually used - sample.pos += used; - sample.adsr.inc(used); + const auto framesToMix = std::min(outputFramesGenerated, frames - framesMixed); + for (auto i = f_cnt_t{0}; i < framesToMix; ++i) + { + _working_buffer[framesMixed + i] += sample.m_mixBufferView[i]; + } + + sample.m_sourceBufferView = sample.m_sourceBufferView.subspan(inputFramesUsed); + sample.m_mixBufferView = sample.m_mixBufferView.subspan(framesToMix); + framesMixed += framesToMix; + } } } @@ -760,8 +752,7 @@ void GigInstrument::addSamples( GigNote & gignote, bool wantReleaseSample ) attenuation *= pDimRegion->SampleAttenuation; } - gignote.samples.push_back( GigSample( pSample, pDimRegion, - attenuation, m_interpolation, gignote.frequency ) ); + gignote.samples.emplace_back(pSample, pDimRegion, attenuation, AudioResampler::Mode::Linear, gignote.frequency); } } @@ -1083,19 +1074,18 @@ void GigInstrumentView::showPatchDialog() // Store information related to playing a sample from the GIG file -GigSample::GigSample( gig::Sample * pSample, gig::DimensionRegion * pDimRegion, - float attenuation, int interpolation, float desiredFreq ) - : sample( pSample ), region( pDimRegion ), attenuation( attenuation ), - pos( 0 ), interpolation( interpolation ), srcState( nullptr ), - sampleFreq( 0 ), freqFactor( 1 ) +GigSample::GigSample(gig::Sample* pSample, gig::DimensionRegion* pDimRegion, float attenuation, + AudioResampler::Mode interpolation, float desiredFreq) + : sample(pSample) + , region(pDimRegion) + , attenuation(attenuation) + , pos(0) + , m_resampler(interpolation) + , sampleFreq(0) + , freqFactor(1) { if( sample != nullptr && region != nullptr ) { - // Note: we don't create the libsamplerate object here since we always - // also call the copy constructor when appending to the end of the - // QList. We'll create it only in the copy constructor so we only have - // to create it once. - // Calculate note pitch and frequency factor only if we're actually // going to be changing the pitch of the notes if( region->PitchTrack == true ) @@ -1112,27 +1102,16 @@ GigSample::GigSample( gig::Sample * pSample, gig::DimensionRegion * pDimRegion, } } - - - -GigSample::~GigSample() +GigSample::GigSample(const GigSample& g) + : sample(g.sample) + , region(g.region) + , attenuation(g.attenuation) + , adsr(g.adsr) + , pos(g.pos) + , m_resampler(AudioResampler::Mode::Linear, DEFAULT_CHANNELS) + , sampleFreq(g.sampleFreq) + , freqFactor(g.freqFactor) { - if( srcState != nullptr ) - { - src_delete( srcState ); - } -} - - - - -GigSample::GigSample( const GigSample& g ) - : sample( g.sample ), region( g.region ), attenuation( g.attenuation ), - adsr( g.adsr ), pos( g.pos ), interpolation( g.interpolation ), - srcState( nullptr ), sampleFreq( g.sampleFreq ), freqFactor( g.freqFactor ) -{ - // On the copy, we want to create the object - updateSampleRate(); } @@ -1145,88 +1124,11 @@ GigSample& GigSample::operator=( const GigSample& g ) attenuation = g.attenuation; adsr = g.adsr; pos = g.pos; - interpolation = g.interpolation; - srcState = nullptr; sampleFreq = g.sampleFreq; freqFactor = g.freqFactor; - - if( g.srcState != nullptr ) - { - updateSampleRate(); - } - return *this; } - - - -void GigSample::updateSampleRate() -{ - if( srcState != nullptr ) - { - src_delete( srcState ); - } - - int error = 0; - srcState = src_new( interpolation, DEFAULT_CHANNELS, &error ); - - if( srcState == nullptr || error != 0 ) - { - qCritical( "error while creating libsamplerate data structure in GigSample" ); - } -} - - - - -bool GigSample::convertSampleRate( SampleFrame & oldBuf, SampleFrame & newBuf, - f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used ) -{ - if( srcState == nullptr ) - { - return false; - } - - SRC_DATA src_data; - src_data.data_in = &oldBuf[0]; - src_data.data_out = &newBuf[0]; - src_data.input_frames = oldSize; - src_data.output_frames = newSize; - src_data.src_ratio = freq_factor; - src_data.end_of_input = 0; - - // We don't need to lock this assuming that we're only outputting the - // samples in one thread - int error = src_process( srcState, &src_data ); - - used = src_data.input_frames_used; - - if( error != 0 ) - { - qCritical( "GigInstrument: error while resampling: %s", src_strerror( error ) ); - return false; - } - - if( oldSize != 0 && src_data.output_frames_gen == 0 ) - { - qCritical( "GigInstrument: could not resample, no frames generated" ); - return false; - } - - if (src_data.output_frames_gen > 0 && static_cast(src_data.output_frames_gen) < newSize) - { - qCritical() << "GigInstrument: not enough frames, wanted" - << newSize << "generated" << src_data.output_frames_gen; - return false; - } - - return true; -} - - - - ADSR::ADSR() : preattack( 0 ), attack( 0 ), decay1( 0 ), decay2( 0 ), infiniteSustain( false ), sustain( 0 ), release( 0 ), diff --git a/plugins/GigPlayer/GigPlayer.h b/plugins/GigPlayer/GigPlayer.h index 685c7f5469a..03b64c14ccf 100644 --- a/plugins/GigPlayer/GigPlayer.h +++ b/plugins/GigPlayer/GigPlayer.h @@ -32,12 +32,14 @@ #include #include +#include "AudioEngine.h" +#include "AudioResampler.h" #include "Instrument.h" #include "PixmapButton.h" #include "InstrumentView.h" #include "Knob.h" #include "LcdSpinBox.h" -#include "LedCheckBox.h" +#include "SampleFrame.h" #include "gig.h" @@ -147,19 +149,14 @@ class ADSR class GigSample { public: - GigSample( gig::Sample * pSample, gig::DimensionRegion * pDimRegion, - float attenuation, int interpolation, float desiredFreq ); - ~GigSample(); + GigSample(gig::Sample* pSample, gig::DimensionRegion* pDimRegion, float attenuation, + AudioResampler::Mode interpolation, float desiredFreq); + ~GigSample() = default; // Needed when initially creating in QList GigSample( const GigSample& g ); GigSample& operator=( const GigSample& g ); - // Needed since libsamplerate stores data internally between calls - void updateSampleRate(); - bool convertSampleRate( SampleFrame & oldBuf, SampleFrame & newBuf, - f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used ); - gig::Sample * sample; gig::DimensionRegion * region; float attenuation; @@ -174,8 +171,11 @@ class GigSample bool pitchtrack; // Used to convert sample rates - int interpolation; - SRC_STATE * srcState; + AudioResampler m_resampler; + std::array m_sourceBuffer; + std::array m_mixBuffer; + std::span m_sourceBufferView; + std::span m_mixBufferView; // Used changing the pitch of the note if desired float sampleFreq; @@ -213,7 +213,7 @@ class GigNote bool isRelease; // Whether this is a release sample, changes when we delete it GigState state; float frequency; - QList samples; + std::vector samples; // Used to determine which note should be released on key up // @@ -290,9 +290,6 @@ public slots: QMutex m_synthMutex; QMutex m_notesMutex; - // Used for resampling - int m_interpolation; - // List of all the currently playing notes QList m_notes; diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index b29a8584e7f..16ac99aa8c5 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -66,13 +66,10 @@ Plugin::Descriptor PLUGIN_EXPORT ladspaeffect_plugin_descriptor = } - -LadspaEffect::LadspaEffect( Model * _parent, - const Descriptor::SubPluginFeatures::Key * _key ) : - Effect( &ladspaeffect_plugin_descriptor, _parent, _key ), - m_controls( nullptr ), - m_maxSampleRate( 0 ), - m_key( LadspaSubPluginFeatures::subPluginKeyToLadspaKey( _key ) ) +LadspaEffect::LadspaEffect(Model* _parent, const Descriptor::SubPluginFeatures::Key* _key) + : Effect(&ladspaeffect_plugin_descriptor, _parent, _key) + , m_controls(nullptr) + , m_key(LadspaSubPluginFeatures::subPluginKeyToLadspaKey(_key)) { Ladspa2LMMS * manager = Engine::getLADSPAManager(); if( manager->getDescription( m_key ) == nullptr ) @@ -137,19 +134,6 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr return ProcessStatus::Sleep; } - auto outFrames = frames; - SampleFrame* outBuf = nullptr; - QVarLengthArray sBuf(frames); - - if( m_maxSampleRate < Engine::audioEngine()->outputSampleRate() ) - { - outBuf = buf; - buf = sBuf.data(); - sampleDown(outBuf, buf, m_maxSampleRate); - outFrames = frames * m_maxSampleRate / - Engine::audioEngine()->outputSampleRate(); - } - // Copy the LMMS audio buffer to the LADSPA input buffer and initialize // the control ports. ch_cnt_t channel = 0; @@ -161,7 +145,7 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr switch( pp->rate ) { case BufferRate::ChannelIn: - for (fpp_t frame = 0; frame < outFrames; ++frame) + for (fpp_t frame = 0; frame < frames; ++frame) { pp->buffer[frame] = buf[frame][channel]; } @@ -172,7 +156,7 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr ValueBuffer * vb = pp->control->valueBuffer(); if( vb ) { - memcpy(pp->buffer, vb->values(), outFrames * sizeof(float)); + memcpy(pp->buffer, vb->values(), frames * sizeof(float)); } else { @@ -181,7 +165,7 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr // This only supports control rate ports, so the audio rates are // treated as though they were control rate by setting the // port buffer to all the same value. - for (fpp_t frame = 0; frame < outFrames; ++frame) + for (fpp_t frame = 0; frame < frames; ++frame) { pp->buffer[frame] = pp->value; } @@ -212,7 +196,7 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr // Process the buffers. for( ch_cnt_t proc = 0; proc < processorCount(); ++proc ) { - (m_descriptor->run)(m_handles[proc], outFrames); + (m_descriptor->run)(m_handles[proc], frames); } // Copy the LADSPA output buffers to the LMMS buffer. @@ -231,7 +215,7 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr case BufferRate::ControlRateInput: break; case BufferRate::ChannelOut: - for (fpp_t frame = 0; frame < outFrames; ++frame) + for (fpp_t frame = 0; frame < frames; ++frame) { buf[frame][channel] = d * buf[frame][channel] + w * pp->buffer[frame]; } @@ -246,11 +230,6 @@ Effect::ProcessStatus LadspaEffect::processImpl(SampleFrame* buf, const fpp_t fr } } - if (outBuf != nullptr) - { - sampleBack(buf, outBuf, m_maxSampleRate); - } - m_pluginMutex.unlock(); return ProcessStatus::ContinueIfNotQuiet; @@ -273,8 +252,6 @@ void LadspaEffect::setControl( int _control, LADSPA_Data _value ) void LadspaEffect::pluginInstantiation() { - m_maxSampleRate = maxSamplerate( displayName() ); - Ladspa2LMMS * manager = Engine::getLADSPAManager(); // Calculate how many processing units are needed. @@ -406,7 +383,7 @@ void LadspaEffect::pluginInstantiation() if( manager->areHintsSampleRateDependent( m_key, port ) ) { - p->max *= m_maxSampleRate; + p->max *= Engine::audioEngine()->outputSampleRate(); } p->min = manager->getLowerBound( m_key, port ); @@ -418,7 +395,7 @@ void LadspaEffect::pluginInstantiation() if( manager->areHintsSampleRateDependent( m_key, port ) ) { - p->min *= m_maxSampleRate; + p->min *= Engine::audioEngine()->outputSampleRate(); } p->def = manager->getDefaultSetting( m_key, port ); @@ -435,7 +412,7 @@ void LadspaEffect::pluginInstantiation() } else if( manager->areHintsSampleRateDependent( m_key, port ) ) { - p->def *= m_maxSampleRate; + p->def *= Engine::audioEngine()->outputSampleRate(); } @@ -480,8 +457,7 @@ void LadspaEffect::pluginInstantiation() } for( ch_cnt_t proc = 0; proc < processorCount(); proc++ ) { - LADSPA_Handle effect = manager->instantiate( m_key, - m_maxSampleRate ); + LADSPA_Handle effect = manager->instantiate(m_key, Engine::audioEngine()->outputSampleRate()); if( effect == nullptr ) { QMessageBox::warning( 0, "Effect", @@ -554,32 +530,6 @@ void LadspaEffect::pluginDestruction() m_portControls.clear(); } - - - - - -static QMap __buggy_plugins; - -sample_rate_t LadspaEffect::maxSamplerate( const QString & _name ) -{ - if( __buggy_plugins.isEmpty() ) - { - __buggy_plugins["C* AmpVTS"] = 88200; - __buggy_plugins["Chorus2"] = 44100; - __buggy_plugins["Notch Filter"] = 96000; - __buggy_plugins["TAP Reflector"] = 192000; - } - if( __buggy_plugins.contains( _name ) ) - { - return( __buggy_plugins[_name] ); - } - return( Engine::audioEngine()->outputSampleRate() ); -} - - - - extern "C" { diff --git a/plugins/LadspaEffect/LadspaEffect.h b/plugins/LadspaEffect/LadspaEffect.h index 7d2f73baf59..b7aeed1a7e6 100644 --- a/plugins/LadspaEffect/LadspaEffect.h +++ b/plugins/LadspaEffect/LadspaEffect.h @@ -76,11 +76,9 @@ private slots: static sample_rate_t maxSamplerate( const QString & _name ); - QMutex m_pluginMutex; LadspaControls * m_controls; - sample_rate_t m_maxSampleRate; ladspa_key_t m_key; int m_portCount; bool m_inPlaceBroken; diff --git a/plugins/Patman/Patman.cpp b/plugins/Patman/Patman.cpp index 4ecbe1bb83b..7c2800287f0 100644 --- a/plugins/Patman/Patman.cpp +++ b/plugins/Patman/Patman.cpp @@ -154,7 +154,7 @@ void PatmanInstrument::playNote( NotePlayHandle * _n, hdata->sample->frequency(); if (hdata->sample->play(_working_buffer + offset, hdata->state, frames, - play_freq, m_loopedModel.value() ? Sample::Loop::On : Sample::Loop::Off)) + m_loopedModel.value() ? Sample::Loop::On : Sample::Loop::Off, DefaultBaseFreq / play_freq)) { applyRelease( _working_buffer, _n ); } @@ -407,7 +407,7 @@ void PatmanInstrument::selectSample( NotePlayHandle * _n ) auto hdata = new handle_data; hdata->tuned = m_tunedModel.value(); hdata->sample = sample ? sample : std::make_shared(); - hdata->state = new Sample::PlaybackState(_n->hasDetuningInfo()); + hdata->state = new Sample::PlaybackState(AudioResampler::Mode::Linear); _n->m_pluginData = hdata; } diff --git a/plugins/Sf2Player/Sf2Player.cpp b/plugins/Sf2Player/Sf2Player.cpp index 020364151f9..25e7b2f5120 100644 --- a/plugins/Sf2Player/Sf2Player.cpp +++ b/plugins/Sf2Player/Sf2Player.cpp @@ -123,7 +123,7 @@ struct Sf2PluginData Sf2Instrument::Sf2Instrument( InstrumentTrack * _instrument_track ) : Instrument(_instrument_track, &sf2player_plugin_descriptor, nullptr, Flag::IsSingleStreamed), - m_srcState( nullptr ), + m_resampler(AudioResampler::Mode::Linear), m_synth(nullptr), m_font( nullptr ), m_fontId( 0 ), @@ -235,11 +235,6 @@ Sf2Instrument::~Sf2Instrument() freeFont(); delete_fluid_synth( m_synth ); delete_fluid_settings( m_settings ); - if( m_srcState != nullptr ) - { - src_delete( m_srcState ); - } - } @@ -590,7 +585,9 @@ void Sf2Instrument::reloadSynth() // Set & get, returns the true sample rate fluid_settings_setnum( m_settings, (char *) "synth.sample-rate", Engine::audioEngine()->outputSampleRate() ); fluid_settings_getnum( m_settings, (char *) "synth.sample-rate", &tempRate ); + m_internalSampleRate = static_cast( tempRate ); + m_resampler.setRatio(m_internalSampleRate, Engine::audioEngine()->outputSampleRate()); if( m_font ) { @@ -620,31 +617,19 @@ void Sf2Instrument::reloadSynth() } m_synthMutex.lock(); - if( Engine::audioEngine()->currentQualitySettings().interpolation >= - AudioEngine::qualitySettings::Interpolation::SincFastest ) - { - fluid_synth_set_interp_method( m_synth, -1, FLUID_INTERP_7THORDER ); - } - else + + if (m_internalSampleRate != Engine::audioEngine()->outputSampleRate()) { - fluid_synth_set_interp_method( m_synth, -1, FLUID_INTERP_DEFAULT ); + // LMMS supports a sample rate of 192 kHZ, while FluidSynth only supports up to 96 kHZ. + // Because of this, the instrument is resampled using libsamplerate when necessary. + // This uses linear interpolation, so the instrument's interpolation is set to FLUID_INTERP_LINEAR + // to match. A better option might be to make the interpolation option modifiable by the user, as well as only + // supporting only up to 96 kHZ (though that may be a problem if theres a strong need for 192 kHZ). + fluid_synth_set_interp_method(m_synth, -1, FLUID_INTERP_LINEAR); } + m_synthMutex.unlock(); - if( m_internalSampleRate < Engine::audioEngine()->outputSampleRate() ) - { - m_synthMutex.lock(); - if( m_srcState != nullptr ) - { - src_delete( m_srcState ); - } - int error; - m_srcState = src_new( Engine::audioEngine()->currentQualitySettings().libsrcInterpolation(), DEFAULT_CHANNELS, &error ); - if( m_srcState == nullptr || error ) - { - qCritical("error while creating libsamplerate data structure in Sf2Instrument::reloadSynth()"); - } - m_synthMutex.unlock(); - } + updateReverb(); updateChorus(); updateReverbOn(); @@ -884,44 +869,38 @@ void Sf2Instrument::play( SampleFrame* _working_buffer ) void Sf2Instrument::renderFrames( f_cnt_t frames, SampleFrame* buf ) { - m_synthMutex.lock(); + const auto guard = std::lock_guard{m_synthMutex}; + fluid_synth_get_gain(m_synth); // This flushes voice updates as a side effect - if( m_internalSampleRate < Engine::audioEngine()->outputSampleRate() && - m_srcState != nullptr ) + + if (m_internalSampleRate == Engine::audioEngine()->outputSampleRate()) { + fluid_synth_write_float(m_synth, frames, buf, 0, 2, buf, 1, 2); + return; + } + + // TODO: These kind of playback pipelines/graphs are repeated within other parts of the codebase that work with + // audio samples. We should find a way to unify this but the right abstraction is not so clear yet. + while (frames > 0) { - const fpp_t f = frames * m_internalSampleRate / Engine::audioEngine()->outputSampleRate(); -#ifdef __GNUC__ - SampleFrame tmp[f]; -#else - SampleFrame* tmp = new SampleFrame[f]; -#endif - fluid_synth_write_float( m_synth, f, tmp, 0, 2, tmp, 1, 2 ); - - SRC_DATA src_data; - src_data.data_in = (float *)tmp; - src_data.data_out = (float *)buf; - src_data.input_frames = f; - src_data.output_frames = frames; - src_data.src_ratio = (double) frames / f; - src_data.end_of_input = 0; - int error = src_process( m_srcState, &src_data ); -#ifndef __GNUC__ - delete[] tmp; -#endif - if( error ) + if (m_bufferView.empty()) { - qCritical( "Sf2Instrument: error while resampling: %s", src_strerror( error ) ); + fluid_synth_write_float(m_synth, m_buffer.size(), m_buffer.data(), 0, 2, m_buffer.data(), 1, 2); + m_bufferView = m_buffer; } - if (static_cast(src_data.output_frames_gen) < frames) + + const auto [inputFramesUsed, outputFramesGenerated] + = m_resampler.process({&m_bufferView.data()[0][0], 2, m_bufferView.size()}, {&buf[0][0], 2, frames}); + + if (inputFramesUsed == 0 && outputFramesGenerated == 0) { - qCritical("Sf2Instrument: not enough frames: %ld / %zu", src_data.output_frames_gen, frames); + std::fill_n(buf, frames, SampleFrame{}); + break; } + + m_bufferView = m_bufferView.subspan(inputFramesUsed); + buf += outputFramesGenerated; + frames -= outputFramesGenerated; } - else - { - fluid_synth_write_float( m_synth, frames, buf, 0, 2, buf, 1, 2 ); - } - m_synthMutex.unlock(); } diff --git a/plugins/Sf2Player/Sf2Player.h b/plugins/Sf2Player/Sf2Player.h index 16d6aa06850..a618869ae0b 100644 --- a/plugins/Sf2Player/Sf2Player.h +++ b/plugins/Sf2Player/Sf2Player.h @@ -32,9 +32,12 @@ #include #include +#include "AudioEngine.h" +#include "AudioResampler.h" #include "Instrument.h" #include "InstrumentView.h" #include "LcdSpinBox.h" +#include "SampleFrame.h" class QLabel; @@ -103,7 +106,9 @@ public slots: void updateTuning(); private: - SRC_STATE * m_srcState; + AudioResampler m_resampler; + std::array m_buffer; + std::span m_bufferView; fluid_settings_t* m_settings; fluid_synth_t* m_synth; diff --git a/plugins/SlicerT/SlicerT.cpp b/plugins/SlicerT/SlicerT.cpp index cbc543fa634..5b7d2027376 100644 --- a/plugins/SlicerT/SlicerT.cpp +++ b/plugins/SlicerT/SlicerT.cpp @@ -89,7 +89,6 @@ void SlicerT::playNote(NotePlayHandle* handle, SampleFrame* workingBuffer) float speedRatio = static_cast(m_originalBPM.value()) / bpm; if (!m_enableSync.value()) { speedRatio = 1; } speedRatio *= pitchRatio; - speedRatio *= Engine::audioEngine()->outputSampleRate() / static_cast(m_originalSample.sampleRate()); float sliceStart, sliceEnd; if (noteIndex == 0) // full sample at base note @@ -109,35 +108,21 @@ void SlicerT::playNote(NotePlayHandle* handle, SampleFrame* workingBuffer) return; } - if (!handle->m_pluginData) { handle->m_pluginData = new PlaybackState(sliceStart); } - auto playbackState = static_cast(handle->m_pluginData); + const auto startFrame = static_cast(sliceStart * m_originalSample.sampleSize()); + if (!handle->m_pluginData) { handle->m_pluginData = new Sample::PlaybackState(AudioResampler::Mode::Linear, startFrame); } - float noteDone = playbackState->noteDone(); - float noteLeft = sliceEnd - noteDone; + auto playbackState = static_cast(handle->m_pluginData); + const auto endFrame = sliceEnd * m_originalSample.sampleSize(); + const auto framesLeft = endFrame - playbackState->frameIndex(); - if (noteLeft > 0) + if (framesLeft > 0 + && m_originalSample.play(workingBuffer + offset, playbackState, frames, Sample::Loop::Off, speedRatio)) { - int noteFrame = noteDone * m_originalSample.sampleSize(); - - SRC_STATE* resampleState = playbackState->resamplingState(); - SRC_DATA resampleData; - resampleData.data_in = (m_originalSample.data() + noteFrame)->data(); - resampleData.data_out = (workingBuffer + offset)->data(); - resampleData.input_frames = noteLeft * m_originalSample.sampleSize(); - resampleData.output_frames = frames; - resampleData.src_ratio = speedRatio; - - src_process(resampleState, &resampleData); - - float nextNoteDone = noteDone + frames * (1.0f / speedRatio) / m_originalSample.sampleSize(); - playbackState->setNoteDone(nextNoteDone); - // exponential fade out, applyRelease() not used since it extends the note length int fadeOutFrames = m_fadeOutFrames.value() / 1000.0f * Engine::audioEngine()->outputSampleRate(); - int noteFramesLeft = noteLeft * m_originalSample.sampleSize() * speedRatio; for (auto i = std::size_t{0}; i < frames; i++) { - float fadeValue = static_cast(noteFramesLeft - static_cast(i)) / fadeOutFrames; + float fadeValue = static_cast(framesLeft * speedRatio - static_cast(i)) / fadeOutFrames; fadeValue = std::clamp(fadeValue, 0.0f, 1.0f); fadeValue = cosinusInterpolate(0, 1, fadeValue); @@ -145,14 +130,15 @@ void SlicerT::playNote(NotePlayHandle* handle, SampleFrame* workingBuffer) workingBuffer[i + offset][1] *= fadeValue; } - emit isPlaying(noteDone, sliceStart, sliceEnd); + const auto currentNote = static_cast(playbackState->frameIndex()) / m_originalSample.sampleSize(); + emit isPlaying(currentNote, sliceStart, sliceEnd); } else { emit isPlaying(-1, 0, 0); } } void SlicerT::deleteNotePluginData(NotePlayHandle* handle) { - delete static_cast(handle->m_pluginData); + delete static_cast(handle->m_pluginData); emit isPlaying(-1, 0, 0); } diff --git a/plugins/SlicerT/SlicerT.h b/plugins/SlicerT/SlicerT.h index f8bc64fb298..1138e48d41e 100644 --- a/plugins/SlicerT/SlicerT.h +++ b/plugins/SlicerT/SlicerT.h @@ -25,43 +25,15 @@ #ifndef LMMS_SLICERT_H #define LMMS_SLICERT_H -#include - #include "AutomatableModel.h" #include "ComboBoxModel.h" #include "Instrument.h" #include "Note.h" #include "Sample.h" +#include "SlicerTView.h" namespace lmms { -class InstrumentTrack; -namespace gui { -class SlicerTView; -class SlicerTWaveform; -} - -class PlaybackState -{ -public: - explicit PlaybackState(float startFrame) - : m_currentNoteDone(startFrame) - , m_resamplingState(src_new(SRC_LINEAR, DEFAULT_CHANNELS, nullptr)) - { - if (!m_resamplingState) { throw std::runtime_error{"Failed to create sample rate converter object"}; } - } - ~PlaybackState() noexcept { src_delete(m_resamplingState); } - - float noteDone() const { return m_currentNoteDone; } - void setNoteDone(float newNoteDone) { m_currentNoteDone = newNoteDone; } - - SRC_STATE* resamplingState() const { return m_resamplingState; } - -private: - float m_currentNoteDone; - SRC_STATE* m_resamplingState; -}; - class SlicerT : public Instrument { Q_OBJECT diff --git a/plugins/Watsyn/Watsyn.h b/plugins/Watsyn/Watsyn.h index a8e266d21be..8d4eb610b03 100644 --- a/plugins/Watsyn/Watsyn.h +++ b/plugins/Watsyn/Watsyn.h @@ -26,6 +26,7 @@ #ifndef WATSYN_H #define WATSYN_H +#include "AudioResampler.h" #include "Instrument.h" #include "InstrumentView.h" #include "Graph.h" @@ -187,28 +188,23 @@ public slots: } // memcpy utilizing libsamplerate (src) for sinc interpolation - inline void srccpy( float * _dst, float * _src ) + inline void srccpy(float* _dst, float* _src) { - int err; - const int margin = 64; - - // copy to temp array - float tmps [ GRAPHLEN + margin ]; // temp array in stack - float * tmp = &tmps[0]; - - memcpy( tmp, _src, sizeof( float ) * GRAPHLEN ); - memcpy( tmp + GRAPHLEN, _src, sizeof( float ) * margin ); - SRC_STATE * src_state = src_new( SRC_SINC_FASTEST, 1, &err ); - SRC_DATA src_data; - src_data.data_in = tmp; - src_data.input_frames = GRAPHLEN + margin; - src_data.data_out = _dst; - src_data.output_frames = WAVELEN; - src_data.src_ratio = static_cast( WAVERATIO ); - src_data.end_of_input = 0; - err = src_process( src_state, &src_data ); - if( err ) { qDebug( "Watsyn SRC error: %s", src_strerror( err ) ); } - src_delete( src_state ); + auto srcIndex = f_cnt_t{0}; + auto dstIndex = f_cnt_t{0}; + + m_resampler.reset(); + m_resampler.setRatio(WAVERATIO); + + while (dstIndex < WAVELEN) + { + const auto input = InterleavedBufferView{_src + srcIndex, GRAPHLEN - srcIndex}; + const auto output = InterleavedBufferView{_dst + dstIndex, WAVELEN - dstIndex}; + const auto result = m_resampler.process(input, output); + + srcIndex = (srcIndex + result.inputFramesUsed) % GRAPHLEN; + dstIndex += result.outputFramesGenerated; + } } // memcpy utilizing cubic interpolation @@ -242,6 +238,7 @@ public slots: } }*/ + AudioResampler m_resampler = AudioResampler{AudioResampler::Mode::SincFastest, 1}; FloatModel a1_vol; FloatModel a2_vol; diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 99db26d5f9c..2da2fe7837a 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -81,7 +81,6 @@ AudioEngine::AudioEngine( bool renderOnly ) : m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), m_newPlayHandles( PlayHandle::MaxNumber ), - m_qualitySettings(qualitySettings::Interpolation::Linear), m_masterGain( 1.0f ), m_audioDev( nullptr ), m_oldAudioDev( nullptr ), @@ -464,25 +463,6 @@ void AudioEngine::clearInternal() } } - - - -void AudioEngine::changeQuality(const struct qualitySettings & qs) -{ - // don't delete the audio-device - stopProcessing(); - - m_qualitySettings = qs; - - emit sampleRateChanged(); - emit qualitySettingsChanged(); - - startProcessing(); -} - - - - void AudioEngine::doSetAudioDevice( AudioDevice * _dev ) { // TODO: Use shared_ptr here in the future. @@ -503,18 +483,10 @@ void AudioEngine::doSetAudioDevice( AudioDevice * _dev ) } } - - - -void AudioEngine::setAudioDevice(AudioDevice * _dev, - const struct qualitySettings & _qs, - bool _needs_fifo, - bool startNow) +void AudioEngine::setAudioDevice(AudioDevice* _dev, bool _needs_fifo, bool startNow) { stopProcessing(); - m_qualitySettings = _qs; - doSetAudioDevice( _dev ); emit qualitySettingsChanged(); diff --git a/src/core/AudioResampler.cpp b/src/core/AudioResampler.cpp index 8fb7d95a2aa..763574dbba4 100644 --- a/src/core/AudioResampler.cpp +++ b/src/core/AudioResampler.cpp @@ -1,7 +1,7 @@ /* - * AudioResampler.cpp - wrapper for libsamplerate + * AudioResampler.cpp * - * Copyright (c) 2023 saker + * Copyright (c) 2025 saker * * This file is part of LMMS - https://lmms.io * @@ -26,44 +26,77 @@ #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)) +namespace { + +constexpr auto converterType(AudioResampler::Mode mode) -> int { - if (!m_state) + switch (mode) { - 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}; + case AudioResampler::Mode::ZOH: + return SRC_ZERO_ORDER_HOLD; + case AudioResampler::Mode::Linear: + return SRC_LINEAR; + case AudioResampler::Mode::SincFastest: + return SRC_SINC_FASTEST; + case AudioResampler::Mode::SincMedium: + return SRC_SINC_MEDIUM_QUALITY; + case AudioResampler::Mode::SincBest: + return SRC_SINC_BEST_QUALITY; + default: + throw std::invalid_argument{"Invalid interpolation mode"}; } } +} // namespace -AudioResampler::~AudioResampler() +AudioResampler::AudioResampler(Mode mode, ch_cnt_t channels) + : m_state{src_new(converterType(mode), channels, &m_error)} + , m_mode{mode} + , m_channels{channels} { - src_delete(m_state); + if (channels <= 0) { throw std::logic_error{"Invalid channel count"}; } + if (!m_state) { throw std::runtime_error{src_strerror(m_error)}; } } -auto AudioResampler::resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio) - -> ProcessResult +auto AudioResampler::process(InterleavedBufferView input, InterleavedBufferView output) -> Result { + if (input.channels() != m_channels || output.channels() != m_channels) + { + throw std::invalid_argument{"Invalid channel count"}; + } + 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.data_in = input.data(); + data.input_frames = input.frames(); + + data.data_out = output.data(); + data.output_frames = output.frames(); + + data.src_ratio = m_ratio; data.end_of_input = 0; - return {src_process(m_state, &data), data.input_frames_used, data.output_frames_gen}; + + if ((m_error = src_process(static_cast(m_state.get()), &data))) + { + throw std::runtime_error{src_strerror(m_error)}; + } + + return {static_cast(data.input_frames_used), static_cast(data.output_frames_gen)}; +} + +void AudioResampler::reset() +{ + if ((m_error = src_reset(static_cast(m_state.get())))) + { + throw std::runtime_error{src_strerror(m_error)}; + } } -void AudioResampler::setRatio(double ratio) +void AudioResampler::StateDeleter::operator()(void* state) { - src_set_ratio(m_state, ratio); + src_delete(static_cast(state)); } } // namespace lmms diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index 02b6f32b588..5ba7707bb1d 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -52,31 +52,11 @@ Effect::Effect( const Plugin::Descriptor * _desc, { m_wetDryModel.setCenterValue(0); - m_srcState[0] = m_srcState[1] = nullptr; - reinitSRC(); - // Call the virtual method onEnabledChanged so that effects can react to changes, // e.g. by resetting state. connect(&m_enabledModel, &BoolModel::dataChanged, [this] { onEnabledChanged(); }); } - - - -Effect::~Effect() -{ - for (const auto& state : m_srcState) - { - if (state != nullptr) - { - src_delete(state); - } - } -} - - - - void Effect::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_enabledModel.saveSettings( _doc, _this, "on" ); @@ -222,50 +202,4 @@ gui::PluginView * Effect::instantiateView( QWidget * _parent ) return new gui::EffectView( this, _parent ); } - - - -void Effect::reinitSRC() -{ - for (auto& state : m_srcState) - { - if (state != nullptr) - { - src_delete(state); - } - int error; - const int currentInterpolation = Engine::audioEngine()->currentQualitySettings().libsrcInterpolation(); - if((state = src_new(currentInterpolation, DEFAULT_CHANNELS, &error)) == nullptr) - { - qFatal( "Error: src_new() failed in effect.cpp!\n" ); - } - } -} - - - - -void Effect::resample( int _i, const SampleFrame* _src_buf, - sample_rate_t _src_sr, - SampleFrame* _dst_buf, sample_rate_t _dst_sr, - f_cnt_t _frames ) -{ - if( m_srcState[_i] == nullptr ) - { - return; - } - m_srcData[_i].input_frames = _frames; - m_srcData[_i].output_frames = Engine::audioEngine()->framesPerPeriod(); - m_srcData[_i].data_in = const_cast(_src_buf[0].data()); - m_srcData[_i].data_out = _dst_buf[0].data (); - m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr; - m_srcData[_i].end_of_input = 0; - - if (int error = src_process(m_srcState[_i], &m_srcData[_i])) - { - qFatal( "Effect::resample(): error while resampling: %s\n", - src_strerror( error ) ); - } -} - } // namespace lmms diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index c56c34068b4..3435f20c25f 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -75,18 +75,12 @@ const std::array ProjectRenderer::fileEnco } ; - - - -ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySettings, - const OutputSettings & outputSettings, - ExportFileFormat exportFileFormat, - const QString & outputFilename ) : - QThread( Engine::audioEngine() ), - m_fileDev( nullptr ), - m_qualitySettings( qualitySettings ), - m_progress( 0 ), - m_abort( false ) +ProjectRenderer::ProjectRenderer( + const OutputSettings& outputSettings, ExportFileFormat exportFileFormat, const QString& outputFilename) + : QThread(Engine::audioEngine()) + , m_fileDev(nullptr) + , m_progress(0) + , m_abort(false) { AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[static_cast(exportFileFormat)].m_getDevInst; @@ -145,7 +139,7 @@ void ProjectRenderer::startProcessing() { // Have to do audio engine stuff with GUI-thread affinity in order to // make slots connected to sampleRateChanged()-signals being called immediately. - Engine::audioEngine()->setAudioDevice( m_fileDev, m_qualitySettings, false, false ); + Engine::audioEngine()->setAudioDevice(m_fileDev, false, false); start( #ifndef LMMS_BUILD_WIN32 diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index d375b95ee5a..1d8cd3f9781 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -34,17 +34,11 @@ namespace lmms { - RenderManager::RenderManager( - const AudioEngine::qualitySettings & qualitySettings, - const OutputSettings & outputSettings, - ProjectRenderer::ExportFileFormat fmt, - QString outputPath) : - m_qualitySettings(qualitySettings), - m_oldQualitySettings( Engine::audioEngine()->currentQualitySettings() ), - m_outputSettings(outputSettings), - m_format(fmt), - m_outputPath(outputPath) + const OutputSettings& outputSettings, ProjectRenderer::ExportFileFormat fmt, QString outputPath) + : m_outputSettings(outputSettings) + , m_format(fmt) + , m_outputPath(outputPath) { Engine::audioEngine()->storeAudioDevice(); } @@ -52,7 +46,6 @@ RenderManager::RenderManager( RenderManager::~RenderManager() { Engine::audioEngine()->restoreAudioDevice(); // Also deletes audio dev. - Engine::audioEngine()->changeQuality( m_oldQualitySettings ); } void RenderManager::abortProcessing() @@ -141,11 +134,7 @@ void RenderManager::renderProject() void RenderManager::render(QString outputPath) { - m_activeRenderer = std::make_unique( - m_qualitySettings, - m_outputSettings, - m_format, - outputPath); + m_activeRenderer = std::make_unique(m_outputSettings, m_format, outputPath); if( m_activeRenderer->isReady() ) { diff --git a/src/core/Sample.cpp b/src/core/Sample.cpp index 3a1dbfcb256..81bc10eadef 100644 --- a/src/core/Sample.cpp +++ b/src/core/Sample.cpp @@ -1,7 +1,7 @@ /* - * Sample.cpp - State for container-class SampleBuffer + * Sample.cpp * - * Copyright (c) 2023 saker + * Copyright (c) 2025 saker * * This file is part of LMMS - https://lmms.io * @@ -24,10 +24,6 @@ #include "Sample.h" -#include "lmms_math.h" - -#include - namespace lmms { Sample::Sample(const QString& audioFile) @@ -78,7 +74,7 @@ Sample::Sample(const Sample& other) { } -Sample::Sample(Sample&& other) +Sample::Sample(Sample&& other) noexcept : m_buffer(std::move(other.m_buffer)) , m_startFrame(other.startFrame()) , m_endFrame(other.endFrame()) @@ -104,7 +100,7 @@ auto Sample::operator=(const Sample& other) -> Sample& return *this; } -auto Sample::operator=(Sample&& other) -> Sample& +auto Sample::operator=(Sample&& other) noexcept -> Sample& { m_buffer = std::move(other.m_buffer); m_startFrame = other.startFrame(); @@ -118,136 +114,96 @@ 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, Loop loop, double ratio) const { - assert(numFrames > 0); - assert(desiredFrequency > 0); - - 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 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); - 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 sampleRateRatio = static_cast(Engine::audioEngine()->outputSampleRate()) / m_buffer->sampleRate(); + const auto freqRatio = frequency() / DefaultBaseFreq; + state->m_resampler.setRatio(sampleRateRatio * freqRatio * ratio); - const auto outputFrames = static_cast(resampleResult.outputFramesGenerated); - if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, SampleFrame{}); } - - if (!approximatelyEqual(m_amplification, 1.0f)) + // TODO: These kind of playback pipelines/graphs are repeated within other parts of the codebase that work with + // audio samples. We should find a way to unify this but the right abstraction is not so clear yet. + while (numFrames > 0) { - for (auto i = std::size_t{0}; i < numFrames; ++i) + if (state->m_bufferView.empty()) { - dst[i][0] *= m_amplification; - dst[i][1] *= m_amplification; + const auto rendered = render(state->m_buffer.data(), state->m_buffer.size(), state, loop); + state->m_bufferView = {state->m_buffer.data(), rendered}; } - } + + const auto [inputFramesUsed, outputFramesGenerated] = state->m_resampler.process( + {&state->m_bufferView.data()[0][0], 2, state->m_bufferView.size()}, {&dst[0][0], 2, numFrames}); - return true; -} + if (inputFramesUsed == 0 && outputFramesGenerated == 0) + { + std::fill_n(dst, numFrames, SampleFrame{}); + break; + } -auto Sample::sampleDuration() const -> std::chrono::milliseconds -{ - const auto numFrames = endFrame() - startFrame(); - const auto duration = numFrames / static_cast(m_buffer->sampleRate()) * 1000; - return std::chrono::milliseconds{static_cast(duration)}; -} + state->m_bufferView = state->m_bufferView.subspan(inputFramesUsed); + dst += outputFramesGenerated; + numFrames -= outputFramesGenerated; + } -void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame, int loopEndFrame) -{ - setStartFrame(startFrame); - setEndFrame(endFrame); - setLoopStartFrame(loopStartFrame); - setLoopEndFrame(loopEndFrame); + return numFrames < Engine::audioEngine()->framesPerPeriod(); } -void Sample::playRaw(SampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const +f_cnt_t Sample::render(SampleFrame* dst, f_cnt_t size, PlaybackState* state, Loop loop) const { - if (m_buffer->size() < 1) { return; } - - auto index = state->m_frameIndex; - auto backwards = state->m_backwards; - - for (size_t i = 0; i < numFrames; ++i) + for (f_cnt_t frame = 0; frame < size; ++frame) { - switch (loopMode) + switch (loop) { case Loop::Off: - if (index < 0 || index >= m_endFrame) { return; } + if (state->m_frameIndex < 0 || state->m_frameIndex >= m_endFrame) { return frame; } break; case Loop::On: - if (index < m_loopStartFrame && backwards) { index = m_loopEndFrame - 1; } - else if (index >= m_loopEndFrame) { index = m_loopStartFrame; } + if (state->m_frameIndex < m_loopStartFrame && state->m_backwards) + { + state->m_frameIndex = m_loopEndFrame - 1; + } + else if (state->m_frameIndex >= m_loopEndFrame) { state->m_frameIndex = m_loopStartFrame; } break; case Loop::PingPong: - if (index < m_loopStartFrame && backwards) + if (state->m_frameIndex < m_loopStartFrame && state->m_backwards) { - index = m_loopStartFrame; - backwards = false; + state->m_frameIndex = m_loopStartFrame; + state->m_backwards = false; } - else if (index >= m_loopEndFrame) + else if (state->m_frameIndex >= m_loopEndFrame) { - index = m_loopEndFrame - 1; - backwards = true; + state->m_frameIndex = m_loopEndFrame - 1; + state->m_backwards = true; } break; default: break; } - dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index - 1 : index]; - backwards ? --index : ++index; + const auto value + = m_buffer->data()[m_reversed ? m_buffer->size() - state->m_frameIndex - 1 : state->m_frameIndex] + * m_amplification; + dst[frame] = value; + state->m_backwards ? --state->m_frameIndex : ++state->m_frameIndex; } + + return size; } -void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const +auto Sample::sampleDuration() const -> std::chrono::milliseconds { - 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; } + const auto numFrames = endFrame() - startFrame(); + const auto duration = numFrames / static_cast(m_buffer->sampleRate()) * 1000; + return std::chrono::milliseconds{static_cast(duration)}; +} - switch (loopMode) - { - 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; - } - break; - case Loop::PingPong: - if (state->m_frameIndex < m_loopStartFrame && state->m_backwards) - { - state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % loopSize; - state->m_backwards = false; - } - else if (state->m_frameIndex >= m_loopEndFrame) - { - state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % loopSize; - state->m_backwards = true; - } - break; - default: - break; - } +void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame, int loopEndFrame) +{ + setStartFrame(startFrame); + setEndFrame(endFrame); + setLoopStartFrame(loopStartFrame); + setLoopEndFrame(loopEndFrame); } } // namespace lmms diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index eed727fc12e..d5410e7f451 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -114,7 +114,7 @@ void SamplePlayHandle::play( SampleFrame* buffer ) m_volumeModel->value() / DefaultVolume } };*/ // SamplePlayHandle always plays the sample at its original pitch; // it is used only for previews, SampleTracks and the metronome. - if (!m_sample->play(workingBuffer, &m_state, frames, DefaultBaseFreq)) + if (!m_sample->play(workingBuffer, &m_state, frames)) { zeroSampleFrames(workingBuffer, frames); } diff --git a/src/core/main.cpp b/src/core/main.cpp index fb54feeab0b..f0e7a94662e 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -190,12 +190,6 @@ void printHelp() " Default: 160.\n" " -f, --format Specify format of render-output where\n" " Format is either 'wav', 'flac', 'ogg' or 'mp3'.\n" - " -i, --interpolation Specify interpolation method\n" - " Possible values:\n" - " - linear\n" - " - sincfastest (default)\n" - " - sincmedium\n" - " - sincbest\n" " -l, --loop Render as a loop\n" " -m, --mode Stereo mode used for MP3 export\n" " Possible values: s, j, m\n" @@ -375,7 +369,6 @@ int main( int argc, char * * argv ) new QCoreApplication( argc, argv ) : new gui::MainApplication(argc, argv); - AudioEngine::qualitySettings qs(AudioEngine::qualitySettings::Interpolation::Linear); OutputSettings os(44100, 160, OutputSettings::BitDepth::Depth16Bit, OutputSettings::StereoMode::JointStereo); ProjectRenderer::ExportFileFormat eff = ProjectRenderer::ExportFileFormat::Wave; @@ -615,39 +608,6 @@ int main( int argc, char * * argv ) { os.setBitDepth(OutputSettings::BitDepth::Depth32Bit); } - else if( arg == "--interpolation" || arg == "-i" ) - { - ++i; - - if( i == argc ) - { - return usageError( "No interpolation method specified" ); - } - - - const QString ip = QString( argv[i] ); - - if( ip == "linear" ) - { - qs.interpolation = AudioEngine::qualitySettings::Interpolation::Linear; - } - else if( ip == "sincfastest" ) - { - qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincFastest; - } - else if( ip == "sincmedium" ) - { - qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincMedium; - } - else if( ip == "sincbest" ) - { - qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincBest; - } - else - { - return usageError( QString( "Invalid interpolation method %1" ).arg( argv[i] ) ); - } - } else if( arg == "--import" ) { ++i; @@ -776,7 +736,7 @@ int main( int argc, char * * argv ) } // create renderer - auto r = new RenderManager(qs, os, eff, renderOut); + auto r = new RenderManager(os, eff, renderOut); QCoreApplication::instance()->connect( r, SIGNAL(finished()), SLOT(quit())); diff --git a/src/gui/modals/ExportProjectDialog.cpp b/src/gui/modals/ExportProjectDialog.cpp index 01d7c23b0bd..ca71fdb769e 100644 --- a/src/gui/modals/ExportProjectDialog.cpp +++ b/src/gui/modals/ExportProjectDialog.cpp @@ -162,8 +162,6 @@ OutputSettings::StereoMode mapToStereoMode(int index) void ExportProjectDialog::startExport() { - auto qs = AudioEngine::qualitySettings( - static_cast(interpolationCB->currentIndex())); const auto bitrates = std::array{64, 128, 160, 192, 256, 320}; OutputSettings os = OutputSettings(samplerateCB->currentData().toInt(), bitrates[bitrateCB->currentIndex()], @@ -183,7 +181,8 @@ void ExportProjectDialog::startExport() { output_name+=m_fileExtension; } - m_renderManager.reset(new RenderManager( qs, os, m_ft, output_name )); + + m_renderManager.reset(new RenderManager(os, m_ft, output_name)); Engine::getSong()->setExportLoop( exportLoopCB->isChecked() ); Engine::getSong()->setRenderBetweenMarkers( renderMarkersCB->isChecked() ); diff --git a/src/gui/modals/export_project.ui b/src/gui/modals/export_project.ui index 2a923021f40..2e98d08e901 100644 --- a/src/gui/modals/export_project.ui +++ b/src/gui/modals/export_project.ui @@ -331,62 +331,6 @@ - - - - Quality settings - - - - - - Interpolation: - - - - - - - 2 - - - - Zero order hold - - - - - Sinc worst (fastest) - - - - - Sinc medium (recommended) - - - - - Sinc best (slowest) - - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - -