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
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
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
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
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
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