Skip to content

Commit

Permalink
Add AudioFile abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
sakertooth committed Feb 12, 2025
1 parent cf0a04c commit 92da0f1
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 118 deletions.
151 changes: 151 additions & 0 deletions include/AudioFile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* AudioFile.h - abstraction for audio files on the filesystem
*
* 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_AUDIO_FILE_H
#define LMMS_AUDIO_FILE_H

#include <filesystem>
#include <sndfile.h>
#include <vector>

#include "AudioEngine.h"
#include "Engine.h"
#include "lmms_basics.h"

namespace lmms {

class SampleFrame;

class AudioFile
{
public:
enum class Mode
{
Read,
Write,
ReadAndWrite
};

struct Type
{
std::string name;
std::string extension;
};

AudioFile(const std::filesystem::path& path, Mode mode);
~AudioFile() { m_codec->close(); }

AudioFile(const AudioFile&) = delete;
AudioFile& operator=(const AudioFile&) = delete;

AudioFile(AudioFile&&) noexcept;
AudioFile& operator=(AudioFile&&) noexcept;

auto read(SampleFrame* dst, std::size_t size) const -> std::size_t { return m_codec->read(dst, size); }
auto write(const SampleFrame* src, std::size_t size) -> std::size_t { return m_codec->write(src, size); }

auto seek(std::size_t offset, int whence) -> std::size_t { return m_codec->seek(offset, whence); }

auto frames() const -> std::size_t { return m_codec->frames(); }
auto sampleRate() const -> int { return m_codec->sampleRate(); }

auto path() const -> const std::filesystem::path& { return m_path; }
auto bytes() const -> std::size_t { return std::filesystem::file_size(m_path); }

static auto supportedTypes() -> std::vector<Type>;

private:
class Codec
{
public:
virtual ~Codec() = default;

virtual auto open(const std::filesystem::path& path, AudioFile::Mode mode) -> bool = 0;
virtual void close() = 0;

virtual auto read(SampleFrame* dst, std::size_t size) -> std::size_t = 0;
virtual auto write(const SampleFrame* src, std::size_t size) -> std::size_t = 0;

virtual auto seek(std::size_t offset, int whence) -> std::size_t = 0;

virtual auto frames() const -> std::size_t = 0;
virtual auto sampleRate() const -> int = 0;

virtual auto supportedTypes() -> std::vector<AudioFile::Type> = 0;
};

class LibSndFileCodec : public Codec
{
public:
auto open(const std::filesystem::path& path, AudioFile::Mode mode) -> bool override;
void close() override;

auto read(SampleFrame* dst, std::size_t size) -> std::size_t override;
auto write(const SampleFrame* src, std::size_t size) -> std::size_t override;

auto seek(std::size_t offset, int whence) -> std::size_t override;

auto frames() const -> std::size_t override { return m_sfInfo.frames; }
auto sampleRate() const -> int override { return m_sfInfo.samplerate; }

auto supportedTypes() -> std::vector<AudioFile::Type> override;

private:
static auto sndfileMode(AudioFile::Mode) -> int;
SNDFILE* m_sndfile = nullptr;
SF_INFO m_sfInfo;
};

class DrumSynthCodec : public Codec
{
public:
auto open(const std::filesystem::path& path, AudioFile::Mode mode) -> bool override;
void close() override {}

auto read(SampleFrame* dst, std::size_t size) -> std::size_t override;
auto write(const SampleFrame* src, std::size_t size) -> std::size_t override;

auto seek(std::size_t offset, int whence) -> std::size_t override;

auto frames() const -> std::size_t override { return m_size / DEFAULT_CHANNELS; }
auto sampleRate() const -> int override { return Engine::audioEngine()->outputSampleRate(); }

auto supportedTypes() -> std::vector<AudioFile::Type> override
{
static auto type = std::vector<Type>{Type{"DrumSynth", "*.ds"}};
return type;
}

private:
std::unique_ptr<int16_t[]> m_ptr; // NOLINT
std::size_t m_pos = 0;
std::size_t m_size = 0;
};

std::filesystem::path m_path;
std::unique_ptr<Codec> m_codec;
};
} // namespace lmms

#endif // LMMS_AUDIO_FILE_H
6 changes: 4 additions & 2 deletions include/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

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

Expand Down Expand Up @@ -81,7 +82,7 @@ class LMMS_EXPORT Sample
Sample(const SampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate());
Sample(const Sample& other);
Sample(Sample&& other);
explicit Sample(const QString& audioFile);
explicit Sample(const QString& path);
explicit Sample(std::shared_ptr<const SampleBuffer> buffer);

auto operator=(const Sample&) -> Sample&;
Expand All @@ -91,7 +92,7 @@ class LMMS_EXPORT Sample
Loop loopMode = Loop::Off) const -> bool;

auto sampleDuration() const -> std::chrono::milliseconds;
auto sampleFile() const -> const QString& { return m_buffer->audioFile(); }
auto sampleFile() const -> QString { return m_buffer->audioFile(); }
auto sampleRate() const -> int { return m_buffer->sampleRate(); }
auto sampleSize() const -> size_t { return m_buffer->size(); }

Expand Down Expand Up @@ -121,6 +122,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::atomic<int> m_startFrame = 0;
std::atomic<int> m_endFrame = 0;
Expand Down
15 changes: 7 additions & 8 deletions include/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@

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

#include "AudioEngine.h"
#include "Engine.h"
#include "PathUtil.h"
#include "lmms_basics.h"
#include "lmms_export.h"

Expand All @@ -52,16 +53,14 @@ class LMMS_EXPORT SampleBuffer
using const_reverse_iterator = std::vector<SampleFrame>::const_reverse_iterator;

SampleBuffer() = default;
explicit SampleBuffer(const QString& audioFile);
SampleBuffer(const QString& base64, int sampleRate);
SampleBuffer(const QString& path);
SampleBuffer(const SampleFrame* src, std::size_t size, int sampleRate);
SampleBuffer(std::vector<SampleFrame> data, int sampleRate);
SampleBuffer(
const SampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate());
SampleBuffer(const QString& base64, int sampleRate);

friend void swap(SampleBuffer& first, SampleBuffer& second) noexcept;
auto toBase64() const -> QString;

auto audioFile() const -> const QString& { return m_audioFile; }
auto audioFile() const -> QString { return PathUtil::qStringFromPath(m_path); }
auto sampleRate() const -> sample_rate_t { return m_sampleRate; }

auto begin() -> iterator { return m_data.begin(); }
Expand Down Expand Up @@ -90,7 +89,7 @@ class LMMS_EXPORT SampleBuffer

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

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
58 changes: 0 additions & 58 deletions include/SampleDecoder.h

This file was deleted.

6 changes: 3 additions & 3 deletions include/SampleStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
#ifndef LMMS_SAMPLE_STREAM_H
#define LMMS_SAMPLE_STREAM_H

#include <filesystem>
#include <future>
#include <vector>
#include "AudioFile.h"

namespace lmms {

Expand All @@ -38,14 +38,14 @@ class SampleStream
{
public:
/**
Creates a new sample stream object that streams data from the given sample decoder in a way that is suitable
Creates a new sample stream object that streams data from the given audio file at `path` in a way that is suitable
for real-time playback.
`size` specifies the maximum number of sample frames the stream can hold at once.
The sample stream delegates disk reading to a dedicated thread running on the `ThreadPool`.
**/
SampleStream(const std::filesystem::path& path, std::size_t size);
SampleStream(const QString& path, std::size_t size);

//! Stops the sample stream and its dedicated disk streaming thread.
~SampleStream();
Expand Down
Loading

0 comments on commit 92da0f1

Please sign in to comment.