-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 10:14 am

DIRECTORY
	CursorDefs, 
	Graphics USING [DrawChar, Context, DrawRope, Map, SetCP, WorldToUser, SetDefaultFont, SetPaintMode, SetStipple], 
	Heuristic USING [MakeBeams, MakeChords, MakeSyncs, SetNoteValues], 
	Interface USING [count, DeleteGraphical, Flash, MoveNote, MoveGraphical, Object, Wait], 
	MusicDefs, 
	Piece USING [AddEvent, CleanUpNotes, Copy, Merge, NearestNote, New, Overflow, Replace], 
	Real USING [Fix, FixI], 
	Score USING [
	  Draw, FileIn, FileOut, Initialize, Look, Print, Redraw, 
	  SetKey, SetMetrenome, SetStyle, SetTimeSignature, ShowLogical, 
	  StartListening, StartPlaying, StopListening, StopPlaying, Test], 
	Screen USING [CommandProcs, DisplayMessage, screen], 
	Selection, -- USING everything
	Sheet USING [
	  FindLine, LineNumber, Map, NearestTime, 
	  Reset, ScreenPoint, Scroll, SetBegin], 
	String USING [AppendString, UpperCase],        
	TTY USING [Create, GetChar, GetDecimal, GetID, Handle, Rubout], 
	UserTerminal USING [cursor, GetCursorPattern, SetCursorPattern], 
	Utility, -- USING everything        
	Voice USING [Check, Set];        


InterfaceImplA: MONITOR 
	IMPORTS 
	  Graphics, Heuristic, Interface, MusicDefs, Piece, Real, 
	  Score, Screen, Selection, Sheet, String, TTY, UserTerminal, Utility, Voice
    EXPORTS Screen = 
BEGIN 
OPEN Graphics, MusicDefs, UserTerminal;
keyboard: TTY.Handle;

Error: SIGNAL;
test: BOOLEAN ← FALSE;

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

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

Initialize: PROCEDURE[context: Graphics.Context] RETURNS[score: ScorePTR] = 
BEGIN
keyboard ← TTY.Create[NIL];
score ← Piece.New[1000, TRUE];
score.beamHeap ← Utility.NewSegment[SIZE[BeamHeapRec[100]], 100, SIZE[BeamHeapRec[0]]-1];
IF score.beamHeap.max # 100 THEN ERROR;
score.chordHeap ← Utility.NewSegment[SIZE[ChordHeapRec[100]], 100, SIZE[ChordHeapRec[0]]-1];
IF score.chordHeap.max # 100 THEN ERROR;
Score.Initialize[score, context];
END;

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

HandleKeyboard: PROCEDURE[score: ScorePTR] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
ClearDirty[score];
Do[score, TTY.GetChar[keyboard]];
IF test AND Score.Test[score] THEN Error; -- you may proceed, but you cannot file out
END;

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

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

HandleRed: PROCEDURE[score: ScorePTR] = -- Only used to change the selection 
BEGIN
OPEN CursorDefs;
x, y: INTEGER;
i, click: CARDINAL ← 0;
temp: Cursor ← GetCursorPattern[];
SetCursorPattern[textCursor];
ClearDirty[score];
WHILE RedBug[] DO
	[x, y] ← Sheet.ScreenPoint[score.sheet];
	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[score, Sheet.NearestTime[score.sheet, x, y].time];
				1 => EXIT;
				2 => ExtendLine[score];
				ENDCASE => ERROR};
   		Shift[] => Selection.AddNote[score, Piece.NearestNote[score, x, y]];
  		ENDCASE => {IF score.command THEN Selection.Clear[]; 
			      Selection.AddNote[score, Piece.NearestNote[score, x, y]]};
	score.command ← FALSE;
	ENDLOOP;
SetCursorPattern[temp];
IF test AND Score.Test[score] THEN Error; -- you may proceed, but you cannot file out
END;

ClearDirty: PROCEDURE[score: ScorePTR] = INLINE {
	score.sheet.dirty1 ← 100000; 
	score.sheet.dirty2 ← -1};

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

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

Do: PROCEDURE[score: ScorePTR, c: CHARACTER] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
a, b: INTEGER;
SELECT c FROM
  'b   => IF Selection.selection.lineSelect 
		THEN Heuristic.MakeBeams[score, Selection.selection.select1, Selection.selection.select2] 
		ELSE Selection.MakeBeam[];
  'B   => Selection.ClearBeam[];
  002C => Selection.MakeBeamOfBeams[];
  'c   => IF Selection.selection.lineSelect THEN 
		Heuristic.MakeChords[score, Selection.selection.select1, Selection.selection.select2] ELSE 
		Selection.MakeChord[];
  'C   => Selection.ClearChord[];
  'd   => Selection.Delete[];
  'e   => {Selection.AddLine[score, 0, EndOfScore[score]]; RETURN}; 
--  'f   => {ReadFileName[".logical"]; Score.FileInOld[fileName]};
--  'F   => {ReadFileName[".logical"]; Score.FileOutOld[fileName]};
--  'g   => GuessNoteValues[select1, Selection.selection.select2];
  'g   => Selection.SetGrace[TRUE];
  'G   => Selection.SetGrace[FALSE];
  007C   => Heuristic.SetNoteValues[score, Selection.selection.select1, Selection.selection.select2];
  'k   => Score.SetKey[score, Selection.selection.select1, Selection.selection.select2, ReadKey[score]];
  'l   => {ChangeLook[score, TTY.GetChar[keyboard]]; RETURN};
  014C => {Score.ShowLogical[score, 0, score.length-1]; Sheet.Reset[score]}; -- debugging aid
  'm   => Score.SetMetrenome[score, Selection.selection.select1, Selection.selection.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 Selection.selection.lineSelect 
	THEN Piece.Replace[Selection.selection.score, Piece.Copy[Selection.selection.score2, Selection.selection.greySelect1, Selection.selection.greySelect2],  Selection.selection.select1, Selection.selection.select2]
	ELSE Selection.SetRest[TRUE];
  'R   => IF ~Selection.selection.lineSelect THEN Selection.SetRest[FALSE] ELSE score.flash ← TRUE; 
  's   => IF Selection.selection.lineSelect THEN 
		Heuristic.MakeSyncs[score, Selection.selection.select1, Selection.selection.select2] ELSE 
		Selection.MakeSync[];
  'S   => Selection.ClearSync[];
  't   => Selection.MakeTie[];
  'T   => Selection.ClearTie[];
  'x   => {Voice.Check[score]; RETURN};
  'v   => Voice.Set[score, ReadDigit[score]];
  '    => {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[score]];
  '↑   => Selection.SetStem[TRUE];
  '←   => Selection.SetStem[FALSE];
  '/   => {[a, b] ← ReadNumbers["time signature: < a > CR < b > CR", TRUE]; 
		Score.SetTimeSignature[score, [a, b], Selection.selection.select1, Selection.selection.select2]};
  033C => {Selection.Clear[]; RETURN};
  ENDCASE => RETURN;
score.command ← TRUE;
IF score.flash THEN {Interface.Flash[score]; RETURN};
commands.count ← commands.count+1;
IF Selection.selection.lineSelect AND score.sheet.dirty1 > score.sheet.dirty2 
	THEN Score.Redraw[score, Selection.selection.select1, Selection.selection.select2] 
	ELSE Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2];
END;

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

ReadKey: PROCEDURE[score: ScorePTR] 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 ← TTY.GetChar[keyboard];
IF key = '- THEN {
	key ← TTY.GetChar[keyboard]; 
	Screen.DisplayMessage[NIL];  
	RETURN[-Digit[score, key]]};
IF key IN ['0..'9] THEN {
	Screen.DisplayMessage[NIL];  
	RETURN[Digit[score, key]]};
-- key must have been named (Am, C # , DM, e, etc.)
key ← String.UpperCase[key];
IF key = 177C THEN TTY.Rubout;
WHILE (c ← TTY.GetChar[keyboard]) # CR DO
	SELECT c FROM
		'm => Major ← FALSE;
		'b => accidental ← flat;
		'# => accidental ← sharp;
		177C => TTY.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[score]; 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
TTY.GetID[keyboard, fileName ! TTY.Rubout => RETRY];
String.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 ← TTY.GetDecimal[keyboard];
Screen.DisplayMessage[NIL];
IF NOT two THEN RETURN;
Screen.DisplayMessage["and the second number..."];
b ← TTY.GetDecimal[keyboard];
Screen.DisplayMessage[NIL];
END;

ReadDigit: PROCEDURE[score: ScorePTR] RETURNS[d: CARDINAL] = 
BEGIN
Screen.DisplayMessage["Please enter a digit"];
d ← Digit[score, TTY.GetChar[keyboard]];
Screen.DisplayMessage[NIL];
END;

Digit: PROCEDURE[score: ScorePTR, 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[score.sheet.justification]; -- default for justify
	ENDCASE => TTY.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  ← UserTerminal.cursor.x; 
y  ← UserTerminal.cursor.y;
DisplayMenu[x+4, y+4];
CursorGetsMenu[0, 0];
WHILE BlueBug[] DO
      newI ← (UserTerminal.cursor.x-x)/16;
      newJ ← (UserTerminal.cursor.y-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 => SetCursorPattern[quarter];
	1 => SetCursorPattern[rest];
	2 => SetCursorPattern[measure];
	3 => SetCursorPattern[measure];
	4 => SetCursorPattern[doubleMeasure];
	5 => SetCursorPattern[repeat1];
	6 => SetCursorPattern[repeat2];
	7 => SetCursorPattern[endMeasure];
	8 => SetCursorPattern[trebleClef];
	9 => SetCursorPattern[bassClef];
	10 => SetCursorPattern[octava];
	ENDCASE => SetCursorPattern[textCursor];
  1 => SELECT i FROM
	0 => SetCursorPattern[doubleFlat];
	1 => SetCursorPattern[flat];
	2 => SetCursorPattern[natural];
	3 => SetCursorPattern[inKey];
	4 => SetCursorPattern[sharp];
	5 => SetCursorPattern[doubleSharp];
	6 => SetCursorPattern[trill];
	7 => SetCursorPattern[mordent1];
	8 => SetCursorPattern[mordent2];
	ENDCASE => SetCursorPattern[textCursor];
  ENDCASE => SetCursorPattern[textCursor];
END;

DisplayMenu: PROCEDURE[x, y: INTEGER] = 
BEGIN
OPEN Screen;
newX, newY: REAL;
[newX, newY] ← Graphics.WorldToUser[screen, x, 808 - y];
Graphics.SetCP[screen, newX, newY];
[] ← Graphics.SetPaintMode[screen, invert];
Graphics.SetStipple[screen, black];
Graphics.SetDefaultFont[screen, music];
Graphics.DrawRope[screen, "tzffghopRS"]; -- note, rest, measures, clefs
Utility.SetFont[screen, text, 12];
Graphics.DrawChar[screen, '8];
Utility.SetFont[screen, music, 8];
newY ← newY-16;
Graphics.SetCP[screen, newX, newY];
Graphics.DrawRope[screen, "EFG.MOUVW"];  -- accidentals, embellishments
Graphics.SetDefaultFont[screen, text];
END;

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

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

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

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

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

Listen: PROCEDURE[score: ScorePTR] = 
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
IF listening THEN { -- TURN THE LISTENER OFF
	Score.StopListening[score];
	IF temp[0] = NIL THEN RETURN; -- nothing recorded
	IF merge THEN Piece.Merge[score, temp, Selection.selection.select1, LAST[Time]] 
	ELSE {
		portion: ScorePortionPTR ← zone.NEW[ScorePortionRec];
		portion.score ← temp;
		portion.duration ← 0; -- indicates a score from the synthesizer
		Piece.Replace[score, portion, Selection.selection.select1, Selection.selection.select2]};
	temp ← NIL;
	Piece.CleanUpNotes[score];
	Score.Look[score, voice, FALSE];
	WHILE AnyBug[] DO NULL; ENDLOOP;
	Score.Draw[score: score, erase: ~merge];
	commands.count ← 100; -- force a backup
	RETURN};
-- ELSE TURN THE LISTENER ON
IF temp = NIL THEN temp ← Piece.New[1000];
IF score.sheet.justification < 64 THEN score.sheet.justification ← 256;
Score.StartListening[temp];
IF score.flash THEN Interface.Flash[score];
merge ← FALSE;
END;

DisplayCursor: PROCEDURE[score: ScorePTR, time: Time] = 
BEGIN
sx, sy: REAL;
sync: SyncPTR;
px, py: INTEGER;
IF score.sheet.display = physical THEN time ← time/score.sheet.justification;
IF time < score.sheet.begin THEN RETURN;
[px, py] ← Sheet.Map[score.sheet, time, , 2];
py ← py+64;
[sx, sy] ← Graphics.Map[score.sheet.context, Screen.screen, px, py];
cursor.x ← Real.FixI[sx];
cursor.y ← MAX[808 - Real.FixI[sy], 0];
IF NOT insertMeasure THEN RETURN;
insertMeasure ← FALSE;
sync ← zone.NEW[EventRec.sync];
sync.time ← time-8;
score ← Piece.AddEvent[score, sync];
Score.Redraw[score, time-8, time-8];
END;

insertMeasure: BOOLEAN ← FALSE;

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

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

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

END.

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