Skip to content

Instantly share code, notes, and snippets.

@ingoogni
Last active June 14, 2025 06:47
Show Gist options
  • Select an option

  • Save ingoogni/07249371a39b506b8a5fb0daff304b5f to your computer and use it in GitHub Desktop.

Select an option

Save ingoogni/07249371a39b506b8a5fb0daff304b5f to your computer and use it in GitHub Desktop.

Revisions

  1. ingoogni revised this gist Jun 14, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion foogle.nim
    Original file line number Diff line number Diff line change
    @@ -96,7 +96,7 @@ proc process*(input: float, f: Foogler): float =
    proc foogle*(input, tap1, tap2, feedback, cutoffHz: iterator: float or float, f: Foogler): iterator(): float =
    return iterator(): float =
    while true:
    # add something for control rate here, if needed
    # add something for control rate here, if needed, when parameter is iterator
    f.setTapPositions(tap1.floatOrIter, tap2.floatOrIter)
    f.setFeedback(feedback.floatOrIter)
    f.setDamping(cutOffHz.floatOrIter)
  2. ingoogni revised this gist Jun 14, 2025. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions foogle.nim
    Original file line number Diff line number Diff line change
    @@ -93,6 +93,16 @@ proc process*(input: float, f: Foogler): float =
    return f.filterState


    proc foogle*(input, tap1, tap2, feedback, cutoffHz: iterator: float or float, f: Foogler): iterator(): float =
    return iterator(): float =
    while true:
    # add something for control rate here, if needed
    f.setTapPositions(tap1.floatOrIter, tap2.floatOrIter)
    f.setFeedback(feedback.floatOrIter)
    f.setDamping(cutOffHz.floatOrIter)
    yield input.floatOrIter.process(f)


    proc foogle*(input: iterator: float or float, f: Foogler): iterator(): float =
    return iterator(): float =
    while true:
  3. ingoogni revised this gist Jun 14, 2025. 1 changed file with 5 additions and 14 deletions.
    19 changes: 5 additions & 14 deletions foogle.nim
    Original file line number Diff line number Diff line change
    @@ -24,7 +24,7 @@ type
    # Parameters
    feedback: float # 0.0 to 0.999
    dampingCutoff: float # Low-pass filter cutoff frequency
    # State for high-frequency damping
    # State for hf damping
    filterState: float
    filterCoeff: float

    @@ -83,31 +83,20 @@ proc process*(input: float, f: Foogler): float =
    ## Process a single sample through the Foogler
    let tap1Output = f.readTap(f.tap1Pos)
    let tap2Output = f.readTap(f.tap2Pos)
    # Mix the two taps
    let tapsOutput = (tap1Output + tap2Output) * 0.5
    # simple 1-pole low-pass
    f.filterState = f.filterState + f.filterCoeff * (tapsOutput - f.filterState)
    let feedbackSample = f.filterState * f.feedback
    # Write to delay line (input + feedback)
    f.delayLine[f.writePos] = input + feedbackSample
    # Advance write position
    f.writePos = (f.writePos + 1) mod 256
    return f.filterState


    proc foogle*(input: iterator: float or float, f: Foogler): iterator(): float =
    return iterator(): float =
    while true:
    let
    inputVal = input.floatOrIter # Get the input value
    tap1Output = f.readTap(f.tap1Pos)
    tap2Output = f.readTap(f.tap2Pos)
    tapsOutput = (tap1Output + tap2Output) * 0.5
    f.filterState = f.filterState + f.filterCoeff * (tapsOutput - f.filterState)
    let feedbackSample = f.filterState * f.feedback
    f.delayLine[f.writePos] = inputVal + feedbackSample
    f.writePos = (f.writePos + 1) mod 256
    yield f.filterState
    yield input.floatOrIter.process(f)


    when isMainModule:
    @@ -125,11 +114,13 @@ when isMainModule:
    echo fmt"Feedback: {foogler.feedback:.2f}"
    echo fmt"Damping cutoff: {foogler.dampingCutoff:.0f} Hz"


    let input1 = pingOsc(30.0, 0.0, 5.0)
    let sinput = sinOsc(70.0, 0.0, 1.0)
    let input = sawOsc(120.0, 0.0, 1.0) + input1 + sinput
    let pr = input.foogle(foogler)


    # rather chaotic
    proc tick*(): float =
    if tickerTape.tick mod 10000 == 0:
    @@ -139,7 +130,7 @@ when isMainModule:
    let v = pr()
    return v


    include io #libSoundIO
    var ss = newSoundSystem()
    ss.outstream.sampleRate = SampleRate
  4. ingoogni revised this gist Jun 14, 2025. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions foogle.nim
    Original file line number Diff line number Diff line change
    @@ -132,9 +132,8 @@ when isMainModule:

    # rather chaotic
    proc tick*(): float =
    if tickerTape.tick mod 100 == 0:
    if tickerTape.tick mod 10000 == 0:
    foogler.setTapPositions(rand(0.09..0.49), rand(0.50..0.90))
    if tickerTape.tick mod 1500 == 0:
    foogler.setFeedback(rand(0.6..0.9999))
    inc tickerTape.tick
    let v = pr()
  5. ingoogni created this gist Jun 13, 2025.
    158 changes: 158 additions & 0 deletions foogle.nim
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    # https://www.musicdsp.org/en/latest/Synthesis/11-weird-synthesis.html
    # "Karplus-Strong-inspired comb filter effect?"
    # Processes incoming audio continuously, not just initial excitation

    import std/[math, random]
    import iterit
    import iteroscillator

    const
    SampleRate {.intdefine.} = 44100
    SRate* = SampleRate.float


    type
    Ticker* = object
    tick: uint

    Foogler* = ref object
    delayLine: array[256, float]
    writePos: int
    # Tap positions in delay line
    tap1Pos: float
    tap2Pos: float
    # Parameters
    feedback: float # 0.0 to 0.999
    dampingCutoff: float # Low-pass filter cutoff frequency
    # State for high-frequency damping
    filterState: float
    filterCoeff: float


    proc initFoogler*(sampleRate: float = SRate): Foogler =
    ## "Foogler" delay effect
    result = Foogler(
    writePos: 0,
    tap1Pos: 0.3, # 30% into delay line
    tap2Pos: 0.7, # 70% into delay line
    feedback: 0.5, # 50% feedback
    dampingCutoff: 8000.0, # 8kHz cutoff
    filterState: 0.0
    )
    # Filter coefficient for simple 1-pole low-pass
    # cutoff = sampleRate * coeff / (2 * PI)
    result.filterCoeff = 2.0 * PI * result.dampingCutoff / sampleRate
    if result.filterCoeff > 1.0:
    result.filterCoeff = 1.0


    proc setTapPositions*(f: var Foogler, tap1, tap2: float) =
    ## Set the positions of the two taps (0.0 to 1.0)
    f.tap1Pos = clamp(tap1, 0.0, 1.0)
    f.tap2Pos = clamp(tap2, 0.0, 1.0)


    proc setFeedback*(f: var Foogler, feedback: float) =
    ## Set feedback amount (0.0 to 0.999)
    f.feedback = clamp(feedback, 0.0, 0.999)


    proc setDamping*(f: var Foogler, cutoffHz: float, sampleRate: float = SRate) =
    ## Set the high-frequency damping cutoff
    f.dampingCutoff = cutoffHz
    f.filterCoeff = 2.0 * PI * cutoffHz / sampleRate
    if f.filterCoeff > 1.0:
    f.filterCoeff = 1.0


    proc readTap(f: Foogler, tapPos: float): float =
    ## Read from delay line at specified tap position
    let delaySamples = tapPos * 255.0 # 0-255 range
    let intDelay = int(delaySamples)
    let fracDelay = delaySamples - float(intDelay)
    # Read positions with wraparound
    let readPos1 = (f.writePos - intDelay - 1 + 256) mod 256
    let readPos2 = (f.writePos - intDelay - 2 + 256) mod 256
    # Linear interpolation, fractional delay
    let sample1 = f.delayLine[readPos1]
    let sample2 = f.delayLine[readPos2]
    return sample1 * (1.0 - fracDelay) + sample2 * fracDelay


    proc process*(input: float, f: Foogler): float =
    ## Process a single sample through the Foogler
    let tap1Output = f.readTap(f.tap1Pos)
    let tap2Output = f.readTap(f.tap2Pos)
    # Mix the two taps
    let tapsOutput = (tap1Output + tap2Output) * 0.5
    # simple 1-pole low-pass
    f.filterState = f.filterState + f.filterCoeff * (tapsOutput - f.filterState)
    let feedbackSample = f.filterState * f.feedback
    # Write to delay line (input + feedback)
    f.delayLine[f.writePos] = input + feedbackSample
    # Advance write position
    f.writePos = (f.writePos + 1) mod 256
    return f.filterState


    proc foogle*(input: iterator: float or float, f: Foogler): iterator(): float =
    return iterator(): float =
    while true:
    let
    inputVal = input.floatOrIter # Get the input value
    tap1Output = f.readTap(f.tap1Pos)
    tap2Output = f.readTap(f.tap2Pos)
    tapsOutput = (tap1Output + tap2Output) * 0.5
    f.filterState = f.filterState + f.filterCoeff * (tapsOutput - f.filterState)
    let feedbackSample = f.filterState * f.feedback
    f.delayLine[f.writePos] = inputVal + feedbackSample
    f.writePos = (f.writePos + 1) mod 256
    yield f.filterState


    when isMainModule:
    import strformat
    var tickerTape = Ticker(tick: 0)

    var foogler = initFoogler(SRate)
    foogler.setTapPositions(0.09, 0.11)
    foogler.setFeedback(0.999)
    foogler.setDamping(6000.0, SRate)

    echo "\nFoogler parameters:"
    echo fmt"Tap 1 position: {foogler.tap1Pos:.2f}"
    echo fmt"Tap 2 position: {foogler.tap2Pos:.2f}"
    echo fmt"Feedback: {foogler.feedback:.2f}"
    echo fmt"Damping cutoff: {foogler.dampingCutoff:.0f} Hz"

    let input1 = pingOsc(30.0, 0.0, 5.0)
    let sinput = sinOsc(70.0, 0.0, 1.0)
    let input = sawOsc(120.0, 0.0, 1.0) + input1 + sinput
    let pr = input.foogle(foogler)

    # rather chaotic
    proc tick*(): float =
    if tickerTape.tick mod 100 == 0:
    foogler.setTapPositions(rand(0.09..0.49), rand(0.50..0.90))
    if tickerTape.tick mod 1500 == 0:
    foogler.setFeedback(rand(0.6..0.9999))
    inc tickerTape.tick
    let v = pr()
    return v


    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
    87 changes: 87 additions & 0 deletions io.nim
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@
    #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