--Author: John Maxwell --last modified: December 14, 1981 12:17 PM DIRECTORY Graphics USING [Texture], InlineDefs USING [LongMult], MusicDefs, Note USING [Duration], Piece USING [AddSync,CleanUpSyncs], Score USING [GetTimeSignature], Sheet USING [HiLite], Sync USING [AddNote], Utility USING [NewSync], Voice USING [ClearState,State,StatePTR]; VoiceImpl: PROGRAM IMPORTS InlineDefs, MusicDefs, Note, Piece, Score, Sheet, Sync, Utility, Voice EXPORTS MusicDefs, Voice = BEGIN OPEN MusicDefs, Voice; Error:SIGNAL = CODE; voice:PUBLIC BOOLEAN _ FALSE; selectedVoice:PUBLIC CARDINAL _0; maxVoice:PUBLIC CARDINAL _ 0; Set:PUBLIC PROCEDURE[v:CARDINAL] = BEGIN n:NotePTR; i,j:CARDINAL; IF lineSelect THEN FOR i IN [0..scoreLength) DO IF score[i]=NIL THEN LOOP; IF score[i].time < select1 OR score[i].time > select2 THEN LOOP; FOR j IN [0..syncLength) DO IF score[i].event[j]=NIL THEN EXIT; IF voice AND score[i].event[j].voice#selectedVoice THEN LOOP; score[i].event[j].voice _ v; ENDLOOP; ENDLOOP ELSE FOR i IN [0..selectionLength) DO IF (n_selection[i])=NIL THEN LOOP; IF voice AND n.voice#selectedVoice THEN LOOP; SetDirty[n.sync.time,n.sync.time]; n.voice _ v; ENDLOOP; IF lineSelect THEN SetDirty[select1,select2]; maxVoice _ MAX[v,maxVoice]; END; Check:PUBLIC PROCEDURE = BEGIN vs:State; i:CARDINAL; sum,sumTS:Time_0; begin,end:CARDINAL_0; FOR i IN [0..scoreLength) DO IF score[i].type = timeSignature THEN sumTS_InlineDefs.LongMult[score[i].ts.top*256,64/score[i].ts.bottom]; IF ~Measure[score[i].type] THEN LOOP; begin _ end; end _ i; IF begin=end THEN LOOP; sum _ Sum[begin,end,voice,@vs]; IF sum=0 THEN LOOP; IF ABS[sum-sumTS]>8 THEN Sheet.HiLite[lightgrey,score[begin].time,score[end].time]; ENDLOOP; END; lightgrey:Graphics.Texture = 004040B; Sum:PROCEDURE[begin,end:CARDINAL,separate:BOOLEAN,ss:StatePTR] RETURNS[sum:Time] = BEGIN sum_0; ClearState[ss]; FOR i:CARDINAL IN (begin..end) DO IF score[i].type#notes THEN LOOP; [] _SetState[ss,score[i],128,separate]; ENDLOOP; [] _SetState[ss,score[end],128,separate]; FOR v:CARDINAL IN [0..maxVoice] DO IF separate AND v#selectedVoice THEN LOOP; sum _ MAX[sum,ss[v].sum+ss[v].duration]; ENDLOOP; END; --***************************************************************** -- enumerating over voices --***************************************************************** SetState:PUBLIC PROCEDURE[vs:StatePTR,s:SyncPTR,m:INTEGER,separate:BOOLEAN] RETURNS[max:Time]= BEGIN --sum contains the total up to but not including the last duration --duration contains the duration of the last note found --a grace note occurs logically just a little before the note it graces --a string of grace notes is treated as one grace note n:NotePTR; i:CARDINAL_0; duration,offset:Time; FOR i IN [0..10) DO vs[i].found _ vs[i].graced _ FALSE; ENDLOOP; FOR i IN [0..syncLength) DO IF (n_s.event[i])=NIL THEN EXIT; IF n.value=unknown THEN LOOP; IF vs[n.voice].grace THEN vs[n.voice].graced _ TRUE; IF n.grace THEN { vs[n.voice].found _ TRUE; vs[n.voice].sum _ vs[n.voice].sum+ vs[n.voice].duration-10; vs[n.voice].duration _ 10; LOOP}; duration _ Note.Duration[n,m]; IF vs[n.voice].found=FALSE THEN { vs[n.voice].sum _ vs[n.voice].sum+ vs[n.voice].duration; vs[n.voice].duration _ duration; vs[n.voice].found _ TRUE; IF n.rest AND n.value=whole THEN { vs[n.voice].sum _ vs[n.voice].sum+ vs[n.voice].duration/2; vs[n.voice].duration _ vs[n.voice].duration/2}}; IF vs[n.voice].duration>duration THEN vs[n.voice].duration_duration; ENDLOOP; --"grace" is set separately to avoid having a second grace note appear to be "graced" by the first. FOR i IN [0..syncLength) DO IF (n_s.event[i])=NIL THEN EXIT; vs[n.voice].grace _ n.grace; ENDLOOP; --measures are treated special IF Measure[s.type] THEN FOR i IN [0..maxVoice] DO vs[i].found _ TRUE; vs[i].sum _ vs[i].sum+vs[i].duration; vs[i].duration_0; -- vs[i].graced_FALSE; -- IF vs[n.voice].grace THEN vs[n.voice].graced_TRUE; -- vs[n.voice].grace_FALSE; ENDLOOP; -- determine the max "time" for this sync max _ 0; FOR i IN [0..maxVoice] DO IF NOT vs[i].found AND vs[i].duration#0 THEN offset_10 ELSE offset_0; max _ MAX[max,vs[i].sum+ offset]; ENDLOOP; IF max=0 THEN RETURN; -- align all of the sums IF ~separate THEN FOR i IN [0..maxVoice] DO IF vs[i].found THEN vs[i].sum _ max; ENDLOOP; END; --ClearState:PUBLIC PROCEDURE[vs:StatePTR]=INLINE {vs_ALL[[FALSE,FALSE,FALSE,0,0]]}; --***************************************************************** --realignning voices --***************************************************************** Correct:PUBLIC PROCEDURE[time1,time2:Time] = BEGIN s,i:CARDINAL; begin,end:CARDINAL_0; sumTS,sum:Time_0; ts:TimeSignature; vs:State; FOR s DECREASING IN [0..scoreLength) DO IF Measure[score[s].type] AND begin=0 THEN {begin_s; LOOP}; IF NOT Measure[score[s].type] THEN LOOP; end _ begin; begin _ s; IF score[begin].timetime2+1 THEN LOOP; --we have a candidate measure SeparateGraceNotes[@begin,@end]; -- may update end sum _ Sum[begin,end,FALSE,@vs]; ts _ Score.GetTimeSignature[score[end].time]; sumTS_InlineDefs.LongMult[ts.top*256,64/ts.bottom]; IF sum-sumTS< 9 THEN LOOP; -- no complaints []_Sum[begin,end,TRUE,@vs]; voicefull _ ALL[FALSE]; FOR i IN [0..maxVoice] DO IF ABS[vs[i].sum-sumTS]<9 THEN voicefull[i] _ TRUE; ENDLOOP; BreakUpSyncs[@begin,@end]; -- may update end ReconstructVoices[begin,end]; ENDLOOP; Piece.CleanUpSyncs[score]; END; SeparateGraceNotes:PROCEDURE[start,stop:POINTER TO CARDINAL] = BEGIN n:NotePTR; sync:SyncPTR; skip:CARDINAL_0; grace,normal:BOOLEAN; FOR i:CARDINAL IN [start^..scoreLength) DO IF i=stop^ THEN EXIT; IF skip>0 THEN {skip_skip-1; LOOP}; -- skip the syncs that we added IF score[i].type#notes THEN LOOP; -- separate grace notes if there are non-grace notes grace _ normal _ FALSE; FOR j:CARDINAL IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.grace THEN grace_TRUE ELSE normal_TRUE; IF NOT (grace AND normal) THEN LOOP; sync _ Utility.NewSync[]; --separate sync.time _ score[i].time; FOR k:CARDINAL DECREASING IN [0..syncLength) DO IF (n_score[i].event[k])=NIL THEN LOOP; IF ~n.grace THEN LOOP; Sync.AddNote[sync,n]; ENDLOOP; Piece.AddSync[score,sync]; stop^ _ stop^+1; skip _ skip+1; EXIT; ENDLOOP; ENDLOOP; END; BreakUpSyncs:PROCEDURE[start,stop:POINTER TO CARDINAL] = BEGIN vs:State; n:NotePTR; sync:SyncPTR; skip:CARDINAL_0; ClearState[@vs]; FOR i:CARDINAL IN (start^..scoreLength) DO IF i=stop^ THEN EXIT; IF skip>0 THEN {skip_skip-1; LOOP}; -- skip the syncs that we added IF score[i].type#notes THEN LOOP; -- separate voices if they have different times []_SetState[@vs,score[i],128,TRUE]; FOR v1:CARDINAL IN [0..maxVoice] DO IF NOT (voicefull[v1] AND vs[v1].found) THEN LOOP; FOR v2:CARDINAL IN (v1..maxVoice] DO IF NOT (voicefull[v2] AND vs[v2].found) THEN LOOP; IF ABS[vs[v1].sum-vs[v2].sum]<9 THEN LOOP; -- same time (close enough) sync _ Utility.NewSync[]; sync.time _ score[i].time; FOR j:CARDINAL DECREASING IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN LOOP; IF n.voice#v1 THEN LOOP; Sync.AddNote[sync,n]; ENDLOOP; Piece.AddSync[score,sync]; stop^ _ stop^ +1; skip _ skip+1; ENDLOOP; ENDLOOP; ENDLOOP; END; ReconstructVoices:PROCEDURE[start,stop:CARDINAL] = BEGIN -- merge things that should be together v1,v2:CARDINAL; FOR v1 IN [0..maxVoice] DO IF NOT voicefull[v1] THEN LOOP; FOR v2 IN (v1..maxVoice] DO IF voicefull[v2] THEN AdjustVoices[v1,v2,start,stop]; ENDLOOP; ENDLOOP; END; AdjustVoices:PROCEDURE[a,b:CARDINAL,start,stop:CARDINAL] = BEGIN --step through the two voices assynchronously. --adjust them according to their relative order and relative sums. A,B:State; n:NotePTR; time:Time_0; temp:SyncPTR; as,bs:CARDINAL_start; getNextA,getNextB:BOOLEAN; ClearState[@A]; ClearState[@B]; WHILE as#stop AND bs#stop DO getNextA _ A[a].sum<=B[b].sum; getNextB _ B[b].sum<=A[a].sum; IF getNextA THEN FOR i:CARDINAL IN (as..stop+1] DO IF i=stop+1 THEN {Error; EXIT}; -- catches infinite loops []_SetState[@A,score[i],128,TRUE]; IF score[i].type#notes AND i#stop THEN LOOP; IF ~A[a].found THEN LOOP; as _ i; EXIT; ENDLOOP; IF getNextB THEN FOR i:CARDINAL IN (bs..stop+1] DO IF i=stop+1 THEN {Error; EXIT}; -- catches infinite loops []_SetState[@B,score[i],128,TRUE]; IF score[i].type#notes AND i#stop THEN LOOP; IF ~B[b].found THEN LOOP; bs _ i; EXIT; ENDLOOP; IF ABS[A[a].sum-B[b].sum]<5 THEN A[a].sum _ B[b].sum _ MAX[A[a].sum,B[b].sum]; --problem: syncs are out of order --solution: put the latter one just before the former IF A[a].sumB[b].sum AND bs>as THEN BEGIN temp _ score[bs]; FOR i:CARDINAL DECREASING IN [as..bs) DO score[i+1] _ score[i]; ENDLOOP; score[as] _ temp; bs _ as; as _ as+ 1; END; --problem: notes which should be synced, aren't. --solution: put the notes in the earlier sync IF A[a].sum=B[b].sum AND as#bs THEN IF bs