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