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/Drive.ml.html
Source file Drive.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(* 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 Drive.mli *) type shape = Hard | Tanh | Cubic | Asymmetric let shapes = [ Hard; Tanh; Cubic; Asymmetric ] let name = function Hard -> "hard" | Tanh -> "tanh" | Cubic -> "cubic" | Asymmetric -> "asymmetric" let bias = 0.3 let curve (shape : shape) (x : float) : float = match shape with | Hard -> Float.max (-1.) (Float.min 1. x) | Tanh -> tanh x | Cubic -> let x = Float.max (-1.) (Float.min 1. x) in 1.5 *. (x -. (x *. x *. x /. 3.)) | Asymmetric -> tanh (x +. bias) -. tanh bias (* an 8th-order Butterworth low-pass: four biquads, the Qs of its poles' * pairs *) let butterworth_qs = [| 0.50980; 0.60134; 0.89998; 2.56292 |] let cutoff = 18000. type t = { oversampling : int; filter : Filter.biquad array; (* the four stages, at L x the rate *) up : Filter.memory array; down : Filter.memory array; mutable dc : float; (* the high-pass's low-pass, taken away *) (* the last block's knobs, ramped from (nan: none yet) *) mutable last_drive : float; mutable last_mix : float; } let create ~(oversampling : int) () : t = let rate = float_of_int (oversampling * Signal.rate) in { oversampling; filter = Array.map (fun q -> Filter.biquad ~rate Low_pass ~cutoff ~q) butterworth_qs; up = Array.init 4 (fun _ -> Filter.silence ()); down = Array.init 4 (fun _ -> Filter.silence ()); dc = 0.; last_drive = Float.nan; last_mix = Float.nan; } let chain (t : t) (memories : Filter.memory array) (x : float) : float = let y = ref x in for k = 0 to 3 do y := Filter.step t.filter.(k) memories.(k) !y done; !y let dc_coefficient = Filter.one_pole_coefficient 10. let process (t : t) (shape : shape) ~(drive : float) ~(mix : float) (s : Signal.t) : unit = let l = t.oversampling and n = Array.length s in let from_drive = if Float.is_nan t.last_drive then drive else t.last_drive and from_mix = if Float.is_nan t.last_mix then mix else t.last_mix in Array.iteri (fun i x -> (* the knobs ramped over the block, the drive in dB *) let g = Mix.of_decibels (Effect.ramp from_drive drive i n) and mix = Effect.ramp from_mix mix i n in let wet = if l = 1 then curve shape (g *. x) else begin (* the sample, then L - 1 zeros, each through the chain; the * zeros make the level L times smaller, hence the L *) let out = ref 0. in for k = 0 to l - 1 do let up = chain t t.up (if k = 0 then float_of_int l *. x else 0.) in let y = chain t t.down (curve shape (g *. up)) in if k = 0 then out := y done; !out end in t.dc <- t.dc +. (dc_coefficient *. (wet -. t.dc)); s.(i) <- ((1. -. mix) *. x) +. (mix *. (wet -. t.dc))) s; t.last_drive <- drive; t.last_mix <- mix let knobs : Effect.knob list = [ { name = "shape"; control = Selector (List.map name shapes); initial = 1. }; { name = "gain"; control = Knob (0., 36.); initial = 12. }; { name = "oversampling"; control = Switch; initial = 1. }; ] let fx () : Effect.t = (* a drive per channel and per oversampling, each keeping its filters' * state while the other is used *) let plain = (create ~oversampling:1 (), create ~oversampling:1 ()) and oversampled = (create ~oversampling:4 (), create ~oversampling:4 ()) in let shape = ref Tanh and gain = ref 0. and x4 = ref false in let set (knob : string) (x : float) = match knob with | "shape" -> shape := List.nth shapes (max 0 (min (List.length shapes - 1) (Control.index x))) | "gain" -> gain := x | "oversampling" -> x4 := Control.on x | _ -> () in List.iter (fun (k : Effect.knob) -> set k.name k.initial) knobs; let process (s : Signal.stereo) = let left, right = if !x4 then oversampled else plain in process left !shape ~drive:!gain ~mix:1. s.left; process right !shape ~drive:!gain ~mix:1. s.right in { name = "drive"; knobs; set; process; meters = (fun () -> []) }
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>