-- Author: John Maxwell
-- Last Edited by: Maxwell, November 22, 1983 10:23 am
DIRECTORY
-- BcplFontFileDefs USING [OpenSDFontFile, CloseSDFontFile,
-- SplineCommandPtr, SplineDataPtr, GetSplineCommands],
-- Cubic USING [Bezier, BezierToCoeffs, Coeffs],
-- Device USING [Free, Handle, Object],
Beam USING [Free],
Chord USING [Free],
Event USING [GetScoreIndex, GetOctava, Staves, Sync],
Graphics USING [
Context, CurveTo, DrawChar, DrawArea,
DrawTo, FontRef, LineTo, MakeFont, MoveTo, NewPath, Path, SetCP, SetDefaultFont],
Heap USING [Create],
Mopcodes USING [zMISC, zPOP],
MusicDefs,
-- OpaqueDevice,
-- PressDefs USING [PressFileDescriptor, PutText, SetFont],
-- PressDevice USING [NewPressDevice],
-- PressDeviceImpl USING [DataRef],
Score USING [],
Space USING [
Create, Delete, GetHandle, Handle, LongPointer,
Map, PageFromLongPointer, Unmap, virtualMemory],
TTY USING [Create, Handle, NumberFormat, PutLine, PutLongNumber, PutString],
Utility USING [];
UtilityImpl: PROGRAM
IMPORTS Beam, Chord, Graphics, Heap, Event, TTY
EXPORTS MusicDefs, Score, -- OpaqueDevice-- Utility =
-- SHARES PressDeviceImpl =
BEGIN
OPEN Graphics, MusicDefs;
-- ****************************************************************************
-- graphics procedures
-- ****************************************************************************
-- context: PUBLIC Context;
text, music: PUBLIC FontRef;
music8: FontRef ← Graphics.MakeFont["music8"];
text12: FontRef ← Graphics.MakeFont["timesroman12"];
light: PUBLIC CARDINAL ← 102041B;
DrawLine: PUBLIC PROCEDURE[dc: Graphics.Context, x1, y1, x2, y2: INTEGER] =
BEGIN
SetCP[dc, x1, y1];
DrawTo[dc, x2, y2];
END;
DrawCubic: PUBLIC PROC[dc: Graphics.Context, x1, y1, x2, y2, height: INTEGER] =
BEGIN
path: Path ← NewPath[10];
MoveTo[path, x1, y1];
CurveTo[path, (4*x1+x2)/5, height+y1, (x1+4*x2)/5, height+y1, x2, y2];
LineTo[path, x2, y2-width1];
CurveTo[path, (x1+4*x2)/5, height-width2+y1,
(4*x1+x2)/5, height-width2+y1, x1, y1-width1];
DrawArea[dc, path];
END;
width1: INTEGER ← 0;
width2: INTEGER ← 3;
SetFont: PUBLIC PROCEDURE[dc: Context, font: FontRef, size: INTEGER] =
BEGIN -- a hack
IF font = text AND size = 12 THEN {Graphics.SetDefaultFont[dc, text12]; RETURN};
IF font = music AND size = 8 THEN {Graphics.SetDefaultFont[dc, music8]; RETURN};
Graphics.SetDefaultFont[dc, font];
END;
DrawString: PUBLIC PROCEDURE[dc: Context, s: STRING] =
BEGIN
FOR i: CARDINAL IN [0..s.length) DO DrawChar[dc, s[i]]; ENDLOOP;
END;
DrawChar: PUBLIC PROCEDURE[dc: Graphics.Context, c: CHARACTER] =
BEGIN
Graphics.DrawChar[dc, c];
END;
PointSize: PROCEDURE[sheet: SheetPTR, n: INTEGER] RETURNS[INTEGER] =
INLINE BEGIN
SELECT sheet.scale FROM
1 => RETURN[n];
2 => RETURN[2*n/3];
4 => RETURN[n/4];
ENDCASE;
RETURN[n];
END;
-- **********************************************************
-- printing the score
-- **********************************************************
print: PUBLIC BOOLEAN ← FALSE;
printChar: BOOLEAN ← FALSE;
-- ****************************************************************************
-- Basic Allocation Procedures
-- ****************************************************************************
zone: PUBLIC UNCOUNTED ZONE ← Heap.Create[32];
endOfBeam: PUBLIC VariousPTR ← [note[NIL]];
NewSegment: PUBLIC PROCEDURE[size, max, maxOffset: CARDINAL]
RETURNS[p: LONG POINTER] =
BEGIN
space: Space.Handle ← Space.Create[(size + 255)/256, Space.virtualMemory];
Space.Map[space];
p ← Space.LongPointer[space];
LongZero[p, size];
LOOPHOLE[p+maxOffset, LONG POINTER TO CARDINAL] ↑ ← max;
END;
FreeSegment: PUBLIC PROCEDURE[segment: LONG POINTER] =
BEGIN
space: Space.Handle ← Space.GetHandle[Space.PageFromLongPointer[segment]];
Space.Unmap[space];
Space.Delete[space];
END;
LongZero: PROC [where: LONG POINTER, nwords: CARDINAL] =
MACHINE CODE {Mopcodes.zMISC, 102B -- aZERO --; Mopcodes.zPOP; Mopcodes.zPOP};
-- ****************************************************************************
-- filestats, consistency checking
-- ****************************************************************************
FileStats: PUBLIC PROCEDURE[score: ScorePTR] =
BEGIN
OPEN Utility;
memory: LONG INTEGER;
highWater, notes: INTEGER ← 0;
WriteLine[""];
memory ← SIZE[ScoreRec[score.length]];
memory ← memory+ LONG[score.length]*SIZE[EventRec];
WriteString["scoreLength = "];
WriteNumber[score.length, [10, FALSE, TRUE, 4]];
FOR i: CARDINAL IN [0..score.length) DO
sync: SyncPTR;
IF score.event[i].type # sync THEN LOOP;
sync ← Event.Sync[score.event[i]];
notes ← notes + sync.length;
highWater ← MAX[highWater, sync.length];
ENDLOOP;
memory ← memory + LONG[notes]*SIZE[NoteRec];
WriteString["; # notes = "];
WriteNumber[notes, [10, FALSE, TRUE, 5]];
WriteString["; maxUsageOfSyncs = "];
WriteNumber[highWater, [10, FALSE, TRUE, 2]];
WriteLine[""];
highWater ← 0;
WriteString[" # chords = "];
WriteNumber[score.chordHeap.length, [10, FALSE, TRUE, 3]];
FOR i: CARDINAL IN [0..score.chordHeap.length) DO
highWater ← MAX[highWater, score.chordHeap.chord[i].length];
ENDLOOP;
WriteString["; maxUsageOfChords = "];
WriteNumber[highWater, [10, FALSE, TRUE, 2]];
memory ← memory + SIZE[ChordRec[score.chordHeap.length]];
memory ← memory + SIZE[BeamRec[score.beamHeap.length]];
WriteLine[""];
highWater ← 0;
WriteString[" # beams = "];
WriteNumber[score.beamHeap.length, [10, FALSE, TRUE, 3]];
FOR i: CARDINAL IN [0..score.beamHeap.length) DO
highWater ← MAX[highWater, score.beamHeap.beam[i].length];
ENDLOOP;
WriteString["; maxUsageOfBeams = "];
WriteNumber[highWater, [10, FALSE, TRUE, 2]];
WriteLine[""];
WriteString["event = "];
WriteNumber[SIZE[EventRec], [10, FALSE, TRUE, 2]];
WriteString["; note = "];
WriteNumber[SIZE[NoteRec], [10, FALSE, TRUE, 2]];
WriteString["; chord = "];
WriteNumber[SIZE[ChordRec], [10, FALSE, TRUE, 2]];
WriteString["; beam = "];
WriteNumber[SIZE[BeamRec], [10, FALSE, TRUE, 2]];
WriteString["; memory = "];
WriteNumber[memory, [10, FALSE, TRUE, 8]];
WriteLine[""];
END;
Test: PUBLIC PROCEDURE[score: ScorePTR] RETURNS[BOOLEAN] =
BEGIN
n: NotePTR;
s: EventPTR;
lastBeam: BeamPTR ← NIL;
beamFound: BOOLEAN;
sync1, sync2: BOOLEAN;
dataError ← FALSE;
FOR i: CARDINAL IN [0..score.max) DO
IF score.event[i] # NIL AND i >= score.length THEN
WriteError[sync, i, -1, "beyond scoreLength"];
IF score.event[i] = NIL AND i < score.length THEN WriteError[sync, i, -1, " = NIL"];
IF score.event[i] = NIL THEN LOOP;
-- IF i # 0 AND score[i-1].time > score[i].time THEN
-- WriteError[sync, i, -1, "out of place"];
WITH ev: score.event[i] SELECT FROM
staves => IF ev.staves IN [octava1..octava2] AND
Event.GetOctava[score, Event.GetOctava[score, Event.Staves[score.event[i]]]] # score.event[i]
THEN WriteError[sync, i, -1, " = octava with no matching end."];
sync => {
IF ev.length = 0 THEN WriteError[sync, i, -1, "empty"];
FOR j: CARDINAL IN [0..ev.max) DO
n ← ev.note[j];
IF n # NIL AND j >= ev.length THEN WriteError[sync, i, j, "beyond syncLength"];
IF n = NIL AND j < ev.length THEN WriteError[sync, i, j, " = NIL"];
IF n = NIL THEN LOOP;
IF n.sync # score.event[i] THEN WriteError[sync, i, j, "n.sync # score.event[i]"];
IF n.beam = NIL THEN LOOP;
IF n.beam = lastBeam THEN LOOP;
beamFound ← FALSE;
FOR k: CARDINAL IN [0..score.beamHeap.length) DO
IF score.beamHeap.beam[k] # n.beam THEN LOOP;
beamFound ← TRUE;
lastBeam ← n.beam;
EXIT; ENDLOOP;
IF NOT beamFound THEN WriteError[sync, i, j, "non-existant beam"];
ENDLOOP};
ENDCASE;
ENDLOOP;
FOR i: CARDINAL IN [0..score.chordHeap.max) DO
IF score.chordHeap.chord[i] # NIL AND i >= score.chordHeap.length THEN
WriteError[chord, i, -1, "beyond score.chordHeapLength"];
IF score.chordHeap.chord[i] = NIL AND i < score.chordHeap.length THEN
WriteError[chord, i, -1, " = NIL"];
IF score.chordHeap.chord[i] = NIL THEN LOOP;
IF score.chordHeap.chord[i].length = 0 THEN WriteError[chord, i, -1, "empty"];
s ← NIL;
FOR j: CARDINAL IN [0..score.chordHeap.length) DO
chord: ChordPTR = score.chordHeap.chord[i];
n ← chord.note[j];
IF n # NIL AND j >= chord.length THEN WriteError[chord, i, j, "beyond chordLength"];
IF n = NIL AND j < chord.length THEN WriteError[chord, i, j, " = NIL"];
IF n = NIL THEN LOOP;
IF s = NIL THEN s ← n.sync;
IF s # n.sync THEN WriteError[chord, i, j, "wrong sync"];
ENDLOOP;
ENDLOOP;
FOR i: CARDINAL IN [0..score.beamHeap.length) DO
IF score.beamHeap.beam[i] # NIL AND i >= score.beamHeap.length THEN
WriteError[beam, i, -1, "beyond score.beamHeapLength"];
IF score.beamHeap.beam[i] = NIL AND i < score.beamHeap.length THEN
WriteError[beam, i, -1, " = NIL"];
IF score.beamHeap.beam[i] = NIL THEN LOOP;
IF score.beamHeap.beam[i].length = 0 THEN WriteError[beam, i, -1, "empty"];
TestBeam[score, i];
sync1 ← Event.GetScoreIndex[score, score.beamHeap.beam[i].sync1] # score.length;
sync2 ← Event.GetScoreIndex[score, score.beamHeap.beam[i].sync2] # score.length;
IF NOT sync1 THEN WriteError[beam, i, -1, "sync1 not in score"];
IF NOT sync2 THEN WriteError[beam, i, -1, "sync2 not in score"];
ENDLOOP;
RETURN[dataError];
END;
TestBeam: PROCEDURE[score: ScorePTR, i: CARDINAL] =
BEGIN
beam: BeamPTR ← score.beamHeap.beam[i];
sync1, sync2: BOOLEAN ← FALSE;
FOR j: CARDINAL IN [0..beam.max) DO
IF beam.chord[j] # endOfBeam AND j >= beam.length THEN
WriteError[beam, i, j, "beyond beamLength"];
IF beam.chord[j] = endOfBeam AND j < beam.length THEN
WriteError[beam, i, j, " = NIL"];
IF beam.chord[j] = endOfBeam THEN LOOP;
WITH ev: beam.chord[j] SELECT FROM
note => BEGIN
IF ev.n.beam # beam THEN WriteError[beam, i, j, "wrong beam"];
IF ev.n.sync = beam.sync1 THEN sync1 ← TRUE;
IF ev.n.sync = beam.sync2 THEN sync2 ← TRUE;
END;
chord => BEGIN
IF ev.c.note[0].sync = beam.sync1 THEN sync1 ← TRUE;
IF ev.c.note[0].sync = beam.sync2 THEN sync2 ← TRUE;
FOR k: CARDINAL IN [0..ev.c.length) DO
IF ev.c.note[k] = NIL THEN LOOP;
IF ev.c.note[k].beam # beam THEN WriteError[beam, i, j, "wrong beam"];
ENDLOOP;
END;
beam => BEGIN
IF ev.b.beam # beam THEN WriteError[beam, i, j, "wrong beam"];
IF ev.b.sync1 = beam.sync1 THEN sync1 ← TRUE;
IF ev.b.sync2 = beam.sync2 THEN sync2 ← TRUE;
END;
ENDCASE;
ENDLOOP;
IF NOT sync1 THEN WriteError[beam, i, -1, "bad sync1"];
IF NOT sync2 THEN WriteError[beam, i, -1, "bad sync2"];
END;
WriteError: PROCEDURE[t: Type, i, j: INTEGER, s: STRING] =
BEGIN
SELECT t FROM
sync => BEGIN
WriteString["sync"];
WriteNumber[i, [10, FALSE, TRUE, 4]];
IF j > -1 THEN
BEGIN
WriteString[", event"];
WriteNumber[j, [10, FALSE, TRUE, 2]];
END;
END;
chord => BEGIN
WriteString["chord"];
WriteNumber[i, [10, FALSE, TRUE, 4]];
IF j > -1 THEN
BEGIN
WriteString[", note"];
WriteNumber[j, [10, FALSE, TRUE, 2]];
END;
END;
beam => BEGIN
WriteString["beam"];
WriteNumber[i, [10, FALSE, TRUE, 4]];
IF j > -1 THEN
BEGIN
WriteString[", chord"];
WriteNumber[j, [10, FALSE, TRUE, 2]];
END;
END;
ENDCASE;
dataError ← TRUE;
WriteString[": "];
WriteLine[s];
END;
Type: TYPE = {sync, chord, beam, none};
dataError: BOOLEAN ← FALSE;
CleanUp: PROCEDURE[score: ScorePTR] =
BEGIN
FOR i: CARDINAL IN [0..score.length) DO
IF score.event[i].type # sync THEN LOOP;
IF Event.Sync[score.event[i]].length = 0 THEN zone.FREE[@score.event[i]];
ENDLOOP;
FOR i: CARDINAL IN [0..score.chordHeap.length) DO
IF score.chordHeap.chord[i].length = 0
THEN Chord.Free[score, score.chordHeap.chord[i]];
ENDLOOP;
FOR i: CARDINAL IN [0..score.beamHeap.length) DO
IF score.beamHeap.beam[i].length = 0
THEN Beam.Free[score, score.beamHeap.beam[i]];
ENDLOOP;
END;
-- **********************************************************
-- interface to Cedar for output
-- **********************************************************
log: TTY.Handle ← TTY.Create[NIL];
Dummy: TYPE = RECORD[base: INTEGER, x, y: BOOLEAN, width: INTEGER];
WriteLine: PROCEDURE[s: STRING] = INLINE {TTY.PutLine[log, s]};
WriteString: PROCEDURE[s: STRING] = INLINE {TTY.PutString[log, s]};
WriteNumber: PROCEDURE[n: LONG UNSPECIFIED, format: TTY.NumberFormat] =
INLINE {TTY.PutLongNumber[log, n, format]};
END..
CleanUpSheets: PROCEDURE =
BEGIN
sheet: Staves;
FOR i: CARDINAL DECREASING IN [0..scoreLength) DO
IF score[i].type NOT IN SheetSwitch THEN LOOP;
sheet ← LOOPHOLE[score[i].event];
FOR j: CARDINAL IN [0..sheet.sl] DO
IF sheet.staff[j].pitch # 0 THEN LOOP;
Utility.FreeEvent[@score[i]]; EXIT;
ENDLOOP;
ENDLOOP;
END;
-- **********************************************************
-- printing the score
-- **********************************************************
print: PUBLIC BOOLEAN ← FALSE;
olddc: Graphics.Context;
printChar: BOOLEAN ← FALSE;
DeviceObject: PUBLIC TYPE = Device.Object; -- exported to OpaqueDevice
device: Device.Handle;
OpenPressDevice: PUBLIC PROCEDURE[splines: BOOLEAN] RETURNS[Device.Handle] =
BEGIN
l: REAL ← 1;
pos: Graphics.Vec;
printChar ← ~splines AND scale = 2;
print ← TRUE;
olddc ← context;
device ← PressDevice.NewPressDevice["music.press"];
context ← Graphics.NewContext[device];
pos ← Graphics.Map[olddc, context, [0, 0]];
pos.x ← pos.x+8;
pos.y ← pos.y+28;
Graphics.Translate[context, pos];
Graphics.SetLineWidth[context, 1];
IF scale = 2 THEN Scale[context, [(2*l)/3, (2*l)/3]] ELSE Scale[context, [l/scale, l/scale]];
SetColor[context, [0, 0, 0]];
IF NOT printChar THEN {
Graphics.Scale[context, [12, 12]]; -- to offset bug in something
BcplFontFileDefs.OpenSDFontFile["music8.sd"];
[ndp, ncp] ← BcplFontFileDefs.GetSplineCommands[0154C, SystemDefs.AllocateHeapNode]};
Utility.SetFont[context, music, 8];
RETURN[device];
END;
ClosePressDevice: PUBLIC PROCEDURE[device: POINTER TO Device.Handle] =
BEGIN
tncp: BcplFontFileDefs.SplineCommandPtr;
Graphics.FreeContext[@context];
Device.Free[device];
IF NOT printChar THEN BEGIN
BcplFontFileDefs.CloseSDFontFile[];
UNTIL ncp = NIL DO
tncp ← ncp.next;
SystemDefs.FreeHeapNode[ncp];
ncp ← tncp;
ENDLOOP;
END;
context ← olddc;
print ← FALSE;
END;
-- ****************************************************************************
-- graphics procedures
-- ****************************************************************************
SetFont: PUBLIC PROCEDURE[dc: Context, font: FontRef, size: INTEGER] =
BEGIN
OPEN PressDeviceImpl;
l: REAL ← 1;
Graphics.SetFont[dc, font, size];
IF NOT print THEN {Graphics.SetFont[dc, font, size]; RETURN};
IF printChar THEN BEGIN
fontname: STRING ← [16];
ph: POINTER TO PressDefs.PressFileDescriptor;
ptsize, face, rotation: CARDINAL ← 0;
ph ← LOOPHOLE[device.data, PressDeviceImpl.DataRef].pressHandle;
IF font = music THEN fontname ← "MOCKINGBIRD" ELSE fontname ← "TIMESROMAN";
ptsize ← IF font = music THEN LOOPHOLE[-24] ELSE 8;
face ← 0; -- PressDefs.EncodeFace['n, 'n, 'n]
PressDefs.SetFont[ph, fontname, ptsize, face, rotation];
END
ELSE BEGIN
BcplFontFileDefs.CloseSDFontFile[];
IF font = music THEN BcplFontFileDefs.OpenSDFontFile["music8.sd"]
ELSE BcplFontFileDefs.OpenSDFontFile["timesroman.sd"];
IF font = text THEN Scale[context, [12, 12]] ELSE Scale[context, [l/12, l/12]];
END;
END;
DrawCubic: PUBLIC PROC[x1, y1, x2, y2, height: INTEGER] =
BEGIN
b1, b2: Cubic.Bezier;
c1, c2: Cubic.Coeffs;
StartAreaPath[context];
EnterPoint[context, [x1, y1]];
b1 ← [[x1, y1], [(4*x1+x2)/5, height+y1], [(x1+4*x2)/5, height+y1], [x2, y2]];
c ← Cubic.BezierToCoeffs[b1];
EnterCubic[context, @c];
EnterPoint[context, [x2, y2-width1]];
b2 ← [[x2, y2-width1], [(x1+4*x2)/5, height-width2+y1],
[(4*x1+x2)/5, height-width2+y1], [x1, y1-width1]];
c ← Cubic.BezierToCoeffs[b2];
EnterCubic[context, @c];
DrawArea[context];
END;
DrawString: PUBLIC PROCEDURE[dc: Context, s: STRING] =
BEGIN
screen, pos: Vec ← GetPosition[context];
IF NOT print THEN {DisplayString[dc, s]; RETURN};
IF printChar THEN {
ph: POINTER TO PressDefs.PressFileDescriptor;
ph ← LOOPHOLE[device.data, PressDeviceImpl.DataRef].pressHandle;
screen ← UserToScreen[context, pos];
IF screen.x < 0 OR screen.y < 0 THEN RETURN;
PressDefs.PutText[ph, s, Real.FixC[screen.x], Real.FixC[screen.y]]}
ELSE {
FOR i: CARDINAL IN [0..s.length) DO
DrawChar[dc, s[i]];
RelMoveTo[dc, [5, 0]];
ENDLOOP};
END;
DrawChar: PUBLIC PROCEDURE[dc: Graphics.Context, c: CHARACTER] =
BEGIN
screen, pos: Vec ← GetPosition[context];
Move: PROC[v: POINTER TO Vec] = { NewBoundary[dc]; EnterPoint[dc, v↑] };
Draw: PROC[v: POINTER TO Vec] = { EnterPoint[dc, v↑] };
ECubic: PROC[c: POINTER TO Cubic.Coeffs] = { EnterCubic[dc, c] };
IF NOT print THEN BEGIN DrawChar[dc, c]; RETURN; END;
IF printChar THEN BEGIN
s: STRING ← [1];
ph: POINTER TO PressDefs.PressFileDescriptor;
ph ← LOOPHOLE[device.data, PressDeviceImpl.DataRef].pressHandle;
s.length ← 1; s[0] ← c;
screen ← UserToScreen[context, pos];
IF screen.x < 0 OR screen.y < 0 THEN RETURN;
PressDefs.PutText[ph, s, Real.FixC[screen.x], Real.FixC[screen.y]];
END
ELSE BEGIN
Translate[context, pos];
StartAreaPath[context, IF c = 'P OR c = 'X THEN FALSE ELSE TRUE];
DoSDChar[c, Move, Draw, ECubic];
DrawArea[context];
Translate[context, [-pos.x, -pos.y]];
END;
END;
ndp: BcplFontFileDefs.SplineDataPtr ← NIL;
ncp: BcplFontFileDefs.SplineCommandPtr ← NIL;
DoSDChar: PROCEDURE[char: CHARACTER,
Move: PROC[v: POINTER TO Vec],
Draw: PROC[v: POINTER TO Vec],
DCubic: PROC[c: POINTER TO Cubic.Coeffs]] =
BEGIN
pos: Vec ← [0, 0];
tscp, scp: BcplFontFileDefs.SplineCommandPtr;
sdp: BcplFontFileDefs.SplineDataPtr;
IF char = 154C AND ndp # NIL THEN {sdp ← ndp; scp ← ncp}
ELSE [sdp, scp] ← BcplFontFileDefs.GetSplineCommands[char,
SystemDefs.AllocateHeapNode];
IF char = 154C AND ndp = NIL THEN {ndp ← sdp; ncp ← scp};
tscp ← scp;
UNTIL scp = NIL DO
WITH scp SELECT FROM
MoveTo => {pos ← [x, y]; Move[@pos]};
DrawTo => {pos ← [x, y]; Draw[@pos]};
DrawCurve => {
c: Cubic.Coeffs ← [c3: [x2, y2], c2: [x1, y1], c1: [x0, y0], c0: pos];
DCubic[@c]; pos ← [pos.x+x0+x1+x2, pos.y+y0+y1+y2];
};
NewObject => NULL;
EndDefinition => EXIT;
ENDCASE;
scp ← scp.next;
ENDLOOP;
IF char # 154C THEN UNTIL (scp ← tscp) = NIL DO
tscp ← scp.next;
SystemDefs.FreeHeapNode[scp];
ENDLOOP;
END;