-- Author: John Maxwell -- Last Edited by: Maxwell, November 22, 1983 8:37 am DIRECTORY Event USING [AddNote, Sync], Inline USING [LongMult], MusicDefs, Note USING [Duration], Piece USING [AddEvent, CleanUpEvents, Overflow], Score USING [GetTimeSignature], Selection USING [Enumerate], Sheet USING [HiLite], Voice USING [ClearState, max, State, StatePTR]; VoiceImpl: PROGRAM IMPORTS Inline, MusicDefs, Note, Piece, Score, Selection, Sheet, Event, Voice EXPORTS Voice = BEGIN OPEN MusicDefs, Voice; Error: SIGNAL = CODE; -- voice: PUBLIC BOOLEAN ← FALSE; -- selectedVoice: PUBLIC CARDINAL ← 0; -- maxVoice: PUBLIC CARDINAL ← 0; Set: PUBLIC PROCEDURE[score: ScorePTR, voice: CARDINAL] = BEGIN SetVoice: PROC[score: ScorePTR, n: NotePTR] = {n.voice ← voice}; Selection.Enumerate[SetVoice]; score.maxVoice ← MAX[voice, score.maxVoice]; END; Check: PUBLIC PROCEDURE[score: ScorePTR] = BEGIN vs: State; sum, sumTS: Time ← 0; begin, end: CARDINAL ← 0; FOR i: CARDINAL IN [0..score.length) DO IF score[i].type = timeSignature THEN { timeSig: LONG POINTER TO EventRec.timeSignature ← LOOPHOLE[score[i]]; sumTS ← Inline.LongMult[timeSig.ts.top*256, 64/timeSig.ts.bottom]}; IF ~Measure[score[i]] THEN LOOP; begin ← end; end ← i; IF begin = end THEN LOOP; sum ← Sum[score, begin, end, score.sheet.voice # noVoice, @vs]; IF sum = 0 THEN LOOP; IF ABS[sum-sumTS] > 8 THEN Sheet.HiLite[score.sheet, lightgrey, score[begin].time, score[end].time]; ENDLOOP; END; lightgrey: CARDINAL = 004040B; Sum: PROCEDURE[score: ScorePTR, 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 # sync THEN LOOP; [] ← SetState[ss, score[i], 128, separate]; ENDLOOP; [] ← SetState[ss, score[end], 128, separate]; FOR v: CARDINAL IN [0..Voice.max) DO IF separate AND v # score.sheet.voice THEN LOOP; sum ← MAX[sum, ss[v].sum+ss[v].duration]; ENDLOOP; END; -- ***************************************************************** -- enumerating over voices -- ***************************************************************** SetState: PUBLIC PROCEDURE[vs: StatePTR, event: EventPTR, 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; sync: SyncPTR ← NIL; IF event.type = sync THEN sync ← Event.Sync[event]; FOR i: CARDINAL IN [0..10) DO vs[i].found ← vs[i].graced ← FALSE; ENDLOOP; IF sync # NIL THEN FOR i: CARDINAL IN [0..sync.length) DO IF (n ← sync.note[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. IF sync # NIL THEN FOR i: CARDINAL IN [0..Voice.max) DO IF (n ← sync.note[i]) = NIL THEN EXIT; vs[n.voice].grace ← n.grace; ENDLOOP; -- measures are treated special IF Measure[event] THEN FOR i: CARDINAL IN [0..Voice.max) 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: CARDINAL IN [0..Voice.max) 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: CARDINAL IN [0..Voice.max) 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[score: ScorePTR, time1, time2: Time] = BEGIN ENABLE Piece.Overflow => IF old = score THEN score ← new; begin, end: CARDINAL ← 0; sumTS, sum: Time ← 0; ts: TimeSignature; vs: State; FOR i: CARDINAL DECREASING IN [0..score.length) DO IF Measure[score[i]] AND begin = 0 THEN {begin ← i; LOOP}; IF NOT Measure[score[i]] THEN LOOP; end ← begin; begin ← i; IF score[begin].time < time1-1 OR score[end].time > time2+1 THEN LOOP; -- we have a candidate measure SeparateGraceNotes[score, @begin, @end]; -- may update end sum ← Sum[score, begin, end, FALSE, @vs]; ts ← Score.GetTimeSignature[score, score[end].time]; sumTS ← Inline.LongMult[ts.top*256, 64/ts.bottom]; IF sum-sumTS < 9 THEN LOOP; -- no complaints [] ← Sum[score, begin, end, TRUE, @vs]; FOR v: CARDINAL IN [0..Voice.max) DO vs[v].full ← ABS[vs[i].sum-sumTS] < 9; ENDLOOP; BreakUpEvents[score, @begin, @end]; -- may update end ReconstructVoices[score, @vs, begin, end]; ENDLOOP; Piece.CleanUpEvents[score]; END; SeparateGraceNotes: PROCEDURE[score: ScorePTR, start, stop: POINTER TO CARDINAL] = BEGIN n: NotePTR; sync, new: SyncPTR; skip: CARDINAL ← 0; grace, normal: BOOLEAN; FOR i: CARDINAL IN [start↑..score.length) DO IF i = stop↑ THEN EXIT; IF skip > 0 THEN {skip ← skip-1; LOOP}; -- skip the syncs that we added IF score[i].type # sync THEN LOOP; sync ← Event.Sync[score[i]]; -- separate grace notes if there are non-grace notes grace ← normal ← FALSE; FOR j: CARDINAL IN [0..sync.length) DO IF (n ← sync.note[j]) = NIL THEN EXIT; IF n.grace THEN grace ← TRUE ELSE normal ← TRUE; IF NOT (grace AND normal) THEN LOOP; new ← zone.NEW[EventRec.sync[4]]; -- separate new.time ← sync.time; FOR k: CARDINAL DECREASING IN [0..sync.length) DO IF (n ← sync.note[k]) = NIL THEN LOOP; IF ~n.grace THEN LOOP; new ← Event.AddNote[score, new, n]; ENDLOOP; score ← Piece.AddEvent[score, new]; stop↑ ← stop↑+1; skip ← skip+1; EXIT; ENDLOOP; ENDLOOP; END; BreakUpEvents: PROCEDURE[score: ScorePTR, start, stop: POINTER TO CARDINAL] = BEGIN vs: State; n: NotePTR; sync, new: SyncPTR; skip: CARDINAL ← 0; ClearState[@vs]; FOR i: CARDINAL IN (start↑..score.length) DO IF i = stop↑ THEN EXIT; IF skip > 0 THEN {skip ← skip-1; LOOP}; -- skip the syncs that we added IF score[i].type # sync THEN LOOP; sync ← Event.Sync[score[i]]; -- separate voices if they have different times [] ← SetState[@vs, score[i], 128, TRUE]; FOR v1: CARDINAL IN [0..Voice.max) DO IF NOT (vs[v1].full AND vs[v1].found) THEN LOOP; FOR v2: CARDINAL IN (v1..Voice.max) DO IF NOT (vs[v2].full AND vs[v2].found) THEN LOOP; IF ABS[vs[v1].sum-vs[v2].sum] < 9 THEN LOOP; -- same time (close enough) new ← zone.NEW[EventRec.sync]; new.time ← score[i].time; FOR j: CARDINAL DECREASING IN [0..sync.length) DO IF (n ← sync.note[j]) = NIL THEN LOOP; IF n.voice # v1 THEN LOOP; new ← Event.AddNote[score, new, n]; ENDLOOP; score ← Piece.AddEvent[score, new]; stop↑ ← stop↑ +1; skip ← skip+1; ENDLOOP; ENDLOOP; ENDLOOP; END; ReconstructVoices: PROCEDURE[score: ScorePTR, vs: StatePTR, start, stop: CARDINAL] = BEGIN -- merge things that should be together FOR v1: CARDINAL IN [0..Voice.max) DO IF NOT vs[v1].full THEN LOOP; FOR v2: CARDINAL IN (v1..Voice.max) DO IF vs[v2].full THEN AdjustVoices[score, v1, v2, start, stop]; ENDLOOP; ENDLOOP; END; AdjustVoices: PROCEDURE[score: ScorePTR, 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: EventPTR; 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 # sync 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 # sync 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].sum < B[b].sum AND bs < as THEN BEGIN temp ← score[as]; FOR i: CARDINAL DECREASING IN [bs..as) DO score[i+1] ← score[i]; ENDLOOP; score[bs] ← temp; as ← bs; bs ← bs+ 1; END; IF A[a].sum > B[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 < as THEN FOR j: CARDINAL DECREASING IN [0..Event.Sync[score[as]].length) DO IF (n ← Event.Sync[score[as]].note[j]) = NIL THEN LOOP; IF n.voice # a THEN LOOP; score[bs] ← Event.AddNote[score, Event.Sync[score[bs]], n]; -- mustn't remove score[as] if it becomes empty! ENDLOOP ELSE FOR j: CARDINAL DECREASING IN [0..Event.Sync[score[bs]].length) DO IF (n ← Event.Sync[score[bs]].note[j]) = NIL THEN LOOP; IF n.voice # b THEN LOOP; score[as] ← Event.AddNote[score, Event.Sync[score[as]], n]; ENDLOOP; IF A[a].sum = B[b].sum AND bs < as THEN as ← bs; IF A[a].sum = B[b].sum AND as < bs THEN bs ← as; -- problem: notes which shouldn't be synced, are. -- solution: already taken care of by BreakUpEvents ENDLOOP; END; END.