nimphea/nimphea_wavparser

Source   Edit  

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  
WavParser {.importcpp: "daisy::WavParser", header: "util/WavParser.h".} = object

WAV file parser

Parses RIFF/WAV file format and extracts:

  • Format information (sample rate, channels, bit depth)
  • Data chunk location and size
  • Optional metadata chunks
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