-- ScoreImplA.mesa
-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 12:23 pm

DIRECTORY
	Beam USING [SetStems], 
	-- Device USING [Handle], 
	Event USING [SetStave, Staves], 
	Graphics USING [Context, MakeFont, SetCP, SetFat], 
	MusicDefs, 
	Piece USING [AddEvent, Overflow, RemoveEvent], 
	-- PressDefs USING [PressFileDescriptor, WritePage], 
	-- PressDeviceImpl USING [DataRef], 
	Score USING [GetKey, Look], 
	Selection USING [AddLine], 
	Sheet USING [FindLine, FindSection, FindStaves, Map, NormalPitch, OctavaHeight, Reset],  
	String USING [AppendDecimal], 
	Utility;
 
ScoreImplA: PROGRAM
  IMPORTS Beam, Event, Graphics, MusicDefs, Piece, Score, Selection, Sheet, String, Utility 
	EXPORTS MusicDefs, Score
	-- SHARES PressDeviceImpl -- = 
BEGIN
OPEN Graphics, MusicDefs, Score, Utility;

Error: PUBLIC SIGNAL[s: STRING] = CODE;

-- ****************************************************************************
-- cache 
-- ****************************************************************************

-- cache: PUBLIC ARRAY [0..maxCacheLength) OF EventPTR;
-- cacheLength: PUBLIC CARDINAL ← 0;
-- currentKey: INTEGER ← 0;
-- keyStart, keyStop: Time ← 0;

BuildCache: PUBLIC PROCEDURE[score: ScorePTR, time: Time] = 
BEGIN
score.cache.key1 ← score.cache.key2 ← NIL;
score.cache.met1 ← score.cache.met2 ← NIL;
score.cache.ts1 ← score.cache.ts2 ← NIL;
[] ← GetKey[score, time];
[] ← GetTimeSignature[score, time];
[] ← GetMetrenome[score, time];
END;

Initialize: PUBLIC PROC[score: ScorePTR, context: Graphics.Context] = 
BEGIN
score.sheet.context ← context;
[] ← Graphics.SetFat[context, TRUE];
music ← Graphics.MakeFont["music5"];
text ← Graphics.MakeFont["TimesRoman10"];
Utility.SetFont[context, music, 8];
-- pianoroll
score.style[0] ← zone.NEW[EventRec.staves];
score.style[0].height ← 0;
score.style[0].offset ← 60;
score.style[0].staff ← [[72, -32], [48, -88], [27, -136], [3, -196]];
-- one staff  
score.style[1] ← zone.NEW[EventRec.staves];
score.style[1].height ← 0;
score.style[1].offset ← 80;
score.style[1].staff ← [[48, -32], [48, -32], [48, -32], [48, -32]];  
-- two staffs  
score.style[2] ← zone.NEW[EventRec.staves];
score.style[2].height ← 0;
score.style[2].offset ← 85;
score.style[2].staff ← [[48, -32], [48, -32], [27, -125], [27, -125]];  
-- two staffs  
score.style[12] ← zone.NEW[EventRec.staves];
score.style[12].height ← 0;
score.style[12].offset ← 85;
score.style[12].staff ← [[48, -32], [48, -32], [27, -150], [27, -150]];
-- three staffs  
score.style[3] ← zone.NEW[EventRec.staves];
score.style[3].height ← 0;
score.style[3].offset ← 75;
score.style[3].staff ← [[48, -32], [48, -32], [27, -110], [27, -185]];  
-- three staffs  
score.style[13] ← zone.NEW[EventRec.staves];
score.style[13].height ← 0;
score.style[13].offset ← 75;
score.style[13].staff ← [[48, -32], [48, -32], [27, -130], [27, -220]];  
-- four staffs  
score.style[4] ← zone.NEW[EventRec.staves];
score.style[4].height ← 0;
score.style[4].offset ← 75;
score.style[4].staff ← [[48, -32], [48, -110], [27, -185], [27, -260]];  
Score.Look[score, style, , 2];
Selection.AddLine[score, 0, 0];
END;

-- ****************************************************************************
-- key 
-- ****************************************************************************

SetKey: PUBLIC PROCEDURE[score: ScorePTR, key: INTEGER, time1, time2: Time] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
oldKey: INTEGER;
keySig: LONG POINTER TO EventRec.keySignature ← NIL;
IF time1 > time2 THEN RETURN;
IF key NOT IN [-7..7] THEN RETURN;
score.sheet.accidental ← TRUE;
oldKey ← Score.GetKey[score, time2];
FOR i: CARDINAL DECREASING IN [0..score.length) DO
    IF score.event[i].time > time2 THEN LOOP;
    IF score.event[i].time < time1 THEN EXIT;
    IF score.event[i].type # keySignature THEN LOOP;
	 Piece.RemoveEvent[score, score.event[i], TRUE];
    -- Utility.FreeEvent[@score.event[i]];
    ENDLOOP;
-- put in the new key signature 
IF key # Score.GetKey[score, time1] THEN {
	keySig ← zone.NEW[EventRec.keySignature];
	keySig.time ← time1;
	keySig.key ← key;
	score ← Piece.AddEvent[score, keySig]};
-- put back the old signature 
IF oldKey # key AND time2 < EndOfScore[score] THEN {
	keySig ← zone.NEW[EventRec.keySignature];
	keySig.time ← time2;
	keySig.key ← oldKey;
	score ← Piece.AddEvent[score, keySig]};
score.cache.key1 ← score.cache.key2 ← NIL; -- invalidate cache
SetDirty[score, time1, LAST[Time]];
Sheet.Reset[score]; -- rebuild sheet
END;
	
GetKey: PUBLIC PROCEDURE[score: ScorePTR, t: Time] RETURNS[key: INTEGER ← 0] = 
BEGIN -- caches the last key range
IF score.cache.key1 # NIL AND t >= score.cache.key1.time AND
	(score.cache.key2 = NIL OR t < score.cache.key2.time) THEN RETURN[score.cache.key1.key];
score.cache.key1 ← score.cache.key2 ← NIL;
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i].type # keySignature THEN LOOP;
	IF score.event[i].time > t THEN {score.cache.key2 ← LOOPHOLE[score.event[i]]; EXIT};
	score.cache.key1 ← LOOPHOLE[score.event[i]];
	ENDLOOP;
IF score.cache.key1 # NIL THEN key ← score.cache.key1.key;
END;

-- ****************************************************************************
-- metrenome
-- ****************************************************************************

SetMetrenome: PUBLIC PROCEDURE[score: ScorePTR, m: INTEGER, time1, time2: Time] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
metrenome: LONG POINTER TO EventRec.metrenome ← NIL;
IF time1 > time2 THEN RETURN;
SetDirty[score, time1, time1+50];
-- remove old metrenomes
FOR i: CARDINAL DECREASING IN [0..score.length) DO
    IF score.event[i].time > time2 THEN LOOP;
    IF score.event[i].time < time1 THEN EXIT;
    IF score.event[i].type # metrenome THEN LOOP;
    SetDirty[score, time1, score.event[i].time+50];
	 Piece.RemoveEvent[score, score.event[i], TRUE];
    -- Utility.FreeEvent[@sync]; 
    ENDLOOP;
-- insert new one 
metrenome ← zone.NEW[EventRec.metrenome];
metrenome.time ← time1;
metrenome.metrenome ← m;
score ← Piece.AddEvent[score, metrenome];
score.cache.met1 ← score.cache.met2 ← NIL; -- invalidates cache
END;
	
GetMetrenome: PUBLIC PROCEDURE[score: ScorePTR, t: Time] RETURNS[metrenome: INTEGER ← 128] = 
BEGIN
IF score.cache.met1 # NIL AND t >= score.cache.met1.time AND
	(score.cache.met2 = NIL OR t < score.cache.met2.time) THEN RETURN[score.cache.met1.metrenome];
score.cache.met1 ← score.cache.met2 ← NIL;
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i].type # metrenome THEN LOOP;
	IF score.event[i].time > t THEN {score.cache.met2 ← LOOPHOLE[score.event[i]]; EXIT};
	score.cache.met1 ← LOOPHOLE[score.event[i]];
	ENDLOOP;
IF score.cache.met1 # NIL THEN metrenome ← score.cache.met1.metrenome;
END;

DrawMetrenome: PUBLIC PROCEDURE[score: ScorePTR, metrenome: INTEGER, time: Time] = 
BEGIN
x, y: INTEGER;
string: STRING ← [10];
context: Graphics.Context ← score.sheet.context;
IF score.sheet.printing THEN RETURN;
[x, y] ← Sheet.Map[score.sheet, time, , 0];
Graphics.SetCP[context, x, y+40];
DrawChar[context, 't];
String.AppendDecimal[string, metrenome];
Utility.SetFont[context, text, 12];
DrawChar[context, '= ];
DrawString[context, string];
Utility.SetFont[context, music, 8];
END;

-- ****************************************************************************
-- time signature 
-- ****************************************************************************

SetTimeSignature: PUBLIC PROCEDURE[score: ScorePTR, ts: TimeSignature, time1, time2: Time] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
oldTS: TimeSignature ← [0, 0];
timeSig: LONG POINTER TO EventRec.timeSignature;
IF time1 > time2 THEN RETURN;
SetDirty[score, time1, time1+50];
-- remove old time signature
FOR i: CARDINAL IN [0..score.length) DO
    IF score.event[i].time > time2 THEN EXIT;
    IF score.event[i].type # timeSignature THEN LOOP;
    IF score.event[i].time < time1 THEN {
    	timeSig ← LOOPHOLE[score.event[i]];
    	oldTS ← timeSig.ts; LOOP};
	 SetDirty[score, time1, score.event[i].time+50];
	 Piece.RemoveEvent[score, score.event[i], TRUE];
    -- Utility.FreeEvent[@sync];
    ENDLOOP;
-- insert new time signature
IF ts.top # 0 AND ts.bottom # 0 THEN {
	timeSig ← zone.NEW[EventRec.timeSignature];
	timeSig.time ← time1+10;
	timeSig.ts ← ts;
	score ← Piece.AddEvent[score, timeSig]};
IF oldTS.top # 0 AND time2 < EndOfScore[score] THEN {
	timeSig ← zone.NEW[EventRec.timeSignature];
	timeSig.time ← time2+10;
	timeSig.ts ← oldTS;
	score ← Piece.AddEvent[score, timeSig];
	SetDirty[score, time1, time2]};
score.cache.ts1 ← score.cache.ts2 ← NIL; -- invalidate cache
END;
	
GetTimeSignature: PUBLIC PROCEDURE[score: ScorePTR, t: Time] RETURNS[ts: TimeSignature ← [4, 4]] = 
BEGIN
IF score.cache.ts1 # NIL AND t >= score.cache.ts1.time AND
	(score.cache.ts2 = NIL OR t < score.cache.ts2.time) THEN RETURN[score.cache.ts1.ts];
score.cache.ts1 ← score.cache.ts2 ← NIL;
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i].type # timeSignature THEN LOOP;
	IF score.event[i].time > t THEN {score.cache.ts2 ← LOOPHOLE[score.event[i]]; EXIT};
	score.cache.ts1 ← LOOPHOLE[score.event[i]];
	ENDLOOP;
IF score.cache.ts1 # NIL THEN ts ← score.cache.ts1.ts;
END;

DrawTimeSignature: PUBLIC PROCEDURE[score: ScorePTR, ts: TimeSignature, time: Time] = 
BEGIN
x, y: INTEGER;
oldStaff: Staff;
context: Graphics.Context ← score.sheet.context;
staves: StavesPTR = Sheet.FindStaves[score.sheet, time];
FOR i: CARDINAL IN [0..staves.length) DO
    IF staves.staff[i] = oldStaff THEN LOOP;
    oldStaff ← staves.staff[i];
    [x, y] ← Sheet.Map[score.sheet, time, , i];
	x ← x-5;
    Graphics.SetCP[context, x, y+8];
    SELECT ts.bottom FROM
	   2  => DrawChar[context, 062C];
	   4  => DrawChar[context, 064C];
	   8  => DrawChar[context, 070C];
	   ENDCASE;
    Graphics.SetCP[context, x, y+24];
    SELECT ts.top FROM
	   1  => DrawChar[context, 061C];
	   2  => DrawChar[context, 062C];
	   3  => DrawChar[context, 063C];
	   4  => DrawChar[context, 064C];
	   5  => DrawChar[context, 065C];
	   6  => DrawChar[context, 066C];
	   7  => DrawChar[context, 067C];
	   8  => DrawChar[context, 070C];
	   9  => DrawChar[context, 071C];
	   12 => {
		 Graphics.SetCP[context, x-5, y+24]; 
		 DrawString[context, "12"]}; 
	   ENDCASE;
    ENDLOOP;
END;

-- ****************************************************************************
-- procedures that change the staves attributes
-- ****************************************************************************

SetStyle: PUBLIC PROCEDURE[score: ScorePTR, index: INTEGER, t1, t2: Time] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
old: INTEGER ← -1;
style: StavesPTR ← NIL;
temp, sync: EventPTR ← NIL;
endOfScore: Time = EndOfScore[score];
IF index NOT IN [0..20] THEN {score.flash ← TRUE; RETURN};
IF score.style[index] = NIL THEN {score.flash ← TRUE; RETURN};
SetDirty[score, t1, EndOfScore[score]];
sync ← NearestMeasure[score, t1, 10];
temp ← NearestMeasure[score, t2, 10];
IF sync # NIL THEN t1 ← sync.time+1;
IF temp # NIL THEN t2 ← temp.time-1;
-- get the old style
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i].type # staves THEN LOOP;
	IF Event.Staves[score.event[i]].staves # style THEN LOOP;
	IF score.event[i].time >= t1-1 THEN EXIT;
	old ← Event.Staves[score.event[i]].value;
	ENDLOOP;
-- change all of the current staves to the new style
FOR i: CARDINAL DECREASING IN [0..score.length) DO
	IF score.event[i].time > t2 THEN LOOP;
	IF score.event[i].time < t1-5 THEN EXIT;
	IF score.event[i].type # staves THEN LOOP;
	IF Event.Staves[score.event[i]].staves # style THEN LOOP;
	Event.Staves[score.event[i]].value ← index;
	ENDLOOP;
-- insert new staves 
IF old # index THEN {
	IF sync # NIL THEN Piece.RemoveEvent[score, sync, TRUE];
	style ← zone.NEW[EventRec.staves];
	style↑ ← score.style[index]↑;
	style.value ← index;
	style.time ← t1;
	score ← Piece.AddEvent[score, style]};
IF old # index AND old # -1 AND t2 # endOfScore THEN {
	IF temp # NIL THEN Piece.RemoveEvent[score, temp, TRUE];
	style ← zone.NEW[EventRec.staves];
	style↑ ← score.style[old]↑;
	style.value ← old;
	style.time ← t2;
	score ← Piece.AddEvent[score, style]};
-- reset world
Sheet.Reset[score]; 
FOR i: CARDINAL IN [0..score.beamHeap.length) DO 
	Beam.SetStems[score.sheet, score.beamHeap.beam[i]]; 
	ENDLOOP;
Selection.AddLine[score, t1, t2]; 
END;

NearestMeasure: PROCEDURE[score: ScorePTR, time, delta: Time] RETURNS[EventPTR] = 
BEGIN
s: EventPTR ← NIL;
FOR i: CARDINAL IN [0..score.length) DO
	IF ~Measure[score.event[i]] THEN LOOP;
	IF ABS[score.event[i].time-time] > delta THEN LOOP;
	delta ← ABS[score.event[i].time-time];
	s ← score.event[i];
	ENDLOOP;
RETURN[s];
END;

-- style: PUBLIC ARRAY [0..20] OF Staves;

GetStyle: PUBLIC PROCEDURE[score: ScorePTR, time: Time] RETURNS[INTEGER] = 
BEGIN
equal: BOOLEAN;
sheet: SheetPTR ← score.sheet;
staves: StavesPTR = sheet.section[Sheet.FindLine[sheet, time]].staves;
IF staves = NIL THEN RETURN[-1];
FOR i: CARDINAL IN [0..20] DO
	IF score.style[i].length # staves.length THEN LOOP;
	equal ← TRUE;
	FOR j: CARDINAL IN [0..score.style[i].length) DO
		IF score.style[i].staff[j].y # staves.staff[j].y THEN {equal ← FALSE; EXIT};
		ENDLOOP;
	IF equal THEN RETURN[i];
	ENDLOOP;
RETURN[-1];
END;

-- ****************************************************************************
-- clefs switches
-- ****************************************************************************

SetClef: PUBLIC PROCEDURE[score: ScorePTR, pitch, staff: INTEGER, time: Time] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
staves: StavesPTR;
-- make up a sync
staves ← zone.NEW[EventRec.staves];
Event.SetStave[score, staves, Sheet.FindStaves[score.sheet, time]];
staves.staff[staff].pitch ← pitch;
staves.staves ← clef;
staves.time ← time;
staves.value ← staff;
score ← Piece.AddEvent[score, staves];
Sheet.Reset[score];
END;

SetOctava: PUBLIC PROCEDURE[score: ScorePTR, pitch, staff, height: INTEGER, t1, t2: Time] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
s: EventPTR ← NIL;
current: INTEGER;
section: CARDINAL;
staves: StavesPTR;
sheet: SheetPTR ← score.sheet;
normal: INTEGER = Sheet.NormalPitch[sheet, staff];
IF t1 >= t2 THEN RETURN;
-- check to make sure that the staff is clear
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i].time < t1 THEN LOOP;
	IF score.event[i].time > t2 THEN EXIT;
	IF score.event[i].type # staves THEN LOOP;
	staves ← Event.Staves[score.event[i]];
	current ← staves.staff[staff].pitch;
	IF current = normal THEN LOOP;
	score.flash ← TRUE; RETURN;
	ENDLOOP;
section ← Sheet.FindSection[sheet, t1];
IF sheet.section[section].staves.staff[staff].pitch # normal THEN {score.flash ← TRUE; RETURN};
-- add the first sync
staves ← zone.NEW[EventRec.staves];
Event.SetStave[score, staves, sheet.section[section].staves];
staves.staff[staff].pitch ← pitch;
staves.height ← Sheet.OctavaHeight[pitch, height];
staves.staves ← octava1;
staves.time ← t1;
staves.value ← staff;
score ← Piece.AddEvent[score, staves];
-- add the second sync
staves ← zone.NEW[EventRec.staves];
Event.SetStave[score, staves, Sheet.FindStaves[sheet, t2]];
staves.staff[staff].pitch ← normal;
staves.staves ← octava2;
staves.time ← t2;
staves.value ← staff;
score ← Piece.AddEvent[score, staves];
Sheet.Reset[score];
END;

END..