Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
08e00a1
Experimental C++ version of `InputSource` and company
sbooth Nov 3, 2025
3588137
Return a value
sbooth Nov 3, 2025
9985e95
Fix destructors
sbooth Nov 3, 2025
34636bb
Correct test behavior for new input sources
sbooth Nov 3, 2025
d98d523
Use `override` instead of `virtual`
sbooth Nov 3, 2025
405684c
Change error code
sbooth Nov 3, 2025
be16b5c
Use `scope_exit` for `Close()`
sbooth Nov 3, 2025
a77b5b3
Tweak lambda capture lists
sbooth Nov 3, 2025
3d93af7
Add missing newline
sbooth Nov 3, 2025
10044c4
Add `noexcept` to dtors
sbooth Nov 4, 2025
c2f64e6
Add noexcept to dtor
sbooth Nov 4, 2025
dcb419b
Make `final`
sbooth Nov 4, 2025
e443d17
Simplify NULL checks
sbooth Nov 4, 2025
200fdd4
Replace concept with requires
sbooth Nov 4, 2025
7a32aab
Use `std::malloc` and `std::free`
sbooth Nov 4, 2025
87c7a76
Merge branch 'main' into c++-input
sbooth Nov 6, 2025
f96f065
Fix includes
sbooth Nov 6, 2025
0545919
Use uniform initialization
sbooth Nov 6, 2025
7ae8066
Merge branch 'main' into c++-input
sbooth Nov 11, 2025
4694e9b
Fix use of `CFRangeMake`
sbooth Nov 12, 2025
6b8144f
Actually read the file contents into memory
sbooth Nov 12, 2025
83062a8
Use `ftello` and `fseeko` for 64-bit support
sbooth Nov 12, 2025
e912e74
Verify input counts are within limits
sbooth Nov 12, 2025
cab5e38
Set `buf_` to null in _Close()
sbooth Nov 12, 2025
4186d30
Use const for scope_exit declarations
sbooth Nov 12, 2025
77b62f4
Minor cleanup
sbooth Nov 12, 2025
27f52ca
Fill out error on failure
sbooth Nov 12, 2025
5319297
Move member function definition to .cpp file
sbooth Nov 12, 2025
36d3a02
Refactor to make some functions inline
sbooth Nov 12, 2025
3bb6e33
Merge branch 'main' into c++-input
sbooth Nov 24, 2025
e700c88
Replace `std::expected` with exceptions
sbooth Nov 24, 2025
9170058
Add empty parameter clause to lambdas
sbooth Nov 24, 2025
b409d10
Rename functions
sbooth Nov 24, 2025
5d26f7d
Improve log messages
sbooth Nov 25, 2025
8b66472
Additional log messages
sbooth Nov 25, 2025
3a956e4
Merge branch 'main' into c++-input
sbooth Nov 26, 2025
031a3d9
Remove unused parameter names
sbooth Nov 26, 2025
83a6fe2
Improve log messages
sbooth Nov 26, 2025
67ca581
Move some function implementations to .cpp file
sbooth Nov 26, 2025
41c25dd
Reorganize
sbooth Nov 26, 2025
4689a9c
More cleanup
sbooth Nov 26, 2025
969672a
Add `BufferInput` class
sbooth Nov 26, 2025
03bffdd
Add `BufferAdoption`
sbooth Nov 26, 2025
7f3dee3
Add `BufferInput` support to `SFBInputSource`
sbooth Nov 26, 2025
8a0d46c
Refactor
sbooth Nov 26, 2025
8635356
Add description
sbooth Nov 26, 2025
da62256
Use `CopyDescription`
sbooth Nov 26, 2025
e01d806
Fix format strings
sbooth Nov 26, 2025
c484210
Fix format strings again
sbooth Nov 26, 2025
69844f7
Add `CopyDataWithLength`
sbooth Nov 26, 2025
f1c9fcf
Translate exceptions to NSError
sbooth Nov 26, 2025
7854478
Improve/eliminate numeric limit checks
sbooth Nov 26, 2025
7a37069
Add `SeekAnchor`, `ReadUnsigned<T>`, and `ReadSigned<T>`
sbooth Nov 26, 2025
f47f460
Add `ReadValue`
sbooth Nov 27, 2025
0777525
Add documentation
sbooth Nov 27, 2025
7898c49
Add default parameter to `SeekToOffset`
sbooth Nov 27, 2025
1b85a77
Remove unneeded parameter
sbooth Nov 27, 2025
a4360fe
Simplify creation
sbooth Nov 27, 2025
882dd88
Improve limit testing
sbooth Nov 27, 2025
6772055
Add `ReadBlock`
sbooth Nov 27, 2025
17f550f
vector::size_type is unsigned
sbooth Nov 27, 2025
d9fefbd
Fill out `error` when an exception is thrown
sbooth Nov 27, 2025
770ffe2
Add factory methods
sbooth Nov 27, 2025
0a6f9ba
Refactor buffer-based inputs
sbooth Nov 27, 2025
3261a50
Refactor seek logic
sbooth Nov 27, 2025
2b927de
Use `ReadValue`
sbooth Nov 27, 2025
4b8eae2
Remove include
sbooth Nov 27, 2025
4def4ff
Add TODO
sbooth Nov 27, 2025
b8addf5
Remove unneeded import
sbooth Nov 27, 2025
3b903a4
Improve comment
sbooth Nov 27, 2025
05581ce
Rename offset to position
sbooth Nov 27, 2025
3aa0e43
Check `IsOpen()` in `SupportsSeeking`
sbooth Nov 28, 2025
93245b3
Reformat
sbooth Nov 28, 2025
19e29b5
Merge branch 'main' into c++-input
sbooth Nov 30, 2025
eacb98c
Add nullability specifier
sbooth Nov 30, 2025
ff77cf1
Add `_Nonnull` and general cleanup
sbooth Dec 1, 2025
efa6368
Add seekability test
sbooth Dec 1, 2025
d0bc562
Merge branch 'main' into c++-input
sbooth Dec 1, 2025
550f3ca
Catch exceptions thrown by `SupportsSeeking()`
sbooth Dec 1, 2025
54f9349
Update formatting
sbooth Dec 1, 2025
43bc118
Merge branch 'main' into c++-input
sbooth Dec 10, 2025
a2f602e
Merge branch 'main' into c++-input
sbooth Dec 17, 2025
1b27347
Merge branch 'main' into c++-input
sbooth Dec 21, 2025
f8c03c5
Merge branch 'main' into c++-input
sbooth Dec 29, 2025
ac13af1
Remove `-dealloc`
sbooth Dec 29, 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
47 changes: 47 additions & 0 deletions Sources/CSFBAudioEngine/Input/BufferInput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#import <algorithm>
#import <cstdio>
#import <cstdlib>

#import "BufferInput.hpp"

SFB::BufferInput::BufferInput(const void *buf, int64_t len, BufferAdoption behavior)
: buf_{const_cast<void *>(buf)}, free_{behavior == BufferAdoption::copy || behavior == BufferAdoption::noCopyAndFree}, len_{len}
{
if(!buf || len < 0) {
os_log_error(sLog, "Cannot create BufferInput with null buffer or negative length");
throw std::invalid_argument("Null buffer or negative length");
}

if(behavior == BufferAdoption::copy) {
buf_ = std::malloc(len_);
if(!buf_)
throw std::bad_alloc();
std::memcpy(buf_, buf, len_);
}
}

SFB::BufferInput::~BufferInput() noexcept
{
if(free_)
std::free(buf_);
}

int64_t SFB::BufferInput::_Read(void *buffer, int64_t count)
{
const auto remaining = len_ - pos_;
count = std::min(count, remaining);
memcpy(buffer, reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(buf_) + pos_), count);
pos_ += count;
return count;
}

CFStringRef SFB::BufferInput::_CopyDescription() const noexcept
{
return CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("<BufferInput %p: %lld bytes at %p>"), this, len_, buf_);
}
54 changes: 54 additions & 0 deletions Sources/CSFBAudioEngine/Input/BufferInput.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#pragma once

#import "InputSource.hpp"

namespace SFB {

class BufferInput: public InputSource
{
public:
/// Buffer adoption behaviors.
enum class BufferAdoption { copy, noCopy, noCopyAndFree };
BufferInput(const void * _Nonnull buf, int64_t len, BufferAdoption behavior = BufferAdoption::copy);
~BufferInput() noexcept;

// This class is non-copyable.
BufferInput(const BufferInput&) = delete;
BufferInput(BufferInput&&) = delete;

// This class is non-assignable.
BufferInput& operator=(const BufferInput&) = delete;
BufferInput& operator=(BufferInput&&) = delete;

protected:
explicit BufferInput() noexcept = default;

/// The data buffer.
void * _Nonnull buf_ {nullptr};
/// Whether the buffer should be freed in the destructor.
bool free_ {false};
/// The length of the buffer in bytes.
int64_t len_ {0};
/// The current byte position in the buffer.
int64_t pos_ {0};

private:
void _Open() override { pos_ = 0; }
void _Close() override {}
bool _AtEOF() const noexcept override { return len_ == pos_; }
int64_t _Position() const noexcept override { return pos_; }
int64_t _Length() const noexcept override { return len_; }
bool _SupportsSeeking() const noexcept override { return true; }
void _SeekToPosition(int64_t position) override { pos_ = position; }

int64_t _Read(void * _Nonnull buffer, int64_t count) override;
CFStringRef _Nonnull _CopyDescription() const noexcept override;
};

} /* namespace SFB */
43 changes: 43 additions & 0 deletions Sources/CSFBAudioEngine/Input/DataInput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#import <algorithm>
#import <limits>

#import "DataInput.hpp"

SFB::DataInput::DataInput(CFDataRef data)
{
if(!data) {
os_log_error(sLog, "Cannot create DataInput with null data");
throw std::invalid_argument("Null data");
}
data_ = static_cast<CFDataRef>(CFRetain(data));
}

SFB::DataInput::~DataInput() noexcept
{
CFRelease(data_);
}

int64_t SFB::DataInput::_Read(void *buffer, int64_t count)
{
if(count > std::numeric_limits<CFIndex>::max()) {
os_log_error(sLog, "_Read() called on <DataInput: %p> with count greater than maximum allowable value", this);
throw std::invalid_argument("Count greater than maximum allowable value");
}
const int64_t remaining = CFDataGetLength(data_) - pos_;
count = std::min(count, remaining);
const auto range = CFRangeMake(pos_, count);
CFDataGetBytes(data_, range, static_cast<UInt8 *>(buffer));
pos_ += count;
return count;
}

CFStringRef SFB::DataInput::_CopyDescription() const noexcept
{
return CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("<DataInput %p: %@>"), this, data_);
}
43 changes: 43 additions & 0 deletions Sources/CSFBAudioEngine/Input/DataInput.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#pragma once

#import "InputSource.hpp"

namespace SFB {

class DataInput: public InputSource
{
public:
explicit DataInput(CFDataRef _Nonnull data);
~DataInput() noexcept;

// This class is non-copyable.
DataInput(const DataInput&) = delete;
DataInput(DataInput&&) = delete;

// This class is non-assignable.
DataInput& operator=(const DataInput&) = delete;
DataInput& operator=(DataInput&&) = delete;

private:
void _Open() noexcept override { pos_ = 0; }
void _Close() noexcept override {}
bool _AtEOF() const noexcept override { return CFDataGetLength(data_) == pos_; }
int64_t _Position() const noexcept override { return pos_; }
int64_t _Length() const noexcept override { return CFDataGetLength(data_); }
bool _SupportsSeeking() const noexcept override { return true; }
void _SeekToPosition(int64_t position) override { pos_ = position; }

int64_t _Read(void * _Nonnull buffer, int64_t count) override;
CFStringRef _Nonnull _CopyDescription() const noexcept override;

CFDataRef _Nonnull data_ {nullptr};
CFIndex pos_ {0};
};

} /* namespace SFB */
69 changes: 69 additions & 0 deletions Sources/CSFBAudioEngine/Input/FileContentsInput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#import <algorithm>
#import <cstdio>
#import <system_error>

#import <sys/stat.h>

#import "FileContentsInput.hpp"
#import "scope_exit.hpp"

SFB::FileContentsInput::FileContentsInput(CFURLRef url)
{
if(!url) {
os_log_error(sLog, "Cannot create FileContentsInput with null URL");
throw std::invalid_argument("Null URL");
}
url_ = static_cast<CFURLRef>(CFRetain(url));
free_ = true;
}

void SFB::FileContentsInput::_Open()
{
UInt8 path [PATH_MAX];
auto success = CFURLGetFileSystemRepresentation(url_, FALSE, path, PATH_MAX);
if(!success)
throw std::runtime_error("Unable to get URL file system representation");

auto file = std::fopen(reinterpret_cast<const char *>(path), "r");
if(!file)
throw std::system_error{errno, std::generic_category()};

// Ensure the file is closed
const auto guard = scope_exit{[&file]() noexcept { std::fclose(file); }};

auto fd = ::fileno(file);

struct stat s;
if(::fstat(fd, &s))
throw std::system_error{errno, std::generic_category()};

buf_ = std::malloc(s.st_size);
if(!buf_)
throw std::bad_alloc();

const auto nitems = std::fread(buf_, 1, s.st_size, file);
if(nitems != s.st_size && std::ferror(file))
throw std::system_error{errno, std::generic_category()};

len_ = nitems;
pos_ = 0;
}

void SFB::FileContentsInput::_Close() noexcept
{
std::free(buf_);
buf_ = nullptr;
}

CFStringRef SFB::FileContentsInput::_CopyDescription() const noexcept
{
CFStringRef lastPathComponent = CFURLCopyLastPathComponent(url_);
const auto guard = scope_exit{[&lastPathComponent]() noexcept { CFRelease(lastPathComponent); }};
return CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("<FileContentsInput %p: %lld bytes copied to %p from \"%@\">"), this, len_, buf_, lastPathComponent);
}
33 changes: 33 additions & 0 deletions Sources/CSFBAudioEngine/Input/FileContentsInput.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#pragma once

#import "BufferInput.hpp"

namespace SFB {

class FileContentsInput: public BufferInput
{
public:
explicit FileContentsInput(CFURLRef _Nonnull url);
~FileContentsInput() noexcept = default;

// This class is non-copyable.
FileContentsInput(const FileContentsInput&) = delete;
FileContentsInput(FileContentsInput&&) = delete;

// This class is non-assignable.
FileContentsInput& operator=(const FileContentsInput&) = delete;
FileContentsInput& operator=(FileContentsInput&&) = delete;

private:
void _Open() override;
void _Close() noexcept override;
CFStringRef _Nonnull _CopyDescription() const noexcept override;
};

} /* namespace SFB */
84 changes: 84 additions & 0 deletions Sources/CSFBAudioEngine/Input/FileInput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Copyright (c) 2010-2025 Stephen F. Booth <me@sbooth.org>
// Part of https://github.com/sbooth/SFBAudioEngine
// MIT license
//

#import <sys/stat.h>

#import "FileInput.hpp"
#import "scope_exit.hpp"

SFB::FileInput::FileInput(CFURLRef url)
{
if(!url) {
os_log_error(sLog, "Cannot create FileInput with null URL");
throw std::invalid_argument("Null URL");
}
url_ = static_cast<CFURLRef>(CFRetain(url));
}

SFB::FileInput::~FileInput() noexcept
{
if(file_)
std::fclose(file_);
}

void SFB::FileInput::_Open()
{
UInt8 path [PATH_MAX];
auto success = CFURLGetFileSystemRepresentation(url_, FALSE, path, PATH_MAX);
if(!success)
throw std::runtime_error("Unable to get URL file system representation");

file_ = std::fopen(reinterpret_cast<const char *>(path), "r");
if(!file_)
throw std::system_error{errno, std::generic_category()};

struct stat s;
if(::fstat(::fileno(file_), &s)) {
std::fclose(file_);
file_ = nullptr;
throw std::system_error{errno, std::generic_category()};
}

len_ = s.st_size;

// Regular files are always seekable
if(S_ISREG(s.st_mode))
seekable_ = true;
else if(const auto offset = ::ftello(file_); offset != -1) {
if(::fseeko(file_, offset, SEEK_SET) == 0)
seekable_ = true;
}
}

void SFB::FileInput::_Close()
{
const auto defer = scope_exit{[this]() noexcept { file_ = nullptr; }};
if(std::fclose(file_))
throw std::system_error{errno, std::generic_category()};
}

int64_t SFB::FileInput::_Read(void *buffer, int64_t count)
{
const auto nitems = std::fread(buffer, 1, count, file_);
if(nitems != count && std::ferror(file_))
throw std::system_error{errno, std::generic_category()};
return nitems;
}

int64_t SFB::FileInput::_Position() const
{
const auto offset = ::ftello(file_);
if(offset == -1)
throw std::system_error{errno, std::generic_category()};
return offset;
}

CFStringRef SFB::FileInput::_CopyDescription() const noexcept
{
CFStringRef lastPathComponent = CFURLCopyLastPathComponent(url_);
const auto guard = scope_exit{[&lastPathComponent]() noexcept { CFRelease(lastPathComponent); }};
return CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("<FileInput %p: \"%@\">"), this, lastPathComponent);
}
Loading