nimphea/nimphea_wavplayer

Search:
Group by:
Source   Edit  

WAV File Streaming Playback Module

This module provides WAV file streaming playback functionality for the Daisy platform. It supports 16-bit WAV files with float output, variable playback speed, and looping.

Features:

  • Streaming playback from SD card (no full file loading required)
  • Variable playback speed with linear interpolation
  • Looping support
  • Low-latency buffered streaming using FIFO
  • Pitch shifting via semitone offset

Memory Usage:

  • Approximately 2 * workspace_bytes
  • Default 4KB workspace = ~8KB total memory

Limitations:

  • 16-bit WAV files only at this time
  • No reverse playback support
  • Forward playback only

Usage Example:

import nimphea_wavplayer

var player: WavPlayer4K

# Initialize with file
if player.init("sample.wav") == WavPlayerResult.Ok:
  player.setLooping(true)
  player.play()

# In audio callback
proc audioCallback(input, output: AudioBuffer, size: int) =
  for i in 0..<size:
    let res = player.stream(output[0][i].addr, 2)  # 2 channels

# In main loop
while true:
  discard player.prepare()  # Refill buffer
  delay(1)

Types

WavPlayer = WavPlayer4K
Default WavPlayer with 4KB workspace Source   Edit  
WavPlayer4K {.importcpp: "daisy::WavPlayer<4096>", byref.} = object
Source   Edit  
WavPlayer8K {.importcpp: "daisy::WavPlayer<8192>", byref.} = object
Source   Edit  
WavPlayer16K {.importcpp: "daisy::WavPlayer<16384>", byref.} = object
Source   Edit  
WavPlayerFileInfo {.importcpp: "daisy::WavPlayer<4096>::FileInfo", bycopy.} = object
  channels* {.importcpp: "channels".}: csize_t
  length* {.importcpp: "length".}: csize_t
  samplerate* {.importcpp: "samplerate".}: csize_t
  data_start* {.importcpp: "data_start".}: csize_t
  data_size_bytes* {.importcpp: "data_size_bytes".}: csize_t
Source   Edit  
WavPlayerResult {.size: 4.} = enum
  Ok = 0, FileNotFoundError = 1, PlaybackUnderrun = 2, PrepareOverrun = 3,
  NewSamplesRequested = 4, DiskError = 5
Source   Edit  

Procs

proc close(player: var WavPlayer4K): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Close())", cdecl, ...raises: [], tags: [],
    forbids: [].}
Close the currently open file and clear all data. Source   Edit  
proc close(player: var WavPlayer8K): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Close())", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc close(player: var WavPlayer16K): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Close())", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc durationSeconds(player: WavPlayer4K): float {.inline, ...raises: [], tags: [],
    forbids: [].}

Get the total duration of the file in seconds.

Returns: Duration in seconds as a float

Note: Uses the Daisy audio system's default sample rate of 48kHz. WAV files are resampled to this rate during playback.

Source   Edit  
proc durationSeconds(player: WavPlayer8K): float {.inline, ...raises: [], tags: [],
    forbids: [].}

Get the total duration of the file in seconds.

Returns: Duration in seconds as a float

Note: Uses the Daisy audio system's default sample rate of 48kHz. WAV files are resampled to this rate during playback.

Source   Edit  
proc durationSeconds(player: WavPlayer16K): float {.inline, ...raises: [],
    tags: [], forbids: [].}

Get the total duration of the file in seconds.

Returns: Duration in seconds as a float

Note: Uses the Daisy audio system's default sample rate of 48kHz. WAV files are resampled to this rate during playback.

Source   Edit  
proc getChannels(player: WavPlayer4K): csize_t {.importcpp: "#.GetChannels()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Get the number of audio channels in the file. Source   Edit  
proc getChannels(player: WavPlayer8K): csize_t {.importcpp: "#.GetChannels()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getChannels(player: WavPlayer16K): csize_t {.importcpp: "#.GetChannels()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getDurationInSamples(player: WavPlayer4K): csize_t {.
    importcpp: "#.GetDurationInSamples()", cdecl, ...raises: [], tags: [],
    forbids: [].}
Get the total number of samples in the audio file. Source   Edit  
proc getDurationInSamples(player: WavPlayer8K): csize_t {.
    importcpp: "#.GetDurationInSamples()", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc getDurationInSamples(player: WavPlayer16K): csize_t {.
    importcpp: "#.GetDurationInSamples()", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc getLooping(player: WavPlayer4K): bool {.importcpp: "#.GetLooping()", cdecl,
    ...raises: [], tags: [], forbids: [].}
Check if looping is enabled. Source   Edit  
proc getLooping(player: WavPlayer8K): bool {.importcpp: "#.GetLooping()", cdecl,
    ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getLooping(player: WavPlayer16K): bool {.importcpp: "#.GetLooping()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getNormalizedPosition(player: WavPlayer4K): cfloat {.
    importcpp: "#.GetNormalizedPosition()", cdecl, ...raises: [], tags: [],
    forbids: [].}

Get the playhead position as a 0.0-1.0 value within the file.

Returns:

  • 0.0 = beginning of file
  • 1.0 = end of file
  • 0.5 = halfway through
Source   Edit  
proc getNormalizedPosition(player: WavPlayer8K): cfloat {.
    importcpp: "#.GetNormalizedPosition()", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc getNormalizedPosition(player: WavPlayer16K): cfloat {.
    importcpp: "#.GetNormalizedPosition()", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc getPlaying(player: WavPlayer4K): bool {.importcpp: "#.GetPlaying()", cdecl,
    ...raises: [], tags: [], forbids: [].}
Check if playback is currently active. Source   Edit  
proc getPlaying(player: WavPlayer8K): bool {.importcpp: "#.GetPlaying()", cdecl,
    ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getPlaying(player: WavPlayer16K): bool {.importcpp: "#.GetPlaying()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getPosition(player: WavPlayer4K): uint32 {.importcpp: "#.GetPosition()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Get the current playhead position in samples from the start of the file. Source   Edit  
proc getPosition(player: WavPlayer8K): uint32 {.importcpp: "#.GetPosition()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc getPosition(player: WavPlayer16K): uint32 {.importcpp: "#.GetPosition()",
    cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc init(player: var WavPlayer4K; name: cstring): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Init(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}

Initialize and open a WAV file for playback. This will open the file, parse the header, and prepare audio for streaming.

Parameters:

  • name: Path to the WAV file on the SD card

Returns:

  • WavPlayerResult.Ok on success
  • WavPlayerResult.FileNotFoundError if file doesn't exist
  • WavPlayerResult.DiskError on disk I/O errors
Source   Edit  
proc init(player: var WavPlayer8K; name: cstring): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Init(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc init(player: var WavPlayer16K; name: cstring): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Init(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc isEof(player: WavPlayer4K): bool {.inline, ...raises: [], tags: [],
                                        forbids: [].}
Check if playback has reached the end of file. Only returns true when not looping and playback stopped at end. Source   Edit  
proc isEof(player: WavPlayer8K): bool {.inline, ...raises: [], tags: [],
                                        forbids: [].}
Source   Edit  
proc isEof(player: WavPlayer16K): bool {.inline, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc open(player: var WavPlayer4K; name: cstring): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Open(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Open a WAV file and prepare for streaming. Similar to init() but doesn't reset playback state. Source   Edit  
proc open(player: var WavPlayer8K; name: cstring): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Open(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc open(player: var WavPlayer16K; name: cstring): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Open(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc play(player: var WavPlayer4K) {.inline, ...raises: [], tags: [], forbids: [].}
Start playback (convenience wrapper for setPlaying(true)) Source   Edit  
proc play(player: var WavPlayer8K) {.inline, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc play(player: var WavPlayer16K) {.inline, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc prepare(player: var WavPlayer4K): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Prepare())", cdecl, ...raises: [], tags: [],
    forbids: [].}

Refill the playback buffer with new samples from disk. Must be called regularly in the main loop (not in audio callback). This performs the actual disk I/O for streaming.

Returns:

  • WavPlayerResult.Ok on success
  • WavPlayerResult.DiskError on disk I/O errors
  • WavPlayerResult.PrepareOverrun if buffer is already full
Source   Edit  
proc prepare(player: var WavPlayer8K): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Prepare())", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc prepare(player: var WavPlayer16K): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Prepare())", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc restart(player: var WavPlayer4K) {.importcpp: "#.Restart()", cdecl,
                                        ...raises: [], tags: [], forbids: [].}
Clear all playback samples and return to the beginning of the file. This will clear buffers, seek to start, and request new samples. Source   Edit  
proc restart(player: var WavPlayer8K) {.importcpp: "#.Restart()", cdecl,
                                        ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc restart(player: var WavPlayer16K) {.importcpp: "#.Restart()", cdecl,
    ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc setLooping(player: var WavPlayer4K; state: bool) {.
    importcpp: "#.SetLooping(@)", cdecl, ...raises: [], tags: [], forbids: [].}
Enable or disable looping playback. When enabled, playback will automatically restart from the beginning. Source   Edit  
proc setLooping(player: var WavPlayer8K; state: bool) {.
    importcpp: "#.SetLooping(@)", cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc setLooping(player: var WavPlayer16K; state: bool) {.
    importcpp: "#.SetLooping(@)", cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc setPlaybackSpeedRatio(player: var WavPlayer4K; speed: cfloat) {.
    importcpp: "#.SetPlaybackSpeedRatio(@)", cdecl, ...raises: [], tags: [],
    forbids: [].}

Set playback speed as a ratio of original speed.

Examples:

  • 1.0 = original speed (normal playback)
  • 0.5 = half speed (slower)
  • 2.0 = double speed (faster)
  • 0.0 = paused

Note: Negative values are not supported (no reverse playback)

Source   Edit  
proc setPlaybackSpeedRatio(player: var WavPlayer8K; speed: cfloat) {.
    importcpp: "#.SetPlaybackSpeedRatio(@)", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc setPlaybackSpeedRatio(player: var WavPlayer16K; speed: cfloat) {.
    importcpp: "#.SetPlaybackSpeedRatio(@)", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc setPlaybackSpeedSemitones(player: var WavPlayer4K; semitones: cfloat) {.
    importcpp: "#.SetPlaybackSpeedSemitones(@)", cdecl, ...raises: [], tags: [],
    forbids: [].}

Set playback speed as semitone offset from original pitch.

Examples:

  • 0 = original pitch
  • +12 = one octave up (2x speed)
  • -12 = one octave down (0.5x speed)
  • +7 = perfect fifth up (1.5x speed)

The speed ratio is calculated as: 2^(semitones/12)

Source   Edit  
proc setPlaybackSpeedSemitones(player: var WavPlayer8K; semitones: cfloat) {.
    importcpp: "#.SetPlaybackSpeedSemitones(@)", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc setPlaybackSpeedSemitones(player: var WavPlayer16K; semitones: cfloat) {.
    importcpp: "#.SetPlaybackSpeedSemitones(@)", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc setPlaying(player: var WavPlayer4K; state: bool) {.
    importcpp: "#.SetPlaying(@)", cdecl, ...raises: [], tags: [], forbids: [].}

Start or stop playback.

Parameters:

  • state: true to start playing, false to stop
Source   Edit  
proc setPlaying(player: var WavPlayer8K; state: bool) {.
    importcpp: "#.SetPlaying(@)", cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc setPlaying(player: var WavPlayer16K; state: bool) {.
    importcpp: "#.SetPlaying(@)", cdecl, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc stop(player: var WavPlayer4K) {.inline, ...raises: [], tags: [], forbids: [].}
Stop playback (convenience wrapper for setPlaying(false)) Source   Edit  
proc stop(player: var WavPlayer8K) {.inline, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc stop(player: var WavPlayer16K) {.inline, ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc stream(player: var WavPlayer4K; samples: ptr cfloat; numChannels: csize_t): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Stream(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}

Stream audio samples from the file. Call this from your audio callback.

The function will fill the samples buffer with audio data, handling:

  • Variable playback speed with linear interpolation
  • Looping behavior
  • Automatic buffer refill requests

Parameters:

  • samples: Buffer to fill with audio samples
  • numChannels: Number of channels to fill (can differ from file channels)

Returns:

  • WavPlayerResult.Ok on success
  • WavPlayerResult.NewSamplesRequested when buffer needs refilling (call prepare())
  • WavPlayerResult.PlaybackUnderrun if buffer is empty during playback
Source   Edit  
proc stream(player: var WavPlayer8K; samples: ptr cfloat; numChannels: csize_t): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Stream(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc stream(player: var WavPlayer16K; samples: ptr cfloat; numChannels: csize_t): WavPlayerResult {.
    importcpp: "static_cast<int>(#.Stream(@))", cdecl, ...raises: [], tags: [],
    forbids: [].}
Source   Edit