WAV File Parser Module
Minimal, allocation-free WAV (RIFF) parser for embedded systems. Parses WAV file headers and validates format without loading sample data.
Key Features:
- Zero heap allocation
- Supports PCM and IEEE float formats
- Handles JUNK and unknown chunks
- Records data offset for streaming
- Metadata extraction support
Usage Example:
import nimphea_wavparser import nimphea/per/sdmmc var parser: WavParser var fil: FIL # Open WAV file discard f_open(addr fil, "sample.wav", FA_READ) # Create file reader and parse var reader = createFileReader(addr fil) if parser.parse(reader): let info = parser.info() echo "Sample Rate: ", info.sampleRate echo "Channels: ", info.numChannels echo "Bits/Sample: ", info.bitsPerSample echo "Data Offset: ", parser.dataOffset() echo "Data Size: ", parser.dataSize() f_close(addr fil)
Supported Formats:
- PCM (16-bit, 24-bit, 32-bit)
- IEEE Float (32-bit, 64-bit)
- Extensible format (basic parsing)
Limitations:
- Does not load sample data (use with WavPlayer for streaming)
- Maximum 16 metadata chunks
- Little-endian host assumed (STM32/Cortex-M)
Types
FileReader {.importcpp: "daisy::FileReader", header: "util/FileReader.h".} = object
- Concrete file reader for FatFS Source Edit
IReader {.importcpp: "daisy::IReader", header: "util/FileReader.h".} = object
- Abstract reader interface Source Edit
MetadataEntry {.importcpp: "daisy::MetadataEntry", header: "util/WavParser.h".} = object fourcc*: uint32 ## Chunk ID (FourCC code) size*: uint32 ## Payload size in bytes offset*: uint32 ## File offset of chunk data
- Metadata chunk information Source Edit
WavFormatInfo {.importcpp: "daisy::WavFormatInfo", header: "util/WavParser.h".} = object audioFormat*: uint16 ## 1 = PCM, 3 = IEEE float, 0xFFFE = extensible numChannels*: uint16 ## Number of channels (1=mono, 2=stereo, etc.) sampleRate*: uint32 ## Sample rate in Hz byteRate*: uint32 ## Byte rate (sampleRate * channels * bytesPerSample) blockAlign*: uint16 ## Block alignment (channels * bytesPerSample) bitsPerSample*: uint16 ## Bits per sample (8, 16, 24, 32, etc.) validBitsPerSample*: uint16 ## For extensible format channelMask*: uint32 ## Channel mask for extensible format subFormat*: uint16 ## Sub-format for extensible format
- WAV format information extracted from header Source Edit
Consts
FOURCC_DATA = 0x61746164'u32
- "data" Source Edit
FOURCC_FACT = 0x74636166'u32
- "fact" Source Edit
FOURCC_FMT = 0x20746D66'u32
- "fmt " Source Edit
FOURCC_INFO = 0x4F464E49'u32
- "INFO" Source Edit
FOURCC_JUNK = 0x4B4E554A'u32
- "JUNK" Source Edit
FOURCC_LIST = 0x5453494C'u32
- "LIST" Source Edit
FOURCC_RIFF = 0x46464952'u32
- "RIFF" Source Edit
FOURCC_WAVE = 0x45564157'u32
- "WAVE" Source Edit
MAX_METADATA_CHUNKS = 16
- Maximum metadata chunks to store Source Edit
WAVE_FORMAT_EXTENSIBLE = 0xFFFE'u16
- Extensible format Source Edit
WAVE_FORMAT_IEEE_FLOAT = 0x0003'u16
- IEEE float format Source Edit
WAVE_FORMAT_PCM = 0x0001'u16
- PCM format Source Edit
Procs
proc bytesPerFrame(info: WavFormatInfo): int {.inline, ...raises: [], tags: [], forbids: [].}
-
Calculate bytes per sample frame
Returns: Bytes per frame (channels * bytesPerSample)
Source Edit proc bytesPerSample(info: WavFormatInfo): int {.inline, ...raises: [], tags: [], forbids: [].}
-
Calculate bytes per sample
Returns: Bytes per sample (bit depth / 8)
Source Edit proc createFileReader(fil: ptr FIL): FileReader {. importcpp: "daisy::FileReader(@)", header: "util/FileReader.h", ...raises: [], tags: [], forbids: [].}
-
Create a FileReader from a FatFS file handle
Parameters:
- fil: Pointer to opened FIL structure
Returns: FileReader instance for use with WavParser
Example:
var fil: FIL discard f_open(addr fil, "test.wav", FA_READ) var reader = createFileReader(addr fil)
Source Edit proc dataOffset(this: WavParser): uint32 {.importcpp: "#.dataOffset()", noSideEffect, ...raises: [], tags: [], forbids: [].}
-
Get file offset of audio data chunk
Returns: Byte offset from start of file where audio samples begin
Example:
let offset = parser.dataOffset() discard f_lseek(addr fil, offset) # Seek to audio data
Source Edit proc dataSize(this: WavParser): uint32 {.importcpp: "#.dataSize()", noSideEffect, ...raises: [], tags: [], forbids: [].}
-
Get size of audio data chunk in bytes
Returns: Size of audio data in bytes
Example:
let numSamples = parser.dataSize() div (parser.info().bitsPerSample div 8) div parser.info().numChannels
Source Edit proc durationSeconds(parser: WavParser): float32 {.inline, ...raises: [], tags: [], forbids: [].}
-
Calculate audio duration in seconds
Returns: Duration in seconds
Source Edit proc info(this: WavParser): WavFormatInfo {.importcpp: "#.info()", noSideEffect, ...raises: [], tags: [], forbids: [].}
-
Get format information from parsed WAV file
Returns: WavFormatInfo structure with sample rate, channels, bit depth, etc.
Example:
let info = parser.info() echo "Sample Rate: ", info.sampleRate echo "Channels: ", info.numChannels echo "Bits/Sample: ", info.bitsPerSample
Source Edit proc isExtensible(info: WavFormatInfo): bool {.inline, ...raises: [], tags: [], forbids: [].}
-
Check if format is extensible
Returns: true if audio format is extensible
Source Edit proc isFloat(info: WavFormatInfo): bool {.inline, ...raises: [], tags: [], forbids: [].}
-
Check if format is IEEE float
Returns: true if audio format is IEEE float
Source Edit proc isPcm(info: WavFormatInfo): bool {.inline, ...raises: [], tags: [], forbids: [].}
-
Check if format is PCM
Returns: true if audio format is PCM
Source Edit proc isValidWav(parser: WavParser): bool {.inline, ...raises: [], tags: [], forbids: [].}
-
Check if parsed file is a valid WAV file
Returns: true if format info indicates valid WAV
Source Edit proc metadata(this: WavParser): ptr MetadataEntry {.importcpp: "#.metadata()", noSideEffect, ...raises: [], tags: [], forbids: [].}
-
Get pointer to metadata entries array
Returns: Pointer to array of MetadataEntry (up to MAX_METADATA_CHUNKS)
Example:
let meta = parser.metadata() for i in 0..<parser.metadataCount(): echo "Chunk: ", cast[ptr UncheckedArray[MetadataEntry]](meta)[i].fourcc
Source Edit proc metadataCount(this: WavParser): cint {.importcpp: "#.metadataCount()", noSideEffect, ...raises: [], tags: [], forbids: [].}
-
Get number of metadata chunks found
Returns: Number of metadata entries (0 to MAX_METADATA_CHUNKS)
Example:
echo "Found ", parser.metadataCount(), " metadata chunks"
Source Edit proc numSamples(parser: WavParser): uint32 {.inline, ...raises: [], tags: [], forbids: [].}
-
Calculate total number of sample frames
Returns: Total sample frames in file
Source Edit proc parse(this: var WavParser; reader: var IReader): bool {. importcpp: "#.parse(@)", ...raises: [], tags: [], forbids: [].}
-
Parse WAV file using provided reader
Parameters:
- reader: IReader implementation (e.g., FileReader)
Returns: true if parsing succeeded, false on error
Note: After successful parsing, use info(), dataOffset(), and dataSize() to access file information
Example:
var parser: WavParser var reader = createFileReader(addr fil) if parser.parse(reader): echo "Parse successful!"
Source Edit