Skip to content

Instantly share code, notes, and snippets.

@ingoogni
Created October 14, 2025 15:43
Show Gist options
  • Select an option

  • Save ingoogni/5da8629e1537291853783c58e97d59a5 to your computer and use it in GitHub Desktop.

Select an option

Save ingoogni/5da8629e1537291853783c58e97d59a5 to your computer and use it in GitHub Desktop.
type for audio in Nim
import std/[math]
#import emitters
template fromTyp(typ, base: typedesc) =
proc `$`*(x: typ): string {.borrow.}
proc `+`*(a, b: typ): typ {.borrow.}
proc `-`*(a, b: typ): typ {.borrow.}
proc `*`*(a: typ, b: base): typ {.borrow.}
proc `*`*(a: base, b: typ): typ {.borrow.}
proc `/`*(a: typ, b: base): typ {.borrow.}
# Division between same type -> Ratio -> dimensionless: base
# e.g. Phase / Phase → float32
proc `/`*(a, b: typ): base {.borrow.}
proc `+`*(x: typ): typ {.borrow.}
proc `-`*(x: typ): typ {.borrow.}
proc `<`*(a, b: typ): bool {.borrow.}
proc `<=`*(a, b: typ): bool {.borrow.}
proc `==`*(a, b: typ): bool {.borrow.}
proc `+=`*(a: var typ, b: typ) {.borrow.}
proc `-=`*(a: var typ, b: typ) {.borrow.}
proc `*=`*(a: var typ, b: base) {.borrow.}
proc `/=`*(a: var typ, b: base) {.borrow.}
template defineFromBase(typ, base: untyped) =
type
typ* = distinct base
fromTyp(typ, base)
defineFromBase(Dur, float32) # duration in seconds
defineFromBase(Freq, float32) # frequency in Hz
defineFromBase(Gain, float32)
defineFromBase(Phase, float32) # phase in radians
defineFromBase(PhaseRate, float32) # phase rate, radians per second
defineFromBase(Sample, float32) # sample, actually amplitude. Theoretically,
# idealy Sample would be an enum indexed array
# of [Dur, Ampl]
type
DurStream* = iterator(): Dur
FreqStream* = iterator(): Freq
GainStream* = iterator(): Gain
PhaseStream* = iterator(): Phase
PhaseRateStream* = iterator(): PhaseRate
SampleStream* = iterator(): Sample
#DurEmitter* = Emitter[Dur, auto]
#FreqEmitter* = Emitter[Freq, auto]
#GainEmitter* = Emitter[Gain, auto]
#PhaseEmitter* = Emitter[Phase, auto]
#PhaseRateEmitter* = Emitter[PhaseRate, auto]
#SampleEmitter* = Emitter[Sample, auto]
proc `*`*(f: Freq, dt: Dur): Phase {.borrow.}
proc `*`*(dt: Dur, f: Freq): Phase {.borrow.}
proc `*`*(pr: PhaseRate, dt: Dur): Phase {.borrow.}
proc `*`*(dt: Dur, pr: PhaseRate): Phase {.borrow.}
proc `*`*(p: Phase, f: Freq): PhaseRate {.borrow.}
proc `*`*(f: Freq, p: Phase): PhaseRate {.borrow.}
proc `*`*(a: Sample, b: Gain): Sample {.borrow.}
proc `*`*(a: Gain, b: Sample): Sample {.borrow.}
proc `*=`*(s: var Sample, g: Gain) {.borrow.}
proc `/`*(a: float32, dt: Dur): Freq {.borrow.}
proc `/`*(a: float32, f: Freq): Dur {.borrow.}
proc `/`(p: Phase, dt: Dur): PhaseRate {.borrow.}
#proc `/`*(p: Phase, d: Dur): Freq = Freq(p.float32 / (d.float32 * Tau)) #clash!
proc freqFromPhaseRate*(pr: PhaseRate): Freq = Freq(pr.float32 / Tau)
proc `/`*(pr: PhaseRate, f: Freq): Phase {.borrow.}
proc `/`*(pr: PhaseRate, dt: Dur): Freq {.borrow.}
proc `mod`*(p: Phase, b: float32): Phase {.borrow.}
proc `mod`*(p: Phase, b: Phase): Phase {.borrow.}
proc `mod`*(d: Dur, b: Dur): Dur {.borrow.}
proc `mod`*(f: Freq, b: Freq): Freq {.borrow.}
proc `mod`*(s: Sample, b: float32): Sample {.borrow.}
proc abs*(p: Phase): Phase {.borrow.}
proc abs*(d: Dur): Dur {.borrow.}
proc abs*(f: Freq): Freq {.borrow.}
proc floor*(p: Phase): Phase {.borrow.}
proc ceil*(p: Phase): Phase {.borrow.}
proc round*(p: Phase): Phase {.borrow.}
proc pow*(s: Sample, exp: float32): Sample {.borrow.}
proc clamp*(s: Sample, min, max: float32): Sample {.borrow.}
proc sin*(p: Phase): Sample {.borrow.}
proc cos*(p: Phase): Sample {.borrow.}
proc tan*(p: Phase): Sample {.borrow.}
proc hz*(f: float32): Freq = Freq(f)
proc sec*(d: float32): Dur = Dur(d)
proc rad*(p: float32): Phase = Phase(p)
proc toFloat*(f: Freq): float32 = f.float32
proc toFloat*(d: Dur): float32 = d.float32
proc toFloat*(p: Phase): float32 = p.float32
proc period*(f: Freq): Dur = 1.0'f32 / f
proc frequency*(d: Dur): Freq = 1.0'f32 / d
proc cycles*(f: Freq, dt: Dur): Phase = f * dt
proc radians*(f: Freq, dt: Dur): Phase = f * dt * Tau
const
SampleRate* {.intdefine.} = 44100 #Hz
SampleFreq* = SampleRate.Freq
SampleDur* = 1.0'f32 / SampleFreq
template orStream*(x: untyped): untyped =
## If 'x' is callable as an iterator (x()), call it
## Else, treat it as a scalar.
when compiles(x()): x() else: x
when isMainModule:
proc linOsc*(
freq: Freq or FreqStream,
phase: Phase or PhaseStream = 0.0.Phase,
sampleRate: Freq = SampleFreq
): SampleStream =
##
var tick: Dur
var lastFreq: Freq
var phaseCorrection: Phase
let increment: Dur = Tau / sampleRate
return iterator(): Sample =
while true:
let f: Freq = freq.orStream
let p: Phase = phase.orStream
phaseCorrection += (lastFreq - f) * tick
lastFreq = f
yield (((tick * f) + phaseCorrection + p) mod TAU).Sample
tick += increment
proc sinOsc*(
freq: Freq or FreqStream,
phase: Phase or PhaseStream = 0.0.Phase,
amp: Gain or GainStream = 1.0.Gain,
sampleRate: Freq = SampleFreq
): SampleStream =
var tick: Dur
var lastFreq: Freq
var phaseCorrection: Phase
let increment: Dur = Tau / sampleRate
return iterator(): Sample =
while true:
let f = freq.orStream
let p = phase.orStream
let a = amp.orStream
phaseCorrection += (lastFreq - f) * tick
lastFreq = f
yield a * sin((tick * f) + phaseCorrection + p)
tick += increment
proc iterSeq*[T](item: seq[T]): iterator(): T =
iterator(): T {.inline.}=
for i in item:
yield i
var freqIter = iterSeq(@[440.0.Freq, 660.0.Freq, 550.0.Freq])
var phaseIter = iterSeq(@[0.0.Phase, 0.1.Phase, 1.2.Phase])
var osc1 = linOsc(440.0.Freq, 0.0.Phase)
var osc2 = sinOsc(freqIter, phaseIter)
# Pull samples
echo "osc1:"
echo osc1()
echo osc1()
echo osc1()
echo "osc2:"
echo osc2()
echo osc2()
echo osc2()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment