-- Author: John Maxwell -- Last Edited by: Maxwell, November 22, 1983 12:47 pm DIRECTORY Beam USING [AddItem, Free, New, RemoveItem, SetStems], Chord USING [AddNote, New, RemoveNote, SetDefaultStem, Sort], Event USING [AddNote, Sync], Graphics USING [DrawChar, SetCP], Heuristic USING [MakeNTuplets], MusicDefs, Note USING [FindChord, Free, GetBackTie, InVoice], Piece USING [AddEvent, CleanUpEvents, Overflow, Replace], Real USING [FixI], Selection USING [Clear, Draw, MakeBeam, MakeBeamOfBeams, RemoveNote], Sheet USING [Height, HiLite, Map, MapNote, NextStaff]; SelectionImpl: PROGRAM IMPORTS Beam, Chord, Graphics, Heuristic, MusicDefs, Note, Piece, Real, Selection, Sheet, Event EXPORTS Selection = BEGIN OPEN Graphics, 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 ← zone.NEW[SelectionRec[100]]; -- *************************************************************************** -- selection manipulation -- *************************************************************************** Clear: PUBLIC PROCEDURE = BEGIN Selection.Draw[]; selection.greySelect1 ← selection.select1 ← 1; selection.greySelect2 ← selection.select2 ← 0; selection.length ← 0; END; AddNote: PUBLIC PROCEDURE[score: ScorePTR, n: NotePTR] = BEGIN x, y: INTEGER; 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: CARDINAL 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]; Graphics.SetCP[score.sheet.context, x, y]; SetBrush[score, black, invert]; Graphics.DrawChar[score.sheet.context, 170C]; END; RemoveNote: PUBLIC PROCEDURE[n: NotePTR] = BEGIN x, y: INTEGER; IF n = NIL THEN RETURN; FOR i: CARDINAL 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; SetBrush[selection.score, black, invert]; [x, y] ← Sheet.MapNote[selection.score.sheet, n]; Graphics.SetCP[selection.score.sheet.context, x, y]; Graphics.DrawChar[selection.score.sheet.context, 170C]; EXIT; ENDLOOP; END; AddLine: PUBLIC PROCEDURE[score: ScorePTR, time1, time2: Time] = BEGIN 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]; END; AddGreyLine: PUBLIC PROCEDURE[score: ScorePTR, time1, time2: Time] = BEGIN 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]; END; Draw: PUBLIC PROCEDURE = BEGIN 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; SetBrush[selection.score, 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]]; Graphics.SetCP[selection.score.sheet.context, x, y]; Graphics.DrawChar[selection.score.sheet.context, 170C]; ENDLOOP; END; Enumerate: PUBLIC PROCEDURE[proc: PROC[ScorePTR, NotePTR]] = BEGIN 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]; END; -- **************************************************************************** -- procedures that take the current selection as an implicit parameter -- **************************************************************************** SetNoteValue: PUBLIC PROCEDURE[v: NoteValue, dots: INTEGER] = BEGIN NoteValue: PROC[score: ScorePTR, n: NotePTR] = BEGIN 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 [note[n]] ELSE [chord[n.chord]]]; END; Enumerate[NoteValue]; END; SetRest: PUBLIC PROCEDURE[rest: BOOLEAN] = BEGIN Rest: PROC[score: ScorePTR, n: NotePTR] = BEGIN n.rest ← rest; END; Enumerate[Rest]; END; SetGrace: PUBLIC PROCEDURE[grace: BOOLEAN] = BEGIN Grace: PROC[score: ScorePTR, n: NotePTR] = BEGIN n.grace ← grace; END; Enumerate[Grace]; END; SetStaff: PUBLIC PROCEDURE[staff: CARDINAL] = BEGIN Staff: PROC[score: ScorePTR, n: NotePTR] = BEGIN Count: PROCEDURE[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]; END; IF selection.score = NIL THEN RETURN; IF staff = 0 THEN {selection.score.flash ← TRUE; RETURN} ELSE staff ← staff-1; Enumerate[Staff]; END; SetStem: PUBLIC PROCEDURE[stemUp: BOOLEAN] = BEGIN Stem: PROC[score: ScorePTR, n: NotePTR] = BEGIN b: BeamPTR; c: ChordPTR; x, y, xb, yb, d: INTEGER; n.stemUp ← stemUp; c ← Note.FindChord[n]; 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]); END; Enumerate[Stem]; END; Transpose: PUBLIC PROCEDURE[halfsteps: INTEGER] = BEGIN Pitch: PROC[score: ScorePTR, n: NotePTR] = BEGIN n.pitch ← n.pitch+halfsteps; IF (halfsteps MOD 12) # 0 THEN {n.spelled ← inKey; n.show ← FALSE}; END; Enumerate[Pitch]; END; Delete: PUBLIC PROC = BEGIN 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; END; -- ************************************************************************** -- syncs -- ************************************************************************** MakeSync: PUBLIC PROCEDURE = BEGIN 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 BEGIN sync ← n.sync; LOOP; END; 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; sync ← Event.AddNote[selection.score, sync, n]; ENDLOOP; ENDLOOP; Piece.CleanUpEvents[selection.score]; END; ClearSync: PUBLIC PROCEDURE = BEGIN Sync: PROC[score: ScorePTR, n: NotePTR] = BEGIN sync: SyncPTR; sync ← zone.NEW[EventRec.sync[10]]; sync.time ← n.sync.time; sync ← Event.AddNote[score, sync, n]; [] ← Piece.AddEvent[score, sync]; END; IF selection.score = NIL THEN RETURN; Enumerate[Sync]; Piece.CleanUpEvents[selection.score]; END; -- ****************************************************************** -- ties -- ****************************************************************** MakeTie: PUBLIC PROCEDURE = BEGIN 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; END; ClearTie: PUBLIC PROCEDURE = BEGIN UnTie: PROC[score: ScorePTR, n: NotePTR] = BEGIN IF n.tie # NIL THEN n.tie.tied ← FALSE; n.tie ← NIL; END; Enumerate[UnTie]; END; -- ****************************************************************** -- chords -- ****************************************************************** MakeChord: PUBLIC PROC = BEGIN 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 ← Chord.AddNote[selection.score, chord, n]; ENDLOOP; Chord.SetDefaultStem[selection.score.sheet, chord]; Piece.CleanUpEvents[selection.score]; END; ClearChord: PUBLIC PROCEDURE = BEGIN UnChord: PROC[score: ScorePTR, n: NotePTR] = BEGIN c: ChordPTR ← Note.FindChord[n]; IF c # NIL THEN Chord.RemoveNote[score, c, n]; END; Enumerate[UnChord]; END; -- **************************************************************************** -- beams and n-tuplets -- **************************************************************************** MakeBeam: PUBLIC PROCEDURE[beamed: BOOLEAN] = BEGIN 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 ← Note.FindChord[n]; IF n.beam # NIL THEN Beam.RemoveItem[score, n.beam, IF c = NIL THEN [note[n]] ELSE [chord[c]]]; IF c # NIL THEN BEGIN tmp ← c.note[0]; b ← Beam.AddItem[score, b, [chord[c]]]; IF first THEN BEGIN stemUp ← c.stemUp; first ← FALSE; [] ← Chord.Sort[c, NOT stemUp]; height ← (IF stemUp THEN -1000 ELSE 1000); b.staff ← c.note[0].staff; END; 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]; END; IF c = NIL THEN BEGIN b ← Beam.AddItem[score, b, [note[n]]]; IF first THEN BEGIN stemUp ← n.stemUp; first ← FALSE; height ← (IF stemUp THEN -1000 ELSE 1000); b.staff ← n.staff; END; 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]; END; 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]; END; MakeBeamOfBeams: PUBLIC PROCEDURE[beamed: BOOLEAN] = -- CONTROL b beams beams together BEGIN n: NotePTR; c: ChordPTR; 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 BEGIN b ← Beam.AddItem[score, b, [beam[oldBeam]]]; b.staff ← oldBeam.staff; b.height ← oldBeam.height; LOOP; END; IF beamed AND n.value < eighth THEN LOOP; IF (c ← Note.FindChord[n]) # NIL THEN b ← Beam.AddItem[score, b, [chord[c]]] ELSE b ← Beam.AddItem[score, b, [note[n]]]; ENDLOOP; IF beamed THEN FOR i: CARDINAL IN [0..b.length) DO WITH ev: b.chord[i] SELECT FROM beam => BEGIN ev.b.height ← b.height; ev.b.staff ← b.staff; ev.b.tilt ← 0; END; ENDCASE; ENDLOOP; b.tilt ← 0; SetDirty[score, b.sync1.time, b.sync2.time]; END; ClearBeam: PUBLIC PROCEDURE = BEGIN UnBeam: PROC[score: ScorePTR, n: NotePTR] = BEGIN IF n.beam # NIL THEN Beam.RemoveItem[score, n.beam, IF n.chord # NIL THEN [chord[n.chord]] ELSE [note[n]]]; END; Enumerate[UnBeam]; END; MakeNTuplet: PUBLIC PROCEDURE[n, a: INTEGER] = BEGIN 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]; END; HideNTuplets: PUBLIC PROCEDURE[invisible: BOOLEAN] = BEGIN Hide: PROC[score: ScorePTR, n: NotePTR] = BEGIN FOR b: BeamPTR ← n.beam, b.beam WHILE b # NIL DO IF b.ntuple = 0 THEN LOOP; b.invisible ← invisible; ENDLOOP; END; Enumerate[Hide]; END; MakeNTupletOfBeams: PUBLIC PROCEDURE[n, a: INTEGER] = BEGIN t: NotePTR; b: BeamPTR ← NIL; newBeam: BOOLEAN ← FALSE; TopBeam: PROCEDURE[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; END.