-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 12:02 pm
DIRECTORY
Beam USING [Draw, Drawn, SetSyncs],
Chord USING [Adjust, Draw],
Event USING [Free, GetScoreIndex, GetOctava, GetStaff, KeySignature, Measure, Metrenome, TimeSignature, Staves, Sync],
Graphics USING [DrawBox, SetCP, SetStipple],
Inline USING [LongCOPY],
MusicDefs,
Note USING [FindChord, Delta, Draw, InVoice],
Score USING [DrawTimeSignature, DrawMetrenome, GetAccidental, GetKey],
Sheet USING [DrawClef, DrawKey, DrawOctava, FindSection, FindStaves, Map, MapNote, Reset],
Utility USING [DrawChar, DrawLine];
EventImpl: PROGRAM
IMPORTS Beam, Chord, Graphics, Inline, MusicDefs, Note, Score, Sheet, Event, Utility
EXPORTS Event =
BEGIN
OPEN Graphics, MusicDefs, Utility;
GetScoreIndex: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR]
RETURNS[index: CARDINAL ← 0] = BEGIN
binary: CARDINAL ← 8192;
IF event = NIL THEN RETURN[score.length];
FOR i: CARDINAL IN [0..13) DO
binary ← binary/2;
IF index + binary >= score.length THEN LOOP;
IF score[index+binary].time <= event.time THEN index ← index + binary;
IF score[index].time # event.time THEN LOOP;
IF score[index] = event THEN RETURN[index];
-- must be a nearby sync
FOR j: CARDINAL IN (index..score.length) WHILE score[j].time = event.time DO
IF score[j] = event THEN RETURN[j]; ENDLOOP;
FOR j: CARDINAL DECREASING IN [0..index) WHILE score[j].time = event.time DO
IF score[j] = event THEN RETURN[j]; ENDLOOP;
EXIT; ENDLOOP;
-- FAILURE: score isn't sorted. Try again.
FOR i: CARDINAL IN [0..score.length) DO IF score[i] = event THEN RETURN[i]; ENDLOOP;
RETURN[score.length]; -- not in score
END;
Invisible: PUBLIC PROCEDURE[score: ScorePTR, index: CARDINAL, leftEdge: Time] RETURNS[BOOLEAN] =
BEGIN
WITH ev: score[index] SELECT FROM
staves => IF ev.staves # clef THEN RETURN[FALSE];
keySignature => NULL;
ENDCASE => RETURN[FALSE];
RETURN[score[index].time-leftEdge <= 15];
END;
Draw: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR] =
BEGIN
IF event = NIL THEN RETURN;
Graphics.SetStipple[score.sheet.context, black];
SELECT event.type FROM
sync => DrawSync[score, Event.Sync[event]];
staves => {
staves: StavesPTR ← Event.Staves[event];
SELECT staves.staves FROM
style => DrawMeasure[score, measure, event.time, 10];
clef => Sheet.DrawClef[score.sheet, staves.staff[staves.value].pitch,
staves.value, staves.time];
octava1 => {
octava2: StavesPTR = Event.GetOctava[score, staves];
IF octava2 = NIL THEN ERROR;
Sheet.DrawOctava[score.sheet, staves.staff[staves.value].pitch,
staves.value, staves.height, staves.time, octava2.time]};
octava2 => IF Event.GetOctava[score, Event.GetOctava[score, staves]] # staves
THEN DrawMeasure[score, doubleMeasure, staves.time, 20];
-- ELSE already drawn
ENDCASE};
keySignature => Sheet.DrawKey[score.sheet, Event.KeySignature[event].key, Score.GetKey[score, event.time - 1], event.time];
timeSignature => Score.DrawTimeSignature[score, Event.TimeSignature[event].ts, event.time];
metrenome => Score.DrawMetrenome[score, Event.Metrenome[event].metrenome, event.time];
measure => DrawMeasure[score, Event.Measure[event].measure, event.time, 0];
ENDCASE;
END;
DrawMeasure: PROCEDURE[score: ScorePTR, type: MeasureType, time: Time, dy: INTEGER] =
BEGIN
staves: StavesPTR;
x1, y1, top, delta: INTEGER;
IF time = 0 THEN RETURN;
IF score.sheet.printing THEN dy ← 0;
Graphics.SetStipple[score.sheet.context, black];
staves ← Sheet.FindStaves[score.sheet, time];
[x1, y1] ← Sheet.Map[score.sheet, time, , staves.length-1];
y1 ← y1 - dy;
top ← -staves.staff[staves.length-1].y+2*dy;
SELECT type FROM
measure => DrawLine[score.sheet.context, x1, y1, x1, y1+top];
doubleMeasure => {DrawLine[score.sheet.context, x1, y1, x1, y1+top];
IF score.sheet.printing OR score.sheet.scale = 1 THEN delta ← 2 ELSE delta ← 3;
DrawLine[score.sheet.context, x1-delta, y1, x1-delta, y1+top]};
repeat1 => BEGIN DrawBox[score.sheet.context, [x1, y1, x1+3, y1+top]];
DrawLine[score.sheet.context, x1+5, y1, x1+5, y1+top]; END;
repeat2, endMeasure => BEGIN DrawBox[score.sheet.context, [x1+1, y1, x1-2, y1+top]];
DrawLine[score.sheet.context, x1-5, y1, x1-5, y1+top]; END;
ENDCASE;
IF type # repeat1 AND type # repeat2 THEN RETURN;
IF type = repeat1 THEN delta ← 6 ELSE delta ← -13;
FOR i: CARDINAL IN [0..staves.length) DO
[x1, y1] ← Sheet.Map[score.sheet, time, , i];
SetCP[score.sheet.context, x1+delta, y1+16+4]; DrawChar[score.sheet.context, '.];
SetCP[score.sheet.context, x1+delta, y1+16-4]; DrawChar[score.sheet.context, '.];
ENDLOOP;
END;
-- ******************************************************************
-- procedures for handling staves
-- ******************************************************************
SetStave: PUBLIC PROCEDURE[score: ScorePTR, oldS: StavesPTR, newS: StavesPTR] =
BEGIN
n, o: BOOLEAN;
pitch, height: INTEGER;
IF newS = NIL THEN RETURN;
height ← newS.height;
IF newS.staves # style THEN {
pitch ← newS.staff[newS.value].pitch;
newS↑ ← oldS↑;
newS.height ← height;
FOR j: CARDINAL IN [0..newS.length) DO
IF newS.staff[j].y # newS.staff[newS.value].y THEN LOOP;
newS.staff[j].pitch ← pitch;
ENDLOOP;
RETURN};
-- newS.staves = staves
newS↑ ← score.style[newS.value]↑;
newS.height ← height;
-- clef switches carry over if the staff form is the same
IF oldS = NIL THEN RETURN;
IF oldS.length # newS.length THEN RETURN;
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;
ENDLOOP;
FOR i: CARDINAL IN [0..newS.length) DO
newS.staff[i].pitch ← oldS.staff[i].pitch;
ENDLOOP;
END;
GetOctava: PUBLIC PROCEDURE[score: ScorePTR, octava: StavesPTR] RETURNS[StavesPTR] =
BEGIN
OPEN Event; -- USING [GetScoreIndex, GetStaff];
IF octava = NIL THEN RETURN[NIL];
IF octava.staves = octava1 THEN {
FOR i: CARDINAL IN (GetScoreIndex[score, octava]..score.length) DO
staves: StavesPTR;
IF score[i].type # staves THEN LOOP;
staves ← Event.Staves[score[i]];
IF staves.staves # octava2 THEN LOOP;
IF octava.value = staves.value THEN RETURN[staves];
IF GetStaff[octava, octava.value].y = GetStaff[staves, staves.value].y THEN RETURN[staves];
ENDLOOP};
IF octava.staves = octava2 THEN {
FOR i: CARDINAL DECREASING IN [0..GetScoreIndex[score, octava]) DO
staves: StavesPTR;
IF score[i].type # staves THEN LOOP;
staves ← Event.Staves[score[i]];
IF staves.staves # octava1 THEN LOOP;
IF octava.value = Event.Staves[staves].value THEN RETURN[staves];
IF GetStaff[octava, octava.value].y = GetStaff[staves, staves.value].y THEN RETURN[staves];
ENDLOOP};
RETURN[NIL];
END;
-- ****************************************************************************
-- data abstractions for a sync
-- CONSTRAINT: s.note[i] # NIL for all i < s.length
-- CONSTRAINT: s.note[i] = NIL for all s.length <= i < s.max
-- CONSTRAINT: chord.note[i].sync = chord.note[j].sync for all i, j < chord.length
-- ****************************************************************************
AddNote: PUBLIC PROCEDURE[score: ScorePTR, sync: SyncPTR, note: NotePTR] RETURNS[new: SyncPTR] =
BEGIN
c: ChordPTR ← note.chord;
IF note.sync = sync THEN RETURN[sync]; -- already in the sync
FOR i: CARDINAL IN [0..(IF c = NIL THEN 1 ELSE c.length)) DO
IF sync.length = sync.max THEN sync ← NewSync[score, sync.max + 4, sync];
IF c # NIL THEN note ← c.note[i];
RemoveNote[score, note.sync, note, FALSE];
note.sync ← sync;
sync.note[sync.length] ← note;
sync.length ← sync.length + 1;
IF note.beam # NIL THEN Beam.SetSyncs[note.beam];
ENDLOOP;
RETURN[sync];
END;
RemoveNote: PUBLIC PROCEDURE[score: ScorePTR, sync: SyncPTR, note: NotePTR, free: BOOLEAN] =
BEGIN
-- does not automatically remove empty syncs! (see Voice.Correct)
IF note = NIL OR sync = NIL THEN RETURN;
FOR i: CARDINAL IN [0..sync.length) DO
IF sync.note[i] # note THEN LOOP;
sync.length ← sync.length - 1;
sync.note[i] ← sync.note[sync.length];
sync.note[sync.length] ← NIL;
ENDLOOP;
IF free AND sync.length = 0 THEN Event.Free[score, sync];
END;
NewSync: PUBLIC PROCEDURE[score: ScorePTR, length: CARDINAL, old: SyncPTR]
RETURNS[sync: SyncPTR] =
BEGIN
sync ← zone.NEW[EventRec.sync[length]];
IF old # NIL THEN {
nullSize: CARDINAL = SIZE[EventRec.sync[0]];
oldSize: CARDINAL = SIZE[EventRec.sync[old.max]];
Inline.LongCOPY[from: old, nwords: nullSize-1, to: sync]; -- skip 'max'
Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: sync+nullSize];
SetBackPointers[score, old, sync];
zone.FREE[@old];
RETURN};
END;
Free: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR] =
BEGIN
SetBackPointers[score, event, NIL];
zone.FREE[@event];
END;
SetBackPointers: PROC[score: ScorePTR, event, new: EventPTR] =
BEGIN -- changes all pointers to event to be pointers to new
-- an event can be pointed at by a score, the cache, notes, the sheet and the styles
index: CARDINAL ← Event.GetScoreIndex[score, event];
IF index # score.length THEN score[index] ← new;
IF index # score.length AND new = NIL THEN {
score.length ← score.length - 1;
FOR i: CARDINAL IN [0..score.length) DO score[i] ← score[i+1]; ENDLOOP;
score[score.length] ← NIL};
SELECT event.type FROM
sync => {
sync: SyncPTR ← Event.Sync[event];
newSync: SyncPTR ← Event.Sync[new];
FOR i: CARDINAL IN [0..sync.length) DO sync[i].sync ← newSync; ENDLOOP};
measure => NULL;
timeSignature => {
IF score.cache.ts1 = event THEN score.cache.ts1 ← Event.TimeSignature[new];
IF score.cache.ts2 = event THEN score.cache.ts2 ← Event.TimeSignature[new]};
keySignature => {
IF score.cache.key1 = event THEN score.cache.key1 ← Event.KeySignature[new];
IF score.cache.key2 = event THEN score.cache.key2 ← Event.KeySignature[new]};
metrenome => {
IF score.cache.met1 = event THEN score.cache.met1 ← Event.Metrenome[new];
IF score.cache.met2 = event THEN score.cache.met2 ← Event.Metrenome[new]};
staves => Sheet.Reset[score];
ENDCASE => ERROR;
END;
DrawSync: PROCEDURE[score: ScorePTR, s: SyncPTR] =
BEGIN
b: BeamPTR;
c: ChordPTR;
sync: ARRAY [0..16) OF NotePTR;
x, y: INTEGER;
min, vMin: INTEGER ← 1000;
max, vMax: INTEGER ← -1000;
FOR i: CARDINAL IN [0..s.length) DO sync[i] ← s[i]; ENDLOOP;
FOR i: CARDINAL IN [0..s.length) DO
IF s[i] = NIL THEN EXIT;
IF sync[i] = NIL THEN LOOP;
c ← Note.FindChord[s[i]];
b ← s[i].beam;
IF b # NIL AND b.beam # NIL THEN b ← b.beam;
IF c # NIL THEN FOR j: CARDINAL IN [0..c.length) DO
IF c.note[j] = NIL THEN EXIT;
FOR k: CARDINAL IN (i..s.length) DO
IF sync[k] = c.note[j] THEN sync[k] ← NIL;
ENDLOOP;
ENDLOOP;
IF b # NIL AND NOT Beam.Drawn[score, b] THEN BEGIN [] ← Beam.Draw[score, b]; LOOP; END;
IF c # NIL AND b = NIL THEN BEGIN Chord.Draw[score, c]; LOOP; END;
IF b = NIL THEN Note.Draw[score, s[i]];
ENDLOOP;
IF NOT score.sheet.sync THEN RETURN;
-- draw the sync line
FOR i: CARDINAL IN [0..s.length) DO
IF s[i] = NIL THEN EXIT;
[x, y] ← Sheet.MapNote[score.sheet, s[i]];
min ← MIN[min, y];
max ← MAX[max, y];
IF ~Note.InVoice[s[i], score.sheet.voice] THEN LOOP;
vMin ← MIN[vMin, y];
vMax ← MAX[vMax, y];
ENDLOOP;
Graphics.SetStipple[score.sheet.context, light];
IF min # vMin OR max # vMax THEN DrawLine[score.sheet.context, x+4, min-6, x+4, max+6];
Graphics.SetStipple[score.sheet.context, black];
IF vMin # vMax AND vMin # 1000 THEN DrawLine[score.sheet.context, x+4, vMin-6, x+4, vMax+6];
END;
Adjust: PUBLIC PROCEDURE[score: ScorePTR, s: SyncPTR] =
BEGIN
n: NotePTR;
c: ChordPTR;
sync: ARRAY [0..16) OF NotePTR;
delta: INTEGER;
pad: ScratchPad;
top: CARDINAL ← s.length-1;
bottom: CARDINAL ← 0;
length: CARDINAL ← 0;
IF s = NIL THEN RETURN;
IF s.type # sync THEN RETURN;
FOR i: CARDINAL IN [0..s.length) DO sync[i] ← s[i]; ENDLOOP;
-- build the pad
FOR i: CARDINAL IN [0..s.length) DO
IF (n ← s[i]) = NIL THEN EXIT;
n.delta ← n.accDelta ← 0;
IF n.rest THEN LOOP;
c ← Note.FindChord[n];
pad[length].n ← n;
pad[length].c ← c;
pad[length].y ← Sheet.MapNote[score.sheet, n].y;
pad[length].acc ← Score.GetAccidental[score, n];
pad[length].stemUp ← IF c = NIL THEN n.stemUp ELSE c.stemUp;
pad[length].stem ← IF pad[length].stemUp THEN pad[length].y+32 ELSE pad[length].y-32;
length ← length+1;
ENDLOOP;
SortPad[FALSE, @pad];
FOR i: CARDINAL IN [0..length) DO
IF i # 0 AND pad[i].y = pad[i-1].y AND pad[i].acc = pad[i-1].acc THEN pad[i].acc ← inKey;
IF pad[i].acc # inKey THEN top ← MIN[top, i];
IF pad[i].acc # inKey THEN bottom ← MAX[bottom, i];
ENDLOOP;
-- handle note adjacencies within a chord
FOR i: CARDINAL IN [0..length) DO
IF sync[i] = NIL THEN LOOP;
c ← Note.FindChord[sync[i]];
IF c # NIL THEN Chord.Adjust[score.sheet, c] ELSE LOOP;
-- no need to process a chord more than once
FOR j: CARDINAL IN [0..c.length) DO
FOR k: CARDINAL IN [0..s.length) DO
IF sync[k] = c.note[j] THEN {sync[k] ← NIL; EXIT};
ENDLOOP;
ENDLOOP;
ENDLOOP;
-- determine the deltas
FOR i: CARDINAL IN [1..length) DO
FOR j: CARDINAL DECREASING IN [0..i) DO
IF pad[j].c = pad[i].c AND pad[i].c # NIL THEN LOOP;
-- special case: allow some notes to overlap
IF Overlap[score, i, j, @pad, length] THEN LOOP;
delta ← Note.Delta[score.sheet, pad[j].n];
-- special case: higher note with stem down and lower with stem up
IF pad[j].stemUp AND NOT pad[i].stemUp THEN
BEGIN IF pad[j].y-pad[i].y >= 8 THEN LOOP;
MoveRightTo[pad[i], delta];
MoveLeft[pad[i], 8];
-- FOR k: CARDINAL IN [0..i] DO IF pad[j].c = pad[k].c AND pad[k].c # NIL THEN LOOP;
-- MoveLeft[pad[k], IF pad[k].n.value = whole THEN 10 ELSE 8];
-- ENDLOOP;
EXIT; END;
-- general case: shift lower note to the right
IF pad[j].y-pad[i].y < 8 THEN
MoveRightTo[pad[i], delta+(IF pad[j].n.value = whole THEN 10 ELSE 8)];
IF pad[i].n.value = whole OR pad[i].n.value = unknown THEN LOOP;
IF pad[j].stem < pad[i].y THEN MoveRightTo[pad[i], delta+2];
IF pad[i].stem > pad[j].y THEN MoveRightTo[pad[i], delta+2];
ENDLOOP;
ENDLOOP;
IF top > bottom THEN RETURN;
-- determine the deltas for the accidentals
-- RULES: (in order of precedence)
-- 1) all of the accidentals appear to the left of all of the notes
-- 2) accidentals for notes offset in a chord have the same offset
-- 3) there must be at least three lines vertically between accidentals
-- 4) the highest and lowest accidentals should have the same horizontal position
-- 5) lower accidentals should go to the left of higher accidentals
FOR i: CARDINAL IN [0..length) DO pad[i].x ← -10; ENDLOOP;
FOR i: CARDINAL IN [1..length) DO
IF pad[i-1].y-pad[i].y >= 8 THEN LOOP;
IF pad[i-1].acc = inKey OR pad[i].acc = inKey THEN LOOP;
IF Note.Delta[score.sheet, pad[i-1].n] > Note.Delta[score.sheet, pad[i].n]
THEN pad[i-1].push ← i
ELSE pad[i].push ← i-1;
ENDLOOP;
-- do top, then whatever pushes bottom, then bottom,
-- then whatever pushes, then whatever.
PlaceAccidental[score, @pad, top, length];
FOR i: CARDINAL IN [0..length) DO
IF i = top THEN LOOP;
IF pad[i].push # bottom THEN LOOP;
PlaceAccidental[score, @pad, i, length];
ENDLOOP;
PlaceAccidental[score, @pad, bottom, length];
FOR i: CARDINAL IN [0..length) DO
IF i = top OR i = bottom THEN LOOP;
IF pad[i].push = bottom THEN LOOP;
IF pad[i].push = padLength THEN LOOP;
PlaceAccidental[score, @pad, i, length];
ENDLOOP;
FOR i: CARDINAL IN [0..length) DO
IF i = top OR i = bottom THEN LOOP;
IF pad[i].push # padLength THEN LOOP;
PlaceAccidental[score, @pad, i, length];
ENDLOOP;
-- put the results in accDelta
FOR i: CARDINAL IN [0..length) DO
IF pad[i].acc = inKey THEN LOOP;
pad[i].n.accDelta ← -pad[i].n.delta - 8*pad[i].x;
ENDLOOP;
END;
Overlap: PROCEDURE[score: ScorePTR, a, b: CARDINAL, pad: POINTER TO ScratchPad, length: CARDINAL] RETURNS[BOOLEAN] =
BEGIN
found: BOOLEAN;
top, bottom: CARDINAL ← length;
PartOf: PROCEDURE[i, a: CARDINAL] RETURNS[BOOLEAN] = INLINE {
RETURN[a = i OR (pad[i].c = pad[a].c AND pad[a].c # NIL)]};
IF pad[a].c = NIL AND pad[b].c = NIL THEN { -- just single notes
IF pad[a].y # pad[b].y THEN RETURN[FALSE];
IF pad[a].n.pitch # pad[b].n.pitch THEN RETURN[FALSE];
IF pad[a].stemUp = pad[b].stemUp THEN RETURN[FALSE];
IF pad[a].n.value = whole OR pad[b].n.value = whole THEN RETURN[FALSE];
RETURN[TRUE]};
-- we have at least one chord; can a and b overlap?
-- find the tops and bottoms for a
FOR i: CARDINAL IN [0..length) DO
IF ~PartOf[i, a] THEN LOOP;
IF top = length THEN top ← i;
bottom ← i;
ENDLOOP;
IF pad[a].stemUp THEN top ← 0 ELSE bottom ← length-1;
-- do all of the notes in b which are within top and bottom have a corresponding note in a?
FOR i: CARDINAL IN [top..bottom] DO
IF ~PartOf[i, b] THEN LOOP;
found ← FALSE;
FOR j: CARDINAL IN [top..bottom] DO
IF ~PartOf[j, a] THEN LOOP;
IF pad[i].n.pitch # pad[j].n.pitch OR pad[i].y # pad[j].y THEN LOOP;
IF Note.Delta[score.sheet, pad[i].n] # Note.Delta[score.sheet, pad[j].n] THEN LOOP;
found ← TRUE;
EXIT; ENDLOOP;
IF ~found THEN RETURN[FALSE];
ENDLOOP;
top ← length;
-- find the tops and bottoms for b
FOR i: CARDINAL IN [0..length) DO
IF ~PartOf[i, b] THEN LOOP;
IF top = length THEN top ← i;
bottom ← i;
ENDLOOP;
IF pad[b].stemUp THEN top ← 0 ELSE bottom ← length-1;
-- do all of the notes in a which are within top and bottom have a corresponding note in b?
FOR i: CARDINAL IN [top..bottom] DO
IF ~PartOf[i, a] THEN LOOP;
found ← FALSE;
FOR j: CARDINAL IN [top..bottom] DO
IF ~PartOf[j, b] THEN LOOP;
IF pad[i].n.pitch # pad[j].n.pitch OR pad[i].y # pad[j].y THEN LOOP;
IF Note.Delta[score.sheet, pad[i].n] # Note.Delta[score.sheet, pad[j].n] THEN LOOP;
found ← TRUE;
EXIT; ENDLOOP;
IF ~found THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
END;
PlaceAccidental: PROCEDURE[score: ScorePTR, pad: POINTER TO ScratchPad, i, length: CARDINAL] =
BEGIN
blocked: BOOLEAN;
d, h: INTEGER;
IF i = padLength THEN RETURN;
IF pad[i].acc = inKey THEN RETURN;
FOR j: CARDINAL IN [0..length) DO IF pad[j].push = i THEN pad[i].x ← MAX[pad[j].x, pad[i].x]; ENDLOOP;
FOR d IN [MAX[pad[i].x, 0]..6) DO blocked ← FALSE;
FOR j: CARDINAL IN [0..length) DO
SELECT (IF pad[j].y > pad[i].y THEN pad[j].acc ELSE pad[i].acc) FROM
flat, doubleFlat, doubleSharp => h ← 8;
ENDCASE => h ← 16;
SELECT (IF pad[j].y > pad[i].y THEN pad[i].acc ELSE pad[j].acc) FROM
doubleSharp => NULL;
ENDCASE => h ← h+8;
IF pad[j].y-pad[i].y >= h THEN LOOP;
IF pad[i].y-pad[j].y >= 24 THEN EXIT;
IF pad[i].y-pad[j].y >= h THEN LOOP;
IF d = 0 AND Note.Delta[score.sheet, pad[j].n] < 0 THEN {blocked ← TRUE; EXIT};
IF j = i THEN LOOP;
IF j = pad[i].push THEN LOOP;
IF pad[j].acc # inKey AND pad[j].x = d THEN {blocked ← TRUE; EXIT};
ENDLOOP;
IF blocked THEN LOOP;
MoveAccTo[pad, i, d];
RETURN;
ENDLOOP;
ERROR;
END;
MoveRightTo: PROCEDURE[p: Scratch, x: INTEGER] = INLINE
BEGIN
IF p.c = NIL THEN p.n.delta ← MAX[p.n.delta, x]
ELSE p.c.delta ← MAX[p.c.delta, x - p.n.delta];
END;
MoveLeft: PROCEDURE[p: Scratch, delta: INTEGER] = INLINE
BEGIN
IF p.c = NIL THEN p.n.delta ← MIN[p.n.delta, -delta]
ELSE p.c.delta ← MIN[p.c.delta, -delta - p.n.delta];
END;
MoveAccTo: PROCEDURE[p: POINTER TO ScratchPad, i: CARDINAL, delta: INTEGER] =
BEGIN
p[i].x ← MAX[p[i].x, delta];
IF p[i].push # padLength AND p[p[i].push].x > -1 THEN MoveAccTo[p, p[i].push, p[i].x+1];
END;
SortPad: PROCEDURE[ascending: BOOLEAN, pad: POINTER TO ScratchPad] = INLINE
BEGIN
temp: Scratch;
FOR i: CARDINAL IN [0..padLength) DO
IF pad[i].n = NIL THEN EXIT;
FOR j: CARDINAL IN (i..padLength) DO
IF pad[j].n = NIL THEN EXIT;
IF NOT ascending AND pad[i].y < pad[j].y
OR ascending AND pad[i].y > pad[j].y
THEN BEGIN temp ← pad[i]; pad[i] ← pad[j]; pad[j] ← temp; END;
ENDLOOP;
ENDLOOP;
END;
padLength: CARDINAL = 16;
ScratchPad: TYPE = ARRAY [0..padLength) OF Scratch;
Scratch: TYPE = RECORD[x, y, stem: INTEGER ← 0, push: CARDINAL ← padLength,
acc: Accidental, stemUp: BOOLEAN,
c: ChordPTR, n: NotePTR ← NIL];
END..