-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 12:29 pm
DIRECTORY
Beam USING [Drawn],
Event USING [Adjust, Draw, GetScoreIndex, Sync],
Graphics USING [Context, DrawBox, DrawChar, GetBounds, SetCP, SetStipple],
MusicDefs,
Note USING [DrawTie, InVoice],
Piece USING [NearestEvent, Overflow],
Score USING [Draw, GetKey, Justify, LogicalToPhysical, ScalePhysical, SetStyle, ShowPitch],
Selection USING [Draw, selection],
Sheet USING [default, Draw, FindSection, FindStaves, Height, HiLite, MapNote, NearestTime, Scale, ScreenPoint, SetBegin];
ScoreImplB: PROGRAM
IMPORTS Beam, Graphics, MusicDefs, Note, Piece, Score, Selection, Sheet, Event
EXPORTS Score =
BEGIN
OPEN MusicDefs;
-- ****************************************************************************
-- drawing the score
-- ****************************************************************************
Draw: PUBLIC PROCEDURE[score: ScorePTR, erase: BOOLEAN] =
BEGIN
sheet: SheetPTR ← score.sheet;
Selection.Draw[]; -- remove selection from screen
IF erase THEN {
SetBrush[score, white, opaque];
Graphics.DrawBox[sheet.context, Graphics.GetBounds[sheet.context]];
SetBrush[score, black, transparent];
Sheet.Draw[sheet]};
DrawInterval[score, sheet.begin, sheet.endTime];
Selection.Draw[];
END;
Redraw: PUBLIC PROCEDURE[score: ScorePTR, t1, t2: Time] =
BEGIN
x, y: INTEGER;
select: BOOLEAN;
selection: SelectionPTR ← Selection.selection;
SetBrush[score, black, transparent];
SELECT score.sheet.scale FROM
1 => IF t2-t1 > 700 THEN {Score.Draw[score]; RETURN};
2 => IF t2-t1 > 2000 THEN {Score.Draw[score]; RETURN};
4 => IF t2-t1 > 8000 THEN {Score.Draw[score]; RETURN};
ENDCASE;
select ← selection.lineSelect AND selection.select2 > t1-40 AND selection.select1 < t2+50;
IF select THEN Selection.Draw[]; -- remove selection from screen
Sheet.HiLite[score.sheet, white, t1-20, t2+30];
DrawInterval[score, t1-30, t2+40];
IF select THEN Selection.Draw[];
IF selection.lineSelect THEN RETURN;
SetBrush[score, black, invert];
FOR i: CARDINAL IN [0..selection.length) DO
IF selection.note[i] = NIL THEN LOOP;
IF selection[i].sync.time > t2+40 OR selection[i].sync.time < t1-30 THEN LOOP;
IF ~Note.InVoice[selection[i], score.sheet.voice] THEN LOOP;
[x, y] ← Sheet.MapNote[score.sheet, selection[i]];
Graphics.SetCP[score.sheet.context, x, y];
Graphics.DrawChar[score.sheet.context, 170C];
ENDLOOP;
END;
DrawInterval: PROCEDURE[score: ScorePTR, t1, t2: Time] =
BEGIN
n: NotePTR;
staves: StavesPTR;
j: CARDINAL ← 0;
sheet: SheetPTR ← score.sheet;
[] ← Beam.Drawn[score, NIL]; -- clears the beam cache
SetBrush[score, black, transparent];
-- draw octava markings first
staves ← Sheet.FindStaves[sheet, t1];
FOR i: CARDINAL IN [0..staves.length) DO
IF staves.staff[i].pitch # 15 AND staves.staff[i].pitch # 60 THEN LOOP;
FOR j: CARDINAL DECREASING IN [0..score.length) DO
IF score.event[j].time >= t1 THEN LOOP;
IF score.event[j].type # staves THEN LOOP;
WITH ev: score.event[j] SELECT FROM
staves => {IF ev.staves NOT IN [octava1..octava2] THEN LOOP;
IF ev.value # i THEN LOOP;
IF ev.staves = octava2 THEN EXIT};
ENDCASE => ERROR;
Event.Draw[score, score.event[j]];
EXIT; ENDLOOP;
ENDLOOP;
-- pre-Adjust the first 10 syncs (beams may draw notes in syncs downstream)
j ← 0;
IF score.sheet.display = graphical THEN
FOR i: CARDINAL IN [0..score.length) DO
IF score.event[i].time < t1 THEN LOOP;
IF score.event[i].time > t2 THEN EXIT;
IF score.event[i].type = sync THEN {
IF j = 10 THEN EXIT ELSE j ← j+1;
Event.Adjust[score, Event.Sync[score.event[i]]]};
ENDLOOP;
-- draw the syncs
j ← 0;
FOR i: CARDINAL IN [0..score.length) DO
IF score.event[i].time < t1 THEN LOOP;
IF score.event[i].time > t2 THEN {j ← i; EXIT};
IF score.sheet.display = graphical THEN {
plus10: CARDINAL ← MIN[i+10, score.length-1];
IF score.event[plus10].type = sync
THEN Event.Adjust[score, Event.Sync[score.event[plus10]]]};
Event.Draw[score, score.event[i]];
IF NOT sheet.printing AND AnyBug[] THEN EXIT;
ENDLOOP;
-- draw ties that go off the end of the section
IF j # 0 THEN FOR i: CARDINAL IN [j..score.length) DO
IF score.event[i].time > t2+200 THEN EXIT;
IF score.event[i].type = sync THEN {
sync: SyncPTR ← Event.Sync[score.event[i]];
FOR j: CARDINAL IN [0..sync.length) DO
IF (n ← sync.note[j]) = NIL THEN EXIT;
IF n.tie = NIL THEN LOOP;
IF n.tie.sync.time > t2 THEN LOOP;
IF ~Note.InVoice[n, score.sheet.voice]
THEN Graphics.SetStipple[score.sheet.context, light]
ELSE Graphics.SetStipple[score.sheet.context, black];
Note.DrawTie[score, n];
ENDLOOP};
ENDLOOP;
[] ← Beam.Drawn[score, NIL];
END;
-- ****************************************************************************
-- printing the score
-- ****************************************************************************
Print: PUBLIC PROCEDURE[score: ScorePTR, splines: BOOLEAN] = {};
-- ****************************************************************************
-- changing the view
-- ****************************************************************************
Look: PUBLIC PROCEDURE[score: ScorePTR, look: LookCommand, switch: BOOLEAN, n: INTEGER] =
BEGIN ENABLE Piece.Overflow => IF score = old THEN score ← new;
draw: BOOLEAN ← TRUE;
SELECT look FROM
accidental => score.sheet.accidental ← switch;
hardcopy => {
Sheet.Scale[score, IF switch THEN 2 ELSE 1];
score.sheet.hardcopy ← switch};
justified => {
score.sheet.density ← n;
Score.Justify[score, Selection.selection.select1, Selection.selection.select2]};
logical => Score.LogicalToPhysical[score, Selection.selection.select1, Selection.selection.select2];
noCarry => score.sheet.noCarry ← switch;
notehead => score.sheet.notehead ← switch;
overview => Overview[score, switch];
physical => Score.ScalePhysical[score, 512/n];
style => Score.SetStyle[score, n, 0, EndOfScore[score]];
sync => score.sheet.sync ← switch;
voice => {score.sheet.voice ← IF ~switch THEN noVoice ELSE n};
ENDCASE;
END;
Overview: PROCEDURE[score: ScorePTR, switch: BOOLEAN] =
BEGIN
time: Time;
x, y: INTEGER;
SELECT TRUE FROM
score.sheet.scale < 4 AND ~switch => {score.flash ← TRUE; RETURN};
score.sheet.scale > 2 AND switch => {score.flash ← TRUE; RETURN};
switch => {Sheet.Scale[score, 4]; Sheet.SetBegin[score.sheet, 0]};
YellowBug[] => {
[x, y] ← Sheet.ScreenPoint[score.sheet];
time ← Sheet.NearestTime[score.sheet, x, y].time;
Sheet.Scale[score, IF score.sheet.hardcopy THEN 2 ELSE 1];
Sheet.SetBegin[score.sheet, time]};
ENDCASE => {
Sheet.Scale[score, IF score.sheet.hardcopy THEN 2 ELSE 1];
Sheet.SetBegin[score.sheet, 0]};
END;
-- ****************************************************************************
-- getting accidentals
-- ****************************************************************************
phi: ARRAY [0..12) OF INTEGER = [6, 1, -4, 3, -2, 5, 0, 7, 2, -3, 4, -1];
flatHeight: ARRAY[0..12) OF INTEGER = [0, 4, 8, 8, 12, 12, 16, 16, 20, 24, 24, 28];
sharpHeight: ARRAY[0..12) OF INTEGER = [0, 4, 4, 8, 8, 12, 12, 16, 20, 20, 24, 24];
GetAccidental: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR] RETURNS[Accidental] =
BEGIN -- hack to set n.shown
n.shown ← GetAccInternal[score, n];
RETURN[n.shown];
END;
GetAccInternal: PROCEDURE[score: ScorePTR, n: NotePTR] RETURNS[Accidental] = INLINE
BEGIN
a: Accidental;
prior: NotePTR;
index: CARDINAL;
pitch, key: INTEGER;
normal: Accidental;
noteHeight: INTEGER;
IF NOT score.sheet.accidental THEN RETURN[inKey];
IF n.tie # NIL AND n.tie.pitch = n.pitch THEN RETURN[inKey];
IF n.show AND n.spelled = inKey THEN Error["show inKey?"];
IF n.show THEN RETURN[n.spelled];
IF score.sheet.noCarry THEN {
IF n.spelled # inKey THEN RETURN[n.spelled];
a ← DefaultAcc[0, n.pitch];
IF a = natural THEN a ← inKey;
RETURN[a]};
key ← Score.GetKey[score, n.sync.time];
pitch ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key];
noteHeight ← Sheet.Height[score.sheet, n.sync.time, pitch, n.staff];
index ← Event.GetScoreIndex[score, n.sync];
IF index = score.length THEN index ← EventIndex[score, n.sync.time];
prior ← PriorNote[score, pitch, noteHeight, index];
normal ← NormalAcc[key, pitch];
-- no prior note
IF prior = NIL THEN SELECT TRUE FROM
normal = inKey => RETURN[n.spelled];
n.spelled = inKey => RETURN[normal];
ENDCASE => RETURN[n.spelled];
-- n.spelled = normal => RETURN[inKey];
-- n.spelled # normal => RETURN[n.spelled];
-- ENDCASE;
-- prior note
IF prior.pitch = n.pitch THEN RETURN[inKey];
-- an accidental MUST be asserted
IF n.spelled # inKey THEN RETURN[n.spelled];
IF normal # inKey THEN RETURN[normal];
IF Mod[n.pitch, 12] = 7 AND key < -5 THEN RETURN[flat];
IF Mod[n.pitch, 12] = 0 AND key < -6 THEN RETURN[flat];
IF Mod[n.pitch, 12] = 1 AND key > 5 THEN RETURN[sharp];
IF Mod[n.pitch, 12] = 8 AND key > 6 THEN RETURN[sharp];
normal ← NormalAcc[0, n.pitch];
IF normal = inKey THEN normal ← natural;
IF key < 0 AND normal = sharp THEN normal ← flat;
RETURN[normal];
END;
EventIndex: PROCEDURE[score: ScorePTR, time: Time] RETURNS[s: CARDINAL] = INLINE
BEGIN
s ← Piece.NearestEvent[score, time];
IF score.event[s] # NIL AND score.event[s].time >= time THEN RETURN[s];
FOR i: CARDINAL DECREASING IN [0..s+10) DO
IF score.event[i] = NIL THEN LOOP;
IF score.event[i].time > time THEN LOOP;
RETURN[i+1];
ENDLOOP;
END;
PriorNote: PUBLIC PROCEDURE[score: ScorePTR, pitch, height: INTEGER, index: CARDINAL] RETURNS[NotePTR] =
BEGIN
n: NotePTR;
sync: SyncPTR;
newPitch, key: INTEGER;
FOR i: CARDINAL DECREASING IN [0..index) DO
IF Measure[score.event[i]] THEN RETURN[NIL];
IF score.event[i].type # sync THEN LOOP;
key ← Score.GetKey[score, score.event[i].time];
sync ← Event.Sync[score.event[i]];
FOR j: CARDINAL IN [0..sync.length) DO
IF (n ← sync.note[j]) = NIL THEN EXIT;
IF n.rest THEN LOOP;
newPitch ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key];
IF NOT newPitch IN [pitch-2..pitch+2] THEN LOOP;
IF Sheet.Height[score.sheet, score.event[i].time, newPitch, n.staff] # height THEN LOOP;
RETURN[n];
ENDLOOP;
ENDLOOP;
RETURN[NIL];
END;
assert: Accidental = LOOPHOLE[LOOPHOLE[LAST[Accidental], CARDINAL]+1];
NormalAcc: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] =
BEGIN
strangeness: INTEGER ← phi[Mod[pitch, 12]];
-- I bet you don't really believe this works.
SELECT TRUE FROM
Mod[(key-strangeness), 12] > 4 => RETURN[inKey];
strangeness > 0 => RETURN[natural];
key < 0 => RETURN[flat];
ENDCASE => RETURN[sharp];
END;
DefaultAcc: PROCEDURE[key, pitch: INTEGER] RETURNS[Accidental] =
BEGIN
SELECT NormalAcc[0, pitch] FROM
inKey => RETURN[natural];
sharp => RETURN[IF key < 0 THEN flat ELSE sharp];
ENDCASE => ERROR;
END;
AddToPitch: PUBLIC PROCEDURE[key, pitch, delta: INTEGER] RETURNS[INTEGER] =
BEGIN
i: INTEGER;
IF delta > 0 THEN FOR i IN (pitch..90] DO
IF NormalAcc[key, i] = inKey THEN delta ← delta-1;
IF delta = 0 THEN RETURN[i];
ENDLOOP
ELSE FOR i DECREASING IN [0..pitch) DO
IF NormalAcc[key, i] = inKey THEN delta ← delta+1;
IF delta = 0 THEN RETURN[i];
ENDLOOP;
ERROR;
END;
KeyHeight: PUBLIC PROCEDURE[key, pitch: INTEGER] RETURNS[INTEGER] =
BEGIN
i: CARDINAL ← Mod[pitch, 12];
SELECT TRUE FROM
i = 7 AND key < -5 => RETURN[flatHeight[8]];
i = 0 AND key < -6 => RETURN[flatHeight[1]];
key < 0 => RETURN[flatHeight[i]];
i = 1 AND key > 5 => RETURN[sharpHeight[0]];
i = 8 AND key > 6 => RETURN[sharpHeight[7]];
ENDCASE => RETURN[sharpHeight[i]];
END;
END . . .
Print: PUBLIC PROCEDURE[splines: BOOLEAN] =
BEGIN
printAll: BOOLEAN;
oldBegin: Time ← begin;
ph: POINTER TO PressDefs.PressFileDescriptor;
device: Device.Handle ← Utility.OpenPressDevice[splines];
ph ← LOOPHOLE[device.data, PressDeviceImpl.DataRef].pressHandle;
printAll ← ~splines AND scale = 2;
IF printAll THEN Sheet.SetBegin[0];
FOR i: CARDINAL IN [0..scoreLength) DO
Event.Adjust[score.event[i]];
ENDLOOP;
WHILE TRUE DO
Sheet.Draw[];
DrawInterval[begin, endTime];
IF endTime > EndOfScore[]-40 THEN EXIT;
IF printAll THEN {Sheet.SetBegin[endTime]; PressDefs.WritePage[ph]} ELSE EXIT;
ENDLOOP;
Utility.ClosePressDevice[@device];
Sheet.SetBegin[oldBegin];
END;