Last active
June 19, 2025 12:20
-
-
Save ingoogni/ed8514815a4e6c13223ba37a1ff10790 to your computer and use it in GitHub Desktop.
Iterative Karplus Strong in Nim
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 | |
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