InterfaceImplA.mesa
Copyright (C) 1982, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
last modified: January 16, 1982  11: 09 AM
Edited by Doug Wyatt, June 14, 1984 3:38:22 pm PDT
 
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: CEDAR 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: BOOL ← FALSE;
commands: Screen.CommandProcs ← [Play, Listen, HandleRed, HandleYellow, HandleBlue, HandleKeyboard, Scroll, Thumb, Score.Draw, Score.FileIn, FileOut, Hardcopy, Initialize, 0];
******************************************************************
Initialization
******************************************************************
Initialize: 
PROC[dc: Graphics.DisplayContext] = {
keyboard ← StreamDefs.GetDefaultKey[];
Utility.InitStorage[];
Sheet.Initialize[dc];
};
 
******************************************************************
Command parser
******************************************************************
HandleKeyboard: 
PROC =  {
ClearDirty[];
Do[IODefs.ReadChar[]];
IF test AND Score.Test[] THEN Error; --you may proceed, but you cannot file out
};
 
HandleBlue: 
PROC =  {
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;
};
 
HandleYellow: 
PROC = 
-- used to delete objects or unselect notes  {
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;
};
 
HandleRed: 
PROC = 
-- Only used to change the selection  {
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
};
 
ClearDirty: PROC = INLINE {min ← 100000; max ← -1};
ExtendLine: 
PROC = {
time: Time;
grey: BOOL ← FALSE;
switch: BOOL ← TRUE;
beginning: BOOL ← 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 
{
grey ← TRUE;
switch ← TRUE;
Selection.AddLine[temp1, temp2];
};
 
IF 
NOT Shift[] 
AND grey 
THEN 
{
grey ← FALSE;
switch ← TRUE;
Selection.AddGreyLine[gTemp1, gTemp2];
};
 
[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;
 
};
 
Beginning: 
PROC[time, begin, end: Time] 
RETURNS[
BOOL] = 
INLINE {
IF time<=begin THEN RETURN[TRUE];
IF time>=end THEN RETURN[FALSE];
RETURN[ABS[time-begin]<ABS[time-end]];
};
 
ChooseLine: 
PROC[time: Time] = {
time2: Time;
temp1, temp2, gTemp1, gTemp2: Time;
grey: BOOL ← FALSE;
temp1 ← select1;
temp2 ← select2;
gTemp1 ← greySelect1;
gTemp2 ← greySelect2;
WHILE RedBug[] 
DO
IF Shift[] 
AND 
NOT grey 
THEN 
{
grey ← TRUE;
Selection.AddLine[temp1, temp2];
};
 
IF 
NOT Shift[] 
AND grey 
THEN 
{
grey ← FALSE;
Selection.AddGreyLine[gTemp1, gTemp2];
};
 
[time2,] ← Sheet.NearestTime[];
IF grey THEN Selection.AddGreyLine[time, time2] ELSE Selection.AddLine[time, time2];
ENDLOOP;
 
};
 
************************************************************************
commands from the keyboard
************************************************************************
Do: 
PROC[c: 
CHARACTER] = {
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 valuese30(0, 3810)(1, 4445)(2, 5696)(3, 6592)\1965i14I181i40I340i41I462i40I895i40I1896i26I1914i65I
'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];
 
};
 
ChangeLook: 
PROC[c: 
CHARACTER] =  {
count: BOOL ← FALSE;
repaint: BOOL ← 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;
};
 
ReadKey: 
PROC 
RETURNS[k: 
INTEGER] = {
key, c: CHARACTER;
accidental: Accidental ← inKey;
CR: CHARACTER=015C;
Major: BOOL ← TRUE;
negative: BOOL ← 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];
};
 
ReadFileName: 
PROC[s: 
STRING] = {
IODefs.ReadID[fileName !IODefs.Rubout=> RETRY];
StringDefs.AppendString[fileName, s];
RETURN;
};
 
fileName: STRING ← [50];
ReadNumbers: 
PROC[s: 
STRING, two: 
BOOL ← 
FALSE] 
RETURNS[a, b: 
INTEGER] = {
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];
};
 
ReadDigit: 
PROC 
RETURNS[d: 
CARDINAL] = {
Screen.DisplayMessage["Please enter a digit"];
d ← Digit[IODefs.ReadChar[]];
Screen.DisplayMessage[NIL];
};
 
Digit: 
PROC[c: 
CHARACTER] 
RETURNS[
CARDINAL] = {
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];
};
 
******************************************************************
The menu: change attributes, insertion
******************************************************************
defaultObject: Interface.Object ← none;
HandleMenu: 
PROC =  {
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;
 
};
 
CursorGetsMenu: 
PROC[i, j: 
CARDINAL] = {
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;
 
};
 
DisplayMenu: 
PROC[x, y: 
INTEGER] = {
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];
};
 
******************************************************************
Utility procedures
******************************************************************
Hardcopy: PROC[s: STRING]={Score.Print[splines: ~hardcopy OR BlueBug[]]};
FileOut: 
PROC[s: 
STRING]= {
IF 
NOT Score.FileOut[s] 
THEN {
Screen.DisplayMessage["FileOut aborted-- see mesa.typescript"]; 
Interface.Flash[]};
 
};
 
Play: 
PROC = {
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[];
};
 
temp: PiecePTR ← NIL;
merge: BOOL ← FALSE;
Listen: 
PROC = {
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;
};
 
DisplayCursor: 
PROC[time: Time] =  {
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];
};
 
insertMeasure: BOOL ← FALSE;
Scroll: 
PROC[by: 
INTEGER] =  {
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[];
}; 
 
Thumb: 
PROC =  {
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[];
}; 
 
Lines: 
PROC[time1, time2: Time] 
RETURNS[
CARDINAL] = 
INLINE {
line, topLine: INTEGER;
line ← Sheet.LineNumber[Sheet.FindLine[time2]];
topLine ← Sheet.LineNumber[Sheet.FindLine[time1]];
RETURN[line-topLine];
};
 
RETURN[@commands];
END.
Thumb: 
PROC =  {
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[];
};