123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134(* 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 Sequencer.mli *)typestep={note:intoption;accent:bool;slide:bool;locks:(string*float)list}letrest={note=None;accent=false;slide=false;locks=[]}letnote?(accent=false)?(slide=false)(n:int):step={note=Somen;accent;slide;locks=[]}letlock(s:step)(name:string)(v:float):step={swithlocks=(name,v)::List.remove_assocnames.locks}letunlock(s:step):step={swithlocks=[]}typeevent=Note_onof{note:int;accent:bool;glide:bool}|Note_offletrate=float_of_intSignal.rateletsamples_per_step(bpm:float):float=60./.(bpm*.4.)*.ratetypet={mutablepattern:steparray;mutablebpm:float;mutablerunning:bool;mutableclock:int;(* samples since the start *)mutablenext:int;(* the step to begin next *)mutablenext_at:float;(* its exact time, in samples *)mutableoff_at:floatoption;(* the gate's closing, exact *)mutablesliding:bool;(* the step sounding slides into the next *)mutablesounding:int;(* the step sounding, for a panel *)mutableblock_start:int;(* the block last advanced, for [position] *)}letcreate?(bpm=120.)(pattern:steparray):t={pattern;bpm;running=false;clock=0;next=0;next_at=0.;off_at=None;sliding=false;sounding=0;block_start=0}letset_pattern(t:t)(p:steparray):unit=t.pattern<-pletset_bpm(t:t)(bpm:float):unit=t.bpm<-Float.max20.bpmletrunning(t:t):bool=t.runningletstep(t:t):int=t.soundingletstart(t:t):unit=t.running<-true;t.next<-0;t.next_at<-float_of_intt.clock;t.off_at<-None;t.sliding<-falseletstop(t:t):unit=t.running<-false(* the first sample at or after an exact time *)letsample_of(time:float):int=int_of_float(Float.ceil(time-.1e-9))letadvance(t:t)(n:int)(f:int->event->unit):unit=t.block_start<-t.clock;letblock_end=t.clock+ninletcontinue=reft.runninginwhile!continuedoletlen=Array.lengtht.patterninletstart=sample_oft.next_atinletoff=Option.mapsample_oft.off_atinmatchoffwith(* the gate closes before the next step begins *)|Someowheno<=start&&o<block_end->f(o-t.clock)Note_off;t.off_at<-None|_whenstart<block_end&&len>0->letk=t.nextmodleninlets=t.pattern.(k)inletsps=samples_per_stept.bpmin(matchs.notewith|Somen->f(start-t.clock)(Note_on{note=n;accent=s.accent;glide=t.sliding});letnext_is_note=t.pattern.((k+1)modlen).note<>Noneint.sliding<-s.slide&&next_is_note;t.off_at<-(ift.slidingthenNoneelseSome(t.next_at+.(sps/.2.)))|None->(* a rest after a slide: the held gate closes now *)ift.slidingthenf(start-t.clock)Note_off;t.sliding<-false);t.sounding<-k;t.next<-t.next+1;t.next_at<-t.next_at+.sps|_->continue:=falsedone;t.clock<-block_end(*****************************************************************************)(* Parameter locks *)(*****************************************************************************)typelocks=Per_step|Pointsoffloatletlock_value(locks:locks)(pattern:steparray)(name:string)(position:float):floatoption=letlen=Array.lengthpatterniniflen=0thenNoneelseletp=Float.remposition(float_of_intlen)inletp=ifp<0.thenp+.float_of_intlenelsepinmatchlockswith|Per_step->List.assoc_optnamepattern.(min(len-1)(Float.to_intp)).locks|Pointssmoothing->((* the lock points, in order: (step, value) *)letpoints=List.concat(List.initlen(funi->matchList.assoc_optnamepattern.(i).lockswithSomev->[(float_of_inti,v)]|None->[]))inmatchpointswith|[]->None|[(_,v)]->Somev|first::_->letlast=List.nthpoints(List.lengthpoints-1)in(* a, the point at or before p, b the one after: round the
* pattern's end if need be *)leta=List.fold_left(funa(i,v)->ifi<=pthen(i,v)elsea)(fstlast-.float_of_intlen,sndlast)pointsinletb=matchList.find_opt(fun(i,_)->i>p)pointswithSomeb->b|None->(fstfirst+.float_of_intlen,sndfirst)inlet(ai,av),(bi,bv)=(a,b)in(* held, then a line over the last [smoothing] of the way *)letstart=bi-.(smoothing*.(bi-.ai))inifp<startthenSomeavelseSome(av+.((bv-.av)*.(p-.start)/.(bi-.start))))letposition(t:t)(offset:int):floatoption=if(nott.running)||t.next=0thenNoneelse(* the last step begun, [next - 1], began at [next_at - sps]; the
* steps in a block all [sps] apart *)letsps=samples_per_stept.bpminlets=float_of_int(t.block_start+offset)inletp=float_of_int(t.next-1)-.((t.next_at-.sps-.s)/.sps)inifp<0.thenNoneelseSomepletlocked(t:t)(locks:locks)(name:string)(offset:int):floatoption=Option.bind(positiontoffset)(lock_valuelockst.patternname)