--Author: John Maxwell --last modified: May 20, 1982 8:58 am DIRECTORY Beam USING [Draw,Drawn,SetSyncs], Chord USING [Adjust,Draw], Graphics USING [DrawBox,SetCP,SetStipple], 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: 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; --**************************************************************************** --data abstractions for a sync --CONSTRAINT: if s.event[i]=NIL then s.event[j]=NIL for all j>i --**************************************************************************** AddNote:PUBLIC PROCEDURE[s:SyncPTR,n:NotePTR] = BEGIN 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; END; RemoveNote:PUBLIC PROCEDURE[s:SyncPTR,n:NotePTR] = BEGIN --does not automatically remove empty syncs! (see Voice.Correct) i,last:CARDINAL; first:BOOLEAN_TRUE; IF n=NIL OR s=NIL THEN RETURN; FOR i DECREASING IN [0..syncLength) DO IF first AND s.event[i]#NIL THEN BEGIN first_FALSE; last _ i; END; IF s.event[i]#n THEN LOOP; s.event[i] _ s.event[last]; s.event[last] _ NIL; ENDLOOP; END; AddTimes:PUBLIC PROCEDURE[s:SyncPTR,time:Time,toc:Time] = BEGIN 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; END; --****************************************************************** --procedures for drawing syncs --****************************************************************** Draw:PUBLIC PROCEDURE[s:SyncPTR] = BEGIN staves:StavesPTR; IF s=NIL THEN RETURN; Graphics.SetStipple[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; END; SetStave:PUBLIC PROCEDURE[oldS:StavesPTR,new:SyncPTR] = BEGIN n,o:BOOLEAN; 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}; -- new.type=staves newS^ _ style[new.value]; newS.height _ height; -- clef switches carry over if the staff form is the same 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; END; Octava:PUBLIC PROCEDURE[s:SyncPTR] RETURNS[SyncPTR] = BEGIN 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]; END; DrawNotes:PROCEDURE[s:SyncPTR] = BEGIN 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 BEGIN [] _ Beam.Draw[b]; LOOP; END; IF c#NIL AND b=NIL THEN BEGIN Chord.Draw[c]; LOOP; END; IF b=NIL THEN Note.Draw[s.event[i]]; ENDLOOP; IF NOT show.sync THEN RETURN; --draw the sync line 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.SetStipple[context,light]; IF min#vMin OR max#vMax THEN DrawLine[x+4,min-6,x+4,max+6]; Graphics.SetStipple[context,black]; IF vMin#vMax AND vMin#1000 THEN DrawLine[x+4,vMin-6,x+4,vMax+6]; END; DrawMeasure:PROCEDURE[type:EventType[measure..m5],time:Time,dy:INTEGER] = BEGIN i:CARDINAL; staves:StavesPTR; x1,y1,top,delta:INTEGER; IF time=0 THEN RETURN; IF print THEN dy_0; Graphics.SetStipple[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 => BEGIN DrawBox[context,[x1,y1,x1+3,y1+top]]; DrawLine[x1+5,y1,x1+5,y1+top]; END; repeat2,endMeasure => BEGIN DrawBox[context,[x1+1,y1,x1-2,y1+top]]; DrawLine[x1-5,y1,x1-5,y1+top]; END; 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]; SetCP[context,x1+delta,y1+16+4]; DrawChar[context,'.]; SetCP[context,x1+delta,y1+16-4]; DrawChar[context,'.]; ENDLOOP; END; --**************************************************************************** --look graphical (making sure that noteheads and accidentals don't interfere) --**************************************************************************** Adjust:PUBLIC PROCEDURE[s:SyncPTR] = BEGIN 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; --build the pad 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; --handle note adjacencies within a chord 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; -- no need to process a chord more than once 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; --determine the deltas 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; --special case: allow some notes to overlap IF Overlap[i,j,@pad,length] THEN LOOP; delta _ Note.Delta[pad[j].n]; --special case: higher note with stem down and lower with stem up IF pad[j].stemUp AND NOT pad[i].stemUp THEN BEGIN IF pad[j].y-pad[i].y>= 8 THEN LOOP; MoveRightTo[pad[i],delta]; MoveLeft[pad[i],8]; --FOR k IN [0..i] DO IF pad[j].c=pad[k].c AND pad[k].c#NIL THEN LOOP; --MoveLeft[pad[k],IF pad[k].n.value=whole THEN 10 ELSE 8]; --ENDLOOP; EXIT; END; --general case: shift lower note to the right 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; --determine the deltas for the accidentals --RULES: (in order of precedence) --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; --do top, then whatever pushes bottom, then bottom, --then whatever pushes, then whatever. 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; --put the results in accDelta 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; END; Overlap:PROCEDURE[a,b:CARDINAL,pad:POINTER TO ScratchPad,length:CARDINAL] RETURNS[BOOLEAN]= BEGIN found:BOOLEAN; top,bottom:CARDINAL_length; PartOf:PROCEDURE[i,a:CARDINAL] RETURNS[BOOLEAN]= 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]}; -- we have at least one chord; can a and b overlap? -- find the tops and bottoms for a 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; -- do all of the notes in b which are within top and bottom have a corresponding note in a? 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; -- find the tops and bottoms for b 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; -- do all of the notes in a which are within top and bottom have a corresponding note in b? 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]; END; PlaceAccidental:PROCEDURE[pad:POINTER TO ScratchPad,i,length:CARDINAL] = BEGIN j:CARDINAL; blocked:BOOLEAN; 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; END; MoveRightTo:PROCEDURE[p:Scratch,x:INTEGER] = INLINE BEGIN 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]; END; MoveLeft:PROCEDURE[p:Scratch,delta:INTEGER] = INLINE BEGIN 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]; END; MoveAccTo:PROCEDURE[p:POINTER TO ScratchPad,i:CARDINAL,delta:INTEGER] = BEGIN 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]; END; SortPad:PROCEDURE[ascending:BOOLEAN,pad:POINTER TO ScratchPad] = INLINE BEGIN 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 BEGIN temp_ pad[i]; pad[i]_ pad[j]; pad[j]_ temp; END; ENDLOOP; ENDLOOP; END; ScratchPad:TYPE= ARRAY [0..syncLength) OF Scratch; Scratch:TYPE = RECORD[x,y,stem:INTEGER_0,push:CARDINAL_syncLength, acc:Accidental,stemUp:BOOLEAN, c:ChordPTR,n:NotePTR_NIL]; (0,3634)(1,4304)(2,4960)\653b8B144i4I149b7B310i10I93b1B138b10B49i63I286b8B407b4B847b8B848b6B678b1B2429i75I81b6B245i14I670i38I140i41I189i21I113i41I75i63I337i43I341i428I48i1I263i89I573i27I Hidden:PUBLIC PROCEDURE[f,s:CARDINAL,leftEdge:Time] RETURNS[BOOLEAN] = BEGIN 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; END.. \b6B Hidden:PUBLIC PROCEDURE[f,s:CARDINAL,leftEdge:Time] RETURNS[BOOLEAN] = BEGIN 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