package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.audio_instruments/Moog_ladder.ml.html
Source file Moog_ladder.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See Moog_ladder.mli *) type model = Naive | Zero_delay | Nonlinear let models = [ Naive; Zero_delay; Nonlinear ] let name = function Naive -> "naive" | Zero_delay -> "zero-delay feedback" | Nonlinear -> "nonlinear" (* the four poles' memories: the naive ones' outputs, the zero-delay * ones' integrator states *) type t = { s : float array } let create () : t = { s = Array.make 4 0. } let reset (t : t) : unit = Array.fill t.s 0 4 0. let clamp (cutoff : float) : float = Float.min 20000. (Float.max 10. cutoff) (* one pole after the other, the fourth's output fed back from the last * sample: the loop can't be computed within the sample, so it is * delayed by one *) let naive (t : t) ~(k : float) ~(c : float) ~(cutoff : float) (x : float) : float = let g = 1. -. exp (-2. *. Float.pi *. clamp cutoff /. float_of_int Signal.rate) in let input = ref ((x *. (1. +. (c *. k))) -. (k *. t.s.(3))) in for i = 0 to 3 do t.s.(i) <- t.s.(i) +. (g *. (!input -. t.s.(i))); input := t.s.(i) done; t.s.(3) (* the zero-delay loop: each pole a trapezoidal integrator, whose output * is G (its input) + (its state) / (1 + g); four in a row, the output * G^4 u + sigma, sigma from the states; with u = x - k y, solved: * y = (G^4 x + sigma) / (1 + k G^4) *) let zero_delay (t : t) ~(nonlinear : bool) ~(k : float) ~(c : float) ~(cutoff : float) (x : float) : float = let g = tan (Float.pi *. clamp cutoff /. float_of_int Signal.rate) in let big_g = g /. (1. +. g) in let s = t.s in let sigma = ((big_g *. big_g *. big_g *. s.(0)) +. (big_g *. big_g *. s.(1)) +. (big_g *. s.(2)) +. s.(3)) /. (1. +. g) in let g4 = big_g *. big_g *. big_g *. big_g in let x = x *. (1. +. (c *. k)) in let y = ((g4 *. x) +. sigma) /. (1. +. (k *. g4)) in (* the loop's input, now known; saturating, it can't run away *) let u = x -. (k *. y) in let input = ref (if nonlinear then tanh u else u) in for i = 0 to 3 do (* the transistors' saturation at each pole's input *) let v = big_g *. ((if nonlinear && i > 0 then tanh !input else !input) -. s.(i)) in let out = v +. s.(i) in s.(i) <- out +. v; input := out done; !input let process ?(compensation = 0.) (t : t) (model : model) ~(cutoff : Signal.t) ~(resonance : float) (samples : Signal.t) : unit = let k = resonance and c = compensation in for i = 0 to Array.length samples - 1 do let cutoff = cutoff.(i) in samples.(i) <- (match model with | Naive -> naive t ~k ~c ~cutoff samples.(i) | Zero_delay -> zero_delay t ~nonlinear:false ~k ~c ~cutoff samples.(i) | Nonlinear -> zero_delay t ~nonlinear:true ~k ~c ~cutoff samples.(i)) done
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>