-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 12:29 pm

DIRECTORY
	Beam USING [Drawn], 
	Event USING [Adjust, Draw, GetScoreIndex, Sync], 
	Graphics USING [Context, DrawBox, DrawChar, GetBounds, SetCP, SetStipple], 
	MusicDefs, 
	Note USING [DrawTie, InVoice], 
	Piece USING [NearestEvent, Overflow], 
	Score USING [Draw, GetKey, Justify, LogicalToPhysical, ScalePhysical, SetStyle, ShowPitch], 
	Selection USING [Draw, selection], 
	Sheet USING [default, Draw, FindSection, FindStaves, Height, HiLite, MapNote, NearestTime, Scale, ScreenPoint, SetBegin];
 
ScoreImplB: PROGRAM
  IMPORTS Beam, Graphics, MusicDefs, Note, Piece, Score, Selection, Sheet, Event 
  EXPORTS Score = 
BEGIN
OPEN MusicDefs;

-- ****************************************************************************
-- drawing the score 
-- ****************************************************************************

Draw: PUBLIC PROCEDURE[score: ScorePTR, erase: BOOLEAN] = 
BEGIN
sheet: SheetPTR ← score.sheet;
Selection.Draw[]; -- remove selection from screen
IF erase THEN {
		SetBrush[score, white, opaque];
		Graphics.DrawBox[sheet.context, Graphics.GetBounds[sheet.context]];
		SetBrush[score, black, transparent]; 
		Sheet.Draw[sheet]};
DrawInterval[score, sheet.begin, sheet.endTime];
Selection.Draw[];
END;

Redraw: PUBLIC PROCEDURE[score: ScorePTR, t1, t2: Time] = 
BEGIN
x, y: INTEGER;
select: BOOLEAN;
selection: SelectionPTR ← Selection.selection;
SetBrush[score, black, transparent];
SELECT score.sheet.scale FROM
	1 => IF t2-t1 > 700 THEN {Score.Draw[score]; RETURN};
	2 => IF t2-t1 > 2000 THEN {Score.Draw[score]; RETURN};
	4 => IF t2-t1 > 8000 THEN {Score.Draw[score]; RETURN};
	ENDCASE;
select ← selection.lineSelect AND selection.select2 > t1-40 AND selection.select1 < t2+50;
IF select THEN Selection.Draw[]; -- remove selection from screen
Sheet.HiLite[score.sheet, white, t1-20, t2+30];
DrawInterval[score, t1-30, t2+40];
IF select THEN Selection.Draw[];
IF selection.lineSelect THEN RETURN;
SetBrush[score, black, invert];
FOR i: CARDINAL IN [0..selection.length) DO
	 IF selection.note[i] = NIL THEN LOOP;
    IF selection[i].sync.time > t2+40 OR selection[i].sync.time < t1-30 THEN LOOP;
    IF ~Note.InVoice[selection[i], score.sheet.voice] THEN LOOP;
    [x, y] ← Sheet.MapNote[score.sheet, selection[i]];
    Graphics.SetCP[score.sheet.context, x, y];
    Graphics.DrawChar[score.sheet.context, 170C];
    ENDLOOP;
END;

DrawInterval: PROCEDURE[score: ScorePTR, t1, t2: Time] = 
BEGIN
n: NotePTR;
staves: StavesPTR;
j: CARDINAL ← 0;
sheet: SheetPTR ← score.sheet;
[] ← Beam.Drawn[score, NIL]; -- clears the beam cache
SetBrush[score, black, transparent];
-- draw octava markings first
staves ← Sheet.FindStaves[sheet, t1];
FOR i: CARDINAL IN [0..staves.length) DO
	IF staves.staff[i].pitch # 15 AND staves.staff[i].pitch # 60 THEN LOOP;
	FOR j: CARDINAL DECREASING IN [0..score.length) DO
		IF score.event[j].time >= t1 THEN LOOP;
		IF score.event[j].type # staves THEN LOOP;
		WITH ev: score.event[j] SELECT FROM
			staves => {IF ev.staves NOT IN [octava1..octava2] THEN LOOP;
				IF ev.value # i THEN LOOP;
				IF ev.staves = octava2 THEN EXIT};
			ENDCASE => ERROR;
		Event.Draw[score, score.event[j]]; 
		EXIT; ENDLOOP;
	ENDLOOP;
-- pre-Adjust the first 10 syncs (beams may draw notes in syncs downstream)
j ← 0;
IF score.sheet.display = graphical THEN 
   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 = sync THEN {
         	IF j = 10 THEN EXIT ELSE j ← j+1;
		  	Event.Adjust[score, Event.Sync[score.event[i]]]};
		  ENDLOOP;
-- draw the syncs
j ← 0;
FOR i: CARDINAL IN [0..score.length) DO
    IF score.event[i].time < t1 THEN LOOP;
    IF score.event[i].time > t2 THEN {j ← i; EXIT};
    IF score.sheet.display = graphical THEN {
    	plus10: CARDINAL ← MIN[i+10, score.length-1];
    	IF score.event[plus10].type = sync 
    		THEN Event.Adjust[score, Event.Sync[score.event[plus10]]]};
    Event.Draw[score, score.event[i]];
    IF NOT sheet.printing AND AnyBug[] THEN EXIT;
    ENDLOOP;
-- draw ties that go off the end of the section
IF j # 0 THEN FOR i: CARDINAL IN [j..score.length) DO
	IF score.event[i].time > t2+200 THEN EXIT;
	IF score.event[i].type = sync THEN {
		sync: SyncPTR ← Event.Sync[score.event[i]];
		FOR j: CARDINAL IN [0..sync.length) DO
			IF (n ← sync.note[j]) = NIL THEN EXIT;
			IF n.tie = NIL THEN LOOP;
			IF n.tie.sync.time > t2 THEN LOOP;
			IF ~Note.InVoice[n, score.sheet.voice]
				THEN Graphics.SetStipple[score.sheet.context, light]
				ELSE Graphics.SetStipple[score.sheet.context, black];
			Note.DrawTie[score, n];
			ENDLOOP};
	ENDLOOP;
[] ← Beam.Drawn[score, NIL];
END;

-- ****************************************************************************
-- printing the score 
-- ****************************************************************************

Print: PUBLIC PROCEDURE[score: ScorePTR, splines: BOOLEAN] = {};

-- ****************************************************************************
-- changing the view
-- ****************************************************************************

Look: PUBLIC PROCEDURE[score: ScorePTR, look: LookCommand, switch: BOOLEAN, n: INTEGER] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
draw: BOOLEAN ← TRUE;
SELECT look FROM
    accidental => score.sheet.accidental ← switch;
    hardcopy   => {
    	Sheet.Scale[score, IF switch THEN 2 ELSE 1]; 
    	score.sheet.hardcopy ← switch};
    justified  => {
    	score.sheet.density ← n; 
    	Score.Justify[score, Selection.selection.select1, Selection.selection.select2]};
    logical    => Score.LogicalToPhysical[score, Selection.selection.select1, Selection.selection.select2];
    noCarry    => score.sheet.noCarry ← switch;
    notehead   => score.sheet.notehead ← switch;
    overview   => Overview[score, switch];
    physical   => Score.ScalePhysical[score, 512/n];
    style      => Score.SetStyle[score, n, 0, EndOfScore[score]];
    sync  => score.sheet.sync ← switch;
    voice      => {score.sheet.voice ← IF ~switch THEN noVoice ELSE n};
    ENDCASE;
END;

Overview: PROCEDURE[score: ScorePTR, switch: BOOLEAN] = 
BEGIN
time: Time;
x, y: INTEGER;
SELECT TRUE FROM
	score.sheet.scale < 4 AND ~switch => {score.flash ← TRUE; RETURN};
	score.sheet.scale > 2 AND switch => {score.flash ← TRUE; RETURN};
	switch => {Sheet.Scale[score, 4]; Sheet.SetBegin[score.sheet, 0]};
	YellowBug[] => {
		[x, y] ← Sheet.ScreenPoint[score.sheet];
		time ← Sheet.NearestTime[score.sheet, x, y].time;
		Sheet.Scale[score, IF score.sheet.hardcopy THEN 2 ELSE 1]; 
		Sheet.SetBegin[score.sheet, time]};
	ENDCASE => {
		Sheet.Scale[score, IF score.sheet.hardcopy THEN 2 ELSE 1]; 
		Sheet.SetBegin[score.sheet, 0]};
END;

-- ****************************************************************************
-- getting accidentals
-- ****************************************************************************

phi: ARRAY [0..12) OF INTEGER = [6, 1, -4, 3, -2, 5, 0, 7, 2, -3, 4, -1];
flatHeight: ARRAY[0..12) OF INTEGER =  [0, 4, 8, 8, 12, 12, 16, 16, 20, 24, 24, 28];
sharpHeight: ARRAY[0..12) OF INTEGER = [0, 4, 4, 8, 8, 12, 12, 16, 20, 20, 24, 24];

GetAccidental: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR] RETURNS[Accidental] = 
BEGIN -- hack to set n.shown
n.shown ← GetAccInternal[score, n];
RETURN[n.shown];
END;

GetAccInternal: PROCEDURE[score: ScorePTR, n: NotePTR] RETURNS[Accidental] = INLINE
BEGIN
a: Accidental;
prior: NotePTR;
index: CARDINAL;
pitch, key: INTEGER;
normal: Accidental;
noteHeight: INTEGER;
IF NOT score.sheet.accidental THEN RETURN[inKey];
IF n.tie # NIL AND n.tie.pitch = n.pitch THEN RETURN[inKey];
IF n.show AND n.spelled = inKey THEN Error["show inKey?"];
IF n.show THEN RETURN[n.spelled];
IF score.sheet.noCarry THEN {
	IF n.spelled # inKey THEN RETURN[n.spelled];
	a ← DefaultAcc[0, n.pitch];
	IF a = natural THEN a ← inKey;
	RETURN[a]};
key ← Score.GetKey[score, n.sync.time];
pitch ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key];
noteHeight ← Sheet.Height[score.sheet, n.sync.time, pitch, n.staff];
index ← Event.GetScoreIndex[score, n.sync];
IF index = score.length THEN index ← EventIndex[score, n.sync.time];
prior ← PriorNote[score, pitch, noteHeight, index];
normal ← NormalAcc[key, pitch];
-- no prior note
IF prior = NIL THEN SELECT TRUE FROM
	normal = inKey => RETURN[n.spelled];
	n.spelled = inKey => RETURN[normal];
	ENDCASE => RETURN[n.spelled];
-- 	n.spelled = normal => RETURN[inKey];
-- 	n.spelled # normal => RETURN[n.spelled];
-- 	ENDCASE;
-- prior note
IF prior.pitch = n.pitch THEN RETURN[inKey];
-- an accidental MUST be asserted
IF n.spelled # inKey THEN RETURN[n.spelled];
IF normal # inKey THEN RETURN[normal];
IF Mod[n.pitch, 12] = 7 AND key < -5 THEN RETURN[flat];
IF Mod[n.pitch, 12] = 0 AND key < -6 THEN RETURN[flat];
IF Mod[n.pitch, 12] = 1 AND key > 5 THEN RETURN[sharp];
IF Mod[n.pitch, 12] = 8 AND key > 6 THEN RETURN[sharp];
normal ← NormalAcc[0, n.pitch];
IF normal = inKey THEN normal ← natural;
IF key < 0 AND normal = sharp THEN normal ← flat;
RETURN[normal];
END;

EventIndex: PROCEDURE[score: ScorePTR, time: Time] RETURNS[s: CARDINAL] = INLINE 
BEGIN
s ← Piece.NearestEvent[score, time];
IF score.event[s] # NIL AND score.event[s].time >= time THEN RETURN[s];
FOR i: CARDINAL DECREASING IN [0..s+10) DO
	IF score.event[i] = NIL THEN LOOP;
	IF score.event[i].time > time THEN LOOP;
	RETURN[i+1];
	ENDLOOP;
END;

PriorNote: PUBLIC PROCEDURE[score: ScorePTR, pitch, height: INTEGER, index: CARDINAL] RETURNS[NotePTR] = 
BEGIN
n: NotePTR;
sync: SyncPTR;
newPitch, key: INTEGER;
FOR i: CARDINAL DECREASING IN [0..index) DO
	IF Measure[score.event[i]] THEN RETURN[NIL];
	IF score.event[i].type # sync THEN LOOP;
	key ← Score.GetKey[score, score.event[i].time];
	sync ← Event.Sync[score.event[i]];
	FOR j: CARDINAL IN [0..sync.length) DO
		IF (n ← sync.note[j]) = NIL THEN EXIT;
		IF n.rest THEN LOOP;
		newPitch ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key];
		IF NOT newPitch IN [pitch-2..pitch+2] THEN LOOP;
		IF Sheet.Height[score.sheet, score.event[i].time, newPitch, n.staff] # height THEN LOOP;
		RETURN[n];
		ENDLOOP;
	ENDLOOP;
RETURN[NIL];
END;

assert: Accidental = LOOPHOLE[LOOPHOLE[LAST[Accidental], CARDINAL]+1]; 

NormalAcc: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] = 
BEGIN
strangeness: INTEGER ← phi[Mod[pitch, 12]];
-- I bet you don't really believe this works.
SELECT TRUE FROM
       Mod[(key-strangeness), 12] > 4 => RETURN[inKey];
       strangeness > 0 => RETURN[natural];
       key < 0 => RETURN[flat];
       ENDCASE => RETURN[sharp];
END;

DefaultAcc: PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] = 
BEGIN
SELECT NormalAcc[0, pitch] FROM
	inKey => RETURN[natural];
	sharp => RETURN[IF key < 0 THEN flat ELSE sharp];
	ENDCASE => ERROR;
END;

AddToPitch: PUBLIC PROCEDURE[key, pitch, delta: INTEGER] RETURNS[INTEGER] = 
BEGIN
i: INTEGER;
IF delta > 0 THEN FOR i IN (pitch..90] DO
		IF NormalAcc[key, i] = inKey THEN delta ← delta-1;
		IF delta = 0 THEN RETURN[i];
		ENDLOOP
	ELSE FOR i DECREASING IN [0..pitch) DO
		IF NormalAcc[key, i] = inKey THEN delta ← delta+1;
		IF delta = 0 THEN RETURN[i];
		ENDLOOP;
ERROR;
END;

KeyHeight: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[INTEGER] = 
BEGIN
i: CARDINAL ← Mod[pitch, 12];
SELECT TRUE FROM
	i = 7 AND key < -5 => RETURN[flatHeight[8]];
	i = 0 AND key < -6 => RETURN[flatHeight[1]];
	key < 0  => RETURN[flatHeight[i]];
	i = 1 AND key > 5 => RETURN[sharpHeight[0]];
	i = 8 AND key > 6 => RETURN[sharpHeight[7]];
	ENDCASE => RETURN[sharpHeight[i]];
END;

END . . .

Print: PUBLIC PROCEDURE[splines: BOOLEAN] = 
BEGIN
printAll: BOOLEAN;
oldBegin: Time ← begin;
ph: POINTER TO PressDefs.PressFileDescriptor;
device: Device.Handle ← Utility.OpenPressDevice[splines];
ph ← LOOPHOLE[device.data, PressDeviceImpl.DataRef].pressHandle;
printAll ← ~splines AND scale = 2;
IF printAll THEN Sheet.SetBegin[0];
FOR i: CARDINAL IN [0..scoreLength) DO
	Event.Adjust[score.event[i]];
	ENDLOOP;
WHILE TRUE DO
    Sheet.Draw[];
    DrawInterval[begin, endTime];
    IF endTime > EndOfScore[]-40 THEN EXIT;
    IF printAll THEN {Sheet.SetBegin[endTime]; PressDefs.WritePage[ph]} ELSE EXIT;
    ENDLOOP;
Utility.ClosePressDevice[@device];
Sheet.SetBegin[oldBegin];
END;