SheetImplA.mesa
Copyright (C) 1983, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
Last Edited by: Maxwell, November 22, 1983 10:21 am
Last Edited by: Doug Wyatt, June 18, 1984 12:40:29 pm PDT
DIRECTORY
Beam USING [SetStems],
Convert USING [RopeFromInt],
Event USING [GetScoreIndex, Invisible, SetStave],
Imager USING [ScaleT],
MusicDefs,
Score,
Sheet,
Utility;
SheetImplA: CEDAR PROGRAM
IMPORTS Beam, Convert, Event, Imager, MusicDefs, Score, Sheet, Utility
EXPORTS Sheet
= BEGIN OPEN MusicDefs, Score, Sheet;
FindStaves: PUBLIC PROC[sheet: SheetPTR, t: Time] RETURNS[StavesPTR] = {
i: NAT ~ FindSection[sheet, t];
RETURN[sheet.section[i].staves]
};
Last: PUBLIC PROC[sheet: SheetPTR, l: CARDINAL] RETURNS[CARDINAL] = {
FOR i: CARDINAL IN [l..sheet.length-1) DO
IF sheet.section[i].time # sheet.section[i+1].time THEN RETURN[i];
ENDLOOP;
RETURN[sheet.length-1];
};
LineNumber: PUBLIC PROC[sheet: SheetPTR, l: CARDINAL] RETURNS[n: CARDINAL ← 1] = {
FOR i: CARDINAL IN (0..l] DO
IF sheet.section[i].y # sheet.section[i-1].y THEN n ← n+1;
ENDLOOP;
};
NextLine: PUBLIC PROC[sheet: SheetPTR, l: CARDINAL] RETURNS[CARDINAL] = {
FOR i: CARDINAL IN (l..sheet.length) DO
IF sheet.section[i].y = sheet.section[l].y THEN LOOP;
RETURN[Last[sheet, i]];
ENDLOOP;
RETURN[sheet.length-1];
};
NextStaff: PUBLIC PROC[sheet: SheetPTR, s: CARDINAL, t: Time] RETURNS[CARDINAL] = {
staves: StavesPTR = FindStaves[sheet, t];
FOR i: CARDINAL IN (s..staves.length) DO
IF staves.staff[i].y # staves.staff[s].y THEN RETURN[i];
ENDLOOP;
RETURN[s];
};
NormalPitch: PUBLIC PROC[sheet: SheetPTR, staff: CARDINAL] RETURNS[INTEGER] = {
RETURN[IF staff IN [0..1] THEN 48 ELSE 27]
};
PriorLine: PUBLIC PROC[sheet: SheetPTR, l: CARDINAL] RETURNS[CARDINAL] = {
FOR i: CARDINAL DECREASING IN [0..l) DO
IF sheet.section[i].y # sheet.section[l].y THEN {l ← i; EXIT};
ENDLOOP;
FOR i: CARDINAL DECREASING IN [0..l) DO
IF sheet.section[i].y # sheet.section[l].y THEN RETURN[Last[sheet, i+1]];
ENDLOOP;
RETURN[Last[sheet, 0]];
};
PriorStaff: PUBLIC PROC[sheet: SheetPTR, s: CARDINAL, t: Time] RETURNS[CARDINAL] = {
staves: StavesPTR = FindStaves[sheet, t];
FOR i: CARDINAL DECREASING IN [0..s) DO
IF staves.staff[i].y # staves.staff[s].y THEN RETURN[i];
ENDLOOP;
RETURN[s];
};
OctavaHeight: PUBLIC PROC[pitch, height: INTEGER] RETURNS[INTEGER] = {
IF pitch = 60 AND height < 40 THEN RETURN[40];
IF pitch = 60 AND height > 90 THEN RETURN[90];
IF pitch = 15 AND height < -40 THEN RETURN[-40];
IF pitch = 15 AND height > -8 THEN RETURN[-8];
RETURN[height];
};
****************************************************************************
the sheet consists of an array of Sections
****************************************************************************
New: PUBLIC PROC[length: CARDINAL] RETURNS[sheet: SheetPTR] = {
sheet ← NEW[SheetRec[length] ← [section: ]];
sheet.length ← length;
};
Free: PUBLIC PROC[sheet: SheetPTR] = { };
Reset: PUBLIC PROC[score: ScorePTR] = {
sync: EventPTR ← NIL;
staves: StavesPTR ← NIL;
page, pitch: INTEGER ← 2;
sheet: SheetPTR ← score.sheet;
v, j, last, next: NAT ← 0;
time, eol, priorEol, eventTime: Time ← 0;
top, height, x, sc, priorHeight: INTEGER ← 0;
Score.BuildCache[score, 0];-- ???
IF sheet.scale = 2 THEN sc ← 3 ELSE sc ← 2*sheet.scale;
[staves, last, next] ← NextStyle[score, NIL, 0, 0, 100];
IF staves = NIL THEN RETURN;
FOR i: NAT IN [0..score.length] DO
IF i = score.length
THEN eventTime ← LONG[sheet.width]*sheet.length
ELSE {IF score.event[i].type # staves THEN LOOP; eventTime ← score.event[i].time};
WHILE time < eventTime OR eventTime = eol DO -- fill in all of the sections between
IF j = sheet.length THEN EXIT;
sheet[j].page ← 0; sheet[j].time ← time;
IF j # 0 AND sheet[j].time = sheet[j-1].time
THEN sheet[j].key ← sheet[j-1].key
ELSE sheet[j].key ← Key[score, time, eol];
IF time < eol THEN x ← x+(time-sheet[j-1].time);
IF time >= eol THEN { -- new line
height ← height-priorHeight;
x ← MAX[8, ABS[8*(sheet[j].key)]];
priorEol ← eol;
eol ← priorEol+sheet.width-x;
[staves, last, next] ← NextStyle[score, staves, last, next, eol-20];
priorHeight ← -staves.staff[staves.length-1].y+staves.offset;
IF top-height > (650*sc)/2 THEN { -- new page
top ← height; sheet[j].page ← page; page ← page+1}};
sheet[j].x ← x;
sheet[j].y ← height;
sheet[j].staves ← staves;
time ← MIN[eol, eventTime];
j ← j + 1;
ENDLOOP;
IF i = score.length THEN EXIT;
WITH score.event[i] SELECT FROM
event: StavesPTR => staves ← event;
ENDCASE => ERROR;
IF staves.staves = style THEN LOOP;
Event.SetStave[score, staves, staves];
IF staves.staves # clef THEN LOOP;
IF Event.Invisible[score, i, priorEol] THEN time ← priorEol;
ENDLOOP;
IF sheet.scale = 2 THEN Paginate[sheet];
Sheet.SetBegin[sheet, sheet.begin];
Score.BuildCache[score, sheet.begin];
FOR i: NAT IN [0..score.beamHeap.length) DO
IF score.beamHeap.beam[i].sync2.time < score.sheet.dirty1 THEN LOOP;
IF score.beamHeap.beam[i].sync1.time > score.sheet.dirty2 THEN LOOP;
Beam.SetStems[sheet, score.beamHeap.beam[i]];
ENDLOOP;
};
NextStyle: PROC[score: ScorePTR, old: StavesPTR, last, next: NAT, eol: Time]
RETURNS[new: StavesPTR, newLast, newNext: NAT] = {
new ← old; newLast ← last; newNext ← next;
FOR i: NAT IN[next..score.length) DO
event: EventPTR ~ score.event[i];
IF event.time >= eol+40 THEN EXIT;
WITH event SELECT FROM
temp: StavesPTR => IF temp.staves=style THEN {
newNext ← i;
IF event.time >= eol THEN EXIT;
IF temp = old THEN LOOP;
IF new#NIL AND i#next AND
new.staff[new.length-1].y < temp.staff[temp.length-1].y THEN LOOP;
new ← temp;
newLast ← i;
};
ENDCASE;
ENDLOOP;
IF newLast=last AND old#NIL THEN RETURN[old, newLast, newNext];
Event.SetStave[score, old, new];
RETURN[new, newLast, newNext];
};
Key: PROC[score: ScorePTR, time, eol: Time] RETURNS[key: INTEGER ← 0] = {
keySig: KeySignaturePTR ← NIL;
key ← Score.GetKey[score, time];
FOR i: NAT IN [Event.GetScoreIndex[score, score.cache.key1]..score.length) DO
event: EventPTR ~ score.event[i];
IF event.time >= time+score.sheet.width THEN EXIT;
WITH event SELECT FROM
k: KeySignaturePTR => { keySig ← k; IF time = eol THEN EXIT };
ENDCASE;
ENDLOOP;
IF keySig = NIL THEN RETURN;
IF ~Event.Invisible[score, Event.GetScoreIndex[score, keySig], eol] THEN RETURN;
RETURN[keySig.key];
};
limit: INTEGER ← 1050;
Paginate: PROC[sheet: SheetPTR] = {
min: INTEGER = 75;
start: INTEGER = 40;
page: NAT ← 2;
height: INTEGER ← -start;
line, last: NAT ← 1;
count, first: NAT ← 0;
FOR line ← 0, Sheet.NextLine[sheet, line] WHILE line # last DO
height ← height-sheet[line].staves.staff[3].y+min;
count ← count+1;
IF height < limit THEN {last ← line; LOOP};
IF count # 1 THEN {-- subtract out the last line
count ← count-1;
height ← height+sheet[line].staves.staff[3].y-min};
layout the page
LayoutPage[sheet, count, first, line, limit+count*min-start-height];
sheet[line].page ← page;
page ← page+1;
set up for next page
first ← line; line ← last;
count ← 0;
height ← -start;
ENDLOOP;
};
LayoutPage: PROC[sheet: SheetPTR, lines, first, last, space: NAT] = {
oldHeight, height: INTEGER ← sheet[first].y;
layout this page
FOR i: NAT IN (first..sheet.length) DO
IF sheet[i].y # oldHeight THEN {-- new line
IF i > last THEN EXIT;
oldHeight ← sheet[i].y;
height ← sheet[i-1].y+sheet[i-1].staves.staff[3].y;
height ← height-space/lines};
sheet[i].y ← height;
sheet[i].page ← 0;
ENDLOOP;
};
FindLine: PUBLIC PROC[sheet: SheetPTR, t: Time] RETURNS[l: INTEGER] = {
start: NAT ← 0;
FOR i: NAT IN [0..sheet.length-1) DO
IF sheet[i].time # sheet[i+1].time THEN {start ← i; EXIT};
ENDLOOP;
l ← start;
FOR i: NAT ← start, NextLine[sheet, i] DO
IF sheet[i].time <= t THEN l ← i ELSE RETURN;
IF i = sheet.length-1 THEN RETURN;
ENDLOOP;
};
FindSection: PUBLIC PROC[sheet: SheetPTR, t: Time] RETURNS[INTEGER] = {
IF t >= sheet[sheet.current].time THEN {
IF sheet.current = sheet.length-1 THEN RETURN[sheet.current];
WHILE t >= sheet[sheet.current+1].time DO
sheet.current ← sheet.current+1;
IF sheet.current = sheet.length-1 THEN RETURN[sheet.current];
ENDLOOP;
RETURN[sheet.current];
}
ELSE {
WHILE sheet.current # 0 AND t < sheet[sheet.current].time DO
sheet.current ← sheet.current -1;
ENDLOOP;
RETURN[sheet.current];
};
};
****************************************************************************
drawing the sheet
****************************************************************************
white: CARDINAL ~ 0;
HiLite: PUBLIC PROC[sheet: SheetPTR, texture: ColorType, t1, t2: Time] = {
x1, y1, x2, y2, lastY: INTEGER;
offset: INTEGERIF texture = white THEN 58 ELSE 40;
section1, section2, i, j: CARDINAL;
IF t1 >= t2 THEN RETURN;
EnableClipping[sheet.context];
Utility.SetBrush[sheet.context, texture, IF texture = white THEN opaque ELSE invert];
i ← Sheet.FindLine[sheet, t1];
j ← Sheet.PriorLine[sheet, i];
IF i = j THEN lastY ← 100 ELSE lastY ← sheet[j].y;
j ← section1 ← FindSection[sheet, t1];
section2 ← FindSection[sheet, t2];
FOR i IN [section1..section2] DO
IF sheet[i].time > sheet.endTime THEN EXIT;
IF i # section2 AND sheet[i].time = sheet[i+1].time THEN LOOP;
IF texture # white AND i # section2 AND sheet[i].y = sheet[i+1].y THEN LOOP;
offset ← sheet[i].staves.offset;
IF texture = white THEN offset ← offset/2 ELSE offset ← offset/4;
[x1, y1] ← Sheet.Map[sheet, MAX[t1, sheet[j].time], , 0];
[x2, y2] ← Sheet.Map[sheet, MIN[t2, sheet[i+1].time-1], , sheet[i].staves.length-1];
y1 ← y1+offset+34; y2 ← y2-offset;
IF x2 < sheet.width-1 OR texture = white THEN x2 ← x2+1 ELSE x2 ← x2+16;
IF texture = white AND sheet[i].y # lastY AND x1 = sheet[i].x THEN x1 ← -25;
lastY ← sheet[i].y;
IF texture = white THEN Utility.SetBrush[sheet.context, white, opaque];
Utility.DrawBox[sheet.context, [x1, y1, x2, y2]];
IF texture = white THEN DrawSection[sheet, sheet[i], x1, x2];
j ← i+1;
ENDLOOP;
EnableClipping[sheet.context];
[] ← Graphics.SetPaintMode[sheet.context, transparent];
};
Draw: PUBLIC PROC[sheet: SheetPTR] = {
x: INTEGER;
j: CARDINAL;
staves: StavesPTR = sheet[0].staves;
sheetHeight: INTEGER = staves.staff[staves.length-1].y;
IF sheet.begin = 0 THEN Utility.DrawLine[sheet.context, -23, 0, -23, sheetHeight];
sheet.endTime ← sheet.begin;
IF sheet.scale = 2 THEN x ← 3 ELSE x ← 2*sheet.scale;
FOR j ← Sheet.FindLine[sheet, sheet.begin], Sheet.NextLine[sheet, j] DO
IF sheet[j].y-sheet.top < (-650*x)/2 THEN {sheet.endTime ← sheet[j].time; EXIT};
DrawSection[sheet, sheet[j], -25, sheet.width];
ENDLOOP;
};
DrawSection: PROC[sheet: SheetPTR, l: Section, start, length: INTEGER] = {
sheetHeight: INTEGER ← l.staves.staff[l.staves.length-1].y;
i: CARDINAL;
y: INTEGER ← l.y-sheet.top;
context: Utility.Context ~ sheet.context;
oldStaff ← [0, 0];
Utility.SetColor[context, black];
IF start = -25 THEN Utility.DrawLine[context, start, y, start, y+sheetHeight];
IF start = -25 AND sheet.accidental THEN Sheet.DrawKey[sheet, l.key, 0, l.time];
IF l.page > 1 THEN {
Utility.SetCP[context, sheet.width+10, y+30];
Utility.SetFont[context, text, 12];
Utility.DrawString[context, Convert.RopeFromInt[l.page]];
Utility.SetFont[context, music, 8];
};
IF sheet.scale > 3 THEN {
Graphics.SetLineWidth[context, 8];
IF length = sheet.width THEN Utility.DrawLine[context, length, y, length, y+sheetHeight];
IF NOT sheet.notehead THEN Utility.SetColor[context, grey]; 
Utility.DrawLine[context, start, y, length, y];
Utility.DrawLine[context, start, y+sheetHeight, length, y+sheetHeight];
Utility.SetColor[context, black];
Graphics.SetLineWidth[context, 4];
}
ELSE FOR i IN [0..l.staves.length) DO
DrawStaff[sheet, l.staves.staff[i], y, start, length]
ENDLOOP;
};
DrawKey: PUBLIC PROC[sheet: SheetPTR, key, oldKey: INTEGER, time: Time] = {
n: INTEGER = 8;
oldStaff: Staff ← [0, 0];
x, y, offset: INTEGER;
context: Utility.Context ← sheet.context;
l: CARDINAL ← Sheet.FindLine[sheet, time];
staves: StavesPTR ← sheet[l].staves;
IF NOT sheet.accidental THEN RETURN;
IF time IN [sheet[l].time-10..sheet[l].time) THEN RETURN;
IF time IN (sheet[l].time..sheet[l].time+15] THEN time ← sheet[l].time;
x ← Sheet.MapHeight[sheet, time, 0].x;
Utility.SetFont[context, music, 8];
FOR i: NAT IN [0..staves.length) DO
IF staves.staff[i] = oldStaff THEN LOOP;
oldStaff ← staves.staff[i];
offset ← Sheet.Height[sheet, time, oldStaff.pitch, i];
IF Mod[staves.staff[i].pitch, 12] = 3 THEN offset ← offset - 8;
[x, y] ← Sheet.MapHeight[sheet, time, offset];
IF sheet[l].time = time THEN x ← 3;
IF key > 0 THEN SlapDown[context, x , 32+y, sharp];
IF key > 1 THEN SlapDown[context, x+ n, 20+y, sharp];
IF key > 2 THEN SlapDown[context, x+2*n, 36+y, sharp];
IF key > 3 THEN SlapDown[context, x+3*n, 24+y, sharp];
IF key > 4 THEN SlapDown[context, x+4*n, 12+y, sharp];
IF key > 5 THEN SlapDown[context, x+5*n, 28+y, sharp];
IF key > 6 THEN SlapDown[context, x+6*n, 16+y, sharp];
IF key < 0 THEN SlapDown[context, x , 16+y, flat];
IF key < -1 THEN SlapDown[context, x+ n, 28+y, flat];
IF key < -2 THEN SlapDown[context, x+2*n, 12+y, flat];
IF key < -3 THEN SlapDown[context, x+3*n, 24+y, flat];
IF key < -4 THEN SlapDown[context, x+4*n, 8+y, flat];
IF key < -5 THEN SlapDown[context, x+5*n, 20+y, flat];
IF key < -6 THEN SlapDown[context, x+6*n, 4+y, flat];
ENDLOOP;
};
SlapDown: PROC[context: Utility.Context, x, y: INTEGER, a: Accidental] = {
Utility.SetCP[context, x, y];
SELECT a FROM
flat => Utility.DrawChar[context, 111C];
natural => Utility.DrawChar[context, 112C];
sharp => Utility.DrawChar[context, 113C];
ENDCASE;
};
DrawStaff: PROC[sheet: SheetPTR, s: Staff, y, start, length: INTEGER] = {
w: REAL ← 1;
staffY: INTEGER;
context: Utility.Context ← sheet.context;
IF s = oldStaff THEN RETURN;
oldStaff ← s;
y ← y + s.y;
Utility.SetColor[context, IF sheet.notehead THEN black ELSE grey];
IF sheet.printing THEN w ← w/2;
staffY ← y;
FOR i: NAT IN [0..5) DO
Utility.DrawBox[context, [start, staffY-w, length, staffY+w]];
staffY ← staffY + 8;
ENDLOOP;
Utility.SetColor[context, black];
IF start < 0 THEN [] ← Graphics.SetPaintMode[context, transparent];
IF (s.pitch = 27 OR s.pitch = 15) AND start < 0 THEN {
Utility.SetCP[context, -21, y+16];
Utility.DrawChar[context, 121C]};
IF (s.pitch = 48 OR s.pitch = 60) AND start < 0 THEN {
Utility.SetCP[context, -21, y+16];
Utility.DrawChar[context, 120C]};
Graphics.SetStipple[context, texture]; -- restore previous color
};
oldStaff: Staff;
d: CARDINAL = 8;
DrawClef: PUBLIC PROC[sheet: SheetPTR, pitch, staff: INTEGER, time: Time] = {
x, y: INTEGER;
l: CARDINAL ← Sheet.FindLine[sheet, time];
IF time IN (sheet.section[l].time..sheet.section[l].time+15] THEN time ← sheet.section[l].time;
[x, y] ← Sheet.Map[sheet, time, , staff];
IF sheet.section[l].time = time THEN x ← -21;
Utility.SetCP[sheet.context, x, y+16];
IF sheet.voice # noVoice THEN Utility.SetColor[sheet.context, light];
SELECT TRUE FROM
x = -21 AND pitch = 27 => Utility.DrawChar[sheet.context, 121C];
x = -21 AND pitch = 48 => Utility.DrawChar[sheet.context, 120C];
pitch = 27 => Utility.DrawChar[sheet.context, 131C];
pitch = 48 => Utility.DrawChar[sheet.context, 130C];
ENDCASE;
};
DrawOctava: PUBLIC PROC[sheet: SheetPTR, pitch, staff, height: INTEGER, t1, t2: Time] = {
x1, x2, y: INTEGER ← 0;
section1, section2: CARDINAL;
IF t1 > t2 THEN RETURN;
IF pitch # 60 AND pitch # 15 THEN RETURN;
height ← Sheet.OctavaHeight[pitch, height];
Utility.SetFont[sheet.context, text, 12];
section1 ← Sheet.FindSection[sheet, t1];
section2 ← Sheet.FindSection[sheet, t2];
IF sheet.voice # noVoice THEN Utility.SetColor[sheet.context, light];
FOR i: CARDINAL IN [section1..section2] DO
IF i+1 = sheet.length THEN EXIT;
IF sheet.section[i].time < sheet.begin THEN LOOP;
IF sheet.section[i].time >= sheet.endTime THEN EXIT;
IF sheet.section[i].time = sheet.section[i+1].time THEN LOOP;
[x1, ] ← Sheet.Map[sheet, MAX[t1, sheet.section[i].time], , staff];
[x2, y] ← Sheet.Map[sheet, MIN[t2, sheet.section[i+1].time-1], , staff];
IF x1 > sheet.width-15 AND i = section1 THEN LOOP;
x2 ← x2+1; y ← y+height;
IF x1 < 10 OR i = section1 THEN {
Utility.SetCP[sheet.context, x1, y-8];
Utility.DrawChar[sheet.context, '8];
x1 ← x1+7; };
IF x1 < x2 THEN DrawDottedLine[sheet, x1, y, x2, y];
IF i = section2 THEN Utility.DrawLine[sheet.context, x2, y, x2, y-(IF height > 0 THEN 7 ELSE -7)];
ENDLOOP;
Utility.SetFont[sheet.context, music, 8];
};
DrawDottedLine: PROC[sheet: SheetPTR, x1, y, x2, y2: INTEGER] = {
IF sheet.printing THEN {
FOR x: INTEGER ← x1, x+5 WHILE x < x2 DO
Utility.DrawLine[sheet.context, x, y, MIN[x+3, x2], y];
ENDLOOP;
}
ELSE {
Utility.SetColor[sheet.context, 146314B];
Utility.DrawLine[sheet.context, x1, y, x2, y];
Utility.SetColor[sheet.context, black];
};
};
****************************************************************************
changing the view on the sheet
****************************************************************************
top: PUBLIC INTEGER ← 0;
SetBegin: PUBLIC PROC[sheet: SheetPTR, now: Time] = {
l: Section ← sheet[Sheet.FindLine[sheet, now]];
sheet.begin ← l.time;
sheet.top ← l.y;
};
Scroll: PUBLIC PROC[sheet: SheetPTR, lines: INTEGER] = {
section: CARDINAL ← Sheet.FindLine[sheet, sheet.begin];
IF lines < 0
THEN FOR i: CARDINAL IN [0..ABS[lines]) DO
IF section = 0 THEN EXIT;
section ← Sheet.PriorLine[sheet, section];
ENDLOOP
ELSE FOR i: CARDINAL IN [0..ABS[lines]) DO
section ← Sheet.NextLine[sheet, section];
IF section = sheet.length THEN RETURN;
ENDLOOP;
Sheet.SetBegin[sheet, sheet[section].time];
sheet.dirty1 ← 0;
sheet.dirty2 ← LAST[Time];
};
Scale: PUBLIC PROC[score: ScorePTR, newScale: INTEGER] = {
one: REAL = 1;
two: REAL = 2;
sheet: SheetPTR ← score.sheet;
context: Utility.Context ← sheet.context;
IF sheet.scale = newScale THEN RETURN;
reset to 1
SELECT sheet.scale FROM
2 => Imager.ScaleT[context, 3/two];
4 => Imager.ScaleT[context, 4];
ENDCASE;
sheet.width ← 550;
now move to the desired scale
SELECT newScale FROM
1 => {sheet.scale ← 1;
sheet.width ← sheet.width};
Graphics.SetLineWidth[context, 1]};
2 => {sheet.scale ← 2; -- actually 3/2
sheet.width ← (3*sheet.width)/2;
Imager.ScaleT[context, two/3]};
Graphics.SetLineWidth[context, 2]};
4 => {sheet.scale ← 4;
sheet.width ← 4*sheet.width;
Imager.ScaleT[context, one/4]};
Graphics.SetLineWidth[context, 4]};
ENDCASE;
Sheet.Reset[score];
Sheet.SetBegin[score, begin];
sheet.dirty1 ← 0;
sheet.dirty2 ← LAST[Time];
};
END.