--Author: John Maxwell
--last modified: January 16, 1982 11:09 AM

DIRECTORY
CursorDefs,
Graphics USING [DisplayChar, DisplayContext, DisplayString, Map, MoveTo, ScreenToUser, SetFont, SetPaint, SetTexture, Vec],
Heuristic USING [MakeBeams,MakeChords,MakeSyncs, SetNoteValues],
Interface USING [count, DeleteGraphical, Flash, MoveNote, MoveGraphical, Object, Wait],
IODefs USING [ReadChar,ReadDecimal,ReadID,Rubout],
MusicDefs,
Piece USING [AddSync, CleanUpNotes, Delete, Insert, Merge, NearestNote, Replace],
Real USING [Fix, FixI],
Score USING [Draw, FileIn, FileOut, Look, Print, Redraw, SetKey, SetMetrenome, SetTimeSignature, ShowLogical, StartListening, StartPlaying, StopListening, StopPlaying, Test],
Screen USING [CommandProcs, DisplayMessage, screen],
Selection, -- USING everything
Sheet USING [FindLine, Initialize, LineNumber, Map, NearestTime, Reset, ScreenPoint, Scroll, SetBegin, SetStyle],
StreamDefs USING [GetDefaultKey,KeyboardHandle],
StringDefs USING [AppendString,UpperCase],
Utility, -- USING everything
Voice USING [Check, Set];


InterfaceImplA: MONITOR RETURNS[POINTER TO Screen.CommandProcs]
IMPORTS Graphics, Heuristic, Interface, IODefs, MusicDefs, Piece, Real, Score, Screen, Selection, Sheet, StreamDefs, StringDefs, Utility, Voice =
BEGIN
OPEN Graphics, MusicDefs;
keyboard:StreamDefs.KeyboardHandle;

Error:SIGNAL;
test:BOOLEAN←FALSE;

commands:Screen.CommandProcs ← [Play, Listen, HandleRed, HandleYellow, HandleBlue, HandleKeyboard, Scroll, Thumb, Score.Draw, Score.FileIn, FileOut, Hardcopy, Initialize, 0];

--******************************************************************
--Initialization
--******************************************************************

Initialize:PROCEDURE[dc:Graphics.DisplayContext] =
BEGIN
keyboard ← StreamDefs.GetDefaultKey[];
Utility.InitStorage[];
Sheet.Initialize[dc];
END;

--******************************************************************
--
Command parser
--******************************************************************

HandleKeyboard:PROCEDURE =
BEGIN
ClearDirty[];
Do[IODefs.ReadChar[]];
IF test AND Score.Test[] THEN Error; --
you may proceed, but you cannot file out
END;

HandleBlue:PROCEDURE =
BEGIN
OPEN Interface;
ClearDirty[];
count←FALSE;
SELECT TRUE FROM
Control[] => {HandleMenu[]; count←FALSE};
Shift[] => {Selection.Clear[];
WHILE BlueBug[] DO Interface.MoveNote[Piece.NearestNote[]]; ENDLOOP};
ENDCASE => Interface.MoveGraphical[defaultObject];
IF test AND Score.Test[] THEN Error; --
you may proceed, but you cannot file out
IF count THEN commands.count←commands.count+1;
END;

HandleYellow:PROCEDURE = -- used to delete objects or unselect notes
BEGIN
OPEN CursorDefs, Interface;
temp:Cursor ← cursor↑;
cursor↑ ← textCursor;
ClearDirty[];
count←FALSE;
SELECT TRUE FROM
scale>3 => ChangeLook[’O];
Shift[] AND Control[] => NULL;
Control[] => NULL;
ENDCASE => Interface.DeleteGraphical[];
cursor↑ ← temp;
WHILE YellowBug[] DO NULL; ENDLOOP;
IF test AND Score.Test[] THEN Error; --
you may proceed, but you cannot file out
IF count THEN commands.count←commands.count+1;
END;

HandleRed:PROCEDURE = -- Only used to change the selection
BEGIN
OPEN CursorDefs;
x,y:INTEGER;
i,click:CARDINAL←0;
temp:Cursor ← cursor↑;
cursor↑ ← textCursor;
ClearDirty[];
WHILE RedBug[] DO
[x,y] ← Sheet.ScreenPoint[];
SELECT TRUE FROM
Control[] => {
FOR i IN [0..5) DO Interface.Wait[1];
IF ~RedBug[] THEN {click←1; EXIT};
ENDLOOP;
IF click=1 THEN FOR i IN [0..5) DO Interface.Wait[1];
IF RedBug[] THEN {click←2; EXIT};
ENDLOOP;
SELECT click FROM
0 => ChooseLine[Sheet.NearestTime[x,y].time];
1 => EXIT;
2 => ExtendLine[];
ENDCASE => ERROR};
Shift[] => Selection.AddNote[Piece.NearestNote[x,y]];
ENDCASE => {IF command THEN Selection.Clear[];
Selection.AddNote[Piece.NearestNote[x,y]]};
command←FALSE;
ENDLOOP;
cursor↑ ← temp;
IF test AND Score.Test[] THEN Error; --
you may proceed, but you cannot file out
END;

ClearDirty:PROCEDURE = INLINE {min ← 100000; max ← -1};

ExtendLine:PROCEDURE =
BEGIN
time:Time;
grey:BOOLEAN ← FALSE;
switch:BOOLEAN ← TRUE;
beginning:BOOLEAN ← FALSE;
temp1,temp2,gTemp1,gTemp2:Time;
temp1 ← select1;
temp2 ← select2;
gTemp1 ← greySelect1;
gTemp2 ← greySelect2;
WHILE RedBug[] DO
-- is the user switching between colors?
IF Shift[] AND NOT grey THEN
BEGIN
grey ← TRUE;
switch ← TRUE;
Selection.AddLine[temp1,temp2];
END;
IF NOT Shift[] AND grey THEN
BEGIN
grey ← FALSE;
switch ← TRUE;
Selection.AddGreyLine[gTemp1,gTemp2];
END;
[time,] ← Sheet.NearestTime[];
-- if the user is switching, then which end does he want?
IF switch THEN IF grey
THEN beginning ← Beginning[time,greySelect1,greySelect2]
ELSE beginning ← Beginning[time,select1,select2];
switch ← FALSE;
-- draw the appropriate line
IF grey
THEN IF beginning
THEN Selection.AddGreyLine[time,greySelect2]
ELSE Selection.AddGreyLine[greySelect1,time]
ELSE IF beginning
THEN Selection.AddLine[time,select2]
ELSE Selection.AddLine[select1,time]
ENDLOOP;
END;

Beginning:PROCEDURE[time,begin,end:Time] RETURNS[BOOLEAN] = INLINE
BEGIN
IF time<=begin THEN RETURN[TRUE];
IF time>=end THEN RETURN[FALSE];
RETURN[ABS[time-begin]<ABS[time-end]];
END;

ChooseLine:PROCEDURE[time:Time] =
BEGIN
time2:Time;
temp1,temp2,gTemp1,gTemp2:Time;
grey:BOOLEAN ← FALSE;
temp1 ← select1;
temp2 ← select2;
gTemp1 ← greySelect1;
gTemp2 ← greySelect2;
WHILE RedBug[] DO
IF Shift[] AND NOT grey THEN
BEGIN
grey ← TRUE;
Selection.AddLine[temp1,temp2];
END;
IF NOT Shift[] AND grey THEN
BEGIN
grey ← FALSE;
Selection.AddGreyLine[gTemp1,gTemp2];
END;
[time2,] ← Sheet.NearestTime[];
IF grey THEN Selection.AddGreyLine[time,time2] ELSE Selection.AddLine[time,time2];
ENDLOOP;
END;

--************************************************************************
--
commands from the keyboard
--************************************************************************

Do:PROCEDURE[c:CHARACTER] =
BEGIN
a,b:INTEGER;
SELECT c FROM
’b => IF lineSelect
THEN Heuristic.MakeBeams[select1,select2]
ELSE Selection.MakeBeam[];
’B => Selection.ClearBeam[];
002C=> Selection.MakeBeamOfBeams[];
’c => IF lineSelect THEN
Heuristic.MakeChords[select1,select2] ELSE
Selection.MakeChord[];
’C => Selection.ClearChord[];
’d => Selection.Delete[];
’e => {Selection.AddLine[0,EndOfScore[]]; RETURN};
-- ’f => {ReadFileName[".logical"]; Score.FileInOld[fileName]};
-- ’F => {ReadFileName[".logical"]; Score.FileOutOld[fileName]};
-- ’g => GuessNoteValues[select1,select2];
’g => Selection.SetGrace[TRUE];
’G => Selection.SetGrace[FALSE];
007C => Heuristic.SetNoteValues[select1,select2];
’k => Score.SetKey[select1,select2,ReadKey[]];
’l => {ChangeLook[IODefs.ReadChar[]]; RETURN};
014C => {Score.ShowLogical[0,scoreLength-1]; Sheet.Reset[]}; -- debugging aid
’m => Score.SetMetrenome[select1,select2,MAX[16,ReadNumbers["metrenome: <m>CR",].a]];
’n => {[a,b] ← ReadNumbers["n-tuplet: <n>CR <m>CR",TRUE]; Selection.MakeNTuplet[b,a]};
’N => Selection.HideNTuplets[TRUE];
016C=> {[a,b] ← ReadNumbers["n-tuplet: <n>CR <m>CR",TRUE];
Selection.MakeNTupletOfBeams[b,a]};
’p => Selection.Transpose[ReadNumbers["transpose: +/- <n>CR",].a];
’r => IF lineSelect
THEN Piece.Replace[select1,select2,greySelect1,greySelect2]
ELSE Selection.SetRest[TRUE];
’R => IF ~lineSelect THEN Selection.SetRest[FALSE] ELSE flash←TRUE;
’s => IF lineSelect THEN
Heuristic.MakeSyncs[select1,select2] ELSE
Selection.MakeSync[];
’S => Selection.ClearSync[];
’t => Selection.MakeTie[];
’T => Selection.ClearTie[];
’x => {Voice.Check[]; RETURN};
’v => Voice.Set[ReadDigit[]];
’ => {insertMeasure ← TRUE; RETURN};
--
values, dotted values, doubly dotted values, triply dotted values
’0 => Selection.SetNoteValue[unknown,0];
’1 => Selection.SetNoteValue[whole,0];’! => Selection.SetNoteValue[whole,1];
’2 => Selection.SetNoteValue[half,0];
’@ => Selection.SetNoteValue[half,1];
’4 => Selection.SetNoteValue[quarter,0];
’$ => Selection.SetNoteValue[quarter,1];
’8 => Selection.SetNoteValue[eighth,0];
’* => Selection.SetNoteValue[eighth,1];
’6 => Selection.SetNoteValue[sixteenth,0];
’~ => Selection.SetNoteValue[sixteenth,1];
’3 => Selection.SetNoteValue[thirtysecond,0];
’# => Selection.SetNoteValue[thirtysecond,1];
’7 => Selection.SetNoteValue[sixtyfourth,0];
’& => Selection.SetNoteValue[sixtyfourth,1];
’[ => Selection.SetStaff[ReadDigit[]];
’↑ => Selection.SetStem[TRUE];
’← => Selection.SetStem[FALSE];
’/ => {[a,b] ← ReadNumbers["time signature: <a>CR <b>CR",TRUE];
Score.SetTimeSignature[select1,select2,[a,b]]};
033C => {Selection.Clear[]; RETURN};
ENDCASE => RETURN;
command ← TRUE;
IF flash THEN {Interface.Flash[]; RETURN};
commands.count ← commands.count+1;
IF lineSelect AND min>max
THEN Score.Redraw[select1,select2]
ELSE Score.Redraw[min,max];
END;

ChangeLook:PROCEDURE[c:CHARACTER] =
BEGIN
count:BOOLEAN ← FALSE;
repaint:BOOLEAN ← TRUE;
SELECT c FROM
’a=> Score.Look[accidental,TRUE,];
’A=> Score.Look[accidental,FALSE,];
’c=> Score.Look[noCarry,FALSE,];
’C=> Score.Look[noCarry,TRUE,];
’g=> flash ← TRUE;
’h=> Score.Look[hardcopy,TRUE,];
’H=> Score.Look[hardcopy,FALSE,];
’j=> {Score.Look[justified,,ReadDigit[]]; count←TRUE};
’l=> {Score.Look[logical,,]; count←TRUE};
’n=> Score.Look[notehead,TRUE,];
’N=> Score.Look[notehead,FALSE,];
’o=> Score.Look[overview,TRUE,];
’O=> Score.Look[overview,FALSE,];
’p=> {Score.Look[physical,,ReadDigit[]]; count←TRUE};
’s=> {Score.Look[sync,TRUE,]; repaint ← FALSE};
’S=> Score.Look[sync,FALSE,];
’v=> Score.Look[voice,TRUE,ReadDigit[]];
’V=> {Score.Look[voice,FALSE,]; repaint ← FALSE};
177C=> IODefs.Rubout;
ENDCASE => {Sheet.SetStyle[Digit[c],select1,select2]; count←TRUE};
command ← TRUE;
IF flash THEN {Interface.Flash[]; RETURN};
Score.Draw[repaint];
IF count THEN commands.count ← commands.count+1;
END;

ReadKey:PROCEDURE RETURNS[k:INTEGER] =
BEGIN
key,c:CHARACTER;
accidental:Accidental←inKey;
CR:CHARACTER=015C;
Major:BOOLEAN ← TRUE;
negative:BOOLEAN ← FALSE;
-- key may be expressed as [-7..7]
Screen.DisplayMessage["Please enter key name (A, Bb, C#m...) or number."];
key ← IODefs.ReadChar[];
IF key=’- THEN {
key ← IODefs.ReadChar[];
Screen.DisplayMessage[NIL];
RETURN[-Digit[key]]};
IF key IN [’0..’9] THEN {
Screen.DisplayMessage[NIL];
RETURN[Digit[key]]};
-- key must have been named (Am, C#, DM, e, etc.)
key ← StringDefs.UpperCase[key];
IF key=177C THEN IODefs.Rubout;
WHILE (c←IODefs.ReadChar[])#CR DO
SELECT c FROM
’m => Major ← FALSE;
’b => accidental ← flat;
’# => accidental ← sharp;
177C=> IODefs.Rubout;
ENDCASE;
ENDLOOP;
Screen.DisplayMessage[NIL];
SELECT key FROM
’C => k ← 0; ’D => k ← 2; ’E => k ← 4; ’F => k← -1;
’G => k ← 1; ’A => k ← 3; ’B => k ← 5;
ENDCASE => {Interface.Flash[]; RETURN[-100]};
IF accidental=flat THEN k ← k- 7;
IF accidental=sharp THEN k← k+ 7;
IF NOT Major THEN k ← k- 3;
RETURN[k];
END;

ReadFileName:PROCEDURE[s:STRING] =
BEGIN
IODefs.ReadID[fileName !IODefs.Rubout=> RETRY];
StringDefs.AppendString[fileName,s];
RETURN;
END;

fileName:STRING ← [50];

ReadNumbers:PROCEDURE[s:STRING,two:BOOLEAN←FALSE] RETURNS[a,b:INTEGER] =
BEGIN
a ← b ← 0;
Screen.DisplayMessage[s];
a ← IODefs.ReadDecimal[];
Screen.DisplayMessage[NIL];
IF NOT two THEN RETURN;
Screen.DisplayMessage["and the second number..."];
b ← IODefs.ReadDecimal[];
Screen.DisplayMessage[NIL];
END;

ReadDigit:PROCEDURE RETURNS[d:CARDINAL] =
BEGIN
Screen.DisplayMessage["Please enter a digit"];
d ← Digit[IODefs.ReadChar[]];
Screen.DisplayMessage[NIL];
END;

Digit:PROCEDURE[c:CHARACTER] RETURNS[CARDINAL] =
BEGIN
SELECT c FROM
’0 => RETURN[0];
’1 => RETURN[1]; ’2 => RETURN[2]; ’3 => RETURN[3];
’4 => RETURN[4]; ’5 => RETURN[5]; ’6 => RETURN[6];
’7 => RETURN[7]; ’8 => RETURN[8]; ’9 => RETURN[9];
--shifted number keys
’) => RETURN[10];
’! => RETURN[11]; ’@ => RETURN[12]; ’# => RETURN[13];
’$ => RETURN[14]; ’% => RETURN[15]; ’~ => RETURN[16];
’& => RETURN[17]; ’* => RETURN[18]; ’( => RETURN[19];
033C => RETURN[TF]; -- default for justify
ENDCASE => IODefs.Rubout;
RETURN[0];
END;

--******************************************************************
--
The menu: change attributes, insertion
--******************************************************************

defaultObject:Interface.Object←none;

HandleMenu:PROCEDURE =
BEGIN
OPEN CursorDefs, Interface;
x,y,i,j,newI,newJ:CARDINAL←0;
x ← CursorX↑;
y ← CursorY↑;
DisplayMenu[x+4,y+4];
CursorGetsMenu[0,0];
WHILE BlueBug[] DO
newI ← (CursorX↑-x)/16;
newJ ← (CursorY↑-y+8)/16;
IF i=newI AND j=newJ THEN LOOP;
i ← newI;
j ← newJ;
CursorGetsMenu[i,j];
ENDLOOP;
DisplayMenu[x+4,y+4];
SELECT j FROM
0 => SELECT i FROM
0 => defaultObject ← note;
1 => defaultObject ← rest;
2 => defaultObject ← staves;
3 => defaultObject ← measure;
4 => defaultObject ← doubleMeasure;
5 => defaultObject ← repeat1;
6 => defaultObject ← repeat2;
7 => defaultObject ← endMeasure;
8 => defaultObject ← treble;
9 => defaultObject ← bass;
10=> defaultObject ← octava;
ENDCASE => defaultObject ← none;
1 => SELECT i FROM
0 => defaultObject ← doubleFlat;
1 => defaultObject ← flat;
2 => defaultObject ← natural;
3 => defaultObject ← inKey;
4 => defaultObject ← sharp;
5 => defaultObject ← doubleSharp;
6 => defaultObject ← trill;
7 => defaultObject ← mordent1;
8 => defaultObject ← mordent2;
ENDCASE => defaultObject ← none;
ENDCASE => defaultObject ← none;
END;

CursorGetsMenu:PROCEDURE[i,j:CARDINAL] =
BEGIN
OPEN CursorDefs;
SELECT j FROM
0 => SELECT i FROM
0 => cursor↑ ← quarter;
1 => cursor↑ ← rest;
2 => cursor↑ ← measure;
3 => cursor↑ ← measure;
4 => cursor↑ ← doubleMeasure;
5 => cursor↑ ← repeat1;
6 => cursor↑ ← repeat2;
7 => cursor↑ ← endMeasure;
8 => cursor↑ ← trebleClef;
9 => cursor↑ ← bassClef;
10=> cursor↑ ← octava;
ENDCASE => cursor↑ ← textCursor;
1 => SELECT i FROM
0 => cursor↑ ← doubleFlat;
1 => cursor↑ ← flat;
2 => cursor↑ ← natural;
3 => cursor↑ ← inKey;
4 => cursor↑ ← sharp;
5 => cursor↑ ← doubleSharp;
6 => cursor↑ ← trill;
7 => cursor↑ ← mordent1;
8 => cursor↑ ← mordent2;
ENDCASE => cursor↑ ← textCursor;
ENDCASE => cursor↑ ← textCursor;
END;

DisplayMenu:PROCEDURE[x,y:INTEGER] =
BEGIN
OPEN Screen;
sp,p:Graphics.Vec ← [x,y];
p← Graphics.ScreenToUser[screen,sp];
Graphics.MoveTo[screen,p];
Graphics.SetPaint[screen,invert];
Graphics.SetTexture[screen,black];
Graphics.SetFont[screen,music,8];
Graphics.DisplayString[screen,"tzffghopRS"]; --
note,rest,measures,clefs
Utility.SetFont[screen,text,12];
Graphics.DisplayChar[screen,’8];

Utility.SetFont[screen,music,8];
p.y ← p.y-16;
Graphics.MoveTo[screen,p];
Graphics.DisplayString[screen,"EFG.MOUVW"]; --
accidentals,embellishments
Graphics.SetFont[screen,text,12];
END;

--******************************************************************
--
Utility procedures
--******************************************************************

Hardcopy:PROCEDURE[s:STRING]={Score.Print[splines:~hardcopy OR BlueBug[]]};

FileOut:PROCEDURE[s:STRING]=
BEGIN
IF NOT Score.FileOut[s] THEN {
Screen.DisplayMessage["FileOut aborted-- see mesa.typescript"];
Interface.Flash[]};
END;

Play:PROCEDURE =
BEGIN
start:CARDINAL;
IF playing THEN {Score.StopPlaying[]; RETURN};
-- ELSE TURN THE PLAYER ON
start ← scoreLength;
insertMeasure ← FALSE;
-- find the indices of the selection
IF select1<select2
THEN FOR i:CARDINAL IN [0..scoreLength) DO
IF start#scoreLength OR score[i].time< select1 THEN LOOP;
start ← i;
EXIT; ENDLOOP
ELSE {start ← 0};
-- should we record any keystrokes?
IF show.display=physical AND ~listening THEN {
IF temp=NIL THEN temp ← Utility.NewPiece[];
Score.StartListening[temp];
merge ← TRUE};
-- start the player
Score.StartPlaying[score,start,show.display=physical,DisplayCursor];
IF flash THEN Interface.Flash[];
END;

temp:PiecePTR←NIL;
merge:BOOLEAN←FALSE;

Listen:PROCEDURE =
BEGIN
IF listening THEN { -- TURN THE LISTENER OFF
Score.StopListening[];
IF temp[0]=NIL THEN RETURN; -- nothing recorded
IF merge
THEN Piece.Merge[select1,EndOfScore[],temp]
ELSE {Piece.Delete[select1,select2]; Piece.Insert[select1,temp]};
temp ← NIL;
Piece.CleanUpNotes[score];
Score.Look[voice,FALSE,];
WHILE AnyBug[] DO NULL; ENDLOOP;
Score.Draw[erase:~merge];
commands.count←100; -- force a backup
RETURN};
-- ELSE TURN THE LISTENER ON
IF temp=NIL THEN temp ← Utility.NewPiece[];
IF TF<64 THEN TF←256;
Score.StartListening[temp];
IF flash THEN Interface.Flash[];
merge ← FALSE;
END;

DisplayCursor:PROCEDURE[time:Time] =
BEGIN
sync:SyncPTR;
sp,p:Graphics.Vec;
IF show.display=physical THEN time ← time/TF;
IF time < begin THEN RETURN;
[p.x,p.y] ← Sheet.Map[time,,2];
p.y ← p.y+64;
sp ← Graphics.Map[context,Screen.screen,p];
CursorX↑ ← Real.FixI[sp.x];
CursorY↑ ← MAX[808 - Real.FixI[sp.y],0];
IF NOT insertMeasure THEN RETURN;
insertMeasure ← FALSE;
sync ← Utility.NewSync[];
sync.time ← time-8;
sync.type ← measure;
Piece.AddSync[score,sync];
Score.Redraw[time-8,time-8];
END;

insertMeasure:BOOLEAN←FALSE;

Scroll:PROCEDURE[by:INTEGER] =
BEGIN
time:Time;
x,y:INTEGER;
[x,y] ← Sheet.ScreenPoint[];
time ← Sheet.NearestTime[x,y-40].time;
IF by>0
THEN Sheet.Scroll[MAX[Lines[begin,time],1]]
ELSE Sheet.Scroll[-MAX[Lines[begin,time],1]];
Score.Draw[];
END;

Thumb:PROCEDURE =
BEGIN
height:INTEGER←680;
endOfScore:Time;
sp,p:Graphics.Vec;
oldBegin:Time=begin;
endOfScore←EndOfScore[];
SELECT scale FROM
1 => height ← height;
2 => height ← (3*height)/2;
4 => height ← 4*height;
ENDCASE => ERROR;
WHILE YellowBug[] DO
[p.x,p.y] ← Sheet.ScreenPoint[];
IF p.x>-10 THEN RETURN;
p.y ← -(height*begin)/endOfScore;
sp ← Graphics.Map[context,Screen.screen,p];
Interface.Wait[1];
CursorY↑ ← MAX[808 - Real.FixI[sp.y],0];
ENDLOOP;
[p.x,p.y] ← Sheet.ScreenPoint[];
begin ← (Real.Fix[top-p.y]*endOfScore)/height;
begin ← MIN[begin,endOfScore-60];
begin ← MAX[begin,0];
Sheet.SetBegin[begin];
IF begin#oldBegin THEN Score.Draw[];
END;

Lines:PROCEDURE[time1,time2:Time] RETURNS[CARDINAL] = INLINE
BEGIN
line,topLine:INTEGER;
line ← Sheet.LineNumber[Sheet.FindLine[time2]];
topLine ← Sheet.LineNumber[Sheet.FindLine[time1]];
RETURN[line-topLine];
END;

RETURN[@commands];


END.

Thumb:PROCEDURE =
BEGIN
time:Time;
x,y:INTEGER;
lines:INTEGER;
[x,y] ← Sheet.ScreenPoint[];
time ← Sheet.NearestTime[x,y].time;
lines ← Lines[begin,time];
SELECT TRUE FROM
lines=0 AND x<0 => {Sheet.SetBegin[0]};
scale=1 => {Sheet.Scale[4]; Sheet.SetBegin[0]};
scale=2 => {Sheet.Scale[4]; Sheet.SetBegin[0]};
scale=4 => {Sheet.Scale[IF hardcopy THEN 2 ELSE 1]; Sheet.SetBegin[time]};
ENDCASE;
Score.Draw[];
END;