diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h index 3077e610562..115f43b5e66 100644 --- a/include/AudioSampleRecorder.h +++ b/include/AudioSampleRecorder.h @@ -34,7 +34,7 @@ namespace lmms { -class SampleBuffer2; +class SampleBuffer; class AudioSampleRecorder : public AudioDevice @@ -44,7 +44,7 @@ class AudioSampleRecorder : public AudioDevice ~AudioSampleRecorder() override; f_cnt_t framesRecorded() const; - void createSampleBuffer(SampleBuffer2** sampleBuffer); + void createSampleBuffer(SampleBuffer** sampleBuffer); private: diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 7abc3910e96..9f1769d67ce 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -167,7 +167,7 @@ public slots: sample_t * m_lfoShapeData; sample_t m_random; bool m_bad_lfoShapeData; - SampleBuffer m_userWave; + std::shared_ptr m_userWave; enum class LfoShape { diff --git a/include/LfoController.h b/include/LfoController.h index adf78abeb46..2e2c45dac41 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -86,7 +86,7 @@ public slots: sample_t (*m_sampleFunction)( const float ); private: - std::shared_ptr m_userDefSampleBuffer; + std::shared_ptr m_userDefSampleBuffer; protected slots: void updatePhase(); diff --git a/include/Oscillator.h b/include/Oscillator.h index 74464d6bc8e..dc58b123423 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -36,7 +36,7 @@ #include "lmmsconfig.h" #include "AudioEngine.h" #include "OscillatorConstants.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" namespace lmms { @@ -91,14 +91,14 @@ class LMMS_EXPORT Oscillator static void waveTableInit(); static void destroyFFTPlans(); - static std::unique_ptr generateAntiAliasUserWaveTable(const SampleBuffer2 *sampleBuffer); + static std::unique_ptr generateAntiAliasUserWaveTable(const SampleBuffer *sampleBuffer); inline void setUseWaveTable(bool n) { m_useWaveTable = n; } - inline void setUserWave(std::shared_ptr _wave) + inline void setUserWave(std::shared_ptr _wave) { m_userWave = _wave; } @@ -164,7 +164,7 @@ class LMMS_EXPORT Oscillator return 1.0f - fast_rand() * 2.0f / FAST_RAND_MAX; } - static inline sample_t userWaveSample(const SampleBuffer2* buffer, const float sample) + static inline sample_t userWaveSample(const SampleBuffer* buffer, const float sample) { if (buffer == nullptr || buffer->size() == 0) { return 0; } const auto frames = buffer->size(); @@ -256,7 +256,7 @@ class LMMS_EXPORT Oscillator Oscillator * m_subOsc; float m_phaseOffset; float m_phase; - std::shared_ptr m_userWave; + std::shared_ptr 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 09ae41f6abe..d45ca371893 100644 --- a/include/Sample.h +++ b/include/Sample.h @@ -1,5 +1,5 @@ /* - * Sample.h - State for container-class SampleBuffer2 + * Sample.h - State for container-class SampleBuffer * * Copyright (c) 2023 saker * @@ -29,7 +29,7 @@ #include #include "Note.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" #include "lmms_export.h" #ifdef __MINGW32__ @@ -88,7 +88,7 @@ class LMMS_EXPORT Sample Sample(const QString& audioFile); Sample(const QByteArray& base64, int sampleRate = Engine::audioEngine()->processingSampleRate()); Sample(const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate()); - Sample(std::shared_ptr buffer); + Sample(std::shared_ptr buffer); Sample(const Sample& other); Sample(Sample&& other) noexcept; @@ -107,7 +107,7 @@ class LMMS_EXPORT Sample auto toBase64() const -> QString; - auto buffer() const -> std::shared_ptr; + auto buffer() const -> std::shared_ptr; auto startFrame() const -> int; auto endFrame() const -> int; auto loopStartFrame() const -> int; @@ -144,7 +144,7 @@ class LMMS_EXPORT Sample auto amplifySampleRange(sampleFrame* src, int numFrames) const -> void; private: - std::shared_ptr m_buffer = std::make_shared(); + std::shared_ptr m_buffer = std::make_shared(); int m_startFrame = 0; int m_endFrame = 0; int m_loopStartFrame = 0; diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 3d1013baa01..62c3f32e07a 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -25,333 +25,58 @@ #ifndef LMMS_SAMPLE_BUFFER_H #define LMMS_SAMPLE_BUFFER_H -#include -#include -#include - +#include +#include +#include #include +#include -#include "lmms_export.h" -#include "interpolation.h" +#include "AudioEngine.h" +#include "Engine.h" #include "lmms_basics.h" -#include "lmms_math.h" -#include "shared_object.h" -#include "OscillatorConstants.h" -#include "MemoryManager.h" - - -class QPainter; -class QRect; - -namespace lmms -{ - -// 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 -const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; +#include "lmms_export.h" -class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject +namespace lmms { +class LMMS_EXPORT SampleBuffer { - Q_OBJECT - MM_OPERATORS public: - enum class LoopMode { - Off = 0, - On, - PingPong - }; - class LMMS_EXPORT handleState - { - MM_OPERATORS - public: - handleState(bool varyingPitch = false, int interpolationMode = SRC_LINEAR); - virtual ~handleState(); - - const f_cnt_t frameIndex() const - { - return m_frameIndex; - } - - void setFrameIndex(f_cnt_t index) - { - m_frameIndex = index; - } - - bool isBackwards() const - { - return m_isBackwards; - } - - void setBackwards(bool backwards) - { - m_isBackwards = backwards; - } - - int interpolationMode() const - { - return m_interpolationMode; - } - - - private: - f_cnt_t m_frameIndex; - const bool m_varyingPitch; - bool m_isBackwards; - SRC_STATE * m_resamplingData; - int m_interpolationMode; - - friend class SampleBuffer; - - } ; - - - SampleBuffer(); - // constructor which either loads sample _audio_file or decodes - // base64-data out of string - SampleBuffer(const QString & audioFile, bool isBase64Data = false); - SampleBuffer(const sampleFrame * data, const f_cnt_t frames); - explicit SampleBuffer(const f_cnt_t frames); - SampleBuffer(const SampleBuffer & orig); - - friend void swap(SampleBuffer & first, SampleBuffer & second) noexcept; - SampleBuffer& operator= (const SampleBuffer that); - - ~SampleBuffer() override; - - bool play( - sampleFrame * ab, - handleState * state, - const fpp_t frames, - const float freq, - const LoopMode loopMode = LoopMode::Off - ); - - void visualize( - QPainter & p, - const QRect & dr, - const QRect & clip, - f_cnt_t fromFrame = 0, - f_cnt_t toFrame = 0 - ); - inline void visualize( - QPainter & p, - const QRect & dr, - f_cnt_t fromFrame = 0, - f_cnt_t toFrame = 0 - ) - { - visualize(p, dr, dr, fromFrame, toFrame); - } - - inline const QString & audioFile() const - { - return m_audioFile; - } - - inline f_cnt_t startFrame() const - { - return m_startFrame; - } - - inline f_cnt_t endFrame() const - { - return m_endFrame; - } - - inline f_cnt_t loopStartFrame() const - { - return m_loopStartFrame; - } - - inline f_cnt_t loopEndFrame() const - { - return m_loopEndFrame; - } - - void setLoopStartFrame(f_cnt_t start) - { - m_loopStartFrame = start; - } - - void setLoopEndFrame(f_cnt_t end) - { - m_loopEndFrame = end; - } - - void setAllPointFrames( - f_cnt_t start, - f_cnt_t end, - f_cnt_t loopStart, - f_cnt_t loopEnd - ) - { - m_startFrame = start; - m_endFrame = end; - m_loopStartFrame = loopStart; - m_loopEndFrame = loopEnd; - } - - inline f_cnt_t frames() const - { - return m_frames; - } - - inline float amplification() const - { - return m_amplification; - } - - inline bool reversed() const - { - return m_reversed; - } - - inline float frequency() const - { - return m_frequency; - } - - sample_rate_t sampleRate() const - { - return m_sampleRate; - } - - int sampleLength() const - { - return double(m_endFrame - m_startFrame) / m_sampleRate * 1000; - } - - inline void setFrequency(float freq) - { - m_frequency = freq; - } - - inline void setSampleRate(sample_rate_t rate) - { - m_sampleRate = rate; - } - - inline const sampleFrame * data() const - { - return m_data; - } - - QString openAudioFile() const; - QString openAndSetAudioFile(); - QString openAndSetWaveformFile(); - - QString & toBase64(QString & dst) const; - - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock() - SampleBuffer * resample(const sample_rate_t srcSR, const sample_rate_t dstSR); - - void normalizeSampleRate(const sample_rate_t srcSR, bool keepSettings = false); - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock(), out of loops for efficiency - inline sample_t userWaveSample(const float sample) const - { - f_cnt_t frames = m_frames; - sampleFrame * data = m_data; - const float frame = sample * frames; - f_cnt_t f1 = static_cast(frame) % frames; - if (f1 < 0) - { - f1 += frames; - } - return linearInterpolate(data[f1][0], data[(f1 + 1) % frames][0], fraction(frame)); - } - - void dataReadLock() - { - m_varLock.lockForRead(); - } - - void dataUnlock() - { - m_varLock.unlock(); - } - - - std::unique_ptr m_userAntiAliasWaveTable; - - -public slots: - void setAudioFile(const QString & audioFile); - void loadFromBase64(const QString & data); - void setStartFrame(const lmms::f_cnt_t s); - void setEndFrame(const lmms::f_cnt_t e); - void setAmplification(float a); - void setReversed(bool on); - void sampleRateChanged(); + using value_type = sampleFrame; + using reference = sampleFrame&; + using const_iterator = std::vector::const_iterator; + using const_reverse_iterator = std::vector::const_reverse_iterator; + using difference_type = std::vector::difference_type; + using size_type = std::vector::size_type; + + SampleBuffer() = default; + SampleBuffer(const QString& audioFile); + SampleBuffer(const QByteArray& base64Data, int sampleRate); + SampleBuffer( + const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate()); + + friend void swap(SampleBuffer& first, SampleBuffer& second) noexcept; + auto toBase64() const -> QString; + + auto audioFile() const -> QString; + auto sampleRate() const -> sample_rate_t; + + auto begin() const -> const_iterator; + auto end() const -> const_iterator; + auto rbegin() const -> const_reverse_iterator; + auto rend() const -> const_reverse_iterator; + + auto data() const -> const sampleFrame*; + auto size() const -> size_type; + bool empty() const; private: - static sample_rate_t audioEngineSampleRate(); - - void update(bool keepSettings = false); - - void convertIntToFloat(int_sample_t * & ibuf, f_cnt_t frames, int channels); - void directFloatWrite(sample_t * & fbuf, f_cnt_t frames, int channels); + void decodeSampleSF(const QString& fileName); + void decodeSampleDS(const QString& fileName); - f_cnt_t decodeSampleSF( - QString fileName, - sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate - ); -#ifdef LMMS_HAVE_OGGVORBIS - f_cnt_t decodeSampleOGGVorbis( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate - ); -#endif - f_cnt_t decodeSampleDS( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate - ); - - QString m_audioFile; - sampleFrame * m_origData; - f_cnt_t m_origFrames; - sampleFrame * m_data; - mutable QReadWriteLock m_varLock; - f_cnt_t m_frames; - f_cnt_t m_startFrame; - f_cnt_t m_endFrame; - f_cnt_t m_loopStartFrame; - f_cnt_t m_loopEndFrame; - float m_amplification; - bool m_reversed; - float m_frequency; - sample_rate_t m_sampleRate; - - sampleFrame * getSampleFragment( - f_cnt_t index, - f_cnt_t frames, - LoopMode loopMode, - sampleFrame * * tmp, - bool * backwards, - f_cnt_t loopStart, - f_cnt_t loopEnd, - f_cnt_t end - ) const; - - f_cnt_t getLoopedIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const; - f_cnt_t getPingPongIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const; - - -signals: - void sampleUpdated(); - -} ; +private: + std::vector m_data; + std::optional m_audioFile; + int m_sampleRate = 0; +}; } // namespace lmms diff --git a/include/SampleBuffer2.h b/include/SampleBuffer2.h deleted file mode 100644 index fbee2121760..00000000000 --- a/include/SampleBuffer2.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SampleBuffer2.h - container-class SampleBuffer2 - * - * Copyright (c) 2005-2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef LMMS_SAMPLE_BUFFER2_H -#define LMMS_SAMPLE_BUFFER2_H - -#include -#include -#include -#include -#include - -#include "AudioEngine.h" -#include "Engine.h" -#include "lmms_basics.h" -#include "lmms_export.h" - -namespace lmms { -class LMMS_EXPORT SampleBuffer2 -{ -public: - using value_type = sampleFrame; - using reference = sampleFrame&; - using const_iterator = std::vector::const_iterator; - using const_reverse_iterator = std::vector::const_reverse_iterator; - using difference_type = std::vector::difference_type; - using size_type = std::vector::size_type; - - SampleBuffer2() = default; - SampleBuffer2(const QString& audioFile); - SampleBuffer2(const QByteArray& base64Data, int sampleRate); - SampleBuffer2( - const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate()); - - friend void swap(SampleBuffer2& first, SampleBuffer2& second) noexcept; - auto toBase64() const -> QString; - - auto audioFile() const -> QString; - auto sampleRate() const -> sample_rate_t; - - auto begin() const -> const_iterator; - auto end() const -> const_iterator; - auto rbegin() const -> const_reverse_iterator; - auto rend() const -> const_reverse_iterator; - - auto data() const -> const sampleFrame*; - auto size() const -> size_type; - bool empty() const; - -private: - void decodeSampleSF(const QString& fileName); - void decodeSampleDS(const QString& fileName); - -private: - std::vector m_data; - std::optional m_audioFile; - int m_sampleRate = 0; -}; - -} // namespace lmms - -#endif // LMMS_SAMPLE_BUFFER2_H diff --git a/include/SampleClip.h b/include/SampleClip.h index d7abd1db7f3..fe158a2ce47 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -78,7 +78,7 @@ class SampleClip : public Clip void setIsPlaying(bool isPlaying); public slots: - void setSampleBuffer(SampleBuffer2* sb); + void setSampleBuffer(SampleBuffer* sb); void setSampleFile( const QString & _sf ); void updateLength(); void toggleRecord(); diff --git a/include/SampleLoader.h b/include/SampleLoader.h index b0a986edb1f..d27701a5f34 100644 --- a/include/SampleLoader.h +++ b/include/SampleLoader.h @@ -28,7 +28,7 @@ #include #include -#include "SampleBuffer2.h" +#include "SampleBuffer.h" #include "lmms_export.h" namespace lmms::gui { @@ -37,8 +37,8 @@ class LMMS_EXPORT SampleLoader public: static QString openAudioFile(const QString& previousFile = ""); static QString openWaveformFile(const QString& previousFile = ""); - static std::unique_ptr createBufferFromFile(const QString& filePath); - static std::unique_ptr createBufferFromBase64(const QString& base64, int sampleRate = Engine::audioEngine()->processingSampleRate()); + static std::unique_ptr createBufferFromFile(const QString& filePath); + static std::unique_ptr createBufferFromBase64(const QString& base64, int sampleRate = Engine::audioEngine()->processingSampleRate()); private: static void displayError(const QString& message); }; diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index e2455902102..3f86ebae5cd 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -29,7 +29,7 @@ #include #include "PlayHandle.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" #include "TimePos.h" namespace lmms @@ -54,7 +54,7 @@ class SampleRecordHandle : public PlayHandle bool isFromTrack( const Track * _track ) const override; f_cnt_t framesRecorded() const; - void createSampleBuffer(SampleBuffer2** _sample_buf); + void createSampleBuffer(SampleBuffer** _sample_buf); private: diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.h b/plugins/AudioFileProcessor/AudioFileProcessor.h index 4397fb39a8b..bcace420425 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.h +++ b/plugins/AudioFileProcessor/AudioFileProcessor.h @@ -97,8 +97,6 @@ private slots: void sampleUpdated(); private: - using handleState = SampleBuffer::handleState; - std::shared_ptr m_sample; FloatModel m_ampModel; diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index c2e155a20c5..0afc2836dd8 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -46,7 +46,7 @@ #include "Knob.h" #include "NotePlayHandle.h" #include "PathUtil.h" -#include "SampleBuffer.h" +#include "Sample.h" #include "Song.h" #include "PatchesDialog.h" @@ -437,7 +437,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) if (sample.region->PitchTrack == true) { freq_factor *= sample.freqFactor; } // We need a bit of margin so we don't get glitching - samples = frames / freq_factor + MARGIN[m_interpolation]; + samples = frames / freq_factor + Sample::s_interpolationMargins[m_interpolation]; } // Load this note's data diff --git a/plugins/Patman/Patman.h b/plugins/Patman/Patman.h index adb1e488124..8d2c8c6577b 100644 --- a/plugins/Patman/Patman.h +++ b/plugins/Patman/Patman.h @@ -29,7 +29,7 @@ #include "Instrument.h" #include "InstrumentView.h" #include "Sample.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" #include "AutomatableModel.h" #include "MemoryManager.h" diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index c355b84e4a3..3069c4b9628 100644 --- a/plugins/TripleOscillator/TripleOscillator.cpp +++ b/plugins/TripleOscillator/TripleOscillator.cpp @@ -93,7 +93,7 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : tr( "Modulation type %1" ).arg( _idx+1 ) ), m_useWaveTableModel(true), - m_sampleBuffer( new SampleBuffer2 ), + m_sampleBuffer( new SampleBuffer ), m_volumeLeft( 0.0f ), m_volumeRight( 0.0f ), m_detuningLeft( 0.0f ), @@ -146,7 +146,7 @@ void OscillatorObject::oscUserDefWaveDblClick() QString af = gui::SampleLoader::openWaveformFile(); auto buffer = gui::SampleLoader::createBufferFromFile(af); // TODO C++20: Deprecated, use std::atomic instead - std::atomic_store(&m_sampleBuffer, std::shared_ptr(std::move(buffer))); + std::atomic_store(&m_sampleBuffer, std::shared_ptr(std::move(buffer))); if( af != "" ) { @@ -293,7 +293,7 @@ void TripleOscillator::loadSettings( const QDomElement & _this ) auto buffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile" + is)); // TODO C++20: Deprecated, use std::atomic instead - std::atomic_store(&m_osc[i]->m_sampleBuffer, std::shared_ptr(std::move(buffer))); + std::atomic_store(&m_osc[i]->m_sampleBuffer, std::shared_ptr(std::move(buffer))); } } diff --git a/plugins/TripleOscillator/TripleOscillator.h b/plugins/TripleOscillator/TripleOscillator.h index c66028a98c2..27c7894eec7 100644 --- a/plugins/TripleOscillator/TripleOscillator.h +++ b/plugins/TripleOscillator/TripleOscillator.h @@ -29,7 +29,7 @@ #include "Instrument.h" #include "InstrumentView.h" #include "AutomatableModel.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" namespace lmms { @@ -69,7 +69,7 @@ class OscillatorObject : public Model IntModel m_waveShapeModel; IntModel m_modulationAlgoModel; BoolModel m_useWaveTableModel; - std::shared_ptr m_sampleBuffer; + std::shared_ptr m_sampleBuffer; float m_volumeLeft; float m_volumeRight; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a66c134f813..d94141a296e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -67,7 +67,6 @@ set(LMMS_SRCS core/RingBuffer.cpp core/Sample.cpp core/SampleBuffer.cpp - core/SampleBuffer2.cpp core/SampleClip.cpp core/SamplePlayHandle.cpp core/SampleRecordHandle.cpp diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 0a9673c8e2c..65a17f85a01 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -28,7 +28,7 @@ #include "AudioEngine.h" #include "Engine.h" #include "Oscillator.h" - +#include "SampleLoader.h" namespace lmms { @@ -221,7 +221,7 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset ) shape_sample = Oscillator::sawSample( phase ); break; case LfoShape::UserDefinedWave: - shape_sample = m_userWave.userWaveSample( phase ); + shape_sample = Oscillator::userWaveSample(m_userWave.get(), phase); break; case LfoShape::RandomWave: if( frame == 0 ) @@ -354,7 +354,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->audioFile()); } @@ -386,8 +386,9 @@ void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this ) m_sustainModel.setValue( 1.0 - m_sustainModel.value() ); } - m_userWave.setAudioFile( _this.attribute( "userwavefile" ) ); - + auto buffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile")); + // TODO C++20: Deprecated, use std::atomic instead + std::atomic_store(&m_userWave, std::shared_ptr(std::move(buffer))); updateSampleVars(); } diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 4cf0850ebc7..2a6617bd2cf 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -49,7 +49,7 @@ LfoController::LfoController( Model * _parent ) : m_phaseOffset( 0 ), m_currentPhase( 0 ), m_sampleFunction( &Oscillator::sinSample ), - m_userDefSampleBuffer(std::make_shared()) + m_userDefSampleBuffer(std::make_shared()) { setSampleExact( true ); connect( &m_waveModel, SIGNAL(dataChanged()), @@ -214,7 +214,7 @@ void LfoController::loadSettings( const QDomElement & _this ) auto buffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile")); // TODO C++20: Deprecated, use std::atomic instead - std::atomic_store(&m_userDefSampleBuffer, std::shared_ptr(std::move(buffer))); + std::atomic_store(&m_userDefSampleBuffer, std::shared_ptr(std::move(buffer))); updateSampleFunction(); } diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index 2393040fa6c..61643eb4947 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -182,7 +182,7 @@ void Oscillator::generateFromFFT(int bands, sample_t* table) normalize(s_sampleBuffer.data(), table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1); } -std::unique_ptr Oscillator::generateAntiAliasUserWaveTable(const SampleBuffer2* sampleBuffer) +std::unique_ptr Oscillator::generateAntiAliasUserWaveTable(const SampleBuffer* sampleBuffer) { auto userAntiAliasWaveTable = std::make_unique(); for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i) diff --git a/src/core/Sample.cpp b/src/core/Sample.cpp index 7da88b3f8ac..8e78ae7b236 100644 --- a/src/core/Sample.cpp +++ b/src/core/Sample.cpp @@ -1,5 +1,5 @@ /* - * Sample.cpp - State for container-class SampleBuffer2 + * Sample.cpp - State for container-class SampleBuffer * * Copyright (c) 2023 saker * @@ -30,7 +30,7 @@ namespace lmms { Sample::Sample(const QString& audioFile) - : m_buffer(std::make_shared(audioFile)) + : m_buffer(std::make_shared(audioFile)) , m_startFrame(0) , m_endFrame(m_buffer->size()) , m_loopStartFrame(0) @@ -39,7 +39,7 @@ Sample::Sample(const QString& audioFile) } Sample::Sample(const QByteArray& base64, int sampleRate) - : m_buffer(std::make_shared(base64, sampleRate)) + : m_buffer(std::make_shared(base64, sampleRate)) , m_startFrame(0) , m_endFrame(m_buffer->size()) , m_loopStartFrame(0) @@ -48,7 +48,7 @@ Sample::Sample(const QByteArray& base64, int sampleRate) } Sample::Sample(const sampleFrame* data, int numFrames, int sampleRate) - : m_buffer(std::make_shared(data, numFrames, sampleRate)) + : m_buffer(std::make_shared(data, numFrames, sampleRate)) , m_startFrame(0) , m_endFrame(m_buffer->size()) , m_loopStartFrame(0) @@ -56,7 +56,7 @@ Sample::Sample(const sampleFrame* data, int numFrames, int sampleRate) { } -Sample::Sample(std::shared_ptr buffer) +Sample::Sample(std::shared_ptr buffer) : m_buffer(buffer) , m_startFrame(0) , m_endFrame(m_buffer->size()) @@ -265,7 +265,7 @@ auto Sample::playbackSize() const -> int : 0; } -auto Sample::buffer() const -> std::shared_ptr +auto Sample::buffer() const -> std::shared_ptr { const auto lock = std::shared_lock{m_mutex}; return m_buffer; diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 75d69bc95e0..57819e27a68 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -23,1568 +23,177 @@ */ #include "SampleBuffer.h" -#include "Oscillator.h" - -#include #include #include -#include -#include - - +#include +#include +#include +#include +#include +#include +#include #include - -#define OV_EXCLUDE_STATIC_CALLBACKS -#ifdef LMMS_HAVE_OGGVORBIS -#include -#endif - -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -#include -#endif - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H -#include -#endif - +#include +#include #include "AudioEngine.h" -#include "base64.h" -#include "ConfigManager.h" #include "DrumSynth.h" -#include "endian_handling.h" #include "Engine.h" -#include "GuiApplication.h" -#include "Note.h" #include "PathUtil.h" -#include "FileDialog.h" - -namespace lmms -{ - -SampleBuffer::SampleBuffer() : - m_userAntiAliasWaveTable(nullptr), - m_audioFile(""), - m_origData(nullptr), - m_origFrames(0), - m_data(nullptr), - m_frames(0), - m_startFrame(0), - m_endFrame(0), - m_loopStartFrame(0), - m_loopEndFrame(0), - m_amplification(1.0f), - m_reversed(false), - m_frequency(DefaultBaseFreq), - m_sampleRate(audioEngineSampleRate()) -{ - - connect(Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(sampleRateChanged())); - update(); -} - +namespace lmms { - -SampleBuffer::SampleBuffer(const QString & audioFile, bool isBase64Data) - : SampleBuffer() +SampleBuffer::SampleBuffer(const sampleFrame* data, int numFrames, int sampleRate) + : m_data(data, data + numFrames) + , m_sampleRate(sampleRate) { - if (isBase64Data) - { - loadFromBase64(audioFile); - } - else - { - m_audioFile = audioFile; - update(); - } } - - - -SampleBuffer::SampleBuffer(const sampleFrame * data, const f_cnt_t frames) - : SampleBuffer() +SampleBuffer::SampleBuffer(const QString& audioFile) { - if (frames > 0) - { - m_origData = MM_ALLOC( frames); - memcpy(m_origData, data, frames * BYTES_PER_FRAME); - m_origFrames = frames; - update(); - } -} - + if (audioFile.isEmpty()) { throw std::runtime_error{"Failure loading audio file: Audio file path is empty."}; } - - -SampleBuffer::SampleBuffer(const f_cnt_t frames) - : SampleBuffer() -{ - if (frames > 0) - { - m_origData = MM_ALLOC( frames); - memset(m_origData, 0, frames * BYTES_PER_FRAME); - m_origFrames = frames; - update(); - } + auto resolvedFileName = PathUtil::toAbsolute(PathUtil::toShortestRelative(audioFile)); + QFileInfo{resolvedFileName}.suffix() == "ds" ? decodeSampleDS(resolvedFileName) : decodeSampleSF(resolvedFileName); } - - - -SampleBuffer::SampleBuffer(const SampleBuffer& orig) +SampleBuffer::SampleBuffer(const QByteArray& base64Data, int sampleRate) + : m_data(reinterpret_cast(base64Data.data()), + reinterpret_cast(base64Data.data()) + base64Data.size() / sizeof(sampleFrame)) + , m_sampleRate(sampleRate) { - orig.m_varLock.lockForRead(); - - m_audioFile = orig.m_audioFile; - m_origFrames = orig.m_origFrames; - m_origData = (m_origFrames > 0) ? MM_ALLOC( m_origFrames) : nullptr; - m_frames = orig.m_frames; - m_data = (m_frames > 0) ? MM_ALLOC( m_frames) : nullptr; - m_startFrame = orig.m_startFrame; - m_endFrame = orig.m_endFrame; - m_loopStartFrame = orig.m_loopStartFrame; - m_loopEndFrame = orig.m_loopEndFrame; - m_amplification = orig.m_amplification; - m_reversed = orig.m_reversed; - m_frequency = orig.m_frequency; - m_sampleRate = orig.m_sampleRate; - - //Deep copy m_origData and m_data from original - const auto origFrameBytes = m_origFrames * BYTES_PER_FRAME; - const auto frameBytes = m_frames * BYTES_PER_FRAME; - if (orig.m_origData != nullptr && origFrameBytes > 0) - { memcpy(m_origData, orig.m_origData, origFrameBytes); } - if (orig.m_data != nullptr && frameBytes > 0) - { memcpy(m_data, orig.m_data, frameBytes); } - - orig.m_varLock.unlock(); } - - - void swap(SampleBuffer& first, SampleBuffer& second) noexcept { using std::swap; - - // Lock both buffers for writing, with address as lock ordering - if (&first == &second) { return; } - else if (&first > &second) - { - first.m_varLock.lockForWrite(); - second.m_varLock.lockForWrite(); - } - else - { - second.m_varLock.lockForWrite(); - first.m_varLock.lockForWrite(); - } - - first.m_audioFile.swap(second.m_audioFile); - swap(first.m_origData, second.m_origData); swap(first.m_data, second.m_data); - swap(first.m_origFrames, second.m_origFrames); - swap(first.m_frames, second.m_frames); - swap(first.m_startFrame, second.m_startFrame); - swap(first.m_endFrame, second.m_endFrame); - swap(first.m_loopStartFrame, second.m_loopStartFrame); - swap(first.m_loopEndFrame, second.m_loopEndFrame); - swap(first.m_amplification, second.m_amplification); - swap(first.m_frequency, second.m_frequency); - swap(first.m_reversed, second.m_reversed); + swap(first.m_audioFile, second.m_audioFile); swap(first.m_sampleRate, second.m_sampleRate); - - // Unlock again - first.m_varLock.unlock(); - second.m_varLock.unlock(); -} - - - - -SampleBuffer& SampleBuffer::operator=(SampleBuffer that) -{ - swap(*this, that); - return *this; -} - - - - -SampleBuffer::~SampleBuffer() -{ - MM_FREE(m_origData); - MM_FREE(m_data); -} - - - -void SampleBuffer::sampleRateChanged() -{ - update(true); -} - -sample_rate_t SampleBuffer::audioEngineSampleRate() -{ - return Engine::audioEngine()->processingSampleRate(); -} - - -void SampleBuffer::update(bool keepSettings) -{ - const bool lock = (m_data != nullptr); - if (lock) - { - Engine::audioEngine()->requestChangeInModel(); - m_varLock.lockForWrite(); - MM_FREE(m_data); - } - - // File size and sample length limits - const int fileSizeMax = 300; // MB - const int sampleLengthMax = 90; // Minutes - - bool fileLoadError = false; - if (m_audioFile.isEmpty() && m_origData != nullptr && m_origFrames > 0) - { - // TODO: reverse- and amplification-property is not covered - // by following code... - m_data = MM_ALLOC( m_origFrames); - memcpy(m_data, m_origData, m_origFrames * BYTES_PER_FRAME); - if (keepSettings == false) - { - m_frames = m_origFrames; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; - } - } - else if (!m_audioFile.isEmpty()) - { - QString file = PathUtil::toAbsolute(m_audioFile); - int_sample_t * buf = nullptr; - sample_t * fbuf = nullptr; - ch_cnt_t channels = DEFAULT_CHANNELS; - sample_rate_t samplerate = audioEngineSampleRate(); - m_frames = 0; - - const QFileInfo fileInfo(file); - if (fileInfo.size() > fileSizeMax * 1024 * 1024) - { - fileLoadError = true; - } - else - { - // Use QFile to handle unicode file names on Windows - QFile f(file); - SNDFILE * sndFile; - SF_INFO sfInfo; - sfInfo.format = 0; - if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false))) - { - f_cnt_t frames = sfInfo.frames; - int rate = sfInfo.samplerate; - if (frames / rate > sampleLengthMax * 60) - { - fileLoadError = true; - } - sf_close(sndFile); - } - f.close(); - } - - if (!fileLoadError) - { -#ifdef LMMS_HAVE_OGGVORBIS - // workaround for a bug in libsndfile or our libsndfile decoder - // causing some OGG files to be distorted -> try with OGG Vorbis - // decoder first if filename extension matches "ogg" - if (m_frames == 0 && fileInfo.suffix() == "ogg") - { - m_frames = decodeSampleOGGVorbis(file, buf, channels, samplerate); - } -#endif - if (m_frames == 0) - { - m_frames = decodeSampleSF(file, fbuf, channels, samplerate); - } -#ifdef LMMS_HAVE_OGGVORBIS - if (m_frames == 0) - { - m_frames = decodeSampleOGGVorbis(file, buf, channels, samplerate); - } -#endif - if (m_frames == 0) - { - m_frames = decodeSampleDS(file, buf, channels, samplerate); - } - } - - if (m_frames == 0 || fileLoadError) // if still no frames, bail - { - // sample couldn't be decoded, create buffer containing - // one sample-frame - m_data = MM_ALLOC( 1); - memset(m_data, 0, sizeof(*m_data)); - m_frames = 1; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; - } - else // otherwise normalize sample rate - { - normalizeSampleRate(samplerate, keepSettings); - } - } - else - { - // neither an audio-file nor a buffer to copy from, so create - // buffer containing one sample-frame - m_data = MM_ALLOC( 1); - memset(m_data, 0, sizeof(*m_data)); - m_frames = 1; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; - } - - if (lock) - { - m_varLock.unlock(); - Engine::audioEngine()->doneChangeInModel(); - } - - emit sampleUpdated(); - - // allocate space for anti-aliased wave table - if (m_userAntiAliasWaveTable == nullptr) - { - m_userAntiAliasWaveTable = std::make_unique(); - } - - if (fileLoadError) - { - QString title = tr("Fail to open file"); - QString message = tr("Audio files are limited to %1 MB " - "in size and %2 minutes of playing time" - ).arg(fileSizeMax).arg(sampleLengthMax); - if (gui::getGUI() != nullptr) - { - QMessageBox::information(nullptr, - title, message, QMessageBox::Ok); - } - else - { - fprintf(stderr, "%s\n", message.toUtf8().constData()); - } - } -} - - -void SampleBuffer::convertIntToFloat( - int_sample_t * & ibuf, - f_cnt_t frames, - int channels -) -{ - // following code transforms int-samples into float-samples and does amplifying & reversing - const float fac = 1 / OUTPUT_SAMPLE_MULTIPLIER; - m_data = MM_ALLOC( frames); - const int ch = (channels > 1) ? 1 : 0; - - // if reversing is on, we also reverse when scaling - bool isReversed = m_reversed; - int idx = isReversed ? (frames - 1) * channels : 0; - for (f_cnt_t frame = 0; frame < frames; ++frame) - { - m_data[frame][0] = ibuf[idx+0] * fac; - m_data[frame][1] = ibuf[idx+ch] * fac; - idx += isReversed ? -channels : channels; - } - - delete[] ibuf; -} - -void SampleBuffer::directFloatWrite( - sample_t * & fbuf, - f_cnt_t frames, - int channels -) -{ - - m_data = MM_ALLOC( frames); - const int ch = (channels > 1) ? 1 : 0; - - // if reversing is on, we also reverse when scaling - bool isReversed = m_reversed; - int idx = isReversed ? (frames - 1) * channels : 0; - for (f_cnt_t frame = 0; frame < frames; ++frame) - { - m_data[frame][0] = fbuf[idx+0]; - m_data[frame][1] = fbuf[idx+ch]; - idx += isReversed ? -channels : channels; - } - - delete[] fbuf; -} - - -void SampleBuffer::normalizeSampleRate(const sample_rate_t srcSR, bool keepSettings) -{ - const sample_rate_t oldRate = m_sampleRate; - // do samplerate-conversion to our default-samplerate - if (srcSR != audioEngineSampleRate()) - { - SampleBuffer * resampled = resample(srcSR, audioEngineSampleRate()); - - m_sampleRate = audioEngineSampleRate(); - MM_FREE(m_data); - m_frames = resampled->frames(); - m_data = MM_ALLOC( m_frames); - memcpy(m_data, resampled->data(), m_frames * sizeof(sampleFrame)); - delete resampled; - } - - if (keepSettings == false) - { - // update frame-variables - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; - } - else if (oldRate != audioEngineSampleRate()) - { - auto oldRateToNewRateRatio = static_cast(audioEngineSampleRate()) / oldRate; - - m_startFrame = std::clamp(f_cnt_t(m_startFrame * oldRateToNewRateRatio), 0, m_frames); - m_endFrame = std::clamp(f_cnt_t(m_endFrame * oldRateToNewRateRatio), m_startFrame, m_frames); - m_loopStartFrame = std::clamp(f_cnt_t(m_loopStartFrame * oldRateToNewRateRatio), 0, m_frames); - m_loopEndFrame = std::clamp(f_cnt_t(m_loopEndFrame * oldRateToNewRateRatio), m_loopStartFrame, m_frames); - m_sampleRate = audioEngineSampleRate(); - } } - - - -f_cnt_t SampleBuffer::decodeSampleSF( - QString fileName, - sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate -) +void SampleBuffer::decodeSampleSF(const QString& audioFile) { - SNDFILE * sndFile; - SF_INFO sfInfo; - sfInfo.format = 0; - f_cnt_t frames = 0; - sf_count_t sfFramesRead; - + SNDFILE* sndFile = nullptr; + auto sfInfo = SF_INFO{}; // Use QFile to handle unicode file names on Windows - QFile f(fileName); - if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false))) - { - frames = sfInfo.frames; - - buf = new sample_t[sfInfo.channels * frames]; - sfFramesRead = sf_read_float(sndFile, buf, sfInfo.channels * frames); - - if (sfFramesRead < sfInfo.channels * frames) - { -#ifdef DEBUG_LMMS - qDebug("SampleBuffer::decodeSampleSF(): could not read" - " sample %s: %s", fileName, sf_strerror(nullptr)); -#endif - } - channels = sfInfo.channels; - samplerate = sfInfo.samplerate; - - sf_close(sndFile); - } - else - { -#ifdef DEBUG_LMMS - qDebug("SampleBuffer::decodeSampleSF(): could not load " - "sample %s: %s", fileName, sf_strerror(nullptr)); -#endif - } - f.close(); - - //write down either directly or convert i->f depending on file type - - if (frames > 0 && buf != nullptr) - { - directFloatWrite(buf, frames, channels); - } - - return frames; -} - - - - -#ifdef LMMS_HAVE_OGGVORBIS - -// callback-functions for reading ogg-file - -size_t qfileReadCallback(void * ptr, size_t size, size_t n, void * udata ) -{ - return static_cast(udata)->read((char*) ptr, size * n); -} - - - - -int qfileSeekCallback(void * udata, ogg_int64_t offset, int whence) -{ - auto f = static_cast(udata); - - if (whence == SEEK_CUR) - { - f->seek(f->pos() + offset); - } - else if (whence == SEEK_END) - { - f->seek(f->size() + offset); - } - else - { - f->seek(offset); - } - return 0; -} - - - - -int qfileCloseCallback(void * udata) -{ - delete static_cast(udata); - return 0; -} - - - - -long qfileTellCallback(void * udata) -{ - return static_cast(udata)->pos(); -} - - - - -f_cnt_t SampleBuffer::decodeSampleOGGVorbis( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate -) -{ - static ov_callbacks callbacks = - { - qfileReadCallback, - qfileSeekCallback, - qfileCloseCallback, - qfileTellCallback - } ; - - OggVorbis_File vf; - - f_cnt_t frames = 0; - - auto f = new QFile(fileName); - if (f->open(QFile::ReadOnly) == false) - { - delete f; - return 0; - } - - int err = ov_open_callbacks(f, &vf, nullptr, 0, callbacks); - - if (err < 0) - { - switch (err) - { - case OV_EREAD: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " media read error\n"); - break; - case OV_ENOTVORBIS: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " not an Ogg Vorbis file\n"); - break; - case OV_EVERSION: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " vorbis version mismatch\n"); - break; - case OV_EBADHEADER: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " invalid Vorbis bitstream header\n"); - break; - case OV_EFAULT: - printf("SampleBuffer::decodeSampleOgg(): " - "internal logic fault\n"); - break; - } - delete f; - return 0; - } - - ov_pcm_seek(&vf, 0); - - channels = ov_info(&vf, -1)->channels; - samplerate = ov_info(&vf, -1)->rate; - - ogg_int64_t total = ov_pcm_total(&vf, -1); - - buf = new int_sample_t[total * channels]; - int bitstream = 0; - long bytesRead = 0; - - do - { - bytesRead = ov_read(&vf, - (char *) &buf[frames * channels], - (total - frames) * channels * BYTES_PER_INT_SAMPLE, - isLittleEndian() ? 0 : 1, - BYTES_PER_INT_SAMPLE, - 1, - &bitstream - ); - - if (bytesRead < 0) - { - break; - } - frames += bytesRead / (channels * BYTES_PER_INT_SAMPLE); - } - while (bytesRead != 0 && bitstream == 0); - - ov_clear(&vf); - - // if buffer isn't empty, convert it to float and write it down - if (frames > 0 && buf != nullptr) - { - convertIntToFloat(buf, frames, channels); - } - - return frames; -} -#endif // LMMS_HAVE_OGGVORBIS - - - - -f_cnt_t SampleBuffer::decodeSampleDS( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate -) -{ - DrumSynth ds; - f_cnt_t frames = ds.GetDSFileSamples(fileName, buf, channels, samplerate); - - if (frames > 0 && buf != nullptr) - { - convertIntToFloat(buf, frames, channels); - } - - return frames; - -} - - - - -bool SampleBuffer::play( - sampleFrame * ab, - handleState * state, - const fpp_t frames, - const float freq, - const LoopMode loopMode -) -{ - f_cnt_t startFrame = m_startFrame; - f_cnt_t endFrame = m_endFrame; - f_cnt_t loopStartFrame = m_loopStartFrame; - f_cnt_t loopEndFrame = m_loopEndFrame; - - if (endFrame == 0 || frames == 0) - { - return false; - } - - // variable for determining if we should currently be playing backwards in a ping-pong loop - bool isBackwards = state->isBackwards(); - - // The SampleBuffer can play a given sample with increased or decreased pitch. However, only - // samples that contain a tone that matches the default base note frequency of 440 Hz will - // produce the exact requested pitch in [Hz]. - const double freqFactor = (double) freq / (double) m_frequency * - m_sampleRate / Engine::audioEngine()->processingSampleRate(); - - // calculate how many frames we have in requested pitch - const auto totalFramesForCurrentPitch = static_cast((endFrame - startFrame) / freqFactor); - - if (totalFramesForCurrentPitch == 0) - { - return false; - } - - - // this holds the index of the first frame to play - f_cnt_t playFrame = std::max(state->m_frameIndex, startFrame); - - if (loopMode == LoopMode::Off) + auto file = QFile{audioFile}; + if (!file.open(QIODevice::ReadOnly)) { - if (playFrame >= endFrame || (endFrame - playFrame) / freqFactor == 0) - { - // the sample is done being played - return false; - } - } - else if (loopMode == LoopMode::On) - { - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - } - else - { - playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame); - } - - f_cnt_t fragmentSize = (f_cnt_t)(frames * freqFactor) + MARGIN[state->interpolationMode()]; - - sampleFrame * tmp = nullptr; - - // check whether we have to change pitch... - if (freqFactor != 1.0 || state->m_varyingPitch) - { - SRC_DATA srcData; - // Generate output - srcData.data_in = - getSampleFragment(playFrame, fragmentSize, loopMode, &tmp, &isBackwards, - loopStartFrame, loopEndFrame, endFrame )->data(); - srcData.data_out = ab->data(); - srcData.input_frames = fragmentSize; - srcData.output_frames = frames; - srcData.src_ratio = 1.0 / freqFactor; - srcData.end_of_input = 0; - int error = src_process(state->m_resamplingData, &srcData); - if (error) - { - printf("SampleBuffer: error while resampling: %s\n", - src_strerror(error)); - } - if (srcData.output_frames_gen > frames) - { - printf("SampleBuffer: not enough frames: %ld / %d\n", - srcData.output_frames_gen, frames); - } - // Advance - switch (loopMode) - { - case LoopMode::Off: - playFrame += srcData.input_frames_used; - break; - case LoopMode::On: - playFrame += srcData.input_frames_used; - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - break; - case LoopMode::PingPong: - { - f_cnt_t left = srcData.input_frames_used; - if (state->isBackwards()) - { - playFrame -= srcData.input_frames_used; - if (playFrame < loopStartFrame) - { - left -= (loopStartFrame - playFrame); - playFrame = loopStartFrame; - } - else left = 0; - } - playFrame += left; - playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame); - break; - } - } - } - else - { - // we don't have to pitch, so we just copy the sample-data - // as is into pitched-copy-buffer - - // Generate output - memcpy(ab, - getSampleFragment(playFrame, frames, loopMode, &tmp, &isBackwards, - loopStartFrame, loopEndFrame, endFrame), - frames * BYTES_PER_FRAME); - // Advance - switch (loopMode) - { - case LoopMode::Off: - playFrame += frames; - break; - case LoopMode::On: - playFrame += frames; - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - break; - case LoopMode::PingPong: - { - f_cnt_t left = frames; - if (state->isBackwards()) - { - playFrame -= frames; - if (playFrame < loopStartFrame) - { - left -= (loopStartFrame - playFrame); - playFrame = loopStartFrame; - } - else left = 0; - } - playFrame += left; - playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame); - break; - } - } - } - - if (tmp != nullptr) - { - MM_FREE(tmp); - } - - state->setBackwards(isBackwards); - state->setFrameIndex(playFrame); - - for (fpp_t i = 0; i < frames; ++i) - { - ab[i][0] *= m_amplification; - ab[i][1] *= m_amplification; - } - - return true; -} - - - - -sampleFrame * SampleBuffer::getSampleFragment( - f_cnt_t index, - f_cnt_t frames, - LoopMode loopMode, - sampleFrame * * tmp, - bool * backwards, - f_cnt_t loopStart, - f_cnt_t loopEnd, - f_cnt_t end -) const -{ - if (loopMode == LoopMode::Off) - { - if (index + frames <= end) - { - return m_data + index; - } - } - else if (loopMode == LoopMode::On) - { - if (index + frames <= loopEnd) - { - return m_data + index; - } - } - else - { - if (!*backwards && index + frames < loopEnd) - { - return m_data + index; - } - } - - *tmp = MM_ALLOC( frames); - - if (loopMode == LoopMode::Off) - { - f_cnt_t available = end - index; - memcpy(*tmp, m_data + index, available * BYTES_PER_FRAME); - memset(*tmp + available, 0, (frames - available) * BYTES_PER_FRAME); - } - else if (loopMode == LoopMode::On) - { - f_cnt_t copied = std::min(frames, loopEnd - index); - memcpy(*tmp, m_data + index, copied * BYTES_PER_FRAME); - f_cnt_t loopFrames = loopEnd - loopStart; - while (copied < frames) - { - f_cnt_t todo = std::min(frames - copied, loopFrames); - memcpy(*tmp + copied, m_data + loopStart, todo * BYTES_PER_FRAME); - copied += todo; - } - } - else - { - f_cnt_t pos = index; - bool currentBackwards = pos < loopStart - ? false - : *backwards; - f_cnt_t copied = 0; - - - if (currentBackwards) - { - copied = std::min(frames, pos - loopStart); - for (int i = 0; i < copied; i++) - { - (*tmp)[i][0] = m_data[pos - i][0]; - (*tmp)[i][1] = m_data[pos - i][1]; - } - pos -= copied; - if (pos == loopStart) { currentBackwards = false; } - } - else - { - copied = std::min(frames, loopEnd - pos); - memcpy(*tmp, m_data + pos, copied * BYTES_PER_FRAME); - pos += copied; - if (pos == loopEnd) { currentBackwards = true; } - } - - while (copied < frames) - { - if (currentBackwards) - { - f_cnt_t todo = std::min(frames - copied, pos - loopStart); - for (int i = 0; i < todo; i++) - { - (*tmp)[copied + i][0] = m_data[pos - i][0]; - (*tmp)[copied + i][1] = m_data[pos - i][1]; - } - pos -= todo; - copied += todo; - if (pos <= loopStart) { currentBackwards = false; } - } - else - { - f_cnt_t todo = std::min(frames - copied, loopEnd - pos); - memcpy(*tmp + copied, m_data + pos, todo * BYTES_PER_FRAME); - pos += todo; - copied += todo; - if (pos >= loopEnd) { currentBackwards = true; } - } - } - *backwards = currentBackwards; - } - - return *tmp; -} - - - - -f_cnt_t SampleBuffer::getLoopedIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const -{ - if (index < endf) - { - return index; - } - return startf + (index - startf) % (endf - startf); -} - - -f_cnt_t SampleBuffer::getPingPongIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const -{ - if (index < endf) - { - return index; - } - const f_cnt_t loopLen = endf - startf; - const f_cnt_t loopPos = (index - endf) % (loopLen * 2); - - return (loopPos < loopLen) - ? endf - loopPos - : startf + (loopPos - loopLen); -} - - -/* @brief Draws a sample buffer on the QRect given in the range [fromFrame, toFrame) - * @param QPainter p: Painter object for the painting operations - * @param QRect dr: QRect where the buffer will be drawn in - * @param QRect clip: QRect used for clipping - * @param f_cnt_t fromFrame: First frame of the range - * @param f_cnt_t toFrame: Last frame of the range non-inclusive - */ -void SampleBuffer::visualize( - QPainter & p, - const QRect & dr, - const QRect & clip, - f_cnt_t fromFrame, - f_cnt_t toFrame -) -{ - if (m_frames == 0) { return; } - - const bool focusOnRange = toFrame <= m_frames && 0 <= fromFrame && fromFrame < toFrame; - //TODO: If the clip QRect is not being used we should remove it - //p.setClipRect(clip); - const int w = dr.width(); - const int h = dr.height(); - - const int yb = h / 2 + dr.y(); - const float ySpace = h * 0.5f; - const int nbFrames = focusOnRange ? toFrame - fromFrame : m_frames; - - const double fpp = std::max(1., static_cast(nbFrames) / w); - // There are 2 possibilities: Either nbFrames is bigger than - // the width, so we will have width points, or nbFrames is - // smaller than the width (fpp = 1) and we will have nbFrames - // points - const int totalPoints = nbFrames > w - ? w - : nbFrames; - std::vector fEdgeMax(totalPoints); - std::vector fEdgeMin(totalPoints); - std::vector fRmsMax(totalPoints); - std::vector fRmsMin(totalPoints); - int curPixel = 0; - const int xb = dr.x(); - const int first = focusOnRange ? fromFrame : 0; - const int last = focusOnRange ? toFrame - 1 : m_frames - 1; - // When the number of frames isn't perfectly divisible by the - // width, the remaining frames don't fit the last pixel and are - // past the visible area. lastVisibleFrame is the index number of - // the last visible frame. - const int visibleFrames = (fpp * w); - const int lastVisibleFrame = focusOnRange - ? fromFrame + visibleFrames - 1 - : visibleFrames - 1; - - for (double frame = first; frame <= last && frame <= lastVisibleFrame; frame += fpp) - { - float maxData = -1; - float minData = 1; - - auto rmsData = std::array{}; - - // Find maximum and minimum samples within range - for (int i = 0; i < fpp && frame + i <= last; ++i) - { - for (int j = 0; j < 2; ++j) - { - auto curData = m_data[static_cast(frame) + i][j]; - - if (curData > maxData) { maxData = curData; } - if (curData < minData) { minData = curData; } - - rmsData[j] += curData * curData; - } - } - - const float trueRmsData = (rmsData[0] + rmsData[1]) / 2 / fpp; - const float sqrtRmsData = sqrt(trueRmsData); - const float maxRmsData = std::clamp(sqrtRmsData, minData, maxData); - const float minRmsData = std::clamp(-sqrtRmsData, minData, maxData); - - // If nbFrames >= w, we can use curPixel to calculate X - // but if nbFrames < w, we need to calculate it proportionally - // to the total number of points - auto x = nbFrames >= w - ? xb + curPixel - : xb + ((static_cast(curPixel) / nbFrames) * w); - // Partial Y calculation - auto py = ySpace * m_amplification; - fEdgeMax[curPixel] = QPointF(x, (yb - (maxData * py))); - fEdgeMin[curPixel] = QPointF(x, (yb - (minData * py))); - fRmsMax[curPixel] = QPointF(x, (yb - (maxRmsData * py))); - fRmsMin[curPixel] = QPointF(x, (yb - (minRmsData * py))); - ++curPixel; - } - - for (int i = 0; i < totalPoints; ++i) - { - p.drawLine(fEdgeMax[i], fEdgeMin[i]); - } - - p.setPen(p.pen().color().lighter(123)); - - for (int i = 0; i < totalPoints; ++i) - { - p.drawLine(fRmsMax[i], fRmsMin[i]); - } -} - - - - -QString SampleBuffer::openAudioFile() const -{ - gui::FileDialog ofd(nullptr, tr("Open audio file")); - - QString dir; - if (!m_audioFile.isEmpty()) - { - QString f = m_audioFile; - if (QFileInfo(f).isRelative()) - { - f = ConfigManager::inst()->userSamplesDir() + f; - if (QFileInfo(f).exists() == false) - { - f = ConfigManager::inst()->factorySamplesDir() + - m_audioFile; - } - } - dir = QFileInfo(f).absolutePath(); - } - else - { - dir = ConfigManager::inst()->userSamplesDir(); - } - // change dir to position of previously opened file - ofd.setDirectory(dir); - ofd.setFileMode(gui::FileDialog::ExistingFiles); - - // set filters - QStringList types; - types << tr("All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc " - "*.aif *.aiff *.au *.raw)") - << tr("Wave-Files (*.wav)") - << tr("OGG-Files (*.ogg)") - << tr("DrumSynth-Files (*.ds)") - << tr("FLAC-Files (*.flac)") - << tr("SPEEX-Files (*.spx)") - //<< tr("MP3-Files (*.mp3)") - //<< tr("MIDI-Files (*.mid)") - << tr("VOC-Files (*.voc)") - << tr("AIFF-Files (*.aif *.aiff)") - << tr("AU-Files (*.au)") - << tr("RAW-Files (*.raw)") - //<< tr("MOD-Files (*.mod)") - ; - ofd.setNameFilters(types); - if (!m_audioFile.isEmpty()) - { - // select previously opened file - ofd.selectFile(QFileInfo(m_audioFile).fileName()); - } - - if (ofd.exec () == QDialog::Accepted) - { - if (ofd.selectedFiles().isEmpty()) - { - return QString(); - } - return PathUtil::toShortestRelative(ofd.selectedFiles()[0]); - } - - return QString(); -} - - - - -QString SampleBuffer::openAndSetAudioFile() -{ - QString fileName = this->openAudioFile(); - - if(!fileName.isEmpty()) - { - this->setAudioFile(fileName); - } - - return fileName; -} - - -QString SampleBuffer::openAndSetWaveformFile() -{ - if (m_audioFile.isEmpty()) - { - m_audioFile = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; + throw std::runtime_error{ + "Failed to open sample " + audioFile.toStdString() + ": " + file.errorString().toStdString()}; } - QString fileName = this->openAudioFile(); - - if (!fileName.isEmpty()) - { - this->setAudioFile(fileName); - } - else + sndFile = sf_open_fd(file.handle(), SFM_READ, &sfInfo, false); + if (sf_error(sndFile) != 0) { - m_audioFile = ""; + throw std::runtime_error{"Failure opening audio handle: " + std::string{sf_strerror(sndFile)}}; } - return fileName; -} - - - -#undef LMMS_HAVE_FLAC_STREAM_ENCODER_H /* not yet... */ -#undef LMMS_HAVE_FLAC_STREAM_DECODER_H - -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -FLAC__StreamEncoderWriteStatus flacStreamEncoderWriteCallback( - const FLAC__StreamEncoder * /*encoder*/, - const FLAC__byte buffer[], - unsigned int /*samples*/, - unsigned int bytes, - unsigned int /*currentFrame*/, - void * clientData -) -{ -/* if (bytes == 0) - { - return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; - }*/ - return (static_cast(clientData)->write( - (const char *) buffer, bytes) == (int) bytes) - ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK - : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; -} - + auto buf = std::vector(sfInfo.channels * sfInfo.frames); + sf_read_float(sndFile, buf.data(), buf.size()); -void flacStreamEncoderMetadataCallback( - const FLAC__StreamEncoder *, - const FLAC__StreamMetadata * metadata, - void * clientData -) -{ - QBuffer * b = static_cast(clientData); - b->seek(0); - b->write((const char *) metadata, sizeof(*metadata)); -} - -#endif // LMMS_HAVE_FLAC_STREAM_ENCODER_H - - - -QString & SampleBuffer::toBase64(QString & dst) const -{ -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H - const f_cnt_t FRAMES_PER_BUF = 1152; - - FLAC__StreamEncoder * flacEnc = FLAC__stream_encoder_new(); - FLAC__stream_encoder_set_channels(flacEnc, DEFAULT_CHANNELS); - FLAC__stream_encoder_set_blocksize(flacEnc, FRAMES_PER_BUF); -/* FLAC__stream_encoder_set_do_exhaustive_model_search(flacEnc, true); - FLAC__stream_encoder_set_do_mid_side_stereo(flacEnc, true);*/ - FLAC__stream_encoder_set_sample_rate(flacEnc, - Engine::audioEngine()->sampleRate()); + sf_close(sndFile); + file.close(); - QBuffer baWriter; - baWriter.open(QBuffer::WriteOnly); - - FLAC__stream_encoder_set_write_callback(flacEnc, - flacStreamEncoderWriteCallback); - FLAC__stream_encoder_set_metadata_callback(flacEnc, - flacStreamEncoderMetadataCallback); - FLAC__stream_encoder_set_client_data(flacEnc, &baWriter); - - if (FLAC__stream_encoder_init(flacEnc) != FLAC__STREAM_ENCODER_OK) - { - printf("Error within FLAC__stream_encoder_init()!\n"); - } - - f_cnt_t frameCnt = 0; - - while (frameCnt < m_frames) + auto result = std::vector(sfInfo.frames); + for (int i = 0; i < static_cast(result.size()); ++i) { - f_cnt_t remaining = std::min(FRAMES_PER_BUF, m_frames - frameCnt); - FLAC__int32 buf[FRAMES_PER_BUF * DEFAULT_CHANNELS]; - for (f_cnt_t f = 0; f < remaining; ++f) + if (sfInfo.channels == 1) { - for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) - { - buf[f*DEFAULT_CHANNELS+ch] = (FLAC__int32)( - AudioEngine::clip(m_data[f+frameCnt][ch]) * - OUTPUT_SAMPLE_MULTIPLIER); - } + // Upmix from mono to stereo + result[i] = {buf[i], buf[i]}; } - FLAC__stream_encoder_process_interleaved(flacEnc, buf, remaining); - frameCnt += remaining; - } - FLAC__stream_encoder_finish(flacEnc); - FLAC__stream_encoder_delete(flacEnc); - printf("%d %d\n", frameCnt, (int)baWriter.size()); - baWriter.close(); - - base64::encode(baWriter.buffer().data(), baWriter.buffer().size(), dst); - -#else // LMMS_HAVE_FLAC_STREAM_ENCODER_H - - base64::encode((const char *) m_data, - m_frames * sizeof(sampleFrame), dst); - -#endif // LMMS_HAVE_FLAC_STREAM_ENCODER_H - - return dst; -} - - - - -SampleBuffer * SampleBuffer::resample(const sample_rate_t srcSR, const sample_rate_t dstSR ) -{ - sampleFrame * data = m_data; - const f_cnt_t frames = m_frames; - const auto dstFrames = static_cast((frames / (float)srcSR) * (float)dstSR); - auto dstSB = new SampleBuffer(dstFrames); - sampleFrame * dstBuf = dstSB->m_origData; - - // yeah, libsamplerate, let's rock with sinc-interpolation! - int error; - SRC_STATE * state; - if ((state = src_new(SRC_SINC_MEDIUM_QUALITY, DEFAULT_CHANNELS, &error)) != nullptr) - { - SRC_DATA srcData; - srcData.end_of_input = 1; - srcData.data_in = data->data(); - srcData.data_out = dstBuf->data(); - srcData.input_frames = frames; - srcData.output_frames = dstFrames; - srcData.src_ratio = (double) dstSR / srcSR; - if ((error = src_process(state, &srcData))) + else if (sfInfo.channels > 1) { - printf("SampleBuffer: error while resampling: %s\n", src_strerror(error)); + // TODO: Add support for higher number of channels (i.e., 5.1 channel systems) + // The current behavior assumes stereo in all cases excluding mono. + // This may not be the expected behavior, given some audio files with a higher number of channels. + result[i] = {buf[i * sfInfo.channels], buf[i * sfInfo.channels + 1]}; } - src_delete(state); } - else - { - printf("Error: src_new() failed in sample_buffer.cpp!\n"); - } - dstSB->update(); - return dstSB; -} - - - -void SampleBuffer::setAudioFile(const QString & audioFile) -{ - m_audioFile = PathUtil::toShortestRelative(audioFile); - update(); + m_data = result; + m_audioFile = audioFile; + m_sampleRate = sfInfo.samplerate; } - - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - -struct flacStreamDecoderClientData +void SampleBuffer::decodeSampleDS(const QString& audioFile) { - QBuffer * readBuffer; - QBuffer * writeBuffer; -} ; - - + auto data = std::unique_ptr{}; + int_sample_t* dataPtr = nullptr; -FLAC__StreamDecoderReadStatus flacStreamDecoderReadCallback( - const FLAC__StreamDecoder * /*decoder*/, - FLAC__byte * buffer, - unsigned int * bytes, - void * clientData -) -{ - int res = static_cast( - clientData)->readBuffer->read((char *) buffer, *bytes); + auto ds = DrumSynth{}; + const auto engineRate = Engine::audioEngine()->processingSampleRate(); + const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate); + data.reset(dataPtr); - if (res > 0) + auto result = std::vector(frames); + if (frames > 0 && data != nullptr) { - *bytes = res; - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS); } + else { throw std::runtime_error{"Decoding failure: failed to decode DrumSynth file."}; } - *bytes = 0; - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + m_data = result; + m_audioFile = audioFile; + m_sampleRate = engineRate; } - - - -FLAC__StreamDecoderWriteStatus flacStreamDecoderWriteCallback( - const FLAC__StreamDecoder * /*decoder*/, - const FLAC__Frame * frame, - const FLAC__int32 * const buffer[], - void * clientData -) +QString SampleBuffer::toBase64() const { - if (frame->header.channels != 2) - { - printf("channels != 2 in flacStreamDecoderWriteCallback()\n"); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - if (frame->header.bits_per_sample != 16) - { - printf("bits_per_sample != 16 in flacStreamDecoderWriteCallback()\n"); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - const f_cnt_t numberOfFrames = frame->header.blocksize; - for (f_cnt_t f = 0; f < numberOfFrames; ++f) - { - sampleFrame sframe = { buffer[0][f] / OUTPUT_SAMPLE_MULTIPLIER, - buffer[1][f] / OUTPUT_SAMPLE_MULTIPLIER - } ; - static_cast( - clientData )->writeBuffer->write( - (const char *) sframe, sizeof(sframe)); - } - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + // 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}; + return byteArray.toBase64(); } - -void flacStreamDecoderMetadataCallback( - const FLAC__StreamDecoder *, - const FLAC__StreamMetadata *, - void * /*clientData*/ -) +auto SampleBuffer::audioFile() const -> QString { - printf("stream decoder metadata callback\n"); -/* QBuffer * b = static_cast(clientData); - b->seek(0); - b->write((const char *) metadata, sizeof(*metadata));*/ + return m_audioFile.value_or(""); } - -void flacStreamDecoderErrorCallback( - const FLAC__StreamDecoder *, - FLAC__StreamDecoderErrorStatus status, - void * /*clientData*/ -) +auto SampleBuffer::sampleRate() const -> sample_rate_t { - printf("error callback! %d\n", status); - // what to do now?? + return m_sampleRate; } -#endif // LMMS_HAVE_FLAC_STREAM_DECODER_H - - -void SampleBuffer::loadFromBase64(const QString & data) +auto SampleBuffer::begin() const -> const_iterator { - char * dst = nullptr; - int dsize = 0; - base64::decode(data, &dst, &dsize); - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - - QByteArray origData = QByteArray::fromRawData(dst, dsize); - QBuffer baReader(&origData); - baReader.open(QBuffer::ReadOnly); - - QBuffer baWriter; - baWriter.open(QBuffer::WriteOnly); - - flacStreamDecoderClientData cdata = { &baReader, &baWriter } ; - - FLAC__StreamDecoder * flacDec = FLAC__stream_decoder_new(); - - FLAC__stream_decoder_set_read_callback(flacDec, - flacStreamDecoderReadCallback); - FLAC__stream_decoder_set_write_callback(flacDec, - flacStreamDecoderWriteCallback); - FLAC__stream_decoder_set_error_callback(flacDec, - flacStreamDecoderErrorCallback); - FLAC__stream_decoder_set_metadata_callback(flacDec, - flacStreamDecoderMetadataCallback); - FLAC__stream_decoder_set_client_data(flacDec, &cdata); - - FLAC__stream_decoder_init(flacDec); - - FLAC__stream_decoder_process_until_end_of_stream(flacDec); - - FLAC__stream_decoder_finish(flacDec); - FLAC__stream_decoder_delete(flacDec); - - baReader.close(); - - origData = baWriter.buffer(); - printf("%d\n", (int) origData.size()); - - m_origFrames = origData.size() / sizeof(sampleFrame); - MM_FREE(m_origData); - m_origData = MM_ALLOC( m_origFrames); - memcpy(m_origData, origData.data(), origData.size()); - -#else /* LMMS_HAVE_FLAC_STREAM_DECODER_H */ - - m_origFrames = dsize / sizeof(sampleFrame); - MM_FREE(m_origData); - m_origData = MM_ALLOC( m_origFrames); - memcpy(m_origData, dst, dsize); - -#endif // LMMS_HAVE_FLAC_STREAM_DECODER_H - - delete[] dst; - - m_audioFile = QString(); - update(); + return m_data.begin(); } - - - -void SampleBuffer::setStartFrame(const f_cnt_t s) +auto SampleBuffer::end() const -> const_iterator { - m_startFrame = s; + return m_data.end(); } - - - -void SampleBuffer::setEndFrame(const f_cnt_t e) +auto SampleBuffer::rbegin() const -> const_reverse_iterator { - m_endFrame = e; + return m_data.rbegin(); } - - - -void SampleBuffer::setAmplification(float a) +auto SampleBuffer::rend() const -> const_reverse_iterator { - m_amplification = a; - emit sampleUpdated(); + return m_data.rend(); } - - - -void SampleBuffer::setReversed(bool on) +auto SampleBuffer::data() const -> const sampleFrame* { - Engine::audioEngine()->requestChangeInModel(); - m_varLock.lockForWrite(); - if (m_reversed != on) { std::reverse(m_data, m_data + m_frames); } - m_reversed = on; - m_varLock.unlock(); - Engine::audioEngine()->doneChangeInModel(); - emit sampleUpdated(); + return m_data.data(); } - - - - -SampleBuffer::handleState::handleState(bool varyingPitch, int interpolationMode) : - m_frameIndex(0), - m_varyingPitch(varyingPitch), - m_isBackwards(false) +auto SampleBuffer::size() const -> size_type { - int error; - m_interpolationMode = interpolationMode; - - if ((m_resamplingData = src_new(interpolationMode, DEFAULT_CHANNELS, &error)) == nullptr) - { - qDebug("Error: src_new() failed in sample_buffer.cpp!\n"); - } + return m_data.size(); } - - - -SampleBuffer::handleState::~handleState() +auto SampleBuffer::empty() const -> bool { - src_delete(m_resamplingData); + return m_data.empty(); } -} // namespace lmms +} // namespace lmms \ No newline at end of file diff --git a/src/core/SampleBuffer2.cpp b/src/core/SampleBuffer2.cpp deleted file mode 100644 index 5788b0f5f18..00000000000 --- a/src/core/SampleBuffer2.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/* - * SampleBuffer2.cpp - container-class SampleBuffer2 - * - * Copyright (c) 2005-2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "SampleBuffer2.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AudioEngine.h" -#include "DrumSynth.h" -#include "Engine.h" -#include "PathUtil.h" - -namespace lmms { - -SampleBuffer2::SampleBuffer2(const sampleFrame* data, int numFrames, int sampleRate) - : m_data(data, data + numFrames) - , m_sampleRate(sampleRate) -{ -} - -SampleBuffer2::SampleBuffer2(const QString& audioFile) -{ - if (audioFile.isEmpty()) { throw std::runtime_error{"Failure loading audio file: Audio file path is empty."}; } - - auto resolvedFileName = PathUtil::toAbsolute(PathUtil::toShortestRelative(audioFile)); - QFileInfo{resolvedFileName}.suffix() == "ds" ? decodeSampleDS(resolvedFileName) : decodeSampleSF(resolvedFileName); -} - -SampleBuffer2::SampleBuffer2(const QByteArray& base64Data, int sampleRate) - : m_data(reinterpret_cast(base64Data.data()), - reinterpret_cast(base64Data.data()) + base64Data.size() / sizeof(sampleFrame)) - , m_sampleRate(sampleRate) -{ -} - -void swap(SampleBuffer2& first, SampleBuffer2& 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); -} - -void SampleBuffer2::decodeSampleSF(const QString& audioFile) -{ - SNDFILE* sndFile = nullptr; - auto sfInfo = SF_INFO{}; - - // Use QFile to handle unicode file names on Windows - auto file = QFile{audioFile}; - if (!file.open(QIODevice::ReadOnly)) - { - throw std::runtime_error{ - "Failed to open sample " + audioFile.toStdString() + ": " + file.errorString().toStdString()}; - } - - sndFile = sf_open_fd(file.handle(), SFM_READ, &sfInfo, false); - if (sf_error(sndFile) != 0) - { - throw std::runtime_error{"Failure opening audio handle: " + std::string{sf_strerror(sndFile)}}; - } - - auto buf = std::vector(sfInfo.channels * sfInfo.frames); - sf_read_float(sndFile, buf.data(), buf.size()); - - sf_close(sndFile); - file.close(); - - auto result = std::vector(sfInfo.frames); - for (int i = 0; i < static_cast(result.size()); ++i) - { - if (sfInfo.channels == 1) - { - // Upmix from mono to stereo - result[i] = {buf[i], buf[i]}; - } - else if (sfInfo.channels > 1) - { - // TODO: Add support for higher number of channels (i.e., 5.1 channel systems) - // The current behavior assumes stereo in all cases excluding mono. - // This may not be the expected behavior, given some audio files with a higher number of channels. - result[i] = {buf[i * sfInfo.channels], buf[i * sfInfo.channels + 1]}; - } - } - - m_data = result; - m_audioFile = audioFile; - m_sampleRate = sfInfo.samplerate; -} - -void SampleBuffer2::decodeSampleDS(const QString& audioFile) -{ - auto data = std::unique_ptr{}; - int_sample_t* dataPtr = nullptr; - - auto ds = DrumSynth{}; - const auto engineRate = Engine::audioEngine()->processingSampleRate(); - const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate); - data.reset(dataPtr); - - auto result = std::vector(frames); - if (frames > 0 && data != nullptr) - { - src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS); - } - else { throw std::runtime_error{"Decoding failure: failed to decode DrumSynth file."}; } - - m_data = result; - m_audioFile = audioFile; - m_sampleRate = engineRate; -} - -QString SampleBuffer2::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}; - return byteArray.toBase64(); -} - -auto SampleBuffer2::audioFile() const -> QString -{ - return m_audioFile.value_or(""); -} - -auto SampleBuffer2::sampleRate() const -> sample_rate_t -{ - return m_sampleRate; -} - -auto SampleBuffer2::begin() const -> const_iterator -{ - return m_data.begin(); -} - -auto SampleBuffer2::end() const -> const_iterator -{ - return m_data.end(); -} - -auto SampleBuffer2::rbegin() const -> const_reverse_iterator -{ - return m_data.rbegin(); -} - -auto SampleBuffer2::rend() const -> const_reverse_iterator -{ - return m_data.rend(); -} - -auto SampleBuffer2::data() const -> const sampleFrame* -{ - return m_data.data(); -} - -auto SampleBuffer2::size() const -> size_type -{ - return m_data.size(); -} - -auto SampleBuffer2::empty() const -> bool -{ - return m_data.empty(); -} - -} // namespace lmms \ No newline at end of file diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 5f5d6da4f38..7dddfc1040e 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -125,10 +125,10 @@ QString SampleClip::sampleFile() const -void SampleClip::setSampleBuffer( SampleBuffer2* sb ) +void SampleClip::setSampleBuffer( SampleBuffer* sb ) { // TODO C++20: Deprecated, use std::atomic instead - auto buffer = std::shared_ptr(sb); + auto buffer = std::shared_ptr(sb); std::atomic_store(&m_sample, std::make_shared(buffer)); updateLength(); diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 05ea2915f19..182d359adce 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -53,7 +53,7 @@ SampleRecordHandle::~SampleRecordHandle() { if( !m_buffers.empty() ) { - SampleBuffer2* sb; + SampleBuffer* sb; createSampleBuffer( &sb ); m_clip->setSampleBuffer(sb); } @@ -111,7 +111,7 @@ f_cnt_t SampleRecordHandle::framesRecorded() const -void SampleRecordHandle::createSampleBuffer(SampleBuffer2** sampleBuf) +void SampleRecordHandle::createSampleBuffer(SampleBuffer** sampleBuf) { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in @@ -130,7 +130,7 @@ void SampleRecordHandle::createSampleBuffer(SampleBuffer2** sampleBuf) data_ptr += ( *it ).second; } // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer2(data, frames, Engine::audioEngine()->inputSampleRate()); + *sampleBuf = new SampleBuffer(data, frames, Engine::audioEngine()->inputSampleRate()); delete[] data; } diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp index f16f56b748d..7ce8e5317c1 100644 --- a/src/core/audio/AudioSampleRecorder.cpp +++ b/src/core/audio/AudioSampleRecorder.cpp @@ -25,7 +25,7 @@ #include "AudioSampleRecorder.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" #include "debug.h" @@ -70,7 +70,7 @@ f_cnt_t AudioSampleRecorder::framesRecorded() const -void AudioSampleRecorder::createSampleBuffer(SampleBuffer2** sampleBuf) +void AudioSampleRecorder::createSampleBuffer(SampleBuffer** sampleBuf) { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in @@ -90,7 +90,7 @@ void AudioSampleRecorder::createSampleBuffer(SampleBuffer2** sampleBuf) data_ptr += ( *it ).second; } // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer2(data, frames, sampleRate()); + *sampleBuf = new SampleBuffer(data, frames, sampleRate()); delete[] data; } diff --git a/src/gui/LfoControllerDialog.cpp b/src/gui/LfoControllerDialog.cpp index 4a03be8e357..5106e8b1f2c 100644 --- a/src/gui/LfoControllerDialog.cpp +++ b/src/gui/LfoControllerDialog.cpp @@ -216,7 +216,7 @@ void LfoControllerDialog::askUserDefWave() auto buffer = SampleLoader::createBufferFromFile(fileName); // TODO C++20: Deprecated, use std::atomic instead - std::atomic_store(&sampleBuffer, std::shared_ptr(std::move(buffer))); + std::atomic_store(&sampleBuffer, std::shared_ptr(std::move(buffer))); if( fileName.isEmpty() == false ) { diff --git a/src/gui/SampleLoader.cpp b/src/gui/SampleLoader.cpp index e8fd87ba6c1..4ad4340a06e 100644 --- a/src/gui/SampleLoader.cpp +++ b/src/gui/SampleLoader.cpp @@ -86,29 +86,29 @@ QString SampleLoader::openWaveformFile(const QString& previousFile) previousFile.isEmpty() ? ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac" : previousFile); } -std::unique_ptr SampleLoader::createBufferFromFile(const QString& filePath) +std::unique_ptr SampleLoader::createBufferFromFile(const QString& filePath) { try { - return std::make_unique(filePath); + return std::make_unique(filePath); } catch (const std::runtime_error& error) { displayError(QString::fromStdString(error.what())); - return std::make_unique(); + return std::make_unique(); } } -std::unique_ptr SampleLoader::createBufferFromBase64(const QString& base64, int sampleRate) +std::unique_ptr SampleLoader::createBufferFromBase64(const QString& base64, int sampleRate) { try { - return std::make_unique(base64.toUtf8().toBase64(), sampleRate); + return std::make_unique(base64.toUtf8().toBase64(), sampleRate); } catch (const std::runtime_error& error) { displayError(QString::fromStdString(error.what())); - return std::make_unique(); + return std::make_unique(); } } diff --git a/src/gui/instrument/EnvelopeAndLfoView.cpp b/src/gui/instrument/EnvelopeAndLfoView.cpp index edb6c99c797..04d96973c11 100644 --- a/src/gui/instrument/EnvelopeAndLfoView.cpp +++ b/src/gui/instrument/EnvelopeAndLfoView.cpp @@ -28,6 +28,7 @@ #include "EnvelopeAndLfoView.h" #include "EnvelopeAndLfoParameters.h" +#include "SampleLoader.h" #include "embed.h" #include "Engine.h" #include "gui_templates.h" @@ -322,8 +323,9 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) QString value = StringPairDrag::decodeValue( _de ); if( type == "samplefile" ) { - m_params->m_userWave.setAudioFile( - StringPairDrag::decodeValue( _de ) ); + auto buffer = SampleLoader::createBufferFromFile(StringPairDrag::decodeValue(_de)); + // TODO C++20: Deprecated, use std::atomic instead + std::atomic_store(&m_params->m_userWave, std::shared_ptr(std::move(buffer))); m_userLfoBtn->model()->setValue( true ); m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); _de->accept(); @@ -332,9 +334,12 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) else if( type == QString( "clip_%1" ).arg( static_cast(Track::Type::Sample) ) ) { DataFile dataFile( value.toUtf8() ); - m_params->m_userWave.setAudioFile( dataFile.content(). + auto file = dataFile.content(). firstChildElement().firstChildElement(). - firstChildElement().attribute( "src" ) ); + firstChildElement().attribute("src"); + auto buffer = SampleLoader::createBufferFromFile(file); + // TODO C++20: Deprecated, use std::atomic instead + std::atomic_store(&m_params->m_userWave, std::shared_ptr(std::move(buffer))); m_userLfoBtn->model()->setValue( true ); m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); _de->accept(); @@ -446,8 +451,6 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) osc_frames *= 100.0f; } - // userWaveSample() may be used, called out of loop for efficiency - m_params->m_userWave.dataReadLock(); float old_y = 0; for( int x = 0; x <= LFO_GRAPH_W; ++x ) { @@ -483,8 +486,7 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) val = m_randomGraph; break; case EnvelopeAndLfoParameters::LfoShape::UserDefinedWave: - val = m_params->m_userWave. - userWaveSample( phase ); + val = Oscillator::userWaveSample(m_params->m_userWave.get(), phase); break; } if( static_cast( cur_sample ) <= @@ -499,7 +501,6 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) graph_y_base + cur_y ) ); old_y = cur_y; } - m_params->m_userWave.dataUnlock(); p.setPen( QColor( 201, 201, 225 ) ); int ms_per_osc = static_cast( SECS_PER_LFO_OSCILLATION * @@ -520,7 +521,7 @@ void EnvelopeAndLfoView::lfoUserWaveChanged() if( static_cast(m_params->m_lfoWaveModel.value()) == EnvelopeAndLfoParameters::LfoShape::UserDefinedWave ) { - if( m_params->m_userWave.frames() <= 1 ) + if (m_params->m_userWave->size() <= 1) { TextFloat::displayMessage( tr( "Hint" ), tr( "Drag and drop a sample into this window." ), diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index 9c5bd0efa37..922b98668fb 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -28,7 +28,7 @@ #include "Graph.h" #include "SampleLoader.h" #include "StringPairDrag.h" -#include "SampleBuffer2.h" +#include "SampleBuffer.h" #include "Oscillator.h" namespace lmms