-- Author: John Maxwell
-- last modified: November 8, 1983 3:18 pm

DIRECTORY
 Beam USING [Drawn],
 -- Device USING [Handle],
 Event USING [Adjust, Draw, GetScoreIndex],
 Graphics USING [DrawBox, GetBounds, SetCP, SetStipple],
 MusicDefs,
 Note USING [DrawTie],
 Piece USING [AddEvent, NearestEvent, RemoveEvent],
 -- PressDefs USING [PressFileDescriptor, WritePage],
 -- PressDeviceImpl USING [DataRef],
 Score USING [Draw, GetKey, Justify, LogicalToPhysical, maxCacheLength, ScalePhysical, ShowPitch],
 Selection USING [Draw],
 Sheet USING [
  Draw, FindLine, FindSection, Height, HiLite, Map,
  MapHeight, MapNote, NearestTime, Reset, Scale, ScreenPoint, SetBegin, SetStyle],
 String USING [AppendDecimal],
 Utility;

ScoreImpl: PROGRAM
IMPORTS Beam, Event, Graphics, MusicDefs, Note, Piece, Score, Selection, Sheet, String, Utility
 EXPORTS MusicDefs, Score
 -- SHARES PressDeviceImpl -- =
BEGIN
OPEN Graphics, MusicDefs, Score, Utility;

Error: PUBLIC SIGNAL[s: STRING] = CODE;

-- ****************************************************************************
-- cache
-- ****************************************************************************

-- cache: PUBLIC ARRAY [0..maxCacheLength) OF EventPTR;
-- cacheLength: PUBLIC CARDINAL ← 0;
-- currentKey: INTEGER ← 0;
-- keyStart, keyStop: Time ← 0;

BuildCache: PUBLIC PROCEDURE[score: ScorePTR] =
BEGIN
[] ← GetKey[score, 0];
[] ← GetTimeSignature[score, 0];
[] ← GetMetrenome[score, 0];
END;

-- ****************************************************************************
-- drawing the score
-- ****************************************************************************

Draw: PUBLIC PROCEDURE[score: ScorePTR, erase: BOOLEAN] =
BEGIN
Selection.Draw[score.selection]; -- remove selection from screen
IF erase THEN {
  SetBrush[score.sheet.context, white, opaque];
  Graphics.DrawBox[score.sheet.context, Graphics.GetBounds[context]];
  SetBrush[score.sheet.contextblack, transparent];
  Sheet.Draw[score.sheet]};
DrawInterval[score, begin, endTime];
Selection.Draw[score.selection];
END;

Redraw: PUBLIC PROCEDURE[score: ScorePTR, t1, t2: Time] =
BEGIN
x, y: INTEGER;
select: BOOLEAN;
selection: SelectionPTR ← score.selection;
SetBrush[score.sheet.context, black, transparent];
SELECT score.sheet.scale FROM
 1 => IF t2-t1 > 700 THEN {Score.Draw[]; RETURN};
 2 => IF t2-t1 > 2000 THEN {Score.Draw[]; RETURN};
 4 => IF t2-t1 > 8000 THEN {Score.Draw[]; RETURN};
 ENDCASE;
select ← selection.lineSelect AND selection.select2 > t1-40 AND selection.select1 < t2+50;
IF select THEN Selection.Draw[selection]; -- remove selection from screen
Sheet.HiLite[score.sheet, white, t1-20, t2+30];
DrawInterval[score, t1-30, t2+40];
IF select THEN Selection.Draw[selection];
IF selection.lineSelect THEN RETURN;
SetBrush[score.sheet.context, black, invert];
FOR i: CARDINAL IN [0..selection.length) DO
  IF selection.note[i] = NIL THEN LOOP;
IF selection[i].sync.time > t2+40 OR selection[i].sync.time < t1-30 THEN LOOP;
IF ~Note.InVoice[selection[i], selection.voice] THEN LOOP;
[x, y] ← Sheet.MapNote[score.sheet, selection[i]];
Graphics.SetCP[score.sheet.context, x, y];
DrawChar[score.sheet.context, 170C];
ENDLOOP;
END;

DrawInterval: PROCEDURE[score: ScorePTR, t1, t2: Time] =
BEGIN
n: NotePTR;
staves: StavesPTR;
j: CARDINAL ← 0;
sheet: SheetPTR ← score.sheet;
[] ← Beam.Drawn[score, NIL]; -- clears the beam cache
SetBrush[sheet.context, black, transparent];
-- draw octava markings first
staves ← sheet.section[Sheet.FindSection[sheet, t1]].staves;
FOR i: CARDINAL IN [0..staves.length) DO
 IF staves.staff[i].pitch # 15 AND staves.staff[i].pitch # 60 THEN LOOP;
 FOR j: CARDINAL DECREASING IN [0..score.length) DO
  IF score.event[j].time > = t1 THEN LOOP;
  IF score.event[j].type # staves THEN LOOP;
  WITH ev: score.event[j] SELECT FROM
   staves => {IF ev.staves NOT IN [octava1..octava2] THEN LOOP;
    IF score.event[j].index # i THEN LOOP;
    IF score.event[j].type = octava2 THEN EXIT};
   ENDCASE => ERROR;
  Event.Draw[score.event[j]];
  EXIT; ENDLOOP;
 ENDLOOP;
-- pre-Adjust the first 10 syncs (beams may draw notes in syncs downstream)
j ← 0;
IF score.sheet.display = graphical THEN
FOR i: CARDINAL IN [0..score.length) DO
IF score.event[i].time < t1 THEN LOOP;
IF score.event[i].time > t2 THEN EXIT;
   IF j = 10 THEN EXIT ELSE j ← j+1;
   Event.Adjust[score.event[i]];
   ENDLOOP;
-- draw the syncs
j ← 0;
FOR i: CARDINAL IN [0..score.length) DO
IF score.event[i].time < t1 THEN LOOP;
IF score.event[i].time > t2 THEN {j ← i; EXIT};
IF score.sheet.display = graphical THEN Event.Adjust[score.event[MIN[scoreLength-1, i+10]]];
Event.Draw[score.event[i]];
IF NOT print AND AnyBug[] THEN EXIT;
ENDLOOP;
-- draw ties that go off the end of the section
IF j # 0 THEN FOR i: CARDINAL IN [j..score.length) DO
 IF score.event[i].time > t2+200 THEN EXIT;
 IF score.event[i].type = sync THEN {
  sync: SyncPTR ← Event.Sync[score.event[i]];
  FOR j: CARDINAL IN [0..sync.length) DO
   IF (n ← sync.note[j]) = NIL THEN EXIT;
   IF n.tie = NIL THEN LOOP;
   IF n.tie.sync.time > t2 THEN LOOP;
   IF ~Note.InVoice[n, score.selection.voice]
    THEN Graphics.SetStipple[score.sheet.context, light]
    ELSE Graphics.SetStipple[score.sheet.context, black];
   Note.DrawTie[score, n];
   ENDLOOP;
 ENDLOOP;
[] ← Beam.Drawn[score, NIL];
END;

-- ****************************************************************************
-- printing the score
-- ****************************************************************************

Print: PUBLIC PROCEDURE[splines: BOOLEAN] = {};

-- ****************************************************************************
-- changing the view
-- ****************************************************************************

Look: PUBLIC PROCEDURE[look: LookCommand, switch: BOOLEAN, n: INTEGER] =
BEGIN
draw: BOOLEAN ← TRUE;
SELECT look FROM
accidental => show.accidental ← switch;
hardcopy => {Sheet.Scale[IF switch THEN 2 ELSE 1]; hardcopy ← switch};
justified => {TF ← n; Score.Justify[select1, select2]};
logical => Score.LogicalToPhysical[select1, select2];
noCarry => show.noCarry ← switch;
notehead => show.structure ← show.notehead ← switch;
overview => Overview[switch];
physical => Score.ScalePhysical[512/n];
sheet => Sheet.SetStyle[n, 0, EndOfScore[]];
sync => show.sync ← switch;
voice => {voice ← switch; selectedVoice ← n};
ENDCASE;
END;

Overview: PROCEDURE[switch: BOOLEAN] =
BEGIN
time: Time;
x, y: INTEGER;
SELECT TRUE FROM
 scale < 4 AND ~switch => {flash ← TRUE; RETURN};
 scale > 2 AND switch => {flash ← TRUE; RETURN};
 switch => {Sheet.Scale[4]; Sheet.SetBegin[0]};
 YellowBug[] => {
  [x, y] ← Sheet.ScreenPoint[];
  time ← Sheet.NearestTime[x, y].time;
  Sheet.Scale[IF hardcopy THEN 2 ELSE 1];
  Sheet.SetBegin[time]};
 ENDCASE => {Sheet.Scale[IF hardcopy THEN 2 ELSE 1]; Sheet.SetBegin[0]};
END;

-- ****************************************************************************
-- procedures that handle keys
-- ****************************************************************************

phi: ARRAY [0..12) OF INTEGER = [6, 1, -4, 3, -2, 5, 0, 7, 2, -3, 4, -1];
flatHeight: ARRAY[0..12) OF INTEGER = [0, 4, 8, 8, 12, 12, 16, 16, 20, 24, 24, 28];
sharpHeight: ARRAY[0..12) OF INTEGER = [0, 4, 4, 8, 8, 12, 12, 16, 20, 20, 24, 24];

SetKey: PUBLIC PROCEDURE[score: ScorePTR, key: INTEGER, time1, time2: Time] =
BEGIN
oldKey: INTEGER;
keySig: LONG POINTER TO EventRec.keySignature ← NIL;
IF time1 > time2 THEN RETURN;
IF k NOT IN [-7..7] THEN RETURN;
score.sheet.min ← 0;
score.sheet.max ← EndOfScore[]+2000;
score.sheet.accidental ← TRUE;
oldKey ← Score.GetKey[score, time2];
FOR i: CARDINAL DECREASING IN [0..score.length) DO
IF score.event[i].time > time2 THEN LOOP;
IF score.event[i].time < time1 THEN EXIT;
IF score.event[i].type # keySignature THEN LOOP;
  Piece.DeleteEvent[score, score.event[i]];
-- Utility.FreeEvent[@score.event[i]];
ENDLOOP;
-- put in the new key signature
IF k # Score.GetKey[score, time1] THEN {
 keySig ← zone.NEW[EventRec.keySignature];
 keySig.time ← time1;
 keySig.key ← k;
 Piece.AddEvent[score, keySig]};
-- put back the old signature
IF oldKey # k AND time2 < EndOfScore[score] THEN {
 keySig ← zone.NEW[EventRec.keySignature];
 keySig.time ← time2;
 keySig.key ← oldKey;
 Piece.AddEvent[score, keySig]};
score.cache.keyStart ← score.cache.keyStop ← 0; -- invalidate cache
Sheet.Reset[score.sheet]; -- rebuild sheet
END;

GetKey: PUBLIC PROCEDURE[score: ScorePTR, t: Time] RETURNS[key: INTEGER ← 0] =
BEGIN -- caches the last key range
IF t IN [score.cache.keyStart..score.cache.keyStop) THEN RETURN[score.cache.key];
score.cache.key ← 0;
score.cache.keyStart ← score.cache.keyStop ← 0;
FOR i: CARDINAL IN [0..score.length) DO
 IF score.event[i].type # keySignature THEN LOOP;
 IF score.event[i].time > t THEN {score.cache.keyStop ← score.event[i].time; EXIT};
 WITH ev: score.event[i] SELECT FROM
  keySignature => {score.cache.key ← ev.key; score.cache.keyStart ← ev.time};
  ENDCASE => ERROR;
 ENDLOOP;
IF score.cache.keyStop = 0 THEN score.cache.keyStop ← EndOfScore[score];
RETURN[score.cache.key];
END;

GetAccidental: PUBLIC PROCEDURE[n: NotePTR] RETURNS[Accidental] =
BEGIN -- hack to set n.shown
n.shown ← GetAccInternal[n];
RETURN[n.shown];
END;

GetAccInternal: PROCEDURE[n: NotePTR] RETURNS[Accidental] = INLINE
BEGIN
a: Accidental;
prior: NotePTR;
index: CARDINAL;
pitch, key: INTEGER;
normal: Accidental;
noteHeight: INTEGER;
IF NOT show.accidental THEN RETURN[inKey];
IF n.tie # NIL AND n.tie.pitch = n.pitch THEN RETURN[inKey];
IF n.show AND n.spelled = inKey THEN Error["show inKey?"];
IF n.show THEN RETURN[n.spelled];
IF show.noCarry THEN {
 IF n.spelled # inKey THEN RETURN[n.spelled];
 a ← DefaultAcc[0, n.pitch];
 IF a = natural THEN a ← inKey;
 RETURN[a]};
key ← Score.GetKey[n.sync.time];
pitch ← Score.ShowPitch[n.pitch, n.spelled, key];
noteHeight ← Sheet.Height[n.sync.time, pitch, n.staff];
index ← Event.GetScoreIndex[n.sync];
IF index = scoreLength THEN index ← EventIndex[n.sync.time];
prior ← PriorNote[pitch, noteHeight, index];
normal ← NormalAcc[key, pitch];
-- no prior note
IF prior = NIL THEN SELECT TRUE FROM
 normal = inKey => RETURN[n.spelled];
 n.spelled = inKey => RETURN[normal];
 ENDCASE => RETURN[n.spelled];
--  n.spelled = normal => RETURN[inKey];
--  n.spelled # normal => RETURN[n.spelled];
--  ENDCASE;
-- prior note
IF prior.pitch = n.pitch THEN RETURN[inKey];
-- an accidental MUST be asserted
IF n.spelled # inKey THEN RETURN[n.spelled];
IF normal # inKey THEN RETURN[normal];
IF Mod[n.pitch, 12] = 7 AND key < -5 THEN RETURN[flat];
IF Mod[n.pitch, 12] = 0 AND key < -6 THEN RETURN[flat];
IF Mod[n.pitch, 12] = 1 AND key > 5 THEN RETURN[sharp];
IF Mod[n.pitch, 12] = 8 AND key > 6 THEN RETURN[sharp];
normal ← NormalAcc[0, n.pitch];
IF normal = inKey THEN normal ← natural;
IF key < 0 AND normal = sharp THEN normal ← flat;
RETURN[normal];
END;

EventIndex: PROCEDURE[time: Time] RETURNS[s: CARDINAL] = INLINE
BEGIN
s ← Piece.NearestEvent[score, time];
IF score.event[s] # NIL AND score.event[s].time > = time THEN RETURN[s];
FOR i: CARDINAL DECREASING IN [0..s+10) DO
 IF score.event[i] = NIL THEN LOOP;
 IF score.event[i].time > time THEN LOOP;
 RETURN[i+1];
 ENDLOOP;
END;

PriorNote: PUBLIC PROCEDURE[pitch, height: INTEGER, index: CARDINAL] RETURNS[NotePTR] =
BEGIN
n: NotePTR;
newPitch, key: INTEGER;
FOR i: CARDINAL DECREASING IN [0..index) DO
 IF Measure[score.event[i].type] THEN RETURN[NIL];
 IF score.event[i].type # notes THEN LOOP;
 key ← Score.GetKey[score.event[i].time];
 FOR j: CARDINAL IN [0..syncLength) DO
  IF (n ← score.event[i].event[j]) = NIL THEN EXIT;
  IF n.rest THEN LOOP;
  newPitch ← Score.ShowPitch[n.pitch, n.spelled, key];
  IF NOT newPitch IN [pitch-2..pitch+2] THEN LOOP;
  IF Sheet.Height[score.event[i].time, newPitch, n.staff] # height THEN LOOP;
  RETURN[n];
  ENDLOOP;
 ENDLOOP;
RETURN[NIL];
END;

assert: Accidental = LOOPHOLE[LOOPHOLE[LAST[Accidental], CARDINAL]+1];

NormalAcc: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] =
BEGIN
strangeness: INTEGER ← phi[Mod[pitch, 12]];
-- i bet you don't really believe this works.
SELECT TRUE FROM
Mod[(key-strangeness), 12] > 4 => RETURN[inKey];
strangeness > 0 => RETURN[natural];
key < 0 => RETURN[flat];
ENDCASE => RETURN[sharp];
END;

DefaultAcc: PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] =
BEGIN
SELECT NormalAcc[0, pitch] FROM
 inKey => RETURN[natural];
 sharp => RETURN[IF key < 0 THEN flat ELSE sharp];
 ENDCASE => ERROR;
END;

AddToPitch: PUBLIC PROCEDURE[key, pitch, delta: INTEGER] RETURNS[INTEGER] =
BEGIN
i: INTEGER;
IF delta > 0 THEN FOR i IN (pitch..90] DO
  IF NormalAcc[key, i] = inKey THEN delta ← delta-1;
  IF delta = 0 THEN RETURN[i];
  ENDLOOP
 ELSE FOR i DECREASING IN [0..pitch) DO
  IF NormalAcc[key, i] = inKey THEN delta ← delta+1;
  IF delta = 0 THEN RETURN[i];
  ENDLOOP;
ERROR;
END;

KeyHeight: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[INTEGER] =
BEGIN
i: CARDINAL ← Mod[pitch, 12];
SELECT TRUE FROM
 i = 7 AND key < -5 => RETURN[flatHeight[8]];
 i = 0 AND key < -6 => RETURN[flatHeight[1]];
 key < 0 => RETURN[flatHeight[i]];
 i = 1 AND key > 5 => RETURN[sharpHeight[0]];
 i = 8 AND key > 6 => RETURN[sharpHeight[7]];
 ENDCASE => RETURN[sharpHeight[i]];
END;


DrawKey: PUBLIC PROCEDURE[key: INTEGER, time: Time] =
BEGIN
n: INTEGER = 8;
oldStaff: Staff ← [0, 0];
x, y, offset: INTEGER;
l: CARDINAL ← Sheet.FindLine[time];
staves: StavesPTR ← sheet[l].staves;
IF NOT show.accidental THEN RETURN;
IF time IN [sheet[l].time-10..sheet[l].time) THEN RETURN;
IF time IN (sheet[l].time..sheet[l].time+15] THEN time ← sheet[l].time;
x ← Sheet.MapHeight[time, 0].x;
FOR i: CARDINAL IN [0..staves.sl] DO
IF staves.staff[i] = oldStaff THEN LOOP;
oldStaff ← staves.staff[i];
offset ← Sheet.Height[time, oldStaff.pitch, i];
IF Mod[staves.staff[i].pitch, 12] = 3 THEN offset ← offset - 8;
[x, y] ← Sheet.MapHeight[time, offset];
IF sheet[l].time = time THEN x ← 3;
IF key > 0 THEN SlapDown[x , 32+y, sharp];
IF key > 1 THEN SlapDown[x+ n, 20+y, sharp];
IF key > 2 THEN SlapDown[x+2*n, 36+y, sharp];
IF key > 3 THEN SlapDown[x+3*n, 24+y, sharp];
IF key > 4 THEN SlapDown[x+4*n, 12+y, sharp];
IF key > 5 THEN SlapDown[x+5*n, 28+y, sharp];
IF key > 6 THEN SlapDown[x+6*n, 16+y, sharp];
IF key < 0 THEN SlapDown[x , 16+y, flat];
IF key < -1 THEN SlapDown[x+ n, 28+y, flat];
IF key < -2 THEN SlapDown[x+2*n, 12+y, flat];
IF key < -3 THEN SlapDown[x+3*n, 24+y, flat];
IF key < -4 THEN SlapDown[x+4*n, 8+y, flat];
IF key < -5 THEN SlapDown[x+5*n, 20+y, flat];
IF key < -6 THEN SlapDown[x+6*n, 4+y, flat];
ENDLOOP;
END;

SlapDown: PROCEDURE[x, y: INTEGER, a: Accidental] =
BEGIN
Graphics.SetCP[context, x, y];
SELECT a FROM
flat => DrawChar[context, 111C];
natural => DrawChar[context, 112C];
sharp => DrawChar[context, 113C];
ENDCASE;
END;

-- ****************************************************************************
-- score attributes: metrenome
-- ****************************************************************************

SetMetrenome: PUBLIC PROCEDURE[time1, time2: Time, m: INTEGER] =
BEGIN
i, j: CARDINAL ← 0;
sync: EventPTR ← NIL;
IF time1 > time2 THEN RETURN;
min ← time1;
max ← time1+50;
-- remove old metrenomes
FOR i DECREASING IN [0..cacheLength) DO
IF score.event[i].time > time2 THEN LOOP;
IF score.event[i].time < time1 THEN EXIT;
IF score.event[i].type # metrenome THEN LOOP;
max ← MAX[max, score.event[i].time+50];
 Piece.RemoveEvent[score, score.event[i]];
Utility.FreeEvent[@score.event[i]];
ENDLOOP;
-- insert new one
sync ← Utility.NewEvent[];
sync.time ← time1;
sync.type ← metrenome;
sync.value ← m;
Piece.AddEvent[score, sync];
BuildCache[];
END;

GetMetrenome: PUBLIC PROCEDURE[t: Time] RETURNS[INTEGER] =
BEGIN
i: CARDINAL;
sync: EventPTR ← NIL;
metrenome: INTEGER ← 0;
FOR i IN [0..cacheLength) DO
IF score.event[i] = NIL THEN EXIT;
IF score.event[i].type # metrenome THEN LOOP;
IF score.event[i].time > t THEN EXIT;
sync ← score.event[i];
ENDLOOP;
IF sync = NIL THEN metrenome ← 128 ELSE metrenome ← sync.value;
RETURN[metrenome];
END;

DrawMetrenome: PUBLIC PROCEDURE[metrenome: INTEGER, time: Time] =
BEGIN
x, y: INTEGER;
string: STRING ← [10];
IF print THEN RETURN;
[x, y] ← Sheet.Map[time, , 0];
Graphics.SetCP[context, x, y+40];
DrawChar[context, 't];
String.AppendDecimal[string, metrenome];
Utility.SetFont[context, text, 12];
DrawChar[context, ' = ];
DrawString[context, string];
Utility.SetFont[context, music, 8];
END;

-- ****************************************************************************
-- time signature
-- ****************************************************************************

SetTimeSignature: PUBLIC PROCEDURE[time1, time2: Time, ts: TimeSignature] =
BEGIN
i, j: CARDINAL ← 0;
sync: EventPTR ← NIL;
oldTS: TimeSignature ← [0, 0];
IF time1 > time2 THEN RETURN;
min ← time1;
max ← time1+50;
-- remove old time signature
FOR i IN [0..cacheLength) DO
IF score.event[i].time > time2 THEN EXIT;
IF score.event[i].type # timeSignature THEN LOOP;
IF score.event[i].time < time1 THEN {oldTS ← score.event[i].ts; LOOP};
 sync ← score.event[i];
max ← MAX[max, sync.time+50];
 Piece.RemoveEvent[score, sync];
Utility.FreeEvent[@sync];
ENDLOOP;
-- insert new time signature
IF ts.top # 0 AND ts.bottom # 0 THEN {
 sync ← Utility.NewEvent[];
 sync.time ← time1+10;
 sync.type ← timeSignature;
 sync.ts ← ts;
 Piece.AddEvent[score, sync]};
IF oldTS.top # 0 AND time2 < EndOfScore[] THEN {
 sync ← Utility.NewEvent[];
 sync.time ← time2+10;
 sync.type ← timeSignature;
 sync.ts ← oldTS;
 Piece.AddEvent[score, sync];
 SetDirty[sync.time, sync.time]};
BuildCache[];
END;

GetTimeSignature: PUBLIC PROCEDURE[t: Time] RETURNS[TimeSignature] =
BEGIN
i: CARDINAL;
sync: EventPTR ← NIL;
ts: TimeSignature ← [4, 4];
FOR i IN [0..cacheLength) DO
IF score.event[i] = NIL THEN EXIT;
IF score.event[i].type # timeSignature THEN LOOP;
IF score.event[i].time > t THEN EXIT;
sync ← score.event[i];
ENDLOOP;
IF sync # NIL THEN ts ← sync.ts;
RETURN[ts];
END;

DrawTimeSignature: PUBLIC PROCEDURE[ts: TimeSignature, time: Time] =
BEGIN
i: CARDINAL;
x, y: INTEGER;
oldStaff: Staff;
staves: StavesPTR = sheet[Sheet.FindSection[time]].staves;
FOR i IN [0..staves.sl] DO
IF staves.staff[i] = oldStaff THEN LOOP;
oldStaff ← staves.staff[i];
[x, y] ← Sheet.Map[time, , i];
 x ← x-5;
Graphics.SetCP[context, x, y+8];
SELECT ts.bottom FROM
  2 => DrawChar[context, 062C];
  4 => DrawChar[context, 064C];
  8 => DrawChar[context, 070C];
  ENDCASE;
Graphics.SetCP[context, x, y+24];
SELECT ts.top FROM
  1 => DrawChar[context, 061C];
  2 => DrawChar[context, 062C];
  3 => DrawChar[context, 063C];
  4 => DrawChar[context, 064C];
  5 => DrawChar[context, 065C];
  6 => DrawChar[context, 066C];
  7 => DrawChar[context, 067C];
  8 => DrawChar[context, 070C];
  9 => DrawChar[context, 071C];
  12 => {
   Graphics.SetCP[context, x-5, y+24];
   DrawString[context, "12"]};
  ENDCASE;
ENDLOOP;
END;

END..e6(0, 3648)(1, 4304)(2, 4939)\965b5B22b11B22b5B34b4B25b2B24b8B107i6I81b5B46b11B23b10B387i18I81b4B250b6B952i16I26i26I408i72I240i15I276i44I538i19I81b5B740i17I81b4B1219i27I290b6B500i28I159i26I189i18I8b6B286b13B1835b9B671b9B109i42I383b11B351b9B352b7B1670i27I81b12B596b12B357b13B446i15I81b16B915b16B334b17B


GetAccidental: PUBLIC PROCEDURE[note: NotePTR] RETURNS[Accidental] =
BEGIN
a: Accidental;
pitch, newPitch, key: INTEGER;
noteHeight: INTEGER;
i, j: CARDINAL;
t: Time;
n: NotePTR;
IF NOT show.accidental THEN RETURN[inKey];
IF note.tie # NIL THEN RETURN[inKey];
t ← note.sync.time;
-- leftEdge ← sheet[Sheet.FindLine[t]].time;
key ← Score.GetKey[t];
IF note.spelled = inKey THEN a ← NormalAccidental[key, note.pitch]
   ELSE a ← note.spelled;
IF show.noCarry THEN RETURN[a];
pitch ← Score.ShowPitch[note.pitch, note.spelled, key];
noteHeight ← Sheet.Height[t, pitch, note.staff];
FOR i DECREASING IN [0..Event.GetScoreIndex[note.sync]) DO
 IF score.event[i].time > = note.sync.time THEN LOOP;
 IF score.event[i].type IN Measure THEN RETURN[a];
 IF score.event[i].type # notes THEN LOOP;
--  IF score.event[i].time < leftEdge THEN RETURN[a];
 FOR j IN [0..syncLength) DO
  IF (n ← score.event[i].event[j]) = NIL THEN EXIT;
  IF n.rest THEN LOOP;
  newPitch ← Score.ShowPitch[n.pitch, n.spelled, key];
  IF NOT newPitch IN [pitch-2..pitch+2] THEN LOOP;
  IF Sheet.Height[t, newPitch, n.staff] # noteHeight THEN LOOP;
  -- there's a note prior to this on the same section
  IF n.pitch = note.pitch THEN RETURN[inKey]; -- don't assert accidental
  IF note.spelled # inKey THEN RETURN[note.spelled];
  a ← NormalAccidental[0, pitch];
  IF a = inKey THEN RETURN[natural]
   ELSE RETURN[IF key > = 0 THEN sharp ELSE flat];
  ENDLOOP;
 ENDLOOP;
RETURN[a];
END;

AddToPitch: PUBLIC PROCEDURE[key, pitch, delta: INTEGER] RETURNS[INTEGER] =
BEGIN
i: INTEGER;
IF delta > 0 THEN FOR i IN (pitch..90] DO
  IF NormalAccidental[key, i] = inKey THEN delta ← delta-1;
  IF delta = 0 THEN RETURN[i];
  ENDLOOP
 ELSE FOR i DECREASING IN [0..pitch) DO
  IF NormalAccidental[key, i] = inKey THEN delta ← delta+1;
  IF delta = 0 THEN RETURN[i];
  ENDLOOP;
ERROR;
END;

NormalAccidental: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] =
BEGIN
strangeness: INTEGER ← phi[Mod[pitch, 12]];
-- i bet you don't really believe this works.
SELECT TRUE FROM
Mod[(key-strangeness), 12] > 4 => RETURN[inKey];
strangeness > 0 => RETURN[natural];
key < 0 => RETURN[flat];
ENDCASE => RETURN[sharp];
END;


Print: PUBLIC PROCEDURE[splines: BOOLEAN] =
BEGIN
printAll: BOOLEAN;
oldBegin: Time ← begin;
ph: POINTER TO PressDefs.PressFileDescriptor;
device: Device.Handle ← Utility.OpenPressDevice[splines];
ph ← LOOPHOLE[device.data, PressDeviceImpl.DataRef].pressHandle;
printAll ← ~splines AND scale = 2;
IF printAll THEN Sheet.SetBegin[0];
FOR i: CARDINAL IN [0..scoreLength) DO
 Event.Adjust[score.event[i]];
 ENDLOOP;
WHILE TRUE DO
Sheet.Draw[];
DrawInterval[begin, endTime];
IF endTime > EndOfScore[]-40 THEN EXIT;
IF printAll THEN {Sheet.SetBegin[endTime]; PressDefs.WritePage[ph]} ELSE EXIT;
ENDLOOP;
Utility.ClosePressDevice[@device];
Sheet.SetBegin[oldBegin];
END;