Recently I see more and more audio / DSP related Nim projects appear, which is awesome, I think.
To help me sift through the crop and eventually keep my notes and assessments of all these projects, I have started a document gathering Nim audio development resources:
https://codeberg.org/SpotlightKid/nim-audio-dev
I hope this will prove useful to other too.
Merge requests welcome, but note the goals listed at the top of the document. This is not intended to be list of each and every Nim project, which handles audio in some way [start with nimpkgs for that], but a help for Nim developers to get an overview of and find libraries and code they can use for their own Open Source audio projects.
Share & Enjoy!
Maybe of interest, I just updated a gist with a silly fast oscillator bank
I mostly use it for, non real time, additive stuff. It should be fast enough for real time and it should be relative easy to make it multi threaded.
(just a gist, as I cannot maintain software and I mostly use it as a mouldable piece and not a library)
- single generalized Goertzel filter
- Hoppin' generalized Goertzel filter bank. Again, something to pick from what one needs. The full version allows to specify resolution/ bin width per frequency in the bank. By hand or with some of the included helper functions. After processing the energy can be distributed over a larger fftSize using Gaussian distribution.
There's more, but scattered all over the place.
FYI a while ago I made https://github.com/SciNim/nimfftw3
I haven't needed to maintain it so far since it's mostly C-bindings but if you open PRs / issues I'll make sure to look at it.
Modal synthesis, with a pool for modes so they can decay properly. Uses SoundIo, took quite some time to figure out, but it works.
https://gist.github.com/ingoogni/4c85ff8add8bb1ea5214bbe38d00f626
Go ahead, it's building blocks after all.
Here's another one, 1/4 sine LUT based oscillators. The basic ones and Phase Distortion, ring modulator, AM, wave shaping. All in simple form to start building from.
https://gist.github.com/ingoogni/b115408fb20d58cffa4d09c343a0a057
More cleaning out the vault, using closure iterators for synthesis. The basic oscillators, chaos oscillators (pendulum and attractors), filters, noise colours, wave terrain synthesis. For most of the the parameters can be floats or iterators, so lots of modulation opportunities. I think most of it can be easily converted to object with init & next proc()
https://gist.github.com/ingoogni/9ec4056da4ee04129efc1e74153bf6a5
a drum sequencer deconstructed: https://gist.github.com/ingoogni/23c8dd33d62e43fdf0011aceaeb78604
for more ways to create sequences: https://abrazol.com/books/rhythm1/
the sequencer required an edit to the Karplus-Strong percussion, so it works. But I do not understand: https://gist.github.com/ingoogni/1e8f92ecc5472e8896ac1cecb8f4ab6a
ADSR, after Nigel Redmon's work:
https://gist.github.com/ingoogni/cfb66d86eab19f2f574a6bb64b617f0b
About half way now. The rest is a bigger mess and will take quite some time and there's graphics work waiting.
Oh I see! Well the Karplus Strong sounded pretty good, for one :)
Now the things is that I was going to develop most of these myself, so Ansatz seems to be a lot better than Reissbrett :)
So thanks, good to know I can just pull in whatever is useful.
Bierdeckelhinterseite, is where most of my work starts ... and ends.
go ahead and make something beautiful.
Not finished, Emitters, not just for audio, closure procs where I ignore the closure way of handling state mostly by generating an object from the variables in a state: block. The created object is accessible from the outside:
import macros
type
Emitter*[T, S] = object
next*: proc(): T
state*: ref S
template `()`*[T, S](e: Emitter[T, S]): T = e.next()
template emit*(x: untyped): untyped = return x
template emitter*(T: typedesc, body: untyped): untyped =
when declared(stateInstance):
# Stateful version
block:
var e: Emitter[T, type(stateInstance[])]
e.state = stateInstance
e.next = proc(): T = body
e
else:
# Stateless version
proc(): T = body
template resolve*(x: untyped): untyped =
when compiles(x.next()):
x.next()
else:
x
macro state*(name: untyped, body: untyped): untyped =
## Create an object type, globally, with `name`. Instantiates it locally as a
## ref with template accessors for each field
result = newStmtList()
let recList = nnkRecList.newTree()
var fieldNames: seq[NimNode] = @[]
# Extract field declarations
for node in body:
if node.kind == nnkVarSection:
for identDef in node:
if identDef.kind == nnkIdentDefs:
let fieldName = identDef[0]
var fieldType: NimNode
if identDef[1].kind != nnkEmpty:
fieldType = identDef[1]
elif identDef.len >= 3 and identDef[2].kind != nnkEmpty:
fieldType = nnkCall.newTree(ident("type"), identDef[2])
#else:
# fieldType = ident("float32") # fallback if really no type
fieldNames.add(fieldName)
recList.add(nnkIdentDefs.newTree(fieldName, fieldType, newEmptyNode()))
# Type definition with conditional compilation, define the type if it hasn't
# been yet
let typeDef = nnkTypeDef.newTree(
name,
newEmptyNode(),
nnkObjectTy.newTree(newEmptyNode(), newEmptyNode(), recList)
)
let typeSection = nnkTypeSection.newTree(typeDef)
# Local instance as a ref object
let instanceName = ident("stateInstance")
result = newStmtList(
quote do:
when not declared(`name`):
`typeSection`
var `instanceName` = new(`name`)
)
for fieldName in fieldNames:
result.add quote do:
template `fieldName`(): untyped {.used.} =
`instanceName`[].`fieldName`
when isMainModule:
import math
const dt = 1.0'f32 / 48000'f32
proc saw*(freq: float32 or Emitter[float32, auto]): auto =
state(SawState): #creates a state object that is acsessible from the outside
var phase: float32
emitter(float32):
let base = 440.0'f32
let f = when freq is Emitter: base * (1 + 0.5 * resolve(freq))
else: freq
phase() += f * 2 * PI * dt
if phase() > 2 * PI:
phase() = phase() - 2 * PI
emit (phase() / PI) - 1.0
proc saw2*(freq: float32 or Emitter[float32, auto]): auto =
var phase: float32 #no state object used, like a closure proc
emitter(float32):
let base = 440.0'f32
let f = when freq is Emitter: base * (1 + 0.5 * resolve(freq))
else: freq
phase += f * 2 * PI * dt
if phase > 2 * PI:
phase = phase - 2 * PI
emit (phase / PI) - 1.0
proc sine*(freq: float32 or Emitter[float32, auto]): auto =
state(SineState):
var phase = 0.35.float32
var f = freq
emitter(float32):
f = resolve(freq)
phase() += f * 2 * PI * dt
if phase() > 2 * PI:
phase() = phase() - 2 * PI
emit sin(phase())
let modulator = sine(0.25'f32)
let sig = saw(modulator) #when we use saw2 here, the cho's below obviously won't work
for i in 0 ..< 5:
echo sig()
echo "Accessing saw state phase: ", sig.state.phase
echo "Accessing modulator state phase: ", modulator.state.phase
sig.state.phase = 0.9999
echo "Accessing saw state phase: ", sig.state.phase
It may need more polishing
An attempt at type for sound/audio:
https://gist.github.com/ingoogni/5da8629e1537291853783c58e97d59a5