Created
June 18, 2025 07:44
-
-
Save ingoogni/23c8dd33d62e43fdf0011aceaeb78604 to your computer and use it in GitHub Desktop.
iterative drum sequencer (deconstructed) 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 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