<> <> <> <> <> DIRECTORY Beam USING [Draw, Drawn, SetSyncs], Chord USING [Adjust, Draw], Graphics USING [DrawRectangle, MoveTo, SetTexture], MusicDefs, Note USING [FindChord, Delta, Draw], Score USING [DrawKey, DrawTimeSignature, DrawMetrenome, GetAccidental], Sheet USING [DrawClef, DrawOctava, FindSection, Map, MapNote], Sync USING [GetScoreIndex, GetStaff, RemoveNote], Utility USING [DrawChar, DrawLine]; SyncImpl: CEDAR PROGRAM IMPORTS Beam, Chord, Graphics, MusicDefs, Note, Score, Sheet, Sync, Utility EXPORTS MusicDefs, Sync = BEGIN OPEN Graphics, MusicDefs, Utility; Error: SIGNAL; Overflow: PUBLIC SIGNAL[type: Sequence] = CODE; GetScoreIndex: PUBLIC PROC[s: SyncPTR] RETURNS[CARDINAL] = {i, j, index: CARDINAL _ 0; binary: CARDINAL _ 8192; IF s=NIL THEN RETURN[scoreLength]; FOR i IN [0..13) DO binary _ binary/2; IF index+binary>=scoreLength THEN LOOP; IF score[index+binary].time<=s.time THEN index _ index+binary; IF score[index].time#s.time THEN LOOP; IF score[index]=s THEN RETURN[index]; <> FOR j IN (index..scoreLength) WHILE score[j].time=s.time DO IF score[j]=s THEN RETURN[j]; ENDLOOP; FOR j DECREASING IN [0..index) WHILE score[j].time=s.time DO IF score[j]=s THEN RETURN[j]; ENDLOOP; EXIT; ENDLOOP; <> FOR i IN [0..scoreLength) DO IF score[i]=s THEN RETURN[i]; ENDLOOP; RETURN[scoreLength]}; --not in score GetStaff: PUBLIC PROC[s: SyncPTR, staff: CARDINAL] RETURNS[LONG POINTER TO Staff] = {staves: StavesPTR = LOOPHOLE[@s.event]; RETURN[@staves.staff[staff]]}; Grace: PUBLIC PROC[s: SyncPTR] RETURNS[BOOL] = {IF s.type#notes THEN RETURN[FALSE]; FOR j: CARDINAL IN [0..syncLength) DO IF s.event[j]=NIL THEN EXIT; IF ~s.event[j].grace THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]}; InVoice: PUBLIC PROC[s: SyncPTR, voice: CARDINAL] RETURNS[BOOL] = {FOR j: CARDINAL IN [0..syncLength) DO IF s.event[j]=NIL THEN EXIT; IF s.event[j].voice=voice THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]}; Length: PUBLIC PROC[s: SyncPTR] RETURNS[CARDINAL] = {FOR i: CARDINAL IN [0..syncLength] DO IF i=syncLength THEN RETURN[i]; IF s.event[i]=NIL THEN RETURN[i]; ENDLOOP; ERROR}; <<****************************************************************************>> <> <i>> <<****************************************************************************>> AddNote: PUBLIC PROC[s: SyncPTR, n: NotePTR] = { i, j: CARDINAL; c: ChordPTR = Note.FindChord[n]; IF n.sync=s THEN RETURN; IF s.type#notes THEN ERROR; FOR j IN [0..chordLength) DO IF c=NIL AND j#0 THEN EXIT; IF c#NIL THEN n_ c.note[j]; IF n=NIL THEN EXIT; IF s.event[syncLength-1]#NIL THEN ERROR; --sync full! FOR i IN [0..syncLength) DO IF s.event[i]=n THEN EXIT; IF s.event[i]#NIL THEN LOOP; Sync.RemoveNote[n.sync, n]; n.sync _ s; s.event[i] _ n; IF n.beam#NIL THEN Beam.SetSyncs[n.beam]; EXIT; ENDLOOP; ENDLOOP; }; RemoveNote: PUBLIC PROC[s: SyncPTR, n: NotePTR] = { <> i, last: CARDINAL; first: BOOL _ TRUE; IF n=NIL OR s=NIL THEN RETURN; FOR i DECREASING IN [0..syncLength) DO IF first AND s.event[i]#NIL THEN { first _ FALSE; last _ i; }; IF s.event[i]#n THEN LOOP; s.event[i] _ s.event[last]; s.event[last] _ NIL; ENDLOOP; }; AddTimes: PUBLIC PROC[s: SyncPTR, time: Time, toc: Time] = { i: CARDINAL; IF s=NIL THEN RETURN; s.time _ s.time + time; FOR i IN [0..syncLength) DO IF s.event[i]=NIL THEN EXIT; s.event[i].toc _ s.event[i].toc + toc; ENDLOOP; }; <<******************************************************************>> <> <<******************************************************************>> Draw: PUBLIC PROC[s: SyncPTR] = { staves: StavesPTR; IF s=NIL THEN RETURN; Graphics.SetTexture[context, black]; SELECT s.type FROM notes => DrawNotes[s]; clef => Sheet.DrawClef[Sync.GetStaff[s, s.value].pitch, s.value, s.time]; octava1 => { pitch, height: INTEGER; octava2: SyncPTR = Octava[s]; IF octava2=NIL THEN {Error; RETURN}; staves _ LOOPHOLE[@s.event]; pitch _ Sync.GetStaff[s, s.value].pitch; height _ staves.height; Sheet.DrawOctava[pitch, s.value, height, s.time, octava2.time]}; octava2 => IF Octava[Octava[s]]#s THEN DrawMeasure[doubleMeasure, s.time, 20]; staves => DrawMeasure[measure, s.time, 10]; keySignature => Score.DrawKey[s.value, s.time]; timeSignature => Score.DrawTimeSignature[s.ts, s.time]; metrenome => Score.DrawMetrenome[s.value, s.time]; IN [measure..m5] => DrawMeasure[s.type, s.time, 0]; ENDCASE; }; SetStave: PUBLIC PROC[oldS: StavesPTR, new: SyncPTR] = { n, o: BOOL; newS: StavesPTR; pitch, height: INTEGER; IF new#NIL THEN newS _ LOOPHOLE[@new.event] ELSE RETURN; height _ newS.height; IF new.type#staves THEN { pitch _ newS.staff[new.value].pitch; newS^ _ oldS^; newS.height _ height; FOR j: CARDINAL IN [0..newS.sl] DO IF newS.staff[j].y#newS.staff[new.value].y THEN LOOP; newS.staff[j].pitch _ pitch; ENDLOOP; RETURN}; <> newS^ _ style[new.value]; newS.height _ height; <> IF oldS=NIL THEN RETURN; IF oldS.sl#newS.sl THEN RETURN; FOR i: CARDINAL IN [1..newS.sl] DO n _ newS.staff[i].y=newS.staff[i-1].y; o _ oldS.staff[i].y=oldS.staff[i-1].y; IF n#o THEN RETURN; ENDLOOP; FOR i: CARDINAL IN [0..newS.sl] DO newS.staff[i].pitch _ oldS.staff[i].pitch; ENDLOOP; }; Octava: PUBLIC PROC[s: SyncPTR] RETURNS[SyncPTR] = { OPEN Sync; -- USING [GetScoreIndex, GetStaff]; IF s=NIL THEN RETURN[NIL]; IF s.type=octava1 THEN { FOR i: CARDINAL IN (GetScoreIndex[s]..scoreLength) DO IF score[i].type#octava2 THEN LOOP; IF s.value=score[i].value THEN RETURN[score[i]]; IF GetStaff[s, s.value].y=GetStaff[score[i], score[i].value].y THEN RETURN[score[i]]; ENDLOOP}; IF s.type=octava2 THEN { FOR i: CARDINAL DECREASING IN [0..GetScoreIndex[s]) DO IF score[i].type#octava1 THEN LOOP; IF s.value=score[i].value THEN RETURN[score[i]]; IF GetStaff[s, s.value].y=GetStaff[score[i], score[i].value].y THEN RETURN[score[i]]; ENDLOOP}; RETURN[NIL]; }; DrawNotes: PROC[s: SyncPTR] = { b: BeamPTR; c: ChordPTR; x, y: INTEGER; sync: SyncRec; i, j, k: CARDINAL; min, vMin: INTEGER _ 1000; max, vMax: INTEGER _-1000; sync _ s^; FOR i IN [0..syncLength) DO IF s.event[i]=NIL THEN EXIT; IF sync.event[i]=NIL THEN LOOP; c _ Note.FindChord[s.event[i]]; b _ s.event[i].beam; IF b#NIL AND b.beam#NIL THEN b _ b.beam; IF c#NIL THEN FOR j IN [0..chordLength) DO IF c.note[j]=NIL THEN EXIT; FOR k IN (i..syncLength) DO IF sync.event[k]=c.note[j] THEN sync.event[k] _ NIL; ENDLOOP; ENDLOOP; IF b#NIL AND NOT Beam.Drawn[b] THEN { [] _ Beam.Draw[b]; LOOP; }; IF c#NIL AND b=NIL THEN { Chord.Draw[c]; LOOP; }; IF b=NIL THEN Note.Draw[s.event[i]]; ENDLOOP; IF NOT show.sync THEN RETURN; <> FOR i IN [0..syncLength) DO IF s.event[i]=NIL THEN EXIT; [x, y] _ Sheet.MapNote[s.event[i]]; min _ MIN[min, y]; max _ MAX[max, y]; IF voice AND s.event[i].voice#selectedVoice THEN LOOP; vMin _ MIN[vMin, y]; vMax _ MAX[vMax, y]; ENDLOOP; Graphics.SetTexture[context, light]; IF min#vMin OR max#vMax THEN DrawLine[x+4, min-6, x+4, max+6]; Graphics.SetTexture[context, black]; IF vMin#vMax AND vMin#1000 THEN DrawLine[x+4, vMin-6, x+4, vMax+6]; }; DrawMeasure: PROC[type: EventType[measure..m5], time: Time, dy: INTEGER] = { i: CARDINAL; staves: StavesPTR; x1, y1, top, delta: INTEGER; IF time=0 THEN RETURN; IF print THEN dy _ 0; Graphics.SetTexture[context, black]; staves _ sheet[Sheet.FindSection[time]].staves; [x1, y1] _ Sheet.Map[time,, staves.sl]; y1 _ y1 - dy; top _ -staves.staff[staves.sl].y+2*dy; SELECT type FROM measure => DrawLine[x1, y1, x1, y1+top]; doubleMeasure => {DrawLine[x1, y1, x1, y1+top]; IF print OR scale=1 THEN delta _ 2 ELSE delta _ 3; DrawLine[x1-delta, y1, x1-delta, y1+top]}; repeat1 => { DrawRectangle[context,[x1, y1],[x1+3, y1+top]]; DrawLine[x1+5, y1, x1+5, y1+top]; }; repeat2, endMeasure => { DrawRectangle[context,[x1+1, y1],[x1-2, y1+top]]; DrawLine[x1-5, y1, x1-5, y1+top]; }; ENDCASE; IF type#repeat1 AND type#repeat2 THEN RETURN; IF type=repeat1 THEN delta _ 6 ELSE delta _ -13; FOR i IN [0..staves.sl] DO [x1, y1] _ Sheet.Map[time,, i]; MoveTo[context,[x1+delta, y1+16+4]]; DrawChar[context,'.]; MoveTo[context,[x1+delta, y1+16-4]]; DrawChar[context,'.]; ENDLOOP; }; <<****************************************************************************>> <> <<****************************************************************************>> Adjust: PUBLIC PROC[s: SyncPTR] = { n: NotePTR; c: ChordPTR; sync: SyncRec; delta: INTEGER; pad: ScratchPad; top: CARDINAL _ syncLength-1; i, j, k, bottom: CARDINAL _ 0; length: CARDINAL _ 0; IF s=NIL THEN RETURN ELSE sync _ s^; IF s.type#notes THEN RETURN; <> FOR i IN [0..syncLength) DO IF (n _ s.event[i])=NIL THEN EXIT; n.delta _ n.accDelta _ 0; IF n.rest THEN LOOP; c _ Note.FindChord[n]; pad[length].n _ n; pad[length].c _ c; pad[length].y _ Sheet.MapNote[n].y; pad[length].acc _ Score.GetAccidental[n]; pad[length].stemUp _ IF c=NIL THEN n.stemUp ELSE c.stemUp; pad[length].stem _ IF pad[length].stemUp THEN pad[length].y+32 ELSE pad[length].y-32; length _ length+1; ENDLOOP; SortPad[FALSE,@pad]; FOR i IN [0..length) DO IF i#0 AND pad[i].y=pad[i-1].y AND pad[i].acc=pad[i-1].acc THEN pad[i].acc _ inKey; IF pad[i].acc#inKey THEN top _ MIN[top, i]; IF pad[i].acc#inKey THEN bottom _ MAX[bottom, i]; ENDLOOP; <> FOR i IN [0..length) DO IF sync.event[i]=NIL THEN LOOP; c _ Note.FindChord[sync.event[i]]; IF c#NIL THEN Chord.Adjust[c] ELSE LOOP; <> FOR j IN [0..chordLength) DO IF c.note[j]=NIL THEN EXIT; FOR k IN [0..syncLength) DO IF sync.event[k]=c.note[j] THEN {sync.event[k] _ NIL; EXIT}; ENDLOOP; ENDLOOP; ENDLOOP; <> FOR i IN [1..length) DO FOR j DECREASING IN [0..i) DO IF pad[j].c=pad[i].c AND pad[i].c#NIL THEN LOOP; <> IF Overlap[i, j,@pad, length] THEN LOOP; delta _ Note.Delta[pad[j].n]; <> IF pad[j].stemUp AND NOT pad[i].stemUp THEN { IF pad[j].y-pad[i].y>= 8 THEN LOOP; MoveRightTo[pad[i], delta]; MoveLeft[pad[i], 8]; <> <> <> EXIT; }; <> IF pad[j].y-pad[i].y< 8 THEN MoveRightTo[pad[i], delta+(IF pad[j].n.value=whole THEN 10 ELSE 8)]; IF pad[i].n.value=whole OR pad[i].n.value=unknown THEN LOOP; IF pad[j].stem< pad[i].y THEN MoveRightTo[pad[i], delta+2]; IF pad[i].stem> pad[j].y THEN MoveRightTo[pad[i], delta+2]; ENDLOOP; ENDLOOP; IF top>bottom THEN RETURN; <> <> <<1) all of the accidentals appear to the left of all of the notes>> <<2) accidentals for notes offset in a chord have the same offset>> <<3) there must be at least three lines vertically between accidentals>> <<4) the highest and lowest accidentals should have the same horizontal position>> <<5) lower accidentals should go to the left of higher accidentals>> FOR i IN [0..length) DO pad[i].x _ -10; ENDLOOP; FOR i IN [1..length) DO IF pad[i-1].y-pad[i].y>=8 THEN LOOP; IF pad[i-1].acc=inKey OR pad[i].acc=inKey THEN LOOP; IF Note.Delta[pad[i-1].n]>Note.Delta[pad[i].n] THEN pad[i-1].push _ i ELSE pad[i].push _ i-1; ENDLOOP; <> <> PlaceAccidental[@pad, top, length]; FOR i IN [0..length) DO IF i=top THEN LOOP; IF pad[i].push#bottom THEN LOOP; PlaceAccidental[@pad, i, length]; ENDLOOP; PlaceAccidental[@pad, bottom, length]; FOR i IN [0..length) DO IF i=top OR i=bottom THEN LOOP; IF pad[i].push=bottom THEN LOOP; IF pad[i].push=syncLength THEN LOOP; PlaceAccidental[@pad, i, length]; ENDLOOP; FOR i IN [0..length) DO IF i=top OR i=bottom THEN LOOP; IF pad[i].push#syncLength THEN LOOP; PlaceAccidental[@pad, i, length]; ENDLOOP; <> FOR i IN [0..length) DO IF pad[i].acc=inKey THEN LOOP; pad[i].n.accDelta _ -pad[i].n.delta - 8*pad[i].x; ENDLOOP; }; Overlap: PROC[a, b: CARDINAL, pad: POINTER TO ScratchPad, length: CARDINAL] RETURNS[BOOL]= { found: BOOL; top, bottom: CARDINAL _ length; PartOf: PROC[i, a: CARDINAL] RETURNS[BOOL]= INLINE { RETURN[a=i OR (pad[i].c=pad[a].c AND pad[a].c#NIL)]}; IF pad[a].c=NIL AND pad[b].c=NIL THEN { -- just single notes IF pad[a].y#pad[b].y THEN RETURN[FALSE]; IF pad[a].n.pitch#pad[b].n.pitch THEN RETURN[FALSE]; IF pad[a].stemUp=pad[b].stemUp THEN RETURN[FALSE]; IF pad[a].n.value=whole OR pad[b].n.value=whole THEN RETURN[FALSE]; RETURN[TRUE]}; <> <> FOR i: CARDINAL IN [0..length) DO IF ~PartOf[i, a] THEN LOOP; IF top=length THEN top _ i; bottom _ i; ENDLOOP; IF pad[a].stemUp THEN top _ 0 ELSE bottom _ length-1; <> FOR i: CARDINAL IN [top..bottom] DO IF ~PartOf[i, b] THEN LOOP; found _ FALSE; FOR j: CARDINAL IN [top..bottom] DO IF ~PartOf[j, a] THEN LOOP; IF pad[i].n.pitch#pad[j].n.pitch OR pad[i].y#pad[j].y THEN LOOP; IF Note.Delta[pad[i].n]#Note.Delta[pad[j].n] THEN LOOP; found _ TRUE; EXIT; ENDLOOP; IF ~found THEN RETURN[FALSE]; ENDLOOP; top _ length; <> FOR i: CARDINAL IN [0..length) DO IF ~PartOf[i, b] THEN LOOP; IF top=length THEN top _ i; bottom _ i; ENDLOOP; IF pad[b].stemUp THEN top _ 0 ELSE bottom _ length-1; <> FOR i: CARDINAL IN [top..bottom] DO IF ~PartOf[i, a] THEN LOOP; found _ FALSE; FOR j: CARDINAL IN [top..bottom] DO IF ~PartOf[j, b] THEN LOOP; IF pad[i].n.pitch#pad[j].n.pitch OR pad[i].y#pad[j].y THEN LOOP; IF Note.Delta[pad[i].n]#Note.Delta[pad[j].n] THEN LOOP; found _ TRUE; EXIT; ENDLOOP; IF ~found THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; PlaceAccidental: PROC[pad: POINTER TO ScratchPad, i, length: CARDINAL] = { j: CARDINAL; blocked: BOOL; d, h: INTEGER; IF i=syncLength THEN RETURN; IF pad[i].acc = inKey THEN RETURN; FOR j IN [0..length) DO IF pad[j].push=i THEN pad[i].x _ MAX[pad[j].x, pad[i].x]; ENDLOOP; FOR d IN [MAX[pad[i].x, 0]..6) DO blocked _ FALSE; FOR j IN [0..length) DO SELECT (IF pad[j].y>pad[i].y THEN pad[j].acc ELSE pad[i].acc) FROM flat, doubleFlat, doubleSharp => h _ 8; ENDCASE => h _ 16; SELECT (IF pad[j].y>pad[i].y THEN pad[i].acc ELSE pad[j].acc) FROM doubleSharp => NULL; ENDCASE => h _ h+8; IF pad[j].y-pad[i].y>=h THEN LOOP; IF pad[i].y-pad[j].y>=24 THEN EXIT; IF pad[i].y-pad[j].y>=h THEN LOOP; IF d=0 AND Note.Delta[pad[j].n]<0 THEN {blocked _ TRUE; EXIT}; IF j=i THEN LOOP; IF j=pad[i].push THEN LOOP; IF pad[j].acc#inKey AND pad[j].x=d THEN {blocked _ TRUE; EXIT}; ENDLOOP; IF blocked THEN LOOP; MoveAccTo[pad, i, d]; RETURN; ENDLOOP; ERROR; }; MoveRightTo: PROC[p: Scratch, x: INTEGER] = INLINE { IF p.c=NIL THEN p.n.delta _ MAX[p.n.delta, x] ELSE p.c.delta _ MAX[p.c.delta, x - p.n.delta]; }; MoveLeft: PROC[p: Scratch, delta: INTEGER] = INLINE { IF p.c=NIL THEN p.n.delta _ MIN[p.n.delta,-delta] ELSE p.c.delta _ MIN[p.c.delta,-delta - p.n.delta]; }; MoveAccTo: PROC[p: POINTER TO ScratchPad, i: CARDINAL, delta: INTEGER] = { p[i].x _ MAX[p[i].x, delta]; IF p[i].push#syncLength AND p[p[i].push].x>-1 THEN MoveAccTo[p, p[i].push, p[i].x+1]; }; SortPad: PROC[ascending: BOOL, pad: POINTER TO ScratchPad] = INLINE { i, j: CARDINAL; temp: Scratch; FOR i IN [0..syncLength) DO IF pad[i].n=NIL THEN EXIT; FOR j IN (i..syncLength) DO IF pad[j].n=NIL THEN EXIT; IF NOT ascending AND pad[i].ypad[j].y THEN { temp_ pad[i]; pad[i]_ pad[j]; pad[j]_ temp; }; ENDLOOP; ENDLOOP; }; ScratchPad: TYPE= ARRAY [0..syncLength) OF Scratch; Scratch: TYPE = RECORD[x, y, stem: INTEGER _ 0, push: CARDINAL _ syncLength, acc: Accidental, stemUp: BOOL, c: ChordPTR, n: NotePTR _ NIL]; Hidden: PUBLIC PROC[f, s: CARDINAL, leftEdge: Time] RETURNS[BOOL] = { IF score[s].type#clef AND score[s].type#keySignature THEN RETURN[FALSE]; IF score[s].time-leftEdge>15 THEN RETURN[FALSE]; RETURN[TRUE]; }; END. Hidden: PUBLIC PROC[f, s: CARDINAL, leftEdge: Time] RETURNS[BOOL] = { IF score[s].type#clef AND score[s].type#keySignature THEN RETURN[FALSE]; IF score[s].time-leftEdge>100 THEN RETURN[FALSE]; FOR i: CARDINAL DECREASING IN (f..s) DO IF score[i].time