78 [[nodiscard]]
bool openRead(
const std::filesystem::path& path)
override
81 file_.open(path, std::ios::binary | std::ios::in);
82 if (!file_.is_open())
return false;
127 file_.open(path, std::ios::binary | std::ios::out | std::ios::trunc);
128 if (!file_.is_open())
return false;
132 totalFramesWritten_ = 0;
151 if (mode_ == Mode::Read && file_.is_open() && info_.
numSamples == 0)
157 int64_t startFrame, int64_t numFrames)
override
159 if (mode_ != Mode::Read || !file_.is_open())
return false;
160 if (startFrame < 0 || numFrames <= 0)
return false;
161 if (startFrame + numFrames > info_.
numSamples)
return false;
166 auto seekPos = dataChunkOffset_ +
static_cast<std::streamoff
>(startFrame * frameSize);
167 file_.seekg(seekPos, std::ios::beg);
168 if (!file_.good())
return false;
171 int64_t framesRemaining = std::min(numFrames,
static_cast<int64_t
>(dest.
getNumSamples()));
172 int64_t destOffset = 0;
174 while (framesRemaining > 0)
176 const auto toRead = std::min<int64_t>(framesRemaining, kChunkFrames);
177 const auto rawBytes =
static_cast<std::streamsize
>(toRead * frameSize);
179 file_.read(
reinterpret_cast<char*
>(ioBuffer_.data()), rawBytes);
180 if (file_.gcount() != rawBytes)
return false;
182 deinterleave(ioBuffer_.data(), dest, nCh,
static_cast<int>(toRead),
static_cast<int>(destOffset));
184 destOffset += toRead;
185 framesRemaining -= toRead;
193 if (mode_ != Mode::Write || !file_.is_open())
return false;
195 const int nCh =
static_cast<int>(info_.
numChannels);
198 const int frameSize =
static_cast<int>(info_.
bitsPerSample / 8) * nCh;
202 const int64_t dataAfter = (totalFramesWritten_ + nS) *
static_cast<int64_t
>(frameSize);
203 if (dataAfter >
static_cast<int64_t
>(0xFFFFFFFFu) - 128)
return false;
205 int framesRemaining = nS;
208 while (framesRemaining > 0)
210 const int toWrite = std::min(framesRemaining, kChunkFrames);
212 interleave(src, ioBuffer_.data(), nCh, srcCh, toWrite, srcOffset);
214 auto rawBytes =
static_cast<std::streamsize
>(
static_cast<int64_t
>(toWrite) * frameSize);
215 file_.write(
reinterpret_cast<const char*
>(ioBuffer_.data()), rawBytes);
216 if (!file_.good())
return false;
218 srcOffset += toWrite;
219 framesRemaining -= toWrite;
222 totalFramesWritten_ += nS;
228 if (!file_.is_open()) { mode_ = Mode::Closed; info_ = {};
return; }
230 if (mode_ == Mode::Write)
235 ioBuffer_.shrink_to_fit();
236 mode_ = Mode::Closed;
240 [[nodiscard]]
bool isOpen() const noexcept
override
242 return file_.is_open() && mode_ != Mode::Closed;
248 static constexpr uint16_t kFormatPCM = 1;
249 static constexpr uint16_t kFormatIEEEFloat = 3;
250 static constexpr uint16_t kFormatExtensible = 0xFFFE;
251 static constexpr int kChunkFrames = 8192;
252 static constexpr uint32_t kMaxChannels = 64;
254 enum class Mode { Closed, Read, Write };
257 Mode mode_ = Mode::Closed;
258 AudioFileInfo info_ {};
259 std::vector<uint8_t> ioBuffer_;
261 std::streamoff dataChunkOffset_ = 0;
262 std::streamoff dataChunkSizeOffset_ = 0;
263 uint32_t dataChunkSize_ = 0;
264 int64_t totalFramesWritten_ = 0;
266 void resizeIoBuffer()
268 const size_t bytesNeeded =
static_cast<size_t>(kChunkFrames)
270 if (ioBuffer_.size() < bytesNeeded)
271 ioBuffer_.resize(bytesNeeded);
279 if (!readBytes(riffId, 4))
return false;
280 if (std::memcmp(riffId,
"RIFF", 4) != 0)
return false;
282 uint32_t riffSize = 0;
283 if (!readLE32(riffSize))
return false;
287 if (!readBytes(waveId, 4))
return false;
288 if (std::memcmp(waveId,
"WAVE", 4) != 0)
return false;
290 bool hasFmt =
false, hasData =
false;
292 while (file_.good() && !(hasFmt && hasData))
295 uint32_t chunkSize = 0;
296 if (!readBytes(chunkId, 4))
break;
297 if (!readLE32(chunkSize))
break;
303 const std::streamoff pad = (chunkSize & 1);
305 if (std::memcmp(chunkId,
"fmt ", 4) == 0)
307 if (!parseFmtChunk(chunkSize))
return false;
308 if (pad) file_.seekg(pad, std::ios::cur);
311 else if (std::memcmp(chunkId,
"data", 4) == 0)
313 auto currentPos = file_.tellg();
314 file_.seekg(0, std::ios::end);
315 auto fileEnd = file_.tellg();
316 file_.seekg(currentPos);
318 auto remaining = fileEnd - currentPos;
319 if (
static_cast<std::streamoff
>(chunkSize) > remaining)
320 chunkSize =
static_cast<uint32_t
>(remaining);
322 dataChunkOffset_ = file_.tellg();
323 dataChunkSize_ = chunkSize;
328 file_.seekg(
static_cast<std::streamoff
>(chunkSize) + pad, std::ios::cur);
333 file_.seekg(
static_cast<std::streamoff
>(chunkSize) + pad, std::ios::cur);
340 if (hasFmt && hasData)
343 if (bytesPerFrame > 0)
344 info_.
numSamples =
static_cast<int64_t
>(dataChunkSize_) / bytesPerFrame;
347 return hasFmt && hasData;
350 bool parseFmtChunk(uint32_t chunkSize)
352 if (chunkSize < 16)
return false;
354 uint16_t audioFormat = 0;
355 uint16_t numChannels = 0;
356 uint32_t sampleRate = 0;
357 uint32_t byteRate = 0;
358 uint16_t blockAlign = 0;
359 uint16_t bitsPerSample = 0;
361 if (!readLE16(audioFormat))
return false;
362 if (!readLE16(numChannels))
return false;
363 if (!readLE32(sampleRate))
return false;
364 if (!readLE32(byteRate))
return false;
366 if (!readLE16(blockAlign))
return false;
368 if (!readLE16(bitsPerSample))
return false;
370 if (audioFormat == kFormatExtensible && chunkSize >= 40)
381 uint16_t validBitsPerSample = 0;
382 readLE16(validBitsPerSample);
383 if (validBitsPerSample > bitsPerSample)
return false;
385 uint32_t channelMask = 0;
386 readLE32(channelMask);
389 uint16_t subFormat = 0;
390 if (!readLE16(subFormat))
return false;
391 audioFormat = subFormat;
393 file_.seekg(14, std::ios::cur);
398 file_.seekg(
static_cast<std::streamoff
>(chunkSize - 40), std::ios::cur);
400 else if (chunkSize > 16)
402 auto skip =
static_cast<std::streamoff
>(chunkSize - 16);
403 file_.seekg(skip, std::ios::cur);
406 info_.
sampleRate =
static_cast<double>(sampleRate);
411 if (audioFormat != kFormatPCM && audioFormat != kFormatIEEEFloat)
return false;
412 if (sampleRate == 0)
return false;
413 if (numChannels == 0 || numChannels > kMaxChannels)
return false;
416 if (bitsPerSample != 32 && bitsPerSample != 64)
return false;
418 else if (bitsPerSample != 8 && bitsPerSample != 16 &&
419 bitsPerSample != 24 && bitsPerSample != 32)
434 const uint16_t formatTag = getWriteFormatTag();
435 const bool extensible = (formatTag == kFormatExtensible);
436 const uint32_t fmtChunkSize = extensible ? 40u : 16u;
438 const int bytesPerSample =
static_cast<int>(info_.
bitsPerSample / 8);
439 const auto blockAlign =
static_cast<uint16_t
>(info_.
numChannels * bytesPerSample);
440 const auto rate =
static_cast<uint32_t
>(std::llround(info_.
sampleRate));
441 const uint64_t byteRate64 =
static_cast<uint64_t
>(rate) * blockAlign;
442 const auto byteRate =
static_cast<uint32_t
>(
443 std::min<uint64_t>(byteRate64, 0xFFFFFFFFu));
445 writeBytes(
"RIFF", 4);
447 writeBytes(
"WAVE", 4);
449 writeBytes(
"fmt ", 4);
450 writeLE32(fmtChunkSize);
451 writeLE16(formatTag);
452 writeLE16(
static_cast<uint16_t
>(info_.
numChannels));
455 writeLE16(blockAlign);
464 uint16_t subFormat = info_.
isFloatingPoint ? kFormatIEEEFloat : kFormatPCM;
465 writeLE16(subFormat);
466 static constexpr uint8_t kGuidSuffix[14] = {
467 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00,
468 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71
470 writeBytes(
reinterpret_cast<const char*
>(kGuidSuffix), 14);
473 writeBytes(
"data", 4);
474 dataChunkSizeOffset_ = file_.tellp();
477 dataChunkOffset_ = file_.tellp();
481 void finaliseHeader()
483 if (!file_.is_open())
return;
490 const int bytesPerSample =
static_cast<int>(info_.
bitsPerSample / 8);
491 const auto dataSize =
static_cast<uint32_t
>(
492 totalFramesWritten_ * info_.
numChannels * bytesPerSample);
494 file_.seekp(dataChunkSizeOffset_, std::ios::beg);
497 file_.seekp(0, std::ios::end);
498 auto fileSize =
static_cast<uint32_t
>(file_.tellp());
499 uint32_t riffSize = fileSize - 8;
500 file_.seekp(4, std::ios::beg);
506 [[nodiscard]] uint16_t getWriteFormatTag() const noexcept
515 void deinterleave(
const uint8_t* raw, AudioBufferView<float>& dest,
int nCh,
int numFrames,
int destOffset)
const
518 if (info_.
bitsPerSample == 32) deinterleaveImpl<float, 32>(raw, dest, nCh, numFrames, destOffset);
519 else deinterleaveImpl<double, 64>(raw, dest, nCh, numFrames, destOffset);
522 case 8: deinterleaveImpl<uint8_t, 8>(raw, dest, nCh, numFrames, destOffset);
break;
523 case 16: deinterleaveImpl<int16_t, 16>(raw, dest, nCh, numFrames, destOffset);
break;
524 case 24: deinterleaveImpl<int32_t, 24>(raw, dest, nCh, numFrames, destOffset);
break;
525 case 32: deinterleaveImpl<int32_t, 32>(raw, dest, nCh, numFrames, destOffset);
break;
530 template <
typename T,
int Bits>
531 void deinterleaveImpl(
const uint8_t* raw, AudioBufferView<float>& dest,
int nCh,
int numFrames,
int destOffset)
const
533 const int bytesPerSample = Bits / 8;
534 const int stride =
static_cast<int>(info_.
numChannels) * bytesPerSample;
539 for (
int ch = 0; ch < nCh; ++ch)
541 float* out = dest.getChannel(ch) + destOffset;
542 const uint8_t* channelRaw = raw + (ch * bytesPerSample);
544 for (
int f = 0; f < numFrames; ++f)
546 const uint8_t* ptr = channelRaw + (f * stride);
548 if constexpr (Bits == 8) {
549 out[f] = (
static_cast<float>(ptr[0]) - 128.0f) * (1.0f / 128.0f);
551 else if constexpr (Bits == 16) {
552 auto val =
static_cast<int16_t
>(ptr[0] | (ptr[1] << 8));
553 out[f] =
static_cast<float>(val) * (1.0f / 32768.0f);
555 else if constexpr (Bits == 24) {
556 int32_t val = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16);
557 if (val & 0x800000) val |= 0xFF000000;
558 out[f] =
static_cast<float>(val) * (1.0f / 8388608.0f);
560 else if constexpr (Bits == 32 && std::is_same_v<T, int32_t>) {
563 int32_t val =
static_cast<int32_t
>(
564 static_cast<uint32_t
>(ptr[0])
565 | (
static_cast<uint32_t
>(ptr[1]) << 8)
566 | (
static_cast<uint32_t
>(ptr[2]) << 16)
567 | (
static_cast<uint32_t
>(ptr[3]) << 24));
568 out[f] =
static_cast<float>(val) * (1.0f / 2147483648.0f);
570 else if constexpr (Bits == 32 && std::is_same_v<T, float>) {
571 float v; std::memcpy(&v, ptr, 4); out[f] = v;
573 else if constexpr (Bits == 64) {
574 double v; std::memcpy(&v, ptr, 8); out[f] =
static_cast<float>(v);
582 void interleave(AudioBufferView<const float> src, uint8_t* raw,
583 int fileCh,
int srcCh,
int numFrames,
int srcOffset)
const
586 if (info_.
bitsPerSample == 32) interleaveImpl<float, 32>(src, raw, fileCh, srcCh, numFrames, srcOffset);
587 else interleaveImpl<double, 64>(src, raw, fileCh, srcCh, numFrames, srcOffset);
590 case 8: interleaveImpl<uint8_t, 8>(src, raw, fileCh, srcCh, numFrames, srcOffset);
break;
591 case 16: interleaveImpl<int16_t, 16>(src, raw, fileCh, srcCh, numFrames, srcOffset);
break;
592 case 24: interleaveImpl<int32_t, 24>(src, raw, fileCh, srcCh, numFrames, srcOffset);
break;
593 case 32: interleaveImpl<int32_t, 32>(src, raw, fileCh, srcCh, numFrames, srcOffset);
break;
598 template <
typename T,
int Bits>
599 void interleaveImpl(AudioBufferView<const float> src, uint8_t* raw,
600 int fileCh,
int srcCh,
int numFrames,
int srcOffset)
const
602 const int bytesPerSample = Bits / 8;
607 for (
int f = 0; f < numFrames; ++f)
609 for (
int ch = 0; ch < fileCh; ++ch)
611 const float value = (ch < srcCh) ? src.getChannel(ch)[srcOffset + f] : 0.0f;
612 uint8_t* ptr = raw + ((f * fileCh + ch) * bytesPerSample);
615 if constexpr (Bits == 8) {
616 auto val =
static_cast<uint8_t
>(std::clamp(std::round(value * 127.0f) + 128.0f, 0.0f, 255.0f));
619 else if constexpr (Bits == 16) {
620 auto val =
static_cast<int16_t
>(std::clamp(std::round(value * 32768.0f), -32768.0f, 32767.0f));
621 ptr[0] = val & 0xFF; ptr[1] = (val >> 8) & 0xFF;
623 else if constexpr (Bits == 24) {
624 auto val =
static_cast<int32_t
>(std::clamp(std::round(value * 8388608.0f), -8388608.0f, 8388607.0f));
625 ptr[0] = val & 0xFF; ptr[1] = (val >> 8) & 0xFF; ptr[2] = (val >> 16) & 0xFF;
627 else if constexpr (Bits == 32 && std::is_same_v<T, int32_t>) {
628 auto val =
static_cast<int32_t
>(std::clamp(std::round(
static_cast<double>(value) * 2147483648.0), -2147483648.0, 2147483647.0));
629 ptr[0] = val & 0xFF; ptr[1] = (val >> 8) & 0xFF; ptr[2] = (val >> 16) & 0xFF; ptr[3] = (val >> 24) & 0xFF;
631 else if constexpr (Bits == 32 && std::is_same_v<T, float>) {
632 std::memcpy(ptr, &value, 4);
634 else if constexpr (Bits == 64) {
635 double d =
static_cast<double>(value);
636 std::memcpy(ptr, &d, 8);
644 bool readBytes(
char* buf,
int n)
647 return file_.gcount() == n;
650 bool readLE16(uint16_t& val)
653 file_.read(
reinterpret_cast<char*
>(b), 2);
654 if (file_.gcount() != 2)
return false;
655 val =
static_cast<uint16_t
>(b[0]) | (
static_cast<uint16_t
>(b[1]) << 8);
659 bool readLE32(uint32_t& val)
662 file_.read(
reinterpret_cast<char*
>(b), 4);
663 if (file_.gcount() != 4)
return false;
664 val =
static_cast<uint32_t
>(b[0])
665 | (
static_cast<uint32_t
>(b[1]) << 8)
666 | (
static_cast<uint32_t
>(b[2]) << 16)
667 | (
static_cast<uint32_t
>(b[3]) << 24);
671 void writeBytes(
const char* buf,
int n)
676 void writeLE16(uint16_t val)
679 static_cast<uint8_t
>(val & 0xFF),
680 static_cast<uint8_t
>((val >> 8) & 0xFF)
682 file_.write(
reinterpret_cast<const char*
>(b), 2);
685 void writeLE32(uint32_t val)
688 static_cast<uint8_t
>(val & 0xFF),
689 static_cast<uint8_t
>((val >> 8) & 0xFF),
690 static_cast<uint8_t
>((val >> 16) & 0xFF),
691 static_cast<uint8_t
>((val >> 24) & 0xFF)
693 file_.write(
reinterpret_cast<const char*
>(b), 4);