SelectionImpl.mesa
Copyright (C) 1981, 1983, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
Last Edited by: Maxwell, November 22, 1983 12:47 pm
Last Edited by: Doug Wyatt, June 18, 1984 11:53:35 am PDT
DIRECTORY
Beam USING [AddItem, Free, New, RemoveItem, SetStems],
Chord USING [AddNote, New, RemoveNote, SetDefaultStem, Sort],
Event USING [AddNote, Sync],
Heuristic USING [MakeNTuplets],
MusicDefs,
Note USING [Free, GetBackTie, InVoice],
Piece USING [AddEvent, CleanUpEvents, Replace],
Real USING [FixI],
Selection USING [Clear, Draw, MakeBeam, MakeBeamOfBeams, RemoveNote],
Sheet USING [Height, HiLite, Map, MapNote, NextStaff],
SelectionImpl: CEDAR PROGRAM
IMPORTS Beam, Chord, Event, Heuristic, MusicDefs, Note, Piece, Real, Selection, Sheet, Utility
EXPORTS Selection =
BEGIN OPEN MusicDefs;
lineSelect: PUBLIC BOOLEAN;
select1, greySelect1: PUBLIC Time ← 1;
greySelect2, select2: PUBLIC Time ← 0;
selection: PUBLIC ARRAY [0..maxSelectionLength) OF NotePTR;
selection.length: PUBLIC CARDINAL ← 0;
min, max: PUBLIC Time ← 1;
command: PUBLIC BOOLEAN ← FALSE;
flash: PUBLIC BOOLEAN ← FALSE;
selection: PUBLIC SelectionPTR ← NEW[SelectionRec[100]];
Includes:
PUBLIC
PROC[n: NotePTR]
RETURNS[
BOOL] = {
IF selection.voice # noVoice AND n.voice # selection.voice THEN RETURN[FALSE];
IF selection.lineSelect THEN RETURN[n.sync.time IN [selection.select1..selection.select2)]
ELSE
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF selection.note[i] = n THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
***************************************************************************
selection manipulation
***************************************************************************
Clear:
PUBLIC
PROC = {
Selection.Draw[];
selection.greySelect1 ← selection.select1 ← 1;
selection.greySelect2 ← selection.select2 ← 0;
selection.length ← 0;
};
AddNote:
PUBLIC
PROC[score: ScorePTR, n: NotePTR] = {
x, y: INTEGER;
context: Utility.Context ~ score.sheet.context;
IF selection.lineSelect THEN Clear[];
IF n = NIL OR score = NIL THEN RETURN;
IF selection.score # score THEN {selection.score ← score; selection.length ← 0};
IF ~Note.InVoice[n, score.sheet.voice] THEN RETURN;
IF selection.length = selection.max THEN RETURN;
FOR i: NAT IN [0..selection.length) DO IF selection[i] = n THEN RETURN; ENDLOOP;
selection[selection.length] ← n;
selection.length ← selection.length+1;
selection.lineSelect ← FALSE;
[x, y] ← Sheet.MapNote[score.sheet, n];
Utility.SetCP[context, x, y];
Utility.SetBrush[context, black, invert];
Utility.DrawChar[context, 170C];
};
RemoveNote:
PUBLIC
PROC[n: NotePTR] = {
x, y: INTEGER;
context: Utility.Context ~ selection.score.sheet.context;
IF n = NIL THEN RETURN;
FOR i:
NAT
IN [0..selection.length)
DO
IF selection[i] # n THEN LOOP;
selection.length ← selection.length - 1;
selection[i] ← selection[selection.length];
selection[selection.length] ← NIL;
Utility.SetBrush[context, black, invert];
[x, y] ← Sheet.MapNote[selection.score.sheet, n];
Utility.SetCP[context, x, y];
Utility.DrawChar[context, 170C];
EXIT; ENDLOOP;
};
AddLine:
PUBLIC
PROC[score: ScorePTR, time1, time2: Time] = {
IF selection.lineSelect
AND selection.score #
NIL
THEN Sheet.HiLite[selection.score.sheet, black, selection.select1, selection.select2]
ELSE Selection.Clear[];
selection.score ← score;
selection.select1 ← time1;
selection.select2 ← time2;
selection.lineSelect ← TRUE;
IF score # NIL THEN Sheet.HiLite[score.sheet, black, time1, time2];
};
AddGreyLine:
PUBLIC
PROC[score: ScorePTR, time1, time2: Time] = {
IF selection.lineSelect
AND selection.score2 #
NIL
THEN Sheet.HiLite[selection.score2.sheet, grey, selection.greySelect1, selection.greySelect2]
ELSE Selection.Clear[];
selection.greySelect1 ← time1;
selection.greySelect2 ← time2;
selection.lineSelect ← TRUE;
IF score # NIL THEN Sheet.HiLite[score.sheet, grey, time1, time2];
};
Draw:
PUBLIC
PROC = {
x, y: INTEGER;
IF selection.lineSelect
THEN {
IF selection.score #
NIL
THEN
Sheet.HiLite[selection.score.sheet, black, selection.select1, selection.select2];
IF selection.score2 #
NIL
THEN
Sheet.HiLite[selection.score2.sheet, grey, selection.greySelect1, selection.greySelect2];
RETURN};
IF selection.length = 0 OR selection.score = NIL THEN RETURN;
Utility.SetBrush[selection.score.sheet.context, black, invert];
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF selection[i] = NIL THEN LOOP;
IF ~Note.InVoice[selection[i], selection.score.sheet.voice] THEN LOOP;
[x, y] ← Sheet.MapNote[selection.score.sheet, selection[i]];
Utility.SetCP[selection.score.sheet.context, x, y];
Utility.DrawChar[selection.score.sheet.context, 170C];
ENDLOOP;
};
Enumerate:
PUBLIC
PROC[proc:
PROC[ScorePTR, NotePTR]] = {
OPEN selection;
-- ! ! !
IF score = NIL THEN RETURN;
IF lineSelect
THEN
FOR i:
CARDINAL
DECREASING
IN [0..score.length)
DO
IF score[i].time < select1 OR score[i].time > select2 THEN LOOP;
IF score[i].type # sync THEN LOOP;
FOR j:
CARDINAL
DECREASING
IN [0..Event.Sync[score[i]].length)
DO
proc[score, Event.Sync[score[i]].note[j]];
ENDLOOP;
ENDLOOP
ELSE
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF ~Note.InVoice[selection[i], score.sheet.voice] THEN LOOP;
proc[score, selection[i]];
SetDirty[score, selection[i].sync.time, selection[i].sync.time];
ENDLOOP;
IF lineSelect THEN SetDirty[score, select1, select2];
};
****************************************************************************
procedures that take the current selection as an implicit parameter
****************************************************************************
SetNoteValue:
PUBLIC
PROC[v: NoteValue, dots:
INTEGER] = {
NoteValue:
PROC[score: ScorePTR, n: NotePTR] = {
n.value ← v;
IF dots > 1 THEN n.doubleDotted ← TRUE ELSE n.doubleDotted ← FALSE;
IF dots MOD 2 = 1 THEN n.dotted ← TRUE ELSE n.dotted ← FALSE;
IF v > quarter OR n.beam = NIL OR NOT n.beam.beamed THEN RETURN;
Beam.RemoveItem[score, n.beam, IF n.chord = NIL THEN n ELSE n.chord];
};
Enumerate[NoteValue];
};
SetRest:
PUBLIC
PROC[rest:
BOOLEAN] = {
Rest: PROC[score: ScorePTR, n: NotePTR] = { n.rest ← rest; };
Enumerate[Rest];
};
SetGrace:
PUBLIC
PROC[grace:
BOOLEAN] = {
Grace: PROC[score: ScorePTR, n: NotePTR] = { n.grace ← grace; };
Enumerate[Grace];
};
SetStaff:
PUBLIC
PROC[staff:
CARDINAL] = {
Staff:
PROC[score: ScorePTR, n: NotePTR] = {
Count: PROC[s: CARDINAL, t: Time] RETURNS[j: CARDINAL] = INLINE {
j ← 0; FOR i: CARDINAL IN [0..s) DO j ← Sheet.NextStaff[score.sheet, j, t]; ENDLOOP};
n.staff ← Count[staff, n.sync.time];
IF n.beam # NIL THEN Beam.SetStems[score.sheet, n.beam];
};
IF selection.score = NIL THEN RETURN;
IF staff = 0 THEN {selection.score.flash ← TRUE; RETURN} ELSE staff ← staff-1;
Enumerate[Staff];
};
SetStem:
PUBLIC
PROC[stemUp:
BOOLEAN] = {
Stem:
PROC[score: ScorePTR, n: NotePTR] = {
b: BeamPTR;
c: ChordPTR;
x, y, xb, yb, d: INTEGER;
n.stemUp ← stemUp;
c ← n.chord;
IF c # NIL THEN c.stemUp ← stemUp;
IF (b ← n.beam) = NIL OR NOT n.beam.beamed THEN RETURN;
[x, y] ← Sheet.Map[score.sheet, n.sync.time, n.pitch, n.staff];
[xb, yb] ← Sheet.Map[score.sheet, b.sync1.time, , b.staff];
yb ← yb+b.height;
d ← Real.FixI[yb+b.tilt*(x-xb)-y];
b.height ← b.height-(IF stemUp THEN MIN[0, d-28] ELSE MAX[0, d+28]);
};
Enumerate[Stem];
};
Transpose:
PUBLIC
PROC[halfsteps:
INTEGER] = {
Pitch:
PROC[score: ScorePTR, n: NotePTR] = {
n.pitch ← n.pitch+halfsteps;
IF (halfsteps
MOD 12) # 0
THEN {n.spelled ← inKey; n.show ←
FALSE};
};
Enumerate[Pitch];
};
Delete:
PUBLIC
PROC = {
ENABLE Piece.Overflow => ERROR;
n: NotePTR;
IF selection.score = NIL THEN RETURN;
IF selection.lineSelect
THEN Piece.Replace[selection.score, NIL, selection.select1, selection.select2]
ELSE
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[n, selection.score.sheet.voice] THEN LOOP;
SetDirty[selection.score, n.sync.time, n.sync.time];
Selection.RemoveNote[n];
Note.Free[selection.score, n];
ENDLOOP;
};
**************************************************************************
syncs
**************************************************************************
MakeSync:
PUBLIC
PROC = {
n: NotePTR;
c: ChordPTR;
end: Time ← -1;
sync: SyncPTR ← NIL;
begin: Time ← LAST[Time];
IF selection.score = NIL THEN RETURN;
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
begin ← MIN[begin, n.sync.time];
end ← MAX[end, n.sync.time];
ENDLOOP;
IF ABS[end-begin] > 20 THEN {selection.score.flash ← TRUE; RETURN};
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[n, selection.score.sheet.voice] THEN LOOP;
SetDirty[selection.score, n.sync.time, n.sync.time];
IF sync = NIL THEN { sync ← n.sync; LOOP; };
c ← n.chord;
FOR j:
CARDINAL
IN [0..(
IF c =
NIL
THEN 1
ELSE c.length))
DO
IF c = NIL AND j # 0 THEN EXIT;
IF c # NIL THEN n ← c.note[j];
IF n = NIL THEN EXIT;
Event.AddNote[selection.score, sync, n];
ENDLOOP;
ENDLOOP;
Piece.CleanUpEvents[selection.score];
};
ClearSync:
PUBLIC
PROC = {
Sync:
PROC[score: ScorePTR, n: NotePTR] = {
sync: SyncPTR ~ NEW[EventRec[sync][10] ← [variant: sync[note: ]]];
sync.time ← n.sync.time;
Event.AddNote[score, sync, n];
Piece.AddEvent[score, sync];
};
IF selection.score = NIL THEN RETURN;
Enumerate[Sync];
Piece.CleanUpEvents[selection.score];
};
******************************************************************
ties
******************************************************************
MakeTie:
PUBLIC
PROC = {
n: NotePTR;
ties: ARRAY [0..10) OF RECORD[n1, n2: NotePTR];
ties ← ALL[[NIL, NIL]];
build up matching pairs
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF selection[i] = NIL THEN LOOP;
FOR j:
CARDINAL
IN [0..10)
DO
IF ties[j].n1 = NIL THEN {ties[j].n1 ← selection[i]; EXIT};
IF ties[j].n1.pitch # selection[i].pitch THEN LOOP;
IF ties[j].n1.sync = selection[i].sync THEN {selection.score.flash ← TRUE; RETURN};
IF ties[j].n2 # NIL THEN {selection.score.flash ← TRUE; RETURN};
ties[j].n2 ← selection[i];
EXIT; ENDLOOP;
ENDLOOP;
IF ties[0].n1 = NIL THEN {selection.score.flash ← TRUE; RETURN};
if there are only two notes, we ignore pitch constraints
IF ties[0].n2 =
NIL
AND ties[1].n2 =
NIL
AND ties[2].n1 =
NIL
THEN {
ties[0].n2 ← ties[1].n1;
ties[1].n1 ← NIL};
check that there are an even number of pitches
FOR i:
CARDINAL
IN [0..10)
DO
IF ties[i].n1 = NIL THEN EXIT;
IF ties[i].n2 = NIL THEN {selection.score.flash ← TRUE; RETURN};
ENDLOOP;
tie the pairs together
FOR i:
CARDINAL
IN [0..10)
DO
IF ties[i].n1 = NIL THEN EXIT;
IF ties[i].n1.sync.time > ties[i].n2.sync.time
THEN
{n ← ties[i].n1; ties[i].n1 ← ties[i].n2; ties[i].n2 ← n};
IF ties[i].n1.tied
THEN {
(n ← Note.GetBackTie[selection.score, ties[i].n1]).tie ← NIL;
SetDirty[selection.score, n.sync.time, n.sync.time]};
IF (n ← ties[i].n2.tie) #
NIL
THEN {
n.tied ← FALSE;
SetDirty[selection.score, n.sync.time, n.sync.time]};
ties[i].n2.tie ← ties[i].n1;
ties[i].n1.tied ← TRUE;
ties[i].n2.tieHeight ← -10;
SetDirty[selection.score, ties[i].n1.sync.time, ties[i].n2.sync.time];
ENDLOOP;
};
ClearTie:
PUBLIC
PROC = {
UnTie:
PROC[score: ScorePTR, n: NotePTR] = {
IF n.tie # NIL THEN n.tie.tied ← FALSE;
n.tie ← NIL;
};
Enumerate[UnTie];
};
******************************************************************
chords
******************************************************************
MakeChord:
PUBLIC
PROC = {
n: NotePTR;
end: Time ← -1;
chord: ChordPTR;
beam: BeamPTR ← NIL;
sync: EventPTR ← NIL;
count: CARDINAL ← 0;
begin: Time ← LAST[Time];
is this a legal chording?
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[n, selection.score.sheet.voice] THEN LOOP;
begin ← MIN[begin, n.sync.time];
end ← MAX[end, n.sync.time];
count ← count+1;
ENDLOOP;
IF ABS[end-begin] > 20 OR count < 2 THEN {selection.score.flash ← TRUE; RETURN};
make the new chord
chord ← Chord.New[selection.score, selection.length+2];
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[n, selection.score.sheet.voice]
THEN
LOOP;
SetDirty[selection.score, n.sync.time, n.sync.time];
Chord.AddNote[selection.score, chord, n];
ENDLOOP;
Chord.SetDefaultStem[selection.score.sheet, chord];
Piece.CleanUpEvents[selection.score];
};
ClearChord:
PUBLIC
PROC = {
UnChord:
PROC[score: ScorePTR, n: NotePTR] = {
c: ChordPTR ← n.chord;
IF c # NIL THEN Chord.RemoveNote[score, c, n];
};
Enumerate[UnChord];
};
****************************************************************************
beams and n-tuplets
****************************************************************************
MakeBeam:
PUBLIC
PROC[beamed:
BOOLEAN] = {
b: BeamPTR;
c: ChordPTR;
n, tmp: NotePTR;
score: ScorePTR ← selection.score;
sheet: SheetPTR ← score.sheet;
height, stem: INTEGER;
stemUp, first: BOOLEAN ← TRUE;
b ← Beam.New[selection.score, selection.length + 2];
b.beamed ← beamed;
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP;
don't beam quarters or above
IF beamed AND n.value < eighth THEN LOOP;
IF n.beam = b THEN LOOP; -- beaming a note in a chord already beamed
c ← n.chord;
IF n.beam # NIL THEN Beam.RemoveItem[score, n.beam, IF c = NIL THEN n ELSE c];
IF c #
NIL
THEN {
tmp ← c.note[0];
Beam.AddItem[score, b, c];
IF first
THEN { stemUp ← c.stemUp; first ←
FALSE;
[] ← Chord.Sort[c, NOT stemUp];
height ← (IF stemUp THEN -1000 ELSE 1000);
b.staff ← c.note[0].staff; };
IF beamed THEN c.stemUp ← stemUp;
[] ← Chord.Sort[c, NOT stemUp];
tmp ← c.note[0];
IF c.stemUp AND height < Sheet.Height[sheet, tmp.sync.time, tmp.pitch, tmp.staff] OR
NOT c.stemUp
AND height > Sheet.Height[sheet, tmp.sync.time, tmp.pitch, tmp.staff]
THEN height ← Sheet.Height[sheet, tmp.sync.time, tmp.pitch, tmp.staff];
};
IF c =
NIL
THEN {
Beam.AddItem[score, b, n];
IF first
THEN { stemUp ← n.stemUp; first ←
FALSE;
height ← (IF stemUp THEN -1000 ELSE 1000);
b.staff ← n.staff; };
IF beamed THEN n.stemUp ← stemUp;
IF n.stemUp
AND height < Sheet.Height[sheet, n.sync.time, n.pitch, n.staff]
OR
NOT n.stemUp
AND height > Sheet.Height[sheet, n.sync.time, n.pitch, n.staff]
THEN height ← Sheet.Height[sheet, n.sync.time, n.pitch, n.staff];
};
ENDLOOP;
IF beamed THEN stem ← 34 ELSE stem ← 28;
b.height ← height-Sheet.Height[sheet, 0, , b.staff]+(IF stemUp THEN stem ELSE -stem);
IF b.chord[1] = endOfBeam THEN {Beam.Free[score, b]; RETURN};
SetDirty[score, b.sync1.time, b.sync2.time];
};
MakeBeamOfBeams:
PUBLIC
PROC[beamed:
BOOLEAN] = {
CONTROL b beams beams together
n: NotePTR;
oldBeam: BeamPTR;
score: ScorePTR ← selection.score;
b: BeamPTR ← Beam.New[score, 8];
b.beamed ← beamed;
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF n.beam = NIL THEN LOOP;
IF NOT n.beam.beamed THEN {score.flash ← TRUE; RETURN};
IF n.beam.beam # NIL THEN {score.flash ← TRUE; RETURN};
ENDLOOP;
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (n ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP;
oldBeam ← n.beam;
IF oldBeam = b THEN LOOP;
IF oldBeam #
NIL
THEN {
Beam.AddItem[score, b, oldBeam];
b.staff ← oldBeam.staff;
b.height ← oldBeam.height;
LOOP; };
IF beamed AND n.value < eighth THEN LOOP;
Beam.AddItem[score, b, IF n.chord=NIL THEN n ELSE n.chord];
ENDLOOP;
IF beamed
THEN
FOR i:
CARDINAL
IN [0..b.length)
DO
WITH b.chord[i]
SELECT
FROM
beam: BeamPTR => {
beam.height ← b.height;
beam.staff ← b.staff;
beam.tilt ← 0;
};
ENDCASE;
ENDLOOP;
b.tilt ← 0;
SetDirty[score, b.sync1.time, b.sync2.time];
};
ClearBeam:
PUBLIC
PROC = {
UnBeam:
PROC[score: ScorePTR, n: NotePTR] = {
IF n.beam#NIL THEN Beam.RemoveItem[score, n.beam, IF n.chord=NIL THEN n ELSE n.chord];
};
Enumerate[UnBeam];
};
MakeNTuplet:
PUBLIC
PROC[n, a:
INTEGER] = {
b: BeamPTR ← NIL;
newBeam: BOOLEAN ← FALSE;
IF selection.score = NIL THEN RETURN;
IF selection.lineSelect
THEN {
Heuristic.MakeNTuplets[selection.score, a, n, selection.select1, selection.select2];
RETURN};
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF ~Note.InVoice[selection[i], selection.score.sheet.voice] THEN LOOP;
IF b = NIL THEN b ← selection[i].beam;
IF b = NIL OR selection[i].beam # b THEN newBeam ← TRUE;
ENDLOOP;
IF newBeam
THEN {
Selection.MakeBeam[FALSE];
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF ~Note.InVoice[selection[i], selection.score.sheet.voice] THEN LOOP;
b ← selection[i].beam;
EXIT; ENDLOOP;
IF b # NIL THEN b.beamed ← FALSE};
IF b = NIL THEN RETURN;
b.against ← n;
b.ntuple ← a;
b.invisible ← FALSE;
SetDirty[selection.score, b.sync1.time, b.sync2.time];
};
HideNTuplets:
PUBLIC
PROC[invisible:
BOOLEAN] = {
Hide:
PROC[score: ScorePTR, n: NotePTR] = {
FOR b: BeamPTR ← n.beam, b.beam
WHILE b #
NIL
DO
IF b.ntuple = 0 THEN LOOP;
b.invisible ← invisible;
ENDLOOP;
};
Enumerate[Hide];
};
MakeNTupletOfBeams:
PUBLIC
PROC[n, a:
INTEGER] = {
t: NotePTR;
b: BeamPTR ← NIL;
newBeam: BOOLEAN ← FALSE;
TopBeam:
PROC[b: BeamPTR]
RETURNS[BeamPTR] =
{IF b # NIL AND b.beam # NIL THEN RETURN[b.beam] ELSE RETURN[b]};
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF (t ← selection[i]) = NIL THEN LOOP;
IF ~Note.InVoice[t, selection.score.sheet.voice] THEN LOOP;
IF b = NIL THEN b ← TopBeam[t.beam];
IF b = NIL OR TopBeam[t.beam] # b THEN newBeam ← TRUE;
ENDLOOP;
IF newBeam
THEN {
Selection.MakeBeamOfBeams[FALSE];
FOR i:
CARDINAL
IN [0..selection.length)
DO
IF selection[i] = NIL THEN LOOP;
IF selection[i].beam = NIL THEN LOOP;
IF ~Note.InVoice[selection[i], selection.score.sheet.voice] THEN LOOP;
b ← TopBeam[selection[i].beam];
EXIT; ENDLOOP;
IF b # NIL THEN b.beamed ← FALSE};
IF b = NIL THEN RETURN;
b.against ← n;
b.ntuple ← a;
b.invisible ← FALSE;
SetDirty[selection.score, b.sync1.time, b.sync2.time];
};
END.