--Author: John Maxwell
--last modified: November 21, 1981 11:33 AM

DIRECTORY
Graphics USING [DisplayContext, Vec, ScreenToUser],
InlineDefs USING [LowHalf],
MusicDefs,
Note USING [Delta],
Real USING [FixI],
Score USING [GetKey, KeyHeight, ShowPitch],
Sheet USING [default, FindLine, FindSection, Height, NextLine, PriorLine];

SheetImplB:PROGRAM
IMPORTS Graphics, InlineDefs, MusicDefs, Note, Real, Score, Sheet
EXPORTS Sheet =
BEGIN
OPEN MusicDefs, Sheet;

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

MapNote:PUBLIC PROCEDURE[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[t]];
x ← InlineDefs.LowHalf[t-l.time+l.x+Note.Delta[n]];
show ← Score.ShowPitch[n.pitch,n.spelled,l.key];
RETURN[x,l.y-top+PitchHeight[t,show,l.staves.staff[n.staff]]];
END;

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

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

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

Pitch:PUBLIC PROCEDURE[t:Time,height:INTEGER,staff:CARDINAL] RETURNS[INTEGER]=
BEGIN
i:CARDINAL;
key:INTEGER ← Score.GetKey[t];
pitch:INTEGER ← 12;
keyPitch:INTEGER ← (44+key*7) MOD 12;
octave:INTEGER;
l:Section ← sheet[FindSection[t]];
height ← height - PitchHeight[t,keyPitch,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[t:Time,pitch: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,key:INTEGER;
IF pitch>100 THEN RETURN[staff.y];
IF NOT show.accidental THEN RETURN[CrackHeight[t,pitch,staff]];
key ← Score.GetKey[t];
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 RETURNS[x,y:INTEGER] =
BEGIN
sp,p:Graphics.Vec;
sp.x ← MouseX↑;
sp.y ← MouseY↑;
p ← Graphics.ScreenToUser[context,sp];
x ← Real.FixI[p.x];
y ← Real.FixI[p.y]+top;
END;

NearestTime:PUBLIC PROCEDURE[x,y:INTEGER] RETURNS[time:Time,height:INTEGER]=
BEGIN
line,next:CARDINAL←0;
IF x=default THEN [x,y] ← ScreenPoint[];
WHILE sheet[next].y>=y DO
line ← next;
next ← Sheet.NextLine[line];
ENDLOOP;
IF y<((sheet[line].y+Sheet.Height[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 > staffLength THEN x ← staffLength;
time ← sheet[line].time+ x- sheet[line].x;
height← y- sheet[line].y;
END;

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

NearestStaff:PUBLIC PROCEDURE[t:Time,height:INTEGER] RETURNS[CARDINAL] =
BEGIN
i,j,k:CARDINAL←10;
staves:StavesPTR = sheet[Sheet.FindSection[t]].staves;
FOR i IN [0..staves.sl] 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.sl];
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 + InlineDefs.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;