ChordImpl.mesa
Copyright (C) 1983, 1984 Xerox Corporation. All rights reserved.
Author: John Maxwell
Last Edited by: Maxwell, November 22, 1983 10:14 am
Last Edited by: Doug Wyatt, June 16, 1984 3:44:13 pm PDT
DIRECTORY
Beam USING [AddItem, RemoveItem, SetSyncs],
Chord USING [default, Free, GetHeapIndex, InVoice, RemoveNote, Sort],
Event USING [AddNote],
MusicDefs,
Note USING [DrawHead, Width],
Sheet USING [Map, MapNote],
Utility USING [DrawBox, DrawChar, DrawLine, SetColor, SetCP];
ChordImpl: CEDAR PROGRAM
IMPORTS Beam, Chord, Note, Sheet, Event, Utility
EXPORTS Chord
= BEGIN OPEN Chord, MusicDefs, Utility;
SetBeam: PUBLIC PROC[c: ChordPTR, b: BeamPTR] = {
FOR i: CARDINAL IN [0..c.length) DO c.note[i].beam ← b; ENDLOOP;
};
GetHeapIndex: PUBLIC PROC[heap: ChordHeapPTR, c: ChordPTR] RETURNS[CARDINAL] = {
FOR i: CARDINAL IN [0..heap.length) DO
IF heap.chord[i] = c THEN RETURN[i];
ENDLOOP;
RETURN[heap.length];
};
Grace: PUBLIC PROC[c: ChordPTR] RETURNS[BOOLEAN] = {
FOR i: CARDINAL IN [0..c.length) DO
IF ~c.note[i].grace THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
InVoice: PUBLIC PROC[c: ChordPTR, voice: CARDINAL] RETURNS[BOOLEAN] = {
IF voice = noVoice THEN RETURN[TRUE];
FOR i: CARDINAL IN [0..c.length) DO
IF c.note[i].voice = voice THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
****************************************************************************
data abstractions for a chord
CONSTRAINT: c.note[i].sync = c.note[j].sync for all i, j < c.length
CONSTRAINT: c.note[i].beam = c.note[j].beam for all i, j < c.length
****************************************************************************
AddNote: PUBLIC PROC[score: ScorePTR, chord: ChordPTR, note: NotePTR] = {
chordSync: SyncPTR ← IF chord.note[0] # NIL THEN chord.note[0].sync ELSE note.sync;
chordBeam: BeamPTR ← IF chord.note[0] # NIL THEN chord.note[0].beam ELSE NIL;
IF chord = note.chord THEN RETURN;
IF note.chord # NIL THEN Chord.RemoveNote[score, note.chord, note]; -- sets note.beam to NIL
put the note in the chord
IF chord.length = chord.max THEN chord ← New[score, chord.max+4, chord];
chord.note[chord.length] ← note;
chord.length ← chord.length + 1;
note.chord ← chord;
IF chordSync # NIL AND chordSync # note.sync THEN {
Event.AddNote[score, chordSync, note];
IF note.beam # NIL THEN Beam.SetSyncs[note.beam]};
IF chordBeam # NIL THEN { -- move note to this beam
IF note.beam # NIL THEN Beam.RemoveItem[score, note.beam, note];
note.beam ← chordBeam};
IF chordBeam = NIL AND note.beam # NIL THEN { -- replace note with chord
beam: BeamPTR ← note.beam;
Beam.RemoveItem[score, beam, note, FALSE]; -- don't free beam!
Beam.AddItem[score, beam, chord]};
};
RemoveNote: PUBLIC PROC[score: ScorePTR, chord: ChordPTR, note: NotePTR, free: BOOL] = {
IF note = NIL OR chord = NIL THEN RETURN;
keep the notes packed;
FOR i: CARDINAL IN [0..chord.length) DO
IF chord.note[i] # note THEN LOOP;
IF note.chord = chord THEN note.chord ← NIL;
IF chord.length = 1 AND note.beam # NIL
THEN Beam.RemoveItem[score, note.beam, chord]; -- remove chord from beam
note.beam ← NIL; -- removing a note from a chord also removes it from the beam
chord.length ← chord.length - 1;
chord.note[i] ← chord.note[chord.length];
chord.note[chord.length] ← NIL;
EXIT; ENDLOOP;
IF free AND chord.length < 2 THEN Chord.Free[score, chord];
};
New: PUBLIC PROC[score: ScorePTR, length: CARDINAL] RETURNS[chord: ChordPTR] = {
chord ← NEW[ChordRec[length]];
IF old # NIL THEN { -- replace old with chord in notes, beam, and chordHeap.
nullSize: CARDINAL = SIZE[ChordRec[0]];
oldSize: CARDINAL = SIZE[ChordRec[old.max]];
Inline.LongCOPY[from: old, nwords: nullSize-1, to: chord]; -- skip 'max'
Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: chord+nullSize];
SetBackPointers[score, old, chord];
zone.FREE[@old];
RETURN};
IF score.chordHeap.length = score.chordHeap.max THEN { -- replace with a bigger one.
old: ChordHeapPTR = score.chordHeap;
nullSize: CARDINAL = SIZE[ChordHeapRec[0]];
oldSize: CARDINAL = SIZE[ChordHeapRec[old.max]];
newMax: CARDINAL ← score.chordHeap.max+100;
new: ChordHeapPTR ← Utility.NewSegment[SIZE[ChordHeapRec[newMax]], newMax, nullSize-1];
Inline.LongCOPY[from: old, nwords: nullSize-1, to: new]; -- skip 'max'
Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: new+nullSize];
IF new.max # newMax THEN ERROR;
score.chordHeap ← new;
Utility.FreeSegment[old]};
score.chordHeap.chord[score.chordHeap.length] ← chord;
score.chordHeap.length ← score.chordHeap.length + 1;
};
Free: PUBLIC PROC[score: ScorePTR, chord: ChordPTR] = {
SetBackPointers[score, chord, NIL];
zone.FREE[@chord];
};
SetBackPointers: PROC[score: ScorePTR, c: ChordPTR, new: ChordPTR] = {
changes all pointers to c to be pointers to new
a chord can be pointed to by notes, beams, and the chordHeap.
chordBeam: BeamPTR ← IF c.length > 0 THEN c[0].beam ELSE NIL;
index: CARDINAL ← Chord.GetHeapIndex[score.chordHeap, c];
score.chordHeap[index] ← new;
IF new = NIL THEN { -- pack the beamHeap
score.chordHeap.length ← score.chordHeap.length - 1;
score.chordHeap[index] ← score.chordHeap[score.chordHeap.length]};
IF chordBeam # NIL AND new # NIL THEN {
Beam.RemoveItem[score, chordBeam, c, FALSE]; -- don't free beam!
Beam.AddItem[score, chordBeam, new]};
IF chordBeam # NIL AND new = NIL THEN {
Beam.RemoveItem[score, chordBeam, c, c.length = 0]; -- don't free beam if length > 0
IF c.length > 0 THEN Beam.AddItem[score, chordBeam, c[0]]};
FOR i: CARDINAL IN [0..c.length) DO -- NIL chord back pointers in notes
IF c.note[i].chord = c THEN c.note[i].chord ← new;
ENDLOOP;
};
Sort: PUBLIC PROC[c: ChordPTR, up: BOOL] RETURNS[note: CARDINAL] = {
temp: NotePTR;
FOR i: CARDINAL IN [0..c.length) DO
FOR j: CARDINAL IN (i..c.length) DO
IF NOT up AND c.note[i].pitch < c.note[j].pitch
OR up AND c.note[i].pitch > c.note[j].pitch
THEN { temp ← c.note[i]; c.note[i] ← c.note[j]; c.note[j] ← temp; };
ENDLOOP;
ENDLOOP;
};
SetDefaultStem: PUBLIC PROC[sheet: SheetPTR, c: ChordPTR] = {
n: CARDINAL;
x0, xn, y0, yn, middle0, middlen: INTEGER;
n ← Chord.Sort[c, TRUE];
[x0, y0] ← Sheet.Map[sheet, c.note[0].sync.time, c.note[0].pitch, c.note[0].staff];
[xn, yn] ← Sheet.Map[sheet, c.note[n-1].sync.time, c.note[n-1].pitch, c.note[n-1].staff];
middle0 ← Sheet.Map[sheet, c.note[0].sync.time, , c.note[0].staff].y+16;
middlen ← Sheet.Map[sheet, c.note[n-1].sync.time, , c.note[n-1].staff].y+16;
SELECT TRUE FROM
(yn+y0)/2 > middlen => c.stemUp ← FALSE;
(yn+y0)/2 < middle0 => c.stemUp ← TRUE;
(yn+y0)/2 > (middlen+middle0)/2 => c.stemUp ← TRUE;
ENDCASE => c.stemUp ← FALSE;
};
******************************************************************
procedures for drawing chords on the screen
******************************************************************
Draw: PUBLIC PROC[score: ScorePTR, c: ChordPTR, stem: INTEGER] = {
w: REAL = 0;
n: NotePTR;
flag: NoteValue;
two: REAL = 2;
dotX: INTEGER ← 0;
inVoice: BOOL;
xWide, yWide: INTEGER;
sheet: SheetPTR = score.sheet;
nonGrace: BOOLFALSE;
minStaff, maxStaff: CARDINAL;
x, y, yMin, yMax, mid, width: INTEGER;
inVoice ← score.sheet.voice = noVoice OR Chord.InVoice[c, score.sheet.voice]; -- draw the note heads
FOR i: CARDINAL IN [0..c.length) DO
n ← c.note[i];
IF ~n.dotted THEN LOOP;
[x, y] ← Sheet.MapNote[sheet, n];
dotX ← MAX[dotX, x+Note.Width[n]+2];
ENDLOOP;
mid ← Sheet.Map[sheet, n.sync.time, , n.staff].y+16;
FOR i: CARDINAL IN [0..c.length) DO
n ← c.note[i];
[x, y] ← Sheet.MapNote[sheet, n];
Note.DrawHead[score, n, x, y, dotX];
IF i = 0 THEN {yMin ← yMax ← y; width ← 0};
width ← MAX[width, Note.Width[n]];
IF n.delta # 0 THEN {
xWide ← MIN[xWide, x];
yWide ← IF y > mid THEN MIN[yWide, y] ELSE MAX[yWide, y]};
IF ~n.grace THEN nonGrace ← TRUE;
IF y <= yMin THEN {yMin ← y; minStaff ← n.staff};
IF y >= yMax THEN {yMax ← y; maxStaff ← n.staff};
ENDLOOP;
IF NOT sheet.notehead THEN RETURN;
draw the ledger lines
Utility.SetColor[sheet.context, IF inVoice THEN black ELSE light];
IF sheet.display = graphical THEN x ← x - n.delta; -- subtract off x offset before drawing stem
FOR i: INTEGER IN [3..9] WHILE yMax-mid >= i*d DO
IF nonGrace
THEN Utility.DrawBox[sheet.context, [x-3, mid+w+i*d, x+width+3, mid-w+i*d]]
ELSE Utility.DrawBox[sheet.context, [x-3, mid+w/two+i*d, x+width+3, mid-w/two+i*d]];
ENDLOOP;
FOR i: INTEGER IN [3..9] WHILE mid-yMin >= i*d DO
IF nonGrace
THEN Utility.DrawBox[sheet.context, [x-3, mid+w-i*d, x+width+3, mid-w-i*d]]
ELSE Utility.DrawBox[sheet.context, [x-3, mid+w/two-i*d, x+width+3, mid-w/two-i*d]];
ENDLOOP;
IF n.value = whole OR n.value = unknown THEN RETURN;
draw the stem
flag ← n.value;
IF stem # default -- drawing part of or beam
THEN {stem ← (IF c.stemUp THEN stem+1 ELSE stem-1); flag ← quarter}
ELSE SELECT TRUE FROM
nonGrace AND c.stemUp => stem ← MAX[yMax+(7*d)/2, mid];
nonGrace AND ~c.stemUp => stem ← MIN[yMin-(7*d)/2, mid];
c.stemUp => stem ← yMax+2*d;
~c.stemUp => stem ← yMin-2*d;
ENDCASE;
IF c.stemUp
THEN DrawLine[sheet.context, x+width, yMin, x+width, stem]
ELSE DrawLine[sheet.context, x, yMax, x, stem];
draw the flag
IF nonGrace
THEN Utility.SetCP[sheet.context, x, IF c.stemUp THEN stem-24 ELSE stem+24]
ELSE Utility.SetCP[sheet.context, x, IF c.stemUp THEN stem-17 ELSE stem+17];
SELECT TRUE FROM
~nonGrace => IF c.stemUp
THEN DrawChar[sheet.context, 133C]
ELSE DrawChar[sheet.context, 134C];
flag = eighth => IF c.stemUp
THEN DrawChar[sheet.context, 153C]
ELSE DrawChar[sheet.context, 163C];
flag = sixteenth => IF c.stemUp
THEN DrawChar[sheet.context, 152C]
ELSE DrawChar[sheet.context, 162C];
flag = thirtysecond => IF c.stemUp
THEN DrawChar[sheet.context, 151C]
ELSE DrawChar[sheet.context, 161C];
flag = sixtyfourth => IF c.stemUp
THEN DrawChar[sheet.context, 151C]
ELSE DrawChar[sheet.context, 161C];
ENDCASE;
};
d: CARDINAL = 8;
****************************************************************************
look graphical
****************************************************************************
Adjust: PUBLIC PROC[sheet: SheetPTR, c: ChordPTR] = {
n: NotePTR;
pad: REF ScratchPad ~ NEW[ScratchPad];
delta: INTEGER;
last: BOOLFALSE;
build the pad, initialize
FOR i: NAT IN [0..c.length) DO
[, pad[i].y] ← Sheet.MapNote[sheet, n];
pad[i].n ← n;
n.delta ← 0;
ENDLOOP;
SortPad[c.stemUp, pad];
c.delta ← 0;
determine the deltas on the notes
IF c.stemUp THEN delta ← 8 ELSE delta ← -8;
FOR i: NAT IN [1..c.length) DO
IF last THEN { last ← FALSE; LOOP; };
IF ABS[pad[i-1].y-pad[i].y] >= 8 THEN LOOP;
pad[i].n.delta ← delta;
last ← TRUE;
ENDLOOP;
};
SortPad: PROC[ascending: BOOL, pad: REF ScratchPad] = {
temp: Scratch;
FOR i: NAT IN [0..padLength) DO
IF pad[i].n = NIL THEN EXIT;
FOR j: NAT IN (i..padLength) 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;
};
Delta: PROC[n: NotePTR] RETURNS[INTEGER] = {
c: ChordPTR ~ n.chord;
IF c = NIL THEN RETURN[n.delta]
ELSE RETURN[n.delta+c.delta];
};
padLength: NAT = 16;
ScratchPad: TYPE = ARRAY [0..padLength) OF Scratch; -- need to make this variable length
Scratch: TYPE = RECORD[
x, y, stem: INTEGER ← 0,
push: NAT ← padLength,
acc: Accidental,
stemUp: BOOL,
c: ChordPTR,
n: NotePTR ← NIL
];
END.