Created
October 14, 2025 15:43
-
-
Save ingoogni/5da8629e1537291853783c58e97d59a5 to your computer and use it in GitHub Desktop.
type for audio 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] | |
| #import emitters | |
| template fromTyp(typ, base: typedesc) = | |
| proc `$`*(x: typ): string {.borrow.} | |
| proc `+`*(a, b: typ): typ {.borrow.} | |
| proc `-`*(a, b: typ): typ {.borrow.} | |
| proc `*`*(a: typ, b: base): typ {.borrow.} | |
| proc `*`*(a: base, b: typ): typ {.borrow.} | |
| proc `/`*(a: typ, b: base): typ {.borrow.} | |
| # Division between same type -> Ratio -> dimensionless: base | |
| # e.g. Phase / Phase → float32 | |
| proc `/`*(a, b: typ): base {.borrow.} | |
| proc `+`*(x: typ): typ {.borrow.} | |
| proc `-`*(x: typ): typ {.borrow.} | |
| proc `<`*(a, b: typ): bool {.borrow.} | |
| proc `<=`*(a, b: typ): bool {.borrow.} | |
| proc `==`*(a, b: typ): bool {.borrow.} | |
| proc `+=`*(a: var typ, b: typ) {.borrow.} | |
| proc `-=`*(a: var typ, b: typ) {.borrow.} | |
| proc `*=`*(a: var typ, b: base) {.borrow.} | |
| proc `/=`*(a: var typ, b: base) {.borrow.} | |
| template defineFromBase(typ, base: untyped) = | |
| type | |
| typ* = distinct base | |
| fromTyp(typ, base) | |
| defineFromBase(Dur, float32) # duration in seconds | |
| defineFromBase(Freq, float32) # frequency in Hz | |
| defineFromBase(Gain, float32) | |
| defineFromBase(Phase, float32) # phase in radians | |
| defineFromBase(PhaseRate, float32) # phase rate, radians per second | |
| defineFromBase(Sample, float32) # sample, actually amplitude. Theoretically, | |
| # idealy Sample would be an enum indexed array | |
| # of [Dur, Ampl] | |
| type | |
| DurStream* = iterator(): Dur | |
| FreqStream* = iterator(): Freq | |
| GainStream* = iterator(): Gain | |
| PhaseStream* = iterator(): Phase | |
| PhaseRateStream* = iterator(): PhaseRate | |
| SampleStream* = iterator(): Sample | |
| #DurEmitter* = Emitter[Dur, auto] | |
| #FreqEmitter* = Emitter[Freq, auto] | |
| #GainEmitter* = Emitter[Gain, auto] | |
| #PhaseEmitter* = Emitter[Phase, auto] | |
| #PhaseRateEmitter* = Emitter[PhaseRate, auto] | |
| #SampleEmitter* = Emitter[Sample, auto] | |
| proc `*`*(f: Freq, dt: Dur): Phase {.borrow.} | |
| proc `*`*(dt: Dur, f: Freq): Phase {.borrow.} | |
| proc `*`*(pr: PhaseRate, dt: Dur): Phase {.borrow.} | |
| proc `*`*(dt: Dur, pr: PhaseRate): Phase {.borrow.} | |
| proc `*`*(p: Phase, f: Freq): PhaseRate {.borrow.} | |
| proc `*`*(f: Freq, p: Phase): PhaseRate {.borrow.} | |
| proc `*`*(a: Sample, b: Gain): Sample {.borrow.} | |
| proc `*`*(a: Gain, b: Sample): Sample {.borrow.} | |
| proc `*=`*(s: var Sample, g: Gain) {.borrow.} | |
| proc `/`*(a: float32, dt: Dur): Freq {.borrow.} | |
| proc `/`*(a: float32, f: Freq): Dur {.borrow.} | |
| proc `/`(p: Phase, dt: Dur): PhaseRate {.borrow.} | |
| #proc `/`*(p: Phase, d: Dur): Freq = Freq(p.float32 / (d.float32 * Tau)) #clash! | |
| proc freqFromPhaseRate*(pr: PhaseRate): Freq = Freq(pr.float32 / Tau) | |
| proc `/`*(pr: PhaseRate, f: Freq): Phase {.borrow.} | |
| proc `/`*(pr: PhaseRate, dt: Dur): Freq {.borrow.} | |
| proc `mod`*(p: Phase, b: float32): Phase {.borrow.} | |
| proc `mod`*(p: Phase, b: Phase): Phase {.borrow.} | |
| proc `mod`*(d: Dur, b: Dur): Dur {.borrow.} | |
| proc `mod`*(f: Freq, b: Freq): Freq {.borrow.} | |
| proc `mod`*(s: Sample, b: float32): Sample {.borrow.} | |
| proc abs*(p: Phase): Phase {.borrow.} | |
| proc abs*(d: Dur): Dur {.borrow.} | |
| proc abs*(f: Freq): Freq {.borrow.} | |
| proc floor*(p: Phase): Phase {.borrow.} | |
| proc ceil*(p: Phase): Phase {.borrow.} | |
| proc round*(p: Phase): Phase {.borrow.} | |
| proc pow*(s: Sample, exp: float32): Sample {.borrow.} | |
| proc clamp*(s: Sample, min, max: float32): Sample {.borrow.} | |
| proc sin*(p: Phase): Sample {.borrow.} | |
| proc cos*(p: Phase): Sample {.borrow.} | |
| proc tan*(p: Phase): Sample {.borrow.} | |
| proc hz*(f: float32): Freq = Freq(f) | |
| proc sec*(d: float32): Dur = Dur(d) | |
| proc rad*(p: float32): Phase = Phase(p) | |
| proc toFloat*(f: Freq): float32 = f.float32 | |
| proc toFloat*(d: Dur): float32 = d.float32 | |
| proc toFloat*(p: Phase): float32 = p.float32 | |
| proc period*(f: Freq): Dur = 1.0'f32 / f | |
| proc frequency*(d: Dur): Freq = 1.0'f32 / d | |
| proc cycles*(f: Freq, dt: Dur): Phase = f * dt | |
| proc radians*(f: Freq, dt: Dur): Phase = f * dt * Tau | |
| const | |
| SampleRate* {.intdefine.} = 44100 #Hz | |
| SampleFreq* = SampleRate.Freq | |
| SampleDur* = 1.0'f32 / SampleFreq | |
| template orStream*(x: untyped): untyped = | |
| ## If 'x' is callable as an iterator (x()), call it | |
| ## Else, treat it as a scalar. | |
| when compiles(x()): x() else: x | |
| when isMainModule: | |
| proc linOsc*( | |
| freq: Freq or FreqStream, | |
| phase: Phase or PhaseStream = 0.0.Phase, | |
| sampleRate: Freq = SampleFreq | |
| ): SampleStream = | |
| ## | |
| var tick: Dur | |
| var lastFreq: Freq | |
| var phaseCorrection: Phase | |
| let increment: Dur = Tau / sampleRate | |
| return iterator(): Sample = | |
| while true: | |
| let f: Freq = freq.orStream | |
| let p: Phase = phase.orStream | |
| phaseCorrection += (lastFreq - f) * tick | |
| lastFreq = f | |
| yield (((tick * f) + phaseCorrection + p) mod TAU).Sample | |
| tick += increment | |
| proc sinOsc*( | |
| freq: Freq or FreqStream, | |
| phase: Phase or PhaseStream = 0.0.Phase, | |
| amp: Gain or GainStream = 1.0.Gain, | |
| sampleRate: Freq = SampleFreq | |
| ): SampleStream = | |
| var tick: Dur | |
| var lastFreq: Freq | |
| var phaseCorrection: Phase | |
| let increment: Dur = Tau / sampleRate | |
| return iterator(): Sample = | |
| while true: | |
| let f = freq.orStream | |
| let p = phase.orStream | |
| let a = amp.orStream | |
| phaseCorrection += (lastFreq - f) * tick | |
| lastFreq = f | |
| yield a * sin((tick * f) + phaseCorrection + p) | |
| tick += increment | |
| proc iterSeq*[T](item: seq[T]): iterator(): T = | |
| iterator(): T {.inline.}= | |
| for i in item: | |
| yield i | |
| var freqIter = iterSeq(@[440.0.Freq, 660.0.Freq, 550.0.Freq]) | |
| var phaseIter = iterSeq(@[0.0.Phase, 0.1.Phase, 1.2.Phase]) | |
| var osc1 = linOsc(440.0.Freq, 0.0.Phase) | |
| var osc2 = sinOsc(freqIter, phaseIter) | |
| # Pull samples | |
| echo "osc1:" | |
| echo osc1() | |
| echo osc1() | |
| echo osc1() | |
| echo "osc2:" | |
| echo osc2() | |
| echo osc2() | |
| echo osc2() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment