-- Author: John Maxwell -- Last Edited by: Maxwell, November 22, 1983 10:14 am DIRECTORY Beam USING [AddItem, RemoveItem, SetSyncs], Chord USING [default, Free, GetHeapIndex, InVoice, RemoveNote, Sort], Event USING [AddNote], Graphics USING [DrawBox, SetCP, SetStipple], Inline USING [LongCOPY], MusicDefs, Note USING [DrawHead, FindChord, Width], Sheet USING [Map, MapNote], Utility USING [DrawChar, DrawLine, FreeSegment, NewSegment]; ChordImpl: PROGRAM IMPORTS Beam, Chord, Graphics, Inline, MusicDefs, Note, Sheet, Event, Utility EXPORTS Chord = BEGIN OPEN Chord, MusicDefs, Graphics, Utility; -- **************************************************************************** -- data abstractions for a chord -- CONSTRAINT: c.note[i].sync = c.note[j].sync for all i, j < c.length -- CONSTRAINT: c.note[i].beam = c.note[j].beam for all i, j < c.length -- **************************************************************************** AddNote: PUBLIC PROCEDURE[score: ScorePTR, chord: ChordPTR, note: NotePTR] RETURNS[new: ChordPTR]= BEGIN chordSync: SyncPTR _ IF chord.note[0] # NIL THEN chord.note[0].sync ELSE note.sync; chordBeam: BeamPTR _ IF chord.note[0] # NIL THEN chord.note[0].beam ELSE NIL; IF chord = note.chord THEN RETURN[chord]; IF note.chord # NIL THEN Chord.RemoveNote[score, note.chord, note]; -- sets note.beam to NIL -- put the note in the chord IF chord.length = chord.max THEN chord _ New[score, chord.max+4, chord]; chord.note[chord.length] _ note; chord.length _ chord.length + 1; note.chord _ chord; IF chordSync # NIL AND chordSync # note.sync THEN { chordSync _ Event.AddNote[score, chordSync, note]; IF note.beam # NIL THEN Beam.SetSyncs[note.beam]}; IF chordBeam # NIL THEN { -- move note to this beam IF note.beam # NIL THEN Beam.RemoveItem[score, note.beam, [note[note]]]; note.beam _ chordBeam}; IF chordBeam = NIL AND note.beam # NIL THEN { -- replace note with chord beam: BeamPTR _ note.beam; Beam.RemoveItem[score, beam, [note[note]], FALSE]; -- don't free beam! beam _ Beam.AddItem[score, beam, [chord[chord]]]}; RETURN[chord]; END; RemoveNote: PUBLIC PROCEDURE[score: ScorePTR, chord: ChordPTR, note: NotePTR, free: BOOLEAN] = BEGIN IF note = NIL OR chord = NIL THEN RETURN; -- keep the notes packed; FOR i: CARDINAL IN [0..chord.length) DO IF chord.note[i] # note THEN LOOP; IF note.chord = chord THEN note.chord _ NIL; IF chord.length = 1 AND note.beam # NIL THEN Beam.RemoveItem[score, note.beam, [chord[chord]]]; -- remove chord from beam note.beam _ NIL; -- removing a note from a chord also removes it from the beam chord.length _ chord.length - 1; chord.note[i] _ chord.note[chord.length]; chord.note[chord.length] _ NIL; EXIT; ENDLOOP; IF free AND chord.length < 2 THEN Chord.Free[score, chord]; END; New: PUBLIC PROCEDURE[score: ScorePTR, length: CARDINAL, old: ChordPTR] RETURNS[chord: ChordPTR] = BEGIN chord _ zone.NEW[ChordRec[length]]; IF old # NIL THEN { -- replace old with chord in notes, beam, and chordHeap. nullSize: CARDINAL = SIZE[ChordRec[0]]; oldSize: CARDINAL = SIZE[ChordRec[old.max]]; Inline.LongCOPY[from: old, nwords: nullSize-1, to: chord]; -- skip 'max' Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: chord+nullSize]; SetBackPointers[score, old, chord]; zone.FREE[@old]; RETURN}; IF score.chordHeap.length = score.chordHeap.max THEN { -- replace with a bigger one. old: ChordHeapPTR = score.chordHeap; nullSize: CARDINAL = SIZE[ChordHeapRec[0]]; oldSize: CARDINAL = SIZE[ChordHeapRec[old.max]]; newMax: CARDINAL _ score.chordHeap.max+100; new: ChordHeapPTR _ Utility.NewSegment[SIZE[ChordHeapRec[newMax]], newMax, nullSize-1]; Inline.LongCOPY[from: old, nwords: nullSize-1, to: new]; -- skip 'max' Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: new+nullSize]; IF new.max # newMax THEN ERROR; score.chordHeap _ new; Utility.FreeSegment[old]}; score.chordHeap.chord[score.chordHeap.length] _ chord; score.chordHeap.length _ score.chordHeap.length + 1; END; Free: PUBLIC PROCEDURE[score: ScorePTR, chord: ChordPTR] = BEGIN SetBackPointers[score, chord, NIL]; zone.FREE[@chord]; END; SetBackPointers: PROC[score: ScorePTR, c: ChordPTR, new: ChordPTR] = BEGIN -- changes all pointers to c to be pointers to new -- a chord can be pointed to by notes, beams, and the chordHeap. chordBeam: BeamPTR _ IF c.length > 0 THEN c[0].beam ELSE NIL; index: CARDINAL _ Chord.GetHeapIndex[score.chordHeap, c]; score.chordHeap[index] _ new; IF new = NIL THEN { -- pack the beamHeap score.chordHeap.length _ score.chordHeap.length - 1; score.chordHeap[index] _ score.chordHeap[score.chordHeap.length]}; IF chordBeam # NIL AND new # NIL THEN { Beam.RemoveItem[score, chordBeam, [chord[c]], FALSE]; -- don't free beam! chordBeam _ Beam.AddItem[score, chordBeam, [chord[new]]]}; IF chordBeam # NIL AND new = NIL THEN { Beam.RemoveItem[score, chordBeam, [chord[c]], c.length = 0]; -- don't free beam if length > 0 IF c.length > 0 THEN chordBeam _ Beam.AddItem[score, chordBeam, [note[c[0]]]]}; FOR i: CARDINAL IN [0..c.length) DO -- NIL chord back pointers in notes IF c.note[i].chord = c THEN c.note[i].chord _ new; ENDLOOP; END; Sort: PUBLIC PROCEDURE[c: ChordPTR, up: BOOLEAN] RETURNS[note: CARDINAL] = BEGIN temp: NotePTR; FOR i: CARDINAL IN [0..c.length) DO FOR j: CARDINAL IN (i..c.length) DO IF NOT up AND c.note[i].pitch < c.note[j].pitch OR up AND c.note[i].pitch > c.note[j].pitch THEN BEGIN temp _ c.note[i]; c.note[i] _ c.note[j]; c.note[j] _ temp; END; ENDLOOP; ENDLOOP; END; SetDefaultStem: PUBLIC PROCEDURE[sheet: SheetPTR, c: ChordPTR] = BEGIN n: CARDINAL; x0, xn, y0, yn, middle0, middlen: INTEGER; n _ Chord.Sort[c, TRUE]; [x0, y0] _ Sheet.Map[sheet, c.note[0].sync.time, c.note[0].pitch, c.note[0].staff]; [xn, yn] _ Sheet.Map[sheet, c.note[n-1].sync.time, c.note[n-1].pitch, c.note[n-1].staff]; middle0 _ Sheet.Map[sheet, c.note[0].sync.time, , c.note[0].staff].y+16; middlen _ Sheet.Map[sheet, c.note[n-1].sync.time, , c.note[n-1].staff].y+16; SELECT TRUE FROM (yn+y0)/2 > middlen => c.stemUp _ FALSE; (yn+y0)/2 < middle0 => c.stemUp _ TRUE; (yn+y0)/2 > (middlen+middle0)/2 => c.stemUp _ TRUE; ENDCASE => c.stemUp _ FALSE; END; -- ****************************************************************** -- procedures for drawing chords on the screen -- ****************************************************************** Draw: PUBLIC PROCEDURE[score: ScorePTR, c: ChordPTR, stem: INTEGER] = BEGIN w: REAL = 0; n: NotePTR; flag: NoteValue; two: REAL = 2; dotX: INTEGER _ 0; inVoice: BOOLEAN; -- xWide, yWide: INTEGER; sheet: SheetPTR = score.sheet; nonGrace: BOOLEAN _ FALSE; minStaff, maxStaff: CARDINAL; x, y, yMin, yMax, mid, width: INTEGER; inVoice _ score.sheet.voice = noVoice OR Chord.InVoice[c, score.sheet.voice]; -- draw the note heads FOR i: CARDINAL IN [0..c.length) DO n _ c.note[i]; IF ~n.dotted THEN LOOP; [x, y] _ Sheet.MapNote[sheet, n]; dotX _ MAX[dotX, x+Note.Width[n]+2]; ENDLOOP; mid _ Sheet.Map[sheet, n.sync.time, , n.staff].y+16; FOR i: CARDINAL IN [0..c.length) DO n _ c.note[i]; [x, y] _ Sheet.MapNote[sheet, n]; Note.DrawHead[score, n, x, y, dotX]; IF i = 0 THEN {yMin _ yMax _ y; width _ 0}; width _ MAX[width, Note.Width[n]]; -- IF n.delta # 0 THEN { -- xWide _ MIN[xWide, x]; -- yWide _ IF y > mid THEN MIN[yWide, y] ELSE MAX[yWide, y]}; IF ~n.grace THEN nonGrace _ TRUE; IF y <= yMin THEN {yMin _ y; minStaff _ n.staff}; IF y >= yMax THEN {yMax _ y; maxStaff _ n.staff}; ENDLOOP; IF NOT sheet.notehead THEN RETURN; -- draw the ledger lines IF inVoice THEN Graphics.SetStipple[sheet.context, black] ELSE Graphics.SetStipple[sheet.context, light]; IF sheet.display = graphical THEN x _ x - n.delta; -- subtract off x offset before drawing stem FOR i: INTEGER IN [3..9] WHILE yMax-mid >= i*d DO IF nonGrace THEN DrawBox[sheet.context, [x-3, mid+w+i*d, x+width+3, mid-w+i*d]] ELSE DrawBox[sheet.context, [x-3, mid+w/two+i*d, x+width+3, mid-w/two+i*d]]; ENDLOOP; FOR i: INTEGER IN [3..9] WHILE mid-yMin >= i*d DO IF nonGrace THEN DrawBox[sheet.context, [x-3, mid+w-i*d, x+width+3, mid-w-i*d]] ELSE DrawBox[sheet.context, [x-3, mid+w/two-i*d, x+width+3, mid-w/two-i*d]]; ENDLOOP; IF n.value = whole OR n.value = unknown THEN RETURN; -- draw the stem flag _ n.value; IF stem # default -- drawing part of or beam THEN {stem _ (IF c.stemUp THEN stem+1 ELSE stem-1); flag _ quarter} ELSE SELECT TRUE FROM nonGrace AND c.stemUp => stem _ MAX[yMax+(7*d)/2, mid]; nonGrace AND ~c.stemUp => stem _ MIN[yMin-(7*d)/2, mid]; c.stemUp => stem _ yMax+2*d; ~c.stemUp => stem _ yMin-2*d; ENDCASE; IF c.stemUp THEN DrawLine[sheet.context, x+width, yMin, x+width, stem] ELSE DrawLine[sheet.context, x, yMax, x, stem]; -- draw the flag IF nonGrace THEN SetCP[sheet.context, x, IF c.stemUp THEN stem-24 ELSE stem+24] ELSE SetCP[sheet.context, x, IF c.stemUp THEN stem-17 ELSE stem+17]; SELECT TRUE FROM ~nonGrace => IF c.stemUp THEN DrawChar[sheet.context, 133C] ELSE DrawChar[sheet.context, 134C]; flag = eighth => IF c.stemUp THEN DrawChar[sheet.context, 153C] ELSE DrawChar[sheet.context, 163C]; flag = sixteenth => IF c.stemUp THEN DrawChar[sheet.context, 152C] ELSE DrawChar[sheet.context, 162C]; flag = thirtysecond => IF c.stemUp THEN DrawChar[sheet.context, 151C] ELSE DrawChar[sheet.context, 161C]; flag = sixtyfourth => IF c.stemUp THEN DrawChar[sheet.context, 151C] ELSE DrawChar[sheet.context, 161C]; ENDCASE; END; d: CARDINAL = 8; -- **************************************************************************** -- look graphical -- **************************************************************************** Adjust: PUBLIC PROCEDURE[sheet: SheetPTR, c: ChordPTR] = BEGIN n: NotePTR; pad: ScratchPad; delta: INTEGER; last: BOOLEAN _ FALSE; -- build the pad, initialize FOR i: CARDINAL IN [0..c.length) DO [, pad[i].y] _ Sheet.MapNote[sheet, n]; pad[i].n _ n; n.delta _ 0; ENDLOOP; SortPad[c.stemUp, @pad]; c.delta _ 0; -- determine the deltas on the notes IF c.stemUp THEN delta _ 8 ELSE delta _ -8; FOR i: CARDINAL IN [1..c.length) DO IF last THEN BEGIN last _ FALSE; LOOP; END; IF ABS[pad[i-1].y-pad[i].y] >= 8 THEN LOOP; pad[i].n.delta _ delta; last _ TRUE; ENDLOOP; END; SortPad: PROCEDURE[ascending: BOOLEAN, pad: POINTER TO ScratchPad] = 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; Delta: PROCEDURE[n: NotePTR] RETURNS[INTEGER] = BEGIN c: ChordPTR _ Note.FindChord[n]; IF c = NIL THEN RETURN[n.delta] ELSE RETURN[n.delta+c.delta]; END; padLength: CARDINAL = 16; ScratchPad: TYPE = ARRAY [0..padLength) OF Scratch; -- need to make this variable length Scratch: TYPE = RECORD[x, y, stem: INTEGER _ 0, push: CARDINAL _ padLength, acc: Accidental, stemUp: BOOLEAN, c: ChordPTR, n: NotePTR _ NIL]; END..