-- 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;