Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sample caching #7058

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/EnvelopeAndLfoParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public slots:
sample_t * m_lfoShapeData;
sample_t m_random;
bool m_bad_lfoShapeData;
std::shared_ptr<const SampleBuffer> m_userWave = SampleBuffer::emptyBuffer();
std::shared_ptr<const SampleBuffer> m_userWave;

enum class LfoShape
{
Expand Down
2 changes: 1 addition & 1 deletion include/LfoController.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public slots:

private:
float m_heldSample;
std::shared_ptr<const SampleBuffer> m_userDefSampleBuffer = SampleBuffer::emptyBuffer();
std::shared_ptr<const SampleBuffer> m_userDefSampleBuffer;

protected slots:
void updatePhase();
Expand Down
2 changes: 1 addition & 1 deletion include/Oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class LMMS_EXPORT Oscillator
Oscillator * m_subOsc;
float m_phaseOffset;
float m_phase;
std::shared_ptr<const SampleBuffer> m_userWave = SampleBuffer::emptyBuffer();
std::shared_ptr<const SampleBuffer> m_userWave;
std::shared_ptr<const OscillatorConstants::waveform_t> m_userAntiAliasWaveTable;
bool m_useWaveTable;
// There are many update*() variants; the modulator flag is stored as a member variable to avoid
Expand Down
15 changes: 8 additions & 7 deletions include/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class LMMS_EXPORT Sample
// the array positions correspond to the converter_type parameter values in libsamplerate
// if there appears problems with playback on some interpolation mode, then the value for that mode
// may need to be higher - conversely, to optimize, some may work with lower values
static constexpr auto s_interpolationMargins = std::array<int, 5>{64, 64, 64, 4, 4};
static constexpr auto InterpolationMargins = std::array<int, 5>{64, 64, 64, 4, 4};

enum class Loop
{
Expand Down Expand Up @@ -80,23 +80,24 @@ class LMMS_EXPORT Sample
};

Sample() = default;
Sample(const QByteArray& base64, int sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const QByteArray& base64, sample_rate_t sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const sampleFrame* data, int numFrames,
sample_rate_t sampleRate = Engine::audioEngine()->processingSampleRate());
Sample(const Sample& other);
Sample(Sample&& other);
Sample(Sample&& other) noexcept;
explicit Sample(const QString& audioFile);
explicit Sample(std::shared_ptr<const SampleBuffer> buffer);

auto operator=(const Sample&) -> Sample&;
auto operator=(Sample&&) -> Sample&;
auto operator=(Sample&&) noexcept -> Sample&;

auto play(sampleFrame* dst, PlaybackState* state, int numFrames, float desiredFrequency = DefaultBaseFreq,
Loop loopMode = Loop::Off) -> 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 -> int { return m_buffer->size(); }
auto source() const -> const SampleBuffer::Source& { return m_buffer->source(); }

auto toBase64() const -> QString { return m_buffer->toBase64(); }

Expand Down Expand Up @@ -126,7 +127,7 @@ class LMMS_EXPORT Sample
void copyBufferBackward(sampleFrame* dst, int initialPosition, int advanceAmount) const;

private:
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::create();
std::atomic<int> m_startFrame = 0;
std::atomic<int> m_endFrame = 0;
std::atomic<int> m_loopStartFrame = 0;
Expand Down
98 changes: 88 additions & 10 deletions include/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

namespace lmms {
class LMMS_EXPORT SampleBuffer
: public std::enable_shared_from_this<SampleBuffer>
{
public:
using value_type = sampleFrame;
Expand All @@ -51,18 +52,97 @@ class LMMS_EXPORT SampleBuffer
using reverse_iterator = std::vector<sampleFrame>::reverse_iterator;
using const_reverse_iterator = std::vector<sampleFrame>::const_reverse_iterator;

SampleBuffer() = default;
explicit SampleBuffer(const QString& audioFile);
SampleBuffer(const QString& base64, int sampleRate);
SampleBuffer(std::vector<sampleFrame> data, int sampleRate);
SampleBuffer(
const sampleFrame* data, int numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate());
class LMMS_EXPORT Source
{
public:
enum class Type
{
Unknown,
AudioFile,
Base64
};

Source() = default;
Source(const QString& filePath);
Source(const QString& base64, sample_rate_t sampleRate);

/**
* A unique string identifying the SampleBuffer's source.
* - For audio files, this is the absolute file path.
* - For base64, this is a string encoding the hash of the base64 data + the sample rate.
* - For anything else, this is empty.
*/
auto identifier() const -> const QString& { return m_identifier; }

auto type() const -> Type { return m_type; }

auto hash() const -> std::size_t { return m_hash; }

//! The audio file full path or an empty string
auto audioFileAbsolute() const -> const QString&;

//! The audio file relative path or an empty string
auto audioFileRelative() const -> QString;

struct Hasher
{
auto operator()(const Source& src) const noexcept -> std::size_t
{
return src.hash();
}
};

friend auto operator==(const Source& lhs, const Source& rhs) noexcept -> bool
{
return lhs.m_type == rhs.m_type
&& lhs.m_hash == rhs.m_hash
&& lhs.m_identifier == rhs.m_identifier;
}

private:
Type m_type = Type::Unknown;
QString m_identifier;
std::size_t m_hash = 0;
};

//! passkey idiom
class Access
{
public:
friend class SampleBuffer;
friend class SampleLoader;
Access(Access&&) = default;
private:
Access() {}
Access(const Access&) = default;
};
sakertooth marked this conversation as resolved.
Show resolved Hide resolved

SampleBuffer() = delete;
SampleBuffer(Access) {}
SampleBuffer(Access, const QString& audioFile);
SampleBuffer(Access, const QString& base64, sample_rate_t sampleRate);
SampleBuffer(Access, std::vector<sampleFrame> data, sample_rate_t sampleRate);
SampleBuffer(Access, const sampleFrame* data, int numFrames, sample_rate_t sampleRate);

static auto create() -> std::shared_ptr<const SampleBuffer>;
static auto create(const QString& audioFile) -> std::shared_ptr<const SampleBuffer>;
static auto create(const QString& base64, sample_rate_t sampleRate) -> std::shared_ptr<const SampleBuffer>;
static auto create(std::vector<sampleFrame> data, sample_rate_t sampleRate)
-> std::shared_ptr<const SampleBuffer>;
static auto create(const sampleFrame* data, int numFrames,
sample_rate_t sampleRate = Engine::audioEngine()->processingSampleRate())
-> std::shared_ptr<const SampleBuffer>;

~SampleBuffer() = default;

friend void swap(SampleBuffer& first, SampleBuffer& second) noexcept;

auto shared() const -> std::shared_ptr<const SampleBuffer>;

auto toBase64() const -> QString;

auto audioFile() const -> const QString& { return m_audioFile; }
auto sampleRate() const -> sample_rate_t { return m_sampleRate; }
auto source() const -> const Source& { return m_source; }

auto begin() -> iterator { return m_data.begin(); }
auto end() -> iterator { return m_data.end(); }
Expand All @@ -86,12 +166,10 @@ class LMMS_EXPORT SampleBuffer
auto size() const -> size_type { return m_data.size(); }
auto empty() const -> bool { return m_data.empty(); }

static auto emptyBuffer() -> std::shared_ptr<const SampleBuffer>;

private:
std::vector<sampleFrame> m_data;
QString m_audioFile;
sample_rate_t m_sampleRate = Engine::audioEngine()->processingSampleRate();
Source m_source;
};

} // namespace lmms
Expand Down
56 changes: 41 additions & 15 deletions include/SampleLoader.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/*
* SampleLoader.h - Load audio and waveform files
* SampleLoader.h - Sample loader with support for caching
*
* Copyright (c) 2023 saker <sakertooth@gmail.com>
* 2024 Dalton Messmer <messmer.dalton/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
Expand All @@ -22,27 +23,52 @@
*
*/

#ifndef LMMS_GUI_SAMPLE_LOADER_H
#define LMMS_GUI_SAMPLE_LOADER_H
#ifndef LMMS_SAMPLE_LOADER_H
#define LMMS_SAMPLE_LOADER_H

#include <QString>
#include <memory>
#include <QFileSystemWatcher>
#include <QObject>
#include <unordered_map>

#include "SampleBuffer.h"
#include "lmms_export.h"

namespace lmms::gui {
class LMMS_EXPORT SampleLoader
namespace lmms {

class LMMS_EXPORT SampleLoader : public QObject
{
Q_OBJECT
public:
static QString openAudioFile(const QString& previousFile = "");
static QString openWaveformFile(const QString& previousFile = "");
static std::shared_ptr<const SampleBuffer> createBufferFromFile(const QString& filePath);
static std::shared_ptr<const SampleBuffer> createBufferFromBase64(
const QString& base64, int sampleRate = Engine::audioEngine()->processingSampleRate());
~SampleLoader() override = default;

static auto instance() -> SampleLoader&;

static auto fromFile(const QString& filePath, bool cache = true)
-> std::shared_ptr<const SampleBuffer>;

static auto fromBase64(const QString& base64, sample_rate_t sampleRate, bool cache = true)
-> std::shared_ptr<const SampleBuffer>;

static auto fromBase64(const QString& base64, bool cache = true)
-> std::shared_ptr<const SampleBuffer>;

private:
static void displayError(const QString& message);
SampleLoader();

struct AutoEvictor;

auto get(const SampleBuffer::Source& source) -> std::shared_ptr<const SampleBuffer>;

void add(const std::shared_ptr<const SampleBuffer>& buffer);
auto remove(const SampleBuffer& buffer) -> bool;
messmerd marked this conversation as resolved.
Show resolved Hide resolved

using Key = SampleBuffer::Source;
using Value = std::weak_ptr<const SampleBuffer>;

std::unordered_map<Key, Value, Key::Hasher> m_entries;
QFileSystemWatcher m_watcher;
};
} // namespace lmms::gui

#endif // LMMS_GUI_SAMPLE_LOADER_H
} // namespace lmms

#endif // LMMS_SAMPLE_LOADER_H
47 changes: 47 additions & 0 deletions include/SampleLoaderDialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* SampleLoaderDialog.h - Load audio and waveform files
*
* Copyright (c) 2023 saker <sakertooth@gmail.com>
*
* 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_GUI_SAMPLE_LOADER_DIALOG_H
#define LMMS_GUI_SAMPLE_LOADER_DIALOG_H

#include <QString>

#include "SampleLoader.h"
#include "lmms_export.h"

namespace lmms::gui {

class LMMS_EXPORT SampleLoaderDialog
{
public:
//! Returns relative path
static auto openAudioFile(const QString& previousFile = "") -> QString;

//! Returns relative path
static auto openWaveformFile(const QString& previousFile = "") -> QString;
};

} // namespace lmms::gui

#endif // LMMS_GUI_SAMPLE_LOADER_DIALOG_H
Loading