1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071(* 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 *)typemodel=Naive|Zero_delay|Nonlinearletmodels=[Naive;Zero_delay;Nonlinear]letname=functionNaive->"naive"|Zero_delay->"zero-delay feedback"|Nonlinear->"nonlinear"(* the four poles' memories: the naive ones' outputs, the zero-delay
* ones' integrator states *)typet={s:floatarray}letcreate():t={s=Array.make40.}letreset(t:t):unit=Array.fillt.s040.letclamp(cutoff:float):float=Float.min20000.(Float.max10.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 *)letnaive(t:t)~(k:float)~(c:float)~(cutoff:float)(x:float):float=letg=1.-.exp(-2.*.Float.pi*.clampcutoff/.float_of_intSignal.rate)inletinput=ref((x*.(1.+.(c*.k)))-.(k*.t.s.(3)))infori=0to3dot.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) *)letzero_delay(t:t)~(nonlinear:bool)~(k:float)~(c:float)~(cutoff:float)(x:float):float=letg=tan(Float.pi*.clampcutoff/.float_of_intSignal.rate)inletbig_g=g/.(1.+.g)inlets=t.sinletsigma=((big_g*.big_g*.big_g*.s.(0))+.(big_g*.big_g*.s.(1))+.(big_g*.s.(2))+.s.(3))/.(1.+.g)inletg4=big_g*.big_g*.big_g*.big_ginletx=x*.(1.+.(c*.k))inlety=((g4*.x)+.sigma)/.(1.+.(k*.g4))in(* the loop's input, now known; saturating, it can't run away *)letu=x-.(k*.y)inletinput=ref(ifnonlinearthentanhuelseu)infori=0to3do(* the transistors' saturation at each pole's input *)letv=big_g*.((ifnonlinear&&i>0thentanh!inputelse!input)-.s.(i))inletout=v+.s.(i)ins.(i)<-out+.v;input:=outdone;!inputletprocess?(compensation=0.)(t:t)(model:model)~(cutoff:Signal.t)~(resonance:float)(samples:Signal.t):unit=letk=resonanceandc=compensationinfori=0toArray.lengthsamples-1doletcutoff=cutoff.(i)insamples.(i)<-(matchmodelwith|Naive->naivet~k~c~cutoffsamples.(i)|Zero_delay->zero_delayt~nonlinear:false~k~c~cutoffsamples.(i)|Nonlinear->zero_delayt~nonlinear:true~k~c~cutoffsamples.(i))done