Audio files
Keep file work on a control or background thread. Capability queries, preflight, open/close, probing, hashing, decoding, seeking, and writing can allocate or block. None belongs in an audio callback.
Control-thread sequence
Section titled “Control-thread sequence”- Call
getAudioFileCapabilities(). Iffile_io_availableis false, stop and report that the package has no usable file provider. The codec rows describe SDK policy; useprobeAudioFile()to determine whether a particular file is accepted. - For output, fill an
AudioFileWriterConfigand callpreflightAudioFileWrite()before creating a destination. The documented procedure uses WAV, 48 kHz, one channel, andAudioSampleFormat::Int16. - Call
createAudioFileWriter()and check for a null pointer. Callopen(path, config)and checkSessionGraphError. The parent directory must already exist. Pass interleaved float samples towriteSamples(buffer, frames), check itsResult<size_t>, then callclose()to flush data and finalize the header. - Call
probeAudioFile(path)when you need metadata without decoding. Check theResult; a missing, unreadable, or corrupt file is an error, not a reason to guess the format. - Call
createAudioFileReader()and check for null. Callopen(path)on the control/background thread, then inspect sample rate, channel count, frame count, format, and bit depth. Allocate a caller-owned interleaved float buffer and callreadSamples(). The result may contain fewer frames at end-of-file; zero frames means EOF. - Close the reader on the control/background thread. Remove temporary output only after the writer is closed and all reads/checks have finished.
IAudioFileReader::open() and close() are explicitly not audio-thread operations. A prepared or worker-fed PCM source can be handed to transport; the callback must not reopen, seek, probe, or write a file.
Writer formats and outcomes
Section titled “Writer formats and outcomes”The writer accepts WAV, AIFF, and FLAC. WAV and AIFF accept Int16, Int24, and Float32; FLAC accepts Int16 and Int24, and rejects Float32. MP3 and OGG are not supported containers. preflightAudioFileWrite() returns:
OKwhen the exact rate/channel/container/encoding tuple is accepted.InvalidParameterfor invalid rate or channel counts, an invalid encoding, or backend rejection.NotSupportedfor an unsupported container.NotReadywhen the provider is unavailable.
Successful preflight does not guarantee that the destination can be created: open() can still return InternalError for a missing parent directory, permissions, or another filesystem failure. With libsndfile, FLAC sample rates above 655350 Hz are rejected during preflight. Integer output clips input floats outside [-1, 1].
Evidence example
Section titled “Evidence example”The installed-package example writes four mono PCM16 WAV frames through the required libsndfile provider, closes the writer, probes and reopens the file, checks 48 kHz/mono/four-frame/16-bit metadata, reads the frames, compares the decoded samples, and removes the temporary file:
b8c8b87540fe585b5c7d5c8f25d193c1a1af93d5// SPDX-License-Identifier: MIT
#include <treefall/audio_file_capabilities.h>
#include <treefall/audio_file_reader.h>
#include <treefall/audio_file_writer.h>
#include <array>
#include <cmath>
#include <filesystem>
#include <iostream>
#include <stdexcept>
namespace {
void require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
} // namespace
int main() {
const auto path = std::filesystem::temp_directory_path() / "treefall-sdk-audio-file-smoke.wav";
try {
const auto capabilities = orpheus::getAudioFileCapabilities();
require(capabilities.file_io_available, "audio file I/O capability is unavailable");
constexpr orpheus::AudioFileWriterConfig config{
.format = orpheus::AudioFileFormat::WAV,
.sample_rate = 48000,
.num_channels = 1,
.sample_format = orpheus::AudioSampleFormat::Int16,
};
require(orpheus::preflightAudioFileWrite(config) == orpheus::SessionGraphError::OK,
"WAV PCM16 preflight failed");
const std::array<float, 4> samples{0.0f, 0.25f, -0.5f, 1.0f};
auto writer = orpheus::createAudioFileWriter();
require(writer != nullptr, "audio writer is unavailable");
require(writer->open(path.string(), config) == orpheus::SessionGraphError::OK,
"audio writer open failed");
const auto written = writer->writeSamples(samples.data(), samples.size());
require(written.isOk() && *written == samples.size(), "audio write frame count mismatch");
require(writer->close() == orpheus::SessionGraphError::OK, "audio writer close failed");
require(writer->getFramesWritten() == 4, "audio writer metadata frame count mismatch");
const auto probe = orpheus::probeAudioFile(path.string());
require(probe.isOk() && probe.value.format == orpheus::AudioFileFormat::WAV &&
probe.value.sample_rate == 48000 && probe.value.num_channels == 1 &&
probe.value.duration_samples == 4 && probe.value.bit_depth == 16,
"audio probe metadata mismatch");
auto reader = orpheus::createAudioFileReader();
require(reader != nullptr, "audio reader is unavailable");
const auto opened = reader->open(path.string());
require(opened.isOk() && opened.value.format == orpheus::AudioFileFormat::WAV &&
opened.value.sample_rate == 48000 && opened.value.num_channels == 1 &&
opened.value.duration_samples == 4 && opened.value.bit_depth == 16,
"audio reader metadata mismatch");
std::array<float, 4> decoded{};
const auto read = reader->readSamples(decoded.data(), decoded.size());
require(read.isOk() && *read == 4, "audio read frame count mismatch");
for (std::size_t index = 0; index < decoded.size(); ++index) {
require(std::abs(decoded[index] - samples[index]) < 1.0e-4f, "audio sample mismatch");
}
reader->close();
std::filesystem::remove(path);
std::cout << "Treefall libsndfile WAV smoke: 4 frames\n";
return 0;
} catch (const std::exception& error) {
std::error_code ignored;
std::filesystem::remove(path, ignored);
std::cerr << "audio file smoke failed: " << error.what() << '\n';
return 1;
}
}Treefall libsndfile WAV smoke: 4 frames
Source and producer-reported verification
- Source-relative path
examples/audio-file-smoke.cpp- SHA-256
d743e5183d94c79363ad4cd191745563310427898941f03103348bb2f73db20b- Producer-reported verification
- executed
- Environment
- os=darwin/arm64; cmake=cmake version 4.1.0; compiler=Apple clang version 21.0.0 (clang-2100.0.123.102)
- Command
./examples-build/audio-file-smoke
This is a narrow executable check of one provider and tuple, not coverage of every codec or provider. If capability discovery is false, a factory returns null, or an operation returns NotReady, report the unavailable provider and stop. Do not silently substitute another provider.
Metadata and preparation boundaries
Section titled “Metadata and preparation boundaries”probeAudioFile() is a header-only operation: successful metadata has an empty file_hash_sha256. IAudioFileReader::open() may calculate the file’s SHA-256 and returns it in metadata. readSamples() returns interleaved frames, while transport playback should consume prepared data rather than issue file calls from the callback.
For a deterministic installed-package composition combining reader, transport, and writer, see offline rendering. The public declarations are in the capability header, reader header, and writer header. The installed composition is exercised by the offline package example.