--Author: John Maxwell
--last modified: December 11, 1981 9:03 AM

DIRECTORY
Beam USING [Draw,Grace,InVoice],
Chord USING [Draw,Width],
Graphics USING [DrawArea,EnterPoint,MoveTo,SetTexture,StartAreaPath],
MusicDefs,
Note USING [Draw,Width],
Real USING [FixI],
Sheet USING [FindLine,Height,MapHeight,NextLine],
StringDefs USING [AppendDecimal],
Utility USING [DrawLine,DrawString,SetFont];


BeamImplB: PROGRAM
IMPORTS Beam, Chord, Graphics, MusicDefs, Note, Real, Sheet, StringDefs, Utility
EXPORTS Beam =

BEGIN
OPEN Graphics,MusicDefs,Utility;

--****************************************************************************
--
displaying beams
--****************************************************************************

Draw:PUBLIC PROCEDURE[b:BeamPTR] RETURNS[INTEGER,INTEGER] =
BEGIN
start:Time;
a:BeamArray;
slope:REAL ← 5;
i,end:CARDINAL;
inVoice:BOOLEAN;
nonGrace:BOOLEAN←FALSE;
thickness,lineHeight:INTEGER;
level,space,s,k,up,d:CARDINAL←0;
x0,y0,xn,yn,xi,yi,overX:REAL ← 1;
j,value,beamValue:NoteValue ← unknown;
dotted,oldDotted,stemUp,none:BOOLEAN←FALSE;
IF NOT show.notehead THEN {DrawPhysicalBeam[b]; RETURN[0,0]};
IF NOT b.beamed THEN BEGIN DrawNTuple[b]; RETURN[0,0]; END;
start ← b.sync1.time;
[x0,y0] ← Sheet.MapHeight[b.sync1.time,b.height+Sheet.Height[b.sync1.time,,b.staff]];
overX ← sheet[Sheet.FindLine[b.sync2.time]].x;
inVoice ← ~(voice AND NOT Beam.InVoice[b,selectedVoice]);
IF b.tilt > -x0/slope AND b.tilt < x0/slope THEN space ← 5 ELSE space ← 7;
IF Beam.Grace[b] THEN {space ← space-2; thickness←1} ELSE thickness←2;
--
first we figure out where everything is going to be
FOR i IN [0..beamLength) DO
IF b.chord[i] = endOfBeam THEN
BEGIN a[i].dotted ← FALSE; end ← i; EXIT; END;
WITH n:b.chord[i] SELECT FROM
note => BEGIN
IF show.display#graphical THEN n.n.delta←0;
IF i=0 THEN start ← start + n.n.delta + (IF n.n.stemUp THEN Note.Width[n.n] ELSE 0);
IF i=0 THEN x0 ← x0 + n.n.delta + (IF n.n.stemUp THEN Note.Width[n.n] ELSE 0);
a[i].x ← n.n.sync.time - start + n.n.delta;
IF n.n.stemUp THEN a[i].x ← a[i].x + Note.Width[n.n];
a[i].y ← y0 + b.tilt*a[i].x;
a[i].value ← n.n.value;
a[i].stemUp ← n.n.stemUp;
a[i].dotted←n.n.dotted;
END;
chord=> BEGIN
IF show.display#graphical THEN n.c.delta←0;
IF i=0 THEN start ← start + n.c.delta + (IF n.c.stemUp THEN Chord.Width[n.c] ELSE 0);
IF i=0 THEN x0 ← x0 + n.c.delta + (IF n.c.stemUp THEN Chord.Width[n.c] ELSE 0);
a[i].x ← n.c.note[0].sync.time - start + n.c.delta;
IF n.c.stemUp THEN a[i].x ← a[i].x + Chord.Width[n.c];
a[i].y ← y0 + b.tilt*a[i].x;
a[i].value ← n.c.note[0].value;
a[i].stemUp ← n.c.stemUp;
a[i].dotted←n.c.note[0].dotted;
END;
beam => BEGIN x:Time←0;
WITH ev:n.b.chord[0] SELECT FROM
note => IF (a[i].stemUp←ev.n.stemUp) THEN x←Note.Width[ev.n];
chord => IF (a[i].stemUp←ev.c.stemUp) THEN x←Chord.Width[ev.c];
ENDCASE;
IF i=0 THEN start ← start +x;
IF i=0 THEN x0 ← x0 + x;
a[i].x ← n.b.sync1.time - start+x;
a[i].y ← y0 + b.tilt*a[i].x;
a[i].value ← eighth;
a[i].dotted ← FALSE;
n.b.tilt ← b.tilt;
n.b.staff ← b.staff;
n.b.height ← Real.FixI[b.height+a[i].y-y0];
IF i#beamLength-1 AND b.chord[i+1]#endOfBeam THEN LOOP;
FOR j:CARDINAL IN [0..beamLength) DO
IF n.b.chord[j]=endOfBeam THEN EXIT;
WITH ev:n.b.chord[j] SELECT FROM
note => IF ev.n.stemUp THEN x←8 ELSE x←0;
chord => IF ev.c.stemUp THEN x←8 ELSE x←0;
ENDCASE;
ENDLOOP;
a[i].stemUp ← (x#0);
a[i].x ← n.b.sync2.time- start+ x;
a[i].y ← y0 + b.tilt*a[i].x;
END;
ENDCASE;
ENDLOOP;
--
then we draw the beams and components
i ← Sheet.FindLine[b.sync1.time];
lineHeight ← sheet[i].y-sheet[Sheet.NextLine[i]].y;
FOR j IN [eighth..unknown) DO
none ← TRUE;
FOR i IN [0..end) DO
IF none THEN IF a[i].value>=j
THEN BEGIN none←FALSE; s←i; END
ELSE LOOP;
IF (i<end-1 AND j=eighth) OR (i<end-1 AND a[i+1].value>=j) THEN LOOP;
up ← d ← 0;
FOR k IN [s..i] DO IF a[k].stemUp THEN up←up+1 ELSE d←d+1; ENDLOOP;
stemUp ← (up>d);
xn ← a[i].x + x0; yn ← a[i].y; xi ← a[s].x + x0;
none ← TRUE;
IF xi=xn THEN SELECT TRUE FROM -- half beam
i=0 => {xn←xn+10; yn←yn+b.tilt*10};
i=end-1 => xi←xi-10;
a[i-1].value<a[i+1].value => {xn←xn+10; yn←yn+b.tilt*10};
a[i-1].value>a[i+1].value => xi←xi-10;
a[i+1].dotted => {xn←xn+10; yn←yn+b.tilt*10};
ENDCASE => xi←xi-10;
IF stemUp THEN yn ← yn - space*level ELSE yn ← yn + space*level;
yi ← yn + b.tilt*(xi-xn);
Graphics.SetTexture[context,IF inVoice THEN black ELSE light];
DrawClippedRect[xi,yi,xn-1,yn,overX,b.tilt,thickness,lineHeight];
FOR k IN [s..i] DO
IF a[k].value#j THEN LOOP;
IF a[k].stemUp=TRUE AND stemUp=FALSE THEN
a[k].y ← a[k].y + space*level;
IF a[k].stemUp=FALSE AND stemUp=TRUE THEN
a[k].y ← a[k].y - space*level;
yi ← a[k].y;
IF a[k].stemUp AND a[k].x+x0>=staffLength+8 OR
NOT a[k].stemUp AND a[k].x+x0>=staffLength
THEN yi ← a[k].y - lineHeight;
IF ~print AND AnyBug[] THEN RETURN[0,0]; -- impatient user
WITH ev:b.chord[k] SELECT FROM
note => Note.Draw[ev.n,Real.FixI[yi]];
chord=> Chord.Draw[ev.c,Real.FixI[yi]];
beam=> [] ← Beam.Draw[ev.b];
ENDCASE;
ENDLOOP;
ENDLOOP;
level ← level+1;
ENDLOOP;
IF b.ntuple#0 AND ~(print AND b.invisible) THEN
BEGIN
string:STRING ← [10];
IF xn<x0 THEN xn ← xn+staffLength;
xi ← (x0+xn-10)/2;
y0 ← Real.FixI[y0+b.tilt*(xi-x0)+(IF stemUp THEN 4 ELSE -12)];
IF xi>staffLength THEN BEGIN xi←xi-staffLength; y0←y0-lineHeight; END;
MoveTo[context,[xi,y0]];
StringDefs.AppendDecimal[string,b.ntuple];
SELECT TRUE FROM
b.invisible => Graphics.SetTexture[context,grey];
inVoice => Graphics.SetTexture[context,black];
ENDCASE => Graphics.SetTexture[context,light];
SetFont[context,text,12];
DrawString[context,string];
SetFont[context,music,8];
END;
RETURN[i,i];
END;

BeamArray:TYPE = ARRAY [0..beamLength) OF BeamRecord;
BeamRecord:TYPE = RECORD[value:NoteValue,stemUp,dotted:BOOLEAN,x,y:REAL];

DrawNTuple:PROCEDURE[b:BeamPTR] =
BEGIN
inVoice:BOOLEAN;
x,y,x1,y1:INTEGER;
height1,height2,i,j:INTEGER←beamLength;
up0,upN:BOOLEAN;
string:STRING ← [10];
IF print AND b.invisible THEN RETURN;
inVoice ← ~(voice AND NOT Beam.InVoice[b,selectedVoice]);
height1 ← b.height+Sheet.Height[0,,b.staff];
[x,y] ← Sheet.MapHeight[b.sync1.time,height1];
[x1,] ← Sheet.MapHeight[b.sync2.time,height1];
y1 ← Real.FixI[y+ b.tilt*(x1+12-x)];
height2 ← Real.FixI[height1+y1-y];
SELECT TRUE FROM
b.invisible => Graphics.SetTexture[context,grey];
inVoice => Graphics.SetTexture[context,black];
ENDCASE => Graphics.SetTexture[context,light];
DrawLine[x-2,y,x1+10,y1];
WITH n:b.chord[0] SELECT FROM
note => up0 ← (Sheet.Height[n.n.sync.time,n.n.pitch,n.n.staff]>height1);
chord=> up0 ← (Sheet.Height[n.c.note[0].sync.time,n.c.note[0].pitch,n.c.note[0].staff]>height1);
beam => up0 ← (n.b.height+Sheet.Height[n.b.sync1.time,,n.b.staff]>height1);
ENDCASE;
FOR i IN [0..beamLength)
DO IF b.chord[i]=endOfBeam THEN BEGIN j←i; EXIT; END;
ENDLOOP;
IF j#0 THEN j ← j-1;
WITH n:b.chord[j] SELECT FROM
note => upN ← (Sheet.Height[n.n.sync.time,n.n.pitch,n.n.staff]>height2);
chord=> upN ← (Sheet.Height[n.c.note[0].sync.time,n.c.note[0].pitch,n.c.note[0].staff]>height2);
beam => upN ← (n.b.height+Sheet.Height[n.b.sync1.time,,n.b.staff]>height2);
ENDCASE;
IF up0 THEN DrawLine[x-2,y,x-2,y+4]
ELSE DrawLine[x-2,y,x-2,y-4];
IF upN THEN DrawLine[x1+10,y1,x1+10,y1+4]
ELSE DrawLine[x1+10,y1,x1+10,y1-4];
FOR i IN [0..beamLength) DO
IF b.chord[i]=endOfBeam THEN EXIT;
WITH n:b.chord[i] SELECT FROM
note => Note.Draw[n.n];
chord=> Chord.Draw[n.c];
beam => []←Beam.Draw[n.b];
ENDCASE;
ENDLOOP;
MoveTo[context,[(x+x1)/2,y+b.tilt*(x1-x)/2+(IF up0 THEN -15 ELSE 5)]];
SELECT TRUE FROM
b.invisible => Graphics.SetTexture[context,grey];
inVoice => Graphics.SetTexture[context,black];
ENDCASE => Graphics.SetTexture[context,light];
StringDefs.AppendDecimal[string,b.ntuple];
SetFont[context,text,12];
DrawString[context,string];
SetFont[context,music,8];
END;

DrawPhysicalBeam:PROCEDURE[b:BeamPTR] =
BEGIN
i:CARDINAL;
FOR i IN [0..beamLength) DO
IF b.chord[i] = endOfBeam THEN EXIT;
WITH ev:b.chord[i] SELECT FROM
note => Note.Draw[ev.n];
chord=> Chord.Draw[ev.c];
beam => DrawPhysicalBeam[ev.b];
ENDCASE;
ENDLOOP;
END;

DrawClippedRect:PROCEDURE[x0,y0,xn,yn,delta:REAL,tilt:REAL,thickness,lineHeight:INTEGER] =
BEGIN
xi:REAL ← xn;
yi:REAL ← yn;
clip:BOOLEAN;
clip ← x0<staffLength AND xn>staffLength;
IF clip THEN BEGIN xi ← staffLength; yi ← y0 + tilt*(xi-x0); END;
IF x0> staffLength THEN
BEGIN y0 ← y0 - lineHeight; yi ← yi - lineHeight;
x0 ← x0 - staffLength+ delta;
xi ← xi - staffLength+ delta;
END;
StartAreaPath[context];
EnterPoint[context,[x0,y0-thickness]];
EnterPoint[context,[x0,y0+thickness]];
EnterPoint[context,[xi+1,yi+thickness]];
EnterPoint[context,[xi+1,yi-thickness]];
DrawArea[context];
IF NOT clip THEN RETURN;
xi ← delta;
xn ← xn - staffLength+ delta;
yn ← yn - lineHeight;
yi ← yn - tilt*(xn-xi);
StartAreaPath[context];
EnterPoint[context,[xi,yi+thickness]];
EnterPoint[context,[xi,yi-thickness]];
EnterPoint[context,[xn+1,yn-thickness]];
EnterPoint[context,[xn+1,yn+thickness]];
DrawArea[context];
END;

Drawn:PUBLIC PROCEDURE[b:BeamPTR] RETURNS[BOOLEAN] =
BEGIN
i:CARDINAL;
IF b=NIL THEN FOR i IN [0..3) DO beamQueue[i] ← NIL; ENDLOOP;
IF b=NIL THEN RETURN[FALSE];
FOR i IN [0..3) DO
IF beamQueue[i]=b THEN RETURN[TRUE]; ENDLOOP;
beamIndex ← beamIndex+1;
IF beamIndex = 3 THEN beamIndex ← 0;
beamQueue[beamIndex] ← b;
RETURN[FALSE];
END;

beamQueue:ARRAY [0..3) OF BeamPTR;
beamIndex:CARDINAL←0;

END.