Skip to content
Open
Changes from all commits
Commits
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
68 changes: 66 additions & 2 deletions Sources/WavReader/WavReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation
import CWavHeader

public struct WavReader: Sequence {
public struct WavReader: Sequence, Sendable {
public let formatType: Format
public let sampleRate: Int
public let numFrames: Int
Expand All @@ -30,7 +30,7 @@ public struct WavReader: Sequence {
case unsupportedFormat(format: Int)
}

public enum Format {
public enum Format: Sendable {
case pcm
case ieeeFloat
}
Expand Down Expand Up @@ -80,6 +80,70 @@ public struct WavReader: Sequence {
try self.init(bytes: wavData, blockSize: blockSize)
}

/// Reads a slice of mono audio samples (first channel) normalized to [-1, 1].
///
/// Handles negative `beginMilliseconds` (zero-pads start) and `endMilliseconds`
/// past EOF (zero-pads end). Supports 16-bit, 24-bit PCM and 32-bit float.
///
/// - Parameters:
/// - beginMilliseconds: Start time in milliseconds (can be negative for zero-padding).
/// - endMilliseconds: End time in milliseconds (can extend past file duration).
/// - Returns: Mono `[Float]` samples normalized to [-1, 1].
public func readSlice(beginMilliseconds: Int, endMilliseconds: Int) -> [Float] {
let beginSample = beginMilliseconds * sampleRate / 1000
let endSample = endMilliseconds * sampleRate / 1000
let duration = endSample - beginSample

var result = [Float](repeating: 0, count: duration)

let totalFrames = numFrames
let srcStart = Swift.max(beginSample, 0)
let srcEnd = Swift.min(endSample, totalFrames)
let dstStart = Swift.max(-beginSample, 0)

guard srcStart < srcEnd else { return result }

let count = srcEnd - srcStart

bytes.withUnsafeBytes { rawBuffer in
let byteBuffer = rawBuffer.bindMemory(to: UInt8.self)

switch (formatType, bytesPerSample) {
case (.pcm, 2):
for i in 0..<count {
let byteOff = offsetToWavData + (srcStart + i) * bytesPerFrame
let lo = UInt16(byteBuffer[byteOff])
let hi = UInt16(byteBuffer[byteOff + 1])
let raw = Int16(bitPattern: lo | (hi << 8))
result[dstStart + i] = Float(raw) / 32768.0
}
case (.pcm, 3):
for i in 0..<count {
let byteOff = offsetToWavData + (srcStart + i) * bytesPerFrame
let b0 = Int32(byteBuffer[byteOff])
let b1 = Int32(byteBuffer[byteOff + 1])
let b2 = Int32(Int8(bitPattern: byteBuffer[byteOff + 2]))
let sample = (b2 << 16) | (b1 << 8) | b0
result[dstStart + i] = Float(sample) / 8388608.0
}
case (.ieeeFloat, 4):
for i in 0..<count {
let byteOff = offsetToWavData + (srcStart + i) * bytesPerFrame
let bits = UInt32(byteBuffer[byteOff]) |
(UInt32(byteBuffer[byteOff + 1]) << 8) |
(UInt32(byteBuffer[byteOff + 2]) << 16) |
(UInt32(byteBuffer[byteOff + 3]) << 24)
result[dstStart + i] = Float(bitPattern: bits)
}
default:
// Unsupported format — return zeros
break
}
}

return result
}

public func makeIterator() -> WavReader.Iterator {
return Iterator(self)
}
Expand Down