VoiceImpl.mesa
Copyright (C) 1983, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
Last Edited by: Maxwell, November 22, 1983 8:37 am
Last Edited by: Doug Wyatt, June 13, 1984 5:26:16 pm PDT
DIRECTORY
Basics USING [LongMult],
Event USING [AddNote, Sync],
MusicDefs,
Note USING [Duration],
Piece USING [AddEvent, CleanUpEvents],
Score USING [GetTimeSignature],
Selection USING [Enumerate],
Sheet USING [HiLite],
Voice USING [max, State, StatePTR];
VoiceImpl: CEDAR PROGRAM
IMPORTS Basics, Event, MusicDefs, Note, Piece, Score, Selection, Sheet
EXPORTS Voice
= BEGIN OPEN MusicDefs, Voice;
voice: PUBLIC BOOLEAN ← FALSE;
selectedVoice: PUBLIC CARDINAL ← 0;
maxVoice: PUBLIC CARDINAL ← 0;
Set: PUBLIC PROC[score: ScorePTR, voice: CARDINAL] = {
SetVoice: PROC[score: ScorePTR, n: NotePTR] = {n.voice ← voice};
Selection.Enumerate[SetVoice];
score.maxVoice ← MAX[voice, score.maxVoice];
};
Check: PUBLIC PROC[score: ScorePTR] = {
vs: StatePTR ~ NEW[State];
sum, sumTS: Time ← 0;
begin, end: CARDINAL ← 0;
FOR i: CARDINAL IN [0..score.length) DO
WITH score[i] SELECT FROM
timeSig: TimeSignaturePTR => {
sumTS ← Basics.LongMult[timeSig.ts.top*256, 64/timeSig.ts.bottom]
};
ENDCASE;
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, light, score[begin].time, score[end].time];
ENDLOOP;
};
lightgrey: CARDINAL = 004040B;
Sum: PROC[score: ScorePTR, begin, end: CARDINAL, separate: BOOLEAN, ss: StatePTR]
RETURNS[sum: Time] = {
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;
};
*****************************************************************
enumerating over voices
*****************************************************************
ClearState: PUBLIC PROC[vs: StatePTR] ~ {
vs^ ← ALL[[FALSE, FALSE, FALSE, FALSE, 0, 0]];
};
SetState: PUBLIC PROC[vs: StatePTR, event: EventPTR, m: INTEGER, separate: BOOLEAN]
RETURNS[max: Time] = {
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;
};
*****************************************************************
realignning voices
*****************************************************************
Correct: PUBLIC PROC[score: ScorePTR, time1, time2: Time] = {
ENABLE Piece.Overflow => IF old = score THEN score ← new;
begin, end: CARDINAL ← 0;
sumTS, sum: Time ← 0;
ts: TimeSignature;
vs: StatePTR ~ NEW[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
end ← SeparateGraceNotes[score, begin, end]; -- may update end
sum ← Sum[score, begin, end, FALSE, vs];
ts ← Score.GetTimeSignature[score, score[end].time];
sumTS ← Basics.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;
end ← BreakUpEvents[score, begin, end]; -- may update end
ReconstructVoices[score, vs, begin, end];
ENDLOOP;
Piece.CleanUpEvents[score];
};
SeparateGraceNotes: PROC[score: ScorePTR, start, stop: CARDINAL]
RETURNS[newStop: CARDINAL] = {
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 ← NEW[EventRec[sync][sync.length] ← [variant: sync[note: ]]]; -- 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;
Event.AddNote[score, new, n];
ENDLOOP;
Piece.AddEvent[score, new];
stop ← stop+1;
skip ← skip+1;
EXIT; ENDLOOP;
ENDLOOP;
RETURN[stop];
};
BreakUpEvents: PROC[score: ScorePTR, start, stop: CARDINAL]
RETURNS[newStop: CARDINAL] = {
vs: StatePTR ~ NEW[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 ← NEW[EventRec[sync][sync.length] ← [variant: sync[note: ]]]; -- separate
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;
Event.AddNote[score, new, n];
ENDLOOP;
Piece.AddEvent[score, new];
stop ← stop +1;
skip ← skip+1;
ENDLOOP;
ENDLOOP;
ENDLOOP;
RETURN[stop];
};
ReconstructVoices: PROC[score: ScorePTR, vs: StatePTR, start, stop: CARDINAL] = {
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;
};
AdjustVoices: PROC[score: ScorePTR, a, b: CARDINAL, start, stop: CARDINAL] = {
step through the two voices asynchronously.
adjust them according to their relative order and relative sums.
A: StatePTR ~ NEW[State];
B: StatePTR ~ NEW[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; -- 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; -- 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 {
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;
};
IF A[a].sum > B[b].sum AND bs > as THEN {
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;
};
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;
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;
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.