-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 11:43 am

DIRECTORY
Beam USING [RemoveItem],
Chord USING [RemoveNote],
Event USING [GetScoreIndex, RemoveNote, Sync],
Graphics USING [SetCP, DrawBox, SetStipple],
Inline USING [LowHalf],
MusicDefs,
Note USING [default, DrawHead, DrawTie, Duration, GetBackTie, Width],
Real USING [Fix],
Score USING [ShowPitch],
Selection USING [RemoveNote],
Sheet USING [FindLine, Height, Map, MapNote],
Utility USING [DrawChar, DrawCubic, DrawLine];

NoteImpl: PROGRAM
IMPORTS Beam, Chord, Graphics, Inline, Musi
cDefs, Note, Real, Score, Selection, Sheet, Event, Utility
EXPORTS Note =

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

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

Free: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR] =
BEGIN -- NILs out all pointers to n.
-- a note can be pointed at by a sync, a chord, a beam, a tie, and the selection
IF n.chord # NIL THEN Chord.RemoveNote[score, n.chord, n]; -- sets n.beam to NIL
IF n.beam # NIL THEN Beam.RemoveItem[score, n.beam, [note[n]]];
IF n.tie # NIL THEN {n.tie.tied ← FALSE; n.tie ← NIL};
IF n.tied THEN {Note.GetBackTie[score, n].tie ← NIL; n.tied ← FALSE};
Event.RemoveNote[score, n.sync, n, TRUE]; -- frees s if s.length = 0.
Selection.RemoveNote[n];
zone.FREE[@n];
END;

SetAccidental: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR, a: Accidental] =
BEGIN
IF score.sheet.voice # noVoice AND n.voice # score.sheet.voice THEN RETURN;
score.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;
score.flash ← FALSE;
n.spelled ← a;
SetDirty[score, n.sync.time, n.sync.time];
END;

SetEmbellishment: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR, e: Embellishment] =
BEGIN
IF score.sheet.voice # noVoice AND n.voice # score.sheet.voice THEN RETURN;
IF n.rest THEN RETURN;
n.embellish ← e;
SetDirty[score, 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[score: ScorePTR, n: NotePTR] RETURNS[t: NotePTR] =
BEGIN
sync: SyncPTR;
IF n = NIL THEN RETURN[NIL];
IF n.sync = NIL THEN RETURN[NIL];
IF ~n.tied THEN RETURN[NIL];
FOR i: CARDINAL IN [Event.GetScoreIndex[score, n.sync]..score.length) DO
IF score.event[i].type # sync THEN LOOP;
sync ← Event.Sync[score.event[i]];
FOR j: CARDINAL IN [0..sync.length) DO
IF sync.note[j].tie = n THEN RETURN[sync.note[j]];
ENDLOOP;
ENDLOOP;
Error;
END;

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

Draw: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR, stem: INTEGER] =
BEGIN
w: REAL = 0; -- width of ledger line
two: REAL = 2;
flag: NoteValue;
sheet: SheetPTR = score.sheet;
x, y, mid, width: INTEGER ← 0;
width ← Note.Width[n];
[x, y] ← Sheet.MapNote[sheet, n];
Note.DrawHead[score, n, x, y, x+width+2]; -- includes x delta
IF n.rest OR NOT sheet.notehead THEN RETURN;
-- draw the ledger lines
mid ← Sheet.Map[sheet, 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 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-y >= i*d DO IF ~n.grace
-- note below mid
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 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[sheet.context, x+width, y, x+width, stem]
ELSE DrawLine[sheet.context, x, y, x, stem];
-- draw the flag
IF n.grace
THEN SetCP[sheet.context, x, IF n.stemUp THEN stem-17 ELSE stem+17]
ELSE SetCP[sheet.context, x, IF n.stemUp THEN stem-24 ELSE stem+24];
SELECT TRUE FROM
flag = quarter => NULL;
n.grace => IF n.stemUp THEN DrawChar[sheet.context, 133C]
ELSE DrawChar[sheet.context, 134C];
flag = eighth => IF n.stemUp THEN DrawChar[sheet.context, 153C]
ELSE DrawChar[sheet.context, 163C];
flag = sixteenth => IF n.stemUp THEN DrawChar[sheet.context, 152C]
ELSE DrawChar[sheet.context, 162C];
flag = thirtysecond => IF n.stemUp THEN DrawChar[sheet.context, 151C]
ELSE DrawChar[sheet.context, 161C];
flag = sixtyfourth => IF n.stemUp THEN DrawChar[sheet.context, 151C]
ELSE DrawChar[sheet.context, 161C];
ENDCASE;
END;

DrawHead: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR, x, y, dotX: INTEGER] =
BEGIN
inVoice: BOOLEAN;
width, dotY: INTEGER;
sheet: SheetPTR ← score.sheet;
-- draw notehead (physical, rest, or normal), dot, and accidental
inVoice ← score.sheet.voice = noVoice OR n.voice = score.sheet.voice;

IF inVoice
THEN Graphics.SetStipple[sheet.context, black]
ELSE Graphics.SetStipple[sheet.context, light];
IF NOT sheet.notehead THEN {DrawPhysical[sheet, n, x, y]; RETURN};
IF n.tie # NIL THEN Note.DrawTie[score, n];
SetCP[sheet.context, x, y];
IF n.grace
THEN IF n.rest
THEN SELECT n.value FROM
ENDCASE => DrawChar[sheet.context, 172C]
ELSE SELECT n.value FROM
ENDCASE => DrawChar[sheet.context, 132C]
ELSE IF n.rest
THEN SELECT n.value FROM
whole => {SetCP[sheet.context, x, y+(IF sheet.printing THEN -5 ELSE 3)] ;
DrawChar[sheet.context, 145C]};
half => {IF sheet.printing THEN SetCP[sheet.context, x, y-9];
DrawChar[sheet.context, 145C]};
quarter => DrawChar[sheet.context, 144C];
eighth => DrawChar[sheet.context, 143C];
sixteenth => DrawChar[sheet.context, 142C];
thirtysecond => DrawChar[sheet.context, 141C];
ENDCASE => DrawChar[sheet.context, 114C]
ELSE SELECT n.value FROM
whole => DrawChar[sheet.context, 156C];
half => DrawChar[sheet.context, 155C];
ENDCASE => DrawChar[sheet.context, 154C];
width ← Note.Width[n];
IF DottedOnLine[sheet, n] THEN dotY ← y+4 ELSE dotY ← y;
IF n.dotted THEN BEGIN
SetCP[sheet.context, dotX, dotY];
DrawChar[sheet.context, 056C];
dotX ← dotX+3;
END;
IF n.doubleDotted THEN BEGIN
SetCP[sheet.context, dotX, dotY]; DrawChar[sheet.context, 056C]; dotX ← dotX+3;
SetCP[sheet.context, dotX, dotY]; DrawChar[sheet.context, 056C];
END;
IF n.embellish # none THEN BEGIN
sy: INTEGER;
sy ← MAX[y, Sheet.Map[sheet, n.sync.time, , n.staff].y+28];
IF n.embellish = trill
THEN SetCP[sheet.context, x, sy+10]
ELSE SetCP[sheet.context, x-2, sy+10];
SELECT n.embellish FROM
trill => DrawChar[sheet.context, ’U];
mordent1 => DrawChar[sheet.context, ’V];
mordent2 => DrawChar[sheet.context, ’W];
ENDCASE;
END;
IF n.rest OR NOT sheet.accidental THEN RETURN;
x ← x-9+(IF sheet.display = graphical THEN n.accDelta ELSE 0);
SetCP[sheet.context, x, y];
SELECT n.shown FROM -- n.shown is set by EventImpl.Adjust’s GetAccidental
doubleFlat => {SetCP[sheet.context, x-4, y]; DrawChar[sheet.context, 110C]};
flat => DrawChar[sheet.context, 111C];
natural => DrawChar[sheet.context, 112C];
sharp => DrawChar[sheet.context, 113C];
doubleSharp => DrawChar[sheet.context, 114C];
ENDCASE;
END;

DottedOnLine: PROCEDURE[sheet: SheetPTR, n: NotePTR] RETURNS[BOOLEAN] = INLINE
BEGIN
IF NOT n.dotted THEN RETURN[FALSE];
RETURN[Mod[Sheet.Height[sheet, n.sync.time, Score.ShowPitch[sheet, n.pitch, n.spelled, 0], n.staff]- Sheet.Height[sheet, 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[sheet: SheetPTR, n: NotePTR, x, y: INTEGER] =
BEGIN
duration: INTEGER;
IF n.rest THEN RETURN;
IF sheet.display # physical THEN duration ←
Inline.LowHalf[7*(Note.Duration[n, 128]/8)/sheet.density];
IF sheet.display = physical OR n.value = unknown THEN duration ← n.duration/sheet.density;
DrawBox[sheet.context, [x, y-2, MIN[x+duration, sheet.width+12], y+1]];
END;

DrawTie: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR] =
BEGIN
temp: NotePTR;
oneTie: BOOLEAN;
sheet: SheetPTR ← score.sheet;
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[score, n].tie ← n.tie;
temp.tied ← n.tied; n.tied ← TRUE;
n.tie ← temp.tie; temp.tie ← n;
n ← temp;
END;
x1 ← Sheet.Map[sheet, n.tie.sync.time, n.tie.pitch, n.tie.staff].x;
x2 ← Sheet.Map[sheet, n.sync.time, n.pitch, n.staff].x;
[xe, y1] ← Sheet.MapNote[sheet, n.tie]; x1 ← MAX[x1, xe];
[xe, y2] ← Sheet.MapNote[sheet, 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 ← sheet.width; ye ← y1; END
ELSE BEGIN oneTie ← TRUE; xe ← x2-2; ye ← y2; END;
x1 ← x1 + 10;
IF n.tie.sync.time >= sheet.begin THEN DrawCubic[sheet.context, x1, y1, xe, ye, n.tieHeight];
IF oneTie THEN RETURN;
IF n.sync.time <= sheet.endTime THEN BEGIN
xe ← sheet.section[Sheet.FindLine[sheet, n.sync.time]].x-6;
ye ← y2;
DrawCubic[sheet.context, 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
DrawBox[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
DrawBox[context, [x-3, middle+line-n*d, x+w, middle-line-n*d]];
ENDLOOP;
IF flag = whole OR flag = unknown THEN RETURN;
SetCP[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;
SetCP[context, x, y];
c ← GetAccidental[n];
SELECT c FROM
doubleFlat => BEGIN SetCP[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;