-- Author: John Maxwell
-- last modified: November 8, 1983 9:40 am
-- Last Edited by: Maxwell, November 18, 1983 1:47 pm

DIRECTORY
	Beam USING [Draw, Grace, InVoice], 
	Chord USING [Draw, Width], 
	Graphics USING [DrawArea, LineTo, MoveTo, NewPath, Path, SetCP, SetStipple], 
	MusicDefs, 
	Note USING [Draw, Width], 
	Real USING [FixI], 
	Sheet USING [FindLine, Height, MapHeight, NextLine], 
	String USING [AppendDecimal], 
	Utility USING [DrawLine, DrawString, SetFont];


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

BEGIN
OPEN Graphics, MusicDefs, Utility;

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

Draw: PUBLIC PROCEDURE[score: ScorePTR, b: BeamPTR] RETURNS[INTEGER, INTEGER] = 
BEGIN
start: Time;
a: BeamArray;
slope: REAL ← 5;
i, end: CARDINAL;
inVoice: BOOLEAN;
sheet: SheetPTR ← score.sheet;
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 sheet.notehead THEN {DrawPhysicalBeam[score, b]; RETURN[0, 0]};
IF NOT b.beamed THEN BEGIN DrawNTuple[score, b]; RETURN[0, 0]; END;
start ← b.sync1.time;
[x0, y0] ← Sheet.MapHeight[sheet, b.sync1.time, b.height+Sheet.Height[sheet, b.sync1.time, , b.staff]];
overX ← sheet.section[Sheet.FindLine[sheet, b.sync2.time]].x;
inVoice ← Beam.InVoice[b, score.sheet.voice]; 
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 ← 0} ELSE thickness ← 1; -- thickness 1, 2
-- first we figure out where everything is going to be
FOR i: CARDINAL IN [0..b.length) DO
	WITH n: b.chord[i] SELECT FROM
	 note => BEGIN
		IF sheet.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 sheet.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+1 # b.length THEN LOOP;
		FOR j: CARDINAL IN [0..b.length) DO
			WITH ev: n.b.chord[j] SELECT FROM -- find last note or chord in beam
				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;
a[b.length].dotted ← FALSE;
end ← b.length;
-- then we draw the beams and components
i ← Sheet.FindLine[sheet, b.sync1.time];
lineHeight ← sheet.section[i].y-sheet.section[Sheet.NextLine[sheet, 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);
	IF inVoice 
	   THEN Graphics.SetStipple[sheet.context, black]
	   ELSE Graphics.SetStipple[sheet.context, light];
	DrawClippedRect[sheet, 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 >= sheet.width+8 OR
			NOT a[k].stemUp AND a[k].x+x0 >= sheet.width
			THEN yi ← a[k].y - lineHeight;
		IF ~sheet.printing AND AnyBug[] THEN RETURN[0, 0]; -- impatient user 
		WITH ev: b.chord[k] SELECT FROM
			note => Note.Draw[score, ev.n, Real.FixI[yi]];
			chord => Chord.Draw[score, ev.c, Real.FixI[yi]];
			beam => [] ← Beam.Draw[score, ev.b];
			ENDCASE;
		ENDLOOP;
	ENDLOOP;
	level ← level+1;
ENDLOOP;
IF b.ntuple # 0 AND ~(sheet.printing AND b.invisible) THEN
	BEGIN
	string: STRING ← [10];
	IF xn < x0 THEN xn ← xn+sheet.width;
	xi ← (x0+xn-10)/2;
	y0 ← Real.FixI[y0+b.tilt*(xi-x0)+(IF stemUp THEN 4 ELSE -12)];
	IF xi > sheet.width THEN BEGIN xi ← xi-sheet.width; y0 ← y0-lineHeight; END;
	SetCP[sheet.context, xi, y0];
	String.AppendDecimal[string, b.ntuple];
	SELECT TRUE FROM
		b.invisible => Graphics.SetStipple[sheet.context, grey];
		inVoice => Graphics.SetStipple[sheet.context, black];
		ENDCASE => Graphics.SetStipple[sheet.context, light];
	SetFont[sheet.context, text, 12];
	DrawString[sheet.context, string];
	SetFont[sheet.context, music, 8];
	END;
RETURN[i, i];
END;

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

DrawNTuple: PROCEDURE[score: ScorePTR, b: BeamPTR] = 
BEGIN
inVoice: BOOLEAN;
x, y, x1, y1: INTEGER;
height1, height2: INTEGER;
up0, upN: BOOLEAN;
string: STRING ← [10];
sheet: SheetPTR ← score.sheet;
IF sheet.printing AND b.invisible THEN RETURN;
inVoice ← Beam.InVoice[b, score.sheet.voice];
height1 ← b.height+Sheet.Height[sheet, 0, , b.staff];
[x, y] ← Sheet.MapHeight[sheet, b.sync1.time, height1];
[x1, ] ← Sheet.MapHeight[sheet, 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.SetStipple[sheet.context, grey];
	inVoice => Graphics.SetStipple[sheet.context, black];
	ENDCASE => Graphics.SetStipple[sheet.context, light];
DrawLine[sheet.context, x-2, y, x1+10, y1];
WITH n: b.chord[0] SELECT FROM
	note => up0 ← (Sheet.Height[sheet, n.n.sync.time, n.n.pitch, n.n.staff] > height1);
	chord => up0 ← (Sheet.Height[sheet, n.c.note[0].sync.time, n.c.note[0].pitch, n.c.note[0].staff] > height1);
	beam => up0 ← (n.b.height+Sheet.Height[sheet, n.b.sync1.time, , n.b.staff] > height1);
	ENDCASE;
WITH n: b.chord[b.length-1] SELECT FROM
	note => upN ← (Sheet.Height[sheet, n.n.sync.time, n.n.pitch, n.n.staff] > height2);
	chord => upN ← (Sheet.Height[sheet, n.c.note[0].sync.time, n.c.note[0].pitch, n.c.note[0].staff] > height2);
	beam => upN ← (n.b.height+Sheet.Height[sheet, n.b.sync1.time, , n.b.staff] > height2);
	ENDCASE;
IF up0 THEN DrawLine[sheet.context, x-2, y, x-2, y+4]
       ELSE DrawLine[sheet.context, x-2, y, x-2, y-4];
IF upN THEN DrawLine[sheet.context, x1+10, y1, x1+10, y1+4]
       ELSE DrawLine[sheet.context, x1+10, y1, x1+10, y1-4];
FOR i: CARDINAL IN [0..b.length) DO
	WITH n: b.chord[i] SELECT FROM
		note => Note.Draw[score, n.n];
		chord => Chord.Draw[score, n.c];
		beam => [] ← Beam.Draw[score, n.b];
		ENDCASE;
	ENDLOOP;
SetCP[sheet.context, (x+x1)/2, y+b.tilt*(x1-x)/2+(IF up0 THEN -15 ELSE 5)];
SELECT TRUE FROM
	b.invisible => Graphics.SetStipple[sheet.context, grey];
	inVoice => Graphics.SetStipple[sheet.context, black];
	ENDCASE => Graphics.SetStipple[sheet.context, light];
String.AppendDecimal[string, b.ntuple];
SetFont[sheet.context, text, 12];
DrawString[sheet.context, string];
SetFont[sheet.context, music, 8];
END;

DrawPhysicalBeam: PROCEDURE[score: ScorePTR, b: BeamPTR] = 
BEGIN
FOR i: CARDINAL IN [0..b.length) DO
	WITH ev: b.chord[i] SELECT FROM
		note => Note.Draw[score, ev.n];
		chord => Chord.Draw[score, ev.c];
		beam => DrawPhysicalBeam[score, ev.b];
		ENDCASE;
	ENDLOOP;
END;

DrawClippedRect: PROCEDURE[sheet: SheetPTR, x0, y0, xn, yn, delta: REAL, tilt: REAL, thickness, lineHeight: INTEGER] = 
BEGIN
path: Path;
xi: REAL ← xn;
yi: REAL ← yn;
clip: BOOLEAN;
clip ← x0 < sheet.width AND xn > sheet.width;
IF clip THEN BEGIN xi ← sheet.width; yi ← y0 + tilt*(xi-x0); END;
IF x0 > sheet.width THEN
   BEGIN y0 ← y0 - lineHeight; yi ← yi - lineHeight; 
	 x0 ← x0 - sheet.width+ delta;
	 xi ← xi - sheet.width+ delta;
	 END;
path ← NewPath[4];
MoveTo[path, x0, y0-thickness];
LineTo[path, x0, y0+thickness];
LineTo[path, xi+1, yi+thickness];
LineTo[path, xi+1, yi-thickness];
LineTo[path, x0, y0-thickness];
DrawArea[sheet.context, path];
IF NOT clip THEN RETURN;
xi ← delta;
xn ← xn - sheet.width+ delta; 
yn ← yn - lineHeight;
yi ← yn - tilt*(xn-xi);
MoveTo[path, xi, yi +thickness];
LineTo[path, xi, yi-thickness];
LineTo[path, xn+1, yn-thickness];
LineTo[path, xn+1, yn+thickness];
LineTo[path, xi, yi +thickness];
DrawArea[sheet.context, path];
END;

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

END.