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