Skip to content

Instantly share code, notes, and snippets.

@ingoogni
Created June 11, 2025 07:30
Show Gist options
  • Save ingoogni/9ec4056da4ee04129efc1e74153bf6a5 to your computer and use it in GitHub Desktop.
Save ingoogni/9ec4056da4ee04129efc1e74153bf6a5 to your computer and use it in GitHub Desktop.
Nim closure iterators for audio synthesis.
import math, sugar
import iterit
const
SampleRate {.intdefine.} = 44100
SRate* = SampleRate.float32
proc linOsc*[Tf, Tp: float or iterator:float](
freq: Tf, phase: Tp, sampleRate: float = SRate
): iterator(): float =
var
tick, lastFreq, phaseCorrection: float
let increment = TAU/sampleRate
return iterator(): float {.inline.}=
while true:
let
f = freq.floatOrIter
p = phase.floatOrIter
phaseCorrection += (lastFreq - f) * (tick)
lastFreq = f
yield (((tick * f) + phaseCorrection + p) mod TAU) / TAU
tick += increment
proc sinOsc*[Tf, Tp, Ta: float or iterator:float](
freq:Tf, phase:Tp, amp:Ta, sampleRate:float = SRate
): iterator(): float =
var
tick, lastFreq, phaseCorrection:float
let increment = TAU/sampleRate
return iterator(): float {.inline.}=
while true:
let
f = freq.floatOrIter
p = phase.floatOrIter
a = amp.floatOrIter
phaseCorrection += (lastFreq - f) * (tick)
lastFreq = f
yield a * sin((tick * f) + phaseCorrection + p)
tick += increment
proc sinLOsc*[Tf, Tp, Ta: float or iterator:float](
freq:Tf, phase:Tp, amp:Ta, sampleRate:float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
let a = amp.floatOrIter
yield a * sin(osc() * TAU)
proc gsinOsc*(
freq: float, phase: float, sampleRate: float = SRate
): iterator(): float =
## sine oscilator, reverse Goertzel
## MS_error Goertzel vs sin() : 2.108412096000499e-15
let
phaseIncrement = freq * Tau / sampleRate
var
multiplier = 2 * cos(phaseIncrement)
current = sin(phase)
previous = sin(phase - phaseIncrement)
return iterator(): float {.inline.} =
yield current
while true:
let d0 = multiplier * current - previous
previous = current
current = d0
yield current
proc phasorOsc*[Tf, Tp: float or iterator:float](
freq: Tf, phase: Tp, min: float, max: float, sampleRate: float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
yield osc() * (max - min) + min
proc expOsc*[Tf, Tp: float or iterator:float](
freq: Tf, phase: Tp, exp: float, sampleRate: float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
yield pow(osc(), exp)
proc exp_absOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, exp: float, sampleRate: float = SRate
): iterator(): float =
let osc = phasorOsc(freq, phase, -1, 1, sampleRate)
return iterator(): float {.inline.} =
while true:
let a = amp.floatOrIter
yield a * (-1 + 2 * pow(abs(osc()), exp))
proc sawOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
let a = amp.floatOrIter
yield a * (osc() * 2 - 1)
proc tsawOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
let a = amp.floatOrIter
yield a * tanh(osc() * 2 - 1)/tanh(1.0)
proc squareOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
let a = amp.floatOrIter
yield a * float(sgn(osc() - 0.5))
proc pulseOsc*[Tf, Tp, Ta, Td: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, dutycycle: Td, sampleRate:float=SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
let
a = amp.floatOrIter
d = dutycycle.floatOrIter
yield a * float(sgn(osc() - (d/100)))
proc triangleOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let osc = linOsc(freq, phase, sampleRate)
return iterator(): float {.inline.}=
while true:
let
a = amp.floatOrIter
r = osc()
let
v = if r <= 0.25:
r
elif r <= 0.75:
0.5 - r
else:
r - 1
yield 4 * a * v
proc sawsinOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let losc = linOsc(freq/2, phase, sampleRate)
let sosc = sinOsc(freq/2, phase, 1.0, sampleRate)
return iterator(): float {.inline.}=
while true:
let
a = amp.floatOrIter
s = sosc() #always call both to keep them in sync
r = losc()
let
v = if r <= 0.5:
s
else:
r * 2 - 1
yield a * (v * 2 - 1)
proc semisinOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let osc = sinOsc(freq/2, phase, 1.0, sampleRate)
return iterator(): float {.inline.} =
while true:
let a = amp.floatOrIter
yield a * (abs(osc()) * 2 - 1)
proc trisinOsc*[Tf, Tp, Ta: float or iterator:float](
freq: Tf, phase: Tp, amp: Ta, sampleRate: float = SRate
): iterator(): float =
let losc = linOsc(freq, phase, sampleRate)
let sosc = sinOsc(freq, phase, 1.0, sampleRate)
return iterator(): float =
while true:
let
a = amp.floatOrIter
r = losc()
s = sosc()
v = if r <= 0.25:
4 * r
elif r <= 0.75:
s
else:
(r - 1) * 4
yield a * v
#bandwidth limited oscillators
proc bwl_squareOsc*[Tf, Tp, Ta: float or iterator:float](
freq:Tf, phase:Tp, amp:Ta, nharmonics: int, sampleRate: float = SRate
):auto {.inline.} =
let oscs = newSeq.collect:
for i in countup(1, nharmonics, 2):
sinOsc(freq * float(i), phase, 1/float(i), sampleRate)
return iterator: float {.inline.}=
while true:
let a = amp.floatOrIter
var v: float
for osc in oscs:
v += osc()
yield a * v
# Bandwidth-Limited Sawtooth Oscillator
proc bwl_sawOsc*[Tf, Tp, Ta: float or iterator: float](
freq: Tf,
phase: Tp,
amp: Ta,
nharmonics: int,
sampleRate: float = 44100.0
): iterator: float =
let oscs = collect(newSeq):
for i in 1..nharmonics: # All harmonics up to nharmonics
let amplitude = pow(-1.0, float(i+1)) / float(i) # (-1)^(i+1)/i
sinOsc(freq * float(i), phase, amplitude, sampleRate)
return iterator(): float =
while true:
let a = amp.floatOrIter
var v = 0.0
for osc in oscs:
v += osc()
yield a * v * 2.0 / PI # Scale to match sawtooth amplitude
# Bandwidth-Limited Triangle Oscillator
proc bwl_triangleOsc*[Tf, Tp, Ta: float or iterator: float](
freq: Tf,
phase: Tp,
amp: Ta,
nharmonics: int,
sampleRate: float = 44100.0
): iterator: float =
var sign = 1.0
let oscs = collect(newSeq):
for i in countup(1, nharmonics, 2): # Odd harmonics
let amplitude = sign / (float(i) * float(i)) # 1/i² with alternating signs
sign *= -1.0
sinOsc(freq * float(i), phase, amplitude, sampleRate)
return iterator(): float =
while true:
let a = amp.floatOrIter
var v = 0.0
for osc in oscs:
v += osc()
yield a * v * 8.0 / (PI * PI) # Correct scaling for triangle wave
when isMainModule:
let
f10 = bwl_triangleOsc(10.0, 0.0, 60.0, 12)
fm200 = sinOsc(200.0, f10, 1.0)
proc tick*(): float =
#inc tickerTape.tick
return fm200()
include io #libSoundIO
var ss = newSoundSystem()
ss.outstream.sampleRate = SampleRate
let outstream = ss.outstream
let sampleRate = outstream.sampleRate.toFloat
echo "Format:\t\t", outstream.format
echo "Sample Rate:\t", sampleRate
echo "Latency:\t", outstream.softwareLatency
while true:
ss.sio.flushEvents
let s = stdin.readLine
if s == "q":
break
#import std/[math]
import soundio
type
SoundSystem* = object
sio*: ptr SoundIo
indevice*: ptr SoundIoDevice
instream*: ptr SoundIoInStream
outdevice*: ptr SoundIoDevice
outstream*: ptr SoundIoOutStream
#outsource*: proc()
proc `=destroy`(s: SoundSystem) =
if not isNil s.outstream:
echo "destroy outstream"
s.outstream.destroy
dealloc(s.outstream.userdata)
if not isNil s.outdevice:
echo "destroy outdevice"
s.outdevice.unref
echo "destroy SoundSystem"
s.sio.destroy
echo "Quit"
proc writeCallback(outStream: ptr SoundIoOutStream, frameCountMin: cint, frameCountMax: cint) {.cdecl.} =
let csz = sizeof SoundIoChannelArea
var areas: ptr SoundIoChannelArea
var framesLeft = frameCountMax
var err: cint
while true:
var frameCount = framesLeft
err = outStream.beginWrite(areas.addr, frameCount.addr)
if err > 0:
quit "Unrecoverable stream error: " & $err.strerror
if frameCount <= 0:
break
let layout = outstream.layout
let ptrAreas = cast[int](areas)
for frame in 0..<frameCount:
let sample = tick()
for channel in 0..<layout.channelCount:
let ptrArea = cast[ptr SoundIoChannelArea](ptrAreas + channel*csz)
var ptrSample = cast[ptr float32](cast[int](ptrArea.pointer) + frame*ptrArea.step)
ptrSample[] = sample
err = outstream.endWrite
if err > 0 and err != cint(SoundIoError.Underflow):
quit "Unrecoverable stream error: " & $err.strerror
framesLeft -= frameCount
if framesLeft <= 0:
break
proc newSoundSystem*(): SoundSystem =
echo "SoundIO version : ", version_string()
result.sio = soundioCreate()
if isNil result.sio: quit "out of mem"
var err = result.sio.connect
if err > 0: quit "Unable to connect to backend: " & $err.strerror
echo "Backend: \t", result.sio.currentBackend.name
result.sio.flushEvents
echo "Output:"
let outdevID = result.sio.defaultOutputDeviceIndex
echo " Device Index: \t", outdevID
if outdevID < 0: quit "Output device is not found"
result.outdevice = result.sio.getOutputDevice(outdevID)
if isNil result.outdevice: quit "out of mem"
if result.outdevice.probeError > 0: quit "Cannot probe device"
echo " Device Name:\t", result.outdevice.name
result.outstream = result.outdevice.outStreamCreate
result.outstream.write_callback = writeCallback
err = result.outstream.open
if err > 0: quit "Unable to open output device: " & $err.strerror
if result.outstream.layoutError > 0:
quit "Unable to set channel layout: " & $result.outstream.layoutError.strerror
err = result.outstream.start
if err > 0: quit "Unable to start stream: " & $err.strerror
import math, random
const
SampleRate {.intdefine.} = 44100
SRate* = SampleRate.float32
let
g :float = 9.8
h :float = 1.0/SRate
type Pendulum* = tuple
l: float ## pendulum length
m: float ## pendulum mass
th: float ## initial angle (radians)
w: float ## initial angular velocity radians/s
proc derivs(p1:Pendulum, p2:Pendulum, g:float, yin:array[4,float]): array[4,float] =
var dydx: array[4,float] = [0.0,0.0,0.0,0.0]
dydx[0] = yin[1]
let
del = yin[2] - yin[0]
den1 = (p1.m + p2.m) * p1.l - p2.m * p1.l * cos(del) * cos(del)
dydx[1] = (
p2.m * p1.l * yin[1] * yin[1] * sin(del) * cos(del) + p2.m * g *
sin(yin[2]) * cos(del) + p2.m * p2.l * yin[3] * yin[3] * sin(del) -
(p1.m + p2.m) * g * sin(yin[0])
) / den1
dydx[2] = yin[3];
let den2 = (p2.l / p1.l) * den1
dydx[3] = (-p2.m * p2.l * yin[3] * yin[3] * sin(del) * cos(del) + (p1.m+p2.m) * g * sin(yin[0]) * cos(del) - (p1.m + p2.m) * p1.l * yin[1] * yin[1] * sin(del) - (p1.m + p2.m) * g * sin(yin[2])) / den2
return dydx
proc rungeKutta4(p1:Pendulum, p2:Pendulum, g:float, h:float, xin:float, yin:array[4,float]): array[4,float]=
#let
#hh = 0.5 * h
#xh = xin + hh
var
yout, yt, k1, k2, k3, k4:array[4, float] = [0.0,0.0,0.0,0.0]
dydx = derivs(p1, p2, g, yin)
for i in 0..<4:
k1[i] = h * dydx[i]
yt[i] = yin[i] + 0.5 * k1[i]
var dydxt = derivs(p1, p2, g, yt)
for i in 0..<4:
k2[i] = h * dydxt[i]
yt[i] = yin[i] + 0.5 * k2[i]
dydxt = derivs(p1, p2, g, yt)
for i in 0..<4:
k3[i] = h * dydxt[i]
yt[i] = yin[i] + k3[i]
dydxt = derivs(p1, p2, g, yt)
for i in 0..<4:
k4[i] = h * dydxt[i]
yout[i] = yin[i] + k1[i] / 6.0 + k2[i] / 3.0 + k3[i] / 3.0 + k4[i] / 6.0
return yout
proc doublePendulum*(
p1:Pendulum,
p2:Pendulum,
g:float=9.8,
sampleRate:float=SRate
): iterator(): array[4, float] =
let h :float = 1.0/sampleRate
var
yin :array[4, float] = [p1.th, p1.w, p2.th, p2.w]
yout:array[4, float]
xin:float = 0.0
return iterator(): array[4, float] {.inline.}=
while true:
yout = rungeKutta4(p1, p2, g, h, xin, yin)
yin = yout
xin += h
let
x1 = p1.l * sin yout[0]
y1 = - p1.l * cos(yout[0])
x2 = x1 + p2.l * sin(yout[2])
y2 = y1 - p2.l * cos(yout[2])
yield [x1,y1,x2,y2]
#[
let p1:Pendulum = (l:1.0, m:1.0, th:3.0, w:10.0)
let p2:Pendulum = (l:0.1, m:1.0, th:1.0, w:11.0)
var p = doublePendulum(p1, p2, g)
for i in 0..100:
echo p()
]#
proc attrLorenz*(x, y, z, h, a, b, c:float): iterator(): (float,float,float) =
var
x0 = x
y0 = y
z0 = z
return iterator(): (float,float,float) {.inline.}=
while true:
x0 = x0 + h * a * (y0 - x0)
y0 = y0 + h * (x0 * (b - z0) - y0)
z0 = z0 + h * (x0 * y0 - c * z0)
yield(x0, y0, z0)
#let L = attrLorenz(0.1,0.0,0.0,0.01,10.0,28.0,8.0/2.0)
#for i in 0..<500:
# echo L()
proc attrRossler*(x,y,z, h,a,b,c:float): iterator(): (float,float,float) =
var
x0 = x
y0 = y
z0 = z
return iterator():(float,float,float) {.inline.}=
while true:
x0 = x0 + h * (-y0 - z0)
y0 = y0 + h * (x0 + a * y0)
z0 = z0 + h * (b + z0 * (x0 - c))
yield(x0, y0, z0)
#let R = attrRossler(0.001,0.001,0.001,0.015,0.2,0.2,5.7)
#for i in 0..<500:
# echo R()
proc attrClifford*(x, y, a, b, c, d:float): iterator(): (float, float) =
var
x0 = x
y0 = y
return iterator():(float,float) {.inline.}=
while true:
x0 = sin(a * y0) + c * cos(a * x0)
y0 = sin(b * x0) + d * cos(b * y0)
yield (x0, y0)
#let C = attrClifford(0.001,0.001,2.0,2.0,1.0,-1.0)
#for i in 0..<500:
# echo C()
proc attrDeJong*(x, y, a, b, c, d:float): iterator(): (float, float) =
var
x0 = x
y0 = y
return iterator():(float,float) {.inline.}=
while true:
x0 = sin(a * y0) * cos(b * x0)
y0 = sin(c * x0) * cos(d * y0)
yield (x0, y0)
#let dJ = attrDeJong(0.001,0.001,1.614,1.9020,0.316,1.525)
#for i in 0..<500:
# echo dJ()
proc smoothRndBezier(nSegments:int): seq[float]=
var
firstHandle = rand(-1.0..1.0)
lastHandle = firstHandle
lastKnot = rand(-1.0..1.0)
firstKnot = lastKnot
point:seq[float]
point.add(lastKnot)
for segment in 0..<nSegments:
point.add(lastKnot - (lastHandle - lastKnot))
lastHandle = rand(-1.0..1.0)
point.add(lastHandle)
if segment == nSegments - 1:
point.add(firstKnot)
point.add(-(point[^1] + (point[0] - firstHandle)))
else:
lastKnot = rand(-1.0..1.0)
point.add(lastKnot)
return point
proc rndBezierOsc*(freq:float, morphTime:float, nSegments:int, amp:float, sampleRate:float=SRate): iterator(): float =
var
morphTo:seq[float]
morphFrom = nSegments.smoothRndBezier
morphInc = morphFrom #just to fill
tStep: float
segment, morphSteps, morphStep:int
return iterator(): float {.inline.} =
while true:
tStep += (freq * float nSegments) / float sampleRate
segment = int tStep.floor
let t = tStep - float segment
if morphStep == 0:
morphTo = nSegments.smoothRndBezier
morphSteps = int(morphTime * float sampleRate ) #in seconds
for i in 0..< len morphFrom:
morphInc[i] = (morphTo[i] - morphFrom[i]) / float morphSteps
if segment >= nSegments:
segment = 0
tStep = t
let
t2 = t * t
t3 = t2 * t
tm = 1 - t
tm2 = tm * tm
tm3 = tm2 * tm
tm2t_3 = 3 * tm2 * t
tmt2_3 = 3 * tm * t2
b1 = (morphFrom[0 + segment * 3]) * tm3
b2 = (morphFrom[1 + segment * 3]) * tm2t_3
b3 = (morphFrom[2 + segment * 3]) * tmt2_3
b4 = (morphFrom[3 + segment * 3]) * t3
for i in 0..<len morphFrom :
morphFrom[i] = morphFrom[i] + morphInc[i]
inc morphStep
if morphStep > morphSteps:
morphStep = 0
morphFrom = morphTo #??
yield amp * (b1+b2+b3+b4)
import math
const
SampleRate {.intdefine.} = 44100
SRate* = SampleRate.float32
proc onePoleLP*(
sample:iterator:float, freq:float, sampleRate:float=SRate
):auto {.inline.}=
let
b1 = exp(-2.0 * PI * (freq / sampleRate))
a0 = 1.0 - b1
var
z1 : float
return iterator(): float {.inline.}=
while true:
z1 = sample() * a0 + z1 * b1
yield z1
proc onePoleHP*(
sample:iterator:float, freq:float, sampleRate:float=SRate
):auto {.inline.}=
let
b1 = -exp(-2.0 * PI * (freq / sampleRate))
a0 = 1.0 + b1
var
z1 : float
return iterator(): float {.inline.}=
while true:
z1 = sample() * a0 + z1 * b1;
yield z1
proc onePoleDC*(
sample:iterator:float, freq:float = 10, sampleRate:float=SRate
):auto {.inline.}=
## DC blocking filter
let
b1 = exp(-2.0 * PI * (freq / sampleRate))
a0 = 1.0 - b1
var
z1 : float
return iterator(): float {.inline.}=
while true:
let inSample = sample()
z1 = inSample * a0 + z1 * b1
yield inSample - z1
proc oneZero*(
sample:iterator:float, freq:float, sampleRate:float=SRate
):auto {.inline.}=
let
b1 = exp(-2.0 * PI * (freq / sampleRate))
a0 = 1.0 - b1
var
z1 : float
lastSample:float
return iterator(): float {.inline.}=
while true:
let inSample = sample()
z1 = inSample * a0 + lastSample * b1
lastSample = inSample
yield z1
proc bqLP*(
sample:iterator:float, freq:float, q:float, sampleRate:float=SRate
):auto {.inline.}=
let
k = tan(PI * freq / sampleRate)
norm = 1.0 / (1.0 + k / q + k * k)
a0 = k * k * norm
a1 = 2.0 * a0
a2 = a0
b1 = 2.0 * (k * k - 1.0) * norm
b2 = (1.0 - k / q + k * k) * norm
var
z1:float
z2:float
return iterator(): float {.inline.}=
while true:
let
inSample = sample()
outSample = inSample * a0 + z1
z1 = inSample * a1 + z2 - b1 * outSample
z2 = inSample * a2 - b2 * outSample
yield outSample
proc bqHP*(
sample:iterator:float, freq:float, q:float, sampleRate:float=SRate
):auto {.inline.}=
let
k = tan(PI * freq / sampleRate)
norm = 1.0 / (1.0 + k / q + k * k)
a0 = 1.0 * norm
a1 = -2.0 * a0
a2 = a0
b1 = 2.0 * (k * k - 1) * norm
b2 = (1.0 - k / q + k * k) * norm
var
z1:float
z2:float
return iterator(): float {.inline.}=
while true:
let
inSample = sample()
outSample = inSample * a0 + z1
z1 = inSample * a1 + z2 - b1 * outSample;
z2 = inSample * a2 - b2 * outSample;
yield outSample
proc bqBP*(
sample:iterator:float, freq:float, q:float, sampleRate:float=SRate
):auto {.inline.}=
let
k = tan(PI * freq / sampleRate)
norm = 1.0 / (1.0 + k / q + k * k)
a0 = k / q * norm
a1 = 0.0
a2 = -a0
b1 = 2.0 * (k * k - 1.0) * norm
b2 = (1.0 - k / q + k * k) * norm
var
z1:float
z2:float
return iterator(): float {.inline.}=
while true:
let
inSample = sample()
outSample = inSample * a0 + z1
z1 = inSample * a1 + z2 - b1 * outSample
z2 = inSample * a2 - b2 * outSample
yield outSample
proc bqPeaking*(
sample:iterator:float, freq:float, q:float, gainDb:float, sampleRate:float=SRate
):auto {.inline.}=
let
v = if gainDb == 0.0: 1.0 else: pow(10.0, abs(gainDb) / 20.0)
k = tan(PI * freq / sampleRate)
# Calculate coefficients based on boost or cut
norm = if gainDb >= 0.0:
# Boost case
1.0 / (1.0 + 1.0/q * k + k * k)
else:
# Cut case
1.0 / (1.0 + v/q * k + k * k)
a0 = if gainDb >= 0.0:
(1.0 + v/q * k + k * k) * norm
else:
(1.0 + 1.0/q * k + k * k) * norm
a1 = 2.0 * (k * k - 1.0) * norm
a2 = if gainDb >= 0.0:
(1.0 - v/q * k + k * k) * norm
else:
(1.0 - 1.0/q * k + k * k) * norm
b1 = 2.0 * (k * k - 1.0) * norm
b2 = if gainDb >= 0.0:
(1.0 - 1.0/q * k + k * k) * norm
else:
(1.0 - v/q * k + k * k) * norm
var
z1:float
z2:float
return iterator(): float {.inline.}=
while true:
let
inSample = sample()
outSample = inSample * a0 + z1
z1 = inSample * a1 + z2 - b1 * outSample
z2 = inSample * a2 - b2 * outSample
yield outSample
proc bqLowShelf*(sample:iterator:float, freq:float, gainDb:float, sampleRate:float=SRate):auto {.inline.}=
let
v = if gainDb == 0.0: 1.0 else: pow(10.0, gainDb / 20.0) # Note: no abs() for shelving
k = tan(PI * freq / sampleRate)
# Low shelf coefficients
norm = if gainDb >= 0.0:
# Boost case
1.0 / (1.0 + sqrt(2.0) * k + k * k)
else:
# Cut case
1.0 / (v + sqrt(2.0 * v) * k + k * k)
a0 = if gainDb >= 0.0:
(v + sqrt(2.0 * v) * k + k * k) * norm
else:
(1.0 + sqrt(2.0) * k + k * k) * norm
a1 = if gainDb >= 0.0:
2.0 * (k * k - v) * norm
else:
2.0 * (k * k - 1.0) * norm
a2 = if gainDb >= 0.0:
(v - sqrt(2.0 * v) * k + k * k) * norm
else:
(1.0 - sqrt(2.0) * k + k * k) * norm
b1 = 2.0 * (k * k - 1.0) * norm
b2 = if gainDb >= 0.0:
(1.0 - sqrt(2.0) * k + k * k) * norm
else:
(v - sqrt(2.0 * v) * k + k * k) * norm
var
z1:float
z2:float
return iterator(): float {.inline.}=
while true:
let
inSample = sample()
outSample = inSample * a0 + z1
z1 = inSample * a1 + z2 - b1 * outSample
z2 = inSample * a2 - b2 * outSample
yield outSample
proc bqHighShelf*(sample:iterator:float, freq:float, gainDb:float, sampleRate:float=SRate):auto {.inline.}=
let
v = if gainDb == 0.0: 1.0 else: pow(10.0, gainDb / 20.0) # Note: no abs() for shelving
k = tan(PI * freq / sampleRate)
# High shelf coefficients
norm = if gainDb >= 0.0:
# Boost case
1.0 / (1.0 + sqrt(2.0) * k + k * k)
else:
# Cut case
1.0 / (1.0 + sqrt(2.0 * v) * k + v * k * k)
a0 = if gainDb >= 0.0:
(v + sqrt(2.0 * v) * k + k * k) * norm
else:
(1.0 + sqrt(2.0) * k + k * k) * norm
a1 = if gainDb >= 0.0:
2.0 * (k * k - v) * norm
else:
2.0 * (k * k - 1.0) * norm
a2 = if gainDb >= 0.0:
(v - sqrt(2.0 * v) * k + k * k) * norm
else:
(1.0 - sqrt(2.0) * k + k * k) * norm
b1 = 2.0 * (k * k - 1.0) * norm
b2 = if gainDb >= 0.0:
(1.0 - sqrt(2.0) * k + k * k) * norm
else:
(1.0 - sqrt(2.0 * v) * k + v * k * k) * norm
var
z1:float
z2:float
return iterator(): float {.inline.}=
while true:
let
inSample = sample()
outSample = inSample * a0 + z1
z1 = inSample * a1 + z2 - b1 * outSample
z2 = inSample * a2 - b2 * outSample
yield outSample
template floatOrIter*(x:untyped):untyped =
when x is float: x else: x()
proc `+`*[T](val:T, items: iterator: T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield val + items()
proc `+`*[T](items: iterator: T, val:T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield items() + val
proc `+`*[T](items1: iterator: T, items2: iterator: T): iterator(): T =
return iterator(): T =
while true:
if finished(items1) or finished(items2):
break
yield items1() + items2()
proc `-`*[T](val:T, items:iterator:T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield val - items()
proc `-`*[T](items: iterator: T, val: T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield items() - val
proc `-`*[T](items1: iterator: T, items2: iterator: T): iterator(): T =
return iterator(): T =
while true:
if finished(items1) or finished(items2):
break
yield items1() - items2()
proc `*`*[T](val: T, items: iterator: T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield val * items()
proc `*`*[T](items: iterator: T, val: T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield val * items()
proc `*`*[T](items1: iterator: T, items2: iterator: T): iterator(): T =
return iterator(): T =
while true:
if finished(items1) or finished(items2):
break
yield items1() * items2()
proc `/`*[T](val:T, items:iterator:T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield val / items()
proc `/`*[T](items:iterator:T, val:T): iterator(): T =
return iterator(): T =
while true:
if finished(items):
break
yield items() / val
proc `/`*[T](items1: iterator: T, items2:iterator:T): iterator(): T =
return iterator(): T =
while true:
if finished(items1) or finished(items2):
break
yield items1() + items2()
proc abs*[T](items: iterator: T): iterator(): T =
return iterator(): T =
while true:
if finished (items):
break
yield abs(items())
proc sum*[T](items: iterator: T): iterator(): T =
var sum: T
return iterator(): T =
while true:
if finished(items):
break
sum += items()
yield sum
proc prod*[T](items: iterator: T): iterator(): T =
var prod: T = items()
return iterator(): T =
while true:
if finished(items):
break
prod *= items()
yield prod
#proc fanOut*[T](items: iterator: T, n: int): iterator(): T =
# ##repeats each item in items n times
# return iterator(): T {.inline.}=
# while true:
# if finished(items):
# break
# let item = items()
# for i in 0..<n:
# yield item
proc repeat*[T](item: T): iterator(): T =
#runs infinite!
return iterator(): T {.inline.}=
while true:
yield item
proc repeatN*[T](item: T, n:int): iterator(): T =
#repeat item n times
var i:int
return iterator(): T {.inline.}=
while i<n:
yield item
inc i
proc repeatSeq*[T](item: seq[T]): iterator(): T =
#repeat sequence indefinite
return iterator(): T {.inline.}=
while true:
for i in item:
yield i
proc repeatSeqN*[T](item: seq[T], n: int): iterator(): T =
#repeat sequence n times
var i:int
return iterator(): T {.inline.}=
while i<n:
for i in item:
yield i
inc i
proc iterseq*[T](item: seq[T]): iterator(): T =
#iterate sequence once
return iterator(): T {.inline.}=
for i in item:
yield i
proc toSeq*[T](items:iterator: T):seq[T]=
for item in items():
result.add item
proc filter*[T](items: iterator: T, f: proc(x: T): bool): iterator(): T =
return iterator(): T =
while true:
let x = items()
if finished(items):
break
else:
if f(x):
yield x
proc iterval*[T: float or iterator: float](item: T): iterator(): float =
# iterate same value if float else what is in iterator
return iterator(): float {.inline.}=
while true:
let it = item.floatOrIter
yield it
import random
import iterit
proc whiteNoise*[Tamp: float or iterator: float](amp: Tamp = 1.0): auto =
## White noise
var rng = initRand(12345)
return iterator(): float =
while true:
let a = amp.floatOrIter
yield a * (rng.rand(2.0) - 1.0) # [-1, 1)
proc pinkNoise*[Tamp: float or iterator: float](amp: Tamp = 1.0): auto =
## Pink noise (-3dB/octave)
let white = whiteNoise()
var b0, b1, b2, b3, b4, b5, b6: float
return iterator(): float =
while true:
let
a = amp.floatOrIter
w = white() # Single white noise sample per iteration
b0 = 0.99886 * b0 + w * 0.0555179
b1 = 0.99332 * b1 + w * 0.0750759
b2 = 0.96900 * b2 + w * 0.1538520
b3 = 0.86650 * b3 + w * 0.3104856
b4 = 0.55000 * b4 + w * 0.5329522
b5 = -0.7616 * b5 - w * 0.0168980
let output = a * 0.11 * (b0 + b1 + b2 + b3 + b4 + b5 + b6 + w * 0.5362)
yield output
b6 = w * 0.115926
proc brownNoise*[Tamp: float or iterator](amp: Tamp = 1.0): auto =
## Brownian noise (-6dB/octave) with safer scaling.
let white = whiteNoise()
var lastSample: float
return iterator(): float =
while true:
let a = amp.floatOrIter
lastSample = (lastSample + (0.02 * white())) / 1.02
yield a * lastSample * 2.0 # Adjusted from 3.5 to 2.0 for safety
proc blueNoise*[Tamp:float or iterator](amp:Tamp = 1.0): auto {.inline.}=
## Blue noise 3dB/octave
let pink = pinkNoise()
var lastInput: float
return iterator(): float {.inline.}=
while true:
let
a = amp.floatOrIter
p = pink()
yield a * (p - lastInput) / 0.35
lastInput = p
proc violetNoise*[Tamp:float or iterator](amp:Tamp = 1.0): auto {.inline.}= #?
## Violet or purple noise, 6db/octave
let white = whiteNoise()
var lastInput: float
return iterator(): float {.inline.}=
while true:
let
a = amp.floatOrIter
w = white()
yield a * (w - lastInput) / 1.41
lastInput = w
import math
import iterit
import iteroscillator
type
Ticker* = object
tick: uint
const
SampleRate {.intdefine.} = 44100
SRate* = SampleRate.float32
# Terrain function
proc shuheiKawachi*[Tix, Tey, Tf0, Tf1, Ta: float or iterator:float](
ix: Tix,
ey: Tey,
f0: Tf0,
f1: Tf1,
amp: Ta,
): iterator =
return iterator (): float =
while true:
let
a = amp.floatOrIter
x = ix.floatOrIter
y = ey.floatOrIter
v0 = f0.floatOrIter
v1 = f1.floatOrIter
sk = ((cos(x) * cos(y) + cos(( sqrt(v0) * x - y) / v1) *
cos((x + sqrt(v0) * y) / v1) + cos(( sqrt(v0) * x + y) / v1) *
cos((x - sqrt(v0) * y) / v1)) / 6 )
yield sk * a
when isMainModule:
#var tickerTape = Ticker(tick: 0)
# sx, sy move the "reader" along, that samples the terrain (the sk function)
let sx = sinOsc(7.3, Pi, 1.0) * 100
let sy = sinOsc(2.0, 0.0, 1.0) * 200
let sk = shuheiKawachi(sx, sy, Pi, Tau, 1.0)
proc tick*(): float =
#inc tickerTape.tick
return sk()
include io #libSoundIO
var ss = newSoundSystem()
ss.outstream.sampleRate = SampleRate
let outstream = ss.outstream
let sampleRate = outstream.sampleRate.toFloat
echo "Format:\t\t", outstream.format
echo "Sample Rate:\t", sampleRate
echo "Latency:\t", outstream.softwareLatency
while true:
ss.sio.flushEvents
let s = stdin.readLine
if s == "q":
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment