-- Author: John Maxwell
-- Last Edited by: Maxwell, November 14, 1983 10:06 am

DIRECTORY
Graphics USING [WorldToUser],
Inline USING [LowHalf],
MusicDefs,
Note USING [Delta],
Real USING [FixI],
Score USING [KeyHeight, ShowPitch],
Sheet USING [default, FindLine, FindSection, FindStaves, Height, NextLine, PriorLine],
UserTerminal USING[mouse];

SheetImplB: PROGRAM
IMPORTS Graphics, Inline, MusicDefs, Note, Real, Score, S
heet, UserTerminal
EXPORTS Sheet =
BEGIN
OPEN MusicDefs, Sheet, UserTerminal;

-- ****************************************************************************
-- procedures that map from note to screen
-- **************************************
**************************************

MapNote: PUBLIC PROCEDURE[sheet: SheetPTR, n: NotePTR] RETURNS[x, y: INTEGER] =
BEGIN
l: Section;
show: INTEGER;
t: Time ← n.sync.time;
IF n.rest AND n.value = whole THEN t ← t-7;
l ← sheet[FindSection[sheet, t]];
x ← Inline.LowHalf[t-l.time+l.x+Note.Delta[sheet, n]];
show ← Score.ShowPitch[sheet, n.pitch, n.spelled, l.key];
RETURN[x, l.y-sheet.top+PitchHeight[sheet, t, show, l.key, l.staves.staff[n.staff]]];
END;

Map: PUBLIC PROCEDURE[sheet: SheetPTR, t: Time, pitch: INTEGER, staff: CARDINAL] RETURNS[x, y: INTEGER] =
BEGIN
l: Section ← sheet[FindSection[sheet, t]];
RETURN[Inline.LowHalf[t-l.time+l.x], l.y-sheet.top + PitchHeight[sheet, t, pitch, l.key, l.staves.staff[staff]]];
END;

MapHeight: PUBLIC PROCEDURE[sheet: SheetPTR, t: Time, height: INTEGER] RETURNS[x, y: INTEGER] =
BEGIN
l: Section ← sheet[FindSection[sheet, t]];
RETURN[Inline.LowHalf[t-l.time+l.x], l.y-sheet.top + height];
END;

Height: PUBLIC PROCEDURE[sheet: SheetPTR, t: Time, pitch: INTEGER, staff: CARDINAL] RETURNS[INTEGER] =
BEGIN
l: Section ← sheet[FindSection[sheet, t]];
RETURN[PitchHeight[sheet, t, pitch, l.key, l.staves.staff[staff]]];
END;

Pitch: PUBLIC PROCEDURE[sheet: SheetPTR, t: Time, height: INTEGER, staff: CARDINAL] RETURNS[INTEGER] =
BEGIN
i: CARDINAL;
pitch: INTEGER ← 12;
octave: INTEGER;
l: Section ← sheet[FindSection[sheet, t]];
keyPitch: INTEGER ← (44+l.key*7) MOD 12;
height ← height - PitchHeight[sheet, t, keyPitch, l.key, l.staves.staff[staff]];
IF height < 0 THEN octave ← height/28-1 ELSE octave ← height/28;
octave ← height/28 - (IF height < 0 THEN 1 ELSE 0);
height ← Mod[height, 28];
FOR i IN [0..12) DO
IF pitchHeight[i] >= height THEN BEGIN pitch ← i; EXIT; END;
ENDLOOP;
RETURN[pitch + keyPitch + 12*octave];
END;

PitchHeight: PROCEDURE[sheet: SheetPTR, t: Time, pitch, key: INTEGER, staff: Staff] RETURNS[INTEGER] =
-- return the y height of the pitch relative to the top of the staves
-- if pitch > 100 use the staff’s pitch
BEGIN
height, octave: INTEGER;
IF pitch > 100 THEN RETURN[staff.y];
IF NOT sheet.accidental THEN RETURN[CrackHeight[t, pitch, staff]];
octave ← (pitch-Mod[pitch, 12]-staff.pitch+Mod[staff.pitch, 12])/12;
height ← 28*octave + Score.KeyHeight[key, pitch] - Score.KeyHeight[key, staff.pitch];
RETURN[staff.y+height];
END;

CrackHeight: PROCEDURE[t: Time, pitch: INTEGER, staff: Staff] RETURNS[INTEGER] =
BEGIN -- pitches with accidentals are placed between lines and spaces
height: INTEGER;
octave: INTEGER;
ms: CARDINAL ← Mod[staff.pitch, 12];
mp: CARDINAL ← Mod[pitch, 12];
octave ← (pitch-mp-staff.pitch+ms)/12;
height ← 28*octave + crackHeight[mp] - crackHeight[ms];
RETURN[staff.y+height];
END;

crackHeight: ARRAY[0..12) OF INTEGER = [0, 4, 6, 8, 10, 12, 14, 16, 20, 22, 24, 26];
pitchHeight: ARRAY [0..12) OF INTEGER = [0, 1, 4, 5, 8, 12, 13, 16, 17, 20, 21, 24];

-- ****************************************************************************
-- procedures that map from screen back
-- ****************************************************************************

ScreenPoint: PUBLIC PROCEDURE[sheet: SheetPTR] RETURNS[x, y: INTEGER] =
BEGIN
rx, ry: REAL;
[rx, ry] ← Graphics.WorldToUser[sheet.context, mouse.x, 808 - mouse.y];
x ← Real.FixI[rx];
y ← Real.FixI[ry]+sheet.top;
END;

NearestTime: PUBLIC PROCEDURE[sheet: SheetPTR, x, y: INTEGER] RETURNS[time: Time, height: INTEGER] =
BEGIN
line, next: CARDINAL ← 0;
IF x = default THEN [x, y] ← ScreenPoint[sheet];
WHILE sheet[next].y >= y DO
line ← next;
next ← Sheet.NextLine[sheet, line];
ENDLOOP;
IF y < ((sheet[line].y+Sheet.Height[sheet, sheet[line].time, , 3]+8)/2+sheet[next].y/2) THEN line ← next;
IF x < sheet[line].x THEN x ← sheet[line].x;
IF x > sheet.width THEN x ← sheet.width;
time ← sheet[line].time+ x- sheet[line].x;
height ← y- sheet[line].y;
END;

AlternateTime: PUBLIC PROCEDURE[sheet: SheetPTR, t1: Time, h1: INTEGER, lines: INTEGER] RETURNS[time: Time, height: INTEGER] =
BEGIN
next, prior, l: CARDINAL;
time ← t1;
height ← h1;
l ← Sheet.FindLine[sheet, time];
IF lines > 0 THEN FOR i: CARDINAL IN [0..ABS[lines]) DO
next ← Sheet.NextLine[sheet, l];
time ← time + (sheet.width - sheet[next].x);
height ← height - (sheet[next].y - sheet[l].y);
l ← next;
ENDLOOP;
IF lines < 0 THEN FOR i: CARDINAL IN [0..ABS[lines]) DO
prior ← Sheet.PriorLine[sheet, l];
time ← time - (sheet.width - sheet[l].x);
height ← height + (sheet[l].y - sheet[prior].y);
l ← prior;
ENDLOOP;
RETURN[time, height];
END;

NearestStaff: PUBLIC PROCEDURE[sheet: SheetPTR, t: Time, height: INTEGER] RETURNS[CARDINAL] =
BEGIN
j: CARDINAL ← 10;
staves: StavesPTR = Sheet.FindStaves[sheet, t];
FOR i: CARDINAL IN [0..staves.length) DO
IF i < 2 AND staves.staff[i] = staves.staff[i+1] THEN LOOP;
IF height < staves.staff[i].y THEN BEGIN j ← i; LOOP; END;
IF j = 10 THEN RETURN[i];
IF height < (staves.staff[j].y+staves.staff[i].y+34)/2 THEN RETURN[i] ELSE RETURN[j];
ENDLOOP;
RETURN[staves.length-1];
END;

END..

GetStaves: PROCEDURE[time: Time] RETURNS[StavesPTR] =
BEGIN
sync: SyncPTR ← NIL;
FOR i: CARDINAL IN [0..cacheLength) DO
IF cache[i] = NIL THEN EXIT;
IF cache[i].type # staves THEN LOOP;
IF cache[i].time > time AND sync = NIL THEN sync ← cache[i];
IF cache[i].time > time THEN EXIT;
sync ← cache[i];
ENDLOOP;
IF sync = NIL THEN BEGIN Sheet.SetStaves[2, 0, EndOfScore[]]; RETURN[GetStaves[time]]; END;
RETURN[LOOPHOLE[@sync.event]];
END;

Reset: PUBLIC PROCEDURE =
BEGIN
staves: Staves;
page: INTEGER ← 2;
i, j: CARDINAL ← 0;
sync: SyncPTR ← NIL;
time, break, top, cacheTime: Time ← 0;
height, x, sc, sheetHeight: INTEGER ← 0;
Score.BuildCache[];
MakeSheetConsistent[];
IF scale = 2 THEN sc ← 3 ELSE sc ← 2*scale;
FOR i IN [0..cacheLength] DO
IF i # cacheLength AND cache[i].type NOT IN SheetSwitch THEN LOOP;
IF i = cacheLength AND sync = NIL THEN RETURN;
IF i = cacheLength THEN cacheTime ← 400000 ELSE cacheTime ← cache[i].time;
IF sync = NIL THEN {sync ← cache[i]; staves ← LOOPHOLE[sync.event]};
WHILE time < cacheTime DO
sheet[j] ← [, height, 0, Key[time], time, LOOPHOLE[@sync.event]];
IF x = 0 AND top-height > (650*sc)/2 THEN BEGIN
top ← height; sheet[j].page ← page; page ← page+1; END;
SELECT TRUE FROM
x = 0 => {x ← ABS[8*(sheet[j].key)]; IF x = 0 THEN x ← 8};
ENDCASE => x ← x + Inline.LowHalf[time-sheet[j-1].time];
sheet[j].x ← x;
IF time >= break THEN break ← break+staffLength-x;
time ← MIN[cacheTime, break];
IF time >= break THEN BEGIN
sheetHeight ← -staves.staff[staves.sl].y;
height ← height - sheetHeight - staves.offset;
x ← 0; END;
j ← j+1; IF j = sheetLength THEN RETURN;
ENDLOOP;
IF i = cacheLength THEN EXIT;
sync ← cache[i];
staves ← LOOPHOLE[sync.event];
ENDLOOP;
FOR i: CARDINAL IN [0..beamHeapLength) DO
Beam.SetStems[beamHeap[i]];
ENDLOOP;
END;