--Author: John Maxwell
--last modified: May 20, 1982 8:58 am

DIRECTORY
Beam USING [Draw,Drawn,SetSyncs],
Chord USING [Adjust,Draw],
Graphics USING [DrawBox,SetCP,SetStipple],
MusicDefs,
Note USING [FindChord,Delta,Draw],
Score USING [DrawKey,DrawTimeSignature,DrawMetrenome,GetAccidental],
Sheet USING [DrawClef,DrawOctava,FindSection,Map,MapNote],
Sync USING [GetScoreIndex,GetStaff,RemoveNote],
Utility USING [DrawChar,DrawLine];

SyncImpl: PROGRAM
IMPORTS Beam, Chord, Graphics, MusicDefs, Note, Score, Sheet, Sync, Utility
EXPORTS MusicDefs, Sync =

BEGIN
OPEN Graphics,MusicDefs,Utility;
Error:SIGNAL;
Overflow:PUBLI
C SIGNAL[type:Sequence] = CODE;

--****************************************************************************
--data abstractions for a sync
--CONSTRA
INT: if s.event[i]=NIL then s.event[j]=NIL for all j>i
--****************************************************************************

AddNote:PUBLIC
PROCEDURE[s:SyncPTR,n:NotePTR] =
BEGIN
i,j:CARDINAL;
c:ChordPTR = Note.FindChord[n];
IF n.sync=s THEN RETURN;
IF s.type#notes THEN ERROR;
FOR j IN [0..chordLength) DO
IF c=NIL AND j#0 THEN EXIT;
IF c#NIL THEN n← c.note[j];
IF n=NIL THEN EXIT;
IF s.event[syncLength-1]#NIL THEN ERROR; --sync full!
FOR i IN [0..syncLength) DO
IF s.event[i]=n THEN EXIT;
IF s.event[i]#NIL THEN LOOP;
Sync.RemoveNote[n.sync,n];
n.sync ← s;
s.event[i] ← n;
IF n.beam#NIL THEN Beam.SetSyncs[n.beam];
EXIT;
ENDLOOP;
ENDLOOP;
END;

RemoveNote:PUB
LIC PROCEDURE[s:SyncPTR,n:NotePTR] =
BEGIN
--does not autom
atically remove empty syncs! (see Voice.Correct)
i,last:CARDIN
AL;
first:BOOLEAN←TRUE;
IF n=NIL OR s=NIL THEN RETURN;
FOR i DECREASING IN [0..syncLength) DO
IF first AND s.event[i]#NIL THEN BEGIN first←FALSE; last ← i; END;
IF s.event[i]#n THEN LOOP;
s.event[i] ← s.event[last];
s.event[last] ← NIL;
ENDLOOP;
END;

AddTimes:PUBLI
C PROCEDURE[s:SyncPTR,time:Time,toc:Time] =
BEGIN
i:CARDINAL;
IF s=NIL THEN RETURN;
s.time ← s.time + time;
FOR i IN [0..syncLength) DO
IF s.event[i]=NIL THEN EXIT;
s.event[i].toc ← s.event[i].toc + toc;
ENDLOOP;
END;

--******************************************************************
--procedures for drawing syncs
--******************************************************************

Draw:PUBLIC PR
OCEDURE[s:SyncPTR] =
BEGIN
staves:StavesPTR;
IF s=NIL THEN RETURN;
Graphics.SetStipple[context,black];
SELECT s.type FROM
notes => DrawNotes[s];
clef => Sheet.DrawClef[Sync.GetStaff[s,s.value].pitch,s.value,s.time];
octava1 => {
pitch,height:INTEGER;
octava2:SyncPTR = Octava[s];
IF octava2=NIL THEN {Error; RETURN};
staves ← LOOPHOLE[@s.event];
pitch ← Sync.GetStaff[s,s.value].pitch;
height ← staves.height;
Sheet.DrawOctava[pitch,s.value,height,s.time,octava2.time]};
octava2 => IF Octava[Octava[s]]#s THEN DrawMeasure[doubleMeasure,s.time,20];
staves => DrawMeasure[measure,s.time,10];
keySignature => Score.DrawKey[s.value,s.time];
timeSignature => Score.DrawTimeSignature[s.ts,s.time];
metrenome => Score.DrawMetrenome[s.value,s.time];
IN [measure..m5] => DrawMeasure[s.type,s.time,0];
ENDCASE;
END;

SetStave:PUBLI
C PROCEDURE[oldS:StavesPTR,new:SyncPTR] =
BEGIN
n,o:BOOLEAN;
newS:StavesPTR;
pitch,height:INTEGER;
IF new#NIL THEN newS←LOOPHOLE[@new.event] ELSE RETURN;
height ← newS.height;
IF new.type#staves THEN {
pitch ← newS.staff[new.value].pitch;
newS↑←oldS↑;
newS.height ← height;
FOR j:CARDINAL IN [0..newS.sl] DO
IF newS.staff[j].y#newS.staff[new.value].y THEN LOOP;
newS.staff[j].pitch ← pitch;
ENDLOOP;
RETURN};
-- new.type=staves
newS↑ ← style[new.value];
newS.height ← height;
-- clef switches carry over if the staff form is the same
IF oldS=NIL THEN RETURN;
IF oldS.sl#newS.sl THEN RETURN;
FOR i:CARDINAL IN [1..newS.sl] DO
n ← newS.staff[i].y=newS.staff[i-1].y;
o ← oldS.staff[i].y=oldS.staff[i-1].y;
IF n#o THEN RETURN;
ENDLOOP;
FOR i:CARDINAL IN [0..newS.sl] DO
newS.staff[i].pitch ← oldS.staff[i].pitch;
ENDLOOP;
END;


Octava:PUBLIC
PROCEDURE[s:SyncPTR] RETURNS[SyncPTR] =
BEGIN
OPEN Sync; -- USING [GetScoreIndex,GetStaff];
IF s=NIL THEN RETURN[NIL];
IF s.type=octava1 THEN {
FOR i:CARDINAL IN (GetScoreIndex[s]..scoreLength) DO
IF score[i].type#octava2 THEN LOOP;
IF s.value=score[i].value THEN RETURN[score[i]];
IF GetStaff[s,s.value].y=GetStaff[score[i],score[i].value].y THEN RETURN[score[i]];
ENDLOOP};
IF s.type=octava2 THEN {
FOR i:CARDINAL DECREASING IN [0..GetScoreIndex[s]) DO
IF score[i].type#octava1 THEN LOOP;
IF s.value=score[i].value THEN RETURN[score[i]];
IF GetStaff[s,s.value].y=GetStaff[score[i],score[i].value].y THEN RETURN[score[i]];
ENDLOOP};
RETURN[NIL];
END;

DrawNotes:PRO
CEDURE[s:SyncPTR] =
BEGIN
b:BeamPTR;
c:ChordPTR;
x,y:INTEGER;
sync:SyncRec;
i,j,k:CARDINAL;
min,vMin:INTEGER ← 1000;
max,vMax:INTEGER ←-1000;
sync ← s↑;
FOR i IN [0..syncLength) DO
IF s.event[i]=NIL THEN EXIT;
IF sync.event[i]=NIL THEN LOOP;
c ← Note.FindChord[s.event[i]];
b ← s.event[i].beam;
IF b#NIL AND b.beam#NIL THEN b←b.beam;
IF c#NIL THEN FOR j IN [0..chordLength) DO
IF c.note[j]=NIL THEN EXIT;
FOR k IN (i..syncLength) DO
IF sync.event[k]=c.note[j] THEN sync.event[k]←NIL;
ENDLOOP;
ENDLOOP;
IF b#NIL AND NOT Beam.Drawn[b] THEN BEGIN [] ← Beam.Draw[b]; LOOP; END;
IF c#NIL AND b=NIL THEN BEGIN Chord.Draw[c]; LOOP; END;
IF b=NIL THEN Note.Draw[s.event[i]];
ENDLOOP;
IF NOT show.sync THEN RETURN;
--draw the sync line
FOR i IN [0..syncLength) DO
IF s.event[i]=NIL THEN EXIT;
[x,y] ← Sheet.MapNote[s.event[i]];
min ← MIN[min,y];
max ← MAX[max,y];
IF voice AND s.event[i].voice#selectedVoice THEN LOOP;
vMin ← MIN[vMin,y];
vMax ← MAX[vMax,y];
ENDLOOP;
Graphics.SetStipple[context,light];
IF min#vMin OR max#vMax THEN DrawLine[x+4,min-6,x+4,max+6];
Graphics.SetStipple[context,black];
IF vMin#vMax AND vMin#1000 THEN DrawLine[x+4,vMin-6,x+4,vMax+6];
END;

DrawMeasure:PROCEDURE[type:EventType[measure..m5],time:Time,dy:INTEGER] =
BEGIN
i:CARDINAL;
staves:StavesPTR;
x1,y1,top,delta:INTEGER;
IF time=0 THEN RETURN;
IF print THEN dy←0;
Graphics.SetStipple[context,black];
staves ← sheet[Sheet.FindSection[time]].staves;
[x1,y1] ← Sheet.Map[time,,staves.sl];
y1 ← y1 - dy;
top ← -staves.staff[staves.sl].y+2*dy;
SELECT type FROM
measure => DrawLine[x1,y1,x1,y1+top];
doubleMeasure => {DrawLine[x1,y1,x1,y1+top];
IF print OR scale=1 THEN delta←2 ELSE delta←3;
DrawLine[x1-delta,y1,x1-delta,y1+top]};
repeat1 => BEGIN DrawBox[context,[x1,y1,x1+3,y1+top]];
DrawLine[x1+5,y1,x1+5,y1+top]; END;
repeat2,endMeasure => BEGIN DrawBox[context,[x1+1,y1,x1-2,y1+top]];
DrawLine[x1-5,y1,x1-5,y1+top]; END;
ENDCASE;
IF type#repeat1 AND type#repeat2 THEN RETURN;
IF type=repeat1 THEN delta←6 ELSE delta ← -13;
FOR i IN [0..staves.sl] DO
[x1,y1] ← Sheet.Map[time,,i];
SetCP[context,x1+delta,y1+16+4]; DrawChar[context,’.];
SetCP[context,x1+delta,y1+16-4]; DrawChar[context,’.];
ENDLOOP;
END;

--****************************************************************************
--look graphical (making sure that not
eheads and accidentals don’t interfere)
--*********************************
*******************************************

Adjust:PUBLIC PROCEDURE[s:SyncPTR] =

BEGIN

n:NotePTR;
c:ChordPTR;
sync:SyncRec;
delta:INTEGER;
pad:ScratchPad;
top:CARDINAL ← syncLength-1;
i,j,k,bottom:CARDINAL←0;
length:CARDINAL←0;
IF s=NIL THEN RETURN ELSE sync←s↑;
IF s.type#notes THEN RETURN;
--build the pad
FOR i IN [0..syncLengt
h) DO
IF (n←s.event[i])=NIL THEN EXIT;
n.delta ← n.accDelta ← 0;
IF n.rest THEN LOOP;
c ← Note.FindChord[n];
pad[length].n ← n;
pad[length].c ← c;
pad[length].y ← Sheet.MapNote[n].y;
pad[length].acc ← Score.GetAccidental[n];
pad[length].stemUp ← IF c=NIL THEN n.stemUp ELSE c.stemUp;
pad[length].stem ← IF pad[length].stemUp THEN pad[length].y+32 ELSE pad[length].y-32;
length ← length+1;
ENDLOOP;
SortPad[FALSE,@pad];
FOR i IN [0..length) DO
IF i#0 AND pad[i].y=pad[i-1].y AND pad[i].acc=pad[i-1].acc THEN pad[i].acc←inKey;
IF pad[i].acc#inKey THEN top ← MIN[top,i];
IF pad[i].acc#inKey THEN bottom ← MAX[bottom,i];
ENDLOOP;
--handle note adjacencies within a cho
rd
FOR i IN [0..length) DO
IF sync.event[i]=NIL THEN LOOP;
c ← Note.FindChord[sync.event[i]];
IF c#NIL THEN Chord.Adjust[c] ELSE LOOP;
-- no need to process a chord more than once
FOR j IN [0..chordLength) DO
IF c.note[j]=NIL THEN EXIT;
FOR k IN [0..syncLength) DO
IF sync.event[k]=c.note[j] THEN {sync.event[k]←NIL; EXIT};
ENDLOOP;
ENDLOOP;
ENDLOOP;
--determine the deltas
FOR i IN [1..le
ngth) DO
FOR j
DECREASING IN [0..i) DO
IF pad[j].c=pad[i].c AND pad[i].c#NIL THEN LOOP;
--special case: allow some notes to overlap
IF Overlap[i,j,@pad,length] THEN LOOP;
delta ← Note.Delta[pad[j].n];
--special case: higher note with stem down and lower with stem up
IF pad[j].stemUp AND NOT pad[i].stemUp THEN
BEGIN IF pad[j].y-pad[i].y>= 8 THEN LOOP;
MoveRightTo[pad[i],delta];
MoveLeft[pad[i],8];
--FOR k IN [0..i] DO IF pad[j].c=pad[k].c AND pad[k].c#NIL THEN LOOP;
--MoveLeft[pad[k],IF pad[k].n.value=whole THEN 10 ELSE 8];
--ENDLOOP;
EXIT; END;
--general case: shift lower note to the right
IF pad[j].y-pad[i].y< 8 THEN
MoveRightTo[pad[i],delta+(IF pad[j].n.value=whole THEN 10 ELSE 8)];
IF pad[i].n.value=whole OR pad[i].n.value=unknown THEN LOOP;
IF pad[j].stem< pad[i].y THEN MoveRightTo[pad[i],delta+2];
IF pad[i].stem> pad[j].y THEN MoveRightTo[pad[i],delta+2];
ENDLOOP;
ENDLOOP;
IF top>bottom THEN RETURN;
--determine the deltas for the acciden
tals
--RULES: (in order of precedence)
--1) all of the accidentals appear to the left of all of the notes
--2) accidentals for notes offset in a chord have the same offset
--3) there must be at least three lines vertically between accidentals
--4) the highest and lowest accidentals should have the same horizontal position
--5) lower accidentals should go to the left of higher accidentals
FOR i IN [0..length) DO pad[i].x ← -
10; ENDLOOP;
FOR i IN [1..length) DO
IF p
ad[i-1].y-pad[i].y>=8 THEN LOOP;
IF pad[i-1].acc=inKey OR pad[i].acc=inKey THEN LOOP;
IF Note.Delta[pad[i-1].n]>Note.Delta[pad[i].n]
THEN pad[i-1].push ← i
ELSE pad[i].push ← i-1;
ENDLOOP;
--do top, then whatever pushes bottom,
then bottom,
--then whatever pushes, then whatever.
PlaceAccidental[@pad,top,length];
F
OR i IN [0..length) DO
IF i=top THEN LOOP;
IF pad[i].push#bottom THEN LOOP;
PlaceAccidental[@pad,i,length];
ENDLOOP;
PlaceAccidental[@pad,bottom,length];
FOR i IN [0..length) DO
IF i=top OR i=bottom THEN LOOP;
IF pad[i].push=bottom THEN LOOP;
IF pad[i].push=syncLength THEN LOOP;
PlaceAccidental[@pad,i,length];
ENDLOOP;
FOR i IN [0..length) DO
IF i=top OR i=bottom THEN LOOP;
IF pad[i].push#syncLength THEN LOOP;
PlaceAccidental[@pad,i,length];
ENDLOOP;
--put the results in accDelta
FOR i IN
[0..length) DO
IF pa
d[i].acc=inKey THEN LOOP;
pad[i].n.accDelta ← -pad[i].n.delta - 8*pad[i].x;
ENDLOOP;
END;

Overlap:PROCEDURE[a,b:CARDINAL,pad:POINTER TO ScratchPad,length:CARDINAL] RETURNS[BOOLEAN]=
BEGIN
found:BOOLEAN;
top,bottom:CARDINAL←length;
PartOf:PROCEDURE[i,a:CARDINAL] RETURNS[BOOLEAN]= INLINE {
RETURN[a=i OR (pad[i].c=pad[a].c AND pad[a].c#NIL)]};
IF pad[a].c=NIL AND pad[b].c=NIL THEN { -- just single notes
IF pad[a].y#pad[b].y THEN RETURN[FALSE];
IF pad[a].n.pitch#pad[b].n.pitch THEN RETURN[FALSE];
IF pad[a].stemUp=pad[b].stemUp THEN RETURN[FALSE];
IF pad[a].n.value=whole OR pad[b].n.value=whole THEN RETURN[FALSE];
RETURN[TRUE]};
-- we have at least one chord; can a and b overlap?
-- find the tops and bottoms for a
FOR i:CARDINAL IN [0..length) DO
IF ~PartOf[i,a] THEN LOOP;
IF top=length THEN top ← i;
bottom ← i;
ENDLOOP;
IF pad[a].stemUp THEN top←0 ELSE bottom←length-1;
-- do all of the notes in b which are within top and bottom have a corresponding note in a?
FOR i:CARDINAL IN [top..bottom] DO
IF ~PartOf[i,b] THEN LOOP;
found ← FALSE;
FOR j:CARDINAL IN [top..bottom] DO
IF ~PartOf[j,a] THEN LOOP;
IF pad[i].n.pitch#pad[j].n.pitch OR pad[i].y#pad[j].y THEN LOOP;
IF Note.Delta[pad[i].n]#Note.Delta[pad[j].n] THEN LOOP;
found ← TRUE;
EXIT; ENDLOOP;
IF ~found THEN RETURN[FALSE];
ENDLOOP;
top ← length;
-- find the tops and bottoms for b
FOR i:CARDINAL IN [0..length) DO
IF ~PartOf[i,b] THEN LOOP;
IF top=length THEN top ← i;
bottom ← i;
ENDLOOP;
IF pad[b].stemUp THEN top←0 ELSE bottom←length-1;
-- do all of the notes in a which are within top and bottom have a corresponding note in b?
FOR i:CARDINAL IN [top..bottom] DO
IF ~PartOf[i,a] THEN LOOP;
found ← FALSE;
FOR j:CARDINAL IN [top..bottom] DO
IF ~PartOf[j,b] THEN LOOP;
IF pad[i].n.pitch#pad[j].n.pitch OR pad[i].y#pad[j].y THEN LOOP;
IF Note.Delta[pad[i].n]#Note.Delta[pad[j].n] THEN LOOP;
found ← TRUE;
EXIT; ENDLOOP;
IF ~found THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
END;

PlaceAccidental:PROCEDURE[pad:POINTER TO ScratchPad,i,length:CARDINAL] =
BEGIN
j:CARDINAL;
blocked:BOOLEAN;
d,h:INTEGER;
IF i=syncLength THEN RETURN;
IF pad[i].acc = inKey THEN RETURN;
FOR j IN [0..length) DO IF pad[j].push=i THEN pad[i].x←MAX[pad[j].x,pad[i].x]; ENDLOOP;
FOR d IN [MAX[pad[i].x,0]..6) DO blocked ← FALSE;
FOR j IN [0..length) DO
SELECT (IF pad[j].y>pad[i].y THEN pad[j].acc ELSE pad[i].acc) FROM
flat, doubleFlat, doubleSharp => h←8;
ENDCASE => h←16;
SELECT (IF pad[j].y>pad[i].y THEN pad[i].acc ELSE pad[j].acc) FROM
doubleSharp => NULL;
ENDCASE => h←h+8;
IF pad[j].y-pad[i].y>=h THEN LOOP;
IF pad[i].y-pad[j].y>=24 THEN EXIT;
IF pad[i].y-pad[j].y>=h THEN LOOP;
IF d=0 AND Note.Delta[pad[j].n]<0 THEN {blocked←TRUE; EXIT};
IF j=i THEN LOOP;
IF j=pad[i].push THEN LOOP;
IF pad[j].acc#inKey AND pad[j].x=d THEN {blocked←TRUE; EXIT};
ENDLOOP;
IF blocked THEN LOOP;
MoveAccTo[pad,i,d];
RETURN;
ENDLOOP;
ERROR;
END;

MoveRightTo:PROCEDURE[p:Scratch,x:INTEGER] = INLINE
BEGIN
IF p.c=NIL THEN p.n.delta ← MAX[p.n.delta,x]
ELSE p.c.delta ← MAX[p.c.delta,x - p.n.delta];
END;

MoveLeft:PROCEDURE[p:Scratch,delta:INTEGER] = INLINE
BEGIN
IF p.c=NIL THEN p.n.delta ← MIN[p.n.delta,-delta]
ELSE p.c.delta ← MIN[p.c.delta,-delta - p.n.delta];
END;

MoveAccTo:PROCEDURE[p:POINTER TO ScratchPad,i:CARDINAL,delta:INTEGER] =
BEGIN
p[i].x ← MAX[p[i].x,delta];
IF p[i].push#syncLength AND p[p[i].push].x>-1 THEN MoveAccTo[p,p[i].push,p[i].x+1];
END;

SortPad:PROCEDURE[ascending:BOOLEAN,pad:POINTER TO ScratchPad] = INLINE
BEGIN
i,j:CARDINAL;
temp:Scratch;
FOR i IN [0..syncLength) DO
IF pad[i].n=NIL THEN EXIT;
FOR j IN (i..syncLength) DO
IF pad[j].n=NIL THEN EXIT;
IF NOT ascending AND pad[i].y<pad[j].y
OR ascending AND pad[i].y>pad[j].y
THEN BEGIN temp← pad[i]; pad[i]← pad[j]; pad[j]← temp; END;
ENDLOOP;
ENDLOOP;
END;

ScratchPad:TYPE= ARRAY [0..syncLength) OF Scratch;
Scratch:TYPE = RECORD[x,y,stem:INTEGER←0,push:CARDINAL←syncLength,
acc:Accidental,stemUp:BOOLEAN,
c:ChordPTR,n:NotePTR←NIL];

Hidden:PUBLIC PROCEDURE[f,s:CARDINAL,leftEdge:Time] RETURNS[BOOLEAN] =
BEGIN
IF score[s].type#clef AND score[s].type#keySignature THEN RETURN[FALSE];
IF score[s].time-leftEdge>15 THEN RETURN[FALSE];
RETURN[TRUE];
END;

END..

Hidden:PUBLIC PROCEDURE[f,s:CARDINAL,leftEdge:Time] RETURNS[BOOLEAN] =
BEGIN
IF score[s].type#clef AND score[s].type#keySignature THEN RETURN[FALSE];
IF score[s].time-leftEdge>100 THEN RETURN[FALSE];
FOR i:CARDINAL DECREASING IN (f..s) DO
IF score[i].time<leftEdge THEN EXIT;
IF score[i].type=keySignature AND score[s].type=keySignature THEN RETURN[FALSE];
IF score[i].type=notes THEN RETURN[FALSE];
IF score[i].type IN Measure THEN RETURN[FALSE];
IF score[i].type=staves THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
END;