--Author: John Maxwell
--last modified: December 15, 1981 4:06 PM

DIRECTORY
CursorDefs,
Beam USING [Draw,SetStems],
Graphics USING [DrawScreenArea, SetPaint, SetTexture],
Interface USING [InsertNote, Object],
MusicDefs,
Note USING [Draw, DrawTie, SetAccidental, SetEmbellishment],
Piece USING [AddSync, DeleteSync, NearestNote, NearestObject, RemoveSync],
ProcessDefs USING [SetTimeout],
Real USING [FixI],
Score USING [GetKey, Redraw],
Selection, -- USING everything
Sheet USING [AlternateTime, DrawClef, DrawOctava, FindLine, FindSection, GetStyle, Height, NearestStaff, NearestTime, NextLine, NextStaff, NormalPitch, OctavaHeight, PriorStaff, Reset, ScreenPoint, SetClef, SetOctava],
Sync USING [Draw, GetScoreIndex, GetStaff, Octava],
Utility; -- USING everything


InterfaceImplB:MONITOR
IMPORTS Beam, Graphics, Interface, MusicDefs, Note, Piece, ProcessDefs, Real, Score, Selection, Sheet, Sync, Utility
EXPORTS Interface =
BEGIN
OPEN Graphics, MusicDefs;

timeout:CONDITION;
Error:SIGNAL = CODE;

--******************************************************************
--
deleting objects
--******************************************************************
count:PUBLIC BOOLEAN;

DeleteGraphical:PUBLIC PROCEDURE =
BEGIN
type:EventType;
obj:ObjectType;
s:SyncPTR←NIL;
time,carry,next:Time;
p:UnspecifiedPTR←NIL;
value,oldClef,oldStyle:INTEGER←0;
SetBrush[black,invert];
WHILE YellowBug[] DO
[obj,p] ← Piece.NearestObject[];
IF p#NIL THEN SELECT obj FROM
note => Note.Draw[p];
measure => Sync.Draw[p];
leftBeam => []←Beam.Draw[p];
rightBeam=> []←Beam.Draw[p];
ENDCASE;
Wait[1];
IF p#NIL THEN SELECT obj FROM
note => Note.Draw[p];
measure => Sync.Draw[p];
leftBeam => []←Beam.Draw[p];
rightBeam=> []←Beam.Draw[p];
ENDCASE;
ENDLOOP;
IF p#NIL AND obj=note THEN Selection.RemoveNote[p];
IF obj#measure OR p=NIL THEN RETURN;
s ← LOOPHOLE[p,SyncPTR];
IF voice AND s.type IN [clef..octava2] THEN RETURN;
type ← s.type; time ← s.time; value ← s.value;
IF type=clef THEN oldClef ← Clef[value,time];
--IF type=staves THEN y ← sheet[Sheet.FindLine[time]].y;
IF type=staves THEN {
next ← sheet[Sheet.NextLine[Sheet.FindLine[time]]].time;
oldStyle ← Sheet.GetStyle[next]};
carry ← GetCarry[s];
Piece.DeleteSync[s];
SetDirty[time,time];
IF type=keySignature THEN SetDirty[time,carry];
IF type=keySignature OR type=clef THEN ResetSheet[time,time];
IF type=clef AND oldClef#Clef[value,time] THEN SetDirty[time,carry];
IF type=staves THEN {
Sheet.Reset[];
IF Sheet.GetStyle[time]#value THEN SetDirty[begin,endTime];
IF Sheet.GetStyle[next]#oldStyle THEN SetDirty[begin,endTime]};
--
IF sheet[Sheet.FindLine[time]].y#y THEN SetDirty[begin,endTime]};
IF type IN [octava1..octava2] THEN Sheet.Reset[];
Score.Redraw[min,max];
count←TRUE;
END;

ChangeSpelling:PROCEDURE[object:Interface.Object] =
BEGIN
n:NotePTR;
min ←1000000;
max ← -1;
SetBrush[black,invert];
WHILE BlueBug[] DO
IF (n←Piece.NearestNote[])=NIL THEN LOOP;
Note.Draw[n];
Note.Draw[n];
ENDLOOP;
SetBrush[black,paint];
IF n=NIL OR n.rest THEN RETURN;
IF voice AND n.voice#selectedVoice THEN RETURN;
SELECT object FROM
doubleFlat => Note.SetAccidental[n,doubleFlat];
flat => Note.SetAccidental[n,flat];
natural => Note.SetAccidental[n,natural];
inKey => Note.SetAccidental[n,inKey];
sharp => Note.SetAccidental[n,sharp];
doubleSharp=> Note.SetAccidental[n,doubleSharp];
ENDCASE;
IF flash THEN {Flash[]; RETURN};
IF n.spelled#inKey THEN n.show←TRUE ELSE n.show←FALSE;
Score.Redraw[min,max];
END;

Embellish:PROCEDURE[object:Interface.Object] =
BEGIN
x,y:INTEGER;
n:NotePTR←NIL;
min ←1000000;
max ← -1;
SetBrush[black,invert];
WHILE BlueBug[] DO
[x,y] ← Sheet.ScreenPoint[];
IF (n ← Piece.NearestNote[x,y])=NIL THEN LOOP;
Note.Draw[n];
Note.Draw[n];
ENDLOOP;
SetBrush[black,paint];
IF n=NIL OR n.rest THEN RETURN;
SELECT object FROM
trill
=> Note.SetEmbellishment[n,trill];
mordent1 => Note.SetEmbellishment[n,mordent1];
mordent2 => Note.SetEmbellishment[n,mordent2];
ENDCASE;
Score.Redraw[min,max];
END;

--******************************************************************
--
inserting objects
--******************************************************************

Insert:PROCEDURE[object:Interface.Object] =
BEGIN
OPEN CursorDefs;
tempCursor:Cursor ← cursor↑;
cursor↑ ← ALL[0];
SELECT object FROM
staves,measure,repeat1,repeat2,doubleMeasure,endMeasure => InsertMeasure[object];
bass,treble => InsertClefChange[object];
octava => {cursor↑ ← tempCursor; InsertOctava[object]};
doubleFlat,flat,natural,inKey,sharp,doubleSharp => ChangeSpelling[object];
trill,mordent1,mordent2 => Embellish[object];
note,rest => Interface.InsertNote[object];
ENDCASE => ERROR;
cursor↑ ← tempCursor;
count ← TRUE;
END;

InsertMeasure: PROCEDURE[object:Interface.Object] =
BEGIN
x,y:INTEGER;
s:SyncPTR ← Utility.NewSync[];
SELECT object FROM
measure => s.type ← measure;
staves => s.type ← staves;
repeat1 => s.type ← repeat1;
repeat2 => s.type ← repeat2;
endMeasure => s.type ← endMeasure;
doubleMeasure => s.type ← doubleMeasure;
ENDCASE => ERROR;
[x,y] ← Sheet.ScreenPoint[];
[s.time,] ← Sheet.NearestTime[x,y];
SetBrush[black,invert];
WHILE BlueBug[] DO
Sync.Draw[s];
Sync.Draw[s];
[x,y] ← Sheet.ScreenPoint[];
[s.time,] ← Sheet.NearestTime[];
ENDLOOP;
IF YellowBug[] THEN RETURN;
IF object=staves THEN {
s.value ← Sheet.GetStyle[s.time];
LOOPHOLE[s.event,Staves] ← style[s.value]};
Piece.AddSync[score,s];
SetDirty[s.time,s.time];
Score.Redraw[min,max];
END;

InsertClefChange: PROCEDURE[object:Interface.Object] =
BEGIN
old,new:Section;
sync:SyncPTR←NIL;
pitch,height:INTEGER;
oldTime,newTime:Time←0;
change:BOOLEAN←FALSE;
oldStaff,newStaff,oldClef:INTEGER←10;
SELECT object FROM
bass => pitch ← 27;
treble => pitch ← 48;
ENDCASE => Error;
SetBrush[black,invert];
[newTime,height] ← Sheet.NearestTime[];
newStaff ← Sheet.NearestStaff[newTime,height];
WHILE BlueBug[] DO
[newTime,height] ← Sheet.NearestTime[];
newStaff ← Sheet.NearestStaff[newTime,height];
IF newTime=oldTime AND newStaff=oldStaff THEN LOOP;
IF oldStaff#10 THEN Sheet.DrawClef[pitch,oldStaff,oldTime];
Sheet.DrawClef[pitch,newStaff,newTime];
oldTime ← newTime;
oldStaff ← newStaff;
ENDLOOP;
Sheet.DrawClef[pitch,newStaff,newTime];
IF YellowBug[] THEN RETURN;
old ← sheet[Sheet.FindLine[newTime]];
oldClef ← Clef[newStaff,newTime];
Sheet.SetClef[pitch,newStaff,newTime];
new ← sheet[Sheet.FindLine[newTime]];
IF flash THEN {Flash[]; RETURN};
IF old#new THEN SetDirty[new.time,new.time];
-- how much of the score does this affect?
SetDirty[newTime,newTime];
IF pitch=oldClef THEN {Score.Redraw[min,max]; RETURN};
FOR i:CARDINAL IN [0..scoreLength) DO
IF score[i].time#newTime THEN LOOP;
IF score[i].type#clef THEN LOOP;
IF score[i].value#newStaff THEN LOOP;
sync←score[i];
EXIT; ENDLOOP;
IF sync#NIL THEN SetDirty[sync.time,GetCarry[sync]];
Score.Redraw[min,max];
END;

InsertOctava: PROCEDURE[object:Interface.Object] =
BEGIN
begin,end,oldEnd:Time← -1;
staff,oldStaff,alternate:INTEGER←1;
pitch,height,oldHeight,octHeight,limit:INTEGER←0;
SetBrush[black,invert];
[begin,oldHeight] ← Sheet.NearestTime[];
staff ← Sheet.NearestStaff[begin,oldHeight];
WHILE BlueBug[] DO
[end,height,staff] ← Slide[oldEnd,oldHeight,oldStaff];
IF end=oldEnd AND height=oldHeight THEN LOOP;
-- WE HAVE A MOVEMENT
IF oldEnd#-1 THEN Sheet.DrawOctava[pitch,oldStaff,octHeight,begin,oldEnd];
oldEnd ← end;
oldStaff ← staff;
oldHeight ← height;
octHeight ← height - Sheet.Height[end,,staff];
pitch ← (IF Sheet.NormalPitch[staff]=27 THEN 15 ELSE 60);
Sheet.DrawOctava[pitch,staff,octHeight,begin,end];
ENDLOOP;
Sheet.DrawOctava[pitch,staff,octHeight,begin,end];
IF YellowBug[] THEN RETURN;
IF begin>=end THEN RETURN;
Sheet.SetOctava[pitch,staff,octHeight,begin,end];
IF flash THEN {Flash[]; RETURN};
Score.Redraw[begin,end];
END;

Slide:PROCEDURE[oldTime:Time,oldHeight:INTEGER,oldStaff:CARDINAL] RETURNS[time:Time,height:INTEGER,staff:CARDINAL] =
BEGIN
limit:INTEGER;
staves:StavesPTR;
staff ← oldStaff;
[time,height] ← Sheet.NearestTime[];
IF oldTime#-1 AND ABS[time-oldTime]>200 AND time>oldTime THEN
[time,height] ← Sheet.AlternateTime[time,height,-1];
IF oldTime#-1 AND ABS[time-oldTime]>200 AND time<oldTime THEN
[time,height] ← Sheet.AlternateTime[time,height,1];
IF time=oldTime AND height=oldHeight THEN RETURN;
-- should we move down to the next staff/line?
staves ← sheet[Sheet.FindSection[time]].staves;
staff ← Sheet.NextStaff[oldStaff,time];
limit ← staves.staff[staff].y+32;
IF staff=oldStaff THEN limit ← limit-32-staves.offset;
IF oldHeight <= limit THEN { -- we went below a lower limit
IF staff=oldStaff THEN { -- must be next line
[time,height] ← Sheet.AlternateTime[time,height,1];
staff ← 0};
RETURN};
-- should we move up to the next staff/line?
staff ← Sheet.PriorStaff[oldStaff,time];
limit ← staves.staff[staff].y;
IF staff=oldStaff THEN limit ← limit+32+staves.offset;
IF height >= limit THEN { -- we went above an upper limit
IF staff=oldStaff THEN { -- must be last line
[time,height] ← Sheet.AlternateTime[time,height,-1];
staff ← staves.sl};
RETURN};
staff ← oldStaff;
END;


--******************************************************************
--
moving things
--******************************************************************

MoveGraphical:PUBLIC PROCEDURE[object:Interface.Object] =
BEGIN
s:SyncPTR;
p:UnspecifiedPTR←NIL;
obj:ObjectType;
IF object#none THEN BEGIN Insert[object]; RETURN; END;
SetBrush[black,invert];
WHILE BlueBug[] DO
[obj,p] ← Piece.NearestObject[];
IF p=NIL THEN LOOP;
s ← LOOPHOLE[p];
IF voice AND s.type IN [clef..octava2] THEN LOOP;
SELECT obj FROM
measure => {
IF s.type IN [octava1..octava2]
THEN MoveOctava[s]
ELSE MoveMeasure[s]};
leftBeam => MoveBeam[p];
rightBeam=> ChangeTilt[p];
tie=> MoveTie[p];
ENDCASE;
ENDLOOP;
IF p#NIL THEN count←TRUE;
END;

MoveMeasure:PROCEDURE[s:SyncPTR] =
BEGIN
old:Time=s.time;
SetBrush[black,invert];
SetDirty[s.time,s.time];
WHILE BlueBug[] DO
Sync.Draw[s];
s.time ← Sheet.NearestTime[].time;
Sync.Draw[s];
ENDLOOP;
Sync.Draw[s];
SetDirty[s.time,s.time];
SELECT s.type FROM
clef => MoveClef[s,old,s.time];
staves => MoveStaves[s,old,s.time];
keySignature => MoveKey[s,old,s.time];
ENDCASE => {Piece.RemoveSync[score,s]; Piece.AddSync[score,s]};
Score.Redraw[min,max];
END;

MoveClef:PROCEDURE[s:SyncPTR,old,new:Time] =
BEGIN
oldCarry:Time;
time:Time=s.time;
newClef,clef:INTEGER;
newClef←Clef[s.value,s.time];
s.time ← old;
oldCarry ← GetCarry[s];
s.time ← new;
Piece.RemoveSync[score,s];
Piece.AddSync[score,s];
ResetSheet[min,max];
clef←Sync.GetStaff[s,s.value].pitch;
IF clef#newClef THEN SetDirty[new,GetCarry[s]];
IF clef#Clef[s.value,old] THEN SetDirty[old,oldCarry];
END;

MoveStaves:PROCEDURE[s:SyncPTR,old,new:Time] =
BEGIN
next:Time;
nextStyle,oldStyle,newStyle:INTEGER;
next ← sheet[Sheet.NextLine[Sheet.FindLine[MAX[old,new]]]].time;
nextStyle ← Sheet.GetStyle[next];
--
y ← sheet[Sheet.FindLine[old]].y;
oldStyle ← Sheet.GetStyle[old];
newStyle ← Sheet.GetStyle[new];
s.time ← new;
Piece.RemoveSync[score,s];
Piece.AddSync[score,s];
Sheet.Reset[];
IF nextStyle#Sheet.GetStyle[next] THEN SetDirty[begin,endTime];
IF oldStyle#Sheet.GetStyle[old] THEN SetDirty[begin,endTime];
IF newStyle#Sheet.GetStyle[new] THEN SetDirty[begin,endTime];
END;

MoveKey:PROCEDURE[s:SyncPTR,old,new:Time] =
BEGIN
oldCarry:Time;
newKey:INTEGER;
s.time ← old;
oldCarry ← GetCarry[s];
newKey ← Score.GetKey[new];
s.time ← new;
Piece.RemoveSync[score,s];
Piece.AddSync[score,s];
ResetSheet[min,max];
IF new>old AND newKey#s.value THEN SetDirty[new,GetCarry[s]];
IF new<old AND Score.GetKey[old]#s.value THEN SetDirty[old,oldCarry];
END;

MoveOctava:PROCEDURE[s:SyncPTR] =
BEGIN
staff:CARDINAL;
staves:StavesPTR;
pitch,height:INTEGER;
octava1,octava2:SyncPTR;
octava1 ← (IF s.type=octava1 THEN s ELSE Sync.Octava[s]);
octava2 ← (IF s.type=octava2 THEN s ELSE Sync.Octava[s]);
IF octava1=NIL THEN {Flash[]; RETURN};
staves ← LOOPHOLE[@octava1.event];
SetDirty[s.time,s.time];
SetBrush[black,invert];
staff ← octava1.value;
pitch ← staves.staff[octava1.value].pitch;
WHILE BlueBug[] DO
Sync.Draw[octava1];
[s.time,height,staff] ← Slide[s.time,height,staff];
staves.height ← height - Sheet.Height[octava1.time,,octava1.value];
staves.height ← Sheet.OctavaHeight[pitch,staves.height];
Sync.Draw[octava1];
ENDLOOP;
SetDirty[s.time,s.time];
IF octava1=NIL OR octava2=NIL OR
octava1.time>=octava2.time THEN {
Piece.DeleteSync[octava1]; -- deletes octava2 as well
Score.Redraw[min,max];
RETURN};
Piece.RemoveSync[score,s];
Piece.AddSync[score,s]; -- Remove/Add keeps the score sorted
Sheet.Reset[];
Score.Redraw[min,max];
END;

MoveBeam:PROCEDURE[b:BeamPTR] =
BEGIN
height,y:INTEGER←0;
SetBrush[black,invert];
y ← MouseY↑;
WHILE BlueBug[] DO
height←b.height+(y-MouseY↑);
IF height=b.height THEN LOOP;
[] ← Beam.Draw[b];
b.height←height; y←MouseY↑;
Beam.SetStems[b];
[] ← Beam.Draw[b];
ENDLOOP;
Score.Redraw[b.sync1.time,b.sync2.time];
END;

ChangeTilt:PROCEDURE[b:BeamPTR] =
BEGIN
height,oldHeight,y:INTEGER←0;
l:REAL ← 1;
SetBrush[black,invert];
oldHeight ← b.height + Real.FixI[b.tilt*(b.sync2.time-b.sync1.time)];
y ← MouseY↑;
WHILE BlueBug[] DO
height←oldHeight+(y-MouseY↑);
IF height=oldHeight THEN LOOP;
[] ← Beam.Draw[b];
b.tilt←l*(height-b.height)/(b.sync2.time-b.sync1.time);
oldHeight ← height; y←MouseY↑;
Beam.SetStems[b];
[] ← Beam.Draw[b];
ENDLOOP;
Score.Redraw[b.sync1.time,b.sync2.time];
END;

MoveTie:PROCEDURE[n:NotePTR] =
BEGIN
y:INTEGER;
time:Time;
time ← (n.sync.time+n.tie.sync.time)/2;
y ← MouseY↑;
SetBrush[black,invert];
WHILE BlueBug[] DO
IF y=MouseY↑ THEN LOOP;
Note.DrawTie[n];
n.tieHeight ← n.tieHeight+(y-MouseY↑);
y ← MouseY↑;
Note.DrawTie[n];
ENDLOOP;
Score.Redraw[n.tie.sync.time,n.sync.time];
END;

--******************************************************************
--
utility procedures
--******************************************************************

ResetSheet:PROCEDURE[t1,t2:Time] =
BEGIN
line:CARDINAL;
old1,old2:Section;
new1,new2:Section;
-- get state before reset
line ← Sheet.FindLine[MIN[t1,t2]];
old1 ← sheet[line];
line ← Sheet.NextLine[line];
old2 ← sheet[line];
Sheet.Reset[];
-- get state after reset
line ← Sheet.FindLine[MIN[t1,t2]];
new1 ← sheet[line];
line ← Sheet.NextLine[line];
new2 ← sheet[line];
-- compare before and after
IF old1#new1 THEN SetDirty[new1.time,new1.time+15];
IF old2#new2 THEN SetDirty[new2.time,new2.time+15];
IF old1.key#new1.key THEN SetDirty[begin,endTime];
IF old2.key#new2.key THEN SetDirty[begin,endTime];
END;

GetCarry:PROCEDURE[s:SyncPTR] RETURNS[Time] =
BEGIN
index:CARDINAL;
IF s.type#clef AND s.type#keySignature THEN RETURN[s.time];
-- is this insertion/deletion a NOOP?
IF s.type=keySignature AND Score.GetKey[s.time-1]=s.value THEN RETURN[s.time];
index←Sync.GetScoreIndex[s];
IF s.type=clef THEN
FOR i:CARDINAL IN (index..scoreLength] DO
IF i=scoreLength THEN RETURN[endTime];
IF score[i].type NOT IN SheetSwitch THEN LOOP;
IF score[i].type = staves AND ~Similar[s,score[i]] THEN RETURN[score[i].time];
IF score[i].type = staves THEN LOOP;
IF Sync.GetStaff[score[i],score[i].value].y#Sync.GetStaff[s,s.value].y THEN LOOP;
RETURN[score[i].time];
ENDLOOP;
IF s.type=keySignature THEN
FOR i:CARDINAL IN (index..scoreLength] DO
IF i=scoreLength THEN RETURN[endTime];
IF score[i].type # keySignature THEN LOOP;
RETURN[score[i].time];
ENDLOOP;
RETURN[s.time];
END;

Similar:PROCEDURE[s1,s2:SyncPTR] RETURNS[BOOLEAN] =
BEGIN
n,o:BOOLEAN;
oldS,newS:StavesPTR;
oldS ← LOOPHOLE[@s1.event];
newS ← LOOPHOLE[@s2.event];
IF oldS.sl#newS.sl THEN RETURN[FALSE];
FOR i:CARDINAL IN [1..newS.sl] 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[FALSE];
ENDLOOP;
RETURN[TRUE];
END;

Clef:PROCEDURE[staff:CARDINAL,time:Time] RETURNS[INTEGER] =
BEGIN
staves:StavesPTR = sheet[Sheet.FindSection[time]].staves;
RETURN[staves.staff[staff].pitch];
END;

Flash:PUBLIC PROCEDURE =
BEGIN
SetBrush[black,invert];
Graphics.DrawScreenArea[context];
Wait[1];
Graphics.DrawScreenArea[context];
flash ← FALSE;
END;

Wait:PUBLIC ENTRY PROCEDURE[ticks:CARDINAL] =
BEGIN
FOR i:CARDINAL IN [0..ticks) DO
WAIT timeout;
ENDLOOP;
END;

ProcessDefs.SetTimeout[@timeout,1];

END..