Created
June 18, 2025 10:20
-
-
Save ingoogni/cfb66d86eab19f2f574a6bb64b617f0b to your computer and use it in GitHub Desktop.
Revisions
-
ingoogni created this gist
Jun 18, 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,255 @@ # Original C++ code: # # Created by Nigel Redmon on 12/18/12. # EarLevel Engineering: earlevel.com # Copyright 2012 Nigel Redmon # # For a complete explanation of the ADSR envelope generator and code, # read the series of articles by the author, starting here: # http://www.earlevel.com/main/2013/06/01/envelope-generators/ # # License: # # This source code is provided as is, without warranty. # You may copy and distribute verbatim copies of this document. # You may modify and use this source code to create binary code for your own purposes, free or commercial. # # 1.01 2016-01-02 njr added calcCoef to SetTargetRatio functions that were in the ADSR widget but missing in this code # 1.02 2017-01-04 njr in calcCoef, checked for rate 0, to support non-IEEE compliant compilers # 1.03 2020-04-08 njr changed float to double; large target ratio and rate resulted in exp returning 1 in calcCoef #------------------------------------------------------------------------- # # https://www.earlevel.com/main/2013/06/01/envelope-generators/ # https://www.earlevel.com/main/2013/06/02/envelope-generators-adsr-part-2/ # https://www.earlevel.com/main/2013/06/03/envelope-generators-adsr-code/ # https://www.youtube.com/watch?v=0oreYmOWgYE # https://www.earlevel.com/main/2013/06/23/envelope-generators-adsr-widget/ # https://web.archive.org/web/20250000000000*/https://www.earlevel.com/main/2013/06/23/envelope-generators-adsr-widget/ import std/[math, random] const SampleRate {.intdefine.} = 44100 SRate* = SampleRate.float type EnvState = enum envIdle, envAttack, envDecay, envSustain, envRelease ADSR* = ref object state: EnvState sampleRate: float output: float attackRate: float # in seconds decayRate: float releaseRate: float sustainLevel: float attackCoef: float decayCoef: float releaseCoef: float targetRatioA: float targetRatioDR: float attackBase: float decayBase: float releaseBase: float gate: bool proc calcCoef(rate: float, targetRatio: float): float = return if rate <= 0: 0.0 else: exp(-log10((1.0 + targetRatio) / targetRatio) / rate) proc setAttackRate*(adsr: ADSR, rate: float) = ## set attackRate in seconds adsr.attackRate = rate * adsr.sampleRate adsr.attackCoef = calcCoef(rate, adsr.targetRatioA) adsr.attackBase = (1.0 + adsr.targetRatioA) * (1.0 - adsr.attackCoef) proc setDecayRate*(adsr: ADSR, rate: float) = ## set decay rate in seconds adsr.decayRate = rate * adsr.sampleRate adsr.decayCoef = calcCoef(rate, adsr.targetRatioDR) adsr.decayBase = (adsr.sustainLevel - adsr.targetRatioDR) * (1.0 - adsr.decayCoef) proc setReleaseRate*(adsr: ADSR, rate: float) = ## set release rate in seconds adsr.releaseRate = rate * adsr.sampleRate adsr.releaseCoef = calcCoef(rate, adsr.targetRatioDR) adsr.releaseBase = -adsr.targetRatioDR * (1.0 - adsr.releaseCoef) proc setSustainLevel*(adsr: ADSR, level: float) = ## set sustain level [0.0, 1.0] adsr.sustainLevel = level adsr.decayBase = (adsr.sustainLevel - adsr.targetRatioDR) * (1.0 - adsr.decayCoef) proc setTargetRatioA*(adsr: ADSR, targetRatio: float) = ## set curvature of attack ratio 0: fast rise sharp bend, 1: linear ## https://www.earlevel.com/main/2013/06/23/envelope-generators-adsr-widget/ adsr.targetRatioA = if targetRatio >= 0.000000001: targetRatio else: 0.000000001 #-180 dB adsr.attackCoef = calcCoef(adsr.attackRate, adsr.targetRatioA); adsr.attackBase = (1.0 + adsr.targetRatioA) * (1.0 - adsr.attackCoef); proc setTargetRatioDR*(adsr: ADSR, targetRatio: float) = ## set curvature for decay and release 0: fast descent, sharp bend 1: linear ## https://www.earlevel.com/main/2013/06/23/envelope-generators-adsr-widget/ adsr.targetRatioDR = if targetRatio >= 0.000000001: targetRatio else: 0.000000001 #-180 dB adsr.decayCoef = calcCoef(adsr.decayRate, adsr.targetRatioDR) adsr.releaseCoef = calcCoef(adsr.releaseRate, adsr.targetRatioDR) adsr.decayBase = (adsr.sustainLevel - adsr.targetRatioDR) * (1.0 - adsr.decayCoef) adsr.releaseBase = -adsr.targetRatioDR * (1.0 - adsr.releaseCoef) proc getState*(adsr: ADSR): EnvState = adsr.state proc reset*(adsr: ADSR) = adsr.state = envIdle adsr.output = 0.0 proc initADSR*(a, d, s, r: float, sampleRate: float = SRate): ADSR = result = ADSR(sampleRate: sampleRate) result.reset() result.gate = false result.setAttackRate(a) result.setDecayRate(d) result.setReleaseRate(r) result.setSustainLevel(s) result.setTargetRatioA(0.3) result.setTargetRatioDR(0.0001) proc adsrGate*(adsr: ADSR, gate: bool)= if gate and not adsr.gate: adsr.state = envAttack adsr.gate = true elif not gate and adsr.state != envIdle: adsr.state = envRelease adsr.gate = false proc next*(adsr: ADSR): float = case adsr.state: of envIdle: discard of envAttack: adsr.output = adsr.attackBase + adsr.output * adsr.attackCoef; if adsr.output >= 1.0: adsr.output = 1.0 adsr.state = envDecay of envDecay: adsr.output = adsr.decayBase + adsr.output * adsr.decayCoef; if adsr.output <= adsr.sustainLevel: adsr.output = adsr.sustainLevel adsr.state = env_sustain of envSustain: discard of envRelease: adsr.output = adsr.releaseBase + adsr.output * adsr.releaseCoef; if adsr.output <= 0.0: adsr.output = 0.0 adsr.state = env_idle return adsr.output proc envelope*( sample: iterator: float or float, gate: iterator: bool, adsr: ADSR ): iterator(): float = return iterator(): float = while true: adsr.adsrGate(gate()) #manage state yield sample() * adsr.next() when isMainModule: import iterit, iteroscillator proc triggerIter*(interval: int): iterator: bool = var count = 0 return iterator(): bool = while true: let output = count mod interval == 0 inc count yield output proc tGate*( openTime: float or iterator: float, triggerPulse: iterator: bool, sampleRate: float = SRate ): iterator(): bool = return iterator(): bool = var cnt = 0 while true: let ns = int(openTime.floatOrIter * sampleRate) let trig = triggerPulse() if not trig: if cnt == 0: yield false elif cnt > 0 and cnt < ns: inc cnt yield true elif cnt >= ns: cnt = 0 yield false else: cnt = 1 yield true let sine0 = sawOsc(120.0, Pi*0.5, 1.0) let trig0 = triggerIter(int(2 * SampleRate)) let gate0 = tGate(1.9, trig0) # gate should be at least a tad shorter than the trigger interval let adsr0 = initADSR(0.3, 0.2, 0.4, 0.2) let env0 = envelope(sine0, gate0, adsr0) let sine1 = sinOsc(90.0, Pi*0.5, 1.0) let trig1 = triggerIter(int(0.4 * SampleRate)) let gate1 = tGate(0.25, trig1) let adsr1 = initADSR(0.01, 0.01, 0.01, 0.2) let env1 = envelope(sine1, gate1, adsr1) let output = (env0 / 3.0) + env1 proc tick*(): float = #inc tickerTape.tick return output() 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