-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 8:19 am

DIRECTORY
CursorDefs,
Beam USING [Draw, SetStems],
Event USING [Clef, Draw, GetOctava, GetScoreIndex, GetStaff, KeySignature, Measure, Octava, Staves],
Graphics USING [DrawBox],
Interface USING [InsertNote, Flash, Object],
MusicDefs,
Note USING [Draw, DrawTie, InVoice, SetAccidental, SetEmbellishment],
Piece USING [AddEvent, NearestNote, NearestObject, Overflow, RemoveEvent],
Process USING [SetTimeout],
Real USING [FixI],
Score USING [GetKey, GetStyle, Redraw, SetClef, SetOctava],
Selection USING [RemoveNote],
Sheet USING [AlternateTime, DrawClef, DrawOctava, FindLine, FindSection, Height, NearestStaff, NearestTime, NextLine, NextStaff, NormalPitch, OctavaHeight, PriorStaff, Reset, ScreenPoint],
UserTerminal USING [mouse, GetCursorPattern, SetCursorPattern];


InterfaceImplB: MONITOR
IMPORTS
Beam, Graphics, Interface, MusicDefs, Note,
Piece, Process, Real, Score, Selection, Sheet, Event, UserTerminal
EXPORTS Interface =
BEGIN
OPEN Graphics, MusicDefs, UserTermin
al;

timeout: CO
NDITION;
Error: SIGNAL = CODE;

-- ***********************************
*******************************
-- del
eting objects
-- ******************************************************************
count: PUBLIC BOOLEAN;

DeleteGraphical: PUBLIC PROCEDURE[score: ScorePTR] =
BEGIN
type: EventType;
obj: ObjectType;
s: EventPTR ← NIL;
time, carry, next: Time;
p: LONG POINTER ← NIL;
value, oldClef, oldStyle: INTEGER ← 0;
SetBrush[score, black, invert];
WHILE YellowBug[] DO
[obj, p] ← Piece.NearestObject[score];
IF p # NIL THEN SELECT obj FROM
note => Note.Draw[score, p];
measure => Event.Draw[score, p];
leftBeam => [] ← Beam.Draw[score, p];
rightBeam => [] ← Beam.Draw[score, p];
ENDCASE;
Wait[1];
IF p # NIL THEN SELECT obj FROM
note => Note.Draw[score, p];
measure => Event.Draw[score, p];
leftBeam => [] ← Beam.Draw[score, p];
rightBeam => [] ← Beam.Draw[score, p];
ENDCASE;
ENDLOOP;
IF p # NIL AND obj = note THEN Selection.RemoveNote[p];
IF obj # measure OR p = NIL THEN RETURN;
s ← LOOPHOLE[p, EventPTR];
IF score.sheet.voice # noVoice AND (Event.Clef[s] OR Event.Octava[s]) THEN RETURN;
type ← s.type; time ← s.time;
IF s.type = staves THEN value ← Event.Staves[s].value;
IF Event.Clef[s] THEN oldClef ← Clef[score.sheet, value, time];
-- IF type = staves THEN y ← score.sheet[Sheet.FindLine[score.sheet, time]].y;
IF type = staves THEN {
next ← score.sheet[Sheet.NextLine[score.sheet, Sheet.FindLine[score.sheet, time]]].time;
oldStyle ← Score.GetStyle[score, next]};
carry ← GetCarry[score, s];
Piece.RemoveEvent[score, s];
SetDirty[score, time, time];
IF type = keySignature THEN SetDirty[score, time, carry];
IF type = keySignature OR Event.Clef[s] THEN ResetSheet[score, time, time];
IF Event.Clef[s] AND oldClef # Clef[score.sheet, value, time] THEN SetDirty[score, time, carry];
IF type = staves THEN {
Sheet.Reset[score];
IF Score.GetStyle[score, time] # value THEN SetDirty[score, 0, LAST[Time]];
IF Score.GetStyle[score, next] # oldStyle THEN SetDirty[score, 0, LAST[Time]]};
--
IF score.sheet[Sheet.FindLine[score.sheet, time]].y # y THEN SetDirty[score, begin, endTime]};
IF Event.Octava[s] THEN Sheet.Reset[score];
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
zone.FREE[@s];
count ← TRUE;
END;

ChangeSpelling: PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN
n: NotePTR;
score.sheet.dirty1 ← 1000000;
score.sheet.dirty2 ← -1;
SetBrush[score, black, invert];
WHILE BlueBug[] DO
IF (n ← Piece.NearestNote[score]) = NIL THEN LOOP;
Note.Draw[score, n];
Note.Draw[score, n];
ENDLOOP;
SetBrush[score, black, transparent];
IF n = NIL OR n.rest THEN RETURN;
IF ~Note.InVoice[n, score.sheet.voice] THEN RETURN;
SELECT object FROM
doubleFlat => Note.SetAccidental[score, n, doubleFlat];
flat => Note.SetAccidental[score, n, flat];
natural => Note.SetAccidental[score, n, natural];
inKey => Note.SetAccidental[score, n, inKey];
sharp => Note.SetAccidental[score, n, sharp];
doubleSharp => Note.SetAccidental[score, n, doubleSharp];
ENDCASE;
IF score.flash THEN {Interface.Flash[score]; RETURN};
IF n.spelled # inKey THEN n.show ← TRUE ELSE n.show ← FALSE;
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

Embellish: PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN
x, y: INTEGER;
n: NotePTR ← NIL;
score.sheet.dirty1 ← 1000000;
score.sheet.dirty2 ← -1;
SetBrush[score, black, invert];
WHILE BlueBug[] DO
[x, y] ← Sheet.ScreenPoint[score.sheet];
IF (n ← Piece.NearestNote[score, x, y]) = NIL THEN LOOP;
Note.Draw[score, n];
Note.Draw[score, n];
ENDLOOP;
SetBrush[score, black, transparent];
IF n = NIL OR n.rest THEN RETURN;
SELECT object FROM
trill
=> Note.SetEmbellishment[score, n, trill];
mordent1 => Note.SetEmbellishment[score, n, mordent1];
mordent2 => Note.SetEmbellishment[score, n, mordent2];
ENDCASE;
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

-- ******************************************************************
-- inserting objects
-- ******************************************************************

Insert: PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN OPEN CursorDefs;
ENABLE Piece.Overflow => IF old = score THEN score ← new;
tempCursor: Cursor ← GetCursorPattern[];
SetCursorPattern[ALL[0]];
SELECT object FROM
staves, measure, repeat1, repeat2, doubleMeasure, endMeasure => InsertMeasure[score, object];
bass, treble => InsertClefChange[score, object];
octava => {SetCursorPattern[tempCursor]; InsertOctava[score, object]};
doubleFlat, flat, natural, inKey, sharp, doubleSharp => ChangeSpelling[score, object];
trill, mordent1, mordent2 => Embellish[score, object];
note, rest => Interface.InsertNote[score, object];
ENDCASE => ERROR;
SetCursorPattern[tempCursor];
count ← TRUE;
END;

InsertMeasure: PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN
x, y: INTEGER;
s: EventPTR;
SELECT object FROM
IN [measure..endMeasure] => {
measure: MeasureType ← measure;
s ← zone.NEW[EventRec.measure];
Event.Measure[s].measure ← LOOPHOLE[LOOPHOLE[object, CARDINAL]
- LOOPHOLE[measure, CARDINAL]]};
staves => s ← zone.NEW[EventRec.staves];
ENDCASE => ERROR;
[x, y] ← Sheet.ScreenPoint[score.sheet];
[s.time] ← Sheet.NearestTime[score.sheet, x, y];
SetBrush[score, black, invert];
WHILE BlueBug[] DO
Event.Draw[score, s];
Event.Draw[score, s];
[x, y] ← Sheet.ScreenPoint[score.sheet];
[s.time] ← Sheet.NearestTime[score.sheet];
ENDLOOP;
IF YellowBug[] THEN RETURN;
IF object = staves THEN {
Event.Staves[s].value ← Score.GetStyle[score, s.time]};
score ← Piece.AddEvent[score, s];
SetDirty[score, s.time, s.time];
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

InsertClefChange: PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
old, new: Section;
sync: EventPTR ← NIL;
oldClef: INTEGER ← 10;
pitch, height: INTEGER;
oldTime, newTime: Time ← 0;
change: BOOLEAN ← FALSE;
oldStaff, newStaff: CARDINAL ← 10;
SELECT object FROM
bass => pitch ← 27;
treble => pitch ← 48;
ENDCASE => Error;
SetBrush[score, black, invert];
[newTime, height] ← Sheet.NearestTime[score.sheet];
newStaff ← Sheet.NearestStaff[score.sheet, newTime, height];
WHILE BlueBug[] DO
[newTime, height] ← Sheet.NearestTime[score.sheet];
newStaff ← Sheet.NearestStaff[score.sheet, newTime, height];
IF newTime = oldTime AND newStaff = oldStaff THEN LOOP;
IF oldStaff # 10 THEN Sheet.DrawClef[score.sheet, pitch, oldStaff, oldTime];
Sheet.DrawClef[score.sheet, pitch, newStaff, newTime];
oldTime ← newTime;
oldStaff ← newStaff;
ENDLOOP;
Sheet.DrawClef[score.sheet, pitch, newStaff, newTime];
IF YellowBug[] THEN RETURN;
old ← score.sheet[Sheet.FindLine[score.sheet, newTime]];
oldClef ← Clef[score.sheet, newStaff, newTime];
Score.SetClef[score, pitch, newStaff, newTime];
new ← score.sheet[Sheet.FindLine[score.sheet, newTime]];
IF score.flash THEN {Interface.Flash[score]; RETURN};
IF old # new THEN SetDirty[score, new.time, new.time];
-- how much of the score does this affect?
SetDirty[score, newTime, newTime];
IF pitch = oldClef THEN {Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; RETURN};
FOR i: CARDINAL IN [0..score.length) DO
IF score[i].time # newTime THEN LOOP;
IF ~Event.Clef[score[i]] OR Event.Staves[score[i]].value # newStaff THEN LOOP;
sync ← score[i];
EXIT; ENDLOOP;
IF sync # NIL THEN SetDirty[score, sync.time, GetCarry[score, sync]];
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

InsertOctava: PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
begin, end, oldEnd: Time ← -1;
staff, oldStaff, alternate: INTEGER ← 1;
pitch, height, oldHeight, octHeight, limit: INTEGER ← 0;
SetBrush[score, black, invert];
[begin, oldHeight] ← Sheet.NearestTime[score.sheet];
staff ← Sheet.NearestStaff[score.sheet, begin, oldHeight];
WHILE BlueBug[] DO
[end, height, staff] ← Slide[score, oldEnd, oldHeight, oldStaff];
IF end = oldEnd AND height = oldHeight THEN LOOP;
-- WE HAVE A MOVEMENT
IF oldEnd # -1 THEN Sheet.DrawOctava[score.sheet, pitch, oldStaff, octHeight, begin, oldEnd];
oldEnd ← end;
oldStaff ← staff;
oldHeight ← height;
octHeight ← height - Sheet.Height[score.sheet, end, , staff];
pitch ← (IF Sheet.NormalPitch[score.sheet, staff] = 27 THEN 15 ELSE 60);
Sheet.DrawOctava[score.sheet, pitch, staff, octHeight, begin, end];
ENDLOOP;
Sheet.DrawOctava[score.sheet, pitch, staff, octHeight, begin, end];
IF YellowBug[] THEN RETURN;
IF begin >= end THEN RETURN;
Score.SetOctava[score, pitch, staff, octHeight, begin, end];
IF score.flash THEN {Interface.Flash[score]; RETURN};
Score.Redraw[score, begin, end];
END;

Slide: PROCEDURE[score: ScorePTR, oldTime: Time, oldHeight: INTEGER, oldStaff: CARDINAL] RETURNS[time: Time, height: INTEGER, staff: CARDINAL] =
BEGIN
limit: INTEGER;
staves: StavesPTR;
staff ← oldStaff;
[time, height] ← Sheet.NearestTime[score.sheet];
IF oldTime # -1 AND ABS[time-oldTime] > 200 AND time > oldTime THEN
[time, height] ← Sheet.AlternateTime[score.sheet, time, height, -1];
IF oldTime # -1 AND ABS[time-oldTime] > 200 AND time < oldTime THEN
[time, height] ← Sheet.AlternateTime[score.sheet, time, height, 1];
IF time = oldTime AND height = oldHeight THEN RETURN;
-- should we move down to the next staff/line?
staves ← score.sheet[Sheet.FindSection[score.sheet, time]].staves;
staff ← Sheet.NextStaff[score.sheet, oldStaff, time];
limit ← staves.staff[staff].y+32;
IF staff = oldStaff THEN limit ← limit-32-staves.offset;
IF oldHeight <= limit THEN { -- we went below a lower limit
IF staff = oldStaff THEN { -- must be next line
[time, height] ← Sheet.AlternateTime[score.sheet, time, height, 1];
staff ← 0};
RETURN};
-- should we move up to the next staff/line?
staff ← Sheet.PriorStaff[score.sheet, oldStaff, time];
limit ← staves.staff[staff].y;
IF staff = oldStaff THEN limit ← limit+32+staves.offset;
IF height >= limit THEN { -- we went above an upper limit
IF staff = oldStaff THEN { -- must be last line
[time, height] ← Sheet.AlternateTime[score.sheet, time, height, -1];
staff ← staves.length-1};
RETURN};
staff ← oldStaff;
END;


-- ******************************************************************
-- moving things
-- ******************************************************************

MoveGraphical: PUBLIC PROCEDURE[score: ScorePTR, object: Interface.Object] =
BEGIN ENABLE Piece.Overflow => IF old = score THEN score ← new;
s: EventPTR;
p: LONG POINTER ← NIL;
obj: ObjectType;
IF object # none THEN BEGIN Insert[score, object]; RETURN; END;
SetBrush[score, black, invert];
WHILE BlueBug[] DO
[obj, p] ← Piece.NearestObject[score];
IF p = NIL THEN LOOP;
s ← LOOPHOLE[p];
IF score.sheet.voice # noVoice AND Event.Octava[s] OR Event.Clef[s] THEN LOOP;
SELECT obj FROM
measure => {
IF Event.Octava[s]
THEN MoveOctava[score, Event.Staves[s]]
ELSE MoveMeasure[score, s]};
leftBeam => MoveBeam[score, p];
rightBeam => ChangeTilt[score, p];
tie => MoveTie[score, p];
ENDCASE;
ENDLOOP;
IF p # NIL THEN count ← TRUE;
END;

MoveMeasure: PROCEDURE[score: ScorePTR, s: EventPTR] =
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
old: Time = s.time;
SetBrush[score, black, invert];
SetDirty[score, s.time, s.time];
WHILE BlueBug[] DO
Event.Draw[score, s];
s.time ← Sheet.NearestTime[score.sheet].time;
Event.Draw[score, s];
ENDLOOP;
Event.Draw[score, s];
SetDirty[score, s.time, s.time];
SELECT TRUE FROM
Event.Clef[s] => MoveClef[score, Event.Staves[s], old, s.time];
Event.Octava[s] => NULL;
s.type = staves => MoveStaves[score, Event.Staves[s], old, s.time];
s.type = keySignature => MoveKey[score, s, old, s.time];
ENDCASE => {Piece.RemoveEvent[score, s]; score ← Piece.AddEvent[score, s]};
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

MoveClef: PROCEDURE[score: ScorePTR, s: StavesPTR, old, new: Time] =
BEGIN
oldCarry: Time;
time: Time = s.time;
newClef, clef: INTEGER;
newClef ← Clef[score.sheet, s.value, s.time];
s.time ← old;
oldCarry ← GetCarry[score, s];
s.time ← new;
Piece.RemoveEvent[score, s];
score ← Piece.AddEvent[score, s];
ResetSheet[score, score.sheet.dirty1, score.sheet.dirty2];
clef ← Event.GetStaff[s, s.value].pitch;
IF clef # newClef THEN SetDirty[score, new, GetCarry[score, s]];
IF clef # Clef[score.sheet, s.value, old] THEN SetDirty[score, old, oldCarry];
END;

MoveStaves: PROCEDURE[score: ScorePTR, s: StavesPTR, old, new: Time] =
BEGIN
next: Time;
nextStyle, oldStyle, newStyle: INTEGER;
next ← score.sheet[Sheet.NextLine[score.sheet, Sheet.FindLine[score.sheet, MAX[old, new]]]].time;
nextStyle ← Score.GetStyle[score, next];
--
y ← score.sheet[Sheet.FindLine[score.sheet, old]].y;
oldStyle ← Score.GetStyle[score, old];
newStyle ← Score.GetStyle[score, new];
s.time ← new;
Piece.RemoveEvent[score, s];
s
core ← Piece.AddEvent[score, s];
Sheet.Reset[score];
IF nextStyle # Score.GetStyle[score, next] THEN SetDirty[score, 0, LAST[Time]];
IF oldStyle # Score.GetStyle[score, old] THEN SetDirty[score, 0, LAST[Time]];
IF newStyle # Score.GetStyle[score, new] THEN SetDirty[score, 0, LAST[Time]];
END;

MoveKey: PROCEDURE[score: ScorePTR, s: EventPTR, old, new: Time] =
BEGIN
oldCarry: Time;
newKey: INTEGER;
s.time ← old;
oldCarry ← GetCarry[score, s];
newKey ← Score.GetKey[score, new];
s.time ← new;
Piece.RemoveEvent[score, s];
score ← Piece.AddEvent[score, s];
ResetSheet[score, score.sheet.dirty1, score.sheet.dirty2];
IF new > old AND newKey # Event.KeySignature[s].key
THEN SetDirty[score, new, GetCarry[score, s]];
IF new < old AND Score.GetKey[score, old] # Event.KeySignature[s].key
THEN SetDirty[score, old, oldCarry];
END;

MoveOctava: PROCEDURE[score: ScorePTR, s: StavesPTR] =
BEGIN
staff: CARDINAL;
pitch, height: INTEGER;
octava1, octava2: StavesPTR;
octava1 ← (IF s.staves = octava1 THEN s ELSE Event.GetOctava[score, s]);
octava2 ← (IF s.staves = octava2 THEN s ELSE Event.GetOctava[score, s]);
IF octava1 = NIL THEN {Interface.Flash[score]; RETURN};
SetDirty[score, s.time, s.time];
SetBrush[score, black, invert];
staff ← octava1.value;
pitch ← s.staff[octava1.value].pitch;
WHILE BlueBug[] DO
Event.Draw[score, octava1];
[s.time, height, staff] ← Slide[score, s.time, height, staff];
s.height ← height - Sheet.Height[score.sheet, octava1.time, , octava1.value];
s.height ← Sheet.OctavaHeight[pitch, s.height];
Event.Draw[score, octava1];
ENDLOOP;
SetDirty[score, s.time, s.time];
IF octava1 = NIL OR octava2 = NIL OR octava1.time >= octava2.time THEN {
Piece.RemoveEvent[score, octava1, TRUE]; -- deletes octava2 as well
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
RETURN};
Piece.RemoveEvent[score, s];
score ← Piece.AddEvent[score, s]; -- Remove/Add keeps the score sorted
Sheet.Reset[score];
Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

MoveBeam: PROCEDURE[score: ScorePTR, b: BeamPTR] =
BEGIN
height, y: INTEGER ← 0;
SetBrush[score, b
lack, invert];
y ← mouse.y;
WHILE BlueBug[] DO
height ← b.height+(y-mouse.y);
IF height = b.height THEN LOOP;
[] ← Beam.Draw[score, b];
b.height ← height; y ← mouse.y;
Beam.SetStems[score.sheet, b];
[] ← Beam.Draw[score, b];
ENDLOOP;
Score.Redraw[score, b.sync1.time, b.sync2.time];
END;

ChangeTilt: PROCEDURE[score: ScorePTR, b: BeamPTR] =
BEGIN
height, oldHeight, y: INTEGER ← 0;
l: REAL ← 1;
SetBrush[score, black, invert];
oldHeight ← b.height + Real.FixI[b.tilt*(b.sync2.time-b.sync1.time)];
y ← mouse.y;
WHILE BlueBug[] DO
height ← oldHeight+(y-mouse.y);
IF height = oldHeight THEN LOOP;
[] ← Beam.Draw[score, b];
b.tilt ← l*(height-b.height)/(b.sync2.time-b.sync1.time);
oldHeight ← height; y ← mouse.y;
Beam.SetStems[score.sheet, b];
[] ← Beam.Draw[score, b];
ENDLOOP;
Score.Redraw[score, b.sync1.time, b.sync2.time];
END;

MoveTie: PROCEDURE[score: ScorePTR, n: NotePTR] =
BEGIN
y: INTEGER;
time: Time;
time ← (n.sync.time+n.tie.sync.time)/2;
y ← mouse.y;
SetBrush[score, black, invert];
WHILE BlueBug[] DO
IF y = mouse.y THEN LOOP;
Note.DrawTie[score, n];
n.tieHeight ← n.tieHeight+(y-mouse.y);
y ← mouse.y;
Note.DrawTie[score, n];
ENDLOOP;
Score.Redraw[score, n.tie.sync.time, n.sync.time];
END;

-- ******************************************************************
-- utility procedures
-- ******************************************************************

ResetSheet: PROCEDURE[score: ScorePTR, t1, t2: Time] =
BEGIN
line: CARDINAL;
old1, old2: Section;
new1, new2: Section;
-- get state before reset
line ← Sheet.FindLine[score.sheet, MIN[t1, t2]];
old1 ← score.sheet[line];
line ← Sheet.NextLine[score.sheet, line];
old2 ← score.sheet[line];
Sheet.Reset[score];
-- get state after reset
line ← Sheet.FindLine[score.sheet, MIN[t1, t2]];
new1 ← score.sheet[line];
line ← Sheet.NextLine[score.sheet, line];
new2 ← score.sheet[line];
-- compare before and after
IF old1 # new1 THEN SetDirty[score, new1.time, new1.time+15];
IF old2 # new2 THEN SetDirty[score, new2.time, new2.time+15];
IF old1.key # new1.key THEN SetDirty[score, 0, LAST[Time]];
IF old2.key # new2.key THEN SetDirty[score, 0, LAST[Time]];
END;

GetCarry: PROCEDURE[score: ScorePTR, s: EventPTR] RETURNS[Time] =
BEGIN
index: CARDINAL;
IF ~Event.Clef[s] AND s.type # keySignature THEN RETURN[s.time];
-- is this insertion/deletion a NOOP?
IF s.type = keySignature AND
Score.GetKey[score, s.time-1] = Event.KeySignature[s].key THEN RETURN[s.time];
index ← Event.GetScoreIndex[score, s];
IF Event.Clef[s] THEN
FOR i: CARDINAL IN (index..score.length] DO
staves: StavesPTR;
IF i = score.length THEN RETURN[LAST[Time]];
IF score[i].type # staves THEN LOOP;
staves ← Event.Staves[score[i]];
IF staves.staves = style AND ~Similar[Event.Staves[s], staves] THEN RETURN[score[i].time];
IF staves.staves = style THEN LOOP;
IF Event.GetStaff[staves, Event.Staves[score[i]].value].y
# Event.GetStaff[Event.Staves[s], Event.Staves[s].value].y THEN LOOP;
RETURN[score[i].time];
ENDLOOP;
IF s.type = keySignature THEN
FOR i: CARDINAL IN (index..score.length] DO
IF i = score.length THEN RETURN[LAST[Time]];
IF score[i].type # keySignature THEN LOOP;
RETURN[score[i].time];
ENDLOOP;
RETURN[s.time];
END;

Similar: PROCEDURE[oldS, newS: StavesPTR] RETURNS[BOOLEAN] =
BEGIN
n, o: BOOLEAN;
IF oldS.length # newS.length THEN RETURN[FALSE];
FOR i: CARDINAL IN [1..newS.length) DO
n ← newS.staff[i].y = newS.staff[i-1].y;
o ← oldS.staff[i].y = oldS.staff[i-1].y;
IF n # o THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
END;

Clef: PROCEDURE[sheet: SheetPTR, staff: CARDINAL, time: Time] RETURNS[INTEGER] =
BEGIN
staves: StavesPTR = sheet[Sheet.FindSection[sheet, time]].staves;
RETURN[staves.staff[staff].pitch];
END;

Flash: PUBLIC PROCEDURE[score: ScorePTR] =
BEGIN
SetBrush[score, black, invert];
Graphics.DrawBox[score.sheet.context, [0, 0, 1024, 808]];
Wait[1];
Graphics.DrawBox[score.sheet.context, [0, 0, 1024, 808]];
score.flash ← FALSE;
END;

Wait: PUBLIC ENTRY PROCEDURE[ticks: CARDINAL] =
BEGIN
FOR i: CARDINAL IN [0..ticks) DO
WAIT timeout;
ENDLOOP;
END;

Process.SetTimeout[@timeout, 1];

END..