-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 10:12 am

DIRECTORY
	Beam USING [AddItem, Free, GetHeapIndex, RemoveItem, SetSyncs, Sort, time], 
	Chord USING [SetBeam, Sort], 
	Inline USING [LongCOPY], 
	MusicDefs, 
	Real USING [FixI], 
	Sheet USING [Height],
	Utility USING [FreeSegment, NewSegment];



BeamImplA: PROGRAM
	IMPORTS Beam, Chord, Inline, MusicDefs, Real, Sheet, Utility 
	EXPORTS Beam = 

BEGIN
OPEN MusicDefs, Utility;

Overflow: PUBLIC SIGNAL[old, new: BeamPTR] = CODE;

-- **************************************************************************
-- data abstractions for a beam
-- CONSTRAINT: b.chord[i] # NIL for all i < b.length
-- CONSTRAINT: b.chord[i].sync.time <= b.chord[i+1].sync.time for all i < b.length
-- CONSTRAINT: b.sync1.time <= b.chord[i].sync.time for all i < b.length
-- CONSTRAINT: b.sync2.time >= b.chord[i].sync.time for all i < b.length
-- **************************************************************************

AddItem: PUBLIC PROCEDURE[score: ScorePTR, beam: BeamPTR, item: VariousPTR] 
	RETURNS[new: BeamPTR] = 
BEGIN
oldBeam: BeamPTR;
sync1, sync2: SyncPTR ← NIL;
FOR i: CARDINAL IN [0..beam.length) DO 
	IF beam.chord[i] = item THEN RETURN[beam]; 
	ENDLOOP;
IF beam.length >= beam.max THEN beam ← New[score, beam.max+4, beam];
WITH vp: item SELECT FROM
	note => {sync1 ← sync2 ← vp.n.sync; oldBeam ← vp.n.beam; vp.n.beam ← beam};
	beam => {sync1 ← vp.b.sync1; sync2 ← vp.b.sync2; oldBeam ← vp.b; vp.b.beam ← beam};
	chord => {
		sync1 ← sync2 ← vp.c.note[0].sync; oldBeam ← vp.c.note[0].beam;
		FOR i: CARDINAL IN [0..vp.c.length) DO vp.c.note[i].beam ← beam; ENDLOOP};
	ENDCASE => ERROR;
IF oldBeam = beam THEN ERROR;
IF oldBeam # NIL THEN Beam.RemoveItem[score, oldBeam, item];
beam.chord[beam.length] ← item;
beam.length ← beam.length + 1;
IF beam.sync1 = NIL THEN beam.sync1 ← sync1;
IF beam.sync2 = NIL THEN beam.sync2 ← sync2;
IF sync1.time < beam.sync1.time THEN beam.sync1 ← sync1;
IF sync2.time > beam.sync2.time THEN beam.sync2 ← sync2;
Sort[beam];
IF beam.beam # NIL THEN Beam.SetSyncs[beam.beam];
RETURN[beam];
END; 

RemoveItem: PUBLIC PROCEDURE[score: ScorePTR, beam: BeamPTR, item: VariousPTR, free: BOOLEAN] = 
BEGIN
found: BOOLEAN ← FALSE;
sync1, sync2: SyncPTR ← NIL;
IF beam = NIL THEN RETURN;
FOR i: CARDINAL IN [0..beam.length) DO
	IF beam.chord[i] # item THEN LOOP;
	FOR j: CARDINAL IN [i..beam.length) DO
		beam.chord[j] ← beam.chord[j+1];
		ENDLOOP;
	beam.length ← beam.length - 1;
	found ← TRUE;
	EXIT; ENDLOOP;
IF NOT found THEN RETURN;
WITH vp: item SELECT FROM
	note => {sync1 ← sync2 ← vp.n.sync; vp.n.beam ← NIL};
	chord => {sync1 ← sync2 ← vp.c.note[0].sync; Chord.SetBeam[vp.c, NIL]};
	beam => {sync1 ← vp.b.sync1; sync2 ← vp.b.sync2; vp.b.beam ← NIL};
	ENDCASE => ERROR;
-- reset beam syncs if needed
IF beam.sync1 = sync1 OR beam.sync1 = sync2 THEN beam.sync1 ← NIL;
IF beam.sync2 = sync2 OR beam.sync2 = sync1 THEN beam.sync2 ← NIL;
IF beam.sync1 = NIL OR beam.sync2 = NIL THEN Beam.SetSyncs[beam];
IF free AND beam.length < 2 THEN Beam.Free[score, beam];
END;

New: PUBLIC PROCEDURE[score: ScorePTR, length: CARDINAL, old: BeamPTR] 
	RETURNS[beam: BeamPTR] = 
BEGIN
beam ← zone.NEW[BeamRec[length]];
IF old # NIL THEN { 
	nullSize: CARDINAL = SIZE[BeamRec[0]];
	oldSize: CARDINAL = SIZE[BeamRec[old.length]];
	IF old.length > length THEN ERROR;
	Inline.LongCOPY[from: old, nwords: nullSize-1, to: beam]; -- skip 'max'
	Inline.LongCOPY[from: old+nullSize, nwords: oldSize - nullSize, to: beam+nullSize]; 
	SetBackPointers[score, beam, old];
	zone.FREE[@old];
	RETURN};
IF score.beamHeap.length = score.beamHeap.max THEN { -- replace with a bigger one.
	old: BeamHeapPTR = score.beamHeap;
	nullSize: CARDINAL = SIZE[BeamHeapRec[0]];
	oldSize: CARDINAL = SIZE[BeamHeapRec[score.beamHeap.max]];
	newMax: CARDINAL = score.beamHeap.max+100;
	new: BeamHeapPTR ← Utility.NewSegment[SIZE[BeamHeapRec[newMax]], newMax, nullSize-1];
	Inline.LongCOPY[from: old, nwords: nullSize-1, to: new]; -- skip 'max'
	Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: new+nullSize]; 
	IF new.max # newMax THEN ERROR;
	score.beamHeap ← new;
	Utility.FreeSegment[old]};
score.beamHeap[score.beamHeap.length] ← beam;
score.beamHeap.length ← score.beamHeap.length + 1;
END;

Free: PUBLIC PROCEDURE[score: ScorePTR, b: BeamPTR] = 
BEGIN
SetBackPointers[score, b, NIL]; 
zone.FREE[@b];
END;

SetBackPointers: PROC[score: ScorePTR, b, new: BeamPTR] = 
BEGIN -- changes all pointers to b to be pointers to new 
-- a beam can be pointed to by notes, beams, the beamHeap, and the cache
index: CARDINAL ← Beam.GetHeapIndex[score.beamHeap, b];
score.beamHeap[index] ← new;
IF new = NIL THEN { -- pack the beamHeap
	score.beamHeap.length ← score.beamHeap.length - 1;
	score.beamHeap[index] ← score.beamHeap[score.beamHeap.length]};
IF b.beam # NIL AND new # NIL THEN { -- replace beam with new in the higher beam
	highBeam: BeamPTR ← b.beam;
	Beam.RemoveItem[score, highBeam, [beam[b]], FALSE]; -- don't free beam!
	highBeam ← Beam.AddItem[score, highBeam, [beam[new]]]};
IF b.beam # NIL AND new = NIL THEN { -- put everything in the higher beam
	highBeam: BeamPTR ← b.beam;
	Beam.RemoveItem[score, highBeam, [beam[b]], b.length = 0]; -- don't free beam if length > 0
	FOR i: CARDINAL DECREASING IN [0..b.length) DO -- add item to high beam
		highBeam ← Beam.AddItem[score, highBeam, b.chord[i]];
		ENDLOOP};
FOR i: CARDINAL IN [0..b.length) DO
	WITH ev: b.chord[0] SELECT FROM
		note => ev.n.beam ← new;
		chord => Chord.SetBeam[ev.c, new];
		beam => ev.b.beam ← new;
		ENDCASE;
	ENDLOOP;
FOR i: CARDINAL DECREASING IN [0..score.cache.beamQueueLength) DO
	IF score.cache.beamQueue[i] = b THEN {
		score.cache.beamQueue[i] ← new;
		IF new = NIL THEN { -- pack the beamQueue
			score.cache.beamQueueLength ← score.cache.beamQueueLength - 1;
			score.cache.beamQueue[i] ← score.cache.beamQueue[score.cache.beamQueueLength]}};
	ENDLOOP;
END;
		
Sort: PUBLIC PROCEDURE[b: BeamPTR] = 
BEGIN
temp: VariousPTR;
FOR i: CARDINAL IN [0..b.length) DO
	FOR j: CARDINAL IN (i..b.length) DO
		IF Beam.time[b.chord[i]] <= Beam.time[b.chord[j]] THEN LOOP;
		temp ← b.chord[i];
		b.chord[i] ← b.chord[j];
		b.chord[j] ← temp;
		ENDLOOP;
	ENDLOOP;
END;

SetSyncs: PUBLIC PROCEDURE[b: BeamPTR] = 
BEGIN
i: CARDINAL;
sync1, sync2: SyncPTR;
b.sync1 ← b.sync2 ← NIL;
FOR i IN [0..b.length) DO
	WITH ev: b.chord[i] SELECT FROM
		note => sync1 ← sync2 ← ev.n.sync;
		chord => sync2 ← sync2 ← ev.c.note[0].sync;
		beam => BEGIN sync1 ← ev.b.sync1; sync2 ← ev.b.sync2; END;
		ENDCASE;
	IF b.sync1 = NIL THEN b.sync1 ← sync1;
	IF b.sync2 = NIL THEN b.sync2 ← sync2;
	IF b.sync1.time > sync1.time THEN b.sync1 ← sync1;
	IF b.sync2.time <= sync2.time THEN b.sync2 ← sync2;
	ENDLOOP;
Beam.Sort[b];
IF b.beam # NIL THEN SetSyncs[b.beam]; 
END;

GetSyncs: PUBLIC PROCEDURE[b: BeamPTR] RETURNS[sync1, sync2: SyncPTR ← NIL] = 
BEGIN
IF b.length = 0 THEN RETURN[NIL, NIL];
WITH chord: b.chord[0] SELECT FROM
	note => sync1 ← chord.n.sync;
	chord => IF chord.c.length > 0 THEN sync1 ← chord.c[0].sync;
	beam => sync1 ← GetSyncs[chord.b].sync1;
	ENDCASE => ERROR;
WITH chord: b.chord[b.length-1] SELECT FROM
	note => sync2 ← chord.n.sync;
	chord => IF chord.c.length > 0 THEN sync2 ← chord.c[0].sync;
	beam => sync2 ← GetSyncs[chord.b].sync2;
	ENDCASE => ERROR;
END;

SetStems: PUBLIC PROCEDURE[sheet: SheetPTR, b: BeamPTR] = 
-- make the chord stems consistent with the position of the beam
BEGIN
i: CARDINAL;
height, heighti, heightb: INTEGER;
time1, timei: Time ← b.sync1.time;
IF NOT b.beamed THEN RETURN;
-- determine the staff
heightb ← b.height+Sheet.Height[sheet, b.sync1.time, , b.staff];
FOR i IN [0..b.length) DO
	WITH ev: b.chord[i] SELECT FROM
	note => {
		heighti ← Sheet.Height[sheet, ev.n.sync.time, ev.n.pitch, ev.n.staff];
		timei ← Beam.time[b.chord[i]];
		height ← Real.FixI[heightb+b.tilt*(timei-time1)];
		ev.n.stemUp ← (height > heighti);
		b.staff ← ev.n.staff};
	 chord => {
		[] ← Chord.Sort[ev.c, FALSE];
		heighti ← Sheet.Height[sheet, ev.c.note[0].sync.time, ev.c.note[0].pitch, ev.c.note[0].staff];
		timei ← Beam.time[b.chord[i]];
		height ← Real.FixI[heightb+b.tilt*(timei-time1)];
		ev.c.stemUp ← (height > heighti);
		b.staff ← ev.c.note[0].staff};
	 beam => {
		ev.b.tilt ← b.tilt;
		height ← Real.FixI[heightb+b.tilt*(ev.b.sync1.time-time1)];
		ev.b.height ← height-Sheet.Height[sheet, 0, , ev.b.staff];
		SetStems[sheet, ev.b];
		b.staff ← ev.b.staff};
	 ENDCASE;
	ENDLOOP;
b.height ← heightb - Sheet.Height[sheet, b.sync1.time, , b.staff]; 
END;

-- **************************************************************************
-- beam attributes
-- **************************************************************************

Grace: PUBLIC PROCEDURE[b: BeamPTR] RETURNS[BOOLEAN] = 
BEGIN -- a grace beam doesn't have ANY non-grace notes
i, j: CARDINAL;
FOR i IN [0..b.length) DO
	IF b.chord[i] = endOfBeam THEN EXIT;
	WITH ev: b.chord[i] SELECT FROM
		note => IF ~ev.n.grace THEN RETURN[FALSE];
		chord => FOR j IN [0..ev.c.length) DO
			IF ~ev.c.note[j].grace THEN RETURN[FALSE];
			ENDLOOP;
		beam => IF ~Grace[ev.b] THEN RETURN[FALSE];
		ENDCASE;
	ENDLOOP;
RETURN[TRUE];
END;

InVoice: PUBLIC PROCEDURE[b: BeamPTR, voice: CARDINAL] RETURNS[BOOLEAN] = 
BEGIN
IF voice = noVoice THEN RETURN[TRUE];
FOR i: CARDINAL IN [0..b.length) DO
	IF b.chord[i] = endOfBeam THEN EXIT;
	WITH ev: b.chord[i] SELECT FROM
		note => IF ev.n.voice = voice THEN RETURN[TRUE];
		chord => FOR j: CARDINAL IN [0..ev.c.length) DO
			IF ev.c.note[j].voice = voice THEN RETURN[TRUE];
			ENDLOOP;
		beam => IF InVoice[ev.b, voice] THEN RETURN[TRUE];
		ENDCASE;
	ENDLOOP;
RETURN[FALSE];
END;

END.