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 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
55 changes: 55 additions & 0 deletions include/FileSystemHelpers.h
sakertooth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* FileSystemHelpers.h
*
* Copyright (c) 2024 saker
*
* 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_FILE_SYSTEM_HELPERS_H
#define LMMS_FILE_SYSTEM_HELPERS_H

#include <QString>
#include <filesystem>

namespace lmms {
class FileSystemHelpers
{
public:
static std::filesystem::path pathFromQString(const QString& path)
{
#ifdef _WIN32
return std::filesystem::path{path.toStdWString()};
#else
return std::filesystem::path{path.toStdString()};
#endif
}

static QString qStringFromPath(const std::filesystem::path& path)
{
#ifdef _WIN32
return QString::fromStdWString(path.generic_wstring());
#else
return QString::fromStdString(path.native());
#endif
}
};
} // namespace lmms

#endif // LMMS_FILE_SYSTEM_HELPERS_H
125 changes: 125 additions & 0 deletions include/SampleDatabase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* SampleDatabase.h
*
* Copyright (c) 2024 saker
*
* 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_DATABASE_H
#define LMMS_SAMPLE_DATABASE_H

#include <QString>
#include <filesystem>
#include <memory>
#include <unordered_map>

#include "SampleBuffer.h"

namespace lmms {
class SampleDatabase
{
public:
/**
Fetches a sample from the database through a path to an audio file,
and returns the stored buffer.

If `path` exists in the database, its last write time is checked with what is currently in the database. If
there is a mismatch, the sample is reloaded from disk, its entry in the database is updated, and the sample is
returned.

If `path` does not exist in the database, the sample is loaded from disk and
then returned.
*/
static auto fetch(const QString& path) -> std::shared_ptr<SampleBuffer>;

/**
Fetches a sample from the database through a Base64 string and a sample rate
and returns the stored buffer.

If an entry for a `base64` string with a certain `sampleRate` exists in the database, the stored sample is
returned. Otherwise, if it does not exist in the database, the sample is loaded and then returned.
*/
static auto fetch(const QString& base64, int sampleRate) -> std::shared_ptr<SampleBuffer>;

private:
struct AudioFileEntry
{
friend bool operator==(const AudioFileEntry& first, const AudioFileEntry& second) noexcept
{
return first.path == second.path && first.lastWriteTime == second.lastWriteTime;
}

std::filesystem::path path;
std::filesystem::file_time_type lastWriteTime;
};

struct Base64Entry
{
friend bool operator==(const Base64Entry& first, const Base64Entry& second) noexcept
{
return first.base64 == second.base64 && first.sampleRate == second.sampleRate;
}

std::string base64;
int sampleRate;
};

struct Hash
{
std::size_t operator()(const AudioFileEntry& entry) const noexcept
{
return std::filesystem::hash_value(entry.path);
}

std::size_t operator()(const Base64Entry& entry) const noexcept
{
return std::hash<std::string>()(entry.base64);
}
};

template <typename T, typename ...Args>
static auto get(const T& entry, std::unordered_map<T, std::weak_ptr<SampleBuffer>, Hash>& map, Args... args)
{
const auto it = map.find(entry);

if (it == map.end())
{
const auto buffer = std::make_shared<SampleBuffer>(std::forward<Args>(args)...);
map.insert(std::make_pair(entry, buffer));
return buffer;
}

const auto entryLock = it->second.lock();
if (!entryLock)
{
const auto buffer = std::make_shared<SampleBuffer>(std::forward<Args>(args)...);
map[entry] = buffer;
return buffer;
}

return entryLock;
}

inline static std::unordered_map<AudioFileEntry, std::weak_ptr<SampleBuffer>, Hash> s_audioFileMap;
inline static std::unordered_map<Base64Entry, std::weak_ptr<SampleBuffer>, Hash> s_base64Map;
sakertooth marked this conversation as resolved.
Show resolved Hide resolved
};
} // namespace lmms

#endif // LMMS_SAMPLE_DATABASE_H
4 changes: 2 additions & 2 deletions include/SampleLoader.h
sakertooth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class LMMS_EXPORT SampleLoader
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(
static std::shared_ptr<const SampleBuffer> loadBufferFromFile(const QString& filePath);
static std::shared_ptr<const SampleBuffer> loadBufferFromBase64(
const QString& base64, int sampleRate = Engine::audioEngine()->outputSampleRate());
private:
static void displayError(const QString& message);
Expand Down
4 changes: 2 additions & 2 deletions plugins/AudioFileProcessor/AudioFileProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ void AudioFileProcessor::loadSettings(const QDomElement& elem)
}
else if (auto sampleData = elem.attribute("sampledata"); !sampleData.isEmpty())
{
m_sample = Sample(gui::SampleLoader::createBufferFromBase64(sampleData));
m_sample = Sample(gui::SampleLoader::loadBufferFromBase64(sampleData));
}

m_loopModel.loadSettings(elem, "looped");
Expand Down Expand Up @@ -317,7 +317,7 @@ void AudioFileProcessor::setAudioFile(const QString& _audio_file, bool _rename)
}
// else we don't touch the track-name, because the user named it self

m_sample = Sample(gui::SampleLoader::createBufferFromFile(_audio_file));
m_sample = Sample(gui::SampleLoader::loadBufferFromFile(_audio_file));
loopPointChanged();
emit sampleUpdated();
}
Expand Down
6 changes: 3 additions & 3 deletions plugins/SlicerT/SlicerT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ std::vector<Note> SlicerT::getMidi()

void SlicerT::updateFile(QString file)
{
if (auto buffer = gui::SampleLoader::createBufferFromFile(file)) { m_originalSample = Sample(std::move(buffer)); }
if (auto buffer = gui::SampleLoader::loadBufferFromFile(file)) { m_originalSample = Sample(std::move(buffer)); }

findBPM();
findSlices();
Expand Down Expand Up @@ -359,7 +359,7 @@ void SlicerT::loadSettings(const QDomElement& element)
{
if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists())
{
auto buffer = gui::SampleLoader::createBufferFromFile(srcFile);
auto buffer = gui::SampleLoader::loadBufferFromFile(srcFile);
m_originalSample = Sample(std::move(buffer));
}
else
Expand All @@ -370,7 +370,7 @@ void SlicerT::loadSettings(const QDomElement& element)
}
else if (auto sampleData = element.attribute("sampledata"); !sampleData.isEmpty())
{
auto buffer = gui::SampleLoader::createBufferFromBase64(sampleData);
auto buffer = gui::SampleLoader::loadBufferFromBase64(sampleData);
m_originalSample = Sample(std::move(buffer));
}

Expand Down
4 changes: 2 additions & 2 deletions plugins/TripleOscillator/TripleOscillator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ void OscillatorObject::oscUserDefWaveDblClick()
auto af = gui::SampleLoader::openWaveformFile();
if( af != "" )
{
m_sampleBuffer = gui::SampleLoader::createBufferFromFile(af);
m_sampleBuffer = gui::SampleLoader::loadBufferFromFile(af);
m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(m_sampleBuffer.get());
// TODO:
//m_usrWaveBtn->setToolTip(m_sampleBuffer->audioFile());
Expand Down Expand Up @@ -287,7 +287,7 @@ void TripleOscillator::loadSettings( const QDomElement & _this )
{
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
{
m_osc[i]->m_sampleBuffer = gui::SampleLoader::createBufferFromFile(userWaveFile);
m_osc[i]->m_sampleBuffer = gui::SampleLoader::loadBufferFromFile(userWaveFile);
m_osc[i]->m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(m_osc[i]->m_sampleBuffer.get());
}
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ set(LMMS_SRCS
core/RenderManager.cpp
core/RingBuffer.cpp
core/Sample.cpp
core/SampleDatabase.cpp
core/SampleBuffer.cpp
core/SampleClip.cpp
core/SampleDecoder.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/core/EnvelopeAndLfoParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this )
{
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
{
m_userWave = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile"));
m_userWave = gui::SampleLoader::loadBufferFromFile(_this.attribute("userwavefile"));
}
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/LfoController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ void LfoController::loadSettings( const QDomElement & _this )
{
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
{
m_userDefSampleBuffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile"));
m_userDefSampleBuffer = gui::SampleLoader::loadBufferFromFile(_this.attribute("userwavefile"));
}
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/SampleClip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ void SampleClip::setSampleFile(const QString& sf)
if (!sf.isEmpty())
{
//Otherwise set it to the sample's length
m_sample = Sample(gui::SampleLoader::createBufferFromFile(sf));
m_sample = Sample(gui::SampleLoader::loadBufferFromFile(sf));
length = sampleLength();
}

Expand Down Expand Up @@ -309,7 +309,7 @@ void SampleClip::loadSettings( const QDomElement & _this )
auto sampleRate = _this.hasAttribute("sample_rate") ? _this.attribute("sample_rate").toInt() :
Engine::audioEngine()->outputSampleRate();

auto buffer = gui::SampleLoader::createBufferFromBase64(_this.attribute("data"), sampleRate);
auto buffer = gui::SampleLoader::loadBufferFromBase64(_this.attribute("data"), sampleRate);
m_sample = Sample(std::move(buffer));
}
changeLength( _this.attribute( "len" ).toInt() );
Expand Down
45 changes: 45 additions & 0 deletions src/core/SampleDatabase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SampleDatabase.cpp
*
* Copyright (c) 2024 saker
*
* 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 "SampleDatabase.h"

#include <filesystem>

#include "FileSystemHelpers.h"
#include "SampleBuffer.h"

namespace lmms {
auto SampleDatabase::fetch(const QString& path) -> std::shared_ptr<SampleBuffer>
{
const auto fsPath = FileSystemHelpers::pathFromQString(path);
const auto entry = AudioFileEntry{fsPath, std::filesystem::last_write_time(fsPath)};
return get(entry, s_audioFileMap, path);
}

auto SampleDatabase::fetch(const QString& base64, int sampleRate) -> std::shared_ptr<SampleBuffer>
{
const auto entry = Base64Entry{base64.toStdString(), sampleRate};
return get(entry, s_base64Map, base64, sampleRate);
}
} // namespace lmms
2 changes: 1 addition & 1 deletion src/gui/FileBrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file)
embed::getIconPixmap("sample_file", 24, 24), 0);
// TODO: this can be removed once we do this outside the event thread
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
if (auto buffer = SampleLoader::createBufferFromFile(fileName))
if (auto buffer = SampleLoader::loadBufferFromFile(fileName))
{
auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer)});
s->setDoneMayReturnTrue(false);
Expand Down
2 changes: 1 addition & 1 deletion src/gui/LfoControllerDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ void LfoControllerDialog::askUserDefWave()

auto lfoModel = dynamic_cast<LfoController*>(model());
auto& buffer = lfoModel->m_userDefSampleBuffer;
buffer = SampleLoader::createBufferFromFile(fileName);
buffer = SampleLoader::loadBufferFromFile(fileName);

m_userWaveBtn->setToolTip(buffer->audioFile());
}
Expand Down
Loading
Loading