DIRECTORY CursorDefs, Beam USING [Draw, SetStems], Event USING [Clef, Draw, GetOctava, GetScoreIndex, GetStaff, KeySignature, Measure, Octava, Staves], Graphics USING [DrawBox], Interface USING [InsertNote, Flash, Object], MusicDefs, Note USING [Draw, DrawTie, InVoice, SetAccidental, SetEmbellishment], Piece USING [AddEvent, NearestNote, NearestObject, Overflow, RemoveEvent], Process USING [SetTimeout], Real USING [FixI], Score USING [GetKey, GetStyle, Redraw, SetClef, SetOctava], Selection USING [RemoveNote], Sheet USING [AlternateTime, DrawClef, DrawOctava, FindLine, FindSection, Height, NearestStaff, NearestTime, NextLine, NextStaff, NormalPitch, OctavaHeight, PriorStaff, Reset, ScreenPoint], UserTerminal USING [mouse, GetCursorPattern, SetCursorPattern]; InterfaceImplB: CEDAR MONITOR IMPORTS Beam, Graphics, Interface, MusicDefs, Note, Piece, Process, Real, Score, Selection, Sheet, Event, UserTerminal EXPORTS Interface = BEGIN OPEN Graphics, MusicDefs, UserTerminal; timeout: CONDITION; Error: SIGNAL = CODE; count: PUBLIC BOOL; DeleteGraphical: PUBLIC PROC[score: ScorePTR] = { type: EventType; obj: ObjectType; s: EventPTR _ NIL; time, carry, next: Time; p: LONG POINTER _ NIL; value, oldClef, oldStyle: INTEGER _ 0; SetBrush[score, black, invert]; WHILE YellowBug[] DO [obj, p] _ Piece.NearestObject[score]; IF p # NIL THEN SELECT obj FROM note => Note.Draw[score, p]; measure => Event.Draw[score, p]; leftBeam => [] _ Beam.Draw[score, p]; rightBeam => [] _ Beam.Draw[score, p]; ENDCASE; Wait[1]; IF p # NIL THEN SELECT obj FROM note => Note.Draw[score, p]; measure => Event.Draw[score, p]; leftBeam => [] _ Beam.Draw[score, p]; rightBeam => [] _ Beam.Draw[score, p]; ENDCASE; ENDLOOP; IF p # NIL AND obj = note THEN Selection.RemoveNote[p]; IF obj # measure OR p = NIL THEN RETURN; s _ LOOPHOLE[p, EventPTR]; IF score.sheet.voice # noVoice AND (Event.Clef[s] OR Event.Octava[s]) THEN RETURN; type _ s.type; time _ s.time; IF s.type = staves THEN value _ Event.Staves[s].value; IF Event.Clef[s] THEN oldClef _ Clef[score.sheet, value, time]; IF type = staves THEN { next _ score.sheet[Sheet.NextLine[score.sheet, Sheet.FindLine[score.sheet, time]]].time; oldStyle _ Score.GetStyle[score, next]}; carry _ GetCarry[score, s]; Piece.RemoveEvent[score, s]; SetDirty[score, time, time]; IF type = keySignature THEN SetDirty[score, time, carry]; IF type = keySignature OR Event.Clef[s] THEN ResetSheet[score, time, time]; IF Event.Clef[s] AND oldClef # Clef[score.sheet, value, time] THEN SetDirty[score, time, carry]; IF type = staves THEN { Sheet.Reset[score]; IF Score.GetStyle[score, time] # value THEN SetDirty[score, 0, LAST[Time]]; IF Score.GetStyle[score, next] # oldStyle THEN SetDirty[score, 0, LAST[Time]]}; IF Event.Octava[s] THEN Sheet.Reset[score]; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; zone.FREE[@s]; count _ TRUE; }; ChangeSpelling: PROC[score: ScorePTR, object: Interface.Object] = { n: NotePTR; score.sheet.dirty1 _ 1000000; score.sheet.dirty2 _ -1; SetBrush[score, black, invert]; WHILE BlueBug[] DO IF (n _ Piece.NearestNote[score]) = NIL THEN LOOP; Note.Draw[score, n]; Note.Draw[score, n]; ENDLOOP; SetBrush[score, black, transparent]; IF n = NIL OR n.rest THEN RETURN; IF ~Note.InVoice[n, score.sheet.voice] THEN RETURN; SELECT object FROM doubleFlat => Note.SetAccidental[score, n, doubleFlat]; flat => Note.SetAccidental[score, n, flat]; natural => Note.SetAccidental[score, n, natural]; inKey => Note.SetAccidental[score, n, inKey]; sharp => Note.SetAccidental[score, n, sharp]; doubleSharp => Note.SetAccidental[score, n, doubleSharp]; ENDCASE; IF score.flash THEN {Interface.Flash[score]; RETURN}; IF n.spelled # inKey THEN n.show _ TRUE ELSE n.show _ FALSE; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; }; Embellish: PROC[score: ScorePTR, object: Interface.Object] = { x, y: INTEGER; n: NotePTR _ NIL; score.sheet.dirty1 _ 1000000; score.sheet.dirty2 _ -1; SetBrush[score, black, invert]; WHILE BlueBug[] DO [x, y] _ Sheet.ScreenPoint[score.sheet]; IF (n _ Piece.NearestNote[score, x, y]) = NIL THEN LOOP; Note.Draw[score, n]; Note.Draw[score, n]; ENDLOOP; SetBrush[score, black, transparent]; IF n = NIL OR n.rest THEN RETURN; SELECT object FROM trill => Note.SetEmbellishment[score, n, trill]; mordent1 => Note.SetEmbellishment[score, n, mordent1]; mordent2 => Note.SetEmbellishment[score, n, mordent2]; ENDCASE; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; }; Insert: PROC[score: ScorePTR, object: Interface.Object] = { OPEN CursorDefs; ENABLE Piece.Overflow => IF old = score THEN score _ new; tempCursor: Cursor _ GetCursorPattern[]; SetCursorPattern[ALL[0]]; SELECT object FROM staves, measure, repeat1, repeat2, doubleMeasure, endMeasure => InsertMeasure[score, object]; bass, treble => InsertClefChange[score, object]; octava => {SetCursorPattern[tempCursor]; InsertOctava[score, object]}; doubleFlat, flat, natural, inKey, sharp, doubleSharp => ChangeSpelling[score, object]; trill, mordent1, mordent2 => Embellish[score, object]; note, rest => Interface.InsertNote[score, object]; ENDCASE => ERROR; SetCursorPattern[tempCursor]; count _ TRUE; }; InsertMeasure: PROC[score: ScorePTR, object: Interface.Object] = { x, y: INTEGER; s: EventPTR; SELECT object FROM IN [measure..endMeasure] => { measure: MeasureType _ measure; s _ zone.NEW[EventRec.measure]; Event.Measure[s].measure _ LOOPHOLE[LOOPHOLE[object, CARDINAL] - LOOPHOLE[measure, CARDINAL]]}; staves => s _ zone.NEW[EventRec.staves]; ENDCASE => ERROR; [x, y] _ Sheet.ScreenPoint[score.sheet]; [s.time] _ Sheet.NearestTime[score.sheet, x, y]; SetBrush[score, black, invert]; WHILE BlueBug[] DO Event.Draw[score, s]; Event.Draw[score, s]; [x, y] _ Sheet.ScreenPoint[score.sheet]; [s.time] _ Sheet.NearestTime[score.sheet]; ENDLOOP; IF YellowBug[] THEN RETURN; IF object = staves THEN { Event.Staves[s].value _ Score.GetStyle[score, s.time]}; score _ Piece.AddEvent[score, s]; SetDirty[score, s.time, s.time]; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; }; InsertClefChange: PROC[score: ScorePTR, object: Interface.Object] = { ENABLE Piece.Overflow => IF score = old THEN score _ new; old, new: Section; sync: EventPTR _ NIL; oldClef: INTEGER _ 10; pitch, height: INTEGER; oldTime, newTime: Time _ 0; change: BOOL _ FALSE; oldStaff, newStaff: CARDINAL _ 10; SELECT object FROM bass => pitch _ 27; treble => pitch _ 48; ENDCASE => Error; SetBrush[score, black, invert]; [newTime, height] _ Sheet.NearestTime[score.sheet]; newStaff _ Sheet.NearestStaff[score.sheet, newTime, height]; WHILE BlueBug[] DO [newTime, height] _ Sheet.NearestTime[score.sheet]; newStaff _ Sheet.NearestStaff[score.sheet, newTime, height]; IF newTime = oldTime AND newStaff = oldStaff THEN LOOP; IF oldStaff # 10 THEN Sheet.DrawClef[score.sheet, pitch, oldStaff, oldTime]; Sheet.DrawClef[score.sheet, pitch, newStaff, newTime]; oldTime _ newTime; oldStaff _ newStaff; ENDLOOP; Sheet.DrawClef[score.sheet, pitch, newStaff, newTime]; IF YellowBug[] THEN RETURN; old _ score.sheet[Sheet.FindLine[score.sheet, newTime]]; oldClef _ Clef[score.sheet, newStaff, newTime]; Score.SetClef[score, pitch, newStaff, newTime]; new _ score.sheet[Sheet.FindLine[score.sheet, newTime]]; IF score.flash THEN {Interface.Flash[score]; RETURN}; IF old # new THEN SetDirty[score, new.time, new.time]; SetDirty[score, newTime, newTime]; IF pitch = oldClef THEN {Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; RETURN}; FOR i: CARDINAL IN [0..score.length) DO IF score[i].time # newTime THEN LOOP; IF ~Event.Clef[score[i]] OR Event.Staves[score[i]].value # newStaff THEN LOOP; sync _ score[i]; EXIT; ENDLOOP; IF sync # NIL THEN SetDirty[score, sync.time, GetCarry[score, sync]]; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; }; InsertOctava: PROC[score: ScorePTR, object: Interface.Object] = { ENABLE Piece.Overflow => IF score = old THEN score _ new; begin, end, oldEnd: Time _ -1; staff, oldStaff, alternate: INTEGER _ 1; pitch, height, oldHeight, octHeight, limit: INTEGER _ 0; SetBrush[score, black, invert]; [begin, oldHeight] _ Sheet.NearestTime[score.sheet]; staff _ Sheet.NearestStaff[score.sheet, begin, oldHeight]; WHILE BlueBug[] DO [end, height, staff] _ Slide[score, oldEnd, oldHeight, oldStaff]; IF end = oldEnd AND height = oldHeight THEN LOOP; IF oldEnd # -1 THEN Sheet.DrawOctava[score.sheet, pitch, oldStaff, octHeight, begin, oldEnd]; oldEnd _ end; oldStaff _ staff; oldHeight _ height; octHeight _ height - Sheet.Height[score.sheet, end, , staff]; pitch _ (IF Sheet.NormalPitch[score.sheet, staff] = 27 THEN 15 ELSE 60); Sheet.DrawOctava[score.sheet, pitch, staff, octHeight, begin, end]; ENDLOOP; Sheet.DrawOctava[score.sheet, pitch, staff, octHeight, begin, end]; IF YellowBug[] THEN RETURN; IF begin >= end THEN RETURN; Score.SetOctava[score, pitch, staff, octHeight, begin, end]; IF score.flash THEN {Interface.Flash[score]; RETURN}; Score.Redraw[score, begin, end]; }; Slide: PROC[score: ScorePTR, oldTime: Time, oldHeight: INTEGER, oldStaff: CARDINAL] RETURNS[time: Time, height: INTEGER, staff: CARDINAL] = { limit: INTEGER; staves: StavesPTR; staff _ oldStaff; [time, height] _ Sheet.NearestTime[score.sheet]; IF oldTime # -1 AND ABS[time-oldTime] > 200 AND time > oldTime THEN [time, height] _ Sheet.AlternateTime[score.sheet, time, height, -1]; IF oldTime # -1 AND ABS[time-oldTime] > 200 AND time < oldTime THEN [time, height] _ Sheet.AlternateTime[score.sheet, time, height, 1]; IF time = oldTime AND height = oldHeight THEN RETURN; staves _ score.sheet[Sheet.FindSection[score.sheet, time]].staves; staff _ Sheet.NextStaff[score.sheet, oldStaff, time]; limit _ staves.staff[staff].y+32; IF staff = oldStaff THEN limit _ limit-32-staves.offset; IF oldHeight <= limit THEN { -- we went below a lower limit IF staff = oldStaff THEN { -- must be next line [time, height] _ Sheet.AlternateTime[score.sheet, time, height, 1]; staff _ 0}; RETURN}; staff _ Sheet.PriorStaff[score.sheet, oldStaff, time]; limit _ staves.staff[staff].y; IF staff = oldStaff THEN limit _ limit+32+staves.offset; IF height >= limit THEN { -- we went above an upper limit IF staff = oldStaff THEN { -- must be last line [time, height] _ Sheet.AlternateTime[score.sheet, time, height, -1]; staff _ staves.length-1}; RETURN}; staff _ oldStaff; }; MoveGraphical: PUBLIC PROC[score: ScorePTR, object: Interface.Object] = { ENABLE Piece.Overflow => IF old = score THEN score _ new; s: EventPTR; p: LONG POINTER _ NIL; obj: ObjectType; IF object # none THEN { Insert[score, object]; RETURN; }; SetBrush[score, black, invert]; WHILE BlueBug[] DO [obj, p] _ Piece.NearestObject[score]; IF p = NIL THEN LOOP; s _ LOOPHOLE[p]; IF score.sheet.voice # noVoice AND Event.Octava[s] OR Event.Clef[s] THEN LOOP; SELECT obj FROM measure => { IF Event.Octava[s] THEN MoveOctava[score, Event.Staves[s]] ELSE MoveMeasure[score, s]}; leftBeam => MoveBeam[score, p]; rightBeam => ChangeTilt[score, p]; tie => MoveTie[score, p]; ENDCASE; ENDLOOP; IF p # NIL THEN count _ TRUE; }; MoveMeasure: PROC[score: ScorePTR, s: EventPTR] = { ENABLE Piece.Overflow => IF score = old THEN score _ new; old: Time = s.time; SetBrush[score, black, invert]; SetDirty[score, s.time, s.time]; WHILE BlueBug[] DO Event.Draw[score, s]; s.time _ Sheet.NearestTime[score.sheet].time; Event.Draw[score, s]; ENDLOOP; Event.Draw[score, s]; SetDirty[score, s.time, s.time]; SELECT TRUE FROM Event.Clef[s] => MoveClef[score, Event.Staves[s], old, s.time]; Event.Octava[s] => NULL; s.type = staves => MoveStaves[score, Event.Staves[s], old, s.time]; s.type = keySignature => MoveKey[score, s, old, s.time]; ENDCASE => {Piece.RemoveEvent[score, s]; score _ Piece.AddEvent[score, s]}; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; }; MoveClef: PROC[score: ScorePTR, s: StavesPTR, old, new: Time] = { oldCarry: Time; time: Time = s.time; newClef, clef: INTEGER; newClef _ Clef[score.sheet, s.value, s.time]; s.time _ old; oldCarry _ GetCarry[score, s]; s.time _ new; Piece.RemoveEvent[score, s]; score _ Piece.AddEvent[score, s]; ResetSheet[score, score.sheet.dirty1, score.sheet.dirty2]; clef _ Event.GetStaff[s, s.value].pitch; IF clef # newClef THEN SetDirty[score, new, GetCarry[score, s]]; IF clef # Clef[score.sheet, s.value, old] THEN SetDirty[score, old, oldCarry]; }; MoveStaves: PROC[score: ScorePTR, s: StavesPTR, old, new: Time] = { next: Time; nextStyle, oldStyle, newStyle: INTEGER; next _ score.sheet[Sheet.NextLine[score.sheet, Sheet.FindLine[score.sheet, MAX[old, new]]]].time; nextStyle _ Score.GetStyle[score, next]; oldStyle _ Score.GetStyle[score, old]; newStyle _ Score.GetStyle[score, new]; s.time _ new; Piece.RemoveEvent[score, s]; score _ Piece.AddEvent[score, s]; Sheet.Reset[score]; IF nextStyle # Score.GetStyle[score, next] THEN SetDirty[score, 0, LAST[Time]]; IF oldStyle # Score.GetStyle[score, old] THEN SetDirty[score, 0, LAST[Time]]; IF newStyle # Score.GetStyle[score, new] THEN SetDirty[score, 0, LAST[Time]]; }; MoveKey: PROC[score: ScorePTR, s: EventPTR, old, new: Time] = { oldCarry: Time; newKey: INTEGER; s.time _ old; oldCarry _ GetCarry[score, s]; newKey _ Score.GetKey[score, new]; s.time _ new; Piece.RemoveEvent[score, s]; score _ Piece.AddEvent[score, s]; ResetSheet[score, score.sheet.dirty1, score.sheet.dirty2]; IF new > old AND newKey # Event.KeySignature[s].key THEN SetDirty[score, new, GetCarry[score, s]]; IF new < old AND Score.GetKey[score, old] # Event.KeySignature[s].key THEN SetDirty[score, old, oldCarry]; }; MoveOctava: PROC[score: ScorePTR, s: StavesPTR] = { staff: CARDINAL; pitch, height: INTEGER; octava1, octava2: StavesPTR; octava1 _ (IF s.staves = octava1 THEN s ELSE Event.GetOctava[score, s]); octava2 _ (IF s.staves = octava2 THEN s ELSE Event.GetOctava[score, s]); IF octava1 = NIL THEN {Interface.Flash[score]; RETURN}; SetDirty[score, s.time, s.time]; SetBrush[score, black, invert]; staff _ octava1.value; pitch _ s.staff[octava1.value].pitch; WHILE BlueBug[] DO Event.Draw[score, octava1]; [s.time, height, staff] _ Slide[score, s.time, height, staff]; s.height _ height - Sheet.Height[score.sheet, octava1.time, , octava1.value]; s.height _ Sheet.OctavaHeight[pitch, s.height]; Event.Draw[score, octava1]; ENDLOOP; SetDirty[score, s.time, s.time]; IF octava1 = NIL OR octava2 = NIL OR octava1.time >= octava2.time THEN { Piece.RemoveEvent[score, octava1, TRUE]; -- deletes octava2 as well Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; RETURN}; Piece.RemoveEvent[score, s]; score _ Piece.AddEvent[score, s]; -- Remove/Add keeps the score sorted Sheet.Reset[score]; Score.Redraw[score, score.sheet.dirty1, score.sheet.dirty2]; }; MoveBeam: PROC[score: ScorePTR, b: BeamPTR] = { height, y: INTEGER _ 0; SetBrush[score, black, invert]; y _ mouse.y; WHILE BlueBug[] DO height _ b.height+(y-mouse.y); IF height = b.height THEN LOOP; [] _ Beam.Draw[score, b]; b.height _ height; y _ mouse.y; Beam.SetStems[score.sheet, b]; [] _ Beam.Draw[score, b]; ENDLOOP; Score.Redraw[score, b.sync1.time, b.sync2.time]; }; ChangeTilt: PROC[score: ScorePTR, b: BeamPTR] = { height, oldHeight, y: INTEGER _ 0; l: REAL _ 1; SetBrush[score, black, invert]; oldHeight _ b.height + Real.FixI[b.tilt*(b.sync2.time-b.sync1.time)]; y _ mouse.y; WHILE BlueBug[] DO height _ oldHeight+(y-mouse.y); IF height = oldHeight THEN LOOP; [] _ Beam.Draw[score, b]; b.tilt _ l*(height-b.height)/(b.sync2.time-b.sync1.time); oldHeight _ height; y _ mouse.y; Beam.SetStems[score.sheet, b]; [] _ Beam.Draw[score, b]; ENDLOOP; Score.Redraw[score, b.sync1.time, b.sync2.time]; }; MoveTie: PROC[score: ScorePTR, n: NotePTR] = { y: INTEGER; time: Time; time _ (n.sync.time+n.tie.sync.time)/2; y _ mouse.y; SetBrush[score, black, invert]; WHILE BlueBug[] DO IF y = mouse.y THEN LOOP; Note.DrawTie[score, n]; n.tieHeight _ n.tieHeight+(y-mouse.y); y _ mouse.y; Note.DrawTie[score, n]; ENDLOOP; Score.Redraw[score, n.tie.sync.time, n.sync.time]; }; ResetSheet: PROC[score: ScorePTR, t1, t2: Time] = { line: CARDINAL; old1, old2: Section; new1, new2: Section; line _ Sheet.FindLine[score.sheet, MIN[t1, t2]]; old1 _ score.sheet[line]; line _ Sheet.NextLine[score.sheet, line]; old2 _ score.sheet[line]; Sheet.Reset[score]; line _ Sheet.FindLine[score.sheet, MIN[t1, t2]]; new1 _ score.sheet[line]; line _ Sheet.NextLine[score.sheet, line]; new2 _ score.sheet[line]; IF old1 # new1 THEN SetDirty[score, new1.time, new1.time+15]; IF old2 # new2 THEN SetDirty[score, new2.time, new2.time+15]; IF old1.key # new1.key THEN SetDirty[score, 0, LAST[Time]]; IF old2.key # new2.key THEN SetDirty[score, 0, LAST[Time]]; }; GetCarry: PROC[score: ScorePTR, s: EventPTR] RETURNS[Time] = { index: CARDINAL; IF ~Event.Clef[s] AND s.type # keySignature THEN RETURN[s.time]; IF s.type = keySignature AND Score.GetKey[score, s.time-1] = Event.KeySignature[s].key THEN RETURN[s.time]; index _ Event.GetScoreIndex[score, s]; IF Event.Clef[s] THEN FOR i: CARDINAL IN (index..score.length] DO staves: StavesPTR; IF i = score.length THEN RETURN[LAST[Time]]; IF score[i].type # staves THEN LOOP; staves _ Event.Staves[score[i]]; IF staves.staves = style AND ~Similar[Event.Staves[s], staves] THEN RETURN[score[i].time]; IF staves.staves = style THEN LOOP; IF Event.GetStaff[staves, Event.Staves[score[i]].value].y # Event.GetStaff[Event.Staves[s], Event.Staves[s].value].y THEN LOOP; RETURN[score[i].time]; ENDLOOP; IF s.type = keySignature THEN FOR i: CARDINAL IN (index..score.length] DO IF i = score.length THEN RETURN[LAST[Time]]; IF score[i].type # keySignature THEN LOOP; RETURN[score[i].time]; ENDLOOP; RETURN[s.time]; }; Similar: PROC[oldS, newS: StavesPTR] RETURNS[BOOL] = { n, o: BOOL; IF oldS.length # newS.length THEN RETURN[FALSE]; FOR i: CARDINAL IN [1..newS.length) DO n _ newS.staff[i].y = newS.staff[i-1].y; o _ oldS.staff[i].y = oldS.staff[i-1].y; IF n # o THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; Clef: PROC[sheet: SheetPTR, staff: CARDINAL, time: Time] RETURNS[INTEGER] = { staves: StavesPTR = sheet[Sheet.FindSection[sheet, time]].staves; RETURN[staves.staff[staff].pitch]; }; Flash: PUBLIC PROC[score: ScorePTR] = { SetBrush[score, black, invert]; Graphics.DrawBox[score.sheet.context, [0, 0, 1024, 808]]; Wait[1]; Graphics.DrawBox[score.sheet.context, [0, 0, 1024, 808]]; score.flash _ FALSE; }; Wait: PUBLIC ENTRY PROC[ticks: CARDINAL] = { FOR i: CARDINAL IN [0..ticks) DO WAIT timeout; ENDLOOP; }; Process.SetTimeout[@timeout, 1]; END. InterfaceImplB.mesa Copyright (C) 1983, 1984 Xerox Corporation. All rights reserved. Author: John Maxwell Last Edited by: Maxwell, November 22, 1983 8:19 am Edited by Doug Wyatt, June 15, 1984 10:39:30 am PDT ****************************************************************** deleting objects ****************************************************************** IF type = staves THEN y _ score.sheet[Sheet.FindLine[score.sheet, time]].y; IF score.sheet[Sheet.FindLine[score.sheet, time]].y # y THEN SetDirty[score, begin, endTime]}; ****************************************************************** inserting objects ****************************************************************** how much of the score does this affect? WE HAVE A MOVEMENT should we move down to the next staff/line? should we move up to the next staff/line? ****************************************************************** moving things ****************************************************************** y _ score.sheet[Sheet.FindLine[score.sheet, old]].y; ****************************************************************** utility procedures ****************************************************************** get state before reset get state after reset compare before and after is this insertion/deletion a NOOP? Ê(˜šœ™Jšœ@™@Jšœ™Jšœ2™2J™3J˜—šÏk ˜ J˜ Jšœœ˜Jšœœa˜lJšœ œ ˜Jšœ œ˜-J˜ Jšœœ<˜FJšœœ@˜KJšœœ˜Jšœœ ˜Jšœœ1˜Jšœœ˜Jšœ œ˜J˜J˜J˜šœ ˜J˜)Jšœ(œœœ˜8J˜J˜Jšœ˜—J˜$Jš œœœœœ˜!šœ˜J˜4J˜7J˜7Jšœ˜—J˜J˜MJ˜/J˜Jšœ˜—J˜ š œ œœ œœœ˜IJšœ"œŸ˜CJ˜Jšœœ˜Jšœœœœ ˜@Jšœ"™"šœœ˜Jšœ:œœ ˜N—J˜&šœœ˜šœœœ˜+J˜Jšœœœœ˜,Jšœœœ˜$J˜ Jšœœ#œœ˜ZJšœœœ˜#šœ8˜:Jšœ;œœ˜E—Jšœ˜Jšœ˜——šœœ˜šœœœ˜+Jšœœœœ˜,Jšœœœ˜*Jšœ˜Jšœ˜——Jšœ ˜Jšœ˜—J˜šžœœœœ˜6Jšœœ˜ Jšœœœœ˜0šœœœ˜&J˜(J˜(Jšœœœœ˜Jšœ˜—Jšœœ˜ Jšœ˜—J˜š žœœœœœ˜MJ˜AJšœ˜"Jšœ˜—J˜šžœœœ˜'J˜J˜9J˜J˜9Jšœœ˜Jšœ˜—J˜š žœœœœœ˜,šœœœ œ˜!Jšœ ˜Jšœ˜—Jšœ˜—J˜J˜ J˜Jšœ˜—…—H~_´