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 a cache for samples #7497

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a76d8c2
Add interface for SampleDatabase
sakertooth Sep 14, 2024
2ca2f25
Add implementation for SampleDatabase
sakertooth Sep 14, 2024
badde84
Rename SampleLoader::create* to SampleLoader::load*
sakertooth Sep 14, 2024
6a6ecfa
Use SampleDatabase in SampleLoader
sakertooth Sep 14, 2024
247955e
Fix segmentation fault on null entries
sakertooth Sep 14, 2024
121b327
Fix duplication
sakertooth Sep 14, 2024
99f29a9
Fix CI attempt 1
sakertooth Sep 14, 2024
35026dd
CI fix attempt 2
sakertooth Sep 14, 2024
777e9c8
Move file system helper functions into PathUtil
sakertooth Nov 13, 2024
9d30457
Move SampleLoader into the core namespace and add SampleFilePicker class
sakertooth Nov 13, 2024
053ba61
Add asserts that ensure only the main thread is requesting samples fr…
sakertooth Nov 13, 2024
b1c1a9a
Rebrand SampleDatabase to SampleCache
sakertooth Nov 13, 2024
ff07622
Merge remote-tracking branch 'upstream/master' into add-sample-database
sakertooth Nov 13, 2024
5810d3d
Remove ::gui suffix
sakertooth Nov 13, 2024
f9d7968
Improve get function
sakertooth Nov 21, 2024
826cbf7
Use insert_or_assign instead of emplace and make args an rvalue refer…
sakertooth Nov 21, 2024
e9a5914
Do not move buffer into map
sakertooth Nov 21, 2024
8cd2472
Are we there yet
sakertooth Nov 21, 2024
ffdf92c
Update SampleCache.cpp
sakertooth Nov 21, 2024
85f03e5
Update SampleCache.cpp
sakertooth Nov 21, 2024
7e34dad
Merge remote-tracking branch 'upstream/master' into add-sample-database
sakertooth Jan 3, 2025
efc5165
Use absolute path
sakertooth Feb 2, 2025
4ea292a
Include QCoreApplication header
sakertooth Feb 9, 2025
fd9861c
Update existing audio file entires instead of adding a completely new…
sakertooth Feb 9, 2025
216de3d
Fix crash when fetching audio files and remove get function
sakertooth Feb 9, 2025
2d2d9fe
Merge remote-tracking branch 'upstream' into add-sample-database
sakertooth Feb 9, 2025
c1c6898
Update copyright
sakertooth Feb 9, 2025
90cf7ca
Merge remote-tracking branch 'upstream' into add-sample-database
sakertooth Feb 14, 2025
38433da
Add ResourceCache
sakertooth Feb 15, 2025
a1d8d8d
Use hashing in the ResourceCache
sakertooth Feb 15, 2025
b8021b2
Update ResourceCache
sakertooth Feb 15, 2025
a3cc60e
Integrate ResourceCache with SampleBuffer
sakertooth Feb 15, 2025
54d625c
Add sampleSize function back in
sakertooth Feb 15, 2025
e56adf8
Fix few issues
sakertooth Feb 15, 2025
4562d86
Improve ResourceCache design
sakertooth Feb 15, 2025
80c097f
Reset hash state
sakertooth Feb 16, 2025
c56acb5
Integrate ResourceCache with SampleThumbnail
sakertooth Feb 16, 2025
89761be
Set default sample rate to the audio engine's output sample rate when…
sakertooth Feb 16, 2025
cc580ea
Add newline
sakertooth Feb 16, 2025
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/AutomationEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ protected slots:
QColor m_detuningNoteColor;
QColor m_ghostSampleColor;

SampleThumbnail m_sampleThumbnail;
std::shared_ptr<const SampleThumbnail> m_sampleThumbnail;

friend class AutomationEditorWindow;

Expand Down
2 changes: 1 addition & 1 deletion include/EnvelopeAndLfoParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,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 = std::make_shared<SampleBuffer>();

constexpr static auto NumLfoShapes = static_cast<std::size_t>(LfoShape::Count);

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 = std::make_shared<SampleBuffer>();

protected slots:
void updatePhase();
Expand Down
6 changes: 3 additions & 3 deletions include/Oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,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->data().size() == 0) { return 0; }
const auto frames = buffer->data().size();
const auto frame = absFraction(sample) * frames;
const auto f1 = static_cast<f_cnt_t>(frame);

Expand Down Expand Up @@ -251,7 +251,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::make_shared<SampleBuffer>();
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
7 changes: 7 additions & 0 deletions include/PathUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "lmms_export.h"

#include <QDir>
#include <filesystem>

namespace lmms::PathUtil
{
Expand Down Expand Up @@ -68,6 +69,12 @@ namespace lmms::PathUtil
//! Defaults to an absolute path if all bases fail.
QString LMMS_EXPORT toShortestRelative(const QString & input, bool allowLocal = false);

//! Converts a QString path to a STL filesystem path.
std::filesystem::path LMMS_EXPORT pathFromQString(const QString& path);

//! Converts an STL filesystem path to a QString path.
QString LMMS_EXPORT qStringFromPath(const std::filesystem::path& path);

} // namespace lmms::PathUtil

#endif // LMMS_PATHUTIL_H
124 changes: 124 additions & 0 deletions include/ResourceCache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* ResourceCache.h
*
* Copyright (c) 2025 Sotonye Atemie <[email protected]>
*
* 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_RESOURCE_CACHE_H
#define LMMS_RESOURCE_CACHE_H

#include <QCryptographicHash>
#include <array>
#include <filesystem>
#include <fstream>
#include <memory>
#include <unordered_map>

namespace lmms {
class ResourceCache
{
public:
static constexpr auto CacheSize = 32;

class Resource
{
int m_age = 0;
friend class ResourceCache;
};

template <typename V, typename K,
typename Enable
= std::enable_if_t<std::conjunction_v<std::is_base_of<Resource, V>, std::is_default_constructible<V>>>,
typename... Args>
static auto fetch(const K& key, Args&&... args) -> std::shared_ptr<const V>
{
const auto digest = hash(key);
if (digest.empty()) { return std::make_shared<V>(); }

auto& resource = s_resources[digest];

if (resource == nullptr)
{
if (s_resources.size() == CacheSize)
{
const auto it = std::min_element(s_resources.begin(), s_resources.end(),
[](const auto& first, const auto& second) { return first.second->m_age < first.second->m_age; });
s_resources.erase(it);
}

try
{
resource = std::make_shared<V>(key, std::forward<Args>(args)...);
}
catch (const std::runtime_error& error)
{
return std::make_shared<V>();
}

return std::static_pointer_cast<const V>(resource);
}

++resource->m_age;
return std::static_pointer_cast<const V>(resource);
}

static auto instance() -> ResourceCache&
{
static auto s_inst = ResourceCache{};
return s_inst;
}

private:
static auto hash(const std::filesystem::path& path) -> std::string
{
if (!std::filesystem::exists(path)) { return std::string{}; }

static constexpr auto blockSize = 8192;
static auto block = std::array<char, blockSize>{};
static auto hash = QCryptographicHash{QCryptographicHash::Md5};

auto fstream = std::fstream{path, std::ios::in | std::ios::binary};
if (!fstream.is_open()) { return std::string{}; }

hash.reset();

do
{
fstream.read(block.data(), blockSize);
hash.addData(block.data(), fstream.gcount());
} while (fstream.gcount() > 0);

return hash.result().toStdString();
}

static auto hash(const std::string& key) -> std::string
{
const auto data = QByteArray::fromStdString(key);
const auto hash = QCryptographicHash::hash(data, QCryptographicHash::Md5).toStdString();
return hash;
}

ResourceCache() { s_resources.reserve(CacheSize); }
inline static std::unordered_map<std::string, std::shared_ptr<Resource>> s_resources;
};
} // namespace lmms

#endif // LMMS_RESOURCE_CACHE_H
14 changes: 7 additions & 7 deletions include/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
#include <cmath>
#include <memory>

#include "AudioEngine.h"
#include "AudioResampler.h"
#include "Engine.h"
#include "Note.h"
#include "PathUtil.h"
#include "SampleBuffer.h"
#include "lmms_export.h"

Expand Down Expand Up @@ -90,14 +93,11 @@ class LMMS_EXPORT Sample
auto play(SampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency = DefaultBaseFreq,
Loop loopMode = Loop::Off) 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 sampleFile() const -> QString { return PathUtil::qStringFromPath(m_buffer->path()); }
auto sampleSize() const -> size_t { return m_buffer->size(); }
auto sampleRate() const -> int { return m_buffer->sampleRate(); }
auto sampleDuration() const -> std::chrono::milliseconds;

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

auto data() const -> const SampleFrame* { return m_buffer->data(); }
auto buffer() const -> std::shared_ptr<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); }
Expand All @@ -121,7 +121,7 @@ class LMMS_EXPORT Sample
void advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const;

private:
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
std::shared_ptr<const SampleBuffer> m_buffer = std::make_shared<SampleBuffer>();
std::atomic<int> m_startFrame = 0;
std::atomic<int> m_endFrame = 0;
std::atomic<int> m_loopStartFrame = 0;
Expand Down
69 changes: 22 additions & 47 deletions include/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,73 +25,48 @@
#ifndef LMMS_SAMPLE_BUFFER_H
#define LMMS_SAMPLE_BUFFER_H

#include <QByteArray>
#include <QString>
#include <memory>
#include <optional>
#include <samplerate.h>
#include <vector>

#include "AudioEngine.h"
#include "Engine.h"
#include "lmms_basics.h"
#include "ResourceCache.h"
#include "SampleFrame.h"
#include "lmms_export.h"

namespace lmms {
class LMMS_EXPORT SampleBuffer

class LMMS_EXPORT SampleBuffer : public ResourceCache::Resource
{
public:
using value_type = SampleFrame;
using reference = SampleFrame&;
using const_reference = const SampleFrame&;
using iterator = std::vector<SampleFrame>::iterator;
using const_iterator = std::vector<SampleFrame>::const_iterator;
using difference_type = std::vector<SampleFrame>::difference_type;
using size_type = std::vector<SampleFrame>::size_type;
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, 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; }

auto begin() -> iterator { return m_data.begin(); }
auto end() -> iterator { return m_data.end(); }
explicit SampleBuffer(const std::filesystem::path& path);
SampleBuffer(const std::string& base64, int sampleRate = Engine::audioEngine()->outputSampleRate());
SampleBuffer(const SampleFrame* buffer, size_t size, int sampleRate);
SampleBuffer(std::vector<SampleFrame> buffer, int sampleRate);

auto begin() const -> const_iterator { return m_data.begin(); }
auto end() const -> const_iterator { return m_data.end(); }
std::string toBase64() const;

auto cbegin() const -> const_iterator { return m_data.cbegin(); }
auto cend() const -> const_iterator { return m_data.cend(); }
int sampleRate() const { return m_sampleRate; }
void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; }

auto rbegin() -> reverse_iterator { return m_data.rbegin(); }
auto rend() -> reverse_iterator { return m_data.rend(); }
auto path() const -> const std::filesystem::path& { return m_path; }

auto rbegin() const -> const_reverse_iterator { return m_data.rbegin(); }
auto rend() const -> const_reverse_iterator { return m_data.rend(); }
auto data() const -> const std::vector<SampleFrame>& { return m_data; }
auto data() -> std::vector<SampleFrame>& { return m_data; }

auto crbegin() const -> const_reverse_iterator { return m_data.crbegin(); }
auto crend() const -> const_reverse_iterator { return m_data.crend(); }
auto size() const { return m_data.size(); }
auto empty() const { return m_data.empty(); }

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(); }
auto begin() const { return m_data.begin(); }
auto begin() { return m_data.begin(); }

static auto emptyBuffer() -> std::shared_ptr<const SampleBuffer>;
auto end() const { return m_data.end(); }
auto end() { return m_data.end(); }

private:
std::vector<SampleFrame> m_data;
QString m_audioFile;
sample_rate_t m_sampleRate = Engine::audioEngine()->outputSampleRate();
std::filesystem::path m_path;
int m_sampleRate = 0;
};

} // namespace lmms
Expand Down
2 changes: 1 addition & 1 deletion include/SampleClip.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class SampleClip : public Clip

void changeLength( const TimePos & _length ) override;
void changeLengthToSampleLength();
const QString& sampleFile() const;
QString sampleFile() const;
bool hasSampleFileLoaded(const QString & filename) const;

void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override;
Expand Down
2 changes: 1 addition & 1 deletion include/SampleClipView.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public slots:

private:
SampleClip * m_clip;
SampleThumbnail m_sampleThumbnail;
std::shared_ptr<const SampleThumbnail> m_sampleThumbnail;
QPixmap m_paintPixmap;
bool splitClip( const TimePos pos ) override;
} ;
Expand Down
20 changes: 6 additions & 14 deletions include/SampleLoader.h → include/SampleFilePicker.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* SampleLoader.h - Load audio and waveform files
* SampleFilePicker.h
*
* Copyright (c) 2023 saker <[email protected]>
* Copyright (c) 2024 saker
*
* This file is part of LMMS - https://lmms.io
*
Expand All @@ -22,27 +22,19 @@
*
*/

#ifndef LMMS_GUI_SAMPLE_LOADER_H
#define LMMS_GUI_SAMPLE_LOADER_H
#ifndef LMMS_GUI_SAMPLE_FILE_PICKER_H
#define LMMS_GUI_SAMPLE_FILE_PICKER_H

#include <QString>
#include <memory>

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

namespace lmms::gui {
class LMMS_EXPORT SampleLoader
class LMMS_EXPORT SampleFilePicker
{
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()->outputSampleRate());
private:
static void displayError(const QString& message);
};
} // namespace lmms::gui

#endif // LMMS_GUI_SAMPLE_LOADER_H
#endif // LMMS_GUI_SAMPLE_FILE_PICKER_H
Loading
Loading