--Author: John Maxwell
--last modified: December 18, 1981 8:55 AM

DIRECTORY
CursorDefs,
Beam USING [SetStems],
Chord USING [Draw],
Graphics USING [SetPaint, SetTexture],
InlineDefs USING [LowHalf],
Interface USING [count, Object,Wait],
MusicDefs,
Note USING [Draw, Duration, FindChord, GetBackTie, SetAccidental],
Piece USING [AddSync, CleanUpSyncs, NearestSync],
Score USING [GetKey, NormalAcc, PriorNote, Redraw, ShowPitch],
Selection, -- USING everything
Sheet USING [FindSection, Height, NearestStaff, NearestTime],
Sync USING [AddNote, Adjust],
Utility; -- USING everything


InterfaceImplC:PROGRAM
IMPORTS Beam, Chord, Graphics, InlineDefs, Interface, MusicDefs, Note, Piece, Score, Selection, Sheet, Sync, Utility
EXPORTS Interface =
BEGIN
OPEN Graphics, MusicDefs;

Error:SIGNAL = CODE;

--******************************************************************
--
deleting objects
--******************************************************************

defaultSync:SyncRec ← [];

InsertNote:PUBLIC PROCEDURE[object:Interface.Object] =
BEGIN
time:Time;
height,key:INTEGER;
n:NotePTR ← Utility.NewNote[];
n.value ← IF object=rest THEN quarter ELSE unknown;
n.rest ← (object=rest);
n.voice ← (IF voice THEN selectedVoice ELSE 0);
FOR i:CARDINAL IN [0..selectionLength) DO
IF selection[i]=NIL THEN LOOP;
n.value ← selection[i].value;
n.dotted ← selection[i].dotted;
n.grace ← selection[i].grace;
EXIT; ENDLOOP;
n.sync ← @defaultSync;
n.duration ← InlineDefs.LowHalf[7*(Note.Duration[n,128]/8)];
[time,height] ← Sheet.NearestTime[];
n.show ← FALSE;
n.sync.time ← time;
key ← Score.GetKey[time];
n.staff ← Sheet.NearestStaff[time,height];
IF n.rest
THEN [n.pitch,n.spelled] ←RestPosition[height,key,n.staff,time]
ELSE [n.pitch,n.spelled] ← DefaultPitch[height,key,n.staff,time];
n.stemUp ← StemDirection[n.pitch,n.staff,time];
n.voice ← IF voice THEN selectedVoice ELSE 0;
IF voice THEN maxVoice ← MAX[maxVoice, selectedVoice];
SetBrush[black,invert];
Note.Draw[n];
MoveNote[n];
Selection.Clear[];
Selection.AddNote[n];
command ← TRUE;
END;

DefaultPitch:PROCEDURE[height,key:INTEGER,staff:CARDINAL,time:Time] RETURNS[pitch:INTEGER,spelled:Accidental] =
BEGIN
prior:NotePTR;
normal:Accidental;
index:CARDINAL = SyncIndex[time];
pitch ← WhiteKey[time,height,staff];
pitch ← Score.ShowPitch[pitch,natural,key];
height ← Sheet.Height[time,pitch,staff];
prior ← Score.PriorNote[pitch,height,index];
IF show.noCarry OR prior=NIL THEN
IF Score.NormalAcc[key,pitch] =natural
THEN IF key<0
THEN RETURN[pitch-1,inKey]
ELSE RETURN[pitch+1,inKey]
ELSE RETURN[pitch,inKey];
pitch ← prior.pitch;
spelled ← prior.spelled;
IF spelled =inKey THEN RETURN;
IF spelled =doubleSharp THEN RETURN;
IF spelled =doubleFlat THEN RETURN;
-- can we substitute inKey for spelled?
normal ← Score.NormalAcc[key,pitch];
IF normal#inKey THEN RETURN;
normal ← Score.NormalAcc[0,pitch];
IF spelled=natural AND normal=inKey THEN spelled←inKey;
IF spelled=flat AND key<0 AND normal=sharp THEN spelled←inKey;
IF spelled=sharp AND key>=0 AND normal=sharp THEN spelled←inKey;
END;


Spell:PROCEDURE[a:Accidental] RETURNS[CARDINAL] = INLINE
{RETURN[SELECT a FROM doubleSharp => 4, sharp => 3,
natural,inKey => 2, flat => 1, ENDCASE => 0]};

SyncIndex:PROCEDURE[time:Time] RETURNS[s:CARDINAL] = INLINE
BEGIN
s ← Piece.NearestSync[score,time];
IF score[s]#NIL AND score[s].time>=time THEN RETURN[s];
FOR i:CARDINAL DECREASING IN [0..s+10) DO
IF score[i]=NIL THEN LOOP;
IF score[i].time>time THEN LOOP;
RETURN[i+1];
ENDLOOP;
END;

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

MoveNote:PUBLIC PROCEDURE[n:NotePTR] =
BEGIN
OPEN CursorDefs;
c:ChordPTR;
i:CARDINAL;
oldTime,time:Time;
scale,offset:INTEGER;
spell,oldSpell:INTEGER;
pitch,oldPitch:INTEGER;
note:NotePTR; s:SyncPTR;
tempCursor:Cursor ← cursor↑;
backtie,forwardtie:NotePTR←NIL;
oldHeight,height,x,y,key:INTEGER;
backEqual, forwardEqual:BOOLEAN;
yellow:{up,down1,clicked,down2}←up;
AccMode:PROCEDURE = {
scale ← 5;
oldSpell ← spell ← Spell[IF n.spelled=inKey
THEN DefaultAcc[Score.GetKey[oldTime],n.pitch]
ELSE n.spelled];
oldPitch ← 1000; -- forces a move
offset ← oldSpell*4*scale+2*scale;
pitch ← Score.ShowPitch[n.pitch,n.spelled,key];
oldHeight ← Sheet.Height[n.sync.time,pitch,n.staff];
y ← MouseY↑;
oldSpell ← 6};
NonAccMode:PROCEDURE = {
scale ← 1;
offset ← scale*10;
oldPitch ← 1000; -- forces a move
pitch ← Score.ShowPitch[n.pitch,n.spelled,key];
oldHeight ← Sheet.Height[n.sync.time,pitch,n.staff];
y ← MouseY↑};
IF n=NIL THEN RETURN;
cursor↑ ← ALL[0];
c ← Note.FindChord[n];
SetBrush[black,invert];
oldTime ← n.sync.time;
min ← max ← time ← oldTime;
IF n.tie#NIL THEN {
backEqual← (n.tie.pitch=n.pitch);
backtie ← n.tie;
n.tie ← NIL};
IF n.tied THEN {
forwardtie ← Note.GetBackTie[n];
forwardEqual ← (forwardtie.pitch=n.pitch)};
defaultSync.event ← ALL[NIL];
FOR i IN [0..chordLength) DO
IF c=NIL AND i>0 THEN EXIT;
IF c=NIL THEN note ← n ELSE note ← c.note[i];
IF note=NIL THEN EXIT;
Selection.RemoveNote[note];
IF note.beam#NIL THEN {
min ← MIN[min,note.beam.sync1.time];
max ← MAX[max,note.beam.sync2.time]};
IF note.sync#NIL THEN {
min ← MIN[min,note.sync.time];
max ← MAX[max,note.sync.time]};
Sync.AddNote[@defaultSync,note];
ENDLOOP;
Piece.CleanUpSyncs[score];
defaultSync.time ← oldTime;
x ← MouseX↑; y ← MouseY↑;
key ← Score.GetKey[oldTime];
NonAccMode[];
oldPitch ← pitch;
WHILE BlueBug[] DO Interface.Wait[1];
SELECT yellow FROM
down1 => IF ~YellowBug[] THEN yellow←clicked;
clicked => IF YellowBug[] THEN yellow←down2;
down2 => IF ~YellowBug[] THEN {yellow←up; NonAccMode[]};
up => IF YellowBug[] THEN {yellow←down1; AccMode[]};
ENDCASE;
time←oldTime+(MouseX↑-x);
[height,spell] ← Scale[oldHeight,(y+offset-MouseY↑),scale];
key ← Score.GetKey[time];
pitch ← WhiteKey[time,height,n.staff];
IF yellow=up THEN oldSpell ← spell;
IF pitch=oldPitch AND spell=oldSpell AND time=n.sync.time THEN LOOP;
-- WE HAVE A MOVEMENT
IF c#NIL THEN Chord.Draw[c] ELSE Note.Draw[n];
n.sync.time ← time;
IF pitch#oldPitch AND ~n.rest AND yellow=up THEN {
n.show ← FALSE;
[n.pitch,n.spelled] ← DefaultPitch[height,key,n.staff,time]};
IF spell#oldSpell AND ~n.rest THEN {
flash ← FALSE;
n.show ← TRUE;
SetAccidental[n,pitch,spell]};
oldPitch ← pitch;
oldSpell ← spell;
Sync.Adjust[n.sync];
IF n.rest THEN [n.pitch,n.spelled]← RestPosition[height,key,n.staff,time];
--
IF n.beam=NIL THEN n.stemUp ← StemDirection[pitch,n.staff,n.sync.time];
IF c#NIL THEN Chord.Draw[c] ELSE Note.Draw[n];
ENDLOOP;
-- ALL MOVEMENTS FINISHED
IF show.display=physical AND c#NIL
THEN FOR i IN [0..chordLength) DO
IF c.note[i]=NIL THEN EXIT;
c.note[i].toc ← c.note[i].sync.time*TF;
ENDLOOP
ELSE n.toc ← n.sync.time*TF;
IF c#NIL THEN Chord.Draw[c] ELSE Note.Draw[n];
s ← score[Piece.NearestSync[p:score,t:time,notesOnly:TRUE]];
IF s=NIL OR ABS[s.time-time]>2 THEN
BEGIN
s ← Utility.NewSync[];
s.time ← time;
Piece.AddSync[score,s];
END;
IF c#NIL THEN FOR i IN [0..chordLength) DO
IF c.note[i]=NIL THEN LOOP;
Sync.AddNote[s,c.note[i]];
ENDLOOP;
IF c=NIL THEN Sync.AddNote[s,n];
IF n.beam#NIL THEN Beam.SetStems[n.beam];
Selection.AddNote[NIL];
cursor↑ ← tempCursor;
flash ← FALSE;
WHILE YellowBug[] DO NULL; ENDLOOP;
n.tie ← backtie;
-- change the pitches of the ties if they were equal before
WHILE backtie#NIL AND backEqual DO
note ← backtie;
backtie ← backtie.tie;
IF backtie#NIL THEN backEqual← (backtie.pitch=note.pitch);
note.pitch ← pitch;
note.spelled←n.spelled;
min ← MIN[min,note.sync.time];
max ← MAX[max,note.sync.time];
ENDLOOP;
WHILE forwardtie#NIL AND forwardEqual DO
note ← forwardtie;
forwardtie←Note.GetBackTie[forwardtie];
IF forwardtie#NIL THEN forwardEqual←(forwardtie.pitch=note.pitch);
note.pitch ← pitch;
note.spelled←n.spelled;
min ← MIN[min,note.sync.time];
max ← MAX[max,note.sync.time];
ENDLOOP;
SetDirty[s.time,s.time];
Score.Redraw[min,max];
Piece.CleanUpSyncs[score];
Interface.count ← TRUE;
END;

tryX:INTEGER ← 3;

Scale:PROCEDURE[oldHeight,delta,scale:INTEGER] RETURNS[height,spell:INTEGER]=
BEGIN
height ← oldHeight+ModDiv[delta,(5*scale)];
spell ← Mod[delta,20*scale]/(4*scale);
END;

WhiteKey:PROCEDURE[t:Time,height:INTEGER,staff:CARDINAL] RETURNS[INTEGER]=
BEGIN
delta:INTEGER;
staffHeight:INTEGER = Sheet.Height[t,,staff];
pitch:INTEGER ← sheet[Sheet.FindSection[t]].staves.staff[staff].pitch;
delta ← ModDiv[(height-staffHeight),4];
IF delta>=0
THEN FOR i:CARDINAL IN [0..ABS[delta]) DO
SELECT Mod[pitch,12] FROM
0,7 => pitch ← pitch+1;
ENDCASE => pitch ← pitch + 2;
ENDLOOP
ELSE FOR i:CARDINAL IN [0..ABS[delta]) DO
SELECT Mod[pitch,12] FROM
1,8 => pitch ← pitch-1;
ENDCASE => pitch ← pitch - 2;
ENDLOOP;
RETURN[pitch];
END;

ModDiv:PROCEDURE[top,bottom:INTEGER] RETURNS[d:INTEGER] = INLINE
{d ← top/bottom;
IF top<0 AND Mod[top,bottom]#0 THEN d← d-1};

DefaultAcc:PROCEDURE[key,pitch:INTEGER] RETURNS[Accidental] =
BEGIN
SELECT Score.NormalAcc[0,pitch] FROM
inKey => RETURN[natural];
sharp => RETURN[IF key<0 THEN flat ELSE sharp];
ENDCASE => ERROR;
END;

SetAccidental:PROCEDURE[n:NotePTR,pitch,spell:INTEGER] =
BEGIN
--key:INTEGER;
--normal:Accidental;
IF n.rest THEN RETURN;
SELECT spell FROM
0=>{n.pitch←pitch-2; Note.SetAccidental[n,doubleFlat]};
1=>{n.pitch←pitch-1; Note.SetAccidental[n,flat]};
2=>{n.pitch←pitch; Note.SetAccidental[n,natural]};
3=>{n.pitch←pitch+1; Note.SetAccidental[n,sharp]};
4=>{n.pitch←pitch+2;Note.SetAccidental[n,doubleSharp]};
ENDCASE => ERROR;
IF flash THEN Error;
--IF n.spelled NOT IN [sharp..flat] THEN RETURN;
--key ← Score.GetKey[n.sync.time];
--normal ← Score.NormalAcc[key,n.pitch];
--IF normal#inKey THEN RETURN;
--normal ← Score.NormalAcc[0,n.pitch];
--IF n.spelled=natural AND normal=inKey THEN n.spelled←inKey;
--IF n.spelled=flat AND key<0 AND normal=sharp THEN n.spelled←inKey;
--IF n.spelled=sharp AND key>=0 AND normal=sharp THEN n.spelled←inKey;
END;

RestPosition:PROCEDURE[height,key:INTEGER,staff:CARDINAL,time:Time] RETURNS[pitch:INTEGER,spelled:Accidental] =
BEGIN
w:INTEGER = 8;
staffHeight:INTEGER = Sheet.Height[time,,staff];
height ← 8*ModDiv[(height-staffHeight),w] +w-4+staffHeight;
pitch ← WhiteKey[time,height,staff];
pitch ← Score.ShowPitch[pitch,natural,key];
spelled ← natural;
END;

StemDirection:PROCEDURE[pitch,staff:INTEGER,t:Time] RETURNS[BOOLEAN] =
BEGIN RETURN[Sheet.Height[t,pitch,staff]<Sheet.Height[t,,staff]+16]; END;

END..