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