SyncImpl.mesa
Copyright (C) 1981, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
last modified: December 16, 1981 11: 09 AM
Edited by Doug Wyatt, June 14, 1984 1:16:31 pm PDT
DIRECTORY
Beam USING [Draw, Drawn, SetSyncs],
Chord USING [Adjust, Draw],
Graphics USING [DrawRectangle, MoveTo, SetTexture],
MusicDefs,
Note USING [FindChord, Delta, Draw],
Score USING [DrawKey, DrawTimeSignature, DrawMetrenome, GetAccidental],
Sheet USING [DrawClef, DrawOctava, FindSection, Map, MapNote],
Sync USING [GetScoreIndex, GetStaff, RemoveNote],
Utility USING [DrawChar, DrawLine];
SyncImpl: CEDAR PROGRAM
IMPORTS Beam, Chord, Graphics, MusicDefs, Note, Score, Sheet, Sync, Utility
EXPORTS MusicDefs, Sync
= BEGIN OPEN Graphics, MusicDefs, Utility;
Error: SIGNAL;
Overflow: PUBLIC SIGNAL[type: Sequence] = CODE;
GetScoreIndex:
PUBLIC
PROC[s: SyncPTR]
RETURNS[
CARDINAL] =
{i, j, index:
CARDINAL ← 0;
binary: CARDINAL ← 8192;
IF s=NIL THEN RETURN[scoreLength];
FOR i
IN [0..13)
DO
binary ← binary/2;
IF index+binary>=scoreLength THEN LOOP;
IF score[index+binary].time<=s.time THEN index ← index+binary;
IF score[index].time#s.time THEN LOOP;
IF score[index]=s THEN RETURN[index];
must be a nearby sync
FOR j
IN (index..scoreLength)
WHILE score[j].time=s.time
DO
IF score[j]=s THEN RETURN[j]; ENDLOOP;
FOR j
DECREASING
IN [0..index)
WHILE score[j].time=s.time
DO
IF score[j]=s THEN RETURN[j]; ENDLOOP;
EXIT; ENDLOOP;
FAILURE: score isn't sorted. Try again.
FOR i IN [0..scoreLength) DO IF score[i]=s THEN RETURN[i]; ENDLOOP;
RETURN[scoreLength]}; --not in score
GetStaff:
PUBLIC
PROC[s: SyncPTR, staff:
CARDINAL]
RETURNS[
LONG
POINTER
TO Staff] =
{staves: StavesPTR = LOOPHOLE[@s.event]; RETURN[@staves.staff[staff]]};
Grace:
PUBLIC
PROC[s: SyncPTR]
RETURNS[
BOOL] =
{
IF s.type#notes
THEN
RETURN[
FALSE];
FOR j:
CARDINAL
IN [0..syncLength)
DO
IF s.event[j]=NIL THEN EXIT;
IF ~s.event[j].grace THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE]};
InVoice:
PUBLIC
PROC[s: SyncPTR, voice:
CARDINAL]
RETURNS[
BOOL] =
{
FOR j:
CARDINAL
IN [0..syncLength)
DO
IF s.event[j]=NIL THEN EXIT;
IF s.event[j].voice=voice THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE]};
Length:
PUBLIC
PROC[s: SyncPTR]
RETURNS[
CARDINAL] =
{
FOR i:
CARDINAL
IN [0..syncLength]
DO
IF i=syncLength THEN RETURN[i];
IF s.event[i]=NIL THEN RETURN[i];
ENDLOOP;
ERROR};
****************************************************************************
data abstractions for a sync
CONSTRAINT: if s.event[i]=NIL then s.event[j]=NIL for all j>i
****************************************************************************
AddNote:
PUBLIC
PROC[s: SyncPTR, n: NotePTR] = {
i, j: CARDINAL;
c: ChordPTR = Note.FindChord[n];
IF n.sync=s THEN RETURN;
IF s.type#notes THEN ERROR;
FOR j
IN [0..chordLength)
DO
IF c=NIL AND j#0 THEN EXIT;
IF c#NIL THEN n← c.note[j];
IF n=NIL THEN EXIT;
IF s.event[syncLength-1]#NIL THEN ERROR; --sync full!
FOR i
IN [0..syncLength)
DO
IF s.event[i]=n THEN EXIT;
IF s.event[i]#NIL THEN LOOP;
Sync.RemoveNote[n.sync, n];
n.sync ← s;
s.event[i] ← n;
IF n.beam#NIL THEN Beam.SetSyncs[n.beam];
EXIT;
ENDLOOP;
ENDLOOP;
};
RemoveNote:
PUBLIC
PROC[s: SyncPTR, n: NotePTR] = {
does not automatically remove empty syncs! (see Voice.Correct)
i, last: CARDINAL;
first: BOOL ← TRUE;
IF n=NIL OR s=NIL THEN RETURN;
FOR i
DECREASING
IN [0..syncLength)
DO
IF first AND s.event[i]#NIL THEN { first ← FALSE; last ← i; };
IF s.event[i]#n THEN LOOP;
s.event[i] ← s.event[last];
s.event[last] ← NIL;
ENDLOOP;
};
AddTimes:
PUBLIC
PROC[s: SyncPTR, time: Time, toc: Time] = {
i: CARDINAL;
IF s=NIL THEN RETURN;
s.time ← s.time + time;
FOR i
IN [0..syncLength)
DO
IF s.event[i]=NIL THEN EXIT;
s.event[i].toc ← s.event[i].toc + toc;
ENDLOOP;
};
******************************************************************
procedures for drawing syncs
******************************************************************
Draw:
PUBLIC
PROC[s: SyncPTR] = {
staves: StavesPTR;
IF s=NIL THEN RETURN;
Graphics.SetTexture[context, black];
SELECT s.type
FROM
notes => DrawNotes[s];
clef => Sheet.DrawClef[Sync.GetStaff[s, s.value].pitch, s.value, s.time];
octava1 => {
pitch, height: INTEGER;
octava2: SyncPTR = Octava[s];
IF octava2=NIL THEN {Error; RETURN};
staves ← LOOPHOLE[@s.event];
pitch ← Sync.GetStaff[s, s.value].pitch;
height ← staves.height;
Sheet.DrawOctava[pitch, s.value, height, s.time, octava2.time]};
octava2 => IF Octava[Octava[s]]#s THEN DrawMeasure[doubleMeasure, s.time, 20];
staves => DrawMeasure[measure, s.time, 10];
keySignature => Score.DrawKey[s.value, s.time];
timeSignature => Score.DrawTimeSignature[s.ts, s.time];
metrenome => Score.DrawMetrenome[s.value, s.time];
IN [measure..m5] => DrawMeasure[s.type, s.time, 0];
ENDCASE;
};
SetStave:
PUBLIC
PROC[oldS: StavesPTR, new: SyncPTR] = {
n, o: BOOL;
newS: StavesPTR;
pitch, height: INTEGER;
IF new#NIL THEN newS ← LOOPHOLE[@new.event] ELSE RETURN;
height ← newS.height;
IF new.type#staves
THEN {
pitch ← newS.staff[new.value].pitch;
newS^ ← oldS^;
newS.height ← height;
FOR j:
CARDINAL
IN [0..newS.sl]
DO
IF newS.staff[j].y#newS.staff[new.value].y THEN LOOP;
newS.staff[j].pitch ← pitch;
ENDLOOP;
RETURN};
new.type=staves
newS^ ← style[new.value];
newS.height ← height;
clef switches carry over if the staff form is the same
IF oldS=NIL THEN RETURN;
IF oldS.sl#newS.sl THEN RETURN;
FOR i:
CARDINAL
IN [1..newS.sl]
DO
n ← newS.staff[i].y=newS.staff[i-1].y;
o ← oldS.staff[i].y=oldS.staff[i-1].y;
IF n#o THEN RETURN;
ENDLOOP;
FOR i:
CARDINAL
IN [0..newS.sl]
DO
newS.staff[i].pitch ← oldS.staff[i].pitch;
ENDLOOP;
};
Octava:
PUBLIC
PROC[s: SyncPTR]
RETURNS[SyncPTR] = {
OPEN Sync; -- USING [GetScoreIndex, GetStaff];
IF s=NIL THEN RETURN[NIL];
IF s.type=octava1
THEN {
FOR i:
CARDINAL
IN (GetScoreIndex[s]..scoreLength)
DO
IF score[i].type#octava2 THEN LOOP;
IF s.value=score[i].value THEN RETURN[score[i]];
IF GetStaff[s, s.value].y=GetStaff[score[i], score[i].value].y THEN RETURN[score[i]];
ENDLOOP};
IF s.type=octava2
THEN {
FOR i:
CARDINAL
DECREASING
IN [0..GetScoreIndex[s])
DO
IF score[i].type#octava1 THEN LOOP;
IF s.value=score[i].value THEN RETURN[score[i]];
IF GetStaff[s, s.value].y=GetStaff[score[i], score[i].value].y THEN RETURN[score[i]];
ENDLOOP};
RETURN[NIL];
};
DrawNotes:
PROC[s: SyncPTR] = {
b: BeamPTR;
c: ChordPTR;
x, y: INTEGER;
sync: SyncRec;
i, j, k: CARDINAL;
min, vMin: INTEGER ← 1000;
max, vMax: INTEGER ←-1000;
sync ← s^;
FOR i
IN [0..syncLength)
DO
IF s.event[i]=NIL THEN EXIT;
IF sync.event[i]=NIL THEN LOOP;
c ← Note.FindChord[s.event[i]];
b ← s.event[i].beam;
IF b#NIL AND b.beam#NIL THEN b ← b.beam;
IF c#
NIL
THEN
FOR j
IN [0..chordLength)
DO
IF c.note[j]=
NIL
THEN
EXIT;
FOR k
IN (i..syncLength)
DO
IF sync.event[k]=c.note[j] THEN sync.event[k] ← NIL;
ENDLOOP;
ENDLOOP;
IF b#NIL AND NOT Beam.Drawn[b] THEN { [] ← Beam.Draw[b]; LOOP; };
IF c#NIL AND b=NIL THEN { Chord.Draw[c]; LOOP; };
IF b=NIL THEN Note.Draw[s.event[i]];
ENDLOOP;
IF NOT show.sync THEN RETURN;
draw the sync line
FOR i
IN [0..syncLength)
DO
IF s.event[i]=NIL THEN EXIT;
[x, y] ← Sheet.MapNote[s.event[i]];
min ← MIN[min, y];
max ←
MAX[max, y];
IF voice AND s.event[i].voice#selectedVoice THEN LOOP;
vMin ← MIN[vMin, y];
vMax ← MAX[vMax, y];
ENDLOOP;
Graphics.SetTexture[context, light];
IF min#vMin OR max#vMax THEN DrawLine[x+4, min-6, x+4, max+6];
Graphics.SetTexture[context, black];
IF vMin#vMax AND vMin#1000 THEN DrawLine[x+4, vMin-6, x+4, vMax+6];
};
DrawMeasure:
PROC[type: EventType[measure..m5], time: Time, dy:
INTEGER] = {
i: CARDINAL;
staves: StavesPTR;
x1, y1, top, delta: INTEGER;
IF time=0 THEN RETURN;
IF print THEN dy ← 0;
Graphics.SetTexture[context, black];
staves ← sheet[Sheet.FindSection[time]].staves;
[x1, y1] ← Sheet.Map[time,, staves.sl];
y1 ← y1 - dy;
top ← -staves.staff[staves.sl].y+2*dy;
SELECT type
FROM
measure => DrawLine[x1, y1, x1, y1+top];
doubleMeasure => {DrawLine[x1, y1, x1, y1+top];
IF print OR scale=1 THEN delta ← 2 ELSE delta ← 3;
DrawLine[x1-delta, y1, x1-delta, y1+top]};
repeat1 => { DrawRectangle[context,[x1, y1],[x1+3, y1+top]];
DrawLine[x1+5, y1, x1+5, y1+top]; };
repeat2, endMeasure => { DrawRectangle[context,[x1+1, y1],[x1-2, y1+top]];
DrawLine[x1-5, y1, x1-5, y1+top]; };
ENDCASE;
IF type#repeat1 AND type#repeat2 THEN RETURN;
IF type=repeat1 THEN delta ← 6 ELSE delta ← -13;
FOR i
IN [0..staves.sl]
DO
[x1, y1] ← Sheet.Map[time,, i];
MoveTo[context,[x1+delta, y1+16+4]]; DrawChar[context,'.];
MoveTo[context,[x1+delta, y1+16-4]]; DrawChar[context,'.];
ENDLOOP;
};
****************************************************************************
look graphical (making sure that noteheads and accidentals don't interfere)
****************************************************************************
Adjust:
PUBLIC
PROC[s: SyncPTR] = {
n: NotePTR;
c: ChordPTR;
sync: SyncRec;
delta: INTEGER;
pad: ScratchPad;
top: CARDINAL ← syncLength-1;
i, j, k, bottom: CARDINAL ← 0;
length: CARDINAL ← 0;
IF s=NIL THEN RETURN ELSE sync ← s^;
IF s.type#notes THEN RETURN;
build the pad
FOR i
IN [0..syncLength)
DO
IF (n ← s.event[i])=NIL THEN EXIT;
n.delta ← n.accDelta ← 0;
IF n.rest THEN LOOP;
c ← Note.FindChord[n];
pad[length].n ← n;
pad[length].c ← c;
pad[length].y ← Sheet.MapNote[n].y;
pad[length].acc ← Score.GetAccidental[n];
pad[length].stemUp ← IF c=NIL THEN n.stemUp ELSE c.stemUp;
pad[length].stem ← IF pad[length].stemUp THEN pad[length].y+32 ELSE pad[length].y-32;
length ← length+1;
ENDLOOP;
SortPad[FALSE,@pad];
FOR i
IN [0..length)
DO
IF i#0 AND pad[i].y=pad[i-1].y AND pad[i].acc=pad[i-1].acc THEN pad[i].acc ← inKey;
IF pad[i].acc#inKey THEN top ← MIN[top, i];
IF pad[i].acc#inKey THEN bottom ← MAX[bottom, i];
ENDLOOP;
handle note adjacencies within a chord
FOR i
IN [0..length)
DO
IF sync.event[i]=NIL THEN LOOP;
c ← Note.FindChord[sync.event[i]];
IF c#NIL THEN Chord.Adjust[c] ELSE LOOP;
no need to process a chord more than once
FOR j
IN [0..chordLength)
DO
IF c.note[j]=NIL THEN EXIT;
FOR k
IN [0..syncLength)
DO
IF sync.event[k]=c.note[j] THEN {sync.event[k] ← NIL; EXIT};
ENDLOOP;
ENDLOOP;
ENDLOOP;
determine the deltas
FOR i
IN [1..length)
DO
FOR j
DECREASING
IN [0..i)
DO
IF pad[j].c=pad[i].c AND pad[i].c#NIL THEN LOOP;
special case: allow some notes to overlap
IF Overlap[i, j,@pad, length] THEN LOOP;
delta ← Note.Delta[pad[j].n];
special case: higher note with stem down and lower with stem up
IF pad[j].stemUp
AND
NOT pad[i].stemUp
THEN
{ IF pad[j].y-pad[i].y>= 8 THEN LOOP;
MoveRightTo[pad[i], delta];
MoveLeft[pad[i], 8];
FOR k IN [0..i] DO IF pad[j].c=pad[k].c AND pad[k].c#NIL THEN LOOP;
MoveLeft[pad[k], IF pad[k].n.value=whole THEN 10 ELSE 8];
ENDLOOP;
EXIT; };
general case: shift lower note to the right
IF pad[j].y-pad[i].y< 8
THEN
MoveRightTo[pad[i], delta+(IF pad[j].n.value=whole THEN 10 ELSE 8)];
IF pad[i].n.value=whole OR pad[i].n.value=unknown THEN LOOP;
IF pad[j].stem< pad[i].y THEN MoveRightTo[pad[i], delta+2];
IF pad[i].stem> pad[j].y THEN MoveRightTo[pad[i], delta+2];
ENDLOOP;
ENDLOOP;
IF top>bottom THEN RETURN;
determine the deltas for the accidentals
RULES: (in order of precedence)
1) all of the accidentals appear to the left of all of the notes
2) accidentals for notes offset in a chord have the same offset
3) there must be at least three lines vertically between accidentals
4) the highest and lowest accidentals should have the same horizontal position
5) lower accidentals should go to the left of higher accidentals
FOR i IN [0..length) DO pad[i].x ← -10; ENDLOOP;
FOR i
IN [1..length)
DO
IF pad[i-1].y-pad[i].y>=8 THEN LOOP;
IF pad[i-1].acc=inKey OR pad[i].acc=inKey THEN LOOP;
IF Note.Delta[pad[i-1].n]>Note.Delta[pad[i].n]
THEN pad[i-1].push ← i
ELSE pad[i].push ← i-1;
ENDLOOP;
do top, then whatever pushes bottom, then bottom,
then whatever pushes, then whatever.
PlaceAccidental[@pad, top, length];
FOR i
IN [0..length)
DO
IF i=top THEN LOOP;
IF pad[i].push#bottom THEN LOOP;
PlaceAccidental[@pad, i, length];
ENDLOOP;
PlaceAccidental[@pad, bottom, length];
FOR i
IN [0..length)
DO
IF i=top OR i=bottom THEN LOOP;
IF pad[i].push=bottom THEN LOOP;
IF pad[i].push=syncLength THEN LOOP;
PlaceAccidental[@pad, i, length];
ENDLOOP;
FOR i
IN [0..length)
DO
IF i=top OR i=bottom THEN LOOP;
IF pad[i].push#syncLength THEN LOOP;
PlaceAccidental[@pad, i, length];
ENDLOOP;
put the results in accDelta
FOR i
IN [0..length)
DO
IF pad[i].acc=inKey THEN LOOP;
pad[i].n.accDelta ← -pad[i].n.delta - 8*pad[i].x;
ENDLOOP;
};
Overlap:
PROC[a, b:
CARDINAL, pad:
POINTER
TO ScratchPad, length:
CARDINAL]
RETURNS[
BOOL]= {
found: BOOL;
top, bottom: CARDINAL ← length;
PartOf:
PROC[i, a:
CARDINAL]
RETURNS[
BOOL]=
INLINE {
RETURN[a=i OR (pad[i].c=pad[a].c AND pad[a].c#NIL)]};
IF pad[a].c=
NIL
AND pad[b].c=
NIL
THEN {
-- just single notes
IF pad[a].y#pad[b].y THEN RETURN[FALSE];
IF pad[a].n.pitch#pad[b].n.pitch THEN RETURN[FALSE];
IF pad[a].stemUp=pad[b].stemUp THEN RETURN[FALSE];
IF pad[a].n.value=whole OR pad[b].n.value=whole THEN RETURN[FALSE];
RETURN[TRUE]};
we have at least one chord; can a and b overlap?
find the tops and bottoms for a
FOR i:
CARDINAL
IN [0..length)
DO
IF ~PartOf[i, a] THEN LOOP;
IF top=length THEN top ← i;
bottom ← i;
ENDLOOP;
IF pad[a].stemUp THEN top ← 0 ELSE bottom ← length-1;
do all of the notes in b which are within top and bottom have a corresponding note in a?
FOR i:
CARDINAL
IN [top..bottom]
DO
IF ~PartOf[i, b] THEN LOOP;
found ← FALSE;
FOR j:
CARDINAL
IN [top..bottom]
DO
IF ~PartOf[j, a] THEN LOOP;
IF pad[i].n.pitch#pad[j].n.pitch OR pad[i].y#pad[j].y THEN LOOP;
IF Note.Delta[pad[i].n]#Note.Delta[pad[j].n] THEN LOOP;
found ← TRUE;
EXIT; ENDLOOP;
IF ~found THEN RETURN[FALSE];
ENDLOOP;
top ← length;
find the tops and bottoms for b
FOR i:
CARDINAL
IN [0..length)
DO
IF ~PartOf[i, b] THEN LOOP;
IF top=length THEN top ← i;
bottom ← i;
ENDLOOP;
IF pad[b].stemUp THEN top ← 0 ELSE bottom ← length-1;
do all of the notes in a which are within top and bottom have a corresponding note in b?
FOR i:
CARDINAL
IN [top..bottom]
DO
IF ~PartOf[i, a] THEN LOOP;
found ← FALSE;
FOR j:
CARDINAL
IN [top..bottom]
DO
IF ~PartOf[j, b] THEN LOOP;
IF pad[i].n.pitch#pad[j].n.pitch OR pad[i].y#pad[j].y THEN LOOP;
IF Note.Delta[pad[i].n]#Note.Delta[pad[j].n] THEN LOOP;
found ← TRUE;
EXIT; ENDLOOP;
IF ~found THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
PlaceAccidental:
PROC[pad:
POINTER
TO ScratchPad, i, length:
CARDINAL] = {
j: CARDINAL;
blocked: BOOL;
d, h: INTEGER;
IF i=syncLength THEN RETURN;
IF pad[i].acc = inKey THEN RETURN;
FOR j IN [0..length) DO IF pad[j].push=i THEN pad[i].x ← MAX[pad[j].x, pad[i].x]; ENDLOOP;
FOR d
IN [
MAX[pad[i].x, 0]..6)
DO blocked ←
FALSE;
FOR j
IN [0..length)
DO
SELECT (
IF pad[j].y>pad[i].y
THEN pad[j].acc
ELSE pad[i].acc)
FROM
flat, doubleFlat, doubleSharp => h ← 8;
ENDCASE => h ← 16;
SELECT (
IF pad[j].y>pad[i].y
THEN pad[i].acc
ELSE pad[j].acc)
FROM
doubleSharp => NULL;
ENDCASE => h ← h+8;
IF pad[j].y-pad[i].y>=h THEN LOOP;
IF pad[i].y-pad[j].y>=24 THEN EXIT;
IF pad[i].y-pad[j].y>=h THEN LOOP;
IF d=0 AND Note.Delta[pad[j].n]<0 THEN {blocked ← TRUE; EXIT};
IF j=i THEN LOOP;
IF j=pad[i].push THEN LOOP;
IF pad[j].acc#inKey AND pad[j].x=d THEN {blocked ← TRUE; EXIT};
ENDLOOP;
IF blocked THEN LOOP;
MoveAccTo[pad, i, d];
RETURN;
ENDLOOP;
ERROR;
};
MoveRightTo:
PROC[p: Scratch, x:
INTEGER] =
INLINE {
IF p.c=
NIL
THEN p.n.delta ←
MAX[p.n.delta, x]
ELSE p.c.delta ← MAX[p.c.delta, x - p.n.delta];
};
MoveLeft:
PROC[p: Scratch, delta:
INTEGER] =
INLINE {
IF p.c=
NIL
THEN p.n.delta ←
MIN[p.n.delta,-delta]
ELSE p.c.delta ← MIN[p.c.delta,-delta - p.n.delta];
};
MoveAccTo:
PROC[p:
POINTER
TO ScratchPad, i:
CARDINAL, delta:
INTEGER] = {
p[i].x ← MAX[p[i].x, delta];
IF p[i].push#syncLength AND p[p[i].push].x>-1 THEN MoveAccTo[p, p[i].push, p[i].x+1];
};
SortPad:
PROC[ascending:
BOOL, pad:
POINTER
TO ScratchPad] =
INLINE {
i, j: CARDINAL;
temp: Scratch;
FOR i
IN [0..syncLength)
DO
IF pad[i].n=NIL THEN EXIT;
FOR j
IN (i..syncLength)
DO
IF pad[j].n=NIL THEN EXIT;
IF
NOT ascending
AND pad[i].y<pad[j].y
OR ascending AND pad[i].y>pad[j].y
THEN { temp← pad[i]; pad[i]← pad[j]; pad[j]← temp; };
ENDLOOP;
ENDLOOP;
};
ScratchPad: TYPE= ARRAY [0..syncLength) OF Scratch;
Scratch:
TYPE =
RECORD[x, y, stem:
INTEGER ← 0, push:
CARDINAL ← syncLength,
acc: Accidental, stemUp: BOOL,
c: ChordPTR, n: NotePTR ← NIL];
Hidden:
PUBLIC
PROC[f, s:
CARDINAL, leftEdge: Time]
RETURNS[
BOOL] = {
IF score[s].type#clef AND score[s].type#keySignature THEN RETURN[FALSE];
IF score[s].time-leftEdge>15 THEN RETURN[FALSE];
RETURN[TRUE];
};
END.
Hidden:
PUBLIC
PROC[f, s:
CARDINAL, leftEdge: Time]
RETURNS[
BOOL] = {
IF score[s].type#clef AND score[s].type#keySignature THEN RETURN[FALSE];
IF score[s].time-leftEdge>100 THEN RETURN[FALSE];
FOR i:
CARDINAL
DECREASING
IN (f..s)
DO
IF score[i].time<leftEdge THEN EXIT;
IF score[i].type=keySignature AND score[s].type=keySignature THEN RETURN[FALSE];
IF score[i].type=notes THEN RETURN[FALSE];
IF score[i].type IN Measure THEN RETURN[FALSE];
IF score[i].type=staves THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};