Skip to content

Instantly share code, notes, and snippets.

@ingoogni
Last active June 19, 2025 12:20
Show Gist options
  • Save ingoogni/ed8514815a4e6c13223ba37a1ff10790 to your computer and use it in GitHub Desktop.
Save ingoogni/ed8514815a4e6c13223ba37a1ff10790 to your computer and use it in GitHub Desktop.
Iterative Karplus Strong in Nim
import math, random
import iterit
const
SampleRate {.intdefine.} = 44100
SRate* = SampleRate.float
MaxKSDelay* = (SampleRate / 20).int #Hz delay line length: SampleRate / Frequency
proc iterKS[Tf: float or iterator:float](
freq: Tf,
noise: iterator: float,
trigger: iterator: bool,
alpha: float = 0.99,
sampleRate: float = SRate
): iterator(): float =
const reflen = 50
var
m = (sampleRate / freq.floatOrIter).int
a = pow(alpha, (float(m) / reflen))
delayLine: array[MaxKSDelay, float] # Fixed len
writeIndex = 0
isActive = false
output = 0.0
return iterator(): float =
var sampleCounter = 0
while true:
if trigger():
isActive = true
m = (sampleRate / freq.floatOrIter).int
a = pow(alpha, (float(m) / reflen))
writeIndex = 0
sampleCounter = 0
if isActive:
let
# Karplus-Strong: noise input + filtered feedback
noiseInput = if sampleCounter < m: noise() else: 0.0
feedback = if sampleCounter - m >= 0: delayLine[(sampleCounter - m) mod m] * a
else: 0.0
newSample = noiseInput + feedback
delayLine[writeIndex] = newSample
output = newSample
inc sampleCounter
writeIndex = sampleCounter mod m
if abs(output) < 0.00001 and sampleCounter > m * 3: #3?
isActive = false
else:
output = 0.0
yield output
#proc iterKS(
# gate: iterator: bool, # When to trigger or
# gate: iterator: tuple(open: bool, length: int)
# delayLength: iterator: int, # How much to use of the delay line
# normaly gate length (pitch and bending)
# damping: iterator: float, # Dynamic filtering
# noise: iterator: float, # Excitation source
#).....
#
# This would be a more "modular" approach
proc gaussNoise(mu = 0.0; sigma = 1.0): iterator: float =
randomize()
return iterator(): float =
while true:
yield gauss(mu, sigma)
proc triggerIter(triggerEvery: int): iterator: bool =
var count = 0
return iterator(): bool =
while true:
inc count
yield count mod triggerEvery == 0
proc repeatSeq*[T](item: seq[T]): iterator(): T =
#repeat sequence indefinite
return iterator(): T {.inline.}=
while true:
for i in item:
yield i
when isMainModule:
let
efs = @[55.0, 110.0, 220.0, 440.0, 880.0]
noiseBurst = gaussNoise(0.0, 0.7)
trigg = triggerIter((3 * SRate).int)
fr = repeatSeq(efs)
ksIter = iterKS(fr, noiseBurst, trigg, 0.998)
proc tick*(): float =
#inc tickerTape.tick
return ksIter()
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