VoiceImpl.mesa
Copyright (C) 1981, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
last modified: December 14, 1981 12: 17 PM
Edited by Doug Wyatt, June 14, 1984 4:01:45 pm PDT
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: CEDAR PROGRAM
IMPORTS InlineDefs, MusicDefs, Note, Piece, Score, Sheet, Sync, Utility, Voice
EXPORTS MusicDefs, Voice
= BEGIN OPEN MusicDefs, Voice;
Error: SIGNAL = CODE;
voice: PUBLIC BOOLFALSE;
selectedVoice: PUBLIC CARDINAL 𡤀
maxVoice: PUBLIC CARDINAL ← 0;
Set: PUBLIC PROC[v: CARDINAL] = {
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];
};
Check: PUBLIC PROC = {
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;
};
lightgrey: Graphics.Texture = 004040B;
Sum: PROC[begin, end: CARDINAL, separate: BOOL, ss: StatePTR] RETURNS[sum: Time] = {
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;
};
*****************************************************************
enumerating over voices
*****************************************************************
SetState: PUBLIC PROC[vs: StatePTR, s: SyncPTR, m: INTEGER, separate: BOOL] 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;
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;
};
ClearState: PUBLIC PROCEDURE[vs: StatePTR]=INLINE {vs ← ALL[[FALSE, FALSE, FALSE, 0, 0]]};
*****************************************************************
realignning voices
*****************************************************************
Correct: PUBLIC PROC[time1, time2: Time] = {
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].time<time1-1 OR score[end].time>time2+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];
};
SeparateGraceNotes: PROC[start, stop: POINTER TO CARDINAL] = {
n: NotePTR;
sync: SyncPTR;
skip: CARDINAL ← 0;
grace, normal: BOOL;
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;
};
BreakUpSyncs: PROC[start, stop: POINTER TO CARDINAL] = {
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;
};
ReconstructVoices: PROC[start, stop: CARDINAL] = {
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;
};
AdjustVoices: PROC[a, b: CARDINAL, start, stop: CARDINAL] = {
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: BOOL;
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].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..syncLength) DO
IF (n ← score[as].event[j])=NIL THEN LOOP;
IF n.voice#a THEN LOOP;
Sync.AddNote[score[bs], n]; -- mustn't remove sync if it becomes empty!
ENDLOOP
ELSE FOR j: CARDINAL DECREASING IN [0..syncLength) DO
IF (n ← score[bs].event[j])=NIL THEN LOOP;
IF n.voice#b THEN LOOP;
Sync.AddNote[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 BreakUpSyncs
ENDLOOP;
};
voicefull: ARRAY [0..10) OF BOOL;
END.