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_effects/Dynamics.ml.html
Source file Dynamics.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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162(* 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 Dynamics.mli *) type mode = Compressor | Limiter | Gate let modes = [ Compressor; Limiter; Gate ] let name = function Compressor -> "compressor" | Limiter -> "limiter" | Gate -> "gate" type detector = Peak | Rms type settings = { mode : mode; threshold : float; ratio : float; knee : float; attack : float; release : float; makeup : float; detector : detector; lookahead : float; } let compressor = { mode = Compressor; threshold = -20.; ratio = 4.; knee = 6.; attack = 0.005; release = 0.1; makeup = 0.; detector = Rms; lookahead = 0.; } let limiter = { compressor with mode = Limiter; threshold = -1.; knee = 0.; attack = 0.0005; release = 0.05; detector = Peak; lookahead = 0.005 } let gate = { compressor with mode = Gate; threshold = -40.; ratio = 10.; knee = 0.; attack = 0.001; release = 0.1; detector = Peak } let rate = float_of_int Signal.rate let curve (s : settings) (x : float) : float = let t = s.threshold and w = s.knee in match s.mode with | Gate -> if x >= t then x else t +. ((x -. t) *. s.ratio) | Compressor | Limiter -> let r = if s.mode = Limiter then Float.infinity else Float.max 1. s.ratio in let over = x -. t in if 2. *. over < -.w then x else if 2. *. Float.abs over <= w && w > 0. then x +. ((1. /. r) -. 1.) *. ((over +. (w /. 2.)) ** 2.) /. (2. *. w) else t +. (over /. r) (* the look-ahead's line, per side: the last [Array.length] samples *) type line = { samples : float array; mutable at : int } let longest_lookahead = Signal.samples 0.02 type t = { mutable reduction : float; (* dB, smoothed *) mutable mean_square : float; (* the RMS detector's *) left : line; right : line; heard : line; (* the detector's input, for the look-ahead window's loudest *) mutable last_makeup : float; (* nan: none yet *) } let create () : t = let line () = { samples = Array.make (longest_lookahead + 1) 0.; at = 0 } in { reduction = 0.; mean_square = 0.; left = line (); right = line (); heard = line (); last_makeup = Float.nan } let reduction (t : t) : float = t.reduction (* [x] in, the sample [d] ago out *) let delayed (l : line) (d : int) (x : float) : float = let n = Array.length l.samples in l.samples.(l.at) <- x; let y = l.samples.((l.at - d + n) mod n) in l.at <- (l.at + 1) mod n; y (* a one-pole's coefficient for a time constant: 63% of the way in * [seconds] *) let pole (seconds : float) : float = exp (-1. /. (Float.max 1e-5 seconds *. rate)) let rms_pole = pole 0.01 let process ?(key : Signal.stereo option) (t : t) (s : settings) (out : Signal.stereo) : unit = let n = Array.length out.left in let attack = pole s.attack and release = pole s.release in let d = min longest_lookahead (Signal.samples s.lookahead) in let from_makeup = if Float.is_nan t.last_makeup then s.makeup else t.last_makeup in for i = 0 to n - 1 do let heard = match key with Some k -> k | None -> out in let v = Float.max (Float.abs heard.left.(i)) (Float.abs heard.right.(i)) in (* the limiter hears the loudest of its look-ahead window: the * samples not out yet, one of which is the next out *) let v = if s.mode = Limiter && d > 0 then begin ignore (delayed t.heard d v); let n = Array.length t.heard.samples and loudest = ref 0. in for k = 1 to d + 1 do loudest := Float.max !loudest t.heard.samples.((t.heard.at - k + n) mod n) done; !loudest end else v in (* the level, in dB *) let level = match s.detector with | Peak -> 20. *. log10 (Float.max 1e-6 v) | Rms -> t.mean_square <- (rms_pole *. t.mean_square) +. ((1. -. rms_pole) *. v *. v); 10. *. log10 (Float.max 1e-12 t.mean_square) in (* the curve's reduction, smoothed: fast up, slow down; the limiter * down at once (its window saw the peak coming: no overshoot) *) let wanted = level -. curve s level in let a = if wanted > t.reduction then if s.mode = Limiter then 0. else attack else release in t.reduction <- (a *. t.reduction) +. ((1. -. a) *. wanted); let gain = Mix.of_decibels (Effect.ramp from_makeup s.makeup i n -. t.reduction) in out.left.(i) <- gain *. delayed t.left d out.left.(i); out.right.(i) <- gain *. delayed t.right d out.right.(i) done; t.last_makeup <- s.makeup let knobs : Effect.knob list = [ { name = "mode"; control = Selector (List.map name modes); initial = 0. }; { name = "threshold"; control = Knob (-60., 0.); initial = -20. }; { name = "ratio"; control = Knob (1., 20.); initial = 4. }; { name = "attack"; control = Knob (0.0005, 0.1); initial = 0.005 }; { name = "release"; control = Knob (0.01, 1.); initial = 0.1 }; { name = "makeup"; control = Knob (0., 24.); initial = 0. }; ] let fx () : Effect.t = let t = create () and s = ref compressor in let set (knob : string) (x : float) = match knob with | "mode" -> let mode = List.nth modes (max 0 (min (List.length modes - 1) (Control.index x))) in (* the mode's own knee, detector and look-ahead; the knobs kept *) let m = match mode with Compressor -> compressor | Limiter -> limiter | Gate -> gate in s := { !s with mode; knee = m.knee; detector = m.detector; lookahead = m.lookahead } | "threshold" -> s := { !s with threshold = x } | "ratio" -> s := { !s with ratio = x } | "attack" -> s := { !s with attack = x } | "release" -> s := { !s with release = x } | "makeup" -> s := { !s with makeup = x } | _ -> () in List.iter (fun (k : Effect.knob) -> set k.name k.initial) knobs; { name = "dynamics"; knobs; set; process = (fun out -> process t !s out); meters = (fun () -> [ ("reduction", t.reduction) ]) }
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>