12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455(* 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 Lfo.mli *)typeshape=Sine|Triangle|Square|Saw_up|Saw_down|Sample_and_holdletshapes=[Sine;Triangle;Square;Saw_up;Saw_down;Sample_and_hold]letname=function|Sine->"sine"|Triangle->"triangle"|Square->"square"|Saw_up->"saw up"|Saw_down->"saw down"|Sample_and_hold->"sample & hold"letvalue(shape:shape)(phase:float):float=matchshapewith|Sine->Oscillator.waveSinephase|Triangle->Oscillator.waveTrianglephase|Square->Oscillator.waveSquarephase|Saw_up->Oscillator.waveSawtoothphase|Saw_down->-.Oscillator.waveSawtoothphase|Sample_and_hold->0.(* the phase; the random generator's state, and the value it last drew,
* held *)typet={mutablephase:float;mutablerandom:int;mutableheld:float}letcreate?(seed=0)():t=letrandom=Noise.lcgseedin{phase=0.;random;held=Noise.uniformrandom}letfill(t:t)(shape:shape)~(rate:float)(out:Signal.t):unit=letdphase=rate/.float_of_intSignal.rateinfori=0toArray.lengthout-1doout.(i)<-(matchshapewithSample_and_hold->t.held|_->valueshapet.phase);letnext=t.phase+.dphasein(* a new period: sample and hold draws its next value *)ifnext>=1.then(t.random<-Noise.lcgt.random;t.held<-Noise.uniformt.random);t.phase<-next-.Float.floornextdoneletunipolar(x:float):float=(x+.1.)/.2.letof_tempo~(bpm:float)~(beats:float):float=bpm/.60./.beats