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