Skip to content

Instantly share code, notes, and snippets.

@ingoogni
Created June 18, 2025 07:44
Show Gist options
  • Save ingoogni/23c8dd33d62e43fdf0011aceaeb78604 to your computer and use it in GitHub Desktop.
Save ingoogni/23c8dd33d62e43fdf0011aceaeb78604 to your computer and use it in GitHub Desktop.
iterative drum sequencer (deconstructed) in Nim.
import std/[math, random]
import iterit, iterkspercussion
const
SampleRate {.intdefine.} = 44100
SRate* = SampleRate.float
type
Trigger* = object
open: bool
interval: uint
toNext: uint
#for "manual" patters use repeatSeq from `iterit`
let soukous* = @[true, false, false, true, false, false, true, false, false, false, true, true, false, false, false, false]
let son* = @[true, false, false, true, false, false, true, false, false, false, true, false, true, false, false, false]
let rumba* = @[true, false, false, true, false, false, false, true, false, false, true, false, true, false, false, false]
let bossanova* = @[true, false, false, true, false, false, true, false, false, false, true, false, false,true, false, false]
let gahu* = @[true, false, false, true, false, false, true, false, false, false, true, false, false, false, true, false]
let shiko* = @[true, false, false, false, true, false, true, false, false, false, true, false, true, false, false, false]
proc christoffelSeq*(p, q: int, upper: bool = true): iterator: bool =
var cnt = 0
return iterator(): bool =
while true:
let chris = if upper:
((cnt + 1) * p + p + q - 1) div (p + q) - (cnt * p + p + q - 1) div (p + q)
else:
((cnt + 1) * p div (p + q)) - (cnt * p div (p + q))
let beat = chris == 1
inc cnt
cnt = cnt mod (p + q)
yield beat
proc stochasticSeq*(density: float, seed: int): iterator: bool =
## messy, usful with low denisty for fills?
var r = initRand(seed)
return iterator(): bool =
while true:
let output = if r.rand(1.0) < density:
true
else:
false
yield output
proc bpmTrigger*(
bpm: float or iterator: float,
sampleRate: float = SRate
): iterator: Trigger =
var
interval: uint
oldBPM: float
tick: uint = 0
return iterator(): Trigger =
while true:
if oldBPM != bpm.floatOrIter:
interval = uint(float(60 * sampleRate) / bpm)
oldBPM = bpm
tick = 0
let cpos = tick mod interval
let trigger = Trigger(
open: cpos == 0,
interval: interval,
toNext: interval - cpos
)
inc tick
yield trigger
#to fan out iterator to serve multiple instruments
proc beatIt*(pattern: iterator: bool, bpm: iterator: Trigger): iterator: Trigger =
## Takes bpm clock / trigger and changes it based on the value of the
## pattern when there is an trigger impulse
var output: Trigger
return iterator(): Trigger =
while true:
output = bpm()
if output.open:
output.open = pattern()
yield output
when isMainModule:
proc whiteNoise*(): iterator: float =
randomize(7)
return iterator(): float =
while true:
yield rand(-1.0..1.0)
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
let freq = 25.0
let bmp = bpmTrigger(360.0)
#let pat = christoffelSeq(8, 13)
let pat = repeatSeq(bossanova)
let beats = beatIt(pat, bmp)
let exciter = ((whiteNoise() + sinOsc(freq * 4.0, Pi / 2.0, 3.0)) / 3.0)
let ksp = ksPercussion(
freq = freq,
loss = 0.4,
fltr = 0.6,
blend = 0.8,
excite = exciter,
trigger = beats,
sampleRate = SRate
)
proc tick*(): float =
return ksp()
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