Created
June 11, 2025 07:30
-
-
Save ingoogni/9ec4056da4ee04129efc1e74153bf6a5 to your computer and use it in GitHub Desktop.
Nim closure iterators for audio synthesis.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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