[
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2014 - 2017 Julien Aubert https://github.com/julienaubert\n\nCopyright (c) 2024 - 2025 Christopher Arndt <chris@chrisarndt.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "nim-sndfile\n===========\n\nA wrapper of [libsndfile] for the [Nim] programming language.\n\n\n# API\n\nThe libsndfile [API] only has a relatively small number of functions, but it\ndefines quite a few types and enum values. The `sndfile` Nim module provides\ndefinitions for all types and enums (except those for deprecated and not-yet\nimplemented functions) and wraps all functions, *except* the following:\n\n* `sf_open_fd`\n* `sf_open_virtual`\n* `sf_wchar_open`\n\nThese missing functions may be added in future versions, if it makes sense to\nuse them in Nim.\n\n\n# Naming conventions\n\n* The `sf_` prefix has been removed from function names, i.e. instead of\n  `sf_read_double`, use just `read_double` (or `readDouble`, Nim's\n  identifier naming rules make no distinction).\n  The wrapper code generally uses lower `camelCase`.\n* All values within each enum have been stripped of their unique prefix and\n  all enums are marked `{.pure.}`. This means you should prefix each enum\n  symbol with its enum type name. For example `SF_FORMAT_WAV` becomes\n  `SFFormat.WAV` (or `SF_Format.Wav` etc.). If the value symbol is\n  non-ambiguous, the enum name can be omitted, e.g. `GET_LOG_INFO` instead\n  of `SFCommand.GET_LOG_INFO`.\n* typedefs and structs start with a capital letter and retain their `SF`\n  prefix, but Nim's identifier naming rules allow to omit underscores and use\n  whatever case after the first letter. For example, instead of `SF_INFO`, you\n  can use `SFInfo`, `SF_info` etc. The wrapper code generally uses\n  `PascalCase`.\n\n\n## Special cases\n\n| C prefix / name                | Nim enum / symbol      |\n| ------------------------------ | ---------------------- |\n| `SFM_`                         | `SFMode`               |\n| `SF_STR_`                      | `SFStrType`            |\n| `SF_LOOP_`                     | `SFLoopMode`           |\n| `SFC_`                         | `SFCommand`            |\n| `SNDFILE`                      | `SndFile`              |\n| `sf_count_t`                   | `SFCount`              |\n| `SF_TRUE`                      | `SFBool.TRUE`          |\n| `SF_FALSE`                     | `SFBool.FALSE`         |\n| `SF_STR_FIRST`\t               | `SFStrType.low`        |\n| `SF_STR_LAST`\t                 | `SFStrType.high`       |\n| `SF_AMBISONIC_NONE`            | `SFAmbisonic.NONE`     |\n| `SF_AMBISONIC_B_FORMAT`        | `SFAmbisonic.B_FORMAT` |\n| `SF_INSTRUMENT.loops`          | `SFLoop`               |\n| `SF_INSTRUMENT.loops[n].end`   | `SFLoops.endPos`       |\n| `SF_INSTRUMENT.loops[n].start` | `SFLoops.startPos`     |\n\n\n## Structs with customizable-sized array fields\n\n* `SF_CUES`: Define / allocate `array[<n>, SFCuePoint]` as needed\n* `SF_BROADCAST_INFO`:  Set number of entries in the `codingHistory` array\n  member of `SFBroadcastInfo` with `-d:sfCodingHistSize=<n>` at compile time.\n  The default size is 256.\n* `SF_CART_INFO`:  Set number of entries in the `tagText` array member of\n  `SFCartInfo` with `-d:sfMaxTagTextSize=<n>` at compile time. The default\n  size is 256.\n\n\n## Authors\n\n*nim-sndfile* was written by [Julien Aubert](https://github.com/julienaubert)\nand this fork was updated, re-factored and extended by\n[Christopher Arndt](https://github.com/SpotlightKid), while also applying\nsome patches by [James Bradbury](https://github.com/jamesb93).\n\n\n## License\n\nThis software is released under the *MIT License*. See the file\n[LICENSE.md](./LICENSE.md) for more information.\n\n\n[API]: https://libsndfile.github.io/libsndfile/api.html\n[libsndfile]: https://libsndfile.github.io/libsndfile/\n[Nim]: https://nim-lang.org/\n"
  },
  {
    "path": "examples/config.nims",
    "content": "switch(\"path\", \"$projectDir/../src\")\n"
  },
  {
    "path": "examples/dump_loops.nim",
    "content": "## Read an audio sample file and print information about defined sample loops\n\nimport std/[os, strformat]\n\nimport sndfile\n\n\nproc main() =\n  if paramCount() != 1:\n    echo \"Usage: dump_loops <filename>\"\n    quit(QuitFailure)\n\n  var filename = paramStr(1)\n\n  # Open the file\n  var info: SFInfo\n  var sf = sndfile.open(filename.cstring, SFMode.READ, info.addr)\n\n  if sf.isNil:\n    echo $sf.strerror()\n    quit(QuitFailure)\n\n  echo &\"File: {filename}\"\n  echo &\"Sample frames: {info.frames}\"\n\n  var instr = SFInstrument()\n\n  if SFBool(command(sf, GET_INSTRUMENT, instr.addr, sizeof(instr).cint)) == FALSE:\n    echo \"Loops: no instrument data found\"\n    quit(QuitSuccess)\n  else:\n    echo &\"Root note: {instr.basenote}\"\n    echo &\"Loops: {instr.loopCount}\"\n\n  for i in 0..<instr.loopCount:\n    # endPos is the first sample position *after* the loop!\n    stdOut.write &\"* #{i}: start={instr.loops[i].startPos}, \"\n    stdOut.write &\"end={instr.loops[i].endPos - 1}, \"\n    stdOut.write &\"mode={instr.loops[i].mode}, \"\n    stdOut.write &\"count={instr.loops[i].count}\\n\"\n\n  sf.close()\n\n\nmain()\n"
  },
  {
    "path": "examples/libsamplerate.nim",
    "content": "when defined(windows):\n  const soname = \"(|lib)samplerate(|-0).dll\"\nelif defined(macosx):\n  const soname = \"libsamplerate.dylib\"\nelse:\n  const soname = \"libsamplerate.so(|.0)\"\n\n{.pragma: libsrc, cdecl, dynlib: soname.}\n\ntype\n  SrcData* = object\n    dataIn*: ptr UncheckedArray[cfloat]\n    dataOut*: ptr UncheckedArray[cfloat]\n    inputFrames*: clong\n    outputFrames*: clong\n    inputFramesUsed*: clong\n    outputFramesGen*: clong\n    endOfInput*: cint\n    srcRatio*: float64\n\n  Converter* {.pure, size: sizeof(cint).} = enum\n    SINC_BEST_QUALITY = 0\n    SINC_MEDIUM_QUALITY = 1\n    SINC_FASTEST = 2\n    ZERO_ORDER_HOLD = 3\n    LINEAR = 4\n\nproc srcSimple*(data: ptr SrcData, converter_type: Converter, channels: cint): cint {.libsrc, importc: \"src_simple\".}\nproc srcStrError*(error: cint): cstring {.libsrc, importc: \"src_strerror\".}\n"
  },
  {
    "path": "examples/list_formats.nim",
    "content": "## List all file and sample formats supported by the used libsndfile\n\nimport std/strformat\nimport sndfile\n\nvar\n  sfinfo: SFInfo\n  fmtinfo: SFFormatInfo\n  simpleCount, majorCount, subtypeCount: cint\n\necho &\"Version: {versionString()}\\n\"\n\ncommand(nil, GET_SIMPLE_FORMAT_COUNT, simpleCount.addr, sizeof(cint).cint)\ncommand(nil, GET_FORMAT_MAJOR_COUNT, majorCount.addr, sizeof(cint).cint)\ncommand(nil, GET_FORMAT_SUBTYPE_COUNT, subtypeCount.addr, sizeof(cint).cint)\n\n\nfor format in  0..<simpleCount:\n  fmtinfo.format = format.cint\n  command(nil, GET_SIMPLE_FORMAT, fmtinfo.addr, sizeof(fmtinfo).cint)\n  echo &\"{fmtinfo.name} (extension: '.{fmtinfo.extension}')\"\n\nsfinfo.samplerate = 48000\n\nfor major_format in  0..<major_count:\n  fmtinfo.format = major_format.cint\n  command(nil, GET_FORMAT_MAJOR, fmtinfo.addr, sizeof(fmtinfo).cint)\n  echo &\"{fmtinfo.name} (extension '.{fmtinfo.extension}')\"\n\n  var format = fmtinfo.format\n\n  for subtype in 0..<subtype_count:\n    fmtinfo.format = subtype.cint\n    command(nil, GET_FORMAT_SUBTYPE, fmtinfo.addr, sizeof(fmtinfo).cint) ;\n\n    format = (format and SFFormatMask.TYPEMASK.cint) or fmtinfo.format\n    sfinfo.format = format\n    var valid = false\n    sfinfo.channels = 1\n\n    if formatCheck(sfinfo.addr) == TRUE:\n      valid = true\n\n    sfinfo.channels = 2\n\n    if formatCheck(sfinfo.addr) == TRUE:\n      valid = true\n\n    if valid:\n      if fmtinfo.extension.isNil:\n        echo &\"   {fmtinfo.name}\"\n      else:\n        echo &\"   {fmtinfo.name} (extension '.{fmtinfo.extension}')\"\n"
  },
  {
    "path": "examples/playfile_jack.nim",
    "content": "## Read an audio file (e.g. WAV or Ogg Vorbis) with libsndfile and play it via JACK\n\nimport std/[logging, os, strformat]\n\nimport jacket\nimport libsamplerate\nimport signal\nimport sndfile\n\nconst\n  MaxChannels = 2\n  Gain = 0.8\n\nvar\n  jclient: Client\n  outPortL, outPortR: Port\n  status: cint\n  exitSignalled: bool = false\n  log = newConsoleLogger(when defined(release): lvlInfo else: lvlDebug)\n\ntype\n  Sample = DefaultAudioSample\n  SampleBuffer = ptr UncheckedArray[Sample]\n  AudioFile = object\n    channels: int\n    samplerate: int\n    samples: SampleBuffer\n    frames: int\n    pos: int\n\n\nproc cleanup() =\n  debug \"Cleaning up...\"\n  if jclient != nil:\n    jclient.deactivate()\n    jclient.clientClose()\n    jclient = nil\n\nproc errorCb(msg: cstring) {.cdecl.} =\n  # Suppress verbose JACK error messages when server is not available by\n  # default. Pass ``lvlAll`` when creating the logger to enable them.\n  debug \"JACK error: \" & $msg\n\nproc signalCb(sig: cint) {.noconv.} =\n  debug \"Received signal: \" & $sig\n  exitSignalled = true\n\nproc shutdownCb(arg: pointer = nil) {.cdecl.} =\n  warn \"JACK server has shut down.\"\n  exitSignalled = true\n\nproc readAudio(filename: string): AudioFile =\n  var info: SFInfo\n  var sf = sndfile.open(filename.cstring, SFMode.READ, info.addr)\n\n  if sf.isNil:\n    echo $sf.strError()\n    quit QuitFailure\n\n  defer: sf.close()\n\n  if info.channels > MaxChannels:\n    raise newException(ValueError, \"Only mono or stereo files are suported.\")\n\n  let samples = createSharedU(Sample, info.frames * info.channels)\n\n  if samples.isNil:\n    raise newException(ResourceExhaustedError, \"Error allocating memory for sample buffer\")\n\n  var samplesRead = sf.readFFloat(samples, info.frames)\n\n  if samplesRead != info.frames:\n    warn \"Mismatch between # samples read (as returned by sf_readf_float) and info.frames:\"\n    warn &\"{samplesRead} != {info.frames}\"\n\n  result.frames = if samplesRead != 0: samplesRead else: info.frames\n  result.samples = cast[SampleBuffer](samples)\n  result.channels = info.channels\n  result.samplerate = info.samplerate\n  result.pos = 0\n\nproc convertSampleRate(input: SampleBuffer, inputSamples, inputRate, outputRate, channels: int): tuple[samples: SampleBuffer, frames: int] =\n  var\n    data: SrcData\n    # Calculate the number of output frames\n    outputFrames = int(inputSamples * outputRate / inputRate)\n\n  var output = createSharedU(Sample, outputFrames * channels)\n\n  if output.isNil:\n    raise newException(ResourceExhaustedError, \"Error allocating memory for resample buffer\")\n\n  # Set up the SRC_DATA structure\n  data.dataIn = cast[ptr UncheckedArray[cfloat]](input)\n  data.inputFrames = inputSamples.clong\n  data.dataOut = cast[ptr UncheckedArray[cfloat]](output)\n  data.outputFrames = outputFrames.clong\n  data.srcRatio = float64(outputRate / inputRate)\n  data.endOfInput = 0\n\n  # Perform the sample rate conversion\n  var error = srcSimple(addr data, SINC_FASTEST, channels.cint)\n  if error != 0:\n    freeShared(output)\n    raise newException(IOError, &\"Error during sample rate conversion: {srcStrError(error)}\")\n  else:\n    # Set output values\n    result.samples = cast[SampleBuffer](output)\n    result.frames = data.outputFramesGen.int\n\nproc processCb(nFrames: NFrames, arg: pointer): cint {.cdecl.} =\n  var outL = cast[SampleBuffer](portGetBuffer(outPortL, nFrames))\n  var outR = cast[SampleBuffer](portGetBuffer(outPortR, nFrames))\n  let audio = cast[ptr AudioFile](arg)\n\n  for i in 0 ..< nFrames:\n    outL[i] = audio.samples[audio.pos] * Gain\n    if audio.channels == 1:\n      outR[i] = audio.samples[audio.pos] * Gain\n    else:\n      outR[i] = audio.samples[audio.pos + 1] * Gain\n\n    audio.pos += audio.channels\n\n    if audio.pos >= audio.frames * audio.channels:\n      audio.pos = 0\n\n  return 0\n\n\nproc main() =\n  addHandler(log)\n\n  if paramCount() != 1:\n    echo \"Usage: playfile_jacket <filename>\"\n    quit(QuitFailure)\n\n  # Print libsndfile version\n  info &\"libsdnfile version: {versionString()}\"\n  # Print JACK version\n  info &\"JACK version: {getVersionString()}\"\n\n  # Create JACK client\n  setErrorFunction(errorCb)\n  jclient = clientOpen(\"playfile_jack\", NullOption, status.addr)\n  debug &\"JACK server status: {status}\"\n\n  if jclient == nil:\n    error getJackStatusErrorString(status)\n    quit QuitFailure\n\n  var audio: AudioFile\n\n  try:\n    audio = readAudio(paramStr(1))\n  except CatchableError:\n    error getCurrentExceptionMsg()\n    cleanup()\n    quit QuitFailure\n\n  defer:\n    cleanup()\n    freeShared(audio.samples)\n\n  info &\"Channels: {audio.channels}\"\n  info &\"Samplerate: {audio.samplerate}\"\n  info &\"Frames: {audio.frames}\"\n\n  var jackSampleRate = int(jclient.getSamplerate())\n\n  if audio.samplerate != jackSampleRate:\n    info &\"Resampling audio to {jackSampleRate} Hz ...\"\n    try:\n      var resampled = convertSampleRate(audio.samples, audio.frames, audio.samplerate, jackSampleRate, audio.channels)\n      freeShared(audio.samples)\n      audio.samples = resampled.samples\n      audio.frames = resampled.frames\n    except CatchableError:\n      error getCurrentExceptionMsg()\n      quit QuitFailure\n    info \"Ready.\"\n\n  # Set up signal handlers to clean up on exit\n  when defined(windows):\n    setSignalProc(signalCb, SIGABRT, SIGINT, SIGTERM)\n  else:\n    setSignalProc(signalCb, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM)\n\n  # Register JACK callbacks\n  if jclient.setProcessCallback(processCb, audio.addr) != 0:\n    error \"Could not set JACK process callback function.\"\n    quit QuitFailure\n\n  jclient.onShutdown(shutdownCb)\n\n  # Create output ports\n  outPortL = jclient.portRegister(\"out_1\", JackDefaultAudioType, PortIsOutput, 0)\n  outPortR = jclient.portRegister(\"out_2\", JackDefaultAudioType, PortIsOutput, 0)\n\n  # Activate JACK client ...\n  if jclient.activate() == 0:\n    # Connect our output ports to system playback ports\n    jclient.connect(outPortL.portName(), \"system:playback_1\")\n    jclient.connect(outPortR.portName(), \"system:playback_2\")\n\n    # ... and keep running until a signal is received\n    while not exitSignalled:\n        sleep(50)\n\n\nmain()\n"
  },
  {
    "path": "examples/playfile_sdl.nim",
    "content": "## Read an audio file (e.g. WAV or Ogg Vorbis) with libsndfile and play it with sdl2\n\nimport std/[math, os, strformat]\n\nimport sndfile\nimport sdl2, sdl2/audio\n\nif paramCount() != 1:\n  echo \"Usage: playfile <filename>\"\n  quit(QuitFailure)\n\nvar filename = paramStr(1)\n\n# Print libsndfile version\necho &\"libsdnfile version: {versionString()}\"\n\n# Print SDL version\nvar version: SDL_Version\nsdl2.getVersion(version)\necho &\"SDL version: {version.major}.{version.minor}.{version.patch}\"\n\n# Open the file\nvar info: SFInfo\nvar file = sndfile.open(filename.cstring, SFMode.READ, info.addr)\n\nif file.isNil:\n  echo $file.strerror()\n  quit(QuitFailure)\n\necho &\"Channels: {info.channels}\"\necho &\"Frames: {info.frames}\"\necho &\"Samplerate: {info.samplerate}\"\necho &\"Format: {info.format}\"\n\n# Callback procedure for audio playback\nconst bufferSizeInSamples = 4096\n\nproc audioCallback(userdata: pointer; stream: ptr uint8; len: cint) {.cdecl.} =\n  var buffer: array[bufferSizeInSamples, cfloat]\n  let count = file.readFloat(addr buffer[0], bufferSizeInSamples)\n\n  if count == 0:\n    echo \"End of file reached\"\n    quit(0)\n\n  for i in 0..<count:\n    cast[ptr int16](cast[int](stream) + i * 2)[] = int16(round(buffer[i] * 0.8 * 32760))\n    # Without the factor of 0.8, the sound gets distorted for my ogg example file\n\n# Init audio playback\nif sdl2.init(INIT_AUDIO) != SdlSuccess:\n  echo \"Couldn't initialize SDL\"\n  quit(QuitFailure)\n\nvar aspec: AudioSpec\naspec.freq = info.samplerate\naspec.format = AUDIO_S16\naspec.channels = info.channels.uint8\naspec.samples = bufferSizeInSamples\naspec.padding = 0\naspec.callback = audioCallback\naspec.userdata = nil\n\nif openAudio(addr aspec, nil) != 0:\n  echo &\"Couldn't open audio device: {getError()}\"\n  quit(QuitFailure)\n\n# Start playback and wait in a loop\npauseAudio(0)\necho \"Playing...\"\n\nwhile true:\n  delay(100)\n"
  },
  {
    "path": "examples/signal.nim",
    "content": "import system/ansi_c\n\nexport SIG_DFL, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV\n\nwhen not defined(windows):\n  export SIGPIPE, SIGTERM\n  var\n    SIG_IGN* {.importc: \"SIG_IGN\", header: \"<signal.h>\".}: cint\n    SIGHUP* {.importc: \"SIGHUP\", header: \"<signal.h>\".}: cint\n    SIGQUIT* {.importc: \"SIGQUIT\", header: \"<signal.h>\".}: cint\nelse:\n  const SIGTERM* = cint(15)\n\ntype CSighandlerT = proc (a: cint) {.noconv.}\n\nproc setSignalProc* (`proc`: CSighandlerT, signals: varargs[cint]) =\n  for sig in signals:\n    discard c_signal(sig, `proc`)\n"
  },
  {
    "path": "sndfile.nimble",
    "content": "version = \"0.2.6\"\nauthor = \"Julien Aubert, Christopher Arndt\"\ndescription = \"Wrapper for libsndfile\"\nlicense = \"MIT\"\n\nsrcDir = \"src\"\nskipDirs = @[\"examples\"]\n\nrequires \"nim >= 2.0\"\n\ntaskrequires \"examples\", \"sdl2\"\ntaskrequires \"examples\", \"jacket >= 0.2.0\"\ntaskrequires \"examples_debug\", \"sdl2\"\ntaskrequires \"examples_debug\", \"jacket >= 0.2.0\"\n\nlet examples = @[\n    \"dump_loops\",\n    \"list_formats\",\n    \"playfile_jack\",\n    \"playfile_sdl\",\n]\n\ntask examples, \"Build examples (release)\":\n    for example in examples:\n        echo \"Building example '\" & example & \"'...\"\n        selfExec(\"compile -d:release -d:strip examples/\" & example & \".nim\")\n\ntask examples_debug, \"Build examples (debug)\":\n    for example in examples:\n        echo \"Building example '\" & example & \"' (debug)...\"\n        selfExec(\"compile examples/\" & example & \".nim\")\n"
  },
  {
    "path": "src/sndfile/api.nim",
    "content": "when defined(windows):\n  const soname = \"(|lib)sndfile(|-1|-2).dll\"\nelif defined(macosx):\n  const soname = \"libsndfile.dylib\"\nelse:\n  const soname = \"libsndfile.so(|.1)\"\n\n{.pragma: libsnd, cdecl, dynlib: soname.}\n\n\nconst codingHistSize {.intdefine: \"sfCodingHistSize\".}: uint32 = 256\nconst maxTagTextSize {.intdefine: \"sfMaxTagTextSize\".}: uint32 = 256\n\n\ntype\n  # The following three enums are one enum in soundfile.h\n  # Separated here into three enums for clarity\n  SFBool* {.pure, size: sizeof(cint).} = enum\n    FALSE\n    TRUE\n\n  SFMode* {.pure, size: sizeof(cint).} = enum\n    READ = 0x10\n    WRITE = 0x20\n    RDWR = 0x30\n\n  SFAmbisonic* {.pure, size: sizeof(cint).} = enum\n    NONE = 0x40\n    B_FORMAT = 0x41\n\n  SFSeek* {.pure, size: sizeof(cint).} = enum\n    SET\n    CUR\n    END\n\n  SFErr* {.pure, size: sizeof(cint).} = enum\n    NO_ERROR\n    UNRECOGNISED_FORMAT\n    SYSTEM\n    MALFORMED_FILE\n    UNSUPPORTED_ENCODING\n\n  SFCommand* {.pure, size: sizeof(cint).} = enum\n    GET_LIB_VERSION = 0x1000\n    GET_LOG_INFO = 0x1001\n    GET_CURRENT_SF_INFO = 0x1002\n    GET_NORM_DOUBLE = 0x1010\n    GET_NORM_FLOAT = 0x1011\n    SET_NORM_DOUBLE = 0x1012\n    SET_NORM_FLOAT = 0x1013\n    SET_SCALE_FLOAT_INT_READ = 0x1014\n    SET_SCALE_INT_FLOAT_WRITE = 0x1015\n    GET_SIMPLE_FORMAT_COUNT = 0x1020\n    GET_SIMPLE_FORMAT = 0x1021\n    GET_FORMAT_INFO = 0x1028\n    GET_FORMAT_MAJOR_COUNT = 0x1030\n    GET_FORMAT_MAJOR = 0x1031\n    GET_FORMAT_SUBTYPE_COUNT = 0x1032\n    GET_FORMAT_SUBTYPE = 0x1033\n    CALC_SIGNAL_MAX = 0x1040\n    CALC_NORM_SIGNAL_MAX = 0x1041\n    CALC_MAX_ALL_CHANNELS = 0x1042\n    CALC_NORM_MAX_ALL_CHANNELS = 0x1043\n    GET_SIGNAL_MAX = 0x1044\n    GET_MAX_ALL_CHANNELS = 0x1045\n    SET_ADD_PEAK_CHUNK = 0x1050\n    SET_ADD_HEADER_PAD_CHUNK = 0x1051\n    UPDATE_HEADER_NOW = 0x1060\n    SET_UPDATE_HEADER_AUTO = 0x1061\n    FILE_TRUNCATE = 0x1080\n    SET_RAW_START_OFFSET = 0x1090\n\n    SET_DITHER_ON_WRITE = 0x10A0\n    SET_DITHER_ON_READ = 0x10A1\n\n    GET_DITHER_INFO_COUNT = 0x10A2\n    GET_DITHER_INFO = 0x10A3\n    GET_EMBED_FILE_INFO = 0x10B0\n    SET_CLIPPING = 0x10C0\n    GET_CLIPPING = 0x10C1\n    GET_INSTRUMENT = 0x10D0\n    SET_INSTRUMENT = 0x10D1\n    GET_LOOP_INFO = 0x10E0\n    GET_BROADCAST_INFO = 0x10F0\n    SET_BROADCAST_INFO = 0x10F1\n    GET_CHANNEL_MAP_INFO = 0x1100\n    SET_CHANNEL_MAP_INFO = 0x1101\n    RAW_DATA_NEEDS_ENDSWAP = 0x1110\n    WAVEX_SET_AMBISONIC = 0x1200\n    WAVEX_GET_AMBISONIC = 0x1201\n\n    RF64_AUTO_DOWNGRADE = 0x1210\n\n    SET_VBR_ENCODING_QUALITY = 0x1300\n    SET_COMPRESSION_LEVEL = 0x1301\n\n    SET_OGG_PAGE_LATENCY_MS = 0x1302\n    SET_OGG_PAGE_LATENCY = 0x1303\n\n    GET_BITRATE_MODE = 0x1304\n    SET_BITRATE_MODE = 0x1305\n\n    GET_OGG_STREAM_SERIALNO = 0x1306\n\n    SET_CART_INFO = 0x1400\n    GET_CART_INFO = 0x1401\n\n    SET_ORIGINAL_SAMPLERATE = 0x1500\n    GET_ORIGINAL_SAMPLERATE = 0x1501\n\n    TEST_IEEE_FLOAT_REPLACE = 0x6001\n\n  SFFormat* {.pure, size: sizeof(cint).} = enum\n    # Subtypes\n    PCM_S8 = 0x0001\n    PCM_16 = 0x0002\n    PCM_24 = 0x0003\n    PCM_32 = 0x0004\n    PCM_U8 = 0x0005\n    FLOAT = 0x0006\n    DOUBLE = 0x0007\n\n    ULAW = 0x0010\n    ALAW = 0x0011\n    IMA_ADPCM = 0x0012\n    MS_ADPCM = 0x0013\n\n    GSM610 = 0x0020\n    VOX_ADPCM = 0x0021\n    NMS_ADPCM_16 = 0x0022\n    NMS_ADPCM_24 = 0x0023\n    NMS_ADPCM_32 = 0x0024\n\n    G721_32 = 0x0030\n    G723_24 = 0x0031\n    G723_40 = 0x0032\n\n    DWVW_12 = 0x0040\n    DWVW_16 = 0x0041\n    DWVW_24 = 0x0042\n    DWVW_N = 0x0043\n\n    DPCM_8 = 0x0050\n    DPCM_16 = 0x0051\n\n    VORBIS = 0x0060\n    OPUS = 0x0064\n\n    ALAC_16 = 0x0070\n    ALAC_20 = 0x0071\n    ALAC_24 = 0x0072\n    ALAC_32 = 0x0073\n\n    MPEG_LAYER_I = 0x0080\n    MPEG_LAYER_II = 0x0081\n    MPEG_LAYER_III = 0x0082\n\n    # Main types\n    WAV = 0x010000\n    AIFF = 0x020000\n    AU = 0x030000\n    RAW = 0x040000\n    PAF = 0x050000\n    SVX = 0x060000\n    NIST = 0x070000\n    VOC = 0x080000\n\n    IRCAM = 0x0A0000\n    W64 = 0x0B0000\n    MAT4 = 0x0C0000\n    MAT5 = 0x0D0000\n    PVF = 0x0E0000\n    XI = 0x0F0000\n    HTK = 0x100000\n    SDS = 0x110000\n    AVR = 0x120000\n    WAVEX = 0x130000\n\n    SD2 = 0x160000\n    FLAC = 0x170000\n    CAF = 0x180000\n    WVE = 0x190000\n    OGG = 0x200000\n    MPC2K = 0x210000\n    RF64 = 0x220000\n\n  SFFormatMask* {.pure, size: sizeof(cint).} = enum\n    SUBMASK = 0x0000FFFF\n    TYPEMASK = 0x0FFF0000\n    ENDMASK = 0x30000000\n\n  # We put the endian-ness options in a separate enum, because Nim enums\n  # don't allow duplicate values and values must be increasing.\n  SFEndian* {.pure, size: sizeof(cint).} = enum\n    FILE = 0x00000000\n    LITTLE = 0x10000000\n    BIG = 0x20000000\n    CPU = 0x30000000\n\n  SFStrType* {.pure, size: sizeof(cint).} = enum\n    TITLE = 0x01\n    COPYRIGHT = 0x02\n    SOFTWARE = 0x03\n    ARTIST = 0x04\n    COMMENT = 0x05\n    DATE = 0x06\n    ALBUM = 0x07\n    LICENSE = 0x08\n    TRACKNUMBER = 0x09\n    GENRE = 0x10\n\n  SFChannelMap* {.pure, size: sizeof(cint).} = enum\n    INVALID\n    MONO\n    LEFT\n    RIGHT\n    CENTER\n    FRONT_LEFT\n    FRONT_RIGHT\n    FRONT_CENTER\n    REAR_CENTER\n    REAR_LEFT\n    REAR_RIGHT\n    LFE\n    FRONT_LEFT_OF_CENTER\n    FRONT_RIGHT_OF_CENTER\n    SIDE_LEFT\n    SIDE_RIGHT\n    TOP_CENTER\n    TOP_FRONT_LEFT\n    TOP_FRONT_RIGHT\n    TOP_FRONT_CENTER\n    TOP_REAR_LEFT\n    TOP_REAR_RIGHT\n    TOP_REAR_CENTER\n    AMBISONIC_B_W\n    AMBISONIC_B_X\n    AMBISONIC_B_Y\n    AMBISONIC_B_Z\n    MAX\n\n  SFBitrateMode* {.pure, size: sizeof(cint).} = enum\n    CONSTANT\n    AVERAGE\n    VARIABLE\n\n  SFLoopMode* {.pure, size: sizeof(cint).} = enum\n    NONE = 800\n    FORWARD\n    BACKWARD\n    ALTERNATING\n\ntype\n  SndFile* = distinct object\n\n  SFCount* = int64\n\n  SFInfo* = object\n    frames*: SFCount\n    samplerate*: cint\n    channels*: cint\n    format*: cint\n    sections*: cint\n    seekable*: cint\n\n  SFFormatInfo* = object\n    format*: cint\n    name*: cstring\n    extension*: cstring\n\n  SFEmbedFileInfo* = object\n    offset*: SFCount\n    length*: SFCount\n\n  SFCuePoint* = object\n    indx*: int32\n    position*: uint32\n    fccChunk*: int32\n    chunkStart*:int32\n    blockStart*: int32\n    sampleOffset*: uint32\n    name*: array[256, char]\n\n  SFLoop* = object\n    mode*: SFLoopMode\n    startPos*: uint32\n    endPos*: uint32  # 'end' is a keyword in Nim\n    count*: uint32\n\n  SFInstrument* = object\n    gain*: cint\n    basenote*: byte\n    detune*: byte\n    velocityLo*: byte\n    velocityHi*: byte\n    keyLo*: byte\n    keyHi*: byte\n    loopCount*: cint\n    loops*: array[16, SFLoop]\n\n  SFLoopInfo* = object\n    timeSigNum*: cshort\n    timeSigDen*: cshort\n    loopMode*: SFLoopMode\n    numBeats*: cint\n    bpm*: cfloat\n    rootKey*: cint\n    future*: array[6, cint]\n\n  SFBroadcastInfo* = object\n    description*: array[256, char]\n    originator*: array[32, char]\n    originatorReference*: array[32, char]\n    originationDate*: array[10, char]\n    originationTime*: array[8, char]\n    timeReferenceLow*: uint32\n    timeReferenceHigh*: uint32\n    version*: cshort\n    umid*: array[64, char]\n    loudnessValue*: int16\n    loudnessRange*: int16\n    maxTruePeakLevel*: int16\n    maxMomentaryLoudness*: int16\n    maxShorttermLoudness*: int16\n    reserved*: array[180, byte]\n    codingHistorySize*: uint32\n    codingHistory*: array[codingHistSize, char]\n\n  SFCartTimer* = object\n    usage*: array[4, char]\n    value*: int32\n\n  SFCartInfo* = object\n    version*: array[4, char]\n    title*: array[64, char]\n    artist*: array[64, char]\n    cutId*: array[64, char]\n    clientId*: array[64, char]\n    category*: array[64, char]\n    classification*: array[64, char]\n    outCue*: array[64, char]\n    startDate*: array[10, char]\n    startTime*: array[8, char]\n    endDate*: array[10, char]\n    endTime*: array[8, char]\n    producerAppId*: array[64, char]\n    producerAppVersion*: array[64, char]\n    userDef*: array[64, char]\n    levelReference*: int32\n    postTimers*: array[8, SFCartTimer]\n    reserved*: array[276, char]\n    url*: array[1024, char]\n    tagTextSize*: uint32\n    tagText*: array[maxTagTextSize, char]\n\n  SFChunkInfo* = object\n    id*: array[64, char]\n    idSize*: cuint\n    dataLen*: cuint\n    data*: ptr UncheckedArray[byte]\n\n  SFChunkIterator* = distinct object\n\n\nproc versionString*(): cstring {.libsnd, importc: \"sf_version_string\".}\n\nproc open*(path: cstring, mode: SFMode, sfInfo: ptr SFInfo): ptr SndFile {.libsnd, importc: \"sf_open\".}\nproc close*(sndfile: ptr SndFile): cint {.libsnd, importc: \"sf_close\", discardable.}\n\nproc formatCheck*(info: ptr SFInfo): SFBool {.libsnd, importc: \"sf_format_check\".}\n\nproc seek*(sndfile: ptr SndFile, frames: SFCount, whence: SFSeek): SFCount {.libsnd, importc: \"sf_seek\".}\n\nproc command*(sndfile: ptr SndFile, cmd: SFCommand, data: pointer, datasize: cint): cint {.libsnd, importc: \"sf_command\", discardable.}\n\nproc error*(sndfile: ptr SndFile): cint {.libsnd, importc: \"sf_error\".}\nproc errorNumber*(errnum: int): cstring {.libsnd, importc: \"sf_error_number\".}\nproc strError*(sndfile: ptr SndFile): cstring {.libsnd, importc: \"sf_strerror\".}\n\nproc setString*(sndfile: ptr SndFile, strType: SFStrType, str: cstring): cint {.libsnd, importc: \"sf_set_string\".}\nproc getString*(sndfile: ptr SndFile, strType: SFStrType): cstring {.libsnd, importc: \"sf_get_string\".}\n\nproc currentByterate*(sndfile: ptr SndFile): cint {.libsnd, importc: \"sf_current_byterate\".}\n\nproc readShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort, items: SFCount): SFCount {.libsnd, importc: \"sf_read_short\".}\nproc readInt*(sndfile: ptr SndFile, bufferPtr: ptr cint, items: SFCount): SFCount {.libsnd, importc: \"sf_read_int\".}\nproc readFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat, items: SFCount): SFCount {.libsnd, importc: \"sf_read_float\".}\nproc readDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble, items: SFCount): SFCount {.libsnd, importc: \"sf_read_double\".}\n\nproc readFShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort, frames: SFCount): SFCount {.libsnd, importc: \"sf_readf_short\".}\nproc readFInt*(sndfile: ptr SndFile, bufferPtr: ptr cint, frames: SFCount): SFCount {.libsnd, importc: \"sf_readf_int\".}\nproc readFFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat, frames: SFCount): SFCount {.libsnd, importc: \"sf_readf_float\".}\nproc readFDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble, frames: SFCount): SFCount {.libsnd, importc: \"sf_readf_double\".}\n\nproc writeShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort; items: SFCount): SFCount {.libsnd, importc: \"sf_write_short\".}\nproc writeInt*(sndfile: ptr SndFile, bufferPtr: ptr cint; items: SFCount): SFCount {.libsnd, importc: \"sf_write_int\".}\nproc writeFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat; items: SFCount): SFCount {.libsnd, importc: \"sf_write_float\".}\nproc writeDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble; items: SFCount): SFCount {.libsnd, importc: \"sf_write_double\".}\n\nproc writeFShort*(sndfile: ptr SndFile, bufferPtr: ptr cshort; frames: SFCount): SFCount {.libsnd, importc: \"sf_writef_short\".}\nproc writeFInt*(sndfile: ptr SndFile, bufferPtr: ptr cint; frames: SFCount): SFCount {.libsnd, importc: \"sf_writef_int\".}\nproc writeFFloat*(sndfile: ptr SndFile, bufferPtr: ptr cfloat; frames: SFCount): SFCount {.libsnd, importc: \"sf_writef_float\".}\nproc writeFDouble*(sndfile: ptr SndFile, bufferPtr: ptr cdouble; frames: SFCount): SFCount {.libsnd, importc: \"sf_writef_double\".}\n\nproc readRaw*(sndfile: ptr SndFile, bufferPtr: ptr byte, numBytes: SFCount): SFCount {.libsnd, importc: \"sf_read_raw\".}\nproc writeRaw*(sndfile: ptr SndFile, bufferPtr: ptr byte, numBytes: SFCount) {.libsnd, importc: \"sf_write_raw\".}\n\nproc writeSync*(sndfile: ptr SndFile) {.libsnd, importc: \"sf_write_sync\".}\n\nproc setChunk*(sndfile: ptr SndFile, chunkInfo: ptr SFChunkInfo): SFErr {.libsnd, importc: \"sf_set_chunk\".}\nproc getChunkIterator*(sndfile: ptr SndFile, chunkInfo: ptr SFChunkInfo): ptr SFChunkIterator {.libsnd, importc: \"sf_get_chunk_iterator\".}\nproc nextChunkIterator*(it: ptr SFChunkIterator): ptr SFChunkIterator {.libsnd, importc: \"sf_next_chunk_iterator\".}\nproc getChunkSize*(it: ptr SFChunkIterator, chunkInfo: ptr SFChunkInfo): SFErr {.libsnd, importc: \"sf_get_chunk_size\".}\nproc getChunkData*(it: ptr SFChunkIterator, chunkInfo: ptr SFChunkInfo): SFErr {.libsnd, importc: \"sf_get_chunk_data\".}\n"
  },
  {
    "path": "src/sndfile/chunks.nim",
    "content": "import api\n\n# Data types for WAV file 'smpl' chunks\ntype\n  ## A single sample loop\n  SmplLoop* = object\n    id*: int32\n    loopType*: int32\n    startPos*: int32\n    endPos*: int32\n    fraction*: int32\n    playCount*: int32\n\n  ## A 'smpl' chunk, containing information for sample player instruments,\n  ## such as manufacturer and product ID of the instrument that is meant\n  ## to use this sample, the MIDI root note, zero or more sample loop\n  ## definitions and, optionally, extra sample player specific data.\n  SmplChunk* = object\n    manufacturer*: array[4, byte]\n    product*: int32\n    samplePeriod*: int32\n    midiUnityNote*: int32\n    midiPitchFraction*: int32\n    smpteFormat*: int32\n    smpteOffset*: int32\n    loopCount*: int32\n    samplerDataLen*: int32\n    loops*: UncheckedArray[SmplLoop]\n\n\n##\n## Iterate over all chunks in the audio file, whose chunk ID starts with\n## `chunk_id`. The loop variable is assigned a `seq[byte]` with the data of\n## each matching chunk.\n##\niterator chunks*(sndfile: ptr SndFile, chunk_id: string): seq[byte] =\n  var chunkInfo = SFChunkInfo()\n\n  if chunk_id.len - 1 > chunkInfo.id.high:\n    raise newException(ValueError, \"Length of chunk_id must be <= 64.\")\n\n  chunkInfo.idSize = chunk_id.len.cuint\n  chunkInfo.id[0..<chunk_id.len] = chunk_id\n\n  var it = getChunkIterator(sndfile, chunkInfo.addr)\n\n  while not it.isNil:\n    var data = newSeq[byte]()\n\n    if getChunkSize(it, chunkInfo.addr) == NO_ERROR and chunkInfo.dataLen > 0:\n      data.setLen(chunkInfo.dataLen)\n      chunkInfo.data = cast[ptr UncheckedArray[byte]](data[0].addr)\n\n      if getChunkData(it, chunkInfo.addr) != NO_ERROR:\n        # XXX Better exception type and error message\n        raise newException(IOError, \"Could not read next chunk.\")\n    else:\n      raise newException(IOError, \"Could not get chunk size.\")\n\n    yield data\n    it = nextChunkIterator(it)\n"
  },
  {
    "path": "src/sndfile.nim",
    "content": "import sndfile/api\nimport sndfile/chunks\nexport api\nexport chunks\n"
  },
  {
    "path": "tests/config.nims",
    "content": "switch(\"path\", \"$projectDir/../src\")\n"
  },
  {
    "path": "tests/test_chunks.nim",
    "content": "import std/[os, unittest]\n\nimport sndfile\n\n\nproc check_smpl_chunk(\n    sf: ptr SndFile,\n    manufacturer: array[4, byte],\n    samplePeriod: int32,\n    midiUnityNote: int32,\n    samplerDataLen: int32,\n    loopCount: int32,\n    loopType: int32,\n    startPos: int32,\n    endPos: int32\n  ): bool =\n  var found = 0\n\n  for chunk in sf.chunks(\"smpl\"):\n    found += 1\n    check(typeof(chunk) is seq[byte])\n\n    if found == 1:\n      # Only check the first 'smpl' chunk - there should be only one.\n      var smpl = cast[ptr SmplChunk](chunk[0].addr)\n\n      check(smpl.manufacturer == manufacturer)\n      check(smpl.samplePeriod == samplePeriod)\n      check(smpl.midiUnityNote == midiUnityNote)\n      check(smpl.samplerDataLen == samplerDataLen)\n\n      check(smpl.loopCount == loopCount)\n      check(smpl.loops[0].loopType == loopType)\n      check(smpl.loops[0].startPos == startPos)\n      check(smpl.loops[0].endPos == endPos)\n\n      let expectedSize = sizeof(SmplChunk) + smpl.samplerDataLen + smpl.loopCount * sizeof(SmplLoop)\n      check(chunk.len == expectedSize)\n\n  return found == 1\n\n\nsuite \"Tests for reading chunks from a WAV file\":\n  test \"test read smpl chunk\":\n    var info: SFInfo\n    var sf: ptr SndFile\n    info.format = 0\n\n    sf = open(cstring(getAppDir() / \"test_chunks.wav\"), READ, info.addr)\n    check(not sf.isNil)\n    defer: close(sf)\n\n    check(\n      check_smpl_chunk(\n        sf,\n        manufacturer = [0x47.byte, 0, 0, 1],\n        samplePeriod = 22675,\n        midiUnityNote = 1,\n        samplerDataLen = 18,\n        loopCount = 1,\n        loopType = 0,\n        startPos = 56252,\n        endPos = 240748\n      )\n    )\n\n  test \"test write and read back smpl chunk\":\n    var infoIn, infoOut: SFInfo\n    var sfIn, sfOut: ptr SndFile\n    infoIn.format = 0\n\n    sfIn = open(cstring(getAppDir() / \"sine.wav\"), READ, infoIn.addr)\n    check(not sfIn.isNil)\n\n    infoOut = infoIn\n    sfOut = open(cstring(getAppDir() / \"sineloop.wav\"), WRITE, infoOut.addr)\n    check(not sfOut.isNil)\n\n    var ci = SFChunkInfo()\n    ci.id[0..3] = \"smpl\"\n    ci.idSize = 4\n    ci.dataLen = sizeof(SmplChunk) + sizeof(SmplLoop)\n    ci.data = cast[ptr UncheckedArray[byte]](alloc0(ci.dataLen))\n\n    var smpl = cast[ptr SmplChunk](ci.data)\n    smpl.manufacturer = [1.byte, 0, 0, 1]\n    smpl.samplePeriod = int32(1_000_000_000 / infoIn.samplerate)\n    smpl.midiUnityNote = 69\n    smpl.loopCount = 1\n\n    var loop = cast[ptr SmplLoop](smpl.loops[0].addr)\n    loop.startPos = 0\n    loop.endPos = infoIn.frames.int32\n\n    check(sfOut.setChunk(ci.addr) == NO_ERROR)\n\n    var\n      frames, readcount: cint\n      buf: array[1024, cfloat]\n\n    frames = cint(1024 / infoOut.channels)\n    readcount = frames\n\n    while readcount > 0:\n      readcount = sfIn.readFFloat(buf[0].addr, frames).cint\n      discard sfOut.writeFFLoat(buf[0].addr, readcount)\n\n    sfIn.close()\n    sfOut.close()\n    dealloc(ci.data)\n\n    infoIn.format = 0\n    sfIn = open(cstring(getAppDir() / \"sineloop.wav\"), READ, infoIn.addr)\n    check(not sfIn.isNil)\n\n    check(\n      check_smpl_chunk(\n        sfIn,\n        manufacturer = [1.byte, 0, 0, 1],\n        samplePeriod = 20833,\n        midiUnityNote = 69,\n        samplerDataLen = 0,\n        loopCount = 1,\n        loopType = 0,\n        startPos = 0,\n        endPos = 48_000\n      )\n    )\n\n    sfIn.close()\n"
  },
  {
    "path": "tests/test_testwav.nim",
    "content": "import std/[os, unittest]\n\nimport sndfile\n\nsuite \"Tests for reading a simple WAV file\":\n  test \"test reading info\":\n    var info: SFInfo\n    var sndFile: ptr SndFile\n    info.format = 0\n\n    sndFile = open(cstring(getAppDir() / \"sine.wav\"), READ, info.addr)\n    check(not sndFile.isNil)\n\n    # expect info read from sound file header to be valid\n    check(formatCheck(info.addr) == TRUE)\n\n    check($info == \"(frames: 48000, samplerate: 48000, channels: 1, format: 65538, sections: 1, seekable: 1)\")\n\n    check(seek(sndFile, 5, SFSeek.SET) == 5)\n    discard seek(sndFile, 0, SFSeek.SET)\n\n    let num_items = info.channels * info.frames\n    check(num_items == 48_000)\n\n    var buffer = newSeq[cint](num_items)\n    let items_read = read_int(sndFile, buffer[0].addr, num_items)\n    check(items_read == 48_000)\n\n    close(sndFile)\n"
  },
  {
    "path": "tools/build_docs.sh",
    "content": "#!/bin/bash\n\nPROJECT=\"sndfile\"\nREPO_URL=\"https://github.com/SpotlightKid/nim-sndfile\"\nDOC_DIR=\"$(pwd)/.gh-pages\"\n\nnimble doc \\\n    --index:on \\\n    --project \\\n    --git.url:\"$REPO_URL\" \\\n    --git.commit:master \\\n    --out:\"$DOC_DIR\" \\\n    src/$PROJECT.nim\ncp \"$DOC_DIR\"/$PROJECT.html \"$DOC_DIR\"/index.html\nxdg-open file://\"$DOC_DIR\"/index.html\n"
  }
]