// wav_trim — pure implementation. See wav_trim.h. NO REAPER / SWELL / vendor. #include "wav_trim.h" #include // std::memcpy, std::memcmp namespace reasampler { namespace { // Little-endian readers. Bounds are checked by the caller before each read; these // assume `off + N <= bytes.size()`. memcpy avoids alignment/aliasing UB. std::uint16_t readU16LE(const std::vector& b, std::size_t off) { return static_cast(b[off] | (b[off + 1] << 8)); } std::uint32_t readU32LE(const std::vector& b, std::size_t off) { return static_cast(b[off]) | (static_cast(b[off + 1]) << 8) | (static_cast(b[off + 2]) << 16) | (static_cast(b[off + 3]) << 24); } bool tagEquals(const std::vector& b, std::size_t off, const char* tag) { return off + 4 <= b.size() && std::memcmp(b.data() + off, tag, 4) == 0; } // WAVE format tags we accept as 32-bit float (see wav_trim.h FORMAT ASSUMPTION). constexpr std::uint16_t kWaveFormatIeeeFloat = 0x0003; constexpr std::uint16_t kWaveFormatExtensible = 0xFFFE; } // namespace WavLayout parseWavLayout(const std::vector& bytes) { WavLayout out; // Minimum viable RIFF/WAVE: "RIFF"(4) size(4) "WAVE"(4) = 12 bytes. if (bytes.size() < 12) return out; if (!tagEquals(bytes, 0, "RIFF")) return out; if (!tagEquals(bytes, 8, "WAVE")) return out; bool haveFmt = false; std::uint16_t fmtTag = 0, channels = 0, bitsPerSample = 0; std::uint32_t sampleRate = 0; std::uint16_t extensibleSubFormatTag = 0; // set only when fmtTag == kWaveFormatExtensible // Walk the sub-chunks after "WAVE" (offset 12). Each is: id(4) size(4) body(size), // body padded to an even byte count (RIFF word alignment). Stop cleanly if a // header would run past the buffer — a malformed/truncated file is "invalid", // never an OOB read. std::size_t pos = 12; while (pos + 8 <= bytes.size()) { const std::size_t bodyOffset = pos + 8; const std::uint32_t bodySize = readU32LE(bytes, pos + 4); if (tagEquals(bytes, pos, "fmt ")) { // fmt body: at least 16 bytes (PCM/float common fields). if (bodyOffset + 16 > bytes.size() || bodySize < 16) return out; fmtTag = readU16LE(bytes, bodyOffset + 0); channels = readU16LE(bytes, bodyOffset + 2); sampleRate = readU32LE(bytes, bodyOffset + 4); bitsPerSample = readU16LE(bytes, bodyOffset + 14); // For WAVE_FORMAT_EXTENSIBLE (0xFFFE), read the SubFormat GUID's leading // 2-byte tag at body offset 24 to distinguish float (0x0003) from PCM // integer (0x0001) and all other sub-formats. Body must be >= 40 bytes to // reach GUID offset 24 + 16 bytes of GUID, and the full GUID must fit in // the buffer; otherwise we leave extensibleSubFormatTag at 0 (rejected). if (fmtTag == kWaveFormatExtensible) { if (bodySize >= 40 && bodyOffset + 40 <= bytes.size()) { extensibleSubFormatTag = readU16LE(bytes, bodyOffset + 24); } } haveFmt = true; } else if (tagEquals(bytes, pos, "data")) { // The data chunk: PCM starts at bodyOffset, declared length bodySize. // Reject if it runs past the buffer (truncated / lying header). if (bodyOffset + bodySize > bytes.size()) return out; if (!haveFmt) return out; // data before fmt — not a WAV we parse // Plain IEEE-float tag (0x0003): accept as-is. // Extensible tag (0xFFFE): accept only when the SubFormat tag read from // the GUID at body offset 24 is also 0x0003 (IEEE float). SubFormat tag // 0x0001 (PCM integer) or anything else with bitsPerSample==32 is NOT // float and must be rejected to prevent mis-decoding as float. const bool floatTag = (fmtTag == kWaveFormatIeeeFloat) || (fmtTag == kWaveFormatExtensible && extensibleSubFormatTag == kWaveFormatIeeeFloat); if (!floatTag || bitsPerSample != 32 || channels == 0) return out; out.valid = true; out.channelCount = channels; out.sampleRate = sampleRate; out.dataByteOffset = bodyOffset; out.dataByteLength = bodySize; out.riffSizeFieldOffset = 4; out.dataSizeFieldOffset = pos + 4; // the `data` size field (LE uint32) return out; } // Advance past this chunk's body, honoring RIFF even-byte padding. Guard the // additions against size_t overflow (a hostile bodySize near SIZE_MAX). std::size_t advance = bodySize; if (advance & 1u) ++advance; // pad byte if (advance > bytes.size() - bodyOffset) break; // would overrun -> stop pos = bodyOffset + advance; } return out; // no data chunk found -> invalid } std::vector extractFloatFrames(const std::vector& bytes, const WavLayout& layout, std::size_t startFrame, std::size_t frameCount) { std::vector out; if (!layout.valid) return out; const std::size_t bytesPerFrame = static_cast(layout.channelCount) * 4u; const std::size_t totalFrames = layout.frameCount(); if (startFrame >= totalFrames) return out; // Clamp the requested span to the frames that actually exist. const std::size_t avail = totalFrames - startFrame; const std::size_t frames = (frameCount < avail) ? frameCount : avail; if (frames == 0) return out; const std::size_t firstByte = layout.dataByteOffset + startFrame * bytesPerFrame; out.resize(frames * layout.channelCount); // memcpy each float (LE on target hosts — see header's byte-order note). for (std::size_t i = 0; i < out.size(); ++i) { float f = 0.0f; std::memcpy(&f, bytes.data() + firstByte + i * 4u, 4u); out[i] = f; } return out; } WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames) { WavTruncatePlan plan; if (!layout.valid) return plan; const std::size_t totalFrames = layout.frameCount(); if (keptFrames > totalFrames) return plan; // never grow const std::size_t bytesPerFrame = static_cast(layout.channelCount) * 4u; const std::size_t keptDataBytes = keptFrames * bytesPerFrame; plan.valid = true; plan.newFileByteLength = layout.dataByteOffset + keptDataBytes; plan.dataSizeFieldOffset = layout.dataSizeFieldOffset; plan.newDataSize = static_cast(keptDataBytes); plan.riffSizeFieldOffset = layout.riffSizeFieldOffset; // RIFF size counts everything after the 8-byte "RIFF"+size prefix. plan.newRiffSize = static_cast(plan.newFileByteLength - 8); return plan; } } // namespace reasampler