--Author: John Maxwell
--last modified: December 8, 1981 8:40 AM

DIRECTORY
InlineDefs USING [LowHalf],
Heuristic USING [MakeChords],
MusicDefs,
Note USING [Duration],
Score USING [GetTimeSignature],
Selection USING [AddLine,AddNote,Clear,MakeBeam,MakeChord,MakeSync],
Voice USING [ClearState,SetState,State];

HeuristicImpl:PROGRAM
IMPORTS InlineDefs, Heuristic, MusicDefs, Note, Score, Selection, Voice
EXPORTS Heuristic =
BEGIN
OPEN MusicDefs;

--***************************************************************************
--
guessing structures
--***************************************************************************

MakeSyncs
:PUBLIC PROCEDURE[begin,end:Time] =
BEGIN
i,j:CARDINAL;
n:NotePTR;
current:Time←0;
Selection.Clear[];
FOR i DECREASING IN [0..scoreLength) DO
IF score[i].time<begin THEN EXIT;
IF score[i].time> end THEN LOOP;
IF ABS[current-score[i].time]>3 THEN
BEGIN
IF selectionLength>1 THEN Selection.MakeSync[];
Selection.Clear[];
current ← score[i].time;
END;
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF voice AND selectedVoice#n.voice THEN LOOP;
Selection.AddNote[n];
ENDLOOP;
ENDLOOP;
Selection.MakeSync[];
Selection.AddLine[begin,end];
END;

MakeChords
:PUBLIC PROCEDURE[begin,end:Time] =
BEGIN
i,j,v:CARDINAL;
n:NotePTR;
FOR v IN [0..maxVoice] DO
IF voice AND selectedVoice#v THEN LOOP;
FOR i IN [0..scoreLength) DO
IF score[i].time<begin THEN LOOP;
IF score[i].time> end THEN LOOP;
Selection.Clear[];
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.beam#NIL THEN LOOP;
IF n.voice#v THEN LOOP;
Selection.AddNote[n];
ENDLOOP;
IF selectionLength>1 THEN Selection.MakeChord[];
ENDLOOP;
ENDLOOP;
Selection.AddLine[begin,end];
END;

MakeBeams:PUBLIC PROCEDURE[begin,end:Time] =
BEGIN
n:NotePTR;
vs:Voice.State;
--ts:TimeSignature;
i,j,v:CARDINAL;
change,found:BOOLEAN;
sum,sumTS,start,duration:Time←0;
count:INTEGER←0;
Beam:PROCEDURE= BEGIN IF selection[1]#NIL THEN Selection.MakeBeam[];
Selection.Clear[]; count←0; duration←0; start←sum; change←FALSE; END;
min ← begin;
max ← end;
FOR v IN [0..maxVoice] DO
Voice.ClearState[@vs];
sum←0; change←FALSE;
Selection.Clear[];
IF voice AND selectedVoice#v THEN LOOP;
FOR i IN [0..scoreLength) DO
IF score[i].time<min THEN LOOP;
IF score[i].time>max THEN LOOP;
IF score[i].type IN [measure..repeat2] THEN
BEGIN Voice.ClearState[@vs]; sum ← 0; Beam[]; LOOP; END;
--determine the sync’s logical position
sum ← Voice.SetState[@vs,score[i]];
IF NOT vs[v].found THEN LOOP;
--decide whether or not to break the current beam
IF vs[v].duration>=4096 THEN Beam[];
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.voice#v THEN LOOP;
IF n.beam#NIL THEN Beam[];
IF n.rest THEN Beam[];
IF n.value=unknown THEN Beam[];
ENDLOOP;
IF change THEN SELECT sum FROM
4096 => Beam[];
8192 => Beam[];
12288 => Beam[];
16384 => Beam[];
ENDCASE;
IF duration#0 AND vs[v].duration#duration THEN change←TRUE;
duration←vs[v].duration;
IF count>3 THEN Beam[];
IF sum=4096 AND duration<2000 THEN Beam[];
IF sum=8192 AND duration<4000 THEN Beam[];
IF sum=12288 AND duration<2000 THEN Beam[];
IF sum=16384 AND duration<4000 THEN Beam[];
--select appropriate notes for beaming
found←FALSE;
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.voice#v THEN LOOP;
IF n.beam#NIL THEN LOOP;
IF n.rest THEN LOOP;
IF n.value=unknown THEN LOOP;
Selection.AddNote[n]; found←TRUE;
ENDLOOP;
IF found THEN count ← count+1;
ENDLOOP;
ENDLOOP;
END;

MakeNTuplets:PUBLIC PROCEDURE[nt,a:INTEGER,begin,end:Time] =
BEGIN
n:NotePTR;
b:BeamPTR;
i,j,v:CARDINAL;
count:INTEGER←0;
found,beamed:BOOLEAN;
value:NoteValue←unknown;
Beam:PROCEDURE= {IF selection[1]#NIL AND count=nt THEN Selection.MakeBeam[FALSE];
IF selection[0]#NIL THEN b←selection[0].beam ELSE b←NIL;
IF b#NIL THEN BEGIN b.against←a; b.ntuple←nt; END;
IF b#NIL THEN b.beamed←beamed; beamed←TRUE;
Selection.Clear[]; count←0; value←unknown};
beamed←TRUE;
min ← begin; max ← end;
FOR v IN [0..maxVoice] DO
IF voice AND selectedVoice#v THEN LOOP;
Selection.Clear[];
FOR i IN [0..scoreLength) DO
IF score[i].time<min THEN LOOP;
IF score[i].time>max THEN LOOP;
IF score[i].type IN [measure..repeat2] THEN BEGIN Beam[]; LOOP; END;
--decide whether or not to break the current beam
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.voice#v THEN LOOP;
IF n.beam#NIL THEN Beam[];
IF n.value=unknown THEN Beam[];
IF value=unknown THEN value←n.value;
IF value#n.value THEN Beam[];
value←n.value;
ENDLOOP;
IF count=nt THEN Beam[];
--select appropriate notes for beaming
found←FALSE;
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.voice#v THEN LOOP;
IF n.beam#NIL THEN LOOP;
IF n.rest THEN beamed←FALSE;
IF n.value<eighth THEN beamed←FALSE;
IF n.value=unknown THEN LOOP;
Selection.AddNote[n]; found←TRUE;
ENDLOOP;
IF found THEN count ← count+1;
ENDLOOP;
ENDLOOP;
Beam[];
END;

--****************************************************************************
--
guessing note values
--****************************************************************************

SetNoteValues:PUBLIC PROCEDURE[begin,end:Time] =
BEGIN
sumTS:INTEGER;
ts:TimeSignature ← [4,4];
i,start,stop:CARDINAL←0;
Heuristic.MakeChords[begin,end];
FOR i DECREASING IN [0..scoreLength) DO
IF score[i].time <begin THEN EXIT;
IF score[i].time >end THEN LOOP;
IF NOT Measure[score[i].type] THEN LOOP;
stop ← start;
start ← i;
IF start>=end THEN LOOP;
ts ← Score.GetTimeSignature[score[stop].time];
sumTS ← (ts.top*256)*(64/ts.bottom);
GuessMeasure[start,stop,sumTS];
ENDLOOP;
SetDirty[begin,end];
END;


GuessMeasure:PROCEDURE[begin,end:CARDINAL,time:INTEGER] =
BEGIN
n:NotePTR;
j,i:CARDINAL;
firstTime,priorTime,totalTime:Time;
delta,bestComplexity,current:INTEGER;
firstValue,nextValue,priorValue:INTEGER;
maxValue,bestValue,totalValue:INTEGER;
IF begin=end OR time=0 THEN RETURN;
--
SUGGESTION: use a note in the prior measure as the beginning rather than the measure line
vs←ALL[[0]];
firstValue ← InlineDefs.LowHalf[time+256];
firstTime ← score[begin].time;
priorValue ← 0;
priorTime ← score[end].time+3;
--
events are processed in DESCENDING order, and score[i].value>score[i+1].value
FOR i DECREASING IN (begin..end) DO {
IF score[i].type#notes THEN LOOP;
--check for durations already given by the user
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.value=unknown THEN LOOP;
delta ← InlineDefs.LowHalf[Note.Duration[n,128]];
bestValue ← delta+vs[n.voice].value;
GOTO done;
ENDLOOP;
totalTime ← priorTime-firstTime;
totalValue ← firstValue-priorValue;
-- obtain the next note sync’s value
nextValue ← firstValue;
FOR j DECREASING IN [begin..i) DO
IF score[j].type#notes THEN LOOP;
delta ← InlineDefs.LowHalf[(totalValue*(priorTime-score[j].time))/totalTime];
nextValue ← priorValue + Quantize[delta,32];
EXIT; ENDLOOP;
--find the tic within a certain range that has the smallest complexity
delta ← InlineDefs.LowHalf[(totalValue*(priorTime-score[i].time))/totalTime];
bestValue ← priorValue+Quantize[delta,64];
maxValue ← 32000;
FOR j IN [0..maxVoice] DO
maxValue ← MIN[maxValue,(bestValue-vs[j].value)/4];
ENDLOOP;
bestComplexity ← Complexity[score[i],bestValue,@vs];
current ← bestValue-Quantize[maxValue,64];
maxValue ← bestValue+Quantize[maxValue,64]+256;
delta ← bestValue;
WHILE TRUE DO
current ← current+256;
IF current <= priorValue THEN LOOP;
IF current >= maxValue THEN EXIT;
IF current >= nextValue THEN EXIT;
IF Complexity[score[i],current,@vs]>bestComplexity THEN LOOP;
IF Complexity[score[i],current,@vs]=bestComplexity AND
ABS[bestValue-delta]<=ABS[current-delta] THEN LOOP;
bestComplexity ← Complexity[score[i],current,@vs];
bestValue ← current;
ENDLOOP;
GOTO done;
EXITS done => {
--event done, assign durations
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.value#unknown THEN LOOP;
IF voice AND n.voice#selectedVoice THEN LOOP;
maxValue ← 64*256;
delta ← bestValue-vs[n.voice].value;
FOR nv:NoteValue IN [whole..unknown) DO
IF delta<maxValue THEN {maxValue ← maxValue/2; LOOP};
IF delta>=3*(maxValue/2) THEN n.dotted ← TRUE;
n.value ← nv; EXIT;
ENDLOOP;
IF n.value=unknown THEN n.value←thirtysecond;
ENDLOOP;
SetVoiceSyncs[score[i], bestValue, @vs];
priorTime ← score[i].time;
priorValue ← bestValue}};
ENDLOOP;
END;

VoiceSyncs:TYPE = ARRAY [0..10) OF RECORD[value:INTEGER];
vs:VoiceSyncs;


SetVoiceSyncs:PROCEDURE[s:SyncPTR,value:INTEGER,vs:POINTER TO VoiceSyncs]=
INLINE BEGIN
i:CARDINAL;
FOR i IN [0..syncLength) DO
IF s.event[i]=NIL THEN EXIT;
vs[s.event[i].voice].value ← value;
ENDLOOP;
END;

Quantize:PROCEDURE[d:Time,q:INTEGER] RETURNS[INTEGER] =
BEGIN
delta:Time←(256*(64/q));
RETURN[InlineDefs.LowHalf[delta*((d+delta/2)/delta)]];
END;

Complexity:PROCEDURE[s:SyncPTR, value:INTEGER, vs:POINTER TO VoiceSyncs] RETURNS[INTEGER]=
BEGIN
x,d:Time←128;
c,y:INTEGER←0;
j,k,start:CARDINAL;
v:ARRAY [0..10) OF BOOLEAN←ALL[FALSE];
FOR j IN [0..syncLength) DO
IF s.event[j]=NIL THEN EXIT;
IF s.event[j].value#unknown THEN LOOP;
IF voice AND s.event[j].voice#selectedVoice THEN LOOP;
IF v[s.event[j].voice] THEN LOOP;
d ← value-vs[s.event[j].voice].value;
v[s.event[j].voice]←TRUE;
x←x*256; start←10;
FOR k IN [0..7] DO
x←x/2; IF x>d THEN LOOP;
d←d-x; IF start>7 THEN start←k;
IF d#0 THEN LOOP;
c←c+(k-start); EXIT;
ENDLOOP;
ENDLOOP;
FOR j IN [0..10) DO IF v[j] THEN y←y+1; ENDLOOP;
RETURN[(c*10)/y];
END;

END...


GuessLogical:PROCEDURE[begin,end:CARDINAL,time:Time] =
BEGIN
i,j:CARDINAL;
voice:ARRAY [0..10) OF CARDINAL ← ALL[0];
vs:Voice.State;
max:Time;
Voice.ClearState[@vs];
FOR i IN [begin..end] DO
max ← Voice.SetState[@vs,score[i]];
FOR j IN [0..maxVoice] DO
IF NOT vs[j].found THEN LOOP;
voice[j] ← i;
ENDLOOP;
ENDLOOP;
END;

FOR v IN [0..maxVoice] DO
duration ← (time*(vs[v]-score[i].time))/measure;
SELECT TRUE FROM
duration IN [15000..19000] => value ← whole;
duration IN [7500..9000] => value ← half;
duration IN [3500..4500] => value ← quarter;
duration IN [1600..2400] => value ← eighth;
duration IN [900..1200] => value ← sixteenth;
duration IN [200..600] => value ← thirtysecond;
ENDCASE => value ← unknown;
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j])=NIL THEN EXIT;
IF n.voice#v THEN LOOP;
vs[v] ← score[i].time;
IF n.value#unknown THEN LOOP;
n.value ← value;
ENDLOOP;
ENDLOOP;
ENDLOOP;
END;


PortionOutTime:PROCEDURE[voice,s1,s2:CARDINAL,duration,ratio:INTEGER] =
BEGIN
--
duration is the logical duration of the interval (64*256=whole)
--ratio is the number of tics that a quarter note occupies
beats,units,d:INTEGER;
i,j,r1,r2,count:CARDINAL←0; index←1;
FOR i IN (s1..s2) DO
IF NOT Sync.InVoice[score[i],voice] THEN LOOP;
list[index]←[score[i],FALSE]; index ← index + 1;
IF score[i].value=0 THEN LOOP;
PortionOutTime[voice,s1,i,score[i].value-score[s1].value,ratio];
PortionOutTime[voice,i,s2,score[s2].value-score[i].value,ratio];
RETURN;
ENDLOOP;
IF index=1 THEN RETURN;
list[0] ← [score[s1],FALSE];
list[index] ← [score[s2],FALSE];
index←index+1;
[beats,units] ← Tranlate[duration]; --
3/4, 6/8, 3/16, 15/24, etc.
--look for runs of equally spaced notes
--then try successively finer grids
--all durations must be strictly ordered
d ← list[1].value-list[0].value;
FOR i IN [0..index) DO list[i].ts.top←0; ENDLOOP;

FOR i IN [2..index) DO
IF ABS[d-list[i].value-list[i-1].value]<d/5 THEN BEGIN r2←i-1; LOOP; END;
IF r2-r1>1 THEN FOR j IN (r1..r2] DO list[j].ts.top←1;
d←list[i].value-list[i-1].value; r1←i-1;
ENDLOOP;
FOR i IN [0..index) DO IF NOT list[i].tuple THEN count←count+1; ENDLOOP;
WHILE units<=128 DO
IF count>beats THEN {units←units*2; beats←beats*2; LOOP};
IF units=128 THEN RETURN;
d ← CheckGrid[s1,s2,beats,units,voice];
IF d<duration/5 THEN EXIT;
units ← units*2;
beats ← beats*2;
ENDLOOP;
AssignGrid[s1,s2,beats,units,voice];
r1 ← 0;
FOR i IN [0..index) DO
IF list[i].ts.top=1 THEN LOOP;
IF i-r1<2 THEN BEGIN r1←i; LOOP; END;
d ← (list[i].value-list[r1].value)/(i-r1-1);
FOR j IN (r1..i) DO list[j].value ← list[r1].value+(j-r1)*d; ENDLOOP;
r1←i; ENDLOOP;
END;

CheckGrid[s1,s2:CARDINAL,unit,voice:INTEGER] RETURNS[INTEGER]=
BEGIN
i:CARDINAL;
d:Time←256*(64/unit);
sum:Time←score[s1].value;
delta:Time ←0;
FOR i IN (s1..s2) DO
WHILE score[i].value>sum DO sum←sum+d; ENDLOOP;
IF NOT Sync.InVoice[score[i],voice] THEN LOOP;
WHILE score[i].time*ratio-delta/2>sum DO sum←sum+d; ENDLOOP;
delta← delta+ABS[score[i].time*ratio-sum];
sum ← sum+d;
ENDLOOP;
RETURN[InlineDefs.LowHalf[delta]];
END;

AssignGrid[s1,s2:CARDINAL,unit,voice:INTEGER]=
BEGIN
i:CARDINAL;
d:Time←256*(64/unit);
sum:Time←score[s1].value;
FOR i IN (s1..s2) DO
WHILE score[i].value>sum DO sum←sum+d; ENDLOOP;
IF NOT Sync.InVoice[score[i],voice] THEN LOOP;
WHILE score[i].time*ratio-delta/2>sum DO sum←sum+d; ENDLOOP;
score[i].value←InlineDefs.LowHalf[sum];
sum ← sum+d;
ENDLOOP;
END;

Translate:PROCEDURE[duration:INTEGER] RETURNS[INTEGER]=
BEGIN
units:INTEGER←64;
duration←duration/256;
WHILE (duration MOD 2)=0 DO
duration←duration/2;
units←units/2;
IF units=1 THEN EXIT;
ENDLOOP;
RETURN[duration,units];
END;

index:CARDINAL;
list:ARRAY [0..30) OF RECORD[sync:SyncPTR,tuple:BOOLEAN];
ss:ARRAY [0..10) OF Time;

END.