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

DIRECTORY
Beam USING [Remove],
Chord USING [RemoveNote],
Graphics USING [MoveTo, DrawRectangle, SetTexture],
InlineDefs USING [LowHalf],
MusicDefs,
Note USING [default, DrawHead, DrawTie, Duration, GetBackTie, FindChord, Width],
Piece USING [NearestSync],
Real USING [Fix],
Score USING [GetAccidental, ShowPitch],
Sheet USING [FindLine, Height, Map, MapNote],
Sync USING [RemoveNote],
Utility USING [DrawChar, DrawCubic, DrawLine, FreeNote, FreeSync];

NoteImpl: PROGRAM
IMPORTS Beam, Chord, Graphics, InlineDefs, MusicDefs, Note, Piece, Real, Score, Sheet, Sync, Utility
EXPORTS Note =

BEGIN
OPEN Graphics, MusicDefs, Note, Utility;
Error:SIGNAL;

--****************************************************************************
--
note procedures
--****************************************************************************

Delete:PUBLIC PROCEDURE[n:NotePTR,free:BOOLEAN] =
BEGIN
s:SyncPTR = n.sync;
c:ChordPTR = Note.FindChord[n];
Sync.RemoveNote[s,n];
IF c#NIL THEN Chord.RemoveNote[c,n];
IF c=NIL AND n.beam#NIL THEN Beam.Remove[n.beam,n,NIL,NIL];
IF NOT free THEN RETURN;
IF s.event[0]=NIL THEN Utility.FreeSync[@n.sync];
Utility.FreeNote[@n];
END;

SetAccidental:PUBLIC PROCEDURE[n:NotePTR,a:Accidental] =
BEGIN
IF voice AND n.voice#selectedVoice THEN RETURN;
flash ← TRUE;
SELECT n.pitch MOD 12 FROM
0 => IF a=doubleFlat OR a=sharp THEN RETURN;

1 => IF a=flat OR a=doubleSharp THEN RETURN;

2 => IF a=doubleFlat OR a=natural THEN RETURN;

3 => IF a=flat OR a=sharp THEN RETURN;

4 => IF a=doubleFlat OR a=natural OR a=doubleSharp THEN RETURN;
5 => IF a=flat OR a=sharp THEN RETURN;

6 => IF a=natural OR a=doubleSharp THEN RETURN;

7 => IF a=doubleFlat OR a=sharp THEN RETURN;

8 => IF a=flat OR a=doubleSharp THEN RETURN;

9 => IF a=doubleFlat OR a=natural THEN RETURN;

10=> IF a=flat OR a=sharp THEN RETURN;

11=> IF a=natural OR a=doubleSharp THEN RETURN;
ENDCASE;
flash ← FALSE;
n.spelled ← a;
SetDirty[n.sync.time,n.sync.time];
END;

SetEmbellishment:PUBLIC PROCEDURE[n:NotePTR,e:Embellishment] =
BEGIN
IF voice AND n.voice#selectedVoice THEN RETURN;
IF n.rest THEN RETURN;
n.embellish ← e;
SetDirty[n.sync.time,n.sync.time];
END;

Duration:PUBLIC PROCEDURE[n:NotePTR,metrenome:INTEGER] RETURNS[d:Time] =
BEGIN
m,t,rem:Time;
l:REAL←1;
b:BeamPTR;
IF metrenome#128 THEN m←Real.Fix[128*(l*256/metrenome)] ELSE m←256;
d ← SELECT n.value FROM
whole=>64, half=>32, quarter=>16, eighth=>8,
sixteenth=>4, thirtysecond=>2, ENDCASE=>1;
IF n.dotted THEN d ← (d*3)/2;
d ← d*m;
FOR b←n.beam,b.beam WHILE b#NIL DO
IF b.ntuple=0 THEN LOOP;
t ← (d*b.against)/b.ntuple;
rem ← d*(b.against)-t*b.ntuple;
IF b.sync2.time=n.sync.time THEN d ← t+rem ELSE d ← t;
ENDLOOP;
END;

GetBackTie:PUBLIC PROCEDURE[n:NotePTR] RETURNS[t:NotePTR] =
BEGIN
i,j:CARDINAL;
start:CARDINAL;
IF n=NIL THEN RETURN[NIL];
IF n.sync=NIL THEN RETURN[NIL];
IF ~n.tied THEN RETURN[NIL];
start ← Piece.NearestSync[p:score,t:n.sync.time,notesOnly:TRUE];
FOR i IN [start..scoreLength) DO
FOR j IN [0..syncLength) DO
IF score[i].event[j]=NIL THEN EXIT;
IF score[i].event[j].tie=n THEN RETURN[score[i].event[j]];
ENDLOOP;
ENDLOOP;
Error;
END;

--****************************************************************************
--
drawing notes
--****************************************************************************

Draw:PUBLIC PROCEDURE[n:NotePTR,stem:INTEGER] =
BEGIN
two:REAL = 2;
flag:NoteValue;
x,y,mid,width:INTEGER←0;
width ← Note.Width[n];
[x,y] ← Sheet.MapNote[n];
Note.DrawHead[n,x,y,x+width+2]; --
includes x delta
IF n.rest OR NOT show.notehead THEN RETURN;
--draw the ledger lines
mid ← Sheet.Map[n.sync.time,,n.staff].y+16;
FOR i:INTEGER IN [3..9] WHILE y-mid >= i*d DO IF ~n.grace
--note above mid
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-y >= i*d DO IF ~n.grace
--note below mid
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 n.stemUp THEN stem+1 ELSE stem-1); flag←quarter}
ELSE
SELECT TRUE FROM
n.grace AND n.stemUp => stem ← y+2*d;
n.grace AND ~n.stemUp => stem ← y-2*d;
n.stemUp => stem ← MAX[y+(7*d)/2,mid];
~n.stemUp => stem ← MIN[y-(7*d)/2,mid];
ENDCASE;
IF n.stemUp THEN DrawLine[x+width,y,x+width,stem] ELSE DrawLine[x,y,x,stem];
--draw the flag
IF n.grace
THEN MoveTo[context,[x,IF n.stemUp THEN stem-17 ELSE stem+17]]
ELSE MoveTo[context,[x,IF n.stemUp THEN stem-24 ELSE stem+24]];
SELECT TRUE FROM
flag=quarter=> NULL;
n.grace=> IF n.stemUp THEN DrawChar[context,133C]
ELSE DrawChar[context,134C];
flag=eighth=> IF n.stemUp THEN DrawChar[context,153C]
ELSE DrawChar[context,163C];
flag=sixteenth=> IF n.stemUp THEN DrawChar[context,152C]
ELSE DrawChar[context,162C];
flag=thirtysecond => IF n.stemUp THEN DrawChar[context,151C]
ELSE DrawChar[context,161C];
flag=sixtyfourth => IF n.stemUp THEN DrawChar[context,151C]
ELSE DrawChar[context,161C];
ENDCASE;
END;

DrawHead:PUBLIC PROCEDURE[n:NotePTR,x,y,dotX:INTEGER] =
BEGIN
inVoice:BOOLEAN;
width,dotY:INTEGER;
--
draw notehead (physical, rest, or normal), dot, and accidental
inVoice← ~(voice AND n.voice#selectedVoice); Graphics.SetTexture[context,IF inVoice THEN black ELSE light];
IF NOT show.notehead THEN {DrawPhysical[n,x,y]; RETURN};
IF n.tie#NIL THEN Note.DrawTie[n];
MoveTo[context,[x,y]];
IF n.grace
THEN IF n.rest
THEN SELECT n.value FROM
ENDCASE => DrawChar[context,172C]
ELSE SELECT n.value FROM
ENDCASE => DrawChar[context,132C]
ELSE IF n.rest
THEN SELECT n.value FROM
whole => {MoveTo[context,[x,y+(IF print THEN -5 ELSE 3)]] ;
DrawChar[context,145C]};
half => {IF print THEN MoveTo[context,[x,y-9]];
DrawChar[context,145C]};
quarter => DrawChar[context,144C];
eighth => DrawChar[context,143C];
sixteenth => DrawChar[context,142C];
thirtysecond => DrawChar[context,141C];
ENDCASE => DrawChar[context,114C]
ELSE SELECT n.value FROM
whole => DrawChar[context,156C];
half => DrawChar[context,155C];
ENDCASE => DrawChar[context,154C];
width ← Note.Width[n];
IF DottedOnLine[n] THEN dotY←y+4 ELSE dotY←y;
IF n.dotted THEN BEGIN
MoveTo[context,[dotX,dotY]];
DrawChar[context,056C];
dotX←dotX+3;
END;
IF n.doubleDotted THEN BEGIN
MoveTo[context,[dotX,dotY]]; DrawChar[context,056C]; dotX←dotX+3;
MoveTo[context,[dotX,dotY]]; DrawChar[context,056C];
END;
IF n.embellish#none THEN BEGIN
sy:INTEGER;
sy ← MAX[y,Sheet.Map[n.sync.time,,n.staff].y+28];
IF n.embellish=trill THEN MoveTo[context,[x,sy+10]] ELSE MoveTo[context,[x-2,sy+10]];
SELECT n.embellish FROM
trill => DrawChar[context,’U];
mordent1 => DrawChar[context,’V];
mordent2 => DrawChar[context,’W];
ENDCASE;
END;
IF n.rest OR NOT show.accidental THEN RETURN;
x ← x-9+(IF show.display=graphical THEN n.accDelta ELSE 0);
MoveTo[context,[x,y]];
SELECT Score.GetAccidental[n] FROM
doubleFlat => {MoveTo[context,[x-4,y]]; DrawChar[context,110C]};
flat => DrawChar[context,111C];
natural => DrawChar[context,112C];
sharp => DrawChar[context,113C];
doubleSharp => DrawChar[context,114C];
ENDCASE;
END;

DottedOnLine:PROCEDURE[n:NotePTR] RETURNS[BOOLEAN]=INLINE
BEGIN
IF NOT n.dotted THEN RETURN[FALSE];
RETURN[Mod[Sheet.Height[n.sync.time, Score.ShowPitch[n.pitch, n.spelled, 0], n.staff]- Sheet.Height[n.sync.time , ,n.staff ], 8]=0];
END;

Mod:PROCEDURE[k,m:INTEGER] RETURNS[n:INTEGER] =
BEGIN n ← k MOD m; IF n<0 THEN n←n+m; END;

DrawPhysical:PROCEDURE[n:NotePTR,x,y:INTEGER] =
BEGIN
duration:INTEGER;
IF n.rest THEN RETURN;
IF show.display#physical THEN duration ←
InlineDefs.LowHalf[7*(Note.Duration[n,128]/8)/TF];
IF show.display=physical OR n.value=unknown THEN duration ← n.duration/TF;
DrawRectangle[context,[x,y-2],[MIN[x+duration,staffLength+12],y+1]];
END;

DrawTie:PUBLIC PROCEDURE[n:NotePTR] =
BEGIN
temp:NotePTR;
oneTie:BOOLEAN;
x1,x2,y1,y2,xe,ye:INTEGER;
IF n.sync.time<n.tie.sync.time THEN BEGIN --
swap the notes
temp ← n.tie;
IF n.tied THEN Note.GetBackTie[n].tie←n.tie;
temp.tied ← n.tied; n.tied ← TRUE;
n.tie ← temp.tie; temp.tie ← n;
n ← temp;
END;
x1 ← Sheet.Map[n.tie.sync.time,n.tie.pitch,n.tie.staff].x;
x2 ← Sheet.Map[n.sync.time,n.pitch,n.staff].x;
[xe,y1] ← Sheet.MapNote[n.tie]; x1 ← MAX[x1,xe];
[xe,y2] ← Sheet.MapNote[n]; x2 ← MIN[x2,xe];
IF n.tieHeight>0 THEN y1 ← y1+2 ELSE y1 ← y1-2;
IF n.tieHeight>0 THEN y2 ← y2+2 ELSE y2 ← y2-2;
IF ABS[n.sync.time-n.tie.sync.time]<28 THEN {
x1 ← x1-5; x2 ← x2+5;
IF n.tieHeight>0 THEN y1 ← y1+4 ELSE y1 ← y1-4;
IF n.tieHeight>0 THEN y2 ← y2+4 ELSE y2 ← y2-4};
IF x1>x2 THEN BEGIN oneTie←FALSE; xe ← staffLength; ye ← y1; END
ELSE BEGIN oneTie←TRUE; xe ← x2-2; ye ← y2; END;
x1 ← x1 + 10;
IF n.tie.sync.time>=begin THEN DrawCubic[x1,y1,xe,ye,n.tieHeight];
IF oneTie THEN RETURN;
IF n.sync.time<=endTime THEN BEGIN
xe ← sheet[Sheet.FindLine[n.sync.time]].x-6;
ye ← y2;
DrawCubic[xe,ye,x2,y2,n.tieHeight];
END;
END;

d:CARDINAL = 8;

END..

DrawStem:PUBLIC PROCEDURE[x,y1,y2,stem,staff:INTEGER,stemUp,grace:BOOLEAN,flag:NoteValue] =
BEGIN
-- x is the left edge of the note,
-- y1 & y2 are the y coordinates of the top and bottom notes of a chord
-- stem is the y coordinate (stem=default is a request for the default stem)
n:INTEGER;
line:REAL←1;
IF stem#default -- drawing part of or beam
THEN
stem←(IF stemUp THEN stem+1 ELSE stem-1)
ELSE
{[,middle] ← Sheet.Map[n.sync.time,1000,n.staff]+16;
IF stemUp THEN stem ← MAX[y+(7*d)/2,middle] ELSE stem ← MIN[y-(7*d)/2,middle]};
FOR n IN [3..9] WHILE MAX[head,head2]-middle >= n*d DO
--
note above middle
DrawRectangle[context,[x-3,middle+line+n*d],[x+w,middle-line+n*d]];
ENDLOOP;
FOR n IN [3..9] WHILE middle-MIN[head,head2] >= n*d DO
--
note below middle
DrawRectangle[context,[x-3,middle+line-n*d],[x+w,middle-line-n*d]];
ENDLOOP;
IF flag=whole OR flag=unknown THEN RETURN;
MoveTo[context,[x,IF stemUp THEN tail-24 ELSE tail+24]];
SELECT flag FROM
eighth => IF stemUp THEN DrawChar[context,153C]
ELSE DrawChar[context,163C];
sixteenth => IF stemUp THEN DrawChar[context,152C]
ELSE DrawChar[context,162C];
thirtysecond => IF stemUp THEN DrawChar[context,151C]
ELSE DrawChar[context,161C];
ENDCASE;
IF stemUp THEN x ← x+8;
DrawLine[x,head,x,tail];
END;

Vector:TYPE = RECORD[x,y:INTEGER];

DisplayAcc:PROCEDURE[x,y:INTEGER,n:NotePTR] =
BEGIN
c:Accidental;
MoveTo[context,[x,y]];
c ← GetAccidental[n];
SELECT c FROM
doubleFlat => BEGIN MoveTo[context,[x-4,y]]; DrawChar[context,110C]; END;
flat => DrawChar[context,111C];
natural => DrawChar[context,112C];
sharp => DrawChar[context,113C];
doubleSharp => DrawChar[context,114C];
ENDCASE;
END;