1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889(* 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 Envelope.mli *)typet={attack:float;decay:float;sustain:float;release:float}letpercussive~(attack:float)~(decay:float):t={attack;decay;sustain=0.;release=0.}(* the attack, decay, sustain part: as if the note were held forever *)letpressed(e:t)(time:float):float=iftime<0.then0.elseiftime<e.attackthentime/.e.attackelseiftime<e.attack+.e.decaythen1.-.((1.-.e.sustain)*.(time-.e.attack)/.e.decay)elsee.sustainletlevel(e:t)~(held:float)(time:float):float=iftime<heldthenpressedetimeelse(* the release, from wherever the volume was when let go *)letfrom=pressedeheldinife.release<=0.||time>=held+.e.releasethen0.elsefrom*.(1.-.((time-.held)/.e.release))letduration(e:t)~(held:float):float=held+.e.releaseletapply(e:t)~(held:float)(samples:Signal.t):Signal.t=Array.mapi(funix->x*.levele~held(float_of_inti/.float_of_intSignal.rate))samplestypecurve=Linear|Exponentialtypestage=Idle|Attack|Decay|Sustain|Release(* the stage and the level; for a straight release, its slope, from
* where it started *)typerunning={mutablestage:stage;mutablelevel:float;mutablerelease_from:float}letstart():running={stage=Idle;level=0.;release_from=0.}letgate_on(r:running):unit=r.stage<-Attackletgate_off(r:running):unit=ifr.stage<>Idlethen(r.stage<-Release;r.release_from<-r.level)letstage(r:running):stage=r.stageletcurrent(r:running):float=r.level(* a time in samples; under one sample, at once *)letsamples(seconds:float):float=Float.max1.(seconds*.float_of_intSignal.rate)(* a one-pole's step taking [seconds] / [per] to go 1 / e of the way *)letcoefficient~(per:float)(seconds:float):float=1.-.exp(-.per/.samplesseconds)letsilent=0.00001letfill(curve:curve)(e:t)(r:running)(out:Signal.t):unit=letsustain=Float.min1.(Float.max0.e.sustain)infori=0toArray.lengthout-1do(match(r.stage,curve)with|Idle,_->r.level<-0.|Attack,Linear->r.level<-r.level+.(1./.samplese.attack)(* aiming at 1.5, arriving at 1 in the attack's time *)|Attack,Exponential->r.level<-r.level+.((1.5-.r.level)*.coefficient~per:(log3.)e.attack)(* the decay and the sustain move to the sustain level, which may
* change: a knob turned while the note is held *)|(Decay|Sustain),Linear->letstep=(1.-.sustain)/.samplese.decayinr.level<-(ifr.level>sustainthenFloat.maxsustain(r.level-.step)elseFloat.minsustain(r.level+.step))|(Decay|Sustain),Exponential->r.level<-r.level+.((sustain-.r.level)*.coefficient~per:(log1000.)e.decay)|Release,Linear->r.level<-Float.max0.(r.level-.(r.release_from/.samplese.release))|Release,Exponential->r.level<-r.level*.(1.-.coefficient~per:(log1000.)e.release));(* the stage changes *)(matchr.stagewith|Attackwhenr.level>=1.->r.level<-1.;r.stage<-Decay|DecaywhenFloat.abs(r.level-.sustain)<silent->r.stage<-Sustain|Releasewhenr.level<silent->r.level<-0.;r.stage<-Idle|_->());out.(i)<-r.leveldone