Last active
June 14, 2025 06:47
-
-
Save ingoogni/07249371a39b506b8a5fb0daff304b5f to your computer and use it in GitHub Desktop.
Revisions
-
ingoogni revised this gist
Jun 14, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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, when parameter is iterator f.setTapPositions(tap1.floatOrIter, tap2.floatOrIter) f.setFeedback(feedback.floatOrIter) f.setDamping(cutOffHz.floatOrIter) -
ingoogni revised this gist
Jun 14, 2025 . 1 changed file with 10 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal 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: -
ingoogni revised this gist
Jun 14, 2025 . 1 changed file with 5 additions and 14 deletions.There are no files selected for viewing
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 charactersOriginal 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 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) 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 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: 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 -
ingoogni revised this gist
Jun 14, 2025 . 1 changed file with 1 addition and 2 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -132,9 +132,8 @@ when isMainModule: # rather chaotic proc tick*(): float = if tickerTape.tick mod 10000 == 0: foogler.setTapPositions(rand(0.09..0.49), rand(0.50..0.90)) foogler.setFeedback(rand(0.6..0.9999)) inc tickerTape.tick let v = pr() -
ingoogni created this gist
Jun 13, 2025 .There are no files selected for viewing
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 charactersOriginal 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 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 charactersOriginal 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