diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h index 691196be62c..ea2c7f02fdb 100644 --- a/include/AudioSampleRecorder.h +++ b/include/AudioSampleRecorder.h @@ -45,7 +45,7 @@ class AudioSampleRecorder : public AudioDevice ~AudioSampleRecorder() override; f_cnt_t framesRecorded() const; - std::shared_ptr createSampleBuffer(); + SampleBuffer createSampleBuffer(); private: void writeBuffer(const SampleFrame* _ab, const fpp_t _frames) override; diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 8c995ac9df5..3a0d4b3e9d6 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -25,8 +25,6 @@ #ifndef LMMS_ENVELOPE_AND_LFO_PARAMETERS_H #define LMMS_ENVELOPE_AND_LFO_PARAMETERS_H -#include - #include "JournallingObject.h" #include "AutomatableModel.h" #include "SampleBuffer.h" @@ -145,7 +143,7 @@ class LMMS_EXPORT EnvelopeAndLfoParameters : public Model, public JournallingObj const TempoSyncKnobModel& getLfoSpeedModel() const { return m_lfoSpeedModel; } const BoolModel& getX100Model() const { return m_x100Model; } const IntModel& getLfoWaveModel() const { return m_lfoWaveModel; } - std::shared_ptr getLfoUserWave() const { return m_userWave; } + const SampleBuffer& getLfoUserWave() const { return m_userWave; } public slots: void updateSampleVars(); @@ -200,7 +198,7 @@ public slots: sample_t * m_lfoShapeData; sample_t m_random; bool m_bad_lfoShapeData; - std::shared_ptr m_userWave = SampleBuffer::emptyBuffer(); + SampleBuffer m_userWave; constexpr static auto NumLfoShapes = static_cast(LfoShape::Count); diff --git a/include/LfoController.h b/include/LfoController.h index 2a44039edd1..ea8b3599d05 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -84,7 +84,7 @@ public slots: private: float m_heldSample; - std::shared_ptr m_userDefSampleBuffer = SampleBuffer::emptyBuffer(); + SampleBuffer m_userDefSampleBuffer; protected slots: void updatePhase(); diff --git a/include/Oscillator.h b/include/Oscillator.h index 84e9264c960..633f1107963 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -98,7 +98,7 @@ class LMMS_EXPORT Oscillator m_useWaveTable = n; } - void setUserWave(std::shared_ptr _wave) + void setUserWave(const SampleBuffer* _wave) { m_userWave = _wave; } @@ -167,8 +167,8 @@ class LMMS_EXPORT Oscillator static sample_t userWaveSample(const SampleBuffer* buffer, const float sample) { - if (buffer == nullptr || buffer->size() == 0) { return 0; } - const auto frames = buffer->size(); + if (buffer == nullptr || buffer->frames() == 0) { return 0; } + const auto frames = buffer->frames(); const auto frame = absFraction(sample) * frames; const auto f1 = static_cast(frame); @@ -250,7 +250,7 @@ class LMMS_EXPORT Oscillator Oscillator * m_subOsc; float m_phaseOffset; float m_phase; - std::shared_ptr m_userWave = SampleBuffer::emptyBuffer(); + const SampleBuffer* m_userWave; std::shared_ptr m_userAntiAliasWaveTable; bool m_useWaveTable; // There are many update*() variants; the modulator flag is stored as a member variable to avoid diff --git a/include/Sample.h b/include/Sample.h index c2450554816..f61051a2b6b 100644 --- a/include/Sample.h +++ b/include/Sample.h @@ -25,8 +25,8 @@ #ifndef LMMS_SAMPLE_H #define LMMS_SAMPLE_H -#include +#include "AudioEngine.h" #include "AudioResampler.h" #include "Note.h" #include "SampleBuffer.h" @@ -68,11 +68,9 @@ class LMMS_EXPORT Sample }; Sample() = default; - - Sample(const SampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate()); Sample(const Sample& other); Sample(Sample&& other) noexcept; - explicit Sample(std::shared_ptr buffer); + explicit Sample(SampleBuffer buffer); auto operator=(const Sample&) -> Sample&; auto operator=(Sample&&) noexcept -> Sample&; @@ -81,14 +79,14 @@ class LMMS_EXPORT Sample double ratio = 1.0) const -> bool; auto sampleDuration() const -> std::chrono::milliseconds; - auto sampleFile() const -> const QString& { return m_buffer->audioFile(); } - auto sampleRate() const -> int { return m_buffer->sampleRate(); } - auto sampleSize() const -> size_t { return m_buffer->size(); } + auto sampleFile() const -> const QString& { return m_buffer.path(); } + auto sampleRate() const -> int { return m_buffer.sampleRate(); } + auto sampleSize() const -> size_t { return m_buffer.frames(); } - auto toBase64() const -> QString { return m_buffer->toBase64(); } + auto toBase64() const -> QString { return m_buffer.toBase64(); } - auto data() const -> const SampleFrame* { return m_buffer->data(); } - auto buffer() const -> std::shared_ptr { return m_buffer; } + auto data() const -> const SampleFrame* { return m_buffer.data(); } + auto buffer() const -> const SampleBuffer& { return m_buffer; } auto startFrame() const -> int { return m_startFrame.load(std::memory_order_relaxed); } auto endFrame() const -> int { return m_endFrame.load(std::memory_order_relaxed); } auto loopStartFrame() const -> int { return m_loopStartFrame.load(std::memory_order_relaxed); } @@ -108,7 +106,7 @@ class LMMS_EXPORT Sample private: f_cnt_t render(SampleFrame* dst, f_cnt_t size, PlaybackState* state, Loop loop) const; - std::shared_ptr m_buffer = SampleBuffer::emptyBuffer(); + SampleBuffer m_buffer; std::atomic m_startFrame = 0; std::atomic m_endFrame = 0; std::atomic m_loopStartFrame = 0; diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 1fe41985a1c..13fec7038a0 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -27,70 +27,87 @@ #include #include +#include #include -#include "AudioEngine.h" -#include "Engine.h" #include "LmmsTypes.h" +#include "SampleFrame.h" #include "lmms_export.h" namespace lmms { +/** + * @class SampleBuffer + * + * @brief A thread-safe, reference-counted container of immutable PCM audio data. + * + * SampleBuffer is designed for playback of audio files or Base64 assets possibly across multiple threads. + * It enforces shared ownership: copying a SampleBuffer increments a ref count for the shared audio data rather than + * deep copying it. + * + * @warning This class is immutable. If you need a buffer for real-time + * effect processing or mixing, use @ref AudioBuffer instead. + * + * @note Populated via static factory methods @ref fromFile() and @ref fromBase64(). + */ class LMMS_EXPORT SampleBuffer { public: - using value_type = SampleFrame; - using reference = SampleFrame&; - using const_reference = const SampleFrame&; - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - using difference_type = std::vector::difference_type; - using size_type = std::vector::size_type; - using reverse_iterator = std::vector::reverse_iterator; - using const_reverse_iterator = std::vector::const_reverse_iterator; - + /** @brief Creates an empty SampleBuffer. */ SampleBuffer() = default; - SampleBuffer(std::vector data, int sampleRate, const QString& audioFile = ""); - SampleBuffer( - const SampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate()); - friend void swap(SampleBuffer& first, SampleBuffer& second) noexcept; - auto toBase64() const -> QString; - - auto audioFile() const -> const QString& { return m_audioFile; } - auto sampleRate() const -> sample_rate_t { return m_sampleRate; } + /** + * @brief Constructs a buffer with explicit data. + * @param data A vector of SampleFrame containing the audio data. + * @param sampleRate The recording rate of the source (e.g., 44100). + * @param path The filesystem path the data originated from. + */ + SampleBuffer(std::vector data, sample_rate_t sampleRate, const QString& path = ""); - auto begin() -> iterator { return m_data.begin(); } - auto end() -> iterator { return m_data.end(); } + /** @returns a const reference to the audio frame at the given frame index. */ + auto operator[](f_cnt_t index) const -> const SampleFrame& { return m_data[index]; } - auto begin() const -> const_iterator { return m_data.begin(); } - auto end() const -> const_iterator { return m_data.end(); } - - auto cbegin() const -> const_iterator { return m_data.cbegin(); } - auto cend() const -> const_iterator { return m_data.cend(); } + /** @brief Serializes the raw PCM data into a Base64 string for project saving. + @todo This function should be removed once Base64 is migrated to audio files on disk. + */ + auto toBase64() const -> QString; - auto rbegin() -> reverse_iterator { return m_data.rbegin(); } - auto rend() -> reverse_iterator { return m_data.rend(); } + /** @returns true if the buffer contains no audio data. */ + auto empty() const -> bool { return m_frames == 0; } - auto rbegin() const -> const_reverse_iterator { return m_data.rbegin(); } - auto rend() const -> const_reverse_iterator { return m_data.rend(); } + /** @returns direct access to the raw audio data. */ + auto data() const -> const SampleFrame* { return m_data.get(); } - auto crbegin() const -> const_reverse_iterator { return m_data.crbegin(); } - auto crend() const -> const_reverse_iterator { return m_data.crend(); } + /** @returns the file path associated with this buffer, if any. */ + auto path() const -> const QString& { return m_path; } - auto data() const -> const SampleFrame* { return m_data.data(); } - auto size() const -> size_type { return m_data.size(); } - auto empty() const -> bool { return m_data.empty(); } + /** @returns the total number of audio frames. */ + auto frames() const -> f_cnt_t { return m_frames; } - static auto emptyBuffer() -> std::shared_ptr; + /** @returns the original sample rate of the data. */ + auto sampleRate() const -> sample_rate_t { return m_sampleRate; } - static std::shared_ptr fromFile(const QString& path); - static std::shared_ptr fromBase64( - const QString& str, int sampleRate = Engine::audioEngine()->outputSampleRate()); + /** + * @brief Factory method to load and decode an audio file into a SampleBuffer. + * @param path The absolute path to the audio file (WAV, FLAC, OGG, etc.). + * @return std::optional containing the buffer on success, or std::nullopt on failure. + */ + static std::optional fromFile(const QString& path); + + /** + * @brief Factory method to reconstruct a buffer from a Base64 string. + * @param str The Base64 encoded audio data. + * @param sampleRate The rate at which the encoded data should be interpreted. + * @return std::optional containing the buffer on success. + * @todo This function should be removed once Base64 is migrated to audio files on disk. + */ + static std::optional fromBase64(const QString& str, sample_rate_t sampleRate); private: - std::vector m_data; - QString m_audioFile; - sample_rate_t m_sampleRate = Engine::audioEngine()->outputSampleRate(); + f_cnt_t m_frames = 0; + sample_rate_t m_sampleRate = 0; + QString m_path; + inline static const auto emptyBuffer = std::make_shared(0); + std::shared_ptr m_data = emptyBuffer; }; } // namespace lmms diff --git a/include/SampleClip.h b/include/SampleClip.h index ea7853386e6..2c12a7b6465 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -77,7 +77,7 @@ class SampleClip : public Clip bool isPlaying() const; void setIsPlaying(bool isPlaying); - void setSampleBuffer(std::shared_ptr sb); + void setSampleBuffer(SampleBuffer buffer); SampleClip* clone() override { diff --git a/include/SampleDecoder.h b/include/SampleDecoder.h index 5013554ddcb..ca29e202f58 100644 --- a/include/SampleDecoder.h +++ b/include/SampleDecoder.h @@ -39,7 +39,7 @@ class SampleDecoder struct Result { std::vector data; - int sampleRate; + sample_rate_t sampleRate; }; struct AudioType diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index b650c67609f..1d9975088f3 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -54,7 +54,7 @@ class SampleRecordHandle : public PlayHandle bool isFromTrack( const Track * _track ) const override; f_cnt_t framesRecorded() const; - std::shared_ptr createSampleBuffer(); + SampleBuffer createSampleBuffer(); private: diff --git a/include/SampleThumbnail.h b/include/SampleThumbnail.h index b0b6d7b459a..7535c688a42 100644 --- a/include/SampleThumbnail.h +++ b/include/SampleThumbnail.h @@ -71,7 +71,7 @@ class LMMS_EXPORT SampleThumbnail }; SampleThumbnail() = default; - SampleThumbnail(const Sample& sample); + SampleThumbnail(const SampleBuffer& buffer); void visualize(VisualizeParameters parameters, QPainter& painter) const; private: @@ -137,7 +137,7 @@ class LMMS_EXPORT SampleThumbnail using ThumbnailCache = std::vector; std::shared_ptr m_thumbnailCache = std::make_shared(); - std::shared_ptr m_buffer = SampleBuffer::emptyBuffer(); + SampleBuffer m_buffer; inline static std::unordered_map, Hash> s_sampleThumbnailCacheMap; }; diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 96f35897988..00cf0e2b4bd 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -224,7 +224,10 @@ void AudioFileProcessor::loadSettings(const QDomElement& elem) } else if (auto sampleData = elem.attribute("sampledata"); !sampleData.isEmpty()) { - m_sample = Sample(SampleBuffer::fromBase64(sampleData)); + if (auto buffer = SampleBuffer::fromBase64(sampleData, Engine::audioEngine()->outputSampleRate())) + { + m_sample = Sample{std::move(buffer.value())}; + } } m_loopModel.loadSettings(elem, "looped"); @@ -318,10 +321,13 @@ void AudioFileProcessor::setAudioFile(const QString& _audio_file, bool _rename) } // else we don't touch the track-name, because the user named it self - m_sample = Sample(SampleBuffer::fromFile(_audio_file)); - loopPointChanged(); - reverseModelChanged(); - emit sampleUpdated(); + if (auto buffer = SampleBuffer::fromFile(_audio_file)) + { + m_sample = Sample(std::move(buffer.value())); + loopPointChanged(); + reverseModelChanged(); + emit sampleUpdated(); + } } diff --git a/plugins/Patman/Patman.cpp b/plugins/Patman/Patman.cpp index 589c3559f15..e818cd6339a 100644 --- a/plugins/Patman/Patman.cpp +++ b/plugins/Patman/Patman.cpp @@ -342,7 +342,7 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( } } - auto data = new SampleFrame[frames]; + auto data = std::vector(frames); for( f_cnt_t frame = 0; frame < frames; ++frame ) { @@ -353,7 +353,7 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( } } - auto psample = std::make_shared(data, frames, sample_rate); + auto psample = std::make_shared(SampleBuffer{std::move(data), sample_rate}); psample->setFrequency(root_freq / 1000.0f); if( modes & MODES_LOOPING ) @@ -363,8 +363,6 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( } m_patchSamples.push_back(psample); - - delete[] data; } fclose( fd ); return( LoadError::OK ); diff --git a/plugins/SlicerT/SlicerT.cpp b/plugins/SlicerT/SlicerT.cpp index a22506f210e..c945c5b0092 100644 --- a/plugins/SlicerT/SlicerT.cpp +++ b/plugins/SlicerT/SlicerT.cpp @@ -306,7 +306,7 @@ std::vector SlicerT::getMidi() void SlicerT::updateFile(QString file) { - if (auto buffer = SampleBuffer::fromFile(file)) { m_originalSample = Sample(std::move(buffer)); } + if (auto buffer = SampleBuffer::fromFile(file)) { m_originalSample = Sample(std::move(buffer.value())); } findBPM(); findSlices(); @@ -351,8 +351,10 @@ void SlicerT::loadSettings(const QDomElement& element) { if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists()) { - auto buffer = SampleBuffer::fromFile(srcFile); - m_originalSample = Sample(std::move(buffer)); + if (auto buffer = SampleBuffer::fromFile(srcFile)) + { + m_originalSample = Sample(std::move(buffer.value())); + } } else { @@ -362,8 +364,10 @@ void SlicerT::loadSettings(const QDomElement& element) } else if (auto sampleData = element.attribute("sampledata"); !sampleData.isEmpty()) { - auto buffer = SampleBuffer::fromBase64(sampleData); - m_originalSample = Sample(std::move(buffer)); + if (auto buffer = SampleBuffer::fromBase64(sampleData, Engine::audioEngine()->outputSampleRate())) + { + m_originalSample = Sample{std::move(buffer.value())}; + } } if (!element.attribute("totalSlices").isEmpty()) diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index 1d28ba1983a..fd5417fcc61 100644 --- a/plugins/TripleOscillator/TripleOscillator.cpp +++ b/plugins/TripleOscillator/TripleOscillator.cpp @@ -92,8 +92,6 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : Oscillator::NumModulationAlgos-1, this, tr( "Modulation type %1" ).arg( _idx+1 ) ), m_useWaveTableModel(true), - - m_sampleBuffer( new SampleBuffer ), m_volumeLeft( 0.0f ), m_volumeRight( 0.0f ), m_detuningLeft( 0.0f ), @@ -139,8 +137,11 @@ void OscillatorObject::oscUserDefWaveDblClick() auto af = gui::FileDialog::openWaveformFile(); if( af != "" ) { - m_sampleBuffer = SampleBuffer::fromFile(af); - m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(m_sampleBuffer.get()); + if (auto buffer = SampleBuffer::fromFile(af)) + { + m_sampleBuffer = std::move(buffer.value()); + m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(&m_sampleBuffer); + } // TODO: //m_usrWaveBtn->setToolTip(m_sampleBuffer->audioFile()); } @@ -251,8 +252,7 @@ void TripleOscillator::saveSettings( QDomDocument & _doc, QDomElement & _this ) "modalgo" + QString::number( i+1 ) ); m_osc[i]->m_useWaveTableModel.saveSettings( _doc, _this, "useWaveTable" + QString::number (i+1 ) ); - _this.setAttribute( "userwavefile" + is, - m_osc[i]->m_sampleBuffer->audioFile() ); + _this.setAttribute("userwavefile" + is, m_osc[i]->m_sampleBuffer.path()); } } @@ -284,8 +284,11 @@ void TripleOscillator::loadSettings( const QDomElement & _this ) { if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists()) { - m_osc[i]->m_sampleBuffer = SampleBuffer::fromFile(userWaveFile); - m_osc[i]->m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(m_osc[i]->m_sampleBuffer.get()); + if (auto buffer = SampleBuffer::fromFile(userWaveFile)) + { + m_osc[i]->m_sampleBuffer = std::move(buffer.value()); + m_osc[i]->m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(&m_osc[i]->m_sampleBuffer); + } } else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); } } @@ -356,8 +359,8 @@ void TripleOscillator::playNote( NotePlayHandle * _n, oscs_r[i]->setUseWaveTable(m_osc[i]->m_useWaveTable); } - oscs_l[i]->setUserWave( m_osc[i]->m_sampleBuffer ); - oscs_r[i]->setUserWave( m_osc[i]->m_sampleBuffer ); + oscs_l[i]->setUserWave(&m_osc[i]->m_sampleBuffer); + oscs_r[i]->setUserWave(&m_osc[i]->m_sampleBuffer); oscs_l[i]->setUserAntiAliasWaveTable(m_osc[i]->m_userAntiAliasWaveTable); oscs_r[i]->setUserAntiAliasWaveTable(m_osc[i]->m_userAntiAliasWaveTable); } diff --git a/plugins/TripleOscillator/TripleOscillator.h b/plugins/TripleOscillator/TripleOscillator.h index e465d2a5510..4e2647eb99e 100644 --- a/plugins/TripleOscillator/TripleOscillator.h +++ b/plugins/TripleOscillator/TripleOscillator.h @@ -70,7 +70,7 @@ class OscillatorObject : public Model IntModel m_waveShapeModel; IntModel m_modulationAlgoModel; BoolModel m_useWaveTableModel; - std::shared_ptr m_sampleBuffer = SampleBuffer::emptyBuffer(); + SampleBuffer m_sampleBuffer; std::shared_ptr m_userAntiAliasWaveTable; float m_volumeLeft; diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 158c8f531f8..9fba9f41521 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -224,7 +224,7 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset ) shape_sample = Oscillator::sawSample( phase ); break; case LfoShape::UserDefinedWave: - shape_sample = Oscillator::userWaveSample(m_userWave.get(), phase); + shape_sample = Oscillator::userWaveSample(&m_userWave, phase); break; case LfoShape::RandomWave: if( frame == 0 ) @@ -352,7 +352,7 @@ void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc, m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" ); m_x100Model.saveSettings( _doc, _parent, "x100" ); m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" ); - _parent.setAttribute("userwavefile", m_userWave->audioFile()); + _parent.setAttribute("userwavefile", m_userWave.path()); } @@ -388,7 +388,10 @@ void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this ) { if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists()) { - m_userWave = SampleBuffer::fromFile(_this.attribute("userwavefile")); + if (auto buffer = SampleBuffer::fromFile(_this.attribute("userwavefile"))) + { + m_userWave = std::move(buffer.value()); + } } else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); } } diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 097921e78d4..a84e95de77c 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -49,8 +49,7 @@ LfoController::LfoController( Model * _parent ) : m_duration( 1000 ), m_phaseOffset( 0 ), m_currentPhase( 0 ), - m_sampleFunction( &Oscillator::sinSample ), - m_userDefSampleBuffer(std::make_shared()) + m_sampleFunction( &Oscillator::sinSample ) { setSampleExact( true ); connect( &m_waveModel, SIGNAL(dataChanged()), @@ -123,7 +122,7 @@ void LfoController::updateValueBuffer() } case Oscillator::WaveShape::UserDefined: { - currentSample = Oscillator::userWaveSample(m_userDefSampleBuffer.get(), phase); + currentSample = Oscillator::userWaveSample(&m_userDefSampleBuffer, phase); break; } default: @@ -223,7 +222,7 @@ void LfoController::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_phaseModel.saveSettings( _doc, _this, "phase" ); m_waveModel.saveSettings( _doc, _this, "wave" ); m_multiplierModel.saveSettings( _doc, _this, "multiplier" ); - _this.setAttribute("userwavefile", m_userDefSampleBuffer->audioFile()); + _this.setAttribute("userwavefile", m_userDefSampleBuffer.path()); } @@ -243,7 +242,10 @@ void LfoController::loadSettings( const QDomElement & _this ) { if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists()) { - m_userDefSampleBuffer = SampleBuffer::fromFile(_this.attribute("userwavefile")); + if (auto buffer = SampleBuffer::fromFile(_this.attribute("userwavefile"))) + { + m_userDefSampleBuffer = std::move(buffer.value()); + } } else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); } } diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index 1df8ccb8c7b..4bc717ff15b 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -823,7 +823,7 @@ inline sample_t Oscillator::getSample( } else { - return userWaveSample(m_userWave.get(), _sample); + return userWaveSample(m_userWave, _sample); } } diff --git a/src/core/Sample.cpp b/src/core/Sample.cpp index b535aa1875d..ef9c6ba2ceb 100644 --- a/src/core/Sample.cpp +++ b/src/core/Sample.cpp @@ -23,24 +23,16 @@ */ #include "Sample.h" +#include "Engine.h" namespace lmms { -Sample::Sample(const SampleFrame* data, size_t numFrames, int sampleRate) - : m_buffer(std::make_shared(data, numFrames, sampleRate)) +Sample::Sample(SampleBuffer buffer) + : m_buffer(std::move(buffer)) , m_startFrame(0) - , m_endFrame(m_buffer->size()) + , m_endFrame(m_buffer.frames()) , m_loopStartFrame(0) - , m_loopEndFrame(m_buffer->size()) -{ -} - -Sample::Sample(std::shared_ptr buffer) - : m_buffer(buffer) - , m_startFrame(0) - , m_endFrame(m_buffer->size()) - , m_loopStartFrame(0) - , m_loopEndFrame(m_buffer->size()) + , m_loopEndFrame(m_buffer.frames()) { } @@ -100,7 +92,7 @@ bool Sample::play(SampleFrame* dst, PlaybackState* state, size_t numFrames, Loop { state->m_frameIndex = std::max(m_startFrame, state->m_frameIndex); - const auto sampleRateRatio = static_cast(Engine::audioEngine()->outputSampleRate()) / m_buffer->sampleRate(); + const auto sampleRateRatio = static_cast(Engine::audioEngine()->outputSampleRate()) / m_buffer.sampleRate(); const auto freqRatio = frequency() / DefaultBaseFreq; state->m_resampler.setRatio(sampleRateRatio * freqRatio * ratio); @@ -163,10 +155,8 @@ f_cnt_t Sample::render(SampleFrame* dst, f_cnt_t size, PlaybackState* state, Loo break; } - const auto value - = m_buffer->data()[m_reversed ? m_buffer->size() - state->m_frameIndex - 1 : state->m_frameIndex] - * m_amplification; - dst[frame] = value; + const auto index = m_reversed ? m_buffer.frames() - state->m_frameIndex - 1 : state->m_frameIndex; + dst[frame] = m_buffer[index] * m_amplification; state->m_backwards ? --state->m_frameIndex : ++state->m_frameIndex; } @@ -176,7 +166,7 @@ f_cnt_t Sample::render(SampleFrame* dst, f_cnt_t size, PlaybackState* state, Loo auto Sample::sampleDuration() const -> std::chrono::milliseconds { const auto numFrames = endFrame() - startFrame(); - const auto duration = numFrames / static_cast(m_buffer->sampleRate()) * 1000; + const auto duration = numFrames / static_cast(m_buffer.sampleRate()) * 1000; return std::chrono::milliseconds{static_cast(duration)}; } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index e04a63205be..1bbd890e796 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -34,45 +34,28 @@ namespace lmms { -SampleBuffer::SampleBuffer(const SampleFrame* data, size_t numFrames, int sampleRate) - : m_data(data, data + numFrames) +// Use aliasing constructor for std::shared_ptr to share ownership of a newly allocated vector without double +// indirection when accessing samples (in the case of a vector in a shared pointer) +SampleBuffer::SampleBuffer(std::vector data, sample_rate_t sampleRate, const QString& path) + : m_frames(data.size()) , m_sampleRate(sampleRate) + , m_path(path) + , m_data(std::shared_ptr(data.data(), [v = std::move(data)](auto) {})) { } -SampleBuffer::SampleBuffer(std::vector data, int sampleRate, const QString& audioFile) - : m_data(std::move(data)) - , m_audioFile(audioFile) - , m_sampleRate(sampleRate) -{ -} - -void swap(SampleBuffer& first, SampleBuffer& second) noexcept -{ - using std::swap; - swap(first.m_data, second.m_data); - swap(first.m_audioFile, second.m_audioFile); - swap(first.m_sampleRate, second.m_sampleRate); -} - QString SampleBuffer::toBase64() const { // TODO: Replace with non-Qt equivalent - const auto data = reinterpret_cast(m_data.data()); - const auto size = static_cast(m_data.size() * sizeof(SampleFrame)); - const auto byteArray = QByteArray{data, size}; + const auto data = reinterpret_cast(m_data.get()); + const auto size = static_cast(m_frames * sizeof(SampleFrame)); + const auto byteArray = QByteArray::fromRawData(data, size); return byteArray.toBase64(); } -auto SampleBuffer::emptyBuffer() -> std::shared_ptr -{ - static auto s_buffer = std::make_shared(); - return s_buffer; -} - -std::shared_ptr SampleBuffer::fromFile(const QString& filePath) +std::optional SampleBuffer::fromFile(const QString& filePath) { - if (filePath.isEmpty()) { return SampleBuffer::emptyBuffer(); } + if (filePath.isEmpty()) { return std::nullopt; } const auto absolutePath = PathUtil::toAbsolute(filePath); const auto storedPath = PathUtil::toShortestRelative(filePath); @@ -95,16 +78,16 @@ std::shared_ptr SampleBuffer::fromFile(const QString& filePa .arg(absolutePath); } - return SampleBuffer::emptyBuffer(); + return std::nullopt; } auto& [data, sampleRate] = *result; - return std::make_shared(std::move(data), sampleRate, storedPath); + return SampleBuffer{std::move(data), sampleRate, storedPath}; } -std::shared_ptr SampleBuffer::fromBase64(const QString& str, int sampleRate) +std::optional SampleBuffer::fromBase64(const QString& str, sample_rate_t sampleRate) { - if (str.isEmpty()) { return SampleBuffer::emptyBuffer(); } + if (str.isEmpty()) { return std::nullopt; } const auto bytes = QByteArray::fromBase64(str.toUtf8()); @@ -122,12 +105,12 @@ std::shared_ptr SampleBuffer::fromBase64(const QString& str, qWarning() << QObject::tr("Failed to load Base64 sample, invalid size"); } - return SampleBuffer::emptyBuffer(); + return std::nullopt; } auto data = std::vector(bytes.size() / sizeof(SampleFrame)); std::memcpy(reinterpret_cast(data.data()), bytes, bytes.size()); - return std::make_shared(std::move(data), sampleRate); + return SampleBuffer{std::move(data), sampleRate}; } } // namespace lmms diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index e279f76236e..b3b6f0e4963 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -138,11 +138,11 @@ bool SampleClip::hasSampleFileLoaded(const QString & filename) const return m_sample.sampleFile() == filename; } -void SampleClip::setSampleBuffer(std::shared_ptr sb) +void SampleClip::setSampleBuffer(SampleBuffer buffer) { { const auto guard = Engine::audioEngine()->requestChangesGuard(); - m_sample = Sample(std::move(sb)); + m_sample = Sample(std::move(buffer)); } updateLength(); @@ -157,8 +157,11 @@ void SampleClip::setSampleFile(const QString& sf) setStartTimeOffset(0); if (!sf.isEmpty()) { - m_sample = Sample(SampleBuffer::fromFile(sf)); - updateLength(); + if (auto buffer = SampleBuffer::fromFile(sf)) + { + m_sample = Sample(std::move(buffer.value())); + updateLength(); + } } else { @@ -320,8 +323,10 @@ void SampleClip::loadSettings( const QDomElement & _this ) auto sampleRate = _this.hasAttribute("sample_rate") ? _this.attribute("sample_rate").toInt() : Engine::audioEngine()->outputSampleRate(); - auto buffer = SampleBuffer::fromBase64(_this.attribute("data"), sampleRate); - m_sample = Sample(std::move(buffer)); + if (auto buffer = SampleBuffer::fromBase64(_this.attribute("data"), sampleRate)) + { + m_sample = Sample{std::move(buffer.value())}; + } } changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); diff --git a/src/core/SampleDecoder.cpp b/src/core/SampleDecoder.cpp index 7153633389d..42e908b23b8 100644 --- a/src/core/SampleDecoder.cpp +++ b/src/core/SampleDecoder.cpp @@ -91,7 +91,7 @@ auto decodeSampleSF(const QString& audioFile) -> std::optional(sfInfo.samplerate)}; + return SampleDecoder::Result{std::move(result), static_cast(sfInfo.samplerate)}; } auto decodeSampleDS(const QString& audioFile) -> std::optional @@ -109,7 +109,7 @@ auto decodeSampleDS(const QString& audioFile) -> std::optional(frames); src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS); - return SampleDecoder::Result{std::move(result), static_cast(engineRate)}; + return SampleDecoder::Result{std::move(result), engineRate}; } #ifdef LMMS_HAVE_OGGVORBIS @@ -180,7 +180,7 @@ auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional(sampleRate)}; + return SampleDecoder::Result{std::move(result), static_cast(sampleRate)}; } #endif // LMMS_HAVE_OGGVORBIS } // namespace diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index e4dc69b0a39..3bd47a08186 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -52,11 +52,8 @@ SamplePlayHandle::SamplePlayHandle(Sample* sample, bool ownAudioBusHandle) : } } - - - -SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : - SamplePlayHandle(new Sample(SampleBuffer::fromFile(sampleFile)), true) +SamplePlayHandle::SamplePlayHandle(const QString& sampleFile) + : SamplePlayHandle(new Sample(SampleBuffer::fromFile(sampleFile).value_or(SampleBuffer{})), true) { } diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index f62de7885e5..47b1b2fd241 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -105,7 +105,7 @@ f_cnt_t SampleRecordHandle::framesRecorded() const -std::shared_ptr SampleRecordHandle::createSampleBuffer() +SampleBuffer SampleRecordHandle::createSampleBuffer() { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in @@ -120,7 +120,7 @@ std::shared_ptr SampleRecordHandle::createSampleBuffer() } // create according sample-buffer out of big buffer - return std::make_shared(std::move(bigBuffer), Engine::audioEngine()->inputSampleRate()); + return SampleBuffer(std::move(bigBuffer), Engine::audioEngine()->inputSampleRate()); } diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp index 2e577d1c670..5c211dca7fa 100644 --- a/src/core/audio/AudioSampleRecorder.cpp +++ b/src/core/audio/AudioSampleRecorder.cpp @@ -66,7 +66,7 @@ f_cnt_t AudioSampleRecorder::framesRecorded() const return frames; } -std::shared_ptr AudioSampleRecorder::createSampleBuffer() +SampleBuffer AudioSampleRecorder::createSampleBuffer() { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in @@ -81,7 +81,7 @@ std::shared_ptr AudioSampleRecorder::createSampleBuffer() } // create according sample-buffer out of big buffer - return std::make_shared(std::move(bigBuffer), sampleRate()); + return SampleBuffer(std::move(bigBuffer), sampleRate()); } void AudioSampleRecorder::writeBuffer(const SampleFrame* _ab, const fpp_t _frames) diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index 9106c7215d4..12fb522437a 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -768,7 +768,7 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file) qApp->processEvents(QEventLoop::ExcludeUserInputEvents); if (auto buffer = SampleBuffer::fromFile(fileName)) { - auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer)}); + auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer.value())}); s->setDoneMayReturnTrue(false); newPPH = s; } diff --git a/src/gui/LfoControllerDialog.cpp b/src/gui/LfoControllerDialog.cpp index c16875fc34f..ab659b3074f 100644 --- a/src/gui/LfoControllerDialog.cpp +++ b/src/gui/LfoControllerDialog.cpp @@ -212,10 +212,12 @@ void LfoControllerDialog::askUserDefWave() if (fileName.isEmpty()) { return; } auto lfoModel = dynamic_cast(model()); - auto& buffer = lfoModel->m_userDefSampleBuffer; - buffer = SampleBuffer::fromFile(fileName); + if (auto buffer = SampleBuffer::fromFile(fileName)) + { + lfoModel->m_userDefSampleBuffer = std::move(buffer.value()); + } - m_userWaveBtn->setToolTip(buffer->audioFile()); + m_userWaveBtn->setToolTip(fileName); } diff --git a/src/gui/SampleThumbnail.cpp b/src/gui/SampleThumbnail.cpp index 76617030a36..4c975dd07ee 100644 --- a/src/gui/SampleThumbnail.cpp +++ b/src/gui/SampleThumbnail.cpp @@ -28,8 +28,6 @@ #include #include -#include "Sample.h" - namespace { constexpr auto MaxSampleThumbnailCacheSize = 32; constexpr auto AggregationPerZoomStep = 10; @@ -71,10 +69,10 @@ SampleThumbnail::Thumbnail SampleThumbnail::Thumbnail::zoomOut(float factor) con return Thumbnail{std::move(peaks), m_samplesPerPeak * factor}; } -SampleThumbnail::SampleThumbnail(const Sample& sample) - : m_buffer(sample.buffer()) +SampleThumbnail::SampleThumbnail(const SampleBuffer& buffer) + : m_buffer(buffer) { - auto entry = SampleThumbnailEntry{sample.sampleFile(), QFileInfo{sample.sampleFile()}.lastModified()}; + auto entry = SampleThumbnailEntry{buffer.path(), QFileInfo{buffer.path()}.lastModified()}; if (!entry.filePath.isEmpty()) { const auto it = s_sampleThumbnailCacheMap.find(entry); @@ -94,9 +92,8 @@ SampleThumbnail::SampleThumbnail(const Sample& sample) s_sampleThumbnailCacheMap[std::move(entry)] = m_thumbnailCache; } - const auto flatBuffer = m_buffer->data()->data(); - const auto flatBufferSize = m_buffer->size() * DEFAULT_CHANNELS; - m_thumbnailCache->emplace_back(flatBuffer, flatBufferSize, flatBufferSize / AggregationPerZoomStep); + const auto numSamples = m_buffer.frames() * 2; + m_thumbnailCache->emplace_back(&m_buffer[0][0], numSamples, numSamples / AggregationPerZoomStep); while (m_thumbnailCache->back().width() >= AggregationPerZoomStep) { @@ -121,7 +118,7 @@ void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painte [&](const auto& thumbnail) { return thumbnail.width() >= targetThumbnailWidth; }); const auto useOriginalBuffer = finerThumbnail == m_thumbnailCache->rend(); - const auto drawOriginalBuffer = static_cast(targetThumbnailWidth) == m_buffer->size(); + const auto drawOriginalBuffer = static_cast(targetThumbnailWidth) == m_buffer.frames(); painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); @@ -132,7 +129,7 @@ void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painte const auto thumbnailEnd = parameters.reversed ? targetThumbnailWidth - thumbnailEndForward : thumbnailEndForward; const auto advanceThumbnailBy = parameters.reversed ? -1 : 1; - const auto finerThumbnailWidth = useOriginalBuffer ? m_buffer->size() : finerThumbnail->width(); + const auto finerThumbnailWidth = useOriginalBuffer ? m_buffer.frames() : finerThumbnail->width(); const auto finerThumbnailScaleFactor = static_cast(finerThumbnailWidth) / targetThumbnailWidth; const auto yScale = renderRect.height() / 2 * parameters.amplification; @@ -141,7 +138,7 @@ void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painte { if (useOriginalBuffer && drawOriginalBuffer) { - const auto value = m_buffer->data()->data()[i]; + const auto value = m_buffer.data()->data()[i]; painter.drawPoint(x, renderRect.center().y() - value * yScale); continue; } @@ -155,7 +152,7 @@ void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painte if (useOriginalBuffer) { - const auto flatBuffer = m_buffer->data()->data(); + const auto flatBuffer = m_buffer.data()->data(); const auto [min, max] = std::minmax_element(flatBuffer + beginIndex, flatBuffer + endIndex); minPeak = *min; maxPeak = *max; diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index f9aaa646839..a4de4dbb455 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -129,9 +129,12 @@ void SampleClipView::dropEvent( QDropEvent * _de ) } else if( StringPairDrag::decodeKey( _de ) == "sampledata" ) { - m_clip->setSampleBuffer(SampleBuffer::fromBase64(StringPairDrag::decodeValue(_de))); - m_clip->updateLength(); - update(); + if (auto buffer = SampleBuffer::fromBase64(StringPairDrag::decodeValue(_de), Engine::audioEngine()->outputSampleRate())) + { + m_clip->setSampleBuffer(std::move(buffer.value())); + m_clip->updateLength(); + update(); + } _de->accept(); } else @@ -194,10 +197,9 @@ void SampleClipView::mouseDoubleClickEvent( QMouseEvent * ) if (!m_clip->hasSampleFileLoaded(selectedAudioFile)) { - auto sampleBuffer = SampleBuffer::fromFile(selectedAudioFile); - if (sampleBuffer != SampleBuffer::emptyBuffer()) + if (auto buffer = SampleBuffer::fromFile(selectedAudioFile)) { - m_clip->setSampleBuffer(sampleBuffer); + m_clip->setSampleBuffer(std::move(buffer.value())); } } m_clip->updateLength(); diff --git a/src/gui/instrument/EnvelopeAndLfoView.cpp b/src/gui/instrument/EnvelopeAndLfoView.cpp index 6fd0e5a3a71..86e0bbca4ce 100644 --- a/src/gui/instrument/EnvelopeAndLfoView.cpp +++ b/src/gui/instrument/EnvelopeAndLfoView.cpp @@ -241,11 +241,14 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) QString value = StringPairDrag::decodeValue( _de ); if( type == "samplefile" ) { - m_params->m_userWave = SampleBuffer::fromFile(value); - m_userLfoBtn->model()->setValue( true ); - m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); + if (auto buffer = SampleBuffer::fromFile(value)) + { + m_params->m_userWave = std::move(buffer.value()); + m_userLfoBtn->model()->setValue( true ); + m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); + update(); + } _de->accept(); - update(); } else if( type == QString( "clip_%1" ).arg( static_cast(Track::Type::Sample) ) ) { @@ -253,11 +256,14 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) auto file = dataFile.content(). firstChildElement().firstChildElement(). firstChildElement().attribute("src"); - m_params->m_userWave = SampleBuffer::fromFile(file); - m_userLfoBtn->model()->setValue( true ); - m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); + if (auto buffer = SampleBuffer::fromFile(file)) + { + m_params->m_userWave = std::move(buffer.value()); + m_userLfoBtn->model()->setValue( true ); + m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); + update(); + } _de->accept(); - update(); } } @@ -269,7 +275,7 @@ void EnvelopeAndLfoView::lfoUserWaveChanged() if( static_cast(m_params->m_lfoWaveModel.value()) == EnvelopeAndLfoParameters::LfoShape::UserDefinedWave ) { - if (m_params->m_userWave->size() <= 1) + if (m_params->m_userWave.empty()) { TextFloat::displayMessage( tr( "Hint" ), tr( "Drag and drop a sample into this window." ), diff --git a/src/gui/instrument/LfoGraph.cpp b/src/gui/instrument/LfoGraph.cpp index fe8e01a7f33..2ab4c87e566 100644 --- a/src/gui/instrument/LfoGraph.cpp +++ b/src/gui/instrument/LfoGraph.cpp @@ -77,7 +77,7 @@ void LfoGraph::paintEvent(QPaintEvent*) const f_cnt_t oscillationFrames = params->getLfoOscillationFrames(); const bool x100 = params->getX100Model().value(); const int lfoWaveModel = params->getLfoWaveModel().value(); - const auto * userWave = params->getLfoUserWave().get(); + const auto& userWave = params->getLfoUserWave(); const int margin = 3; const int lfoGraphWidth = width() - margin; // subtract margin @@ -128,7 +128,7 @@ void LfoGraph::paintEvent(QPaintEvent*) value = m_randomGraph; break; case EnvelopeAndLfoParameters::LfoShape::UserDefinedWave: - value = Oscillator::userWaveSample(userWave, phase); + value = Oscillator::userWaveSample(&userWave, phase); break; } diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index 35920733823..4e05780e4e0 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -588,10 +588,12 @@ QString graphModel::setWaveToUser() QString fileName = gui::FileDialog::openWaveformFile(); if( fileName.isEmpty() == false ) { - auto sampleBuffer = SampleBuffer::fromFile(fileName); - for( int i = 0; i < length(); i++ ) + if (auto buffer = SampleBuffer::fromFile(fileName)) { - m_samples[i] = Oscillator::userWaveSample(sampleBuffer.get(), i / static_cast(length())); + for (int i = 0; i < length(); i++) + { + m_samples[i] = Oscillator::userWaveSample(&buffer.value(), i / static_cast(length())); + } } }