-- 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;