Full Code of SpotlightKid/nim-sndfile for AI

master 94591b36e0f1 cached
17 files
35.2 KB
11.4k tokens
1 requests
Download .txt
Repository: SpotlightKid/nim-sndfile
Branch: master
Commit: 94591b36e0f1
Files: 17
Total size: 35.2 KB

Directory structure:
gitextract_o4nvxbth/

├── LICENSE.md
├── README.md
├── examples/
│   ├── config.nims
│   ├── dump_loops.nim
│   ├── libsamplerate.nim
│   ├── list_formats.nim
│   ├── playfile_jack.nim
│   ├── playfile_sdl.nim
│   └── signal.nim
├── sndfile.nimble
├── src/
│   ├── sndfile/
│   │   ├── api.nim
│   │   └── chunks.nim
│   └── sndfile.nim
├── tests/
│   ├── config.nims
│   ├── test_chunks.nim
│   └── test_testwav.nim
└── tools/
    └── build_docs.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE.md
================================================
# MIT License

Copyright (c) 2014 - 2017 Julien Aubert https://github.com/julienaubert

Copyright (c) 2024 - 2025 Christopher Arndt <chris@chrisarndt.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
nim-sndfile
===========

A wrapper of [libsndfile] for the [Nim] programming language.


# API

The libsndfile [API] only has a relatively small number of functions, but it
defines quite a few types and enum values. The `sndfile` Nim module provides
definitions for all types and enums (except those for deprecated and not-yet
implemented functions) and wraps all functions, *except* the following:

* `sf_open_fd`
* `sf_open_virtual`
* `sf_wchar_open`

These missing functions may be added in future versions, if it makes sense to
use them in Nim.


# Naming conventions

* The `sf_` prefix has been removed from function names, i.e. instead of
  `sf_read_double`, use just `read_double` (or `readDouble`, Nim's
  identifier naming rules make no distinction).
  The wrapper code generally uses lower `camelCase`.
* All values within each enum have been stripped of their unique prefix and
  all enums are marked `{.pure.}`. This means you should prefix each enum
  symbol with its enum type name. For example `SF_FORMAT_WAV` becomes
  `SFFormat.WAV` (or `SF_Format.Wav` etc.). If the value symbol is
  non-ambiguous, the enum name can be omitted, e.g. `GET_LOG_INFO` instead
  of `SFCommand.GET_LOG_INFO`.
* typedefs and structs start with a capital letter and retain their `SF`
  prefix, but Nim's identifier naming rules allow to omit underscores and use
  whatever case after the first letter. For example, instead of `SF_INFO`, you
  can use `SFInfo`, `SF_info` etc. The wrapper code generally uses
  `PascalCase`.


## Special cases

| C prefix / name                | Nim enum / symbol      |
| ------------------------------ | ---------------------- |
| `SFM_`                         | `SFMode`               |
| `SF_STR_`                      | `SFStrType`            |
| `SF_LOOP_`                     | `SFLoopMode`           |
| `SFC_`                         | `SFCommand`            |
| `SNDFILE`                      | `SndFile`              |
| `sf_count_t`                   | `SFCount`              |
| `SF_TRUE`                      | `SFBool.TRUE`          |
| `SF_FALSE`                     | `SFBool.FALSE`         |
| `SF_STR_FIRST`	               | `SFStrType.low`        |
| `SF_STR_LAST`	                 | `SFStrType.high`       |
| `SF_AMBISONIC_NONE`            | `SFAmbisonic.NONE`     |
| `SF_AMBISONIC_B_FORMAT`        | `SFAmbisonic.B_FORMAT` |
| `SF_INSTRUMENT.loops`          | `SFLoop`               |
| `SF_INSTRUMENT.loops[n].end`   | `SFLoops.endPos`       |
| `SF_INSTRUMENT.loops[n].start` | `SFLoops.startPos`     |


## Structs with customizable-sized array fields

* `SF_CUES`: Define / allocate `array[<n>, SFCuePoint]` as needed
* `SF_BROADCAST_INFO`:  Set number of entries in the `codingHistory` array
  member of `SFBroadcastInfo` with `-d:sfCodingHistSize=<n>` at compile time.
  The default size is 256.
* `SF_CART_INFO`:  Set number of entries in the `tagText` array member of
  `SFCartInfo` with `-d:sfMaxTagTextSize=<n>` at compile time. The default
  size is 256.


## Authors

*nim-sndfile* was written by [Julien Aubert](https://github.com/julienaubert)
and this fork was updated, re-factored and extended by
[Christopher Arndt](https://github.com/SpotlightKid), while also applying
some patches by [James Bradbury](https://github.com/jamesb93).


## License

This software is released under the *MIT License*. See the file
[LICENSE.md](./LICENSE.md) for more information.


[API]: https://libsndfile.github.io/libsndfile/api.html
[libsndfile]: https://libsndfile.github.io/libsndfile/
[Nim]: https://nim-lang.org/


================================================
FILE: examples/config.nims
================================================
switch("path", "$projectDir/../src")


================================================
FILE: examples/dump_loops.nim
================================================
## Read an audio sample file and print information about defined sample loops

import std/[os, strformat]

import sndfile


proc main() =
  if paramCount() != 1:
    echo "Usage: dump_loops <filename>"
    quit(QuitFailure)

  var filename = paramStr(1)

  # Open the file
  var info: SFInfo
  var sf = sndfile.open(filename.cstring, SFMode.READ, info.addr)

  if sf.isNil:
    echo $sf.strerror()
    quit(QuitFailure)

  echo &"File: {filename}"
  echo &"Sample frames: {info.frames}"

  var instr = SFInstrument()

  if SFBool(command(sf, GET_INSTRUMENT, instr.addr, sizeof(instr).cint)) == FALSE:
    echo "Loops: no instrument data found"
    quit(QuitSuccess)
  else:
    echo &"Root note: {instr.basenote}"
    echo &"Loops: {instr.loopCount}"

  for i in 0..<instr.loopCount:
    # endPos is the first sample position *after* the loop!
    stdOut.write &"* #{i}: start={instr.loops[i].startPos}, "
    stdOut.write &"end={instr.loops[i].endPos - 1}, "
    stdOut.write &"mode={instr.loops[i].mode}, "
    stdOut.write &"count={instr.loops[i].count}\n"

  sf.close()


main()


================================================
FILE: examples/libsamplerate.nim
================================================
when defined(windows):
  const soname = "(|lib)samplerate(|-0).dll"
elif defined(macosx):
  const soname = "libsamplerate.dylib"
else:
  const soname = "libsamplerate.so(|.0)"

{.pragma: libsrc, cdecl, dynlib: soname.}

type
  SrcData* = object
    dataIn*: ptr UncheckedArray[cfloat]
    dataOut*: ptr UncheckedArray[cfloat]
    inputFrames*: clong
    outputFrames*: clong
    inputFramesUsed*: clong
    outputFramesGen*: clong
    endOfInput*: cint
    srcRatio*: float64

  Converter* {.pure, size: sizeof(cint).} = enum
    SINC_BEST_QUALITY = 0
    SINC_MEDIUM_QUALITY = 1
    SINC_FASTEST = 2
    ZERO_ORDER_HOLD = 3
    LINEAR = 4

proc srcSimple*(data: ptr SrcData, converter_type: Converter, channels: cint): cint {.libsrc, importc: "src_simple".}
proc srcStrError*(error: cint): cstring {.libsrc, importc: "src_strerror".}


================================================
FILE: examples/list_formats.nim
================================================
## List all file and sample formats supported by the used libsndfile

import std/strformat
import sndfile

var
  sfinfo: SFInfo
  fmtinfo: SFFormatInfo
  simpleCount, majorCount, subtypeCount: cint

echo &"Version: {versionString()}\n"

command(nil, GET_SIMPLE_FORMAT_COUNT, simpleCount.addr, sizeof(cint).cint)
command(nil, GET_FORMAT_MAJOR_COUNT, majorCount.addr, sizeof(cint).cint)
command(nil, GET_FORMAT_SUBTYPE_COUNT, subtypeCount.addr, sizeof(cint).cint)


for format in  0..<simpleCount:
  fmtinfo.format = format.cint
  command(nil, GET_SIMPLE_FORMAT, fmtinfo.addr, sizeof(fmtinfo).cint)
  echo &"{fmtinfo.name} (extension: '.{fmtinfo.extension}')"

sfinfo.samplerate = 48000

for major_format in  0..<major_count:
  fmtinfo.format = major_format.cint
  command(nil, GET_FORMAT_MAJOR, fmtinfo.addr, sizeof(fmtinfo).cint)
  echo &"{fmtinfo.name} (extension '.{fmtinfo.extension}')"

  var format = fmtinfo.format

  for subtype in 0..<subtype_count:
    fmtinfo.format = subtype.cint
    command(nil, GET_FORMAT_SUBTYPE, fmtinfo.addr, sizeof(fmtinfo).cint) ;

    format = (format and SFFormatMask.TYPEMASK.cint) or fmtinfo.format
    sfinfo.format = format
    var valid = false
    sfinfo.channels = 1

    if formatCheck(sfinfo.addr) == TRUE:
      valid = true

    sfinfo.channels = 2

    if formatCheck(sfinfo.addr) == TRUE:
      valid = true

    if valid:
      if fmtinfo.extension.isNil:
        echo &"   {fmtinfo.name}"
      else:
        echo &"   {fmtinfo.name} (extension '.{fmtinfo.extension}')"


================================================
FILE: examples/playfile_jack.nim
================================================
## Read an audio file (e.g. WAV or Ogg Vorbis) with libsndfile and play it via JACK

import std/[logging, os, strformat]

import jacket
import libsamplerate
import signal
import sndfile

const
  MaxChannels = 2
  Gain = 0.8

var
  jclient: Client
  outPortL, outPortR: Port
  status: cint
  exitSignalled: bool = false
  log = newConsoleLogger(when defined(release): lvlInfo else: lvlDebug)

type
  Sample = DefaultAudioSample
  SampleBuffer = ptr UncheckedArray[Sample]
  AudioFile = object
    channels: int
    samplerate: int
    samples: SampleBuffer
    frames: int
    pos: int


proc cleanup() =
  debug "Cleaning up..."
  if jclient != nil:
    jclient.deactivate()
    jclient.clientClose()
    jclient = nil

proc errorCb(msg: cstring) {.cdecl.} =
  # Suppress verbose JACK error messages when server is not available by
  # default. Pass ``lvlAll`` when creating the logger to enable them.
  debug "JACK error: " & $msg

proc signalCb(sig: cint) {.noconv.} =
  debug "Received signal: " & $sig
  exitSignalled = true

proc shutdownCb(arg: pointer = nil) {.cdecl.} =
  warn "JACK server has shut down."
  exitSignalled = true

proc readAudio(filename: string): AudioFile =
  var info: SFInfo
  var sf = sndfile.open(filename.cstring, SFMode.READ, info.addr)

  if sf.isNil:
    echo $sf.strError()
    quit QuitFailure

  defer: sf.close()

  if info.channels > MaxChannels:
    raise newException(ValueError, "Only mono or stereo files are suported.")

  let samples = createSharedU(Sample, info.frames * info.channels)

  if samples.isNil:
    raise newException(ResourceExhaustedError, "Error allocating memory for sample buffer")

  var samplesRead = sf.readFFloat(samples, info.frames)

  if samplesRead != info.frames:
    warn "Mismatch between # samples read (as returned by sf_readf_float) and info.frames:"
    warn &"{samplesRead} != {info.frames}"

  result.frames = if samplesRead != 0: samplesRead else: info.frames
  result.samples = cast[SampleBuffer](samples)
  result.channels = info.channels
  result.samplerate = info.samplerate
  result.pos = 0

proc convertSampleRate(input: SampleBuffer, inputSamples, inputRate, outputRate, channels: int): tuple[samples: SampleBuffer, frames: int] =
  var
    data: SrcData
    # Calculate the number of output frames
    outputFrames = int(inputSamples * outputRate / inputRate)

  var output = createSharedU(Sample, outputFrames * channels)

  if output.isNil:
    raise newException(ResourceExhaustedError, "Error allocating memory for resample buffer")

  # Set up the SRC_DATA structure
  data.dataIn = cast[ptr UncheckedArray[cfloat]](input)
  data.inputFrames = inputSamples.clong
  data.dataOut = cast[ptr UncheckedArray[cfloat]](output)
  data.outputFrames = outputFrames.clong
  data.srcRatio = float64(outputRate / inputRate)
  data.endOfInput = 0

  # Perform the sample rate conversion
  var error = srcSimple(addr data, SINC_FASTEST, channels.cint)
  if error != 0:
    freeShared(output)
    raise newException(IOError, &"Error during sample rate conversion: {srcStrError(error)}")
  else:
    # Set output values
    result.samples = cast[SampleBuffer](output)
    result.frames = data.outputFramesGen.int

proc processCb(nFrames: NFrames, arg: pointer): cint {.cdecl.} =
  var outL = cast[SampleBuffer](portGetBuffer(outPortL, nFrames))
  var outR = cast[SampleBuffer](portGetBuffer(outPortR, nFrames))
  let audio = cast[ptr AudioFile](arg)

  for i in 0 ..< nFrames:
    outL[i] = audio.samples[audio.pos] * Gain
    if audio.channels == 1:
      outR[i] = audio.samples[audio.pos] * Gain
    else:
      outR[i] = audio.samples[audio.pos + 1] * Gain

    audio.pos += audio.channels

    if audio.pos >= audio.frames * audio.channels:
      audio.pos = 0

  return 0


proc main() =
  addHandler(log)

  if paramCount() != 1:
    echo "Usage: playfile_jacket <filename>"
    quit(QuitFailure)

  # Print libsndfile version
  info &"libsdnfile version: {versionString()}"
  # Print JACK version
  info &"JACK version: {getVersionString()}"

  # Create JACK client
  setErrorFunction(errorCb)
  jclient = clientOpen("playfile_jack", NullOption, status.addr)
  debug &"JACK server status: {status}"

  if jclient == nil:
    error getJackStatusErrorString(status)
    quit QuitFailure

  var audio: AudioFile

  try:
    audio = readAudio(paramStr(1))
  except CatchableError:
    error getCurrentExceptionMsg()
    cleanup()
    quit QuitFailure

  defer:
    cleanup()
    freeShared(audio.samples)

  info &"Channels: {audio.channels}"
  info &"Samplerate: {audio.samplerate}"
  info &"Frames: {audio.frames}"

  var jackSampleRate = int(jclient.getSamplerate())

  if audio.samplerate != jackSampleRate:
    info &"Resampling audio to {jackSampleRate} Hz ..."
    try:
      var resampled = convertSampleRate(audio.samples, audio.frames, audio.samplerate, jackSampleRate, audio.channels)
      freeShared(audio.samples)
      audio.samples = resampled.samples
      audio.frames = resampled.frames
    except CatchableError:
      error getCurrentExceptionMsg()
      quit QuitFailure
    info "Ready."

  # Set up signal handlers to clean up on exit
  when defined(windows):
    setSignalProc(signalCb, SIGABRT, SIGINT, SIGTERM)
  else:
    setSignalProc(signalCb, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM)

  # Register JACK callbacks
  if jclient.setProcessCallback(processCb, audio.addr) != 0:
    error "Could not set JACK process callback function."
    quit QuitFailure

  jclient.onShutdown(shutdownCb)

  # Create output ports
  outPortL = jclient.portRegister("out_1", JackDefaultAudioType, PortIsOutput, 0)
  outPortR = jclient.portRegister("out_2", JackDefaultAudioType, PortIsOutput, 0)

  # Activate JACK client ...
  if jclient.activate() == 0:
    # Connect our output ports to system playback ports
    jclient.connect(outPortL.portName(), "system:playback_1")
    jclient.connect(outPortR.portName(), "system:playback_2")

    # ... and keep running until a signal is received
    while not exitSignalled:
        sleep(50)


main()


================================================
FILE: examples/playfile_sdl.nim
================================================
## Read an audio file (e.g. WAV or Ogg Vorbis) with libsndfile and play it with sdl2

import std/[math, os, strformat]

import sndfile
import sdl2, sdl2/audio

if paramCount() != 1:
  echo "Usage: playfile <filename>"
  quit(QuitFailure)

var filename = paramStr(1)

# Print libsndfile version
echo &"libsdnfile version: {versionString()}"

# Print SDL version
var version: SDL_Version
sdl2.getVersion(version)
echo &"SDL version: {version.major}.{version.minor}.{version.patch}"

# Open the file
var info: SFInfo
var file = sndfile.open(filename.cstring, SFMode.READ, info.addr)

if file.isNil:
  echo $file.strerror()
  quit(QuitFailure)

echo &"Channels: {info.channels}"
echo &"Frames: {info.frames}"
echo &"Samplerate: {info.samplerate}"
echo &"Format: {info.format}"

# Callback procedure for audio playback
const bufferSizeInSamples = 4096

proc audioCallback(userdata: pointer; stream: ptr uint8; len: cint) {.cdecl.} =
  var buffer: array[bufferSizeInSamples, cfloat]
  let count = file.readFloat(addr buffer[0], bufferSizeInSamples)

  if count == 0:
    echo "End of file reached"
    quit(0)

  for i in 0..<count:
    cast[ptr int16](cast[int](stream) + i * 2)[] = int16(round(buffer[i] * 0.8 * 32760))
    # Without the factor of 0.8, the sound gets distorted for my ogg example file

# Init audio playback
if sdl2.init(INIT_AUDIO) != SdlSuccess:
  echo "Couldn't initialize SDL"
  quit(QuitFailure)

var aspec: AudioSpec
aspec.freq = info.samplerate
aspec.format = AUDIO_S16
aspec.channels = info.channels.uint8
aspec.samples = bufferSizeInSamples
aspec.padding = 0
aspec.callback = audioCallback
aspec.userdata = nil

if openAudio(addr aspec, nil) != 0:
  echo &"Couldn't open audio device: {getError()}"
  quit(QuitFailure)

# Start playback and wait in a loop
pauseAudio(0)
echo "Playing..."

while true:
  delay(100)


================================================
FILE: examples/signal.nim
================================================
import system/ansi_c

export SIG_DFL, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV

when not defined(windows):
  export SIGPIPE, SIGTERM
  var
    SIG_IGN* {.importc: "SIG_IGN", header: "<signal.h>".}: cint
    SIGHUP* {.importc: "SIGHUP", header: "<signal.h>".}: cint
    SIGQUIT* {.importc: "SIGQUIT", header: "<signal.h>".}: cint
else:
  const SIGTERM* = cint(15)

type CSighandlerT = proc (a: cint) {.noconv.}

proc setSignalProc* (`proc`: CSighandlerT, signals: varargs[cint]) =
  for sig in signals:
    discard c_signal(sig, `proc`)


================================================
FILE: sndfile.nimble
================================================
version = "0.2.6"
author = "Julien Aubert, Christopher Arndt"
description = "Wrapper for libsndfile"
license = "MIT"

srcDir = "src"
skipDirs = @["examples"]

requires "nim >= 2.0"

taskrequires "examples", "sdl2"
taskrequires "examples", "jacket >= 0.2.0"
taskrequires "examples_debug", "sdl2"
taskrequires "examples_debug", "jacket >= 0.2.0"

let examples = @[
    "dump_loops",
    "list_formats",
    "playfile_jack",
    "playfile_sdl",
]

task examples, "Build examples (release)":
    for example in examples:
        echo "Building example '" & example & "'..."
        selfExec("compile -d:release -d:strip examples/" & example & ".nim")

task examples_debug, "Build examples (debug)":
    for example in examples:
        echo "Building example '" & example & "' (debug)..."
        selfExec("compile examples/" & example & ".nim")


================================================
FILE: src/sndfile/api.nim
================================================
when defined(windows):
  const soname = "(|lib)sndfile(|-1|-2).dll"
elif defined(macosx):
  const soname = "libsndfile.dylib"
else:
  const soname = "libsndfile.so(|.1)"

{.pragma: libsnd, cdecl, dynlib: soname.}


const codingHistSize {.intdefine: "sfCodingHistSize".}: uint32 = 256
const maxTagTextSize {.intdefine: "sfMaxTagTextSize".}: uint32 = 256


type
  # The following three enums are one enum in soundfile.h
  # Separated here into three enums for clarity
  SFBool* {.pure, size: sizeof(cint).} = enum
    FALSE
    TRUE

  SFMode* {.pure, size: sizeof(cint).} = enum
    READ = 0x10
    WRITE = 0x20
    RDWR = 0x30

  SFAmbisonic* {.pure, size: sizeof(cint).} = enum
    NONE = 0x40
    B_FORMAT = 0x41

  SFSeek* {.pure, size: sizeof(cint).} = enum
    SET
    CUR
    END

  SFErr* {.pure, size: sizeof(cint).} = enum
    NO_ERROR
    UNRECOGNISED_FORMAT
    SYSTEM
    MALFORMED_FILE
    UNSUPPORTED_ENCODING

  SFCommand* {.pure, size: sizeof(cint).} = enum
    GET_LIB_VERSION = 0x1000
    GET_LOG_INFO = 0x1001
    GET_CURRENT_SF_INFO = 0x1002
    GET_NORM_DOUBLE = 0x1010
    GET_NORM_FLOAT = 0x1011
    SET_NORM_DOUBLE = 0x1012
    SET_NORM_FLOAT = 0x1013
    SET_SCALE_FLOAT_INT_READ = 0x1014
    SET_SCALE_INT_FLOAT_WRITE = 0x1015
    GET_SIMPLE_FORMAT_COUNT = 0x1020
    GET_SIMPLE_FORMAT = 0x1021
    GET_FORMAT_INFO = 0x1028
    GET_FORMAT_MAJOR_COUNT = 0x1030
    GET_FORMAT_MAJOR = 0x1031
    GET_FORMAT_SUBTYPE_COUNT = 0x1032
    GET_FORMAT_SUBTYPE = 0x1033
    CALC_SIGNAL_MAX = 0x1040
    CALC_NORM_SIGNAL_MAX = 0x1041
    CALC_MAX_ALL_CHANNELS = 0x1042
    CALC_NORM_MAX_ALL_CHANNELS = 0x1043
    GET_SIGNAL_MAX = 0x1044
    GET_MAX_ALL_CHANNELS = 0x1045
    SET_ADD_PEAK_CHUNK = 0x1050
    SET_ADD_HEADER_PAD_CHUNK = 0x1051
    UPDATE_HEADER_NOW = 0x1060
    SET_UPDATE_HEADER_AUTO = 0x1061
    FILE_TRUNCATE = 0x1080
    SET_RAW_START_OFFSET = 0x1090

    SET_DITHER_ON_WRITE = 0x10A0
    SET_DITHER_ON_READ = 0x10A1

    GET_DITHER_INFO_COUNT = 0x10A2
    GET_DITHER_INFO = 0x10A3
    GET_EMBED_FILE_INFO = 0x10B0
    SET_CLIPPING = 0x10C0
    GET_CLIPPING = 0x10C1
    GET_INSTRUMENT = 0x10D0
    SET_INSTRUMENT = 0x10D1
    GET_LOOP_INFO = 0x10E0
    GET_BROADCAST_INFO = 0x10F0
    SET_BROADCAST_INFO = 0x10F1
    GET_CHANNEL_MAP_INFO = 0x1100
    SET_CHANNEL_MAP_INFO = 0x1101
    RAW_DATA_NEEDS_ENDSWAP = 0x1110
    WAVEX_SET_AMBISONIC = 0x1200
    WAVEX_GET_AMBISONIC = 0x1201

    RF64_AUTO_DOWNGRADE = 0x1210

    SET_VBR_ENCODING_QUALITY = 0x1300
    SET_COMPRESSION_LEVEL = 0x1301

    SET_OGG_PAGE_LATENCY_MS = 0x1302
    SET_OGG_PAGE_LATENCY = 0x1303

    GET_BITRATE_MODE = 0x1304
    SET_BITRATE_MODE = 0x1305

    GET_OGG_STREAM_SERIALNO = 0x1306

    SET_CART_INFO = 0x1400
    GET_CART_INFO = 0x1401

    SET_ORIGINAL_SAMPLERATE = 0x1500
    GET_ORIGINAL_SAMPLERATE = 0x1501

    TEST_IEEE_FLOAT_REPLACE = 0x6001

  SFFormat* {.pure, size: sizeof(cint).} = enum
    # Subtypes
    PCM_S8 = 0x0001
    PCM_16 = 0x0002
    PCM_24 = 0x0003
    PCM_32 = 0x0004
    PCM_U8 = 0x0005
    FLOAT = 0x0006
    DOUBLE = 0x0007

    ULAW = 0x0010
    ALAW = 0x0011
    IMA_ADPCM = 0x0012
    MS_ADPCM = 0x0013

    GSM610 = 0x0020
    VOX_ADPCM = 0x0021
    NMS_ADPCM_16 = 0x0022
    NMS_ADPCM_24 = 0x0023
    NMS_ADPCM_32 = 0x0024

    G721_32 = 0x0030
    G723_24 = 0x0031
    G723_40 = 0x0032

    DWVW_12 = 0x0040
    DWVW_16 = 0x0041
    DWVW_24 = 0x0042
    DWVW_N = 0x0043

    DPCM_8 = 0x0050
    DPCM_16 = 0x0051

    VORBIS = 0x0060
    OPUS = 0x0064

    ALAC_16 = 0x0070
    ALAC_20 = 0x0071
    ALAC_24 = 0x0072
    ALAC_32 = 0x0073

    MPEG_LAYER_I = 0x0080
    MPEG_LAYER_II = 0x0081
    MPEG_LAYER_III = 0x0082

    # Main types
    WAV = 0x010000
    AIFF = 0x020000
    AU = 0x030000
    RAW = 0x040000
    PAF = 0x050000
    SVX = 0x060000
    NIST = 0x070000
    VOC = 0x080000

    IRCAM = 0x0A0000
    W64 = 0x0B0000
    MAT4 = 0x0C0000
    MAT5 = 0x0D0000
    PVF = 0x0E0000
    XI = 0x0F0000
    HTK = 0x100000
    SDS = 0x110000
    AVR = 0x120000
    WAVEX = 0x130000

    SD2 = 0x160000
    FLAC = 0x170000
    CAF = 0x180000
    WVE = 0x190000
    OGG = 0x200000
    MPC2K = 0x210000
    RF64 = 0x220000

  SFFormatMask* {.pure, size: sizeof(cint).} = enum
    SUBMASK = 0x0000FFFF
    TYPEMASK = 0x0FFF0000
    ENDMASK = 0x30000000

  # We put the endian-ness options in a separate enum, because Nim enums
  # don't allow duplicate values and values must be increasing.
  SFEndian* {.pure, size: sizeof(cint).} = enum
    FILE = 0x00000000
    LITTLE = 0x10000000
    BIG = 0x20000000
    CPU = 0x30000000

  SFStrType* {.pure, size: sizeof(cint).} = enum
    TITLE = 0x01
    COPYRIGHT = 0x02
    SOFTWARE = 0x03
    ARTIST = 0x04
    COMMENT = 0x05
    DATE = 0x06
    ALBUM = 0x07
    LICENSE = 0x08
    TRACKNUMBER = 0x09
    GENRE = 0x10

  SFChannelMap* {.pure, size: sizeof(cint).} = enum
    INVALID
    MONO
    LEFT
    RIGHT
    CENTER
    FRONT_LEFT
    FRONT_RIGHT
    FRONT_CENTER
    REAR_CENTER
    REAR_LEFT
    REAR_RIGHT
    LFE
    FRONT_LEFT_OF_CENTER
    FRONT_RIGHT_OF_CENTER
    SIDE_LEFT
    SIDE_RIGHT
    TOP_CENTER
    TOP_FRONT_LEFT
    TOP_FRONT_RIGHT
    TOP_FRONT_CENTER
    TOP_REAR_LEFT
    TOP_REAR_RIGHT
    TOP_REAR_CENTER
    AMBISONIC_B_W
    AMBISONIC_B_X
    AMBISONIC_B_Y
    AMBISONIC_B_Z
    MAX

  SFBitrateMode* {.pure, size: sizeof(cint).} = enum
    CONSTANT
    AVERAGE
    VARIABLE

  SFLoopMode* {.pure, size: sizeof(cint).} = enum
    NONE = 800
    FORWARD
    BACKWARD
    ALTERNATING

type
  SndFile* = distinct object

  SFCount* = int64

  SFInfo* = object
    frames*: SFCount
    samplerate*: cint
    channels*: cint
    format*: cint
    sections*: cint
    seekable*: cint

  SFFormatInfo* = object
    format*: cint
    name*: cstring
    extension*: cstring

  SFEmbedFileInfo* = object
    offset*: SFCount
    length*: SFCount

  SFCuePoint* = object
    indx*: int32
    position*: uint32
    fccChunk*: int32
    chunkStart*:int32
    blockStart*: int32
    sampleOffset*: uint32
    name*: array[256, char]

  SFLoop* = object
    mode*: SFLoopMode
    startPos*: uint32
    endPos*: uint32  # 'end' is a keyword in Nim
    count*: uint32

  SFInstrument* = object
    gain*: cint
    basenote*: byte
    detune*: byte
    velocityLo*: byte
    velocityHi*: byte
    keyLo*: byte
    keyHi*: byte
    loopCount*: cint
    loops*: array[16, SFLoop]

  SFLoopInfo* = object
    timeSigNum*: cshort
    timeSigDen*: cshort
    loopMode*: SFLoopMode
    numBeats*: cint
    bpm*: cfloat
    rootKey*: cint
    future*: array[6, cint]

  SFBroadcastInfo* = object
    description*: array[256, char]
    originator*: array[32, char]
    originatorReference*: array[32, char]
    originationDate*: array[10, char]
    originationTime*: array[8, char]
    timeReferenceLow*: uint32
    timeReferenceHigh*: uint32
    version*: cshort
    umid*: array[64, char]
    loudnessValue*: int16
    loudnessRange*: int16
    maxTruePeakLevel*: int16
    maxMomentaryLoudness*: int16
    maxShorttermLoudness*: int16
    reserved*: array[180, byte]
    codingHistorySize*: uint32
    codingHistory*: array[codingHistSize, char]

  SFCartTimer* = object
    usage*: array[4, char]
    value*: int32

  SFCartInfo* = object
    version*: array[4, char]
    title*: array[64, char]
    artist*: array[64, char]
    cutId*: array[64, char]
    clientId*: array[64, char]
    category*: array[64, char]
    classification*: array[64, char]
    outCue*: array[64, char]
    startDate*: array[10, char]
    startTime*: array[8, char]
    endDate*: array[10, char]
    endTime*: array[8, char]
    producerAppId*: array[64, char]
    producerAppVersion*: array[64, char]
    userDef*: array[64, char]
    levelReference*: int32
    postTimers*: array[8, SFCartTimer]
    reserved*: array[276, char]
    url*: array[1024, char]
    tagTextSize*: uint32
    tagText*: array[maxTagTextSize, char]

  SFChunkInfo* = object
    id*: array[64, char]
    idSize*: cuint
    dataLen*: cuint
    data*: ptr UncheckedArray[byte]

  SFChunkIterator* = distinct object


proc versionString*(): cstring {.libsnd, importc: "sf_version_string".}

proc open*(path: cstring, mode: SFMode, sfInfo: ptr SFInfo): ptr SndFile {.libsnd, importc: "sf_open".}
proc close*(sndfile: ptr SndFile): cint {.libsnd, importc: "sf_close", discardable.}

proc formatCheck*(info: ptr SFInfo): SFBool {.libsnd, importc: "sf_format_check".}

proc seek*(sndfile: ptr SndFile, frames: SFCount, whence: SFSeek): SFCount {.libsnd, importc: "sf_seek".}

proc command*(sndfile: ptr SndFile, cmd: SFCommand, data: pointer, datasize: cint): cint {.libsnd, importc: "sf_command", discardable.}

proc error*(sndfile: ptr SndFile): cint {.libsnd, importc: "sf_error".}
proc errorNumber*(errnum: int): cstring {.libsnd, importc: "sf_error_number".}
proc strError*(sndfile: ptr SndFile): cstring {.libsnd, importc: "sf_strerror".}

proc setString*(sndfile: ptr SndFile, strType: SFStrType, str: cstring): cint {.libsnd, importc: "sf_set_string".}
proc getString*(sndfile: ptr SndFile, strType: SFStrType): cstring {.libsnd, importc: "sf_get_string".}

proc currentByterate*(sndfile: ptr SndFile): cint {.libsnd, importc: "sf_current_byterate".}

proc readShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort, items: SFCount): SFCount {.libsnd, importc: "sf_read_short".}
proc readInt*(sndfile: ptr SndFile, bufferPtr: ptr cint, items: SFCount): SFCount {.libsnd, importc: "sf_read_int".}
proc readFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat, items: SFCount): SFCount {.libsnd, importc: "sf_read_float".}
proc readDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble, items: SFCount): SFCount {.libsnd, importc: "sf_read_double".}

proc readFShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort, frames: SFCount): SFCount {.libsnd, importc: "sf_readf_short".}
proc readFInt*(sndfile: ptr SndFile, bufferPtr: ptr cint, frames: SFCount): SFCount {.libsnd, importc: "sf_readf_int".}
proc readFFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat, frames: SFCount): SFCount {.libsnd, importc: "sf_readf_float".}
proc readFDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble, frames: SFCount): SFCount {.libsnd, importc: "sf_readf_double".}

proc writeShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort; items: SFCount): SFCount {.libsnd, importc: "sf_write_short".}
proc writeInt*(sndfile: ptr SndFile, bufferPtr: ptr cint; items: SFCount): SFCount {.libsnd, importc: "sf_write_int".}
proc writeFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat; items: SFCount): SFCount {.libsnd, importc: "sf_write_float".}
proc writeDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble; items: SFCount): SFCount {.libsnd, importc: "sf_write_double".}

proc writeFShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort; frames: SFCount): SFCount {.libsnd, importc: "sf_writef_short".}
proc writeFInt*(sndfile: ptr SndFile, bufferPtr: ptr cint; frames: SFCount): SFCount {.libsnd, importc: "sf_writef_int".}
proc writeFFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat; frames: SFCount): SFCount {.libsnd, importc: "sf_writef_float".}
proc writeFDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble; frames: SFCount): SFCount {.libsnd, importc: "sf_writef_double".}

proc readRaw*(sndfile: ptr SndFile, bufferPtr: ptr byte, numBytes: SFCount): SFCount {.libsnd, importc: "sf_read_raw".}
proc writeRaw*(sndfile: ptr SndFile, bufferPtr: ptr byte, numBytes: SFCount) {.libsnd, importc: "sf_write_raw".}

proc writeSync*(sndfile: ptr SndFile) {.libsnd, importc: "sf_write_sync".}

proc setChunk*(sndfile: ptr SndFile, chunkInfo: ptr SFChunkInfo): SFErr {.libsnd, importc: "sf_set_chunk".}
proc getChunkIterator*(sndfile: ptr SndFile, chunkInfo: ptr SFChunkInfo): ptr SFChunkIterator {.libsnd, importc: "sf_get_chunk_iterator".}
proc nextChunkIterator*(it: ptr SFChunkIterator): ptr SFChunkIterator {.libsnd, importc: "sf_next_chunk_iterator".}
proc getChunkSize*(it: ptr SFChunkIterator, chunkInfo: ptr SFChunkInfo): SFErr {.libsnd, importc: "sf_get_chunk_size".}
proc getChunkData*(it: ptr SFChunkIterator, chunkInfo: ptr SFChunkInfo): SFErr {.libsnd, importc: "sf_get_chunk_data".}


================================================
FILE: src/sndfile/chunks.nim
================================================
import api

# Data types for WAV file 'smpl' chunks
type
  ## A single sample loop
  SmplLoop* = object
    id*: int32
    loopType*: int32
    startPos*: int32
    endPos*: int32
    fraction*: int32
    playCount*: int32

  ## A 'smpl' chunk, containing information for sample player instruments,
  ## such as manufacturer and product ID of the instrument that is meant
  ## to use this sample, the MIDI root note, zero or more sample loop
  ## definitions and, optionally, extra sample player specific data.
  SmplChunk* = object
    manufacturer*: array[4, byte]
    product*: int32
    samplePeriod*: int32
    midiUnityNote*: int32
    midiPitchFraction*: int32
    smpteFormat*: int32
    smpteOffset*: int32
    loopCount*: int32
    samplerDataLen*: int32
    loops*: UncheckedArray[SmplLoop]


##
## Iterate over all chunks in the audio file, whose chunk ID starts with
## `chunk_id`. The loop variable is assigned a `seq[byte]` with the data of
## each matching chunk.
##
iterator chunks*(sndfile: ptr SndFile, chunk_id: string): seq[byte] =
  var chunkInfo = SFChunkInfo()

  if chunk_id.len - 1 > chunkInfo.id.high:
    raise newException(ValueError, "Length of chunk_id must be <= 64.")

  chunkInfo.idSize = chunk_id.len.cuint
  chunkInfo.id[0..<chunk_id.len] = chunk_id

  var it = getChunkIterator(sndfile, chunkInfo.addr)

  while not it.isNil:
    var data = newSeq[byte]()

    if getChunkSize(it, chunkInfo.addr) == NO_ERROR and chunkInfo.dataLen > 0:
      data.setLen(chunkInfo.dataLen)
      chunkInfo.data = cast[ptr UncheckedArray[byte]](data[0].addr)

      if getChunkData(it, chunkInfo.addr) != NO_ERROR:
        # XXX Better exception type and error message
        raise newException(IOError, "Could not read next chunk.")
    else:
      raise newException(IOError, "Could not get chunk size.")

    yield data
    it = nextChunkIterator(it)


================================================
FILE: src/sndfile.nim
================================================
import sndfile/api
import sndfile/chunks
export api
export chunks


================================================
FILE: tests/config.nims
================================================
switch("path", "$projectDir/../src")


================================================
FILE: tests/test_chunks.nim
================================================
import std/[os, unittest]

import sndfile


proc check_smpl_chunk(
    sf: ptr SndFile,
    manufacturer: array[4, byte],
    samplePeriod: int32,
    midiUnityNote: int32,
    samplerDataLen: int32,
    loopCount: int32,
    loopType: int32,
    startPos: int32,
    endPos: int32
  ): bool =
  var found = 0

  for chunk in sf.chunks("smpl"):
    found += 1
    check(typeof(chunk) is seq[byte])

    if found == 1:
      # Only check the first 'smpl' chunk - there should be only one.
      var smpl = cast[ptr SmplChunk](chunk[0].addr)

      check(smpl.manufacturer == manufacturer)
      check(smpl.samplePeriod == samplePeriod)
      check(smpl.midiUnityNote == midiUnityNote)
      check(smpl.samplerDataLen == samplerDataLen)

      check(smpl.loopCount == loopCount)
      check(smpl.loops[0].loopType == loopType)
      check(smpl.loops[0].startPos == startPos)
      check(smpl.loops[0].endPos == endPos)

      let expectedSize = sizeof(SmplChunk) + smpl.samplerDataLen + smpl.loopCount * sizeof(SmplLoop)
      check(chunk.len == expectedSize)

  return found == 1


suite "Tests for reading chunks from a WAV file":
  test "test read smpl chunk":
    var info: SFInfo
    var sf: ptr SndFile
    info.format = 0

    sf = open(cstring(getAppDir() / "test_chunks.wav"), READ, info.addr)
    check(not sf.isNil)
    defer: close(sf)

    check(
      check_smpl_chunk(
        sf,
        manufacturer = [0x47.byte, 0, 0, 1],
        samplePeriod = 22675,
        midiUnityNote = 1,
        samplerDataLen = 18,
        loopCount = 1,
        loopType = 0,
        startPos = 56252,
        endPos = 240748
      )
    )

  test "test write and read back smpl chunk":
    var infoIn, infoOut: SFInfo
    var sfIn, sfOut: ptr SndFile
    infoIn.format = 0

    sfIn = open(cstring(getAppDir() / "sine.wav"), READ, infoIn.addr)
    check(not sfIn.isNil)

    infoOut = infoIn
    sfOut = open(cstring(getAppDir() / "sineloop.wav"), WRITE, infoOut.addr)
    check(not sfOut.isNil)

    var ci = SFChunkInfo()
    ci.id[0..3] = "smpl"
    ci.idSize = 4
    ci.dataLen = sizeof(SmplChunk) + sizeof(SmplLoop)
    ci.data = cast[ptr UncheckedArray[byte]](alloc0(ci.dataLen))

    var smpl = cast[ptr SmplChunk](ci.data)
    smpl.manufacturer = [1.byte, 0, 0, 1]
    smpl.samplePeriod = int32(1_000_000_000 / infoIn.samplerate)
    smpl.midiUnityNote = 69
    smpl.loopCount = 1

    var loop = cast[ptr SmplLoop](smpl.loops[0].addr)
    loop.startPos = 0
    loop.endPos = infoIn.frames.int32

    check(sfOut.setChunk(ci.addr) == NO_ERROR)

    var
      frames, readcount: cint
      buf: array[1024, cfloat]

    frames = cint(1024 / infoOut.channels)
    readcount = frames

    while readcount > 0:
      readcount = sfIn.readFFloat(buf[0].addr, frames).cint
      discard sfOut.writeFFLoat(buf[0].addr, readcount)

    sfIn.close()
    sfOut.close()
    dealloc(ci.data)

    infoIn.format = 0
    sfIn = open(cstring(getAppDir() / "sineloop.wav"), READ, infoIn.addr)
    check(not sfIn.isNil)

    check(
      check_smpl_chunk(
        sfIn,
        manufacturer = [1.byte, 0, 0, 1],
        samplePeriod = 20833,
        midiUnityNote = 69,
        samplerDataLen = 0,
        loopCount = 1,
        loopType = 0,
        startPos = 0,
        endPos = 48_000
      )
    )

    sfIn.close()


================================================
FILE: tests/test_testwav.nim
================================================
import std/[os, unittest]

import sndfile

suite "Tests for reading a simple WAV file":
  test "test reading info":
    var info: SFInfo
    var sndFile: ptr SndFile
    info.format = 0

    sndFile = open(cstring(getAppDir() / "sine.wav"), READ, info.addr)
    check(not sndFile.isNil)

    # expect info read from sound file header to be valid
    check(formatCheck(info.addr) == TRUE)

    check($info == "(frames: 48000, samplerate: 48000, channels: 1, format: 65538, sections: 1, seekable: 1)")

    check(seek(sndFile, 5, SFSeek.SET) == 5)
    discard seek(sndFile, 0, SFSeek.SET)

    let num_items = info.channels * info.frames
    check(num_items == 48_000)

    var buffer = newSeq[cint](num_items)
    let items_read = read_int(sndFile, buffer[0].addr, num_items)
    check(items_read == 48_000)

    close(sndFile)


================================================
FILE: tools/build_docs.sh
================================================
#!/bin/bash

PROJECT="sndfile"
REPO_URL="https://github.com/SpotlightKid/nim-sndfile"
DOC_DIR="$(pwd)/.gh-pages"

nimble doc \
    --index:on \
    --project \
    --git.url:"$REPO_URL" \
    --git.commit:master \
    --out:"$DOC_DIR" \
    src/$PROJECT.nim
cp "$DOC_DIR"/$PROJECT.html "$DOC_DIR"/index.html
xdg-open file://"$DOC_DIR"/index.html
Download .txt
gitextract_o4nvxbth/

├── LICENSE.md
├── README.md
├── examples/
│   ├── config.nims
│   ├── dump_loops.nim
│   ├── libsamplerate.nim
│   ├── list_formats.nim
│   ├── playfile_jack.nim
│   ├── playfile_sdl.nim
│   └── signal.nim
├── sndfile.nimble
├── src/
│   ├── sndfile/
│   │   ├── api.nim
│   │   └── chunks.nim
│   └── sndfile.nim
├── tests/
│   ├── config.nims
│   ├── test_chunks.nim
│   └── test_testwav.nim
└── tools/
    └── build_docs.sh
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (38K chars).
[
  {
    "path": "LICENSE.md",
    "chars": 1178,
    "preview": "# MIT License\n\nCopyright (c) 2014 - 2017 Julien Aubert https://github.com/julienaubert\n\nCopyright (c) 2024 - 2025 Christ"
  },
  {
    "path": "README.md",
    "chars": 3573,
    "preview": "nim-sndfile\n===========\n\nA wrapper of [libsndfile] for the [Nim] programming language.\n\n\n# API\n\nThe libsndfile [API] onl"
  },
  {
    "path": "examples/config.nims",
    "chars": 37,
    "preview": "switch(\"path\", \"$projectDir/../src\")\n"
  },
  {
    "path": "examples/dump_loops.nim",
    "chars": 1083,
    "preview": "## Read an audio sample file and print information about defined sample loops\n\nimport std/[os, strformat]\n\nimport sndfil"
  },
  {
    "path": "examples/libsamplerate.nim",
    "chars": 835,
    "preview": "when defined(windows):\n  const soname = \"(|lib)samplerate(|-0).dll\"\nelif defined(macosx):\n  const soname = \"libsamplerat"
  },
  {
    "path": "examples/list_formats.nim",
    "chars": 1523,
    "preview": "## List all file and sample formats supported by the used libsndfile\n\nimport std/strformat\nimport sndfile\n\nvar\n  sfinfo:"
  },
  {
    "path": "examples/playfile_jack.nim",
    "chars": 6042,
    "preview": "## Read an audio file (e.g. WAV or Ogg Vorbis) with libsndfile and play it via JACK\n\nimport std/[logging, os, strformat]"
  },
  {
    "path": "examples/playfile_sdl.nim",
    "chars": 1836,
    "preview": "## Read an audio file (e.g. WAV or Ogg Vorbis) with libsndfile and play it with sdl2\n\nimport std/[math, os, strformat]\n\n"
  },
  {
    "path": "examples/signal.nim",
    "chars": 536,
    "preview": "import system/ansi_c\n\nexport SIG_DFL, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV\n\nwhen not defined(windows):\n  export SIGP"
  },
  {
    "path": "sndfile.nimble",
    "chars": 842,
    "preview": "version = \"0.2.6\"\nauthor = \"Julien Aubert, Christopher Arndt\"\ndescription = \"Wrapper for libsndfile\"\nlicense = \"MIT\"\n\nsr"
  },
  {
    "path": "src/sndfile/api.nim",
    "chars": 12123,
    "preview": "when defined(windows):\n  const soname = \"(|lib)sndfile(|-1|-2).dll\"\nelif defined(macosx):\n  const soname = \"libsndfile.d"
  },
  {
    "path": "src/sndfile/chunks.nim",
    "chars": 1874,
    "preview": "import api\n\n# Data types for WAV file 'smpl' chunks\ntype\n  ## A single sample loop\n  SmplLoop* = object\n    id*: int32\n "
  },
  {
    "path": "src/sndfile.nim",
    "chars": 66,
    "preview": "import sndfile/api\nimport sndfile/chunks\nexport api\nexport chunks\n"
  },
  {
    "path": "tests/config.nims",
    "chars": 37,
    "preview": "switch(\"path\", \"$projectDir/../src\")\n"
  },
  {
    "path": "tests/test_chunks.nim",
    "chars": 3307,
    "preview": "import std/[os, unittest]\n\nimport sndfile\n\n\nproc check_smpl_chunk(\n    sf: ptr SndFile,\n    manufacturer: array[4, byte]"
  },
  {
    "path": "tests/test_testwav.nim",
    "chars": 827,
    "preview": "import std/[os, unittest]\n\nimport sndfile\n\nsuite \"Tests for reading a simple WAV file\":\n  test \"test reading info\":\n    "
  },
  {
    "path": "tools/build_docs.sh",
    "chars": 346,
    "preview": "#!/bin/bash\n\nPROJECT=\"sndfile\"\nREPO_URL=\"https://github.com/SpotlightKid/nim-sndfile\"\nDOC_DIR=\"$(pwd)/.gh-pages\"\n\nnimble"
  }
]

About this extraction

This page contains the full source code of the SpotlightKid/nim-sndfile GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (35.2 KB), approximately 11.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!