-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 1:00 pm

DIRECTORY
   Beam USING [Height, InVoice], 
   Event USING [GetOctava, Octava, Staves, Sync], 
   Inline USING [LongCOPY], 
   MusicDefs, 
   Note USING [Delta, FindChord, Free, InVoice], 
   Piece USING [CleanUpEvents], 
   Real USING [FixI], 
   Score USING [GetKey, ShowPitch], 
   Selection USING [selection], 
   Sheet USING [FindLine, FindSection, Height, NearestTime, New, NextLine],
   Utility USING [FreeSegment, NewSegment];

PieceImplA: PROGRAM
  IMPORTS Beam, Event, Inline, MusicDefs, Note, Piece, Real, Score, Selection, Sheet, Utility
  EXPORTS MusicDefs, Piece = 

BEGIN
OPEN MusicDefs;

dataStructureInFlux: PUBLIC BOOLEAN ← FALSE;

-- ****************************************************************************
-- data abstractions for a piece
-- CONSTRAINT: score.event[i-1].time <= score.event[i].time <= score.event[i+1].time
-- CONSTRAINT: score.event[i] # NIL for all i such that 0 <= i < score.length
-- CONSTRAINT: score.event[score.length] = NIL
-- CONSTRAINT: score.event[i] # score.event[j] for all i # j < score.length
-- ****************************************************************************

Overflow: PUBLIC SIGNAL[old, new: ScorePTR] = CODE;

AddEvent: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR] 
	RETURNS[new: ScorePTR] = 
BEGIN
-- any procedure that inserts syncs should process syncs in decreasing order
IF event = NIL THEN RETURN[score];
IF score.length+1 = score.max THEN score ← New[score.max+100, TRUE, score];
-- insert sync
dataStructureInFlux ← TRUE;
FOR i: CARDINAL DECREASING IN [0..score.length) DO
	IF score.event[i] = event THEN ERROR;
	IF score.event[i].time < event.time THEN {score.event[i+1] ← event; event ← NIL; EXIT};
	IF score.event[i].time = event.time AND ~LessThan[event, score.event[i]] 
		THEN {score.event[i+1] ← event; event ← NIL; EXIT};
	score.event[i+1] ← score.event[i];
	ENDLOOP;
IF event # NIL THEN score.event[0] ← event;
score.length ← score.length + 1;
dataStructureInFlux ← FALSE;
RETURN[score];
END;

LessThan: PROCEDURE[a, b: EventPTR] RETURNS[BOOLEAN] = INLINE
BEGIN
SELECT TRUE FROM
	a.type = staves AND Event.Staves[a].staves = style => RETURN[TRUE];
	b.type = staves AND Event.Staves[b].staves = style => RETURN[FALSE];
	a.type = keySignature => RETURN[TRUE];
	b.type = keySignature => RETURN[FALSE];
	a.type = measure => RETURN[TRUE];
	b.type = measure => RETURN[FALSE];
	a.type = metrenome => RETURN[TRUE];
	b.type = metrenome => RETURN[FALSE];
	a.type = timeSignature => RETURN[TRUE];
	b.type = timeSignature => RETURN[FALSE];
	a.type = staves => RETURN[TRUE];
	b.type = staves => RETURN[FALSE];
	ENDCASE => RETURN[TRUE];
END;

RemoveEvent: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR, free: BOOLEAN] = 
BEGIN
length: CARDINAL ← 0;
octava: EventPTR ← NIL;
IF Event.Octava[event] AND free THEN octava ← Event.GetOctava[score, Event.Staves[event]];
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i] = event THEN LOOP;
	IF i # length THEN score.event[length] ← score.event[i];
	length ← length+1; ENDLOOP;
IF event.type = sync THEN {
	sync: SyncPTR ← Event.Sync[event];
	FOR i: CARDINAL IN [0..sync.length) DO 
    	sync.note[i].sync ← NIL;
    	ENDLOOP};
score.length ← length;
score.event[score.length] ← NIL;
IF free THEN zone.FREE[@event];
IF free AND octava # NIL THEN { 
	SetDirty[score, octava.time, octava.time];
	RemoveEvent[score, octava, FALSE]; 
	zone.FREE[@octava]};
END;

nullScore: ScorePTR ← zone.NEW[ScoreRec[0]];

New: PUBLIC PROCEDURE[length: CARDINAL, auxiliary: BOOLEAN, old: ScorePTR] 
	RETURNS[score: ScorePTR] = 
BEGIN
oldSize: CARDINAL;
nullSize: CARDINAL ← SIZE[ScoreRec[0]];
score ← Utility.NewSegment[SIZE[ScoreRec[length]], length, nullSize-1];
IF old = NIL THEN old ← nullScore;  -- gets the default values for us
oldSize ← SIZE[ScoreRec[old.length]];
Inline.LongCOPY[from: old, nwords: nullSize-1, to: score]; -- skip 'max'
Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: score+nullSize];
IF score.max # length THEN ERROR; -- check that it worked.
IF old # nullScore THEN {
	SetBackPointers[old, score]; 
	Utility.FreeSegment[old]};
IF auxiliary THEN {
	IF score.cache = NIL THEN score.cache ← zone.NEW[CacheRec];
	IF score.sheet = NIL THEN score.sheet ← Sheet.New[100];
	IF score.style = NIL THEN score.style ← zone.NEW[StyleRec[20]]};
END;

Free: PUBLIC PROCEDURE[score: ScorePTR] = 
BEGIN
IF score.beamHeap # NIL THEN { -- free all of the beams
	FOR i: CARDINAL IN [0..score.beamHeap.length) DO
		zone.FREE[@score.beamHeap[i]]; -- no need to carefully dismantle
		ENDLOOP;
	Utility.FreeSegment[score.beamHeap]};
IF score.chordHeap # NIL THEN { -- free all of the chords
	FOR i: CARDINAL IN [0..score.chordHeap.length) DO
		zone.FREE[@score.chordHeap[i]]; -- no need to carefully dismantle
		ENDLOOP;
	Utility.FreeSegment[score.chordHeap]};
FOR i: CARDINAL IN [0..score.length) DO
	IF score.event[i] = NIL THEN LOOP;
	IF score.event[i].type = sync THEN {
		sync: SyncPTR = Event.Sync[score.event[i]];
		FOR j: CARDINAL IN [0..sync.length) DO
			zone.FREE[@sync.note[j]];
			ENDLOOP};
	zone.FREE[@score.event[i]];
	ENDLOOP;
SetBackPointers[score, NIL];
Utility.FreeSegment[score];
END;

SetBackPointers: PROC[old, new: ScorePTR] = 
BEGIN
IF Selection.selection.score = old THEN Selection.selection.score ← new;
IF Selection.selection.score2 = old THEN Selection.selection.score2 ← new;
SIGNAL Overflow[old, new];
END;

Sort: PUBLIC PROCEDURE[score: ScorePTR] = 
-- useful when going from physical to logical files 
BEGIN
temp: EventPTR;
FOR i: CARDINAL IN [0..score.length) DO
	FOR j: CARDINAL IN (i..score.length) DO
		IF score.event[i].time > score.event[j].time 
			THEN {temp ← score.event[i]; score.event[i] ← score.event[j]; score.event[j] ← temp};
		IF score.event[i].time = score.event[j].time THEN 
	   		{IF score.event[i].type # sync OR score.event[j].type # sync THEN LOOP;
	    	 IF Event.Sync[score.event[i]].note[0] = NIL THEN LOOP;
	    	 IF Event.Sync[score.event[j]].note[0] = NIL THEN LOOP;
	    	 IF Event.Sync[score.event[i]].note[0].toc < Event.Sync[score.event[j]].note[0].toc THEN LOOP;
	    	 temp ← score.event[i]; score.event[i] ← score.event[j]; score.event[j] ← temp};
		ENDLOOP;
	ENDLOOP;
END;

CleanUpEvents: PUBLIC PROCEDURE[score: ScorePTR] = 
BEGIN
length: CARDINAL ← 0;
FOR i: CARDINAL DECREASING IN [0..score.length) DO
	IF score.event[i] = NIL THEN LOOP;
	IF score.event[i].type = sync THEN 
		IF Event.Sync[score.event[i]].length = 0 
			THEN zone.FREE[@score.event[i]]
			ELSE {score.event[length] ← score.event[i]; length ← length+1};
	ENDLOOP;
FOR i: CARDINAL IN [length..score.length) DO score.event[i] ← NIL; ENDLOOP;
score.length ← length;
score[score.length] ← NIL;
END;

CleanUpNotes: PUBLIC PROCEDURE[score: ScorePTR] = 
BEGIN
sync: SyncPTR;
FOR i: CARDINAL DECREASING IN [0..score.length) DO
	IF score.event[i].type # sync THEN LOOP;
	sync ← Event.Sync[score.event[i]];
	FOR j: CARDINAL DECREASING IN [0..sync.length) DO
		IF sync.note[j].value # unknown THEN LOOP;
		IF sync.note[j].duration # 0 THEN LOOP;
		Note.Free[score, sync.note[j]]; -- may remove the sync from the score
		ENDLOOP;
	ENDLOOP;
Piece.CleanUpEvents[score];
END;

-- ****************************************************************************
-- what is being pointed at?
-- ****************************************************************************

NearestEvent: PUBLIC PROCEDURE[score: ScorePTR, t: Time, syncsOnly: BOOLEAN ← FALSE] RETURNS[index: CARDINAL] = 
BEGIN
delta: Time ← 10000;
index ← 0;
FOR binary: CARDINAL ← 8192, binary/2 UNTIL binary = 0 DO
      IF index+binary >= score.length THEN LOOP;
      IF score.event[index+binary].time <= t THEN index ← index+binary;
      IF score.event[index].time = t THEN EXIT;
      ENDLOOP;
-- the above will fail when the p isn't strictly ordered
FOR i: CARDINAL IN [(IF index < 5 THEN 0 ELSE index-5)..MIN[index+5, score.length]) DO
      IF syncsOnly AND score.event[i].type # sync THEN LOOP;
      IF ABS[score.event[i].time-t] > delta THEN LOOP;
      index ← i; delta ← ABS[score.event[i].time-t];
      ENDLOOP;
IF syncsOnly AND index # score.length AND score.event[index].type # sync 
	THEN index ← score.length;
END;

NearestNote: PUBLIC PROCEDURE[score: ScorePTR, x, y: INTEGER] RETURNS[note: NotePTR] = 
BEGIN
n: NotePTR;
key: INTEGER;
sync: SyncPTR;
l, next: CARDINAL;
time, dt, t: Time ← 11;
sheet: SheetPTR ← score.sheet;
height, dy, show: INTEGER ← 8; 
[time, height] ← Sheet.NearestTime[sheet, x, y];
next ← Sheet.FindLine[sheet, time];
time ← time - (sheet.width - sheet.section[next].x);
l ← Sheet.FindLine[sheet, time]; 
height ← height + (sheet.section[next].y - sheet.section[l].y);
FOR k: CARDINAL IN [0..2] DO
	FOR i: CARDINAL IN [0..score.length) DO
		IF score.event[i].type # sync THEN LOOP;
		IF score.event[i].time < time - 40 THEN LOOP;
		IF score.event[i].time > time + 40 THEN EXIT;
		IF score.event[i].time < sheet.section[l].time THEN LOOP;
		IF score.event[i].time >= sheet.section[next].time THEN EXIT;
		sync ← Event.Sync[score.event[i]];
		FOR j: CARDINAL IN [0..sync.length) DO
			n ← sync.note[j];
			IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP;
			t ← ABS[score[i].time+5+Note.Delta[score.sheet, n]-time];
			key ← Score.GetKey[score, n.sync.time];
			show ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key];
			y ← ABS[height-Sheet.Height[sheet, n.sync.time, show, n.staff]];
			IF y+t > dy+dt THEN LOOP;
			IF y+t = dy+dt AND t > dt THEN LOOP;
			dt ← t; dy ← y;
			note ← n;
			ENDLOOP;
		ENDLOOP;
	time ← time+ (sheet.width - sheet.section[next].x);
	height ← height- (sheet.section[next].y - sheet.section[l].y);
	l ← next;
	next ← Sheet.NextLine[sheet, l];
	ENDLOOP;
IF dt > 10 OR dy > 7 THEN note ← NIL;
END;

NearestObject: PUBLIC PROCEDURE[score: ScorePTR, x, y: INTEGER] RETURNS[obj: ObjectType, p: LONG POINTER ← NIL] = 
BEGIN
n: NotePTR;
l: CARDINAL;
key: INTEGER;
staves: StavesPTR;
time, dt, t: Time ← 11;
s, next: CARDINAL ← 0;
sheet: SheetPTR ← score.sheet;
height, dy, show: INTEGER ← 8; 
[time, height] ← Sheet.NearestTime[sheet, x, y];
next ← Sheet.FindLine[sheet, time];
time ← time - (sheet.width - sheet.section[next].x);
l ← Sheet.FindLine[sheet, time]; 
height ← height + (sheet.section[next].y - sheet.section[l].y);
FOR k: CARDINAL IN [0..2] DO
-- left and right corners of a beam
FOR i: CARDINAL IN [0..score.beamHeap.length) DO
    IF score.beamHeap.beam[i].beam # NIL AND score.beamHeap.beam[i].beam.beamed THEN LOOP;
    IF score.beamHeap.beam[i].sync1.time-5 > time THEN LOOP; 
	 IF score.beamHeap.beam[i].sync2.time+5 < time THEN LOOP;
    IF ~Beam.InVoice[score.beamHeap.beam[i], score.sheet.voice] THEN LOOP;
    y ← Beam.Height[sheet, score.beamHeap.beam[i], time];
    IF ABS[y-height]+4 > dt+dy THEN LOOP;
    IF ABS[y-height]+4 = dt+dy THEN IF y > height THEN LOOP;
    dt ← 4; dy ← ABS[y-height];
    IF time < (score.beamHeap.beam[i].sync1.time+score.beamHeap.beam[i].sync2.time+4)/2 
		THEN obj ← leftBeam 
		ELSE obj ← rightBeam;
    p ← score.beamHeap.beam[i];
    ENDLOOP;
-- measure sections, signatures and other events
FOR i: CARDINAL IN [s..score.length) DO
    IF score.event[i].time < time - 300 THEN LOOP;
    IF score.event[i].time > time + 300 THEN {s ← i; EXIT};
    IF score.event[i].time < sheet.section[l].time THEN LOOP;
    IF score.event[i].type = sync THEN { -- ties
    	sync: SyncPTR ← Event.Sync[score.event[i]];
    	FOR j: CARDINAL IN [0..sync.length) DO 
			n ← sync.note[j];
			IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP;
			IF n.tie = NIL THEN LOOP;
			IF n.sync.time < time OR n.tie.sync.time > time THEN LOOP;
			t ← ABS[(n.sync.time+n.tie.sync.time)/2-time];
			key ← Score.GetKey[score, n.sync.time];
			show ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key];
			y ← ABS[height-(Sheet.Height[sheet, n.sync.time, show, n.staff]+n.tieHeight/2)];
			IF y+t > dy+dt THEN LOOP;
			IF y+t = dy+dt THEN IF t > dt THEN LOOP;
			dt ← t; dy ← y;
			obj ← tie; p ← n;
			ENDLOOP};
    IF score.event[i].time >= sheet[next].time THEN LOOP;
    IF ABS[score.event[i].time-time] > 40 THEN LOOP;
    IF score.event[i].type = staves THEN {
		octava: StavesPTR ← NIL;
		staves: StavesPTR ← Event.Staves[score.event[i]];
		staff, top, bottom: INTEGER ← staves.value;
		t ← staves.time;
		IF staves.staves = clef THEN t ← t+6; 
		IF ABS[t-time]+6 > dt + dy THEN LOOP; 
		IF ABS[t-time]+6 = dt+dy AND t < time THEN LOOP;
		IF staves.staves = octava2 THEN staves ← Event.GetOctava[score, staves];
		-- IF octava # NIL THEN  staves ← LOOPHOLE[@octava.event]
			-- ELSE staves ← LOOPHOLE[@score[i].event];
		SELECT staves.staff[staff].pitch FROM
			60, 15 => {top ← staves.height+5; bottom ← staves.height-5};
			ENDCASE => {top ← 32; bottom ← 0};   
		IF height > Sheet.Height[sheet, score.event[i].time, , staff]+top THEN LOOP;
		IF height < Sheet.Height[sheet, score.event[i].time, , staff]+bottom THEN LOOP; 
		dt ← ABS[t-time]; dy ← 6;
		p ← score.event[i];
		obj ← measure;
		LOOP}; 
	IF score.event[i].type # sync THEN {
		IF ABS[score.event[i].time-time]+6 > dt + dy THEN LOOP; 
		IF ABS[score.event[i].time-time]+6 = dt+dy AND score.event[i].time < time THEN LOOP;
		IF score.event[i].type = staves AND score.event[i].time < 1 THEN LOOP; -- don't touch the first one
		staves ← sheet.section[Sheet.FindSection[sheet, score[i].time]].staves; 
		IF height > Sheet.Height[sheet, score.event[i].time, , 0]+32 THEN LOOP;
		IF height < Sheet.Height[sheet, score.event[i].time, , staves.length-1] THEN LOOP; 
		dt ← ABS[score.event[i].time-time]; dy ← 6;
		p ← score.event[i];
		obj ← measure;
		LOOP}; 
	IF score.event[i].type = sync THEN { -- notes
		sync: SyncPTR ← Event.Sync[score.event[i]];
		FOR j: CARDINAL IN [0..sync.length) DO
			IF (n ← sync.note[j]) = NIL THEN EXIT;
			IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP;
			t ← ABS[score.event[i].time+5+Note.Delta[score.sheet, n]-time];
			y ← ABS[height-Sheet.Height[sheet, n.sync.time, n.pitch, n.staff]];
			IF y+t > dy+dt THEN LOOP;
			IF y+t = dy+dt AND t > dt THEN LOOP;
			dt ← t; dy ← y;
			p ← n; obj ← note;
			ENDLOOP};
    ENDLOOP; -- end of this line, go to next
time ← time+ (sheet.width - sheet.section[next].x);
height ← height- (sheet.section[next].y - sheet.section[l].y);
l ← next;
next ← Sheet.NextLine[sheet, l];
ENDLOOP;
IF dt > 10 OR dy > 7 THEN p ← NIL;
END;

END..

DeleteEvent: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR] = 
BEGIN
octava: StavesPTR ← NIL;
index: CARDINAL ← Event.GetScoreIndex[score, event];
IF Event.Octava[event] THEN octava ← Event.GetOctava[score, Event.Staves[event]];
Piece.RemoveEvent[score, event];
IF octava # NIL THEN {
	SetDirty[score, octava.time, octava.time];
	Piece.RemoveEvent[score, octava];
	zone.FREE[@octava]};
Utility.FreeEvent[@event];
END;