--Author: John Maxwell
--last modified: December 15, 1981 4:15 PM

DIRECTORY
Beam USING [Height,InVoice],
MusicDefs,
Note USING [Delete,Delta,FindChord],
Piece USING [CleanUpSyncs, Length, RemoveSync],
Real USING [FixI],
Score USING [GetKey, ShowPitch],
Sheet USING [FindLine, FindSection, Height, NearestTime, NextLine],
Sync USING [GetScoreIndex, Octava],
Utility USING [FreeSync];

PieceImplA: PROGRAM
IMPORTS Beam, MusicDefs, Note, Piece, Real, Score, Sheet, Sync, Utility
EXPORTS MusicDefs, Piece =

BEGIN
OPEN MusicDefs;

dataStructureInFlux:PUBLIC BOOLEAN ← FALSE;

--****************************************************************************
--data abstractions for a
piece
--CONSTRAINT: piece[i-1].time <= piece[i].time <= piece[i+1].time
--CONSTRAINT: piece[i]#NIL for all i such that 0 <= i < pieceLength
--CONSTRAINT: piece[pieceLength]=NIL
--CONSTRAINT: a sync may be entered at most one time
--****************************************************************************

AddSync:PUBLIC PROCEDURE[p:PiecePTR,s:SyncPTR] =
BEGIN
--any procedure that inserts syncs should process syncs in decreasing order
length:CARDINAL=Piece.Length[p];
IF s=NIL THEN RETURN;
--check for overflow
IF p=score AND length+1=maxScoreLength THEN Overflow[score];
IF p#score AND length+1=maxPieceLength THEN Overflow[piece];
--insert sync
IF p=score THEN dataStructureInFlux←TRUE;
FOR i:CARDINAL DECREASING IN [0..length) DO
IF p[i]=NIL THEN LOOP;
IF p[i].time < s.time THEN {p[i+1]←s; s←NIL; EXIT};
IF p[i].time = s.time AND ~LessThan[s.type,p[i].type]
THEN {p[i+1]←s; s←NIL; EXIT};
p[i+1] ← p[i];
ENDLOOP;
IF s#NIL THEN p[0] ← s;
IF p=score THEN scoreLength ← scoreLength+1;
IF p=score THEN dataStructureInFlux←FALSE;
END;

LessThan:PROCEDURE[a,b:EventType] RETURNS[BOOLEAN] = INLINE
BEGIN
SELECT TRUE FROM
a=staves => RETURN[TRUE];
b=staves => RETURN[FALSE];
a=keySignature => RETURN[TRUE];
b=keySignature => RETURN[FALSE];
Measure[a] => RETURN[TRUE];
Measure[b] => RETURN[FALSE];
a=metrenome => RETURN[TRUE];
b=metrenome => RETURN[FALSE];
a=timeSignature => RETURN[TRUE];
b=timeSignature => RETURN[FALSE];
a IN SheetSwitch => RETURN[TRUE];
b IN SheetSwitch => RETURN[FALSE];
ENDCASE => RETURN[TRUE];
END;


RemoveSync:PUBLIC PROCEDURE[p:PiecePTR,s:SyncPTR] =
BEGIN
--
DOES NOT free notes or itself!
--any procedure that removes syncs should process syncs in decreasing order
i,j:CARDINAL←0;
length:CARDINAL←Piece.Length[p];
FOR i IN [0..length) DO
IF p[i]=s THEN LOOP;
IF i#j THEN p[j] ← p[i];
j ← j+1; ENDLOOP;
FOR i IN [0..syncLength) DO
IF s.event[i]=NIL THEN EXIT;
s.event[i].sync ← NIL;
ENDLOOP;
length ← j;
p[length] ← NIL;
IF p=score THEN scoreLength←length;
END;

DeleteSync:PUBLIC PROCEDURE[s:SyncPTR] =
BEGIN
octava:SyncPTR ← NIL;
index:CARDINAL←Sync.GetScoreIndex[s];
IF s.type IN [octava1..octava2] THEN octava ← Sync.Octava[s];
Piece.RemoveSync[score,s];
IF octava#NIL THEN {
SetDirty[octava.time,octava.time];
Piece.RemoveSync[score,octava];
Utility.FreeSync[@octava]};
Utility.FreeSync[@s];
END;

Sort:PUBLIC PROCEDURE[p:PiecePTR] =
--useful when going from physical to logical files
BEGIN
i,j:CARDINAL;
temp:SyncPTR;
length:CARDINAL = Piece.Length[p];
FOR i IN [0..length) DO
FOR j IN (i..length) DO
IF p[j] = NIL THEN LOOP;
IF p[i] = NIL THEN {p[i]←p[j]; p[j]←NIL; LOOP};
IF p[i].time > p[j].time THEN {temp←p[i]; p[i]←p[j]; p[j]←temp};
IF p[i].time= p[j].time THEN
{IF p[i].event[0]=NIL OR p[j].event[0]=NIL THEN LOOP;
IF p[i].event[0].toc<p[j].event[0].toc THEN LOOP;
temp←p[i]; p[i]←p[j]; p[j]←temp};
ENDLOOP;
ENDLOOP;
END;

CleanUpSyncs:PUBLIC PROCEDURE[p:PiecePTR] =
BEGIN
next:CARDINAL ← 0;
length:CARDINAL ← Piece.Length[p];
FOR i:CARDINAL IN [0..length) DO
IF p[i].type=notes AND p[i].event[0]=NIL
THEN Utility.FreeSync[@p[i]]
ELSE {p[next] ← p[i]; next ← next+1};
ENDLOOP;
FOR i:CARDINAL IN [next..length) DO p[i]←NIL; ENDLOOP;
IF p = score THEN scoreLength←next;
END;

CleanUpNotes:PUBLIC PROCEDURE[p:PiecePTR] =
BEGIN
i,j:CARDINAL;
n:NotePTR;
FOR i DECREASING IN [0..Piece.Length[p]) DO
IF p[i].type#notes THEN LOOP;
FOR j DECREASING IN [0..syncLength) DO
IF p[i]=NIL THEN LOOP;
IF (n←p[i].event[j])=NIL THEN LOOP;
IF n.value#unknown THEN LOOP;
IF n.duration#0 THEN LOOP;
Note.Delete[n,TRUE];
ENDLOOP;
ENDLOOP;
Piece.CleanUpSyncs[p];
END;

--****************************************************************************
--what is being pointed at?
--****************************************************************************

NearestSync:PUBLIC PROCEDURE[p:PiecePTR,t:Time,notesOnly:BOOLEAN←FALSE] RETURNS[index:CARDINAL]=
BEGIN
delta:Time ← 10000;
length:CARDINAL = Piece.Length[p];
index ← 0;
FOR binary:CARDINAL←8192, binary/2 UNTIL binary=0 DO
IF index+binary>=length THEN LOOP;
IF p[index+binary].time<=t THEN index←index+binary;
IF p[index].time=t THEN EXIT;
ENDLOOP;
--the above will fail when the p isn’t strictly ordered
FOR i:CARDINAL IN [(IF index<5 THEN 0 ELSE index-5)..MIN[index+5,length]) DO
IF notesOnly AND p[i].type#notes THEN LOOP;
IF ABS[p[i].time-t] > delta THEN LOOP;
index ← i; delta ← ABS[p[i].time-t];
ENDLOOP;
IF notesOnly AND index#length AND p[index].type#notes THEN index←length;
END;

NearestNote:PUBLIC PROCEDURE[x,y:INTEGER] RETURNS[p:NotePTR] =
BEGIN
n:NotePTR;
key:INTEGER;
time,dt,t:Time←11;
i,j,k,l,next:CARDINAL;
height,dy,show:INTEGER←8;
[time,height] ← Sheet.NearestTime[x,y];
next ← Sheet.FindLine[time];
time ← time - (staffLength - sheet[next].x);
l ← Sheet.FindLine[time];
height ← height + (sheet[next].y - sheet[l].y);
FOR k IN [0..2] DO
FOR i IN [0..scoreLength) DO
IF score[i].time< time - 40 THEN LOOP;
IF score[i].time> time + 40 THEN EXIT;
IF score[i].time<sheet[l].time THEN LOOP;
IF score[i].time>=sheet[next].time THEN EXIT;
FOR j IN [0..syncLength) DO
IF (n←score[i].event[j]) = NIL THEN EXIT;
IF voice AND n.voice#selectedVoice THEN LOOP;
t ← ABS[score[i].time+5+Note.Delta[n]-time];
key ← Score.GetKey[n.sync.time];
show ← Score.ShowPitch[n.pitch,n.spelled,key];
y ← ABS[height-Sheet.Height[n.sync.time,show,n.staff]];
IF y+t>dy+dt THEN LOOP;
IF y+t=dy+dt AND t>dt THEN LOOP;
dt ← t; dy ← y;
p ← n;
ENDLOOP;
ENDLOOP;
time ← time+ (staffLength - sheet[next].x);
height ← height- (sheet[next].y - sheet[l].y);
l ← next;
next ← Sheet.NextLine[l];
ENDLOOP;
IF dt>10 OR dy>7 THEN p←NIL;
END;

NearestObject:PUBLIC PROCEDURE[x,y:INTEGER] RETURNS[obj:ObjectType,p:UnspecifiedPTR←NIL] =
BEGIN
n:NotePTR;
l:CARDINAL;
key:INTEGER;
staves:StavesPTR;
i,j,k,s,next:CARDINAL←0;
time,dt,t:Time ← 11;
height,dy,show:INTEGER ← 8;
[time,height] ← Sheet.NearestTime[x,y];
next ← Sheet.FindLine[time];
time ← time - (staffLength - sheet[next].x);
l ← Sheet.FindLine[time];
height ← height + (sheet[next].y - sheet[l].y);
FOR k IN [0..2] DO
--
left and right corners of a beam
FOR i IN [0..beamHeapLength) DO
IF beamHeap[i].beam#NIL AND beamHeap[i].beam.beamed THEN LOOP;
IF beamHeap[i].sync1.time-5 > time THEN LOOP;
IF beamHeap[i].sync2.time+5 < time THEN LOOP;
IF voice AND ~Beam.InVoice[beamHeap[i],selectedVoice] THEN LOOP;
y ← Beam.Height[beamHeap[i],time];
IF ABS[y-height]+4 > dt+dy THEN LOOP;
IF ABS[y-height]+4 = dt+dy THEN IF y>height THEN LOOP;
dt ← 4; dy ← ABS[y-height];
IF time < (beamHeap[i].sync1.time+beamHeap[i].sync2.time+4)/2
THEN obj ←leftBeam
ELSE obj ←rightBeam;
p ← beamHeap[i];
ENDLOOP;
--
measure sections, signatures and other events
FOR i IN [s..scoreLength) DO
IF score[i].time< time - 300 THEN LOOP;
IF score[i].time> time + 300 THEN {s←i; EXIT};
IF score[i].time<sheet[l].time THEN LOOP;
FOR j IN [0..syncLength) DO --
ties
IF (n←score[i].event[j]) = NIL THEN EXIT;
IF voice AND n.voice#selectedVoice THEN LOOP;
IF n.tie=NIL THEN LOOP;
IF n.sync.time<time OR n.tie.sync.time>time THEN LOOP;
t ← ABS[(n.sync.time+n.tie.sync.time)/2-time];
key ← Score.GetKey[n.sync.time];
show ← Score.ShowPitch[n.pitch,n.spelled,key];
y ← ABS[height-(Sheet.Height[n.sync.time,show,n.staff]+n.tieHeight/2)];
IF y+t>dy+dt THEN LOOP;
IF y+t=dy+dt THEN IF t>dt THEN LOOP;
dt ← t; dy ← y;
obj ← tie; p ← n;
ENDLOOP;
IF score[i].time>=sheet[next].time THEN LOOP;
IF ABS[score[i].time-time]>40 THEN LOOP;
IF score[i].type IN SheetSwitch AND score[i].type#staves THEN {
octava:SyncPTR←NIL;
staff,top,bottom:INTEGER ← score[i].value;
t ← score[i].time;
IF score[i].type=clef THEN t←t+6;
IF ABS[t-time]+6> dt + dy THEN LOOP;
IF ABS[t-time]+6=dt+dy AND t<time THEN LOOP;
IF score[i].type=octava2 THEN octava ← Sync.Octava[score[i]];
IF octava#NIL THEN staves ← LOOPHOLE[@octava.event]
ELSE staves ← LOOPHOLE[@score[i].event];
SELECT staves.staff[staff].pitch FROM
60,15 => {top←staves.height+5; bottom←staves.height-5};
ENDCASE => {top←32; bottom←0};
IF height>Sheet.Height[score[i].time,,staff]+top THEN LOOP;
IF height<Sheet.Height[score[i].time,,staff]+bottom THEN LOOP;
dt ← ABS[t-time]; dy ← 6;
p ← score[i];
obj ← measure;
LOOP};
IF score[i].type#notes THEN {
IF ABS[score[i].time-time]+6> dt + dy THEN LOOP;
IF ABS[score[i].time-time]+6=dt+dy AND score[i].time<time THEN LOOP;
IF score[i].type=staves AND score[i].time<1 THEN LOOP; -- don’t touch the first one
staves ← sheet[Sheet.FindSection[score[i].time]].staves;
IF height>Sheet.Height[score[i].time,,0]+32 THEN LOOP;
IF height<Sheet.Height[score[i].time,,staves.sl] THEN LOOP;
dt ← ABS[score[i].time-time]; dy ← 6;
p ← score[i];
obj ← measure;
LOOP};
FOR j IN [0..syncLength) DO --notes
IF (n←score[i].event[j]) = NIL THEN EXIT;
IF voice AND n.voice#selectedVoice THEN LOOP;
t ← ABS[score[i].time+5+Note.Delta[n]-time];
y ← ABS[height-Sheet.Height[n.sync.time,n.pitch,n.staff]];
IF y+t>dy+dt THEN LOOP;
IF y+t=dy+dt AND t>dt THEN LOOP;
dt ← t; dy ← y;
p ← n; obj ← note;
ENDLOOP;
ENDLOOP; --
end of this line, go to next
time ← time+ (staffLength - sheet[next].x);
height ← height- (sheet[next].y - sheet[l].y);
l ← next;
next ← Sheet.NextLine[l];
ENDLOOP;
IF dt>10 OR dy>7 THEN p←NIL;
END;

END..