--Author: John Maxwell
--last modified: October 23, 1981 12:58 PM
DIRECTORY
Beam USING [AddChord,Remove],
Chord USING [default,InVoice,RemoveNote,Sort],
Graphics USING [DrawRectangle,MoveTo,SetTexture],
MusicDefs,
Note USING [DrawHead,FindChord,Width],
Sheet USING [Map,MapNote],
Sync USING [AddNote],
Utility USING [DrawChar,DrawLine,FreeChord];
ChordImpl: PROGRAM
IMPORTS Beam, Chord, Graphics, MusicDefs, Note, Sheet, Sync, 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
--CONSTRAINT: c.note[i].beam = c.note[j].beam for all i,j
--****************************************************************************
AddNote:PUBLIC PROCEDURE[c:ChordPTR,n:NotePTR] =
BEGIN
oldBeam:BeamPTR = n.beam;
oldChord:ChordPTR = Note.FindChord[n];
chordSync:SyncPTR = IF c.note[0]#NIL THEN c.note[0].sync ELSE n.sync;
chordBeam:BeamPTR = IF c.note[0]#NIL THEN c.note[0].beam ELSE NIL;
--check for aborted attempts
IF c=oldChord THEN RETURN;
IF c.note[chordLength-1]#NIL THEN Overflow[chord];
--put the note in the chord
FOR i:CARDINAL IN [0..chordLength) DO
IF c.note[i]=NIL THEN {c.note[i] ← n; EXIT};
ENDLOOP;
n.chord ← c;
n.beam ← chordBeam;
IF chordBeam=NIL AND oldBeam#NIL THEN Beam.AddChord[oldBeam,c];
IF n.sync#chordSync THEN Sync.AddNote[chordSync,n];
--remove the note from old places
IF oldChord#NIL THEN Chord.RemoveNote[oldChord,n];
IF oldBeam#NIL AND oldChord=NIL AND oldBeam#n.beam THEN Beam.Remove[oldBeam,n,NIL,NIL];
END;
RemoveNote:PUBLIC PROCEDURE[c:ChordPTR,n:NotePTR] =
BEGIN
i,k:CARDINAL ← chordLength;
IF n=NIL OR c=NIL THEN RETURN;
--keep the notes packed;
FOR i DECREASING IN [0..chordLength) DO
IF c.note[i]=NIL THEN k←i;
IF c.note[i]#n THEN LOOP;
IF n.chord=c THEN n.chord ← NIL;
n.beam ← NIL; -- removing a note from a chord also removes it from the beam
c.note[i]←c.note[k-1];
c.note[k-1] ← NIL;
ENDLOOP;
IF c.note[1]=NIL THEN Utility.FreeChord[@c];
END;
Sort:PUBLIC PROCEDURE[c:ChordPTR,up:BOOLEAN] RETURNS[n:CARDINAL] =
BEGIN
i,j:CARDINAL;
temp:NotePTR;
FOR i IN [0..chordLength) DO
IF c.note[i]=NIL THEN BEGIN n←i; EXIT; END;
FOR j IN (i..chordLength) DO
IF c.note[j]=NIL THEN EXIT;
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[c:ChordPTR] =
BEGIN
n:CARDINAL;
x0,xn,y0,yn,middle0,middlen:INTEGER;
n ← Chord.Sort[c,TRUE];
[x0,y0] ← Sheet.Map[c.note[0].sync.time,c.note[0].pitch,c.note[0].staff];
[xn,yn] ← Sheet.Map[c.note[n-1].sync.time,c.note[n-1].pitch,c.note[n-1].staff];
middle0 ← Sheet.Map[c.note[0].sync.time,,c.note[0].staff].y+16;
middlen ← Sheet.Map[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[c:ChordPTR,stem:INTEGER] =
BEGIN
n:NotePTR;
flag:NoteValue;
two:REAL = 2;
dotX:INTEGER←0;
inVoice:BOOLEAN;
--xWide,yWide:INTEGER;
nonGrace:BOOLEAN ← FALSE;
minStaff,maxStaff:CARDINAL;
x,y,yMin,yMax,mid,width:INTEGER;
inVoice ← ~(voice AND NOT Chord.InVoice[c,selectedVoice]); -- draw the note heads
FOR i:CARDINAL IN [0..chordLength) DO
IF c.note[i]=NIL THEN EXIT ELSE n ← c.note[i];
IF ~n.dotted THEN LOOP;
[x,y] ← Sheet.MapNote[n];
dotX ← MAX[dotX,x+Note.Width[n]+2];
ENDLOOP;
mid ← Sheet.Map[n.sync.time,,n.staff].y+16;
FOR i:CARDINAL IN [0..chordLength) DO
IF c.note[i]=NIL THEN EXIT ELSE n ← c.note[i];
[x,y] ← Sheet.MapNote[n];
Note.DrawHead[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 show.notehead THEN RETURN;
-- draw the ledger lines
Graphics.SetTexture[context,IF inVoice THEN black ELSE light];
IF show.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 DrawRectangle[context,[x-3,mid+1+i*d],[x+width+3,mid-1+i*d]]
ELSE DrawRectangle[context,[x-3,mid+1/two+i*d],[x+width+3,mid-1/two+i*d]];
ENDLOOP;
FOR i:INTEGER IN [3..9] WHILE mid-yMin >= i*d DO
IF nonGrace
THEN DrawRectangle[context,[x-3,mid+1-i*d],[x+width+3,mid-1-i*d]]
ELSE DrawRectangle[context,[x-3,mid+1/two-i*d],[x+width+3,mid-1/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[x+width,yMin,x+width,stem] ELSE DrawLine[x,yMax,x,stem];
--draw the flag
IF nonGrace
THEN MoveTo[context,[x,IF c.stemUp THEN stem-24 ELSE stem+24]]
ELSE MoveTo[context,[x,IF c.stemUp THEN stem-17 ELSE stem+17]];
SELECT TRUE FROM
~nonGrace => IF c.stemUp
THEN DrawChar[context,133C]
ELSE DrawChar[context,134C];
flag=eighth => IF c.stemUp
THEN DrawChar[context,153C]
ELSE DrawChar[context,163C];
flag=sixteenth => IF c.stemUp
THEN DrawChar[context,152C]
ELSE DrawChar[context,162C];
flag=thirtysecond => IF c.stemUp
THEN DrawChar[context,151C]
ELSE DrawChar[context,161C];
flag=sixtyfourth => IF c.stemUp
THEN DrawChar[context,151C]
ELSE DrawChar[context,161C];
ENDCASE;
END;
d:CARDINAL=8;
--****************************************************************************
--look graphical
--****************************************************************************
Adjust:PUBLIC PROCEDURE[c:ChordPTR] =
BEGIN
n:NotePTR;
pad:ScratchPad;
delta:INTEGER;
last:BOOLEAN ← FALSE;
i,length:CARDINAL;
--build the pad, initialize
FOR i IN [0..chordLength) DO
IF (n←c.note[i])=NIL THEN BEGIN length ← i; EXIT; END;
[,pad[i].y] ← Sheet.MapNote[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 IN [1..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
i,j:CARDINAL;
temp:Scratch;
FOR i IN [0..syncLength) DO
IF pad[i].n=NIL THEN EXIT;
FOR j IN (i..syncLength) 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;
ScratchPad:TYPE= ARRAY [0..syncLength) OF Scratch;
Scratch:TYPE = RECORD[x,y,stem:INTEGER←0,push:CARDINAL←syncLength,
acc:Accidental,stemUp:BOOLEAN,
c:ChordPTR,n:NotePTR←NIL];
END..