HeuristicImpl.mesa
Copyright (C) 1981, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
last modified: December 8, 1981 8: 40 AM
Edited by Doug Wyatt, June 14, 1984 1:07:30 pm PDT
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: CEDAR PROGRAM
IMPORTS InlineDefs, Heuristic, MusicDefs, Note, Score, Selection, Voice
EXPORTS Heuristic
= BEGIN OPEN MusicDefs;
***************************************************************************
guessing structures
***************************************************************************
MakeSyncs: PUBLIC PROC[begin, end: Time] = {
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
{
IF selectionLength>1 THEN Selection.MakeSync[];
Selection.Clear[];
current ← score[i].time;
};
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];
};
MakeChords: PUBLIC PROC[begin, end: Time] = {
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];
};
MakeBeams: PUBLIC PROC[begin, end: Time] = {
n: NotePTR;
vs: Voice.State;
ts: TimeSignature;
i, j, v: CARDINAL;
change, found: BOOL;
sum, sumTS, start, duration: Time ← 0;
count: INTEGER ← 0;
Beam: PROC= { IF selection[1]#NIL THEN Selection.MakeBeam[];
Selection.Clear[]; count ← 0; duration ← 0; start ← sum; change ← FALSE; };
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
{ Voice.ClearState[@vs]; sum ← 0; Beam[]; 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 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;
};
MakeNTuplets: PUBLIC PROC[nt, a: INTEGER, begin, end: Time] = {
n: NotePTR;
b: BeamPTR;
i, j, v: CARDINAL;
count: INTEGER ← 0;
found, beamed: BOOL;
value: NoteValue ← unknown;
Beam: PROC= {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 { b.against ← a; b.ntuple ← nt; };
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 { Beam[]; LOOP; };
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[];
};
****************************************************************************
guessing note values
****************************************************************************
SetNoteValues: PUBLIC PROC[begin, end: Time] = {
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];
};
GuessMeasure: PROC[begin, end: CARDINAL, time: INTEGER] = {
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;
};
VoiceSyncs: TYPE = ARRAY [0..10) OF RECORD[value: INTEGER];
vs: VoiceSyncs;
SetVoiceSyncs: PROC[s: SyncPTR, value: INTEGER, vs: POINTER TO VoiceSyncs]= INLINE {
i: CARDINAL;
FOR i IN [0..syncLength) DO
IF s.event[i]=NIL THEN EXIT;
vs[s.event[i].voice].value ← value;
ENDLOOP;
};
Quantize: PROC[d: Time, q: INTEGER] RETURNS[INTEGER] = {
delta: Time ← (256*(64/q));
RETURN[InlineDefs.LowHalf[delta*((d+delta/2)/delta)]];
};
Complexity: PROC[s: SyncPTR, value: INTEGER, vs: POINTER TO VoiceSyncs] RETURNS[INTEGER]= {
x, d: Time ← 128;
c, y: INTEGER ← 0;
j, k, start: CARDINAL;
v: ARRAY [0..10) OF BOOLALL[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.
GuessLogical: PROC[begin, end: CARDINAL, time: Time] = {
i, j: CARDINAL;
voice: ARRAY [0..10) OF CARDINALALL[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;
};
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;
};
PortionOutTime: PROC[voice, s1, s2: CARDINAL, duration, ratio: INTEGER] = {
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 { r2 ← i-1; LOOP; };
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 { r1 ← i; LOOP; };
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;
};
CheckGrid: PROC[s1, s2: CARDINAL, unit, voice: INTEGER] RETURNS[INTEGER]= {
i: CARDINAL;
d: Time ← 256*(64/unit);
sum: Time ← score[s1].value;
delta: Time 𡤀
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]];
};
AssignGrid: PROC[s1, s2: CARDINAL, unit, voice: INTEGER]= {
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;
};
Translate: PROC[duration: INTEGER] RETURNS[INTEGER]= {
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];
};
index: CARDINAL;
list: ARRAY [0..30) OF RECORD[sync: SyncPTR, tuple: BOOL];
ss: ARRAY [0..10) OF Time;