-- Author: John Maxwell
-- last modified: November 10, 1983 1:49 pm
-- Last Edited by: Maxwell, November 14, 1983 10:39 am

DIRECTORY
Inline USING [LowHalf],
Event USING [Sync],
Heuristic USING [MakeChords],
MusicDefs,
Note USING [Duration, InVoice],
Score USING [GetTimeSignature],
Selection USING [AddLine, AddNote, Clear, MakeBeam, MakeChord, MakeSync, selection],
Voice USING [ClearState, Selected, SetState, State];

HeuristicImpl: PROGRAM
IMPORTS Inline, Event, Heuristic, MusicDefs, Note, Score, Selection, Voice
E
XPORTS Heuristic =
BEGIN
OPEN MusicDefs;

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

MakeSyncs: PUBLIC PROCEDURE[score: ScorePTR, begin, end: Time] =
BEGIN
n: NotePTR;
current: Time ← 0;
Selection.Clear[];
FOR i: CARDINAL DECREASING IN [0..score.length) 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 Selection.selection.length > 1 THEN Selection.MakeSync[];
Selection.Clear[];
current ← score[i].time;
END;
IF score[i].type # sync THEN LOOP;
FOR j: CARDINAL IN [0..Event.Sync[score[i]].length) DO
IF (n ← Event.Sync[score[i]].note[j]) = NIL THEN EXIT;
IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP;
Selection.AddNote[score, n];
ENDLOOP;
ENDLOOP;
Selection.MakeSync[];
Selection.AddLine[score, begin, end];
END;

MakeChords: PUBLIC PROCEDURE[score: ScorePTR, begin, end: Time] =
BEGIN
n: NotePTR;
count: CARDINAL;
FOR v: CARDINAL IN [0..score.maxVoice] DO
IF ~Voice.Selected[score, v] THEN LOOP;
FOR i: CARDINAL IN [0..score.length) DO
IF score[i].time < begin THEN LOOP;
IF score[i].time > end THEN LOOP;
IF score[i].type # sync THEN LOOP;
Selection.Clear[];
count ← 0;
FOR j: CARDINAL IN [0..Event.Sync[score[i]].length) DO
n ← Event.Sync[score[i]].note[j];
IF n.beam # NIL THEN LOOP;
IF n.voice # v THEN LOOP;
Selection.AddNote[score, n];
count ← count + 1;
ENDLOOP;
IF count > 1 THEN Selection.MakeChord[];
ENDLOOP;
ENDLOOP;
Selection.AddLine[score, begin, end];
END;

MakeBeams: PUBLIC PROCEDURE[score: ScorePTR, begin, end: Time] =
BEGIN
n: NotePTR;
vs: Voice.State;
-- ts: TimeSignature;
change, found: BOOLEAN;
sum, sumTS, start, duration: Time ← 0;
count: INTEGER ← 0;
Beam: PROCEDURE = BEGIN IF Selection.selection[1] # NIL THEN Selection.MakeBeam[];
Selection.Clear[]; count ← 0; duration ← 0; start ← sum; change ← FALSE; END;
SetDirty[score, begin, end];
FOR v:
CARDINAL IN [0..score.maxVoice] DO
Voice.Cl
earState[@vs];
sum ← 0; change ← FALSE;
Selection.Clear[];
IF ~Voice.Selected[score, v] THEN LOOP;
FOR i: CARDINAL IN [0..score.length) DO
IF score[i].time < begin THEN LOOP;
IF score[i].time > end THEN LOOP;
IF score[i].type = measure THEN -- IN [measure..repeat2]
BEGIN Voice.ClearState[@vs]; sum ← 0; Beam[]; LOOP; END;
IF score[i].type # sync THEN LOOP;
-- 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: CARDINAL IN [0..Event.Sync[score[i]].length) DO
IF (n ← Event.Sync[score[i]].note[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: CARDINAL IN [0..Event.Sync[score[i]].length) DO
IF (n ← Event.Sync[score[i]].note[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[score, n]; found ← TRUE;
ENDLOOP;
IF found THEN count ← count+1;
ENDLOOP;
ENDLOOP;
END;

MakeNTuplets: PUBLIC PROCEDURE[score: ScorePTR, nt, a: INTEGER, begin, end: Time] =
BEGIN
n: NotePTR;
b: Be
amPTR;
count: INTEGER ← 0;
found, beamed: BOOLE
AN;
value: NoteValue ← unknown;
Beam: PROCEDURE = {
IF Selection.selection[1] # NIL AND count = nt THEN Selection.MakeBeam[FALSE];
IF Selection.selection[0] # NIL THEN b ← Selection.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;
SetDirty[score, begin, end];
FOR v: CARDINAL IN [0..score.maxVoice] DO
IF ~Voice.Selected[score, v] THEN LOOP;
Selection.Clear[];
FOR i: CARDINAL IN [0..score.length) DO
IF score[i].time < begin THEN LOOP;
IF score[i].time > end THEN LOOP;
IF score[i].type = measure THEN BEGIN Beam[]; LOOP; END; -- IN [measure..repeat2]
IF score[i].type # sync THEN LOOP;
-- decide whether or not to break the current beam
FOR j: CARDINAL IN [0..Event.Sync[score[i]].length) DO
IF (n ← Event.Sync[score[i]].note[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: CARDINAL IN [0..Event.Sync[score[i]].length) DO
IF (n ← Event.Sync[score[i]].note[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[score, n]; found ← TRUE;
ENDLOOP;
IF found THEN count ← count+1;
ENDLOOP;
ENDLOOP;
Beam[];
END;

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

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


GuessMeasure: PROCEDU
RE[score: ScorePTR, begin, end: CARDINAL, time: INTEGER] =
BEGIN
n: NotePTR;
sync: SyncPTR;
firstTime, priorTime, totalTime: Time;
delta, b
estComplexity, current: INTEGER;
firstValue, nextValue, priorValue: INTEGER;
maxValue, bestValue, totalValue: INTEGER;
IF begin = end OR time = 0 T
HEN RETURN;
-- SUGGESTION: use a note in the prior measure as the beg
inning rather than the measure line
vs ← ALL[[0]];
firstValue ← Inline.LowHalf[time+256];
firstTime ← score[begin].time;

p
riorValue ← 0;
pr
iorTime ← score[end].time+3;
-- events are processed in DESCENDING order, and score[i].value > score[i+1].value
FOR i: CARDINAL DECREASING IN (begin..end) DO {
IF score[i].type # sync THEN LOOP;
sync ← Event.Sync[score[i]];
-- check for durations already given by the user
FOR j: CARDINAL IN [0..sync.length) DO
IF (n ← sync.note[j]) = NIL THEN EXIT;
IF n.value = unknown THEN LOOP;
delta ← Inline.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: CARDINAL DECREASING IN [begin..i) DO
IF score[j].type # sync THEN LOOP;
delta ← Inline.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 ← Inline.LowHalf[(totalValue*(priorTime-score[i].time))/totalTime];
bestValue ← priorValue+Quantize[delta, 64];
maxValue ← 32000;
FOR j: CARDINAL IN [0..score.maxVoice] DO
maxValue ← MIN[maxValue, (bestValue-vs[j].value)/4];
ENDLOOP;
bestComplexity ← Complexity[score, sync, 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, sync, current, @vs] > bestComplexity THEN LOOP;
IF Complexity[score, sync, current, @vs] = bestComplexity AND
ABS[bestValue-delta] <= ABS[current-delta] THEN LOOP;
bestComplexity ← Complexity[score, sync, current, @vs];
bestValue ← current;
ENDLOOP;
GOTO done;
EXITS done => {
-- event done, assign durations
FOR j: CARDINAL IN [0..sync.length) DO
IF (n ← sync.note[j]) = NIL THEN EXIT;
IF n.value # unknown THEN LOOP;
IF ~Note.InVoice[n, score.sheet.voice] 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[sync, 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
FOR i: CARDINAL IN [0..s.length) DO
IF s.note[i] = NIL THEN EXIT;
vs[s.note[i].voice].value ← value;
ENDLOOP;
END;

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

Complexity: PROCEDURE[score: ScorePTR, s: SyncPTR, value: INTEGER, vs: POINTER TO VoiceSyncs] RETURNS[INTEGER] =
BEGIN
x, d: Time ← 128;
c, y: INTEGER ← 0;
start: CARDINAL;
v: ARRAY [0..10) OF BOOLEAN ← ALL[FALSE];
FOR j: CARDINAL IN [0..s.length) DO
IF s.note[j] = NIL THEN EXIT;
IF s.note[j].value # unknown THEN LOOP;
IF ~Note.InVoice[s.note[j], score.sheet.voice] THEN LOOP;
IF v[s.note[j].voice] THEN LOOP;
d ← value-vs[s.note[j].voice].value;
v[s.note[j].voice] ← TRUE;
x ← x*256; start ← 10;
FOR k: CARDINAL 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: CARDINAL 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: CARDINAL IN [begin..end] DO
max ← Voice.SetState[@vs, score[i]];
FOR j: CARDINAL IN [0..score.maxVoice] DO
IF NOT vs[j].found THEN LOOP;
voice[j] ← i;
ENDLOOP;
ENDLOOP;
END;

FOR v: CARDINAL IN [0..score.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: CARDINAL IN [0..sync.length) DO
IF (n ← sync.note[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: CARDINAL 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: CARDINAL IN [0..index) DO list[i].ts.top ← 0; ENDLOOP;
FOR i: CARDINAL 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: CARDINAL IN (r1..r2] DO list[j].ts.top ← 1;
d ← list[i].value-list[i-1].value; r1 ← i-1;
ENDLOOP;
FOR i: CARDINAL 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: CARDINAL 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: CARDINAL 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: CARDINAL 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[Inline.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: CARDINAL 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 ← Inline.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.