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