You are on page 1of 268

1

Pelog: A Musical Constraint System


Michael Droettboom
COSC4080 Independent Study Project
Advisor: Dr. Peter Roosen-Runge
November 30, 1999

Contents
Introduction
0.1 Overview . . . . . . . . . . . .
0.2 Purpose . . . . . . . . . . . . .
0.3 Guide to this document . . . .
0.4 Comparisons . . . . . . . . . .
0.4.1 Musical languages . . .
0.4.2 Automatic composition
0.5 Future Directions . . . . . . . .
0.5.1 Pelog . . . . . . . . . .
0.5.2 Visual Pelog . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

Pelog

1 The
1.1
1.2
1.3
1.4

ii
ii
iii
iii
iv
iv
iv
v
v
v

Pelog language
What is a rule set? . . . . . . . . . . . . . . . . . . . . . .
Anatomy of a rule . . . . . . . . . . . . . . . . . . . . . .
How rules are applied to the score . . . . . . . . . . . . .
Rule ordering for efficiency . . . . . . . . . . . . . . . . .
1.4.1 Put the rule class with the fewest branches first . .
1.4.2 When the given class and weighting system doesnt
it yourself . . . . . . . . . . . . . . . . . . . . . . .
Pelog library reference . . . . . . . . . . . . . . . . . . . .
1.5.1 Comments . . . . . . . . . . . . . . . . . . . . . . .
1.5.2 Event . . . . . . . . . . . . . . . . . . . . . . . . .
1.5.3 Global . . . . . . . . . . . . . . . . . . . . . . . . .
1.5.4 Interval . . . . . . . . . . . . . . . . . . . . . . . .
1.5.5 Parts . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5.6 Pitch . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5.7 Range . . . . . . . . . . . . . . . . . . . . . . . . .
1.5.8 Scale . . . . . . . . . . . . . . . . . . . . . . . . . .

2
2
2
4
5
5

. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
apply, do
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .

6
8
8
9
10
10
12
13
14
15

2 Modal Counterpoint
2.1 Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 The rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

20
20
22

1.5

CONTENTS

2.3

2.4

ii

2.2.1 Species rules . . . . . . . . . . . .


2.2.2 Static rules . . . . . . . . . . . . .
2.2.3 Horizontal rules . . . . . . . . . . .
Vertical rules . . . . . . . . . . . . . . . .
2.3.1 Horizontal / Vertical rules . . . . .
2.3.2 Extensions of Modal Counterpoint
Implementation . . . . . . . . . . . . . . .
2.4.1 modal counterpoint.pl . . . . . . .

3 GUIDO music notation format


3.1 Scores . . . . . . . . . . . . .
3.2 Parts . . . . . . . . . . . . . .
3.3 Events . . . . . . . . . . . . .
3.4 Notename . . . . . . . . . . .
3.5 Accidentals . . . . . . . . . .
3.6 Octave . . . . . . . . . . . . .
3.7 Duration . . . . . . . . . . . .
3.8 Tags . . . . . . . . . . . . . .
3.8.1 Key . . . . . . . . . .
3.8.2 Scale . . . . . . . . . .
3.8.3 Range . . . . . . . . .
3.8.4 Meter . . . . . . . . .
3.8.5 Accent pattern . . . .
3.8.6 Comment style . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

22
23
24
26
26
28
28
28

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

35
36
36
36
36
36
37
37
37
37
38
39
39
39
39

4 Tools
4.1 Selecting the tools . . . . . . . .
4.1.1 Prolog interpreter . . . .
4.1.2 GUI development system
4.1.3 Connecting the two . . .
4.2 Reflections after-the-fact . . . .
4.2.1 SWI-Prolog . . . . . . . .
4.2.2 Tcl/Tk . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

41
41
41
42
43
44
44
44

5 Data Structure
5.1 Design notes . . . . . . .
5.2 A worked example . . . .
5.2.1 Index . . . . . . .
5.2.2 Part . . . . . . . .
5.2.3 Pitch . . . . . . . .
5.2.4 Duration . . . . .
5.2.5 Time . . . . . . . .
5.2.6 Previous and Next
5.2.7 Vertical . . . . . .
5.2.8 Comments . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

45
45
46
48
48
48
49
51
51
51
53

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

CONTENTS

iii

5.2.9 Penalty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.10 Other . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6 Code and Testing
6.1 The Pelog-GUI . . . . . . . .
6.1.1 pelog-gui.tcl . . . . . .
6.2 The Core Interpreter . . . . .
6.2.1 pelog.pl . . . . . . . .
6.2.2 main.pl . . . . . . . .
6.2.3 bi-math.pl . . . . . . .
6.2.4 preprocessor.pl . . . .
6.2.5 rule-def.pl . . . . . . .
6.2.6 score.pl . . . . . . . .
6.2.7 trees.pl . . . . . . . .
6.3 The GUIDO parser/grammar
6.3.1 parser.pl . . . . . . . .
6.3.2 lexer.pl . . . . . . . .
6.3.3 grammar.pl . . . . . .
6.3.4 Testing . . . . . . . .
6.4 The Pelog library . . . . . . .
6.4.1 main.pl . . . . . . . .
6.4.2 comments.pl . . . . .
6.4.3 event.pl . . . . . . . .
6.4.4 global.pl . . . . . . . .
6.4.5 interval.pl . . . . . . .
6.4.6 parts.pl . . . . . . . .
6.4.7 pitch.pl . . . . . . . .
6.4.8 range.pl . . . . . . . .
6.4.9 scale.pl . . . . . . . .

II

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

Visual Pelog

7 Motivations for a visual language


7.1 Summary of core Pelog system .
7.2 The next step . . . . . . . . . . .
7.3 The problem . . . . . . . . . . .
7.4 The solution . . . . . . . . . . . .
8 The
8.1
8.2
8.3

54
54
55
55
55
67
67
70
70
74
80
82
84
84
84
89
91
93
101
101
102
105
108
110
118
120
131
135

150
.
.
.
.

.
.
.
.

.
.
.
.

Visual Pelog language


Musical Constraint Graphs . . . . . .
Constraint primitives . . . . . . . . . .
Some examples . . . . . . . . . . . . .
8.3.1 Step-wise motion . . . . . . . .
8.3.2 Step-wise or third-wise motion
8.3.3 Avoiding parallel intervals . . .

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.

.
.
.
.

151
151
151
152
153

.
.
.
.
.
.

154
154
156
157
157
157
159

CONTENTS
8.4
9 The
9.1
9.2
9.3
9.4

iv

Future extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

160
160
160
162
162
162
162
162
162
162
162
163
164
165

10 Graph Layout Algorithm


10.1 Specification of problem . . . . . . . . . . . . . . . . . . . . . .
10.2 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.2.1 Sugiyama algorithm . . . . . . . . . . . . . . . . . . . .
10.2.2 Constraint-based extensions to the Sugiyama algorithm
10.2.3 The problem of graph stability . . . . . . . . . . . . . .
10.2.4 The problem of two-dimensionality . . . . . . . . . . . .
10.3 GLA Implementation . . . . . . . . . . . . . . . . . . . . . . . .
10.3.1 General design . . . . . . . . . . . . . . . . . . . . . . .
10.3.2 The top-level . . . . . . . . . . . . . . . . . . . . . . . .
10.3.3 The near constraint . . . . . . . . . . . . . . . . . . . .
10.3.4 Constraint-based topological sort . . . . . . . . . . . . .
10.3.5 Conflict resolution . . . . . . . . . . . . . . . . . . . . .
10.3.6 Mapping physical coordinates to logical coordinates . .
10.3.7 Animation . . . . . . . . . . . . . . . . . . . . . . . . . .
10.3.8 Creating nodes . . . . . . . . . . . . . . . . . . . . . . .
10.4 Code related to the graph layout algorithm . . . . . . . . . . .
10.4.1 RuleEditor.py . . . . . . . . . . . . . . . . . . . . . . . .
10.4.2 Nodes.py . . . . . . . . . . . . . . . . . . . . . . . . . .
10.4.3 CanvasItems.py . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

166
166
167
167
168
168
168
169
169
169
169
169
170
170
170
170
171
171
178
187

.
.
.
.
.
.

192
193
193
194
194
203
203

9.5

9.6

Visual Pelog environment


Introduction . . . . . . . . . . . . .
A Visual Pelog session . . . . . . .
Opening and saving rule sets . . .
Rule manager . . . . . . . . . . . .
9.4.1 Selecting rules . . . . . . .
9.4.2 Adding rules . . . . . . . .
9.4.3 Moving rules . . . . . . . .
9.4.4 Deleting rules . . . . . . . .
9.4.5 Editing rules . . . . . . . .
Rule editor . . . . . . . . . . . . .
9.5.1 Graph editor . . . . . . . .
9.5.2 Using the musical keyboard
Tool palette . . . . . . . . . . . . .

11 Custom Megawidgets
11.1 Tree Widget . . . . . . .
11.1.1 End user usage .
11.1.2 Developer usage
11.1.3 Tree.py . . . . .
11.2 Help Browser Widget . .
11.2.1 End user usage .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

CONTENTS
11.2.2 Developer usage . .
11.2.3 Help.py . . . . . . .
11.3 On-screen musical keyboard
11.3.1 End user usage . . .
11.3.2 Developer usage . .
11.3.3 Future plans . . . .
11.3.4 MusicWidgets.py . .

v
. . . . .
. . . . .
Widget
. . . . .
. . . . .
. . . . .
. . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

203
204
209
209
210
211
211

12 Implementation of other components


12.1 Tool palette . . . . . . . . . . . . . .
12.1.1 Palette.py . . . . . . . . . . .
12.1.2 Tools.py . . . . . . . . . . . .
12.1.3 IntervalTool.py . . . . . . . .
12.2 Constants and Utility functions . . .
12.2.1 Constants.py . . . . . . . . .
12.2.2 Util.py . . . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

220
220
220
223
229
230
230
231

III

Appendices

233

A Tcl/Tk-Prolog Connector System


A.1 The problem . . . . . . . . . . . .
A.2 C-language connector method . . .
A.2.1 Tcl/Tk-Prolog Connection .
A.3 Piping method . . . . . . . . . . .
A.3.1 Interaction Model . . . . .
A.3.2 Design . . . . . . . . . . . .
A.3.3 Usage . . . . . . . . . . . .
A.3.4 Implementation . . . . . . .
A.3.5 pl interface.tcl . . . . . . .
A.3.6 tcl interface.pl . . . . . . .
B The
B.1
B.2
B.3
B.4

graphical user interface


Installing the GUI . . . . .
Running the GUI . . . . . .
A quick introduction . . . .
Visual elements . . . . . . .
B.4.1 The main window .
B.4.2 The menubar . . . .
B.4.3 The toolbar . . . . .
B.4.4 The editor . . . . . .
B.5 Functions . . . . . . . . . .
B.5.1 Basic file and editing
B.5.2 Look . . . . . . . . .
B.5.3 Hear . . . . . . . . .
B.5.4 Go . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
functions
. . . . . .
. . . . . .
. . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

234
234
234
235
236
236
236
236
236
239
241

.
.
.
.
.
.
.
.
.
.
.
.
.

244
244
245
245
248
248
248
248
249
249
249
249
249
250

CONTENTS

vi

B.6 Notes about portability . . . . . . . . . . . . . . . . . . . . . . . . . 251

Introduction
There once was a brainy baboon
Who always breathed down a bassoon,
For he said, It appears
That in billions of years
I shall certainly hit on a tune.
Ezra Pound (1885-1972)
How can we make our modern day brainy baboon (i.e. a computer) hit on a
tune any faster? We certainly dont have billions of years to wait. The answer
lies in constraining the number of possibilities. Certainly, this is what Western Art
Music has done all along. Would Renaissance music have been the same if the
church had not imposed strong restrictions1 on musical language? Many believe
that Mozarts genius lies not in his emotive ability, but in how he worked within the
rigid musical confines of his day. Even the so-called Romantic composers expressed
themselves within a rigid tonality. Despite recent years of liberalism, musical constraints remain alive and well. Just consider the serialist and minimalist movements.
The purpose of this project is to help determine exactly what makes up these various musical frameworks. In other words, what restrictions do we need to place on
the brainy baboon in order to arrive at the elusive tune?

0.1

Overview

The Pelog system provides an intuitive language in which to specify musical constraints. These constraints are then applied by the Pelog interpreter to generate
or evaluate musical scores.
The musical constraints are written in a superset of Prolog known as the Pelog
language (see 1). Since the constraints themselves are separate from the core system, it is possible for the end user to modify or create constraints with minimal
effort. The advantage of using Prolog as the base language is that Pelog applications
are highly declarative and accessible to the novice programmer with a musical background. Just like Prolog, the complexities of solution searching and backtracking
are hidden from the programmer.
Sets of constraints can be written to represent any rule-based musical system.
For example, the first set of constraints developed in Pelog is for Modal Counterpoint
[20]. Future candidates include Tonal Harmonic Counterpoint and Schonberg-style
1

often at the penalty of death

vii

INTRODUCTION

viii

Twelve-Tone Serial Composition [26]. It is also possible to implement entirely new


systems of musical constraints.
The original textual language for musical constraints has been extended to an
editable visual representation, Visual Pelog (see part II). The Visual Pelog system
allows the user to specify musical constraints as graphs of unary and binary relationships between musical events. For some users, this visual representation, with
immediate feedback about logical errors and an interactive help system, may be
more intuitive than using the text-based language.
Musical phrases are input in a musical notation language called GUIDO [15].
This language has been extended to allow certain events to be variable. These
variable events are filled-in by the Pelog interpreter.

0.2

Purpose

Pelog serves a two-tiered purpose:


Using pre-built add-ins known as Pelog rule sets, musicians can use Pelog as
a counterpoint generation and evaluation system.
For the novice programmer with a musicological bent, Pelog is a musical
constraint specification language that can be used to test the validity of a
given musical framework or develop entirely new musical relationships. Visual
Pelog extends this purpose by increasing the accessibility of the language
Pelog does not pretend to produce beautiful music. At this stage, it is perceived as a
test-bed for research into musical systems. Often the logic used to describe musical
constraints is very fuzzy leaving a lot of room for individual interpretation. One
only needs to look at Gradus ad Parnassum [11] or any undergraduate counterpoint
classroom for an example of this. Translating these constraints for use by a computer
forces one to be meticulously precise. This exercise alone can be very illuminating.
Having a computer apply these rules to musical phrases reveals the weaknesses of
a given musical constraint system in a very automatic and efficient way. Pelog can
therefore assist is asking questions such as How precise were Fuxs definitions of
modal counterpoint? If imprecise, what additional information is needed to match
his intent?

0.3

Guide to this document

This document is divided into two main parts:


1. Pelog: The core Pelog language and musical constraint interpreter. (page 2)
2. Visual Pelog: The Visual Pelog extensions to the language. (page 151)
Each part is broken down into chapters for end users of the system, followed by
chapters dealing with the implementation of the system.

INTRODUCTION

ix

The appendices contain reports on research conducted that proved to be obsolete


to the project as a whole.
For a general overview of the project, I suggest reading this introduction and
the first chapter of each part.

0.4

Comparisons

This section is intended to shed some light on where Pelog fits within the field of
Computer Music. It is by no means as an exhaustive survey of Computer Music
tools.

0.4.1

Musical languages

Some of the more common music-specific languages are Common Music [36], KeyKit
[38], CSound [40] and the commercial Max language [2]. Common Music is essentially Common Lisp Object System (CLOS) with a number of musical extensions.
KeyKit receives its lineage from awk. CSound is the latest in a series of musical
generation languages based on Music V. Max is both a language and visual programming environment. The programmer can set up musical generators as units
connected by wires. What sets Pelog apart from all of these languages is its
logical semantics. It builds on the strength of Prolog by allowing the programmer to specify what the output should look like rather than how to get there.
While the application domains of these other languages is perhaps larger than that
of Pelog, Pelog takes a more natural approach to musical generation that is likely
more familiar to non-programmers.

0.4.2

Automatic composition

Automatic Counterpoint [33] appears to do one thing very well, and has in fact
proved to be a very valuable starting point for Pelogs Modal Counterpoint application. Pelogs advantage however2 , is in the declarative nature and maintainability of
the musical constraints themselves. While Schottsteadts work seems to be firmly
grounded in modal counterpoint, Pelog can be extended to any musical system
imaginable.
Perhaps the most popular work in automatic composition is David Copes Experiments in Musical Intelligence (EMI) [6]. His work began in the same domain
as Pelog: musical constraints. This approach was quickly abandoned for one involving recombination of musical phrases from composers bodies of work. While
Copes work was remarkably effective at generating enjoyable and original music
in the styles of various composers, it lacks the pure musicological focus of Pelog.
The research domain of Pelog is less interested in generating interesting music than
testing the effectiveness of given sets of musical constraints.
Copes work does, however, illustrate a fundamental weakness of the Pelog system. No composer exists in a vacuum, and therefore it is impossible to believe
2

This is postulation, as Schottsteadts source code is not readily available

INTRODUCTION

that effective music could be generated using constraints exclusively. (Composer


and Professor James Tenney, with whom I have studied, always asserts that a good
composer must also be an avid listener.) Perhaps the ideal system, then, would be
a combination of EMI and Pelog: a system that has both constraints and a history
of external musical experience. (Give our brainy baboon a collection of Bach
Chorale recordings and he might stand a better chance.)

0.5
0.5.1

Future Directions
Pelog

Pelog is still very much a ninety-pound weakling, and theres lots more to do.
Following are some future projects I am currently considering, in approximate order
of priority.
1. Re-write the core algorithm
The rule application mechanism as it currently stands is extremely
inefficient, almost to the point of uselessness. A complete re-write
of the main rule application mechanism to take advantage of the
constraint proving theorems presented in Constraint Satisfaction in
Logic Programming [39] is in order. These theorems could decrease
the time complexity by a factor of up to 10 times.
2. Improve and expand library functions
There is almost an infinite variety of library functions that would
be useful to rule programmers in various domains. These might
include texture, rhythmic accent patterns, contextualism or parallelism. A deeper examination of other musical languages may reveal
the kinds of high-level information often deemed necessary by Pelog
programmers.
3. Implement more rule sets
As already mentioned, there are many more musical constraint systems than just modal counterpoint. Future applications could include tonal counterpoint or serial composition.

0.5.2

Visual Pelog

The Visual Pelog system currently exists only as a user-interface prototype. Most
importantly, the connection between Visual Pelog and the core Pelog system needs
to be completed. Beyond the obvious to-do list, there are some other important
directions the system could take:
1. Integrate it with musical input/output system

INTRODUCTION

xi

This would most likely take the form of adding MIDI capability
to Python. The on-screen musical keyboard widget could be expanded to accept input from a physical musical keyboard connected
via MIDI. The system could output the results of the musical constraints through a MIDI synthesiser.
2. Re-write parts of the code to take advantage of an object-oriented GUI
paradigm.
It was rather late in the project when I discovered the book ObjectOriented GUI Application Development by Geoff Lee [22]. Adopting a more disciplined approach to GUI development, such as the
Document-View architecture, would improve the scalability and
overall flexibility of the system.

I
Pelog

The Pelog language


The Pelog system is completely extendible and allows the end user to modify existing
rule sets or create new ones from scratch.
Editing rule sets requires at least a working knowledge of Prolog. I recommend
Programming in Prolog [5] as a good starting point, and it should provide you
with everything you need to understand this section. It is also helpful to use a
programmers text editor with good support for Prolog, such as GNU Emacs.
This section provides a tutorial introduction to developing rule sets followed by
a reference of built-in functions available to the Pelog programmer.

1.1

What is a rule set?

The Pelog language is nothing more than a superset of the Prolog language. Therefore, a rule set is essentially a Prolog source file containing a number of specially
defined rule predicates. The Pelog programmer has access to all of the built-in and
library functions that make up SWI-Prolog, as well as the extensions provided by
Pelog.
The difference between a Pelog rule set and a plain-vanilla Prolog source file
is that Pelog rule sets are run through a special preprocessor before they can be
interpreted. This process is explained below.

1.2

Anatomy of a rule

Each rule in the rule set looks like a regular Prolog predicate of the form:
rule(name, class, weight) :goal1,
goal2, . . .
name is a simply a name for the rule to use for reference.
2

CHAPTER 1. THE PELOG LANGUAGE

class specifies the rule class that the rule belongs to.
weight is an integer value specifying the weight of the rule.
Rule classes and weights are described in the next section.
The following is a rule that ensures that every event stays within the scale.
% P i t c h must be a member o f t h e c u r r e n t s c a l e
r u l e ( In scale , scale , 0 ) :
$event
scale .

The name of this rule is In scale, and its class is scale, and the strength value
is 0. Following this is one goal that ensures the current event is in the scale. For a
list of the Pelog library functions that are useful for writing musical goals, see the
Pelog library reference (page 8).
In many ways the predicate above is just like a normal Prolog predicate. Notice,
however, that there are no arguments that unify with variables, and thus seemingly
no way to interact with external data. This problem is solved, however, by a special
Pelog rule preprocessor. Rather than listing all of the arguments in the head of
each and every rule predicate, which could get quite tedious and cumbersome, the
preprocessor does all that work for you. From a predicate such as the one above, it
creates a new predicate with a set of all the arguments you could possibly need to
interact with the score. These arguments are matched up with Pelogs concept of
metavariables, which all begin with a dollar sign $. Table 1.1 is a listing of the
available metavariables. The above rule predicate, therefore, is converted internally
metavariable
$event
$prevx

abbrev.
$e
$px

$vert

$v

$events

$x

description
The current event
The x th event before the current event
An event vertically related to
the current event. (i.e. an
event occurring at the same
time as the current event)
The set of all events. Used by
some library predicates that
need access to the enter score.
Table 1.1: Metavariables

to:
r u l e ( In scale , s c a l e ,
a r g s (A , B , C , D , E , F , G , H , I , J , K , L ) ) :
A
scale .

(And it actually creates an extra fact predicate, rule def/3, to keep around for
internal housekeeping.)
r u l e d e f ( In scale , s c a l e , 0 ) .

CHAPTER 1. THE PELOG LANGUAGE

1.3

How rules are applied to the score

It is wise for the rule programmer to know exactly how the rules will be applied to
the score in order to maximize time efficiency.
The entire set of rules is applied to the score event-by-event. The order in which
the events are visited is determined when the input file is read in by the GUIDO
parser. A time path is generated by sorting all the events by their starting times.
Therefore its essentially a first-events-first scheme. Figure 1.1 shows an example
time-path. For more information on the internal data representation, see A data
structure for encoding incomplete musical fragments on page 45.

Figure 1.1: Example score showing time-path


You may have noticed above that there are no metavariables provided for events
after the current event. This is because those events that have not been visited yet
may contain variables. It is good practice to determine the current event based only
on the previous events because they have already been pinned-down as non-variable.
This will eliminate unnecessary backtracking due to erroneous assumptions about
the future.
The order in which the rules are applied to each event is a little bit more complex.
All the rules are divided into classes. All the rules within each class are presumed
to be mutually exclusive. (Though it is the rule programmers responsibility to
ensure this.) For example, you may have the rule melodic interval of a second
and melodic interval of a third in the same class called melodic interval. These
two rules should be in the same class because it is impossible for an event to have
both a melodic interval of a second and a melodic interval of a third at the same
time (i.e. they are mutually exclusive.) It would not be a good idea to put melodic
interval of a second and in c major in the same class because it is possible for an
event to fulfill both rules simultaneously. The concept of class is important because
it allows the interpreter to be lazy. If an event passes the rule melodic interval
of a second, the rule melodic interval of a third does not need to be checked
because we already know it to be false.
Within each class, each rule is assigned a weight. Rules with lower weights

CHAPTER 1. THE PELOG LANGUAGE

are preferred to rules with higher weights. For example, in modal counterpoint
[11], steps are considered preferable to skips, so a rule for steps would have a lower
weighting than a rule for skips. Rules with the same weight are equally preferred.
The interpreter will arbitrarily choose one rule as preferable to another rule with
the same weight.
Lets examine how all of these classifications are used by the Pelog interpreter.
Each event needs to pass only one rule from each of the rule classes. The rule
classes are visited in the order they appear in the rule set file. When a class is
visited, the rule with the lowest weighting is tried first. If this rule fails, a rule with
the same weighting is tried. If all the rules with the same weighting fail, control
backtracks through the previously visited rule classes to find an alternate solution.
If all the rules at a particular weight level fail with all of the alternate solutions of
the previously visited rule classes, then, and only then, will rules of higher weighting
be tried. If all the rules in an entire rule class still fail, control backtracks to the
previous event. Alternate solutions of the previous event may lead to successful
completion of the current event.

1.4

Rule ordering for efficiency

Given this, what is the best way to order the rules to maximize efficiency?

1.4.1

Put the rule class with the fewest branches first

The first rule class visited will serve as the generating rule class. If an event
is specified as a variable in the input score, the first-visited rule class will serve
to instantiate that variable with a value. All rule classes visited thereafter will
only serve to verify that the instantiation is correct, not to generate new values.
By limiting the alternative solutions in the generating rule class you can limit the
number of values that need to be tested to arrive at a solution.
Take, for example, the following two rules:
% Good i n t e r v a l s
r u l e ( Good intervals , melodic intervals , 1 ) :
$prev1 = s t a r t
;
( member ( I , [ 2 , 3 , 4 , 5 ] ) ,
member ( IC , [ min , maj , p e r ] ) ,
$prev1 : $event a b s o l u t e i n t e r v a l
IC I ) .
% P i t c h must be a member o f t h e c u r r e n t s c a l e
r u l e ( In scale , scale , 0 ) :
$event
scale .

The Good intervals rule checks for permissible melodic intervals. There are 12
possible solutions for this rule. The In scale rule ensures that the event is in
the current scale. A major scale, for example, consists of seven notes. When

CHAPTER 1. THE PELOG LANGUAGE

you consider that Pelog handles those seven notes in sixteen octaves, this rule will
generate 7 16 = 112 possible solutions. It would therefore be much wiser to select
Good intervals as the generating rule instead of In scale.

1.4.2

When the given class and weighting system doesnt apply, do it


yourself

The rule set programmer is by no means forced to follow the default weighting and
class scheme for every rule class. It is merely provided as convenience. In cases
where it may be simpler or more declarative to override it, the rule programmer is
encouraged to do so.
Lets take another example from modal counterpoint. Events that occur at
certain times during the melody, namely the first, last and penultimate events,
must be at certain degrees of the scale. What exactly these are depends on whether
the event is in the top voice or in the bottom voice. This relationship is given in
Table 1.2. Since the mutual exclusivity is a little bit hard to follow here, the entire

top voice
bottom voice

first event
final, tenor
final

last event
final, third
final

penultimate event
leading tone
2, 4, 5

Table 1.2: Modal counterpoint rules related to chronological position


rule class has been implemented as one rule with a number of helper predicates.
Dont forget that a Pelog rule set file is really just a Prolog program in disguise.
/
CHRONOLOGICAL POSITION
N ot e s a t s p e c i a l p o s i t i o n s , namely t h e f i r s t , p e n u l t i m a t e and
l a s t n o t e s must be c e r t a i n s c a l e d e g r e e s
/
r u l e ( First -Last- Penultimate , chronological , 0 ) :
$ e v e n t f i r s t e v e n t >
m c f i r s t p i t c h ( $event )
;
$ e v e n t l a s t e v e n t >
m c l a s t p i t c h ( $event )
;
$ e v e n t s p e n u l t i m a t e e v e n t >
mc penultimate pitch ( $event )
;
true .
% The f i r s t p i t c h d e p e n d s on w hi c h v o i c e i t i s i n
m c f i r s t p i t c h (E) :
E comment First pitch ,
( E p a r t t o p >
m c f i r s t p i t c h t o p (E)

CHAPTER 1. THE PELOG LANGUAGE

;
E p a r t bottom >
m c f i r s t p i t c h b o t t o m (E ) ) .
% F i r s t p i t c h i n t o p v o i c e must be f i n a l ( t o n i c ) o r t e n o r ( dominant )
m c f i r s t p i t c h t o p (E) :
E final
;
E scale degree tenor .
% F i r s t p i t c h i n bottom v o i c e must be f i n a l ( t o n i c )
m c f i r s t p i t c h b o t t o m (E) :
E final .
% L a s t p i t c h d e p e n d s on w hi c h v o i c e i t i s i n
m c l a s t p i t c h (E) :
E comment Last pitch ,
( E p a r t t o p >
m c l a s t p i t c h t o p (E)
;
E p a r t bottom >
m c l a s t p i t c h b o t t o m (E ) ) .
% L a s t p i t c h i n t o p v o i c e must be f i n a l ( t o n i c ) o r
% t h e t h i r d ( m e d i a n t ) i f t h e r e a r e more t h a n t h r e e v o i c e s
m c l a s t p i t c h t o p (E) :
E final
;
( num parts X , X > 2,
E scale degree 3).
% L a s t p i t c h i n bottom v o i c e must be f i n a l
m c l a s t p i t c h b o t t o m (E) :
E final .
% P e n u l t i m a t e p i t c h d e p e n d s on w hi c h v o i c e i t i s i n
m c p e n u l t i m a t e p i t c h (E) :
E comment Penultimate pitch ,
( E p a r t t o p >
m c p e n u l t i m a t e p i t c h t o p (E)
;
E p a r t bottom >
mc penultimate pitch bottom (E ) ) .
% P e n u l t i m a t e p i t c h i n t o p v o i c e must be l e a d i n g t o n e o r
% f o u r t h d e g r e e ( s u b d o m i n a n t ) i f t h e r e a r e more t h a n two v o i c e s
m c p e n u l t i m a t e p i t c h t o p (E) :
E leading tone
;
( num parts X , X > 2,
E scale degree 4).

CHAPTER 1. THE PELOG LANGUAGE

% P e n u l t i m a t e p i t c h i n bottom v o i c e must be 2 nd , 4 t h o r 5 t h s c a l e d e g r e e
mc penultimate pitch bottom (E) :
E scale degree 2
;
E scale degree 4
;
E scale degree 5.

1.5

Pelog library reference

The Pelog system provides a rich set of library predicates for specifying musical relationships. Most of the predicates are implemented as infix operators. For example,
to ensure that the interval between two notes is a minor third, you would write:
$ p r e v 1 : $ e v e n t a b s o l u t e i n t e r v a l min 3

Of course, this is equivalent to Prologs more standard notation with the functor in
front:
a b s o l u t e i n t e r v a l ( $ p r e v 1 : $ e v e n t , min 3 )

While the syntax may be a little different from what most Prolog programmers
may be used to, it makes the code seem more declarative. After all, one of the goals
of this project was to make the rule specifications as readable as possible.
For consistency, all library predicates follow the convention of having the events
on the left hand side of the functor and all other arguments on the right hand side.
The Pelog library predicates are listed by library below. Each section contains
an introduction to the conventions used throughout the library followed by an alphabetical listing of the user predicates.
Syntax follows the form used in a lot of Prolog documentation, including the
SWI-Prolog documentation and OKeefe.
An argument preceded by a + is input only (i.e. it must be instantiated)
An argument preceded by a - is output only (i.e. it may not be instantiated)
An argument preceded by a ? is input or output.

1.5.1

Comments

This library provides a mechanism to store comment strings with each event. These
comments can be used to provide feedback about events within the score. These
comments are added to the output file in the style specified by the comment style
tag in the input file. See the section on GUIDO Music Notation (page 35) for more
information.

comment
+event comment +comment

CHAPTER 1. THE PELOG LANGUAGE

comment(+event, +comment)
If comment is instantiated, comment is added to events comment list.
If comment is a variable, comment unifies with any comment currently in events
comment list.
r u l e ( Step - step , contour , 1 ) :
$prev2 = s t a r t
;
( $prev2 : $prev1 a i n t e r v a l step ,
$prev1 : $event a i n t e r v a l step ,
$ p r e v 2 comment Step - step ) .

1.5.2

Event

first event
+event first event
first event(+event)
Succeeds if event is the first event in its part.
r u l e ( First -Last- Penultimate , chronological , 0 ) :
$ e v e n t f i r s t e v e n t >
m c f i r s t p i t c h ( $event )
;
$ e v e n t l a s t e v e n t >
m c l a s t p i t c h ( $event )
;
$ e v e n t p e n u l t i m a t e e v e n t >
mc penultimate pitch ( $event )
;
true .

last event
+event last event
last event(+event)
Succeeds if event is the last event in its part.

penultimate event
+events penultimate event
penultimate event(+events)
Succeeds if the current event defined in events is the penultimate event.
Note that this predicate requires $events and not $event as input.

CHAPTER 1. THE PELOG LANGUAGE

1.5.3

10

Global

The global library can be used to extend the GUIDO parser to recognize new tags.
Simply add a global default predicate to your Pelog rule set file. global default
predicates are of the form:
global default(tag(tagname), default)
tagname is the name of the tag
default is the default value for the tag if the tag is not present in the input file
The value of the tag can be used in your rules at runtime using the tag predicate.

tag
+tagname tag -value
tag(+tagname, -value)
Succeeds if tagname is assigned to value.
Tags that are assigned to specific parts, (i.e. not global to the score,) are retrieved using the form tag ~part, where tag is the tag name as it occurs in the input
file and part is the part name.

1.5.4

Interval

The relationship between two pitches.


In Pelog, intervals can be specified in the form type~size, where type is a threecharacter atom specifying the type of interval. The available types are listed in
Table 1.3.
type
maj
min
per
dim
aug
chr
sem
tri

meaning
major
minor
perfect
diminished
augmented
chromatic
semitones (size field is # of semitones)
tritone
Table 1.3: Interval types

For example, min~3 is a minor third and maj~7 is a major seventh. The sem
type returns the size field as the number of semitones, regardless of note names.
Therefore, maj~3 is equivalent to sem~4.
Note that either the type or size field may be left variable using Prologs anonymous variable, the underscore . Therefore, ~3 will specify dim~3, min~3, maj~3
and aug~3.

CHAPTER 1. THE PELOG LANGUAGE


type
unison
step
skip
consonant
dissonant

11

meaning
Unison (the two given notes have the same pitch)
A second
Any interval larger than a second
Any thirds, fifths, sixths or octaves that are not diminished
or augmented
Any seconds, fourths or sevenths
Table 1.4: Interval keywords

Intervals can also be specified using any of the keywords in Table 1.4.

absolute interval
?eventa : ?eventb absolute interval ?interval
?eventa : ?eventb absolute interval ?interval contour ?contour
absolute interval(?eventa : ?eventb, ?interval)
absolute interval(?eventa : ?eventb, ?interval contour ?contour )
?eventa : ?eventb a interval ?interval
?eventa : ?eventb a interval ?interval contour ?contour
a interval(?eventa : ?eventb, ?interval)
a interval(?eventa : ?eventb, ?interval contour ?contour )
Succeeds if the absolute interval between two events, eventa and empheventb, is
equal to interval. interval may be in any form specified in the introduction to the
interval library.
The optional contour unifies with up if empheventb is higher in pitch than eventa,
down if empheventb is lower in pitch than eventa, and static if empheventb is the
same pitch as eventa.
For specifying the cyclic interval between two events (i.e. where maj~10 is equivalent to maj~3, see the cyclic interval predicate.)
% Good i n t e r v a l s
r u l e ( Good intervals , melodic intervals , 1 ) :
$prev1 = s t a r t
;
( member ( I , [ 2 , 3 , 4 , 5 ] ) ,
member ( IC , [ min , maj , p e r ] ) ,
$prev1 : $event a i n t e r v a l
IC I ) . $

contour
+eventa : +eventb contour ?contour
contour(+eventa : +eventb, ?contour )
contour unifies with up if empheventb is higher in pitch than eventa, down if
empheventb is lower in pitch than eventa, and static if empheventb is the same pitch
as eventa.

CHAPTER 1. THE PELOG LANGUAGE

12

eventa : eventb contour contour


is much faster than the equivalent
eventa : eventb absolute interval contour contour

cyclic interval
?eventa : ?eventb cyclic interval ?interval
?eventa : ?eventb cyclic interval ?interval contour ?contour
cyclic interval(?eventa : ?eventb, ?interval)
cyclic interval(?eventa : ?eventb, ?interval contour ?contour )
?eventa : ?eventb c interval ?interval
?eventa : ?eventb c interval ?interval contour ?contour
c interval(?eventa : ?eventb, ?interval)
c interval(?eventa : ?eventb, ?interval contour ?contour )
Succeeds if the cyclic interval between two events, eventa and eventb, is equal
to interval. interval may be in any form specified in the introduction to the interval
library.
The optional contour unifies with up if empheventb is higher in pitch than eventa,
down if empheventb is lower in pitch than eventa, and static if empheventb is the
same pitch as eventa.
For specifying the absolute interval between two events (i.e. where maj~10 is
not equivalent to maj~3, see the absolute interval predicate.)

1.5.5

Parts

Each part in a score contains a single monophonic line.


A part can be referenced to by number, in which case the top part is part 1, the
next highest is part 2, etc. A part can also be referenced by a name defined by the
part def fact predicate. The built-in part names are shown in Table 1.5.
only
top
bottom
cantus firmus
inner
soprano
alto
tenor
bass

Table 1.5: Built-in part names


Additional part names can be defined by adding part def predicates to the rule
set file. part def predicates have the form:

CHAPTER 1. THE PELOG LANGUAGE

13

part def(name, partno, numparts)


name is the name of the part
partno is the corresponding part number
numparts is the number of parts
The built-in part defs are listed below:
part
part
part
part
part
part
part
part
part

def ( only ,
, 1) : !.
d e f ( top , 1 , X ) .
d e f ( bottom , X , X ) : X > 1 .
d e f ( c a n t u s f i r m u s , X , X) : X > 1.
d e f ( i n n e r , X , Y ) : X > 1 , X \= Y .
d e f ( soprano , 1 , 4 ) .
def ( alto , 2 , 4 ) .
def ( tenor , 3 , 4 ) .
def ( bass , 4 , 4 ) .

part
+event part -part
part(+event, -part)
Succeeds if event is in part.
part may be an integer or a part name defined in part def.

1.5.6

Pitch

In Pelog, pitch names follow the same convention as GUIDO (see GUIDO Music
Notation on page 35 for more information.) A pitch name is defined as a letter {c,
d, e, f, g, a, b} optionally followed by an accidental {#, &, ##, &&}.
An octave is an integer where octave 1 is the octave containing A440Hz.

equivalent pitch
+eventa : +eventb equivalent pitch
equivalent pitch(+eventa : +eventb)
Succeeds if eventa sounds at the same pitch as eventb.
For example, c# : d& equivalent pitch succeeds.

octave
+event octave -octave
octave(+event, -octave)
Succeeds if event is in octave.

pitch name

CHAPTER 1. THE PELOG LANGUAGE

14

?event pitch name ?name


pitch name(?event, ?name)
Succeeds if event has pitch name.

1.5.7

Range

Range can be specified in a number of ways.


As an interval. See the interval library (page 10) for more information.
As a set of two events lowevent : highevent. When using this form, the two
events must be instantiated.
By name
Please note that all events used with the range predicates must be instantiated.
The built-in range definitions are in Table 1.6.
soprano
alto
tenor
bass
Table 1.6: Built-in ranges
Additional named ranges can be defined by adding range def predicates to the
Pelog rule set file. range def predicates have the form:
range def(name, lowpitch, highpitch)
name is the name of the range.
lowpitch is the lowest pitch in the range, given in the form pitchname~octave. See the
pitch library (page 13) for details.
highpitch is the highest pitch in the range
The built-in range defs are given below:
range
range
range
range

def ( soprano , c 1 , a 3).


def ( alto , f 0, d3).
def ( tenor , c 0, a 1).
def ( bass , f 1, d 0).

in range
+event in range +range
in range(+event, +range)
Succeeds if event is in range.
event must be instantiated. in range does not generate pitches.

CHAPTER 1. THE PELOG LANGUAGE

15

range must be between two fixed points. This may be a named range or two
instantiated events. It can not be defined as an roaming interval.

range is
+events range is ?range
range is(+events, ?range)
Succeeds if the range of all events up to and including the current event is exactly
equal to range. The events must be instantiated.
range can be defined in any of the ways defined in the introduction to the range
library.

range within
+events range within +range
range within(+events, +range)
Succeeds if all events up to and including the current event are within range
range. The events and the range must be instantiated.
range can be defined in any of the ways defined in the introduction to the range
library.

1.5.8

Scale

A collection of pitches. In this context, the words scale and mode are used
interchangeably.
In Pelog, scales are specified in the form tonic~pitchset, where tonic is the starting pitch of the scale, and pitchset the name of a set of pitches.
tonic can be any pitch name. See the pitch library (page 13) for more information.
pitchset can be any pitch set name defined in a scale def clause.
The built-in pitch set definitions are given in Table 1.7.
The Pelog rule set programmer can add more scale definitions by adding scale def
fact predicates to the rule set file. scale def predicates have the form:
scale def(name, pitch list, key interval).
name is the name of the scale.
pitch list is a list of pitch names that make up the scale. This list should be provided
with c as tonic. The scale library will do the work of transposing the scale to
tonics other than c.
key interval is the interval between the scales tonic and the tonic of the major key signature that this scale should be written in. For example, the tonic of the dorian
mode is on the second degree of the major scale, so the key interval value is
maj~2.

CHAPTER 1. THE PELOG LANGUAGE

major
harmonic-minor
natural-minor
melodic-minor
dorian
hypo-dorian
phrygian
hypo-phyrgian
lydian
hypo-lydian
mixolydian
hypo-mixolydian
aeolian
hypo-aeolian
locrian
ionian
hypo-ionian
chromatic (-sharps, -flats)
whole-tone (-sharps, -flats)
gypsy
pentatonic (-23, -32)
octatonic (-sharps, -flats)
half-whole (-sharps, -flats)
whole-half (-sharps, -flats)
Table 1.7: Built-in pitch sets

16

CHAPTER 1. THE PELOG LANGUAGE

17

The built-in scale defs are listed below.


scale
scale
scale
scale

d e f ( major , [ c , d , e , f , g , a , b ] , per 1 ) : ! .
d e f ( h a r m o n i cm i n o r , [ c , d , e & , f , g , a & , b ] , maj 6 ) : ! .
d e f ( n a t u r a lm i n o r , [ c , d , e & , f , g , a & , b & ] , maj 6 ) : ! .
d e f ( m e l o d i cm i n o r , [ c , d , e & , f , g , a & , a , b & , b ] , maj 6 ) : ! .

scale
scale
scale
scale
scale
scale
scale
scale
scale
scale
scale
scale
scale

d e f ( d o r i a n , [ c , d , e & , f , g , a , b & , b ] , maj 2 ) : ! .


d e f ( hypod o r i a n , X , Y ) : s c a l e ( d o r i a n , X , Y ) , ! .
d e f ( p h r y g i a n , [ c , d & , e & , f , g , a & , b & ] , maj 3 ) : ! .
d e f ( hypop h r y g i a n , X , Y ) : s c a l e ( p h r y g i a n , X , Y ) , ! .
d e f ( l y d i a n , [ c , d , e , f #, g , a , b ] , p e r 4 ) : ! .
d e f ( hypol y d i a n , X , Y ) : s c a l e ( l y d i a n , X , Y ) , ! .
d e f ( m i x o l y di a n , [ c , d , e , f , g , a , b &, b ] , per 5 ) : ! .
d e f ( hypom i x o l y d i a n , X , Y ) : s c a l e ( m i x o l y d i a n , X , Y ) , ! .
d e f ( a e o l i a n , [ c , d , e & , f , g , a & , b & , a , b ] , maj 6 ) : ! .
d e f ( hypoa e o l i a n , X , Y ) : s c a l e ( a e o l i a n , X , Y ) , ! .
d e f ( l o c r i a n , [ c , d & , e & , f , g & , a & , b & ] , maj 7 ) : ! .
d e f ( i o n i a n , X , Y) : s c a l e ( major , X , Y ) , ! .
d e f ( hypoi o n i a n , X , Y ) : s c a l e ( i o n i a n , X , Y ) , ! .

s c a l e d e f ( c h r o m a t i c , [ c , c #, d , d #, e , f , f #, g , g #, a , a #, b ] , none ) : ! .
s c a l e d e f ( c h r o m a t i cs h a r p s , X , Y ) : s c a l e ( c h r o m a t i c , X , Y ) , ! .
s c a l e d e f ( c h r o m a t i c f l a t s , [ c , d & , d , e & , e , f , g & , g , a & , a , b & , b ] ,
none ) : ! .
s c a l e d e f ( w h o l et o n e , [ c , d , e , f #, g #, a # ] , none ) : ! .
s c a l e d e f ( w h o l et o n es h a r p s , X , Y ) : s c a l e ( w h o l et o n e , X , Y ) , ! .
s c a l e d e f ( w h o l et o n e f l a t s , [ c , d , e , g & , a & , b & ] , none ) : ! .
s c a l e d e f ( g y p s y , [ c , d , e & , f #, g , a , a & , b ] , u n i s ) : ! .
scale def ( pentatonic , [ c , d , f , g , a ] , unis ) : !.
s c a l e d e f ( p e n t a t o n i ca , X , Y ) : s c a l e d e f ( p e n t a t o n i c , X , Y ) , ! .
s c a l e d e f ( p e n t a t o n i cb , [ c , d , e , g , a ] , u n i s ) : ! .
s c a l e d e f ( o c t a t o n i c , [ c , c #, d #, e , f #, g , a , a # ] , none ) : ! .
s c a l e d e f ( o c t a t o n i cs h a r p s , X , Y ) : s c a l e d e f ( o c t a t o n i c , X , Y ) .
s c a l e d e f ( o c t a t o n i c f l a t s , [ c , d & , e & , e , g & , g , a , b & ] , none ) : ! .
scale
scale
scale
scale
scale

d e f ( h a l fw h o l e , X , Y ) : s c a l e d e f ( o c t a t o n i c , X , Y ) , ! .
d e f ( h a l fw h o l eX , Y , Z ) : s c a l e d e f ( o c t a t o n i cX , Y , Z ) , ! .
d e f ( w h o l eh a l f , [ c , d , d #, f , f #, g #, a , b ] , none ) : ! .
d e f ( w h o l eh a l fs h a r p s , X , Y ) : s c a l e d e f ( w h o l eh a l f , X , Y ) .
d e f ( w h o l eh a l f f l a t s , [ c , d , e & , f , g & , a & , a , b ] , none ) : ! .

s c a l e d e f ( b l u e s , [ c , d , e &, e , f , g &, g , a , b & ] , u n i s ) : ! .

The scale library also handles scale degrees. Scale degrees can be specified as an
integer where the tonic of the scale is equal to 1. Scale degrees can also be specified
by name. The list of built-in scale degree names, or special tones is given in Table
1.8. Note that not all of these special tones are available for use with all scales.

CHAPTER 1. THE PELOG LANGUAGE

18

final
ambitus
tenor
tonic
leading
supertonic
mediant
subdominant
dominant
submediant
subtonic
Table 1.8: Built-in special tones
The Pelog rule programmer can add more special tone definitions by adding
special tone def fact predicates to the Pelog rule set file. special tone def predicates
have the form:
special tone def(name, pitchset, pitch).
name is the name of the special tone.
pitchset specifies the scales that the special tone applies to.
pitch is the special tone in that scale with c as tonic.
The built-in special tone defs are listed below:
special
special
special
special
special
special
special
special
special
special
special
% Only
special
special
special
special
special
special
special
special
special

tone
tone
tone
tone
tone
tone
tone
tone
tone
tone
tone

def ( final ,
, c ) : !.
d e f ( ambitus , X , Y) : s p e c i a l t o n e d e f ( t o n i c , X , Y) .
d e f ( t e n o r , hypo , e ) : ! .
def ( tenor ,
, g) : !.
d e f ( t o n i c , hypo , g ) : ! .
def ( tonic ,
, c ) : !.
d e f ( leading , phrygian , b &) : !.
d e f ( l e a d i n g , hypol y d i a n , f # ) : ! .
d e f ( l e a d i n g , hypo , f ) : ! .
d e f ( l e a d i n g , n a t u r a lm i n o r , b & ) : ! .
def ( leading ,
, b) : !.

u s e d i n TONAL h a r m o n i c p r a c t i c e
t o n e d e f ( s u p e r t o n i c , major , d ) : ! .
t o n e d e f ( s u p e r t o n i c , m i n o r , d ) : ! .
t o n e d e f ( mediant , major , e ) : ! .
t o n e d e f ( m e d i a n t , m i n o r , e & ) : ! .
t o n e d e f ( subdominant , major , f ) : ! .
t o n e d e f ( s u b d o m i n a n t , m i n o r , f ) : ! .
t o n e d e f ( dominant , m a j o r , g ) : ! .
t o n e d e f ( dominant , m i n o r , g ) : ! .
t o n e d e f ( submediant , major , a ) : ! .

CHAPTER 1. THE PELOG LANGUAGE


special
special
special
special
special

tone
tone
tone
tone
tone

19

d e f ( s u b m e d i a n t , m e l o d i cm i n o r , a ) : ! .
d e f ( s u b m e d i a n t , m i n o r , a & ) : ! .
d e f ( s u b t o n i c , major , b ) : ! .
d e f ( s u b t o n i c , n a t u r a lm i n o r , b & ) : ! .
d e f ( s u b t o n i c , m i n o r , b ) : ! .

current scale
current scale ?scale
current scale(?scale)
Succeeds if emphscale is the current scale as defined by the scale tag in the input
file.

in scale
?event in scale
?event in scale +scale
in scale(?event)
in scale(?event, +scale)
Succeeds if event is in scale.
If scale is omitted, the scale specified by the scale tag in the input file is used.

scale degree
?event scale degree +degree
scale degree(?event, +degree)
Succeeds if event is the scale degree degree of the scale specified by the current
scale. (i.e. specified by the scale tag in the input file.)
degree can be
an integer, indicating the degreeth scale degree.
a special tone defined by special tone def
of the form raised~n, indicating the nth scale degree, raised by a semitone
of the form lowered~n, indicating the nth scale degree, lowered by a semitone.

An Example Application: Modal


Counterpoint
The first stage of this project will concern itself exclusively with modal counterpoint.
The system as a whole will be designed, however, to be extendible to tonal, twelvetone and other forms of counterpoint.

2.1

Sources

There is an avalanche textbooks on modal counterpoint. To simplify the process


of breaking down modal counterpoint into logical predicates, and to be somewhat
standard and correct, the sources selected for this project, as a whole, meet the
following criteria:
definitive and widely-accepted standard
precise definitions, avoiding concepts like feeling and affect
complete description of counterpoint, containing no simplifications
[33] Schottstaedt, William. (1984) Automatic Counterpoint. Current Directions
in Computer Music Research. Ed. Max V. Mathews and John R. Pierce. Cambridge, Mass.: MIT Press, 1989.
This article describes a system, similar to the present project, for solving
counterpoint exercises. While no implementation of the system is given,
it has a concise summary of Fux [11] as a list of simple rules [pp. 200202]. The majority of the rules below are derived directly from this
list. I have translated many of these rules from somewhat logically
ambiguous language (often quoted directly from Fux) into phrases that
better resemble Prolog clauses.
20

CHAPTER 2. MODAL COUNTERPOINT

21

[20] Krenek, Ernst. (1953) Modal Counterpoint in the Style of the Sixteenth Century.
New York: Boosey & Hawkes, 1959.
Krenek provides a very concise overview of counterpoint that is widely
regarded as the definitive modern standard. Modal Counterpoint is the
first volume in a series which also contains Tonal Counterpoint in the
Style of the Eighteenth Century and Studies in Counterpoint based on
Twelve-Tone Technique. Each volume is under thirty pages in length,
making it very easy to convert the text into individual rules.
[11] Fux, Johann Joseph. (1725) The Study of Counterpoint from Johann Joseph
Fuxs Gradus ad Parnassum, translated and edited by Alfred Mann. New York: W.
W. Norton & Co., 1943.
The central role of counterpoint in classical musical education is due in
large part to this influential book. It is used here to correct any loose
ends in the rules developed in Schottstaedt [33]. It is an odd, rambling,
book that is for the most part presented as a dialogue.
The serious difficulty of using Fux for the purposes of this project is
that the rules are not often fully distilled. For example, consider the
following excerpt (page 22), where the three possible motions are direct,
contrary and oblique:
First rule: From one perfect consonance to another perfect consonance one
must proceed in contrary or oblique motion.
Second rule: From a perfect consonance to an imperfect consonance one may
proceed in any of the three motions.
Third rule: From an imperfect consonance to a perfect consonance, one must
proceed in contrary or oblique motion.
Fourth rule: From one imperfect consonance to another imperfect consonance
one may proceed in any of the three motions.
By constructing these rules in a diagram, it becomes obvious that these
four rules can be combined into one.
perfect consonance imperfect consonance
perfect consonance
parallel
imperfect consonance parallel
The only situation not allowed is direct motion to a perfect consonance.
This, and numerous examples like it, occur frequently throughout the
text.
[37] Thakar, Markand. (1990) Counterpoint: Fundamentals of Music Making. New
Haven: Yale University Press, 1990.
Thakar takes a performers approach to counterpoint, which does not
always readily lend itself to this project. It does present, however, some
practical issues, such as the overall length of exercises, that are overlooked by the other texts.

CHAPTER 2. MODAL COUNTERPOINT

2.2
2.2.1

22

The rules
Species rules

Though it is not readily apparent from the overly-complex approach of Fux [11],
the species of counterpoint are not very different.
Note that Krenek [20] does away with the concept of species, presenting all
examples in Fuxs fifth species.
Here, cantus firmus refers to the lowest voice.
The only rules that are dependent on species are:
Every duration in the cantus firmus is a whole note
First species is note-against-note.
In first species, every duration in the upper voice, except the last, is equal to
the durations in the cantus firmus
Second species is note-against-two-notes.
In second species, every duration in the upper voice, except the last, is half
the length of the durations in the cantus firmus
Third species is note-against-four-notes.
In third species, every duration in the upper voice, except the last, is onequarter the length of the durations in the cantus firmus
Fourth species is note-against-note with ligatures.
In fourth species, every duration in the upper voice, except that of the last
note, is equal to the length of the durations in the cantus firmus. The start
times of the notes in the upper voice are shifted forward by half of the length
of the durations in the cantus firmus.
Fifth species is free counterpoint. If in fifth species, the durations of the
upper voice may be any of the following: { whole note, half note, quarter note,
eigth note } Selection of these durations must follow the rules listed below
Fifth-Species rhythmic rules
Fifth-species rhythmic rules are quite complex and involve a lot of inter-relations.
Periodical symmetry of rhythmic values.
More than two thirds of potentially accented beats are articulated
More than eight quarter notes in a row
Less than 75% of melody in half notes

CHAPTER 2. MODAL COUNTERPOINT

2.2.2

23

Static rules

Static rules are rules that involve only individual notes, not the relationships between notes.
Note class
These rules involve selecting notes from a particular subset of notes. These subsets
are typically modes or scales.
Fux [11] sticks to the convention of using only one mode across all parts. Krenek
[20] claims that the parts may each be in their own different modes as long as those
modes are related.
The standard modes and scales, and their inter-relationships will be included in
the system.
Note outside of mode or scale
In Dorian, Mixolydian and Aeolian modes, the leading tone is not raised by a
semi-tone
In Aeolian, if leading tone is approached stepwise from below, this sixth scale
degree is not raised
Range
Note outside the range of a per12
Note outside the range of a per8
Note on outer edge of range
Chronological position
Notes at certain points in time must meet certain criteria.
First pitch is not tonic/final or fifth-scale-degree pitch
If there are two voices, last pitch in upper voice is not the tonic/final
If more than two voices, last pitch in upper voice is not the tonic/final or
third-scale-degree
Last pitch in lower voice is not the tonic/final
Last note is not on downbeat
Last duration is less than a whole note
Penultimate pitch in the upper voice is not the leading-tone
Penultimate pitch in lowest voice is not the fourth or fifth-scale-degree
Penultimate pitch in lowest voice is not the second scale degree.

CHAPTER 2. MODAL COUNTERPOINT

24

Overall length
Fux [11] and Krenek [20] make no references to overall length, though Thakar [37]
recommends a length of 8 to 14 whole notes. Presumably some finite limit will need
to be defined or the system will continue to generate counterpoint infinitely (or at
least until running out of memory.)

2.2.3

Horizontal rules

Horizontal rules involve melodic relationships between notes occurring sequentially.


Referencing between different melodic lines is not necessary to apply these rules.
In this section about horizontal rules, interval refers to melodic interval.
Melodic intervals
Augmented intervals
Diminished intervals
Chromatic intervals
Intervals larger than per5, excluding min6, per8
Downward min6
Three notes or more notes outlining a tritone: Given three notes {a, b, c},
the interval a-c is a tritone and a-b and a-c are in the same direction
Interval of per8
Two skips that do not form a triad
Interval of maj6 or min6
Direction / Contour
A skip is a melodic interval greater than a maj2. A step is a melodic interval of
maj2 or min2.
Three consecutive skips in whole or half notes
Two consecutive skips in quarter notes
Two eighth notes with skip
Skip followed by motion in same direction
Skip followed by skip in the opposite direction
Skip followed by a skip
Skip preceded by motion in same direction
Step up followed by step down
Step down followed by step up

CHAPTER 2. MODAL COUNTERPOINT

25

Repetition
First pitch is repeated
Repeated pattern of four notes
Repeated pattern of three notes
Repeated pattern of two notes
Imitation
Better melodies include sequences of three or more notes that are repeated in pitchtransposed form.
Melody doesnt contain an imitative sequence of three notes
Melody doesnt contain an imitative sequence of four notes
Melody doesnt contain an imitative sequence of five notes
Melody doesnt contain an imitative sequence of six notes
Accent Pattern
Certain rules are dependent on the beat on which they occur. In standard fourbeats-per-measure counterpoint, beat 1 is the strongest, and beat 3 is slightly less
strong. Beats 2 and 4 are weak beats. Another definition of accent pattern is that
an accented beat exists wherever two or more note onsets occur at the same time.
If two quarter notes are on an accented beat, not preceded by at least one
quarter note, followed by two quarter notes or followed by a duration greater
than a half note
Upward skip from accented beat in quarter notes
In fourth species, upper voice note starts on down beat of bar
If on downbeat, repeated note
If on last beat in bar, repeated note
If on downbeat of bar, preceded by a skip
Voice leading
Leading tone not followed by tonic
Leading tone not approached by step

CHAPTER 2. MODAL COUNTERPOINT

26

High point
Highest note in melody occurs more than once over entire melody

2.3

Vertical rules

Vertical rules define the relationship between notes in different melodic lines at a
given moment in time. In this section, interval refers to harmonic interval.
Harmonic intervals
Interval is aug5
Interval is unis
If three-part or more, pitches do not form triad
If Lydian mode, interval is dim5
Interval is per4 or per5
Interval is dim5
Interval is greater than per8 (Krenek [20] says maj10 )
Chronological position / Accent pattern
Interval of last notes is not unis or per8
Interval of notes on accented beat is dissonant and duration is less than a half
note
Penultimate interval is not maj6 or min3
If note is on downbeat of bar, interval is unis
per8 or per5 appear less than four quarter notes apart
Voice crossing
Upper voice is below cantus firmus

2.3.1

Horizontal / Vertical rules

Parallel motion
Parallel motion occurs when the same harmonic interval occurs consecutively.
Parallel per5 or per8 : Harmonic interval is per5 or per8 and previous harmonic interval is the same.

CHAPTER 2. MODAL COUNTERPOINT

27

Parallel motion: Harmonic interval is equal to the preceding harmonic interval.


Rules which exhibit some of the properties of parallel motion are:
Harmonic interval of per8 followed be harmonic interval of per5
Harmonic interval is maj6 or min6 and next melodic intervals are in similar
motion
Parallel motion of any interval over more than three consecutive tones
Harmonic interval is maj10 or min10 and next harmonic interval is per8
Harmonic interval is per8 or unis and previous melodic interval is a skip
Harmonic interval is per5 or per8 and next melodic intervals are in similar
motion
Harmonic interval is unis and next melodic interval is a skip
Direct motion
Direct motion occurs when two voices traveling in the same direction melodically
land on an undesirable harmonic interval. Krenek [20] calls direct motion similar motion.
Direct motion to per5 or per8
Direct motion to dim5
Direct motion to per4 or per5
Direct motion to maj6 or min6
Direct motion to dim5
Scale degrees
Certain harmonic intervals are undesirable when the pitches involved are at certain
degrees in the scale.
Harmonic interval is unis or per8 and pitch is leading tone
Harmonic interval is unis or per8 and scale degree is third, fifth or sixth
Passing tone
Dissonance must be in a passing tone: Harmonic interval is { min2, maj2,
per4, min7, maj7 } (dissonant), notes preceding and following do not have a
melodic interval of min3 or maj3. Passing tones must occur on unaccented
beats.

CHAPTER 2. MODAL COUNTERPOINT

28

Cross-part imitation
Melodies that imitate the other voices in the counterpoint are preferable. At this
stage, the system will not be embedded with the logic of complex imitative forms
such as the fugue. However, it will reward counterpoints that contain embedded
imitative motifs. According to Krenek [20], the motifs can be transposed (pitchshifted), inverted (pitches-mirror imaged), retrograde (pitches in reverse order),
augmented (durations doubled) or diminished (durations halved) in order to be
considered imitative. This is a lot of different ways to modify motifs, and may
require considerable processing time to search for all possible variations.
Melody doesnt contain a cross-part imitative sequence of three notes
Melody doesnt contain a cross-part imitative sequence of four notes
Melody doesnt contain a cross-part imitative sequence of five notes
Melody doesnt contain a cross-part imitative sequence of six notes
Miscellaneous
All voices have skips at once

2.3.2

Extensions of Modal Counterpoint

Strict canon
In strictly imitative canon, the melodic lines are identical, with different start times.
The parts must obey all other counterpoint rules.

2.4
2.4.1

Implementation
modal counterpoint.pl

Code
: d i s c o n t i g u o u s
r u l e d e f /4,
r u l e /3.
r u l e o r d e r ( [ chronological ,
special cases for modes ,
melodic intervals ,
scale ,
range ,
tendency ,
harmonic intervals ,
avoid harmonic intervals ,
two leading tones ,
contour ,

CHAPTER 2. MODAL COUNTERPOINT

29

repeat first ,
repeat patterns ,
outline a tritone ] ) .
/
CHRONOLOGICAL POSITION
N ot e s a t s p e c i a l p o s i t i o n s , namely t h e f i r s t , p e n u l t i m a t e and
l a s t n o t e s must be c e r t a i n s c a l e d e g r e e s
/
r u l e ( First -Last- Penultimate , chronological , 0 ) :
$ e v e n t f i r s t e v e n t >
m c f i r s t p i t c h ( $event )
;
$ e v e n t l a s t e v e n t >
m c l a s t p i t c h ( $event )
;
$ e v e n t s p e n u l t i m a t e e v e n t >
mc penultimate pitch ( $event )
;
true .
% The f i r s t p i t c h d e p e n d s on w hi c h v o i c e i t i s i n
m c f i r s t p i t c h (E) :
E comment First pitch ,
( E p a r t t o p >
m c f i r s t p i t c h t o p (E)
;
E p a r t bottom >
m c f i r s t p i t c h b o t t o m (E ) ) .
% F i r s t p i t c h i n t o p v o i c e must be f i n a l ( t o n i c ) o r t e n o r ( dominant )
m c f i r s t p i t c h t o p (E) :
E final
;
E scale degree tenor .
% F i r s t p i t c h i n bottom v o i c e must be f i n a l ( t o n i c )
m c f i r s t p i t c h b o t t o m (E) :
E final .
% L a s t p i t c h d e p e n d s on w hi c h v o i c e i t i s i n
m c l a s t p i t c h (E) :
E comment Last pitch ,
( E p a r t t o p >
m c l a s t p i t c h t o p (E)
;
E p a r t bottom >
m c l a s t p i t c h b o t t o m (E ) ) .

CHAPTER 2. MODAL COUNTERPOINT

30

% L a s t p i t c h i n t o p v o i c e must be f i n a l ( t o n i c ) o r
% t h e t h i r d ( m e d i a n t ) i f t h e r e a r e more t h a n t h r e e v o i c e s
m c l a s t p i t c h t o p (E) :
E final
;
( num parts X , X > 2,
E scale degree 3).
% L a s t p i t c h i n bottom v o i c e must be f i n a l
m c l a s t p i t c h b o t t o m (E) :
E final .
% P e n u l t i m a t e p i t c h d e p e n d s on w hi c h v o i c e i t i s i n
m c p e n u l t i m a t e p i t c h (E) :
E comment Penultimate pitch ,
( E p a r t t o p >
m c p e n u l t i m a t e p i t c h t o p (E)
;
E p a r t bottom >
mc penultimate pitch bottom (E ) ) .
% P e n u l t i m a t e p i t c h i n t o p v o i c e must be l e a d i n g t o n e o r
% f o u r t h d e g r e e ( s u b d o m i n a n t ) i f t h e r e a r e more t h a n two v o i c e s
m c p e n u l t i m a t e p i t c h t o p (E) :
E leading tone
;
E scale degree 2
;
( num parts X , X > 2,
E scale degree 4).
% P e n u l t i m a t e p i t c h i n bottom v o i c e must be 2 nd , 4 t h o r 5 t h s c a l e d e g r e e
mc penultimate pitch bottom (E) :
E scale degree 2
;
E scale degree 4
;
E scale degree 5.
/
TENDENCIES
/
r u l e ( leading tone leads to tonic , tendency , 0 ) :
$ e v e n t l e a d i n g t o n e >
$next t o n i c
;
true .

CHAPTER 2. MODAL COUNTERPOINT

31

SPECIAL CASES FOR MODES


C e r t a i n modes hav e r u l e s o f t h e i r own
/
r u l e ( Aeolian , special cases for modes , 0 ) :
c u r r e n t s c a l e a e o l i a n >
m c a e o l i a n s p e c i a l ( $event , $prev1 )
;
true .
% I f l e a d i n g t o n e i s a p p r o a c h e d from b e l o w , i t must be r a i s e d .
m c a e o l i a n s p e c i a l (E , P) :
E leading tone ,
not P s c a l e d e g r e e 6 ,
((P s c a l e d e g r e e r a i s e d 6,
P comment Aeolian raised 6 th )
;
true ) .

/
MELODIC INTERVALS
/
% Good i n t e r v a l s
r u l e ( Good intervals , melodic intervals , 1 ) :
$prev1 = s t a r t ;
( member ( I , [ 2 , 3 , 4 , 5 ] ) ,
member ( IC , [ min , maj , p e r ] ) ,
$ p r e v 1 : $ e v e n t a i n t e r v a l IC I ) .
r u l e ( Repeated note , melodic intervals , 1 ) :
$prev1 = s t a r t ;
$prev1 : $event a i n t e r v a l unis .
% intervals
r u l e ( Less good intervals , melodic intervals , 1 0 ) :
$prev1 = s t a r t ;
( $ p r e v 1 : $ e v e n t a i n t e r v a l min 6 c o n t o u r up
; $prev1 : $event a i n t e r v a l per 8).

/
IN SCALE
/
% P i t c h must be a member o f t h e c u r r e n t s c a l e
r u l e ( In scale , scale , 0 ) :
$event s c a l e .

CHAPTER 2. MODAL COUNTERPOINT

32

/
HARMONIC INTERVALS
N ot e s a t s p e c i a l p o s i t i o n s , namely t h e f i r s t , p e n u l t i m a t e and
l a s t n o t e s must be c e r t a i n s c a l e d e g r e e s
/
r u l e ( Good intervals , harmonic intervals , 0 ) :
$vert = []
;
( member ( I , [ 3 , 6 , 8 , 1 0 , 1 3 , 1 5 ] ) ,
member ( IC , [ min , maj , p e r ] ) ,
$ v e r t : $ e v e n t a i n t e r v a l IC I ) .
r u l e ( Diminished 5 th , harmonic intervals , 0 ) :
$vert = []
;
not c u r r e n t s c a l e l y d i a n ,
$ v e r t : $ e v e n t c i n t e r v a l dim 5 .
r u l e ( Perfect intervals , harmonic intervals , 1 0 ) :
member ( I , [ 4 , 5 ] ) ,
$vert : $event c i n t e r v a l per I .
r u l e ( Unison , harmonic intervals , 2 0 ) :
$vert : $event a i n t e r v a l unis .

/
AVOID CERTAIN HARMONIC INTERVALS
/
r u l e ( Penultimate maj6 or min3 , avoid harmonic intervals , 0 ) :
$ e v e n t p e n u l t i m a t e e v e n t >
( not ( $ v e r t : $ e v e n t c i n t e r v a l maj 6
; $ v e r t : $ e v e n t c i n t e r v a l min 3 ) )
;
true .
r u l e ( Avoid , two leading tones , 0 ) :
not ( $ v e r t l e a d i n g t o n e ,
$event leading tone ) .

/
DIRECTION / CONTOUR
/
r u l e ( Step - step , contour , 1 ) :
$prev2 = s t a r t ;

CHAPTER 2. MODAL COUNTERPOINT

33

( $prev2 : $prev1 a i n t e r v a l step ,


$prev1 : $event a i n t e r v a l step ,
$ p r e v 2 comment Step - step ) .
r u l e ( X - skip : same direction , contour , 1 ) :
$prev2 = s t a r t ;
( $prev2 : $prev1 contour X,
$prev1 : $event a i n t e r v a l skip contour X,
$ p r e v 2 comment X - skip : same direction ) .
r u l e ( Skip - X: opposite direction , contour , 3 ) :
$prev2 = s t a r t ;
( $prev2 : $prev1 a i n t e r v a l skip contour X,
$prev1 : $event contour Y,
$ p r e v 2 comment Skip - X: opposite direction ,
X \= Y ) .
r u l e ( Repeat - Step , contour , 3 ) :
$prev2 = s t a r t ;
( $prev2 : $prev1 contour s t a t i c ,
$prev1 : $event a i n t e r v a l step ,
$ p r e v 2 comment Repeat - Step ) .
/
OUTLINING A TRITONE
/
% Outlining a tritone
r u l e ( Ensure not , outline a tritone , 1 ) :
$prev2 = s t a r t ;
(( $prev2 : $prev1 contour X,
$ p r e v 1 : $ e v e n t c o n t o u r X) >
( not $ p r e v 2 : $ e v e n t a i n t e r v a l t r i ) ;
true ) .
% Two s k i p s t h a t form a t r i a d
r u l e ( Two skips that form a triad , contour , 5 ) :
( $prev2 : $prev1 a i n t e r v a l
3 contour X,
$prev1 : $event a i n t e r v a l
3 contour X ) , ! .

/
REPEAT FIRST NOTE
/
r u l e ( Avoid the repeat , repeat first , 0 ) :
$prev1 = s t a r t
;
not $ p r e v 1 : $ e v e n t a i n t e r v a l u n i s .
r u l e ( Let it repeat , repeat first , 1 0 ) :

CHAPTER 2. MODAL COUNTERPOINT

34

$prev1 : $event a i n t e r v a l unis .

/
REPEATING PATTERNS
/
r u l e ( Avoid two , repeat patterns , 0 ) :
not m c r e p e a t p a t t e r n ( $ p r e v 3 , $ p r e v 2 , $ p r e v 1 , $ e v e n t ) .
r u l e ( Avoid three , repeat patterns , 1 0 ) :
$ p r e v 3 comment repeated two-note sequence ,
not m c r e p e a t p a t t e r n ( $ p r e v 5 , $ p r e v 4 , $ p r e v 3 , $ p r e v 2 , $ p r e v 1 , $ e v e n t ) .

r u l e ( Avoid four , repeat patterns , 2 0 ) :


$ p r e v 5 comment repeated four-note sequence ,
not m c r e p e a t p a t t e r n ( $ p r e v 7 , $ p r e v 6 , $ p r e v 5 , $ p r e v 4 , $ p r e v 3 , $ p r e v 2 , $ p r e
m c r e p e a t p a t t e r n (A , B , A , B ) .
m c r e p e a t p a t t e r n (A , B , C , A , B , C ) .
m c r e p e a t p a t t e r n (A , B , C , D , A , B , C , D ) .

/
RANGE
/

% Each n o t e must s t a y w i t h i n i t s g i v e n p a r t s r a n g e a s s p e c i f i e d i n t h e i n p u t f i
r u l e ( In range , range , 0 ) :
$event part Part ,
r a n g e P a r t t a g Range ,
$ e v e n t i n r a n g e Range .

GUIDO music notation format

The GUIDO Music Notation Format [15] was developed as a joint project between
Holger H. Hoos at the Technical University of Darmstadt and Keith A. Hamel at
the University of British Columbia.
It was chosen as the input/output language of the Pelog system for the following
reasons:
It is a very concise and user-friendly language
It is simple to write a parser/grammar for it
There are free tools available for viewing and listening to GUIDO files
It is easily extendible to support features unique to the Pelog system
The Pelog system only supports a subset of the complete specification provided by
Hoos & Hamel. Therefore, while any input and output used by Pelog is in GUIDO
Format, not every file in GUIDO Format can be used by the Pelog system. In
particular, because Pelog is only concerned with (at least at this stage) counterpoint
and the interaction between monophonic voices, it does not support elements such
as:
multiple voices on one staff
accents and other expressive markings
strictly visual features such as beaming, staves and octava markings
This section describes all of the GUIDO language features supported by the Pelog
system1 . Using features beyond what is defined below is not recommended and may
have unpredictable results.
1
This is an adaptation of the complete specification available at http://www.informatik.tudarmstadt.de/AFS/GUIDO

35

CHAPTER 3. GUIDO MUSIC NOTATION FORMAT

3.1

36

Scores

The entire score must be enclosed in curly braces { }.

3.2

Parts

Each part, (i.e. a monophonic voice,) is enclosed in square brackets [ ]. For


scores with more than one part, the parts are separated by a comma ,.
{ [ c d e d c ], [ c e g g c] }

3.3

Events

Any note, rest or variable note is considered an event.


In general, no white space should be used within each event description.
The general syntax for representing notes is:
notename accidentals octave duration
For rests (events without sounding pitch):
duration

For variable notes (pitches that are left to be filled in by the interpreter):
duration

octave and duration can be omitted. If so, they are inferred from preceding
events. The default octave is 1, and the default duration is the quarter note.

3.4

Notename

There are different systems of notenames:


1. c d e f g a h/b (diatonic or german)
2. c cis d dis e f fis g gis a ais b (chromatic)
3. cd re mi fa sol la si/ti (solfege)

3.5

Accidentals

accidentals is one of the following:


# (sharp) raises note by one semi-tone
& (flat) lowers note by one semi-tone
## (double sharp) raises note by two semi-tones
&& (double flat) lowers note by two semi-tones

CHAPTER 3. GUIDO MUSIC NOTATION FORMAT

3.6

37

Octave

octave is an integer indicating the octave of the note, where a1 is 440Hz, and
middle c is c1.
Pelog supports octaves in the range [8, +8], though the need for such a large
range is questionable.
If octave is omitted from the note description, it is assumed to be identical to
the last octave specified.

3.7

Duration

duration is specified in one of the following forms:


1. * enum / denom dotting
2. * enum dotting
3. / denom dotting
where enum and denom are positive integers and dotting is either empty, . or ...
When either enum or denom are omitted, it is given a value of 1.
If the entire duration is omitted, it is assumed to be identical to the last duration
specified. The default duration is 1/4 (quarter note).

3.8

Tags

Tags provide additional information about the score. They are of the form:
\ i d <param l i s t >

The tags must occur within parts. (i.e. inside square brackets [ ].)
The tags supported by Pelog are listed below. Of these, only meter and key are
part of the standard GUIDO specification. The rest are Pelog-specific extensions
to the language.
Individual rule sets may also support proprietary tags to handle unique attributes of a given musical genre.

3.8.1

Key

\key<i>

sets the key signature to i sharps, if i is positive, or to |i| flats, if i is negative. For
i = 0 , the key signature contains no sharps or flats.
\key<s> indicates a key of s, where s is of the form n, n#, or n& for any
note name n. Uppercase and lowercase letters are used to denote major and minor
scales, respectively.

CHAPTER 3. GUIDO MUSIC NOTATION FORMAT

3.8.2

38

Scale

scale <tonicscale>

indicates a scale starting on tonic. tonic is of the form notename described above.
Table 3.1 contains a list of the built-in scale definitions. It is also possible to define
additional scales for a given rule set (see Scale library on page 15).
major
harmonic-minor
natural-minor
melodic-minor
dorian
hypo-dorian
phrygian
hypo-phrygian
lydian
hypo-lydian
mixolydian
hypo-mixolydian
aeolian
hypo-aeolian
locrian
ionian
hypo-ionian
chromatic (-sharps, -flats)
whole-tone (-sharps, -flats)
gypsy
pentatonic (-23, -32)
octatonic (-sharps, -flats)
half-whole (-sharps, -flats)
whole-half (-sharps, -flats)
Table 3.1: built-in scales
A note about key vs. scale: While scale explicitly specifies the set of
pitches allowable in the score, key only specifies the number of sharps or
flats in the key signature. The key and scale of a score are determined
automatically based on the following rules:
If an input score only specifies the key, then the scale defaults to
major with key as the tonic.
If an input score only specifies the scale, the key is chosen based on
rules defined by the given scale. (e.g. d dorian is in the key of c.)
If a score specifies both scale and key, both are transferred as-is to
the output score.

CHAPTER 3. GUIDO MUSIC NOTATION FORMAT

3.8.3

39

Range

\range<range>

selects the allowable range for the current part. This may be one of the built-in
ranges (soprano, alto, tenor, bass) or one of the custom ranges defined in the current
rule set.

3.8.4

Meter

\meter<s>

sets the time signature to s where s is of the form numerator / denominator .

3.8.5

Accent pattern

\accented beats<s>

sets the accent pattern to s where s is of the form a1 , a2 . . . an , where an are the
accented beats.
The duration of each beat is equal to the denominator of the time signature
specified by the meter tag. If accented beats is not specified, it defaults to the first
beat of the measure.
accented beats is useful for defining things such as French 3/4 where the accented beat is not the first beat of each measure.

3.8.6

Comment style

\comment style<s>

defines the style of comments in the output file where s is one of referenced, lyrics
or none.
There are three ways in which comments about the score can be presented in
the output file:
referenced: Each event is assigned a number using the text tag which will be
displayed under the note in GUIDO NoteViewer. The comments for each of
these notes is listed by event number at the end of the output file. Figure 3.1
shows how referenced comments are displayed in the Pelog-GUI.
lyrics: Each comment is displayed as a lyric underneath each event.
Note: this option is not currently very useful due to a bug in GUIDO NoteViewer which causes the lyrics to write on top of each other.
none: No comments are added to the output file.

CHAPTER 3. GUIDO MUSIC NOTATION FORMAT

Figure 3.1: Pelog-GUI with referenced comments

40

Tools
4.1

Selecting the tools

This project requires the following categories of development tools:


1. Prolog interpreter
2. GUI development system
3. A system connecting these two tools
The specific implementations of these tools must meet the following requirements:
Free for educational use
Runs on the chosen development platform (32-bit Microsoft Windows), with
support for porting to other platforms
As industry-standard as possible, to ensure maintainability and portability
Creates stand-alone distributions that do not require that the entire development system is installed on the users machine
Supports rapid application development (RAD) allowing edits without recompilation

4.1.1

Prolog interpreter

Chosen tool
SWI-Prolog [http://www.swi.psy.uva.nl/projects/SWI-Prolog/]

41

CHAPTER 4. TOOLS
SWI-Prolog was developed at the University of Amsterdam by Jan
Wielemaker. It is free for non-commercial use and runs on Windows
and UNIX platforms. It interprets standard Edinburgh-Prolog, with
large subsets of the ISO-standard, Quintus and SICStus libraries. All
the included modules are upward compatible to Quintus and SICStus,
and most other Quintus modules will run directly in SWI without modifications. It will create stand-alone Prolog applications and has an
extensive C language interface.
Other candidates
B-Prolog [http://www.cad.mse.kyutech.ac.jp/people/zhou/bprolog.html]
There is another freeware Prolog interpreter for Windows called BProlog developed by Neng-Fa Zhou at the Kyushu Institute in Japan.
It is a strong second-choice to SWI-Prolog in most respects. However,
it does not support libraries or garbage collection, nor does it claim to
support as many standards as SWI-Prolog.
BIN-Prolog [www.binnet.com]
Bin-Prolog was originally developed as a free, portable Prolog by Paul
Tarau at the University of Moncton. Since 1994, it is a commercial
product distributed by BinNet corporation in Texas. While the free
product is still available by FTP, it lacks some of the more advanced
features of SWI-Prolog, such as the creation of stand-alones. It is also
only available as a 16-bit Windows application, making interaction with
32-bit Windows applications difficult or impossible.
Quintus Prolog and Sicstus Prolog [www.sics.com]
Quintus and SICStus Prolog seem to be the most popular commercial
Prolog interpreters on the market. Quintus was recently acquired by
SICS and the two products appear to be gradually merging into one.
The advantages of these commercial systems over SWI-Prolog are many,
but few of them are significant. The commercial products have graphical
IDEs, and will create C or Java code from Prolog source. The interface
between these Prologs and other languages and systems is also stronger
than their freeware counterpoints.

4.1.2

GUI development system

Chosen tool Tcl/Tk [www.scriptics.com]


Tcl/Tk is a very efficient and increasingly popular language for the automation of simple tasks and the creation of graphical user interfaces.
In particular, Tcl/Tk makes it easy to create new graphical widgets by
combining the functionality of existing ones. Since it is an interpreted

42

CHAPTER 4. TOOLS

43

language, any changes made to the code are updated instantly. Not only
is it never necessary to recompile, it is often not necessary to restart execution of the Tcl code.
Ports of Tcl/Tk are available for virtually every major platform, including Windows, UNIX and Macintosh. The core functions are free
and open-sourced. Additional functionality, such as the generation of
stand-alone executables and object-oriented data structures, is included
as part of the commercial Tcl/Tk Pro package.
Other candidates
Java [www.java.sun.com]
Javas new GUI system, Swing, released with Java 1.2, makes it very
easy to create flexible and portable graphical user interfaces. While implementing these interfaces directly with code is quite labour-intensive,
there are a number of free tools available for graphically drawing interface designs. Presumably, however, such tools reduce the amount of
flexibility for future changes to the interface. As well, the fact that Java
is a byte-compiled language means that updating and debugging the
code is a slow and tedious process.
XPCE [http://www.swi.psy.uva.nl/projects/xpce/]
XPCE is a graphical user interface system developed by the authors of
SWI-Prolog. Since it is designed from the ground up for use with Prolog,
it is perhaps the most natural choice. It is available on the all of the
same platforms as SWI-Prolog. Unfortunately, XPCE is a commercial
product, currently costing 300 euros for a single-user academic license.
It is also extremely specialized and proprietary, designed only for the
Prolog domain.

4.1.3

Connecting the two

Connecting Tcl/Tk and Prolog is rather involved. A great deal of effort was spent
implementing a set of procedures and predicates for this purpose (see the appendix
on the Tcl/Tk-Prolog Connector System on page 234). While this work was enlightening and a good tutorial on using piping on a Windows system, that approach was
ultimately abandoned. The technique that won out was simply to use the command
line interface. The Tcl/Tk portion of the system starts the Prolog portion by calling
it with all its arguments on the MS-DOS command line. Its simple, easy to debug
and directly ports to UNIX. It doesnt provide any interactivity between Tcl/Tk
and Prolog, but as it turns out, this is not a major drawback. You can see this
mechanism implemented in the rules apply function in the Pelog-GUI code.

CHAPTER 4. TOOLS

4.2

44

Reflections after-the-fact

These reflections are based on my own casual observations during the development
of the Pelog system.

4.2.1

SWI-Prolog

SWI-Prolog has proven to be a very high quality tool.


Robust. It crashed only once during the development of Pelog, and that was
due to an unrelated disk access error. Most common errors such as memory overflows
due to infinite recursion are caught before crashing the operating system.
Standard.
SWI-Prologs high level of support for the Edinburgh Prolog
standard allowed the Pelog system to reuse code from a number of different sources.
For example, the binary tree structure that is so pivotal to the central Pelog data
structure (page 45) was borrowed from the Quintus Prolog library. It ran in SWIProlog with absolutely no modification. The standard also made it possible to take
advantage of the great number of tutorial texts on Edinburgh Prolog. As someone
who is relatively new to Prolog, it would have been far more difficult to complete
this project in a more proprietary language using only proprietary documentation.
(The author of SWI-Prolog, Jan Wielemaker, is currently implementing the recently
adopted ISO Prolog standard into SWI-Prolog.)

4.2.2

Tcl/Tk

Tcl/Tk was much less impressive.


Instability on the Windows platform. The current Windows implementation of Tcl/Tk 8.1 has a memory allocation bug that does not free memory and
system resources upon exit of the Tcl shell. During the development of the Pelog
GUI, it was typically only possible to restart the Tcl shell about four times before
the entire operating system would crash, taking all of my unsaved work with it.
It made the modify-and-test development cycle, for which scripting languages are
so revered, virtually useless. While the severity of this problem should be a high
priority, according to the Tcl/Tk FAQ file on the web, this problem has existed at
least since version 7.6. Since no application should require frequent rebooting of
the operating system, Tcl as it currently stands in highly inadequate for full-scale
commercial development for Windows.
Language issues.
Tcl does not scale well. Most Tcl programs, including
those provided as examples, rely on archaic language features such as global variables. While it is possible to divide a project into multiple source files, there is
no method to deal with name space problems. For this reason, the Tcl language
would impose a considerable amount of unnecessary manual housekeeping on part
of the programmer to go beyond the complexity of a simple program such as the
Pelog-GUI.

A Data Structure for Encoding


Incomplete Musical Fragments in
Prolog
5.1

Design notes

The data structure, hereafter referred to as incomplete score, was designed with the
following initiatives:
To allow the most common calculations, such as intervals and scale degrees,
to be straightforward and efficient
To access the horizontal and vertical relationships between events efficiently
To make the conversion to and from the GUIDO music notation language [15]
as elegant and trivial as possible
To allow portions of the score to be unspecified. These unspecified parts can
later be filled in by the solver.
Incomplete score uses A Generalized Model for Encoding Musical Data [32]
as its starting point. From their model comes the representation of events as Prolog
terms and the use of Brinkmans binomial pitch representation. Their text is the
only full length tutorial, to my knowledge, on using Prolog as a tool for musical
research and makes a strong case for the straightforward implementation of musical expert systems. However, their data structure design is based too strongly
on Brinkman [4]. In their implementation, every musical event is asserted into the
Prolog database. Any changes in the database must be explicitly retracted and
asserted. In most Prolog implementations, backtracking over assertions and retractions does not restore the database to its previous state. Schaffer and McGees
45

CHAPTER 5. DATA STRUCTURE

46

fundamental design choice therefore negates one of the principle features of Prolog,
namely backtracking to compute alternate solutions. Their approach is thus useful
only for analysis, and thus inadequate for generation. By contrast, the incomplete
score uses a binary tree structure to store the events, where the previous states of
variable elements can be restored through backtracking. This allows the same code
to function both as a verifier and generator of musical events.

5.2

A worked example

The most intuitive way to present the incomplete score data structure is through a
case study of how a given musical fragment is encoded. While encoding an incomplete score by hand has been useful for preliminary testing, the next phase of the
project will include a grammar/parser to convert between the internal representation and the GUIDO music notation format [15]. GUIDO has been chosen as the
input/output language for its brevity and ease of use: two things lacking in this
internal representation designed for efficient manipulation by the Prolog system.
Figure 5.1 shows an example musical fragment. Table 5.1 shows its corresponding data representation.

Figure 5.1: An example score

The incomplete score data structure consists of a tree of terms of the form:
event(index, part, pitch, duration, time, previous, vertical, next, comment,
penalty, other )
The tree structure itself is the binary tree implementation included with Quintus
Prolog [5].
Borrowing from object-oriented programming practice, users of the data structure are encouraged (though not forced) to unify with the fields of each event using
a series of predicates with names such as e pitch and e duration etc.
We will now examine each of these arguments one-by-one.

CHAPTER 5. DATA STRUCTURE

47

t r e e ( e v e n t ( 1 , 1 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 4 ) , time ( 0 , 3 0 0 0 ) , s t a r t ,
[], 2,
,
, )
e v e n t ( 2 , 1 , p i t c h ( 1 1 , 6 ) , d u r ( 1 / 4 ) , time ( 3 0 0 0 , 6 0 0 0 ) , 1 ,
[12, 9], 3,
,
, )
e v e n t ( 3 , 1 , p i t c h ( 9 , 5 ) , d u r ( 1 / 4 ) , time ( 6 0 0 0 , 9 0 0 0 ) , 2 ,
[12], 4,
,
, )
e v e n t ( 4 , 1 , p i t c h ( 5 , 3 ) , d u r ( 1 / 4 ) , time ( 9 0 0 0 , 1 2 0 0 0 ) , 3 ,
[10, 12], 5,
,
, )
e v e n t ( 5 , 1 , p i t c h ( 7 , 4 ) , d u r ( 1 / 8 ) , time ( 1 2 0 0 0 , 1 3 5 0 0 ) , 4 ,
[10], 6,
,
, )
e v e n t ( 6 , 1 , p i t c h ( 9 , 5 ) , d u r ( 1 / 8 ) , time ( 1 3 5 0 0 , 1 5 0 0 0 ) , 5 ,
[13, 10], 7,
,
, )
e v e n t ( 7 , 1 , p i t c h ( 1 1 , 6 ) , d u r ( 1 / 4 ) , time ( 1 5 0 0 0 , 1 8 0 0 0 ) , 6 ,
[13, 10], 8,
,
, )
e v e n t ( 8 , 1 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 2 ) , time ( 1 8 0 0 0 , 2 4 0 0 0 ) , 7 ,
[ 1 3 ] , end ,
,
, )
e v e n t ( 9 , 2 , p i t c h ( 4 , 2 ) , d u r ( 1 / 2 ) , time ( 0 , 6 0 0 0 ) , s t a r t ,
[1], 10,
,
, )
e v e n t ( 1 0 , 2 , p i t c h ( 2 , 1 ) , d u r ( 1 / 1 ) , time ( 6 0 0 0 , 1 8 0 0 0 ) , 9 ,
[3, 12], 11,
,
, )
e v e n t ( 1 1 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , time ( 1 8 0 0 0 , 2 4 0 0 0 ) , 1 0 ,
[ 8 , 1 3 ] , end ,
,
, )
e v e n t ( 1 2 , 3 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 1 ) , time ( 0 , 1 2 0 0 0 ) , s t a r t ,
[9, 1], 13,
,
, )
e v e n t ( 1 3 , 3 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 1 ) , time ( 1 2 0 0 0 , 2 4 0 0 0 ) , 1 2 ,
[ 5 , 1 0 ] , end ,
,
, )
)

Table 5.1: Internal representation of example score

CHAPTER 5. DATA STRUCTURE

5.2.1

48

Index

e index(?Event, ?Index)
Each event has a unique index reference number. The indexing of elements is
completely arbitrary, as long as all indexes are unique. All of the events are stored
in a binary tree [5] so they can be retrieved by their index in O(lgn) time.1
Figure 5.2 shows example indexes that are likely to occur if the score were
interpreted from GUIDO.

Figure 5.2: Index values of the example score

5.2.2

Part

e part(?Event, ?Part)
Part is an integer value indicating which melodic line, or voice, the event belongs
to.
In our example, the first staff, or soprano part, is part 1, the second staff is part
2 and the bottom staff, or cantus firmus is part 3.

5.2.3

Pitch

e pitch(?Event, ?Pitch)
Pitches are stored in a format that is loosely adapted from binomial pitch representation (BPR) [4]. BPR defines musical pitch using four key attributes: pitch
class, note class, continuous pitch class and continuous note class.
1

Originally, an array implementation with constant access time [24] was considered. However,
when changes are made to such an array, the old state is lost and can not be restored through
backtracking. OKeefe himself describes the array implementation as a logical hack that is not
suitable for most logic programming applications. His array implementation is therefore inadequate for the present problem, in which many different subgoals must be examined before arriving
at a globally optimal solution. Some Prolog implementations have array libraries implemented
externally in C which may work for the present purpose.

CHAPTER 5. DATA STRUCTURE

49

Pitch class is an integer value corresponding to the frequency of the note. One
pitch class degree represents one semi-tone. Pitch class is the standard representation in most electronic music applications, such as MIDI.
Note class is an integer value related to the lines and spaces on the staff. Each
note class degree corresponds to one of the white keys on keyboard instruments. The
historical notation format DARMS uses only note class for pitch representation.
By maintaining both representations, a distinction is made between notes with
the same sounding pitch but different note classes (eg. c# and d&). It also makes
diatonic intervals, which are dependent on note class, much easier to calculate. This
is an important factor when developing systems, such as the present project, with
knowledge of traditional modal and tonal practice.
While pitch class is confined to the range [0 ... 11] and repeats at every octave,
continuous pitch class includes the octave as a factor. The same relationship exists
between note class and continuous note class. Octave numbering follows the convention adopted by GUIDO that the octave containing middle C is numbered 0.
The octaves below are negative and the octaves above are positive.
The relationship between continuous and non-continuous classes is therefore
defined by the following equations:
continuous-pitch-class = pitch-class + 12 octave
continous-note-class = note-class + 7 octave
Each event in the data structure stores a pitch value in a term of the form:
pitch(continuous-pitch-class, continuous-note-class)
Note that only the continuous classes are explicitly stored in the data structure.
Continuous classes are used in calculating intervals, which comprises the majority
of the counterpoint rules in this project. The penalty of converting from continuous
to non-continuous classes, for example to determine if a particular note is a member
of a scale, is quite minimal (modulo arithmetic.) Therefore, it is moderately more
efficient to keep track of continuous classes rather than non-continuous classes with
an octave value.
Figure 5.3 shows the continuous pitch and note classes of all the events in the
example score.

5.2.4

Duration

e duration(?Event, ?Duration)
Every event stores duration information in a term of the following form:
dur(numerator / denominator)
The representation used here is fairly intuitive: durations are written as fractions
of the whole note. This is the same convention used in GUIDO.
Figure 5.4 shows the duration values in the given example.

CHAPTER 5. DATA STRUCTURE

Figure 5.3: Pitch and note class values in the example score

Figure 5.4: Duration values in the example score

50

CHAPTER 5. DATA STRUCTURE

5.2.5

51

Time

e time(?Event, ?Time)
Every event stores the absolute time of its onset and offset. Time is stored in a
term of the form:
time(start, end)
Absolute time is given as an integer value where 0 is the start of the score and
each whole note unit thereafter is a factor of some constant. By default, this constant is 12000, because it is divisible by most of the commonly occurring duration
denominators such as 2, 3, 4, 8, 9 and 16.
The time values of events can be derived automatically, when duration, previous
and next are non-variable, by using the set event time/2 predicate.
Figure 5.5 shows the time values in the example using a time precision constant
of 12000.

Figure 5.5: Time values in the example score

5.2.6

Previous and Next

e prev(?Event, ?Prev)
e next(?Event, ?Next)
The previous and next values define the horizontal relationships between notes.
The previous and next fields contain the index of the events preceding and following
a given event.
The previous field of the first event in each part contains the atom start. The
next field in the last event of each part contains the atom end.

5.2.7

Vertical

e vertical(?Event, ?Vertical)
The vertical field defines the vertical relationships between notes. Perhaps
counter-intuitively, the vertical field does not contain the indexes of all events occurring at the same time in other parts. Instead, the field contains directed vectors

CHAPTER 5. DATA STRUCTURE

Figure 5.6: Previous references in the example score

Figure 5.7: Next references in the example score

52

CHAPTER 5. DATA STRUCTURE

53

to other events that will require vertical relationship examination. The set of vectors in all events represents the minimum number of vectors required to specify all
vertical relationships. The predicate build verticals creates these vectors using the
following criteria:
Eliminate redundant vectors to improve efficiency. Vertical relationships are symmetric, therefore if the vector X Y is defined, the vector
Y X does not need to also be defined. This will prevent the same vertical
relationship from being tested twice.
No vector points to a previously unexamined event. This improves
efficiency by ensuring that vertical rules will never generate new results, but
will only verify that the results of horizontal rules meet vertical criteria.
The former criteria is simple enough to understand on its own. The explain
the latter, however, requires taking a diversion into the concept of time-paths. The
concept of time-path presented here is generally based on Schaffer & McGees [32]
time-spine. The time-path defines the order in which the events will be visited by
the rule-applying mechanism. For lack of a better ordering scheme, the time-path
is created by sorting all the events by their start times. (If experimentation reveals
a more efficient order, this could change.) In our example, the events will be visited
in the order shown in Figure 5.8.

Figure 5.8: The time-path of the example score

As stated above, the vertical vectors should never point to unvisited events.
Therefore, the vertical vectors are generated from a reversed time-path. The vertical
vectors in the example are given in Figure 5.9
Note that all vectors point either vertically or backwards.

5.2.8

Comments

e comment(?Event, ?Comment)

CHAPTER 5. DATA STRUCTURE

54

Figure 5.9: The vertical relationships of the example score

The comment field contains a list of the rules that were applied to the event.
This information is output as comments in the GUIDO code. This field allows
the system to function as a primitive musical expert system, providing feedback
revealing how pitches were chosen or penalties were applied.
This list is implemented as an improper list so that an arbitrary number of
comments can be added throughout execution.

5.2.9

Penalty

e penalty(?Event, ?Penalty)
The penalty field contains the sum of all penalties applied to the event. This, in
conjunction with the comment field, creates a simple musical expert system. The
user can discover which events were severely penalized and in the most need of
improvement.

5.2.10

Other

The other field is reserved for future use.

Code and Testing


6.1

The Pelog-GUI

6.1.1

pelog-gui.tcl

Code
#
# PELOGGUI
#
###############################################
# MAIN WINDOW
###############################################
# s t a r t g u i i n i t i a l i s e s t h e main e d i t i n g window
proc s t a r t g u i {} {
g l o b a l PG m ain fr am e menuCount t c l p l a t f o r m
t o p l e v e l . m ai n fr a m e
wm w i t h d r a w . m ai nf r a m e
s e t P a r e n t . m ai nf r a m e
# Menu f r a m e
s e t menuframe $ P a r e n t . menuframe
f r a m e $menuframe
pack $menuframe \
a n c h o r nw \
s i d e t o p \
expand 0 \
f i l l x
# Toolbar frame

55

CHAPTER 6. CODE AND TESTING

56

s e t toolbarframe $Parent . buttons


frame $ t o o l b a r f r a m e
pack $ t o o l b a r f r a m e \
a n c h o r nw \
s i d e t o p \
expand 0 \
f i l l x \
padx 5 \
pady 3
# Menu d e f i n i t i o n s
s e t m e n u f i l e $menuframe . f i l e
s e t Menu String ( $menufile ) {
{{ t e a r o f f } { no } { } }
{{command } { New } \
{command f i l e n e w a c c e l e r a t o r "Ctrl+N" u n d e r l i n e 0 } }
{{command } { Open . . . } \
{command f i l e o p e n a c c e l e r a t o r "Ctrl+O" u n d e r l i n e 0 } }
{{command } { Open URL . . . } \
{command f i l e o p e n u r l a c c e l e r a t o r "Ctrl+U" u n d e r l i n e 5 } }
{{command } { R e v e r t } \
{command f i l e r e v e r t a c c e l e r a t o r "Ctrl+R" u n d e r l i n e 0 } }
{{ s e p a r a t o r } { } { } }
{{command } { Save } \
{command f i l e s a v e a c c e l e r a t o r "Ctrl+S" u n d e r l i n e 0 } }
{{command } { Save As . . . } \
{command f i l e s a v e a s a c c e l e r a t o r "Ctrl+W" u n d e r l i n e 5 } }
{{ s e p a r a t o r } { } { } }
{{command } { E x i t } \
{command f i l e e x i t a c c e l e r a t o r "Alt+F4" u n d e r l i n e 1 } }
}
construct menu $menufile F i l e $Menu String ( $menufile )
s e t m e n u e d i t $menuframe . e d i t
s e t Menu String ( $menuedit ) {
{{ t e a r o f f } { no } { } }
{{command } { Cut } \
{command e d i t c u t a c c e l e r a t o r "Ctrl+X" u n d e r l i n e 2 } }
{{command } { Copy } \
{command e d i t c o p y a c c e l e r a t o r "Ctrl+C" u n d e r l i n e 0 } }
{{command } { P a s t e } \
{command e d i t p a s t e a c c e l e r a t o r "Ctrl+V" u n d e r l i n e 0 } }
{{command } { C l e a r } \
{command e d i t c l e a r a c c e l e r a t o r "Del" u n d e r l i n e 3 } }
}
construct menu $menuedit E dit $Menu String ( $menuedit )
s e t menuview $menuframe . v i e w
s e t M e n u S t r i n g ( $menuview ) {
{{ t e a r o f f } { no } { } }
{{command } { Look } \

CHAPTER 6. CODE AND TESTING

57

{command v i e w l o o k a c c e l e r a t o r "Ctrl+L" u n d e r l i n e 0 } }
{{command } { Hear } \
{command v i e w h e a r a c c e l e r a t o r "Ctrl+H" u n d e r l i n e 0 } }
}
c o n s t r u c t m e n u $menuview View $ M e n u S t r i n g ( $menuview )
s e t m e n u r u l e s $menuframe . r u l e s
s e t Menu String ( $menurules ) {
{{ t e a r o f f } { no } { } }
{{command } { S e l e c t r u l e s e t . . . } \
{command r u l e s b r o w s e a c c e l e r a t o r "Ctrl+R" u n d e r l i n e 0 } }
{{command } { Go ! } \
{command r u l e s a p p l y a c c e l e r a t o r "Ctrl+G" u n d e r l i n e 0 } }
}
construct menu $menurules Rules $Menu String ( $menurules )

# Toolbar button d e f i n i t i o n s
construct button $toolbarframe
" file_new " "New"
construct button $toolbarframe
" file_open " "Open"
construct button $toolbarframe
" file_save " "Save"
construct spacer $toolbarframe
construct button $toolbarframe
" edit_cut " "Cut"
construct button $toolbarframe
" edit_copy " "Copy"
construct button $toolbarframe
" edit_paste " " Paste "
construct spacer $toolbarframe
construct button $toolbarframe
" view_look " "Look"
construct button $toolbarframe
" view_hear " "Hear"
construct spacer $toolbarframe
construct button $toolbarframe
" rules_apply " "Go"
construct spacer $toolbarframe

. new " images / filenew .gif" \


. open " images / fileopen .gif" \
. s a v e " images / filesave .gif" \
. spacer0
. c u t " images / editcut .gif" \
. copy " images / editcopy .gif" \
. p a s t e " images / editpaste .gif" \
. spacer1
. l o o k " images / viewlook .gif" \
. h e a r " images / viewhear .gif" \
. spacer2
. go " images / rulesapply .gif" \
. spacer3

button $ t o o l b a r f r a m e . browse \
t e x t { S e l e c t r u l e s e t : } \
command { r u l e s b r o w s e } \
c u r s o r hand2 \
h e i g h t 1
pack $ t o o l b a r f r a m e . b r o w s e \
a n c h o r w \
s i d e l e f t \
expand 0 \
f i l l x

CHAPTER 6. CODE AND TESTING

construct spacer $toolbarframe . spacer4


t e x t $ t o o l b a r f r a m e . t e x t h e i g h t 1 padx 5
pack $ t o o l b a r f r a m e . t e x t \
a n c h o r w \
s i d e l e f t \
expand 1 \
f i l l x
# Main window s e t t i n g s
wm g e o m e t r y . m ai nfr a m e 6 4 0 x480
wm m i n s i z e . m ain fr a m e 3 2 0 2 4 0
# Text e d i t o r
c o n s t r u c t e d i t o r . m a i n fr a m e
# Ready t o go . . .
wm d e i c o n i f y . m ain f ram e
file new
update rule set
update
}
###############################################
# MENU CONSTRUCTION
###############################################
# c o n s t r u c t m e n u ad ds a new menu t o t h e menubar
p r o c c o n s t r u c t m e n u { Name l a b e l c m d l i s t } {
g l o b a l PG
menubutton $Name \
t e x t $ l a b e l \
u n d e r l i n e 0
i n c r PG ( menuCount ) ;
s e t newmenu $Name . m$ PG ( menuCount )
$Name c o n f i g u r e menu $newmenu
c a t c h " destroy $newmenu "
e v a l "menu $newmenu "
e v a l [ l i s t a d d i t e m s t o m e n u $newmenu $ c m d l i s t ]
pack $Name \
a n c h o r nw \
expand 0 \
i p a d x 4 \
s i d e l e f t
}
# a d d i t e m s t o m e n u ad ds i n d i v i d u a l i t e m s t o a g i v e n menu
# i n t h e menu b a r . T h i s p r o c e d u r e i s c a l l e d by c o n s t r u c t m e n u
p r o c a d d i t e m s t o m e n u { menubutton c m d L i s t } {

58

CHAPTER 6. CODE AND TESTING


global

59

PG ( menuCount )

f o r e a c h cmd $ c m d L i s t {
s w i t c h [ l i n d e x $cmd 0 ] {
" separator " {
s e t d o i t " $menubutton add separator [ lindex $cmd 2]"
eval $doit
}
" tearoff " {
i f { [ s t r i n g match [ l i n d e x $cmd 1 ] "no" ] } {
$menubutton c o n f i g u r e t e a r o f f no
}
}
" command " {
s e t d o i t " $menubutton add [ lindex $cmd 0] \
l a b e l { [ l i n d e x $cmd 1 ] } \
[ l i n d e x $cmd 2 ] "
eval $doit
}
" cascade " {
i n c r PG ( menuCount ) ;
s e t newmenu $menubutton . m$ PG ( menuCount )
s e t d o i t " $menubutton add cascade \
l a b e l { [ l i n d e x $cmd 1 ] } \
menu $newmenu"
eval $doit
menu $newmenu
a d d i t e m s t o m e n u $newmenu [ l i n d e x $cmd 2 ]
}
}
}
}

###############################################
# TOOLBAR BUTTON CONSTRUCTION
###############################################
# c r e a t e s an image b u t t o n
p r o c c o n s t r u c t b u t t o n { Name f i l e cmd h e l p m s g } {
s e t im [ image c r e a t e p h o t o f i l e $ f i l e ]
b u t t o n $Name \
image $im \
t e x t $ he lp m s g \
j u s t i f y center \
c u r s o r hand2 \
r e l i e f raised \
command "$cmd" \
padx 0 pady 0
pack $Name \

CHAPTER 6. CODE AND TESTING


a n c h o r nw \
s i d e l e f t \
expand 0 \
f i l l x
}
# c r e a t e s a spacer f o r the t o o l b a r
p r o c c o n s t r u c t s p a c e r { Name } {
l a b e l $Name t e x t " "
pack $Name \
a n c h o r nw \
s i d e l e f t \
expand 0 \
f i l l x
}
###############################################
# EDITOR CONSTRUCTION
###############################################
# c r e a t e s t h e t e x t e d i t o r and s c r o l l b a r
p r o c c o n s t r u c t e d i t o r { Name } {
f r a m e $Name . t e x t F r a m e
pack $Name . t e x t F r a m e \
a n c h o r sw \
expand 1 \
f i l l both \
s i d e bottom
f r a m e $Name . t e x t F r a m e . r i g h t
pack $Name . t e x t F r a m e . r i g h t \
a n c h o r s e \
expand 0 \
f i l l y \
s i d e r i g h t
s c r o l l b a r $Name . t e x t F r a m e . r i g h t . v e r t S c r o l l b a r \
command " $Name . textFrame .left.text yview " \
w i d t h 1 2
pack $Name . t e x t F r a m e . r i g h t . v e r t S c r o l l b a r \
a n c h o r c e n t e r \
expand 0 \
f i l l y \
s i d e r i g h t
f r a m e $Name . t e x t F r a m e . l e f t
pack $Name . t e x t F r a m e . l e f t \
a n c h o r c e n t e r \
expand 1 \
f i l l bo t h \
s i d e l e f t

60

CHAPTER 6. CODE AND TESTING

61

t e x t $Name . t e x t F r a m e . l e f t . t e x t \
y s c r o l l c o m m a n d " $Name . textFrame . right . vertScrollbar set" \
f o n t { { A n d a l e Mono } 9 { n o r m a l }}
pack $Name . t e x t F r a m e . l e f t . t e x t \
a n c h o r c e n t e r \
expand 1 \
f i l l bo t h \
s i d e l e f t
b i n d $Name . t e x t F r a m e . l e f t . t e x t < K e y P r e s s> m a k e d i r t y
update
}

###############################################
# SPLASH WINDOW
###############################################
# d i s p l a y s t h e s p l a s h window
proc sp lash {} {
wm w i t h d r a w .
toplevel . splash
f r a m e . s p l a s h . m ain frame
pack . s p l a s h . m ainfra m e
wm t i t l e . s p l a s h { }
wm r e s i z a b l e . s p l a s h 0 0
focus force . splash
b u t t o n . s p l a s h . mai nf ra m e . main \
image [ image c r e a t e p h o t o f i l e " images / splash .gif" ] \
b o r d e r w i d t h 0 \
command s p l a s h b u t t o n
pack . s p l a s h . m a inf ra m e . main
update
}
# r e m o v e s t h e s p l a s h window
proc splash button {} {
wm w i t h d r a w . s p l a s h
start gui
}
###############################################
# FILE FUNCTIONS
###############################################

CHAPTER 6. CODE AND TESTING


# c l e a r s t h e t e x t e d i t o r and f i l e n a m e
proc f i l e n e w {} {
g l o b a l PG
i f { $ PG ( d i r t y ) == 1} {
s e t outcome l o s e c h a n g e s
i f { $outcome == "no" } { r e t u r n }
}
s e t PG ( d i r t y ) 0
. m a inf ram e . t e x t F r a m e . l e f t . t e x t d e l e t e 1 . 0 end
s e t PG ( f i l e ) ""
update titlebar
update
}
# loads a f i l e i n t o the e d i t o r
proc f i l e o p e n {} {
g l o b a l PG
file new
set

PG ( f i l e ) [ t k g e t O p e n F i l e \
d e f a u l t e x t e n s i o n . gmn \
f i l e t y p e s { { {GUIDO Music N o t a t i o n } { . gmn } } } \
p a r e n t . m a i n fr a m e \
t i t l e { Open . . . } ]

i f { $ PG ( f i l e ) == "" } { r e t u r n }
i f { ! [ f i l e r e a d a b l e $ PG ( f i l e ) ] } {
s h o w e r r o r "File \[ $_PG(file )\] is not readable ."
return
}
i f { [ c a t c h "open $_PG(file ) r" f d ] } {
s h o w e r r o r " Error while opening $_PG(file ): \[ $fd \]"
return
}
. m ain fr am e . t e x t F r a m e . l e f t . t e x t i n s e r t end [ r e a d $ f d ]
c l o s e $fd
update titlebar
update
}
# o p e n s t h e c u r r e n t f i l e from d i s k i n t o t h e e d i t o r
proc f i l e r e v e r t {} {
g l o b a l PG

62

CHAPTER 6. CODE AND TESTING

63

i f { $ PG ( d i r t y ) == 1} {
s e t outcome l o s e c h a n g e s
i f { $outcome == "no" } { r e t u r n }
}
s e t PG ( d i r t y ) 0
. m a inf ram e . t e x t F r a m e . l e f t . t e x t d e l e t e 1 . 0 end
i f { [ c a t c h "open $_PG(file ) r" f d ] } {
s h o w e r r o r " Error while opening $_PG(file ): \[ $fd \]"
return
}
. m ain fr am e . t e x t F r a m e . l e f t . t e x t i n s e r t end [ r e a d $ f d ]
c l o s e $fd
}
# saves the curre nt f i l e .
proc f i l e s a v e {} {
g l o b a l PG

I f no f i l e n a m e e x i s t s , prompts f o r one

i f { $ PG ( d i r t y ) == 0} { r e t u r n }
i f { $ PG ( f i l e ) == "" } { f i l e s a v e a s }
i f { [ f i l e e x i s t s $ PG ( f i l e ) ] } {
i f { ! [ f i l e w r i t a b l e $ PG ( f i l e ) ] } {
s h o w e r r o r "File \[ $PG(file )\] is not writable ."
return
}
}
i f { [ c a t c h "open $_PG(file ) w" f d ] } {
s h o w e r r o r " Error writing to $_PG(file ): \[ $fd \]"
return
}
p u t s $ f d "[. mainframe . textFrame .left.text get 1.0 end ]"
c l o s e $fd
set

PG ( d i r t y ) 0

}
# Prompts f o r a new f i l e n a m e and s a v e s t o d i s k
proc f i l e s a v e a s {} {
g l o b a l PG
set

PG ( f i l e ) [ t k g e t S a v e F i l e \
d e f a u l t e x t e n s i o n . gmn \
f i l e t y p e s { { {GUIDO Music N o t a t i o n } { . gmn } } } \
p a r e n t . m ai n fr am e \
t i t l e { Save As . . . } \
i n i t i a l f i l e $ PG ( f i l e ) ]

CHAPTER 6. CODE AND TESTING

64

file save
update titlebar
update
}
# c l o s e s t h e P e l o g GUI
proc f i l e e x i t {} {
exit
}
# I f the t e x t e d i t o r i s d i r t y , ensure that the user i s i n t e n d s to l o s e
# changes
proc lose changes {} {
tk messageBox \
d e f a u l t no \
t y p e y e s n o \
message { Any u n s a v e d c h a n g e s w i l l be l o s t . P r o c e e d anyway ? } \
p a r e n t . m a i n fr a m e \
t i t l e { L o s e c h a n g e s ?}
}
###############################################
# EDITING ( CLIPBOARD ) FUNCTIONS
###############################################
proc e d i t c u t {} {
t k t e x t C u t . ma in fr a m e . t e x t F r a m e . l e f t . t e x t
}
proc e d i t c o p y {} {
t k t e x t C o p y . m ai nf r a m e . t e x t F r a m e . l e f t . t e x t
}
proc e d i t p a s t e {} {
t k t e x t P a s t e . m a i nf ra m e . t e x t F r a m e . l e f t . t e x t
}
proc e d i t c l e a r {} {
. m ai nf ram e . t e x t F r a m e . l e f t . t e x t d e l e t e s e l . f i r s t
}
###############################################
# VIEWING FUNCTIONS
###############################################
proc view look {} {
g l o b a l PG
save temp
e x e c command . com / c gmnview . e x e temp . gmn &

sel . last

CHAPTER 6. CODE AND TESTING

65

update
}
proc view hear {} {
g l o b a l PG
save temp
c a t c h "exec gmn2midi .exe temp.gmn"
update
c a t c h "exec mplayer .exe - play temp.mid &"
update
}
# s a v e s t h e c o n t e n t s o f t h e e d i t o r i n a t e m p o r a r y f i l e s o i t can
# be v i e w e d by an e x t e r n a l program
proc save temp {} {
s e t f i l e temp . gmn
i f {[ f i l e exists $file )]} {
i f {![ f i l e writable $file )]} {
s h o w e r r o r "File \[ $file \] is not writable ."
return
}
}
i f { [ c a t c h "open $file w" f d ] } {
s h o w e r r o r " Error writing to $file : \[ $fd \]"
return
}
p u t s $ f d "[. mainframe . textFrame .left.text get 1.0 end ]"
c l o s e $fd
}
###############################################
# RULES FUNCTIONS
###############################################
proc r u l e s a p p l y {} {
g l o b a l PG
i f { $ PG ( r u l e s ) == "" } {
s h o w e r r o r "You need to specify a rule set before you can apply rules ."
rules browse
}
s e t outcome [ t k m e s s a g e B o x \
d e f a u l t ok \
t y p e o k c a n c e l \
message " Apply $_PG( rules ) to the currently open buffer ?" \
p a r e n t . m ai n fr am e \
t i t l e { App l y r u l e s ? } ]

CHAPTER 6. CODE AND TESTING

66

i f { $outcome == " cancel " } { r e t u r n }


save temp
s e t o u t f i l e [ s t r i n g t r i m r i g h t $ PG ( f i l e ) . gmn ] o u t . gmn
c a t c h "exec plcon .exe -t [ main ]. -g go ( temp.gmn , $outfile , $_PG( rules ))."
i f { $ PG ( h a s s l a v e ) == "" } {
s e t PG ( r u n s l a v e ) [ i n t e r p c r e a t e r u n s l a v e ]
r u n s l a v e e v a l { l o a d { } Tk}
r u n s l a v e e v a l { s o u r c e p e l o gg u i . t c l }
runslave eval splash button
s e t PG ( h a s s l a v e ) y e s }
$ PG ( r u n s l a v e ) e v a l s e t PG ( f i l e ) $ o u t f i l e
$ PG ( r u n s l a v e ) e v a l f i l e r e v e r t
}
proc r u l e s b r o w s e {} {
g l o b a l PG
PG ( r u l e s ) [ t k g e t O p e n F i l e \
d e f a u l t e x t e n s i o n . p r s \
f i l e t y p e s {{{ Pelog r u l e s e t } { . prs }}} \
p a r e n t . m ai n fr am e \
t i t l e { S e l e c t a Pelog r u l e s e t . . . } \
i n i t i a l f i l e $ PG ( r u l e s ) ]
update rule set

set

}
###############################################
# HELPER FUNCTIONS
###############################################
# c a l l e d when a cha nge i s made i n t h e e d i t o r
proc make dirty {} {
g l o b a l PG
s e t PG ( d i r t y ) 1
}
# c a l l e d when t h e name o f t h e open f i l e c h a n g e s
proc u p d a t e t i t l e b a r {} {
g l o b a l PG
wm t i t l e . ma inf r am e " Pelog GUI - $_PG(file )"
update
}
# c a l l e d when t h e name o f t h e r u l e s e t c h a n g e s
proc u p d a t e r u l e s e t {} {
g l o b a l PG

CHAPTER 6. CODE AND TESTING

. m ain f ram e . b u t t o n s . t e x t d e l e t e 1 . 0 end


. m ain f ram e . b u t t o n s . t e x t i n s e r t end $ PG ( r u l e s )
update
}
p r o c s h o w e r r o r { message } {
tk messageBox \
t y p e ok \
message $message \
p a r e n t . m ai n fr am e \
i c o n e r r o r \
t i t l e { Error }
}
###############################################
# INIT FUNCTIONS
###############################################
f o r e a c h g l o b a l v a r [ i n f o g l o b a l s PG ] {
global $globalvar
c a t c h " unset $globalvar " dummy
}
f o r e a c h g l o b a l v a r [ i n f o g l o b a l s pg ] {
global $globalvar
c a t c h " unset $globalvar " dummy
}
set
set
set
set
set
set
set
set
set

PG ( menuCount ) 0 ;
PG ( f i l e ) ""
PG ( r u n s l a v e ) ""
PG ( f i l e ) ""
PG ( t e m p f i l e ) ""
PG ( f i l e k e y n a m e ) ""
PG ( d i r t y ) 0
PG ( r u l e s ) ""
PG ( h a s s l a v e ) ""

c a t c h " destroy . mainframe "


destroy . toplevel
splash

6.2
6.2.1

The Core Interpreter


pelog.pl

Code
/
PRELIMINARY MAIN MODULE

67

CHAPTER 6. CODE AND TESTING

68

M i c h a e l Droettboom
May 1 9 9 9
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( library /main.pl ) .
: e n s u r e l o a d e d ( core/main.pl ) .
: e n s u r e l o a d e d ( guido /main.pl ) .
: d i s c o n t i g u o u s
r u l e d e f /3,
r u l e /2,
g l o b a l d e f a u l t /2.
: m u l t i f i l e
g l o b a l d e f a u l t /2.

t e s t : go ( thakar -p176.gmn , thakar - p176out .gmn , rule-sets/ modal_counterpoint .


go ( I n F i l e , O u t F i l e , R u l e F i l e ) :
load rules ( RuleFile ) , ! ,
l o a d s c o r e ( I n F i l e , T , TimePath ) , ! ,
a l l s o l u t i o n s (T , TimePath ) ,
b e s t r e s u l t ( Penalty , Best ) ,
n l , n l , w r i t e ( SUCCEEDED ! ==========================> ) , n l ,
w r i t e ( Overall Penalty : ) , w r i t e ( P e n a l t y ) , n l , n l ,
p r i n t t r e e ( Best ) , ! ,
s a v e s c o r e ( O u t F i l e , T , TimePath ) .
p r i n t t r e e (T) :
t r e e t o l i s t (T , L i s t ) ,
f o r a l l ( member ( I t e m , L i s t ) , ( w r i t e ( I t e m ) , n l ) ) .

PRIVATE PREDICATES

/
a l l s o l u t i o n s (T , TimePath ) :
start penalty , ! ,
o r g a n i z e r u l e s ( Rules ) , nl ,
w r i t e ( Time Path : ) , w r i t e ( TimePath ) , n l , n l ,

CHAPTER 6. CODE AND TESTING

69

w r i t e ( Event tree as input : ) , n l ,


p r i n t t r e e (T ) , n l , n l ,
w r i t e ( Tags : ) ,
l i s t i n g ( global ),
a p p l y r u l e s (T , TimePath , R u l e s , p e n a l t y ( 0 , P ) ) ,
s t o r e r e s u l t (P , T ) .
apply rules ( , [ ] ,
, p e n a l t y (P , P ) ) .
a p p l y r u l e s (T , [ ( , I n d e x ) | TimePath ] , R u l e s , p e n a l t y ( P e n In , PenOut ) ) :
g e t l a b e l ( Index , T, E ) ,
e v e r t i c a l (E , V ) ,
a p p l y r u l e s 0 (T , E , V , R u l e s , p e n a l t y ( P e n I n , PenOut0 ) ) ,
block ( Index ,
a p p l y r u l e s (T , TimePath , R u l e s , p e n a l t y ( PenOut0 , PenOut ) ) ,
Y) >
(Y == f a i l > f a i l ; t r u e ) .
a p p l y r u l e s 0 (T , E , [ ] , R u l e s , P e n a l t y ) :
p a c k a g e p a r a m e t e r (T , E , [ ] , Param ) ,
a p p l y r u l e s 1 ( E , Param , R u l e s , P e n a l t y ) .
a p p l y r u l e s 0 (T , E , [ V e r t ] , R u l e s , P e n a l t y ) :
p a c k a g e p a r a m e t e r (T , E , V e r t , Param ) ,
a p p l y r u l e s 1 ( E , Param , R u l e s , P e n a l t y ) .
a p p l y r u l e s 0 (T , E , [ VertHead | V e r t T a i l ] , R u l e s , p e n a l t y ( P e n I n , PenOut ) ) :
p a c k a g e p a r a m e t e r (T , E , VertHead , Param ) ,
a p p l y r u l e s 1 ( E , Param , R u l e s , p e n a l t y ( P e n I n , PenOut0 ) ) ,
a p p l y r u l e s 0 (T , E , V e r t T a i l , R u l e s , p e n a l t y ( PenOut0 , PenOut ) ) .

apply rules1 ( ,
, [ ] , p e n a l t y (P , P ) ) .
a p p l y r u l e s 1 ( E , Param , [ R u l e C l a s s | R u l e T a i l ] , p e n a l t y ( P e n I n , PenOut ) ) :
g l o b a l s e t ( d i r e c t i o n , forward ) ,
a p p l y r u l e s 1 ( E , Param , R u l e T a i l , p e n a l t y ( P e n I n , PenOut0 ) ) ,
( e p i t c h (E , Pitch ) ,
n o n v a r ( P i t c h ) >
onc e ( ( member ( R u l e L e v e l , R u l e C l a s s ) ,
a p p l y r u l e c l a s s ( E , Param , R u l e L e v e l , p e n a l t y ( PenOut0 , Pe
;
( member ( R u l e L e v e l , R u l e C l a s s ) ,
a p p l y r u l e c l a s s ( E , Param , R u l e L e v e l , p e n a l t y ( PenOut0 , PenOut ) ) ) )
;
([[( ,
,
, FailTo ) | ] | ] = RuleClass ,
a p p l y r u l e c l a s s f a i l ( F a i l T o , Param ) ) .
a p p l y r u l e c l a s s ( E , Param , R u l e L e v e l , P e n a l t y ) :
( member ( ( PenValue , R u l e , C l a s s , F a i l T o ) , R u l e L e v e l ) ,
a p p l y r u l e ( E , Param , PenValue , C l a s s , R u l e , F a i l T o , P e n a l t y ) ) .
apply rule class fail (1, ) : !, fail .
apply rule class fail (11, ) : !, f a i l .
a p p l y r u l e c l a s s f a i l ( F a i l T o , Param ) :
global ( d i r e c t i o n , forward ) ,

CHAPTER 6. CODE AND TESTING

70

w r i t e ( Special Fail : ) , w r i t e ( F a i l T o ) ,
a r g ( F a i l T o , Param , X ) ,
e i n d e x (X , F a i l T o I n d e x ) ,
w r i t e ( Going back to --> ) , w r i t e ( F a i l T o I n d e x ) , n l ,
g l o b a l s e t ( d i r e c t i o n , backward ) ,
f a i l ( FailToIndex ).
a p p l y r u l e ( E , Param , P e n a l t y , C l a s s , R u l e ,
, p e n a l t y ( P e nI n , PenOut ) ) :
e i n d e x (E , Index ) ,
g e t r e p ( E , PName , Octave ) ,
( r u l e ( R u l e , C l a s s , Param) >
( a d d p e n a l t y ( P e n a l t y , P e n I n , PenOut ) ,
w r i t e ( Event ) , w r i t e ( I n d e x ) , w r i t e ( : ) ,
w r i t e (PName ) , w r i t e ( Octave ) ,
w r i t e ( --> ) ,
w r i t e ( C l a s s ) , w r i t e ( :: ) , w r i t e ( R u l e ) , n l ) ;
( w r i t e ( Event ) , w r i t e ( I n d e x ) , w r i t e ( : ) ,
w r i t e (PName ) , w r i t e ( Octave ) ,
w r i t e ( -/> ) , w r i t e ( C l a s s ) ,
w r i t e ( :: ) , w r i t e ( R u l e ) , n l , f a i l )
).
g e t r e p ( E , ~ , ) :
e p i t c h (E , P ) ,
v a r (P ) , ! .
g e t r e p ( E , PName , Octave ) :
p i t c h n a m e o c t a v e ( E , PName , Octave ) , ! .

6.2.2

main.pl

Code
%%%%%%%%%%%%%%%%%%%%%%%
% CORE MAIN MODULE
%%%%%%%%%%%%%%%%%%%%%%%
% Simply ens ure s that a l l the Pelog core l i b r a r i e s are loaded
:
:
:
:
:
:

ensure
ensure
ensure
ensure
ensure
ensure

6.2.3

l o a d e d ( b imath ) .
l o a d e d ( message ) .
loaded ( preprocessor ).
l o a d e d ( r u l ed e f ) .
loaded ( score ).
loaded ( trees ).

bi-math.pl

Code
/
BIDIRECTIONAL MATH LIBRARY
G e n e r a t i v e math f u n c t i o n s

CHAPTER 6. CODE AND TESTING

71

Some o f t h e s e f u n c t i o n s a r e i n t e g e r v e r s i o n s o f t h e Q u i n t u s
f l o a t i n gp o i n t math l i b r a r y , math . p l
M i c h a e l Droettboom
May 1 9 9 9
/
/
OPPOSITE
o p p o s i t e ( ?X , ? Y)
m a i n t a i n s X = Y
/
o p p o s i t e (X , Y) :
n o n v a r (X ) ,
Y is X 1, !.
o p p o s i t e (X , Y) :
n o n v a r (Y ) ,
X is Y 1, !.
/
FACTOR
f a c t o r ( ?X , ? Y , ? Z , ? F )
m a i n t a i n s X = Z F + Y , where Y < Z , 8 < F < 8 .
/
, 0).
factor (0, 0,
f a c t o r (X , X , Z , 0 ) :
n o n v a r (Z ) ,
g e t i n t e g e r (X ) ,
X > 0,
X < Z.
f a c t o r (X , Y , Z , F ) :
nonvar (X ) ,
nonvar (Z ) ,
X \= 0,
Y1 i s X mod Z ,
F1 i s X / / Z ,
( Y1 < 0 >
(Y i s Y1 + Z ,
F i s F1 1 )
;
(Y i s Y1 , F i s F1 ) ) ,
F \= 0.
f a c t o r (X , Y , Z , F ) :
v a r (X ) ,
n o n v a r (Y ) ,
n o n v a r (Z ) ,

CHAPTER 6. CODE AND TESTING


Y < Z,
g e t i n t e g e r (F ) ,
F \= 0,
X i s Z F + Y.
g e t i n t e g e r ( I ) :
member ( I , [ 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 ,
6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12]).

Testing
1 ? [ b imath ] .
b imath c o m p i l e d , 0 . 0 0 s e c , 2 , 2 3 6 b y t e s .
Yes
2 ? o p p o s i t e ( 5 , X ) .
X = 5 ;
No
3 ? o p p o s i t e (Y , 3 ) .
Y = 3 ;
No
4 ? o p p o s i t e ( 1 2 , X ) .
X = 12 ;
No
5 ? o p p o s i t e (X , Y ) .
No
6 ? o p p o s i t e ( 5 , 3 ) .
No
7 ? f a c t o r ( 0 , X , Y , Z ) .
X = 0
Y = G297
Z = 0 ;
No
8 ? f a c t o r ( 1 , X , Y , Z ) .
No
9 ? f a c t o r ( 1 , X , Y , 0 ) .
No
10 ? f a c t o r (X , Y , 3 , 0 ) .

72

CHAPTER 6. CODE AND TESTING


X = 0
Y = 0 ;
X = 1
Y = 1 ;
X = 2
Y = 2 ;
No
11 ? f a c t o r ( 1 , Y , 3 , F ) .
Y = 1
F = 0 ;
No
12 ? f a c t o r ( 1 , Y , 3 , F ) .
Y = 4
F = 1 ;
No
13 ? f a c t o r (X , 3 , 5 , F ) .
X = 3
F = 0 ;
X = 8
F = 1 ;
X = 2
F = 1 ;
X = 13
F = 2 ;
X = 7
F = 2 ;
X = 18
F = 3 ;
X = 12
F = 3 ;
X = 23
F = 4 ;
X = 17
F = 4 ;

73

CHAPTER 6. CODE AND TESTING


X = 28
F = 5 ;
X = 22
F = 5 ;
X = 33
F = 6 ;
X = 27
F = 6 ;
X = 38
F = 7 ;
X = 32
F = 7 ;
X = 43
F = 8 ;
X = 37
F = 8 ;
X = 48
F = 9 ;
X = 42
F = 9 ;
X = 53
F = 10 ;
X = 47
F = 10 ;
X = 58
F = 11 ;
X = 52
F = 11 ;
X = 63
F = 12 ;
X = 57
F = 12 ;

6.2.4
Code

preprocessor.pl

74

CHAPTER 6. CODE AND TESTING

75

/
PREPROCESSOR FOR RULE FILES
M i c h a e l Droettboom
May 1 9 9 9
/
/
T h i s p r ep r o c e s s o r module i s d e s i g n e d t o make w r i t i n g r u l e s e t s a s c o n c i s e a s
p o s s i b l e . E v e r y r u l e i s s e n t a l a r g e number o f v a r i a b l e s by t h e r u l e
i n t e r p r e t e r . Rather than l i s t i n g a l l t h e s e parameters with e v e r y
r u l e , t h e p r e p r o c e s s o r p e r f o r m s some magic s o t h a t r u l e s o n l y need make
r e f e r e n c e t o metav a r i a b l e s t h a t a r e l i n k e d a t c o m p i l et i m e t o t h e
a c t u a l v a r i a b l e s . These metav a r i a b l e s by c o n v e n t i o n a l w a y s b e g i n
w i t h $ and a r e d e f i n e d i n p r e a t o m d e f / 2 .
For e x a m p l e , t h e f o l l o w i n g r u l e d e f i n i t i o n :
r u l e ( i n s c a l e , min ) :
$event s c a l e .
becomes :
r u l e d e f ( i n s c a l e , min ) % u s e d o n l y f o r i n f o r m a t i o n
r u l e ( i n s c a l e , a r g (A , B , C , D , E , F , G , H , I , J ) ) :
A scale .
s i n c e $ e v e n t i s d e f i n e d a s t h e f i r s t argument i n t h e term a r g ( ) .

/
TERM EXPANSION
T h i s i s a hook p r e d i c a t e i n t o t h e term r e a d i n g p r o c e s s t h a t
o c c u r s when c o n s u l t i n g a f i l e .
/
t e r m e x p a n s i o n ( ( r u l e (X , A , B ) : G o a l s ) ,
[ ( r u l e d e f (X , A , B , C ) ) , ( r u l e (X , A , A r g s ) : R e p l a c e d G o a l s ) ] ) :
f u n c t o r ( Args , a r g s , 1 2 ) ,
p r e r e p l a c e a t o m s ( Goals , ReplacedGoals , Args , 0 , C ) .

/
REPLACING METAVARIABLES
pre re p lac e at om s /3 searches through a l i s t of goals , r e p l a c i n g
a l l metav a r i a b l e s d e f i n e d i n p r e a t o m d e f / 2 w i t h t h e
a p p r o p r i a t e v a r i a b l e argument .
/

CHAPTER 6. CODE AND TESTING

76

p r e r e p l a c e a t o m s ( G o a l s , R e p l a c e d G o a l s , A r g s , L I n , LOut ) :
Goals = . . GoalList ,
p r e r e p l a c e a t o m s 1 ( G o a l L i s t , R e p l a c e d G o a l L i s t , A r g s , L I n , LOut ) ,
ReplacedGoals =. . ReplacedGoalList .
pre replace atoms1 ( [ ] , [ ] ,
, L, L).
p r e r e p l a c e a t o m s 1 ( [ X | I n R e s t ] , [ Y | OutRest ] , A r g s , L I n , LOut ) :
p r e a t o m (X , ArgNo ) ,
a r g ( ArgNo , A r g s , Y ) ,
( ArgNo > L I n > L0 = ArgNo ; L0 = L I n ) ,
p r e r e p l a c e a t o m s 1 ( I n R e s t , OutRest , A r g s , L0 , LOut ) .
p r e r e p l a c e a t o m s 1 ( [ X | I n R e s t ] , [ Y | OutRest ] , A r g s , L I n , LOut ) :
n o t a t o m i c (X ) ,
p r e r e p l a c e a t o m s (X , Y , A r g s , L I n , L0 ) ,
p r e r e p l a c e a t o m s 1 ( I n R e s t , OutRest , A r g s , L0 , LOut ) .
p r e r e p l a c e a t o m s 1 ( [ X | I n R e s t ] , [ X | OutRest ] , A r g s , L I n , LOut ) :
p r e r e p l a c e a t o m s 1 ( I n R e s t , OutRest , A r g s , L I n , LOut ) .
p r e a t o m (X , Y) :
n o n v a r (X ) ,
p r e a t o m d e f (X , Y ) .
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre
pre

atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom
atom

def ( $event , 1 ) .
d e f ( $e , 1 ) .
def ( $prev1 , 2 ) .
d e f ( $p1 , 2 ) .
def ( $prev2 , 3 ) .
d e f ( $p2 , 3 ) .
def ( $prev3 , 4 ) .
d e f ( $p3 , 4 ) .
def ( $prev4 , 5 ) .
d e f ( $p4 , 5 ) .
def ( $prev5 , 6 ) .
d e f ( $p5 , 6 ) .
def ( $prev6 , 7 ) .
d e f ( $p6 , 7 ) .
def ( $prev7 , 8 ) .
d e f ( $p7 , 8 ) .
def ( $prev8 , 9 ) .
d e f ( $p8 , 9 ) .
def ( $vert , 1 0 ) .
d e f ( $v , 1 0 ) .
def ( $events , 1 1 ) .
d e f ( $x , 1 1 ) .
def ( $next , 1 2 ) .

/
PACKAGING PARAMETERS
T h i s i s t h e o t h e r s i d e o f t h e p r e p r o c e s s o r . The r u l e i n t e r p r e t o r
uses t h i s p r e d i c a t e to package a l l of the n e c e s s a r y i n f o r m a t i o n
to send to the r u l e s .

CHAPTER 6. CODE AND TESTING

77

/
p a c k a g e p a r a m e t e r (T , E , V , Param ) :
f u n c t o r ( Param , a r g s , 1 2 ) ,
a r g ( 1 , Param , E ) ,
g e t p r e v e v e n t (T , E , EP1 ) ,
g e t p r e v e v e n t (T , EP1 , EP2 ) ,
g e t p r e v e v e n t (T , EP2 , EP3 ) ,
g e t p r e v e v e n t (T , EP3 , EP4 ) ,
g e t p r e v e v e n t (T , EP4 , EP5 ) ,
g e t p r e v e v e n t (T , EP5 , EP6 ) ,
g e t p r e v e v e n t (T , EP6 , EP7 ) ,
g e t p r e v e v e n t (T , EP7 , EP8 ) ,
a r g ( 2 , Param , EP1 ) ,
a r g ( 3 , Param , EP2 ) ,
a r g ( 4 , Param , EP3 ) ,
a r g ( 5 , Param , EP4 ) ,
a r g ( 6 , Param , EP5 ) ,
a r g ( 7 , Param , EP6 ) ,
a r g ( 8 , Param , EP7 ) ,
a r g ( 9 , Param , EP8 ) ,
(V \= [] >
( g e t l a b e l (V , T , V e r t ) ,
a r g ( 1 0 , Param , V e r t ) )
;
a r g ( 1 0 , Param , [ ] ) ) ,
a r g ( 1 1 , Param , ( T , E ) ) ,
g e t n e x t e v e n t (T , E , N ) ,
a r g ( 1 2 , Param , N ) .

Testing
2 ? [ rule-sets/ modal_counterpoint .pl ] .
r u l es e t s / m o d a l c o u n t e r p o i n t . p l c o m p i l e d , 0 . 1 7 s e c , 1 2 , 1 4 4 b y t e s .
Yes 3 ? l i s t i n g ( r u l e ) .

r u l e ( First -Last- Penultimate , c h r o n o l o g i c a l , a r g s (A , B , C , D , E , F ,


G, H, I , J , K, L)) :
( A first event
> m c f i r s t p i t c h (A)
; A last event
> m c l a s t p i t c h (A)
; K penultimate event
> m c p e n u l t i m a t e p i t c h (A)
; true
).
r u l e ( leading tone leads to tonic , t e n d e n c y , a r g s (A , B ,
C , D, E , F , G, H, I , J , K, L)) :
( B leading tone
> A t o n i c

CHAPTER 6. CODE AND TESTING

78

; true
).
r u l e ( Aeolian , special cases for modes , a r g s (A , B , C ,
D, E , F , G, H, I , J , K, L)) :
( c u r r e n t s c a l e M a e o l i a n
> m c a e o l i a n s p e c i a l (A , B)
; true
).
r u l e ( Good intervals , melodic intervals , a r g s (A , B , C ,
D, E , F , G, H, I , J , K, L)) :
( B=s t a r t
; member (M, [ 2 , 3 , 4 , 5 ] ) ,
member (N , [ min , maj , p e r ] ) ,
B : A a i n t e r v a l NM
).
r u l e ( Repeated note , melodic intervals , a r g s (A , B , C ,
D, E , F , G, H, I , J , K, L)) :
( B=s t a r t
; B:A a i n t e r v a l unis
).
r u l e ( Less good intervals , melodic intervals , a r g s (A ,
B, C , D, E , F , G, H, I , J , K, L)) :
( B=s t a r t
; B : A a i n t e r v a l min 6 c o n t o u r up
; B : A a i n t e r v a l p e r 8
).
r u l e ( In scale , s c a l e , a r g s (A , B , C , D , E , F , G , H , I , J ,
K, L)) :
A s c a l e . r u l e ( Good intervals , harmonic intervals , a r g s (A ,
B, C , D, E , F , G, H, I , J , K, L)) :
( J =[]
; member (M, [ 3 , 6 , 8 , 1 0 , 1 3 , 1 5 ] ) ,
member (N , [ min , maj , p e r ] ) ,
J : A a i n t e r v a l NM
).
r u l e ( Diminished 5 th , harmonic intervals , a r g s (A , B , C ,
D, E , F , G, H, I , J , K, L)) :
( J =[]
; n o t c u r r e n t s c a l e M l y d i a n ,
J : A c i n t e r v a l dim 5
).
r u l e ( Perfect intervals , harmonic intervals , a r g s (A , B ,
C , D, E , F , G, H, I , J , K, L)) :
member (M, [ 4 , 5 ] ) ,
J : A c i n t e r v a l p e r M. r u l e ( Unison , harmonic intervals ,
a r g s (A , B , C , D , E , F , G , H , I , J , K , L ) ) :
J : A a i n t e r v a l u n i s . r u l e ( Penultimate maj6 or min3 , avoid
h a r m o n i c i n t e r v a l s , args(A, B, C, D, E, F, G, H, I, J, K, L )) :( A penultimate event
> n o t ( (
( J : A c i n t e r v a l maj 6
; J : A c i n t e r v a l min 3
)
))
; true
).
r u l e ( Avoid , two leading tones , a r g s (A , B , C , D , E , F ,
G, H, I , J , K, L)) :
not ( (

CHAPTER 6. CODE AND TESTING

79

J leading tone ,
A leading tone
)).
r u l e ( Step - step , c o n t o u r , a r g s (A , B , C , D , E , F ,
G, H, I , J , K, L)) :
( C=s t a r t
; C:B a i n t e r v a l step ,
B:A a i n t e r v a l step ,
C commentStep - step
).
r u l e ( X - skip : same direction , c o n t o u r , a r g s (A , B , C , D ,
E , F , G, H, I , J , K, L)) :
( C=s t a r t
; C : B c o n t o u r M,
B : A a i n t e r v a l s k i p c o n t o u r M,
C commentX - skip : same direction
).
r u l e ( Skip - skip : opposite direction , c o n t o u r , a r g s (A ,
B, C , D, E , F , G, H, I , J , K, L)) :
( C=s t a r t
; C : B a i n t e r v a l s k i p c o n t o u r M,
B:A a i n t e r v a l s k i p contour N,
M\=N
).
r u l e ( Ensure not , outline a tritone , a r g s (A , B , C , D ,
E , F , G, H, I , J , K, L)) :
( C=s t a r t
; C : B c o n t o u r M,
B:A contour M
> n o t C : A a i n t e r v a l t r i N
; true
).
r u l e ( Two skips that form a triad , c o n t o u r , a r g s (A , B , C ,
D, E , F , G, H, I , J , K, L)) :
C : B a i n t e r v a l M 3 c o n t o u r N ,
B : A a i n t e r v a l O 3 c o n t o u r N , ! .
r u l e ( Avoid the repeat ,
repeat first , a r g s (A , B , C , D , E , F , G , H , I , J , K , L ) ) :
( B=s t a r t
; not B:A a i n t e r v a l u n i s
).
r u l e ( Let it repeat , repeat first , a r g s (A , B , C , D , E ,
F , G, H, I , J , K, L)) :
B : A a i n t e r v a l u n i s . r u l e ( Avoid two , repeat patterns ,
a r g s (A , B , C , D , E , F , G , H , I , J , K , L ) ) :
n o t m c r e p e a t p a t t e r n (D , C , B , A ) .
r u l e ( Avoid three ,
repeat patterns , a r g s (A , B , C , D , E , F , G , H , I , J , K , L ) ) :
not m c r e p e a t p a t t e r n (F , E , D, C , B , A ) .
r u l e ( Avoid four ,
repeat patterns , a r g s (A , B , C , D , E , F , G , H , I , J , K , L ) ) :
n o t m c r e p e a t p a t t e r n (H , G , F , E , D , C , B , A ) .
r u l e ( In
r a n g e , range , args(A, B, C, D, E, F, G, H, I, J, K, L )) :A p a r t M,
r a n g e M t a g N ,
A i n r a n g e N.
Yes 4 ? l i s t i n g ( r u l e d e f ) .
r u l e d e f ( First -Last- Penultimate , c h r o n o l o g i c a l , 0 ) .

CHAPTER 6. CODE AND TESTING

80

r u l e d e f ( leading tone leads to tonic , t e n d e n c y , 0 ) .


r u l e d e f ( Aeolian , special cases for modes , 0 ) .
r u l e d e f ( Good
i n t e r v a l s , m e l o d i c i n t e r v a l s , 1). rule_def ( R e p e a t e d n o t e ,
melodic intervals , 1 ) .
r u l e d e f ( Less good intervals , melodic
i n t e r v a l s , 10). rule_def ( I n s c a l e , scale , 0). rule_def (Good
i n t e r v a l s , h a r m o n i c i n t e r v a l s , 0). rule_def ( D i m i n i s h e d 5 t h ,
harmonic intervals , 0 ) .
r u l e d e f ( Perfect intervals , harmonic
i n t e r v a l s , 10). rule_def ( U n i s o n , h a r m o n i c i n t e r v a l s , 20).
r u l e d e f ( Penultimate maj6 or min3 , avoid harmonic intervals , 0 ) .
r u l e d e f ( Avoid , two leading tones , 0 ) .
r u l e d e f ( Step - step ,
contour , 1 ) .
r u l e d e f ( X - skip : same direction , c o n t o u r , 1 ) .
r u l e d e f ( Skip - skip : opposite direction , c o n t o u r , 3 ) .
r u l e d e f ( Ensure not , outline a tritone , 1 ) .
r u l e d e f ( Two skips
t h a t form a t r i a d , contour , 5). rule_def ( A v o i d t h e r e p e a t , r e p e a t
f i r s t , 0). rule_def ( L e t i t r e p e a t , r e p e a t f i r s t , 10).
r u l e d e f ( Avoid two , repeat patterns , 0 ) .
r u l e d e f ( Avoid three ,
repeat patterns , 1 0 ) .
r u l e d e f ( Avoid four , repeat patterns ,
2 0 ) . r u l e d e f ( In range , r a n g e , 0 ) .
Yes

This test runs the modal counterpoint.pl rule set through the preprocessor and
then lists the created rule and rule def predicates.

6.2.5

rule-def.pl

Code
/
RULE DEFINITION LIBRARY
P r e d i c a t e s f o r managing t h e r u l e d e f i n i t i o n d a t a b a s e .
M i c h a e l Droettboom
May 1 9 9 9
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( p r e p r o c e s s o r ) .

/
LOAD RULES
/
l o a d r u l e s (X) :
c o n s u l t (X ) .

CHAPTER 6. CODE AND TESTING

81

/
MANAGEMENT FUNCTIONS
/
r u l e p e n a l t y (R , P e n a l t y ) : r u l e d e f (R , P e n a l t y ) .
o r g a n i z e r u l e s ( RulePath ) :
r u l e o r d e r ( Order0 ) ,
r e v e r s e ( Order0 , Order ) ,
f i n d a l l ( L1 ,
( member (X , O r d e r ) ,
f i n d a l l ((S , N, X, FailTo ) ,
( r u l e d e f (N , X , P , F a i l T o ) ,
(P = min > S i s 1 ; S i s P ) ) ,
L),
s o r t ( L , L0 ) ,
o r g a n i z e r u l e s 0 ( L0 , L1 ) ) ,
RulePath ) ,
w r i t e ( Rule path : ) , w r i t e ( R u l e P a t h ) , n l .
organize rules0 ( [ ] , [ ] ) .
o r g a n i z e r u l e s 0 ( [ ( P , A , B ) | T ] , [ HO|TO] ) :
o r g a n i z e r u l e s 0 (P , [ ( P , A , B ) | T ] , HO , R e s t ) ,
o r g a n i z e r u l e s 0 ( R e s t , TO ) .
o r g a n i z e r u l e s 0 (P , [ ( P , A , B ) | T ] , [ ( P , A , B ) | TO ] , R e s t ) :
o r g a n i z e r u l e s 0 (P , T , TO , R e s t ) .
o r g a n i z e r u l e s 0 ( P0 , [ ( P1 , A , B ) | T ] , [ ] , [ ( P1 , A , B ) | T ] ) :
P0 \= P1 .
organize rules0 ( , [ ] , [ ] , [ ] ) .

Testing
2 ? go ( example .gmn , exampleout .gmn ,
rule-sets/ modal_counterpoint .pl ) .
r u l es e t s / m o d a l c o u n t e r p o i n t . p l
c o m p i l e d , 0 . 1 1 s e c , 1 2 , 1 4 4 b y t e s . P a r s e OK R u l e p a t h : [ [ [ ( 1 , E n s u r e
n o t , o u t l i n e a t r i t o n e ) ] ] , [ [ ( 0 , A v o i d two , r e p e a t p a t t e r n s ) ] , [ ( 1 0 ,
Avoid t h r e e , r e p e a t p a t t e r n s ) ] , [ ( 2 0 , Avoid f o u r , r e p e a t p a t t e r n s ) ] ] ,
[ [ ( 0 , Avoid the r e p e a t , r e p e a t f i r s t ) ] , [ ( 1 0 , Let i t r e p e a t , r e p e a t
f i r s t ) ] ] , [ [ ( 1 , S t e p s t e p , c o n t o u r ) , ( 1 , X s k i p : same d i r e c t i o n ,
c o n t o u r ) ] , [ ( 3 , S k i p s k i p : o p p o s i t e d i r e c t i o n , c o n t o u r ) ] , [ ( 5 , Two
s k i p s t h a t form a t r i a d , c o n t o u r ) ] ] , [ [ ( 0 , A v o i d , two l e a d i n g
t o n e s ) ] ] , [ [ ( 0 , P e n u l t i m a t e maj6 o r min3 , a v o i d h a r m o n i c
i n t e r v a l s ) ] ] , [ [ ( 0 , D i m i n i s h e d 5 t h , h a r m o n i c i n t e r v a l s ) , ( 0 , Good
i n t e r v a l s , harmonic i n t e r v a l s ) ] , [ ( 1 0 , P e r f e c t i n t e r v a l s , harmonic
i n t e r v a l s ) ] , [ ( 2 0 , Unison , harmonic i n t e r v a l s ) ] ] , [ [ ( 0 , l e a d i n g tone
l e a d s to t o n i c , tendency ) ] ] , [ [ ( 0 , In s c a l e , s c a l e ) ] ] , [ [ ( 0 , In
r a n g e , r a n g e ) ] ] , [ [ ( 1 , Good i n t e r v a l s , m e l o d i c i n t e r v a l s ) , ( 1 ,
R e p e a t e d n o t e , m e l o d i c i n t e r v a l s ) ] , [ ( 1 0 , L e s s good i n t e r v a l s ,
m e l o d i c i n t e r v a l s ) ] ] , [ [ ( 0 , A e o l i a n , s p e c i a l c a s e s f o r modes ) ] ] , [ [
( 0 , F i r s tL a s tP e n u l t i m a t e , c h r o n o l o g i c a l ) ] ] ]

CHAPTER 6. CODE AND TESTING

82

This testing shows the rule path created for the modal counterpoint.pl rule set.

6.2.6

score.pl

Code
/
SCORE DATA STRUCTURE
M i c h a e l Droettboom
May 1 9 9 9
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( ../ library / event .pl ) .
: e n s u r e l o a d e d ( t r e e s ) .

% c r e a t e s a TimePath and s e t s up t h e v e r t i c a l r e l a t i o n s h i p s i n a
% score
i n i t s c o r e (T , TimePath ) :
t i m e p a t h (T , TimePath ) ,
b u i l d v e r t i c a l s (T , TimePath ) .
l o a d s c o r e ( F i l e , T , TimePath ) :
parse GUIDO ( F i l e , L ) , ! ,
number score (L ) ,
L0 = [ L , endm ar k e r ] ,
f l a t t e n ( L0 , F ) ,
l i s t t o t r e e (F , T) ,
t i m e p a t h (T , TimePath ) ,
b u i l d v e r t i c a l s (T , TimePath ) .
s a v e s c o r e ( F i l e , T , TimePath ) :
grammar GUIDO ( F i l e , T , TimePath ) .
number score (L) :
number score (L , 1 ,

).

n u m b e r s c o r e ( [ Head | T a i l ] , I , F ) :
n u m b e r p a r t ( Head , s t a r t , I , N ) ,
number score ( Tail , N, F ) .
number score ( [ ] , X , X) .
n u m b e r p a r t ( [ L a s t ] , P , I , N) :

CHAPTER 6. CODE AND TESTING

83

e index ( Last , I ) ,
e prev ( Last , P) ,
e n e x t ( L a s t , end ) ,
N i s I + 1.
n u m b e r p a r t ( [ Head | T a i l ] , P , I , F ) :
e i n d e x ( Head , I ) ,
e p r e v ( Head , P ) ,
N i s I + 1,
e n e x t ( Head , N ) ,
number part ( Tai l , I , N, F ) .
/
TIME PATH BUILDER
T h i s b u i l d s a l i s t o f i n d e x e s t o e v e n t s s o r t e d by s t a r t t i m e s .
/
t i m e p a t h (T , TimePath ) :
t r e e s i z e (T , T S i z e ) ,
t i m e p a t h (T , T S i z e , TimePath ) .
t i m e p a t h (T , T S i z e , TimePath ) :
t i m e p a t h 0 (T , 1 ,
, T S i z e , TimePath0 ) ,
s o r t ( TimePath0 , TimePath ) .
time path0 ( , E , E , E , [ ] ) .
t i m e p a t h 0 (T , E l I n , ElOut , T S i z e , [ ( Time , E l I n ) | T a i l ] ) :
get label ( ElIn , T, E),
s e t e v e n t t i m e (T , E ) ,
t i m e s t a r t ( E , Time ) ,
ElIn0 i s ElIn + 1,
t i m e p a t h 0 (T , E l I n 0 , ElOut , T S i z e , T a i l ) .

/
VERTICAL RELATIONSHIPS
T h i s c r e a t e s t h e v e r t i c a l r e l a t i o n s h i p s between e v e n t s .
/
b u i l d v e r t i c a l s (T , TimePath ) :
r e v e r s e ( TimePath , TimePathP ) , ! ,
b u i l d v e r t 1 (T , TimePathP ) .
build vert1 ( , [ ] ) .
b u i l d v e r t 1 (T , [ ( S t a r t T i m e , I n d e x ) | T i m e P a t h T a i l ] ) :
g e t l a b e l ( Index , T, E ) ,
t i m e e n d ( E , EndTime ) ,
b u i l d v e r t 0 (T , S t a r t T i m e , EndTime , T i m e P a t h T a i l , V e r t i c a l s ) ,
e v e r t i c a l (E , V e r t i c a l s ) ,
b u i l d v e r t 1 (T , T i m e P a t h T a i l ) .

CHAPTER 6. CODE AND TESTING

84

build vert0 ( ,
,
, [], []).
b u i l d v e r t 0 (T , S t a r t T i m e , EndTime ,
[ ( StartTime2 , Index ) | TimePathTail ] , [ Index | T a i l ] ) :
g e t l a b e l ( Index , T, E ) ,
t i m e e n d ( E , EndTime2 ) ,
FixEndTime i s EndTime 1 ,
FixEndTime2 i s EndTime2 1 ,
( between ( S t a r t T i m e , FixEndTime , S t a r t T i m e 2 )
;
between ( S t a r t T i m e , FixEndTime , FixEndTime2 )
;
between ( S t a r t T i m e 2 , FixEndTime2 , S t a r t T i m e )
;
between ( S t a r t T i m e 2 , FixEndTime2 , FixEndTime ) ) , ! ,
b u i l d v e r t 0 (T , S t a r t T i m e , EndTime , T i m e P a t h T a i l , T a i l ) .
b u i l d v e r t 0 (T , S t a r t T i m e , EndTime , [ | T a i l ] , V e r t i c a l s ) :
b u i l d v e r t 0 (T , S t a r t T i m e , EndTime , T a i l , V e r t i c a l s ) .

Testing
For testing of this module, see the testing for the GUIDO parser/grammar.

6.2.7

trees.pl

trees.pl is from the Quintus Prolog library.

6.3
6.3.1

The GUIDO parser/grammar


parser.pl

Code
/
GUIDO LANGUAGE PARSER
M i c h a e l Droettboom
June 1 9 9 9
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( l e x e r ) .
: e n s u r e l o a d e d ( ../ library / global .pl ) .
: e n s u r e l o a d e d ( ../ library / pitch .pl ) .
/

CHAPTER 6. CODE AND TESTING

85

USER PREDICATE
/
parse GUIDO ( F i l e , L i s t ) :
see ( F i l e ) ,
t o k e n i s e G U I D O ( Words ) , ! ,
w r i t e ( Words ) ,
seen ,
parse GUIDO ( L i s t , Words , [ ] ) ,
w r i t e ( Parse OK ) , n l .

PRIVATE PREDICATES

/
% TOP LEVEL
parse GUIDO (X) >
{ i n i t p a r s e r },
s c o r e (X ) ,
{ set key }.
s c o r e ( L) >
( [ { ] , p a r t s ( L , 1 , L a s t P a r t ) , [ } ] ,
{ g l o b a l s e t ( num parts , LastPart )})
;
( part (L , 1 ) ,
{ g l o b a l s e t ( num parts , 1 ) } ) .
p a r t s ( L , PartNo , L a s t P a r t ) >
p a r t (X , PartNo ) ,
(([(,)],
{ N e x t P a r t i s PartNo + 1 } ,
p a r t s ( Xs , N e x t P a r t , L a s t P a r t ) ,
{L = [X | Xs ] } )
;
([],
{L = [X ] , L a s t P a r t = PartNo } ) ) .
p a r t (X , PartNo) >
[ [ ] , e v e n t s ( L , PartNo ) , [ ] ] ,
{ i n c r p a r t , f l a t t e n (L , X)}.
e v e n t s ( L , PartNo) >
e v e n t (X , PartNo ) ,
( ( e v e n t s ( Xs , PartNo ) ,
{ (X = , L = Xs ) ; ( L = [X | Xs ] ) } )
;

CHAPTER 6. CODE AND TESTING

86

([],
{ (X = , L = [ ] ) ; ( L = X ) } ) ) .
e v e n t ( E , PartNo) >
c h o r d ( E ) ; t a g ( E , PartNo ) ; n o t e o r r e s t ( E , PartNo ) .
% NOTE OR REST EVENTS t o k e n s w i t h a d i r e c t c o r r e l a t i o n t o t h e
%
Pelog data s t r u c t u r e .
n o t e o r r e s t ( E , PartNo) >
p i t c h (P ) ,
( d u r a t i o n (D ) ; ( [ ] , { g l o b a l ( d u r a t i o n , D ) } ) ) , ! ,
{ e p a r t ( E , PartNo ) ,
e p i t c h (E , P ) ,
e d u r a t i o n ( E , D) } .
p i t c h (CP) >
r e s t (CP ) ; v a r i a b l e (CP ) ; s o u n d e d (CP ) .
s o un d e d (CP) >
p i t c h c l a s s (P ) ,
( ( o c t a v e (O ) , a c c i d e n t a l s (A ) ) ; ( a c c i d e n t a l s (A ) , o c t a v e (O ) ) ) ,
{ c o n c a t (P , A , N ) ,
t e r m t o a t o m ( N0 , N ) ,
p i t c h n a m e o c t a v e (CP , N0 , O) } .
p i t c h c l a s s (P) > [P ] , { p i t c h n a m e (P ,

)}.

o c t a v e (O) >
( [ O]
;
( [ ] , { g l o b a l ( octave , O) } ) ) ,
{ i n t e g e r (O ) ,
O > 13,
O < 13,
g l o b a l s e t ( o c t a v e , O) } .
a c c i d e n t a l s (C) >
a c c i d e n t a l (A ) ,
a c c i d e n t a l (B ) ,
{A = B , c o n c a t (A , B , C ) } .
a c c i d e n t a l s (C) > a c c i d e n t a l (C ) .
a c c i d e n t a l s ( ) > [ ] .
a c c i d e n t a l (A) > [A ] , { A = & ; A = # } .
r e s t ( r e s t ) > [ _ ] .
% NOTE : v a r i a b l e p i t c h e s a r e an e x t e n s i o n t o t h e n o r m a l GUIDO grammar ,
% and w i l l c a u s e a p a r s i n g e r r o r i f v i e w e d by t h e GUIDO N o t e V i e w e r
v a r i a b l e ( ) > [ ~ ] .
d u r a t i o n ( Dur) >
( ( n u m e r a t o r (N ) , d e n o m i n a t o r (D) )

CHAPTER 6. CODE AND TESTING


;
;

87

( d e n o m i n a t o r (D) > {N i s 1 }
( n u m e r a t o r (N ) , { D i s 1 } ) ) ) ,
d o t s (NF , DF ) , { D0 i s D DF , N0 i s N NF } ,
{ dur num ( Dur , N0 ) ,
d u r d e n ( Dur , D0 ) ,
g l o b a l s e t ( d u r a t i o n , Dur ) } .

n u m e r a t o r (N) > [ * ] , ! , [ N ] , { i n t e g e r (N ) } .
d e n o m i n a t o r (D) > [ / ] , ! , [ D ] , { i n t e g e r (D) } .
% e a c h d o t l e n g t h e n s d u r a t i o n by 150%
d o t s (NF , DF) > c o u n t d o t s (N ) ,
{N > 0 > (DF i s N 2 , NF i s ( DF 2 1 ) )
; ( DF i s 1 , NF i s 1 ) } .
c o u n t d o t s (N) > [ . ] , ! , c o u n t d o t s (M) , { s u c c (M, N ) } .
count dots (0) > [].
% TAGS and ESCAPE SEQUENCES
t a g ( E , PartNo) >
( [ \\], [ Tag ]),
( ( [ < ] , param ( Param ) , [ > ] ) ; ( [ ] , { Param = } ) ) ,
{ h a n d l e t a g ( Tag , Param ) } ,
( ( [ ( ] , e v e n t s ( E , PartNo ) , [ ) ] )
;
( [ ] , ( e v e n t s ( E , PartNo ) ) ; ( [ ] , { E = } ) ) ) .
param ( I n t e g e r ) > [ I n t e g e r ] , { i n t e g e r ( I n t e g e r ) } .
param ( Param ) > [ S t r i n g ] , { s t r i n g t o a t o m ( S t r i n g , Param ) } .

% PREDICATES TO HANDLE CERTAIN TAGS


h a n d l e t a g (X , Y) :
% This i s f o r tags not r e l a t e d to p a r t s
n o n v a r (X ) ,
member (X , [ k e y , s c a l e , p i t c h s y s t e m , m e t e r , l a b e l , t i t l e , composer , c o m m e n t s t y l e
a t o m t o t e r m (Y , Y0 ,
),
g l o b a l s e t ( t a g (X ) , Y0 ) .
h a n d l e t a g (X , Y) :
% This i s f o r tags that are r e l a t e d to p a r t s
n o n v a r (X ) ,
member (X , [ r a n g e ] ) ,
global ( part , P),
),
a t o m t o t e r m (Y , Y0 ,
g l o b a l s e t ( t a g ( ( X) P ) , Y0 ) .
handle tag ( ,

) . % Handle u n r e c o g n i z e d t a g s g r a c e f u l l y

% CHORD
% a l w a y s f a i l . No p a r t may c o n t a i n c h o r d s .
c h o r d ( ) > [ { ] , { f a i l } .

CHAPTER 6. CODE AND TESTING

88

/
HELPER PREDICATES
/
i n i t p a r s e r :
% s e t g l o b a l v a r i a b l e s u s e d t o f o r making o c t a v e s
% and d u r a t i o n s s t i c k u n t i l changed
retractall ( global ( ,
)),
g l o b a l s e t ( octave , 1 ) ,
g l o b a l s e t ( d u r a t i o n , dur ( 1 / 4 ) ) ,
global set ( part , 1 ) ,
set all defaults .
i n c r p a r t :
global ( part , X),
Y i s X + 1,
global set ( part , Y).
s e t k e y :
not g l o b a l ( tag ( key ) ,
),
not g l o b a l ( tag ( s c a l e ) ,
), !,
g l o b a l s e t ( tag ( key ) , 0 ) ,
g l o b a l s e t ( tag ( s c a l e ) , c major ) .
s e t k e y :
not g l o b a l ( tag ( key ) ,
),
g l o b a l ( tag ( s c a l e ) , Scale ) , ! ,
s c a l e k e y ( S c a l e , Key ) ,
k e y s h a r p s f l a t s ( KeyNumber , Key ) ,
g l o b a l s e t ( t a g ( k e y ) , KeyNumber ) .
s e t k e y :
not g l o b a l ( tag ( s c a l e ) ,
),
g l o b a l ( t a g ( k e y ) , Key ) ,
i n t e g e r ( Key ) , ! ,
k e y s h a r p s f l a t s ( Key , KeyName ) ,
g l o b a l s e t ( t a g ( s c a l e ) , KeyName m a j o r ) .
s e t k e y :
not g l o b a l ( tag ( s c a l e ) ,
),
g l o b a l ( t a g ( k e y ) , Key ) ,
g l o b a l s e t ( t a g ( s c a l e ) , Key m a j o r ) .
/
TEST
/
test parser grammar ( F i l e I n , FileOut ) :
w r i t e ( Testing GUIDO Parser / Grammar ----> ) , n l ,
w r i t e ( Parsing ) , w r i t e ( F i l e I n ) , n l ,
l o a d s c o r e ( F i l e I n , T , TimePath ) ,
n l , w r i t e ( Score tree : ) , n l ,

CHAPTER 6. CODE AND TESTING

89

p r i n t t r e e (T ) , n l ,
w r i t e ( Tags : ) , n l ,
l i s t i n g ( global ),
n l , w r i t e ( Generating ) , w r i t e ( F i l e O u t ) , n l ,
s a v e s c o r e ( F i l e O u t , T , TimePath ) ,
w r i t e ( Succeeded . ) , n l .

6.3.2

lexer.pl

Code
/
GUIDO LANGUAGE LEXER
M i c h a e l Droettboom
June 1 9 9 9
/
/ ADAPTED FROM THE FOLLOWING QUINTUS LIBRARY > /
%
%
%
%

Module :
Aut hor :
Updated :
Purpose :

read in
R i c h a r d A . O K e e f e
2 1 Apr 1 9 8 7
Read i n a s e n t e n c e a s a l i s t o f words .

%
%
%

There i s a s h a r e d v e r s i o n o f t h i s f i l e w r i t t e n by L a w re nc e Byrd .
T h i s v e r s i o n was w r i t t e n e n t i r e l y from s c r a t c h f o r Q u i n t u s .
C o p y r i g h t ( C ) 1 9 8 7 , Q u i n t u s Computer S y s t e m s , I n c . A l l r i g h t s r e s e r v e d .

%
%
%
%
%
%
%
%
%

r e a d i n ( Words )
r e a d s c h a r a c t e r s u n t i l i t f i n d s a p e r i o d ( . ? o r ! ) a t t h e end o f a
l i n e , t h a t i s , f o l l o w e d by any number o f s p a c e s b u t n o t h i n g e l s e .
Words a r e s e q u e n c e s o f l e t t e r s ( i n e i t h e r c a s e , f o r c e d t o l o w e r
c a s e ) , o r s t r i n g s o f d i g i t s , o r p u n c t u a t i o n marks . O t h er c h a r a c t e r s
a c t a s word s e p a r a t o r s , and a r e o t h e r w i s e i g n o r e d . The s u b r o u t i n e s
i n t h i s f i l e a l l s t a r t with r i , to avoid a c o l l i s i o n with a user s
p r e d i c a t e s . Note t h a t t h i s p r e d i c a t e may r e a d more t h a n one
sentence ; i t i s best thought of as re ading paragraphs .

t o k e n i s e G U I D O ( Words ) :
g e t 0 (C ) ,
t g s e n t (C , Found ) ,
Words = Found .

t g s e n t (C , Words ) :
(
C < 0
;
C =< " "

> Words = [ ]
> = g e t 0 (D ) , t g s e n t (D , Words )

CHAPTER 6. CODE AND TESTING


;
;

90

C =:= "%"
> g e t 0 (D ) , t g l i n e c o m m e n t (D ) , t g s e n t (D , Words )
Words = [ Word | R e s t ] , D i s C \ / 3 2 ,
(
C =:= "." > Word = . , g e t 0 ( F ) , t g s t o p ( F , R e s t )
;
C =:= "!" > Word = ! , g e t 0 ( F ) , t g s t o p ( F , R e s t )
;
C =:= "?" > Word = ? , g e t 0 ( F ) , t g s t o p ( F , R e s t )
;
D >= "a" , D =< "z" > =
get0 (E ) , tg word (E , Chars , F ) ,
name ( Word , [ D | C h a r s ] ) , t g s e n t ( F , R e s t )
;
C =:= "\"" >
get0 (E ) , t g s t r i n g (E , Chars ) ,
Word = C h a r s , g e t 0 ( F ) , t g s e n t ( F , R e s t )
;
C =:= "-" >
get0 (E ) , tg neg (E , Chars , F ) ,
name ( Word , [ C | C h a r s ] ) , t g s e n t ( F , R e s t )
;
C >= "0" , C =< "9" > =
get0 (E ) , tg nmbr (E , Chars , F ) ,
name ( Word , [ C | C h a r s ] ) , t g s e n t ( F , R e s t )
;
get0 (F ) ,
name ( Word , [ C ] ) ,
t g s e n t (F , Rest )
)

).
t g l i n e c o m m e n t (C ) : % i g n o r e comments
C < " "
;
( g e t 0 (D ) ,
t g l i n e c o m m e n t (D ) ) .
t g s t o p (C , R e s t ) :
(
C =:= " " > g e t 0 (D ) , t g s t o p (D , R e s t )
;
C =:= 9 > g e t 0 (D ) , t g s t o p (D , R e s t )
;
C < " " > R e s t = [ ]
;
t g s e n t (C , R e s t )
).

t g w o r d (C , [ Lower | L o w e rs ] , F ) :
(
C >= "a" , C =< "z" > = Lower = C
;
C =:= "_"
> Lower = C
;
C >= "A" , C =< "Z" > = Lower i s C"A"+"a"
/ ;
C >= 0 , C =< 9
> Lower = C /
), !,
g e t 0 (D ) ,
t g w o r d (D , L o w e r s , F ) .
tg word (F , [ ] , F ) .
tg string (34, []) : !.
t g s t r i n g ( Char , [ Char | C h a r s ] ) :
g e t 0 (D ) ,
t g s t r i n g (D , C h a r s ) .

% space
% tab
% end o f l i n e

CHAPTER 6. CODE AND TESTING

91

t g n e g (C , D i g i t s , F ) :
(
D i g i t s = [C| D i g i t s 1 ] ,
g e t 0 (D ) ,
t g n m b r (D , D i g i t s 1 , F )
;
F = C, Digits = [] ).
t g n m b r (C , D i g i t s , F ) :
(
C >= "0" , C =< "9" > =
D i g i t s = [C| D i g i t s 1 ] ,
g e t 0 (D ) ,
t g n m b r (D , D i g i t s 1 , F )
;
C =:= "_" >
g e t 0 (D ) ,
t g n m b r (D , D i g i t s , F )
;
F = C, Digits = []
).

6.3.3

grammar.pl

Code
/
GRAMMAR FOR GUIDO
M i c h a e l Droettboom
May 1 9 9 9
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( ../ library / global .pl ) .
: e n s u r e l o a d e d ( ../ core/ trees .pl ) .
: e n s u r e l o a d e d ( ../ library / pitch.pl ) .

/
OPTIONS
/
% c o m m e n t s t y l e = { l y r i c s | r e f e r e n c e d | none }
%
l y r i c s : comments a r e w r i t t e n i n a s l y r i c s on t h e s c o r e
%
r e f e r e n c e d : comments a r e added t o t h e end o f f i l e and r e f e r e n c e d
%
t o t h e n o t e s t h e m s e l v e s by number
%
none : no comments
g l o b a l d e f a u l t ( tag ( comment style ) , r e f e r e n c e d ) .

CHAPTER 6. CODE AND TESTING

92

/
GRAMMAR
/
grammar GUIDO ( F i l e , T , TimePath ) :
g g m ai n (T , TimePath , L , [ ] ) ,
tell ( File ),
write tokens (L ) ,
told .
g g m ai n (T ,
gg
gg
gg

TimePath) >
header ,
s c o r e (T , TimePath ) ,
i n f o (T ) .

g g h e a d e r >
[ % GUIDO Music Notation Language v1 .0\n ] ,
[ %\n ] ,
[ % This file was generated by the PELOG musical rule system \n ] ,
[ % (c ) 1999 Michael Droettboom \n\n ] .
g g s c o r e (T , TimePath) >
[ {\n ] , g g p a r t (T , TimePath ) , [ }\n\n ] .

g g p a r t (T , [ ( 0 , I n d e x )]) >
! , [ [ ] , g g t a g s , g g e v e n t (T , I n d e x ) , [ ]\n ] .
g g p a r t (T , [ ( 0 , I n d e x ) , ( X ,
) | ]) >
{X > 0 } , ! ,
[ [ ] , g g t a g s , g g e v e n t (T , I n d e x ) , [ ] ] .
g g p a r t (T , [ ( 0 , I n d e x ) | T a i l ]) >
!,
[ [ ] , g g t a g s , g g e v e n t (T , I n d e x ) , [ ] ] , [ ( , ) ] , [ \n ] , g g p a r t (T , T a i l )
gg part ( , ) > [].
g g t a g s >
{ f i n d a l l ( ( X , Y ) , g l o b a l ( t a g (X ) , Y ) , L ) } ,
gg tag (L ) .
gg
gg
gg
gg

tag ([]) > [].


tag ( [ ( range ,
)| ]) > !, [].
tag ( [ ( s c a l e ,
)| ]) > !, [].
t a g ( [ ( X , Y ) | T a i l ]) >
[ \\], [ X ], [<\"], [ Y ], [\"> ],
gg tag ( Tail ) .

g g e v e n t ( , end) > [ ] .
g g e v e n t (T , I n d e x ) >
{ g e t l a b e l ( Index , T, E ) ,
e p i t c h (E , Pitch ) ,
p i t c h n a m e ( P i t c h , PN ) ,

CHAPTER 6. CODE AND TESTING

93

o c t a v e ( P i t c h , Octave ) ,
e d u r a t i o n (E , Duration ) ,
dur num ( D u r a t i o n , Numerator ) ,
d u r d e n ( D u r a t i o n , Denominator ) ,
e n e x t ( E , Next ) } ,
(({ g l o b a l ( tag ( comment style ) , l y r i c s ) ,
e p r e t t y c o m m e n t ( E , Comment ) } ,
gg comment ( Comment ) )
;
({ g l o b a l ( tag ( comment style ) , r e f e r e n c e d ) } ,
gg comment ( I n d e x ) ) ) ,
g g e v e n t t o k e n (PN , Octave , Numerator , Denominator ) ,
g g e v e n t (T , Next ) .
gg comment ( Comment) >
[ \\ text <\" ] , [ Comment ] , [ \"> ] .
g g e v e n t t o k e n (PN , Octave , Numerator , Denominator) >
[ PN ] , [ Octave ] , [ * ] , [ Numerator ] , [ / ] , [ Denominator ] , [ ] .
g g i n f o (T) >
{ g l o b a l ( tag ( comment style ) , r e f e r e n c e d ) ,
l i s t t o t r e e (L , T) } ,
gg referenced comments (L ) .
g g i n f o ( ) > [].
g g r e f e r e n c e d c o m m e n t s ( [ Head | T a i l ]) >
{ e i n d e x ( Head , I n d e x ) ,
e p r e t t y c o m m e n t ( Head , Comment ) } ,
[ % ] , [ I n d e x ] , [ : ] , [ Comment ] , [ \n ] ,
gg referenced comments ( Tail ) .
g g r e f e r e n c e d c o m m e n t s ( [ endm a r k e r ] ) > [ ] .

/
A REVERSE LEXER
/
write tokens ( [ ] ) .
w r i t e t o k e n s ( [ Head | T a i l ] ) :
w r i t e ( Head ) , w r i t e t o k e n s ( T a i l ) .

6.3.4

Testing

Testing the parser/grammar involved using three GUIDO BNF testing files provided
by the GUIDO authors. The test parser grammar predicate parses an input file,
displays the score tree and global tags, and generates a copy to an output file.
If the output file is functionally identical to the input file, then one can assume
the parser/grammar is working correctly. Each test below contains the following
information:
The text of the input and output files

CHAPTER 6. CODE AND TESTING

94

A view of the input and ouput files in common music notation (cmn) as
generated by the GUIDO NoteViewer
The console output of the test parser grammar predicate
simple1
IN
%
%
%
%

s i m p l e GUIDO e x a m p l e
f i r s t enhancement
added some more n o t e s
added m e t e rt a g

[ \ m e t e r<"2/4">m e t e r c1 1 / 8 d e f g / 4 g a / 8 a a a g /2
a / 8 a a a g / 2 f / 8 f f f e / 4 e g /8
g g g c 1 ]
% s e e s i m p l e 2 . gmn v o r f u r t h e r e n h a c e m e n t s .

OUT
% GUIDO Music N o t a t i o n Language v1 . 0
%
% T h i s f i l e was g e n e r a t e d by t h e PELOG m u s i c a l r u l e s y s t e m
% ( c ) 1 9 9 9 M i c h a e l Droettboom

{
[ \ p i t c h s y s t e m<" diatonic ">p i t c h s y s t e m \ c o m m e n t s t y l e<" referenced ">c o m m e n t s t y l e \
\ k e y<"c">k e y \ s c a l e<"c~ major ">s c a l e \ t e x t<"1">t e x t c 1 1 / 8 \ t e x t<"2">t e x t d 1 1/8
\ t e x t<"3">t e x t e 1 1 / 8 \ t e x t<"4">t e x t f 1 1 / 8 \ t e x t<"5">t e x t g 1 1 / 4 \ t e x t<"6">t e x t g 1 1/
\ t e x t<"7">t e x t a 1 1 / 8 \ t e x t<"8">t e x t a 1 1 / 8 \ t e x t<"9">t e x t a 1 1 / 8 \ t e x t<"10">t e x t a 1 1
\ t e x t<"11">t e x t g 1 1 / 2 \ t e x t<"12">t e x t a 1 1 / 8 \ t e x t<"13">t e x t a 1 1/8
\ t e x t<"14">t e x t a 1 1 / 8 \ t e x t<"15">t e x t a 1 1 / 8 \ t e x t<"16">t e x t g 1 1/2
\ t e x t<"17">t e x t f 1 1 / 8 \ t e x t<"18">t e x t f 1 1 / 8 \ t e x t<"19">t e x t f 1 1/8
\ t e x t<"20">t e x t f 1 1 / 8 \ t e x t<"21">t e x t e 1 1 / 4 \ t e x t<"22">t e x t e 1 1/4
\ t e x t<"23">t e x t g 1 1 / 8 \ t e x t<"24">t e x t g 1 1 / 8 \ t e x t<"25">t e x t g 1 1/8
\ t e x t<"26">t e x t g 1 1 / 8 \ t e x t<"27">t e x t c 1 1 / 1 ] }
% 1:

CHAPTER 6. CODE AND TESTING

95

TERMINAL
t e s t p a r s e r g r a m m a r ( simple1 .gmn , simple1out .gmn ) .
T e s t i n g GUIDO P a r s e r /Grammar >
P a r s i n g s i m p l e 1 . gmn
P a r s e OK
Score t r e e :
event ( 1 , 1 , p i t c h ( 1 2 , 7 ) , dur ( 1 / 8 ) , time ( 0 , 1 5 0 0 ) , s t a r t , [ ] , 2 ,
G1715 , G1716 , G1717 ) e v e n t ( 2 , 1 , p i t c h ( 1 4 , 8 ) , d u r ( 1 / 8 ) , t i m e ( 1 5 0 0 ,
3 0 0 0 ) , 1 , [ ] , 3 , G1753 , G1754 , G1755 ) e v e n t ( 3 , 1 , p i t c h ( 1 6 , 9 ) ,
d u r ( 1 / 8 ) , t i m e ( 3 0 0 0 , 4 5 0 0 ) , 2 , [ ] , 4 , G1791 , G1792 , G1793 ) e v e n t ( 4 ,
1 , p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) , t i m e ( 4 5 0 0 , 6 0 0 0 ) , 3 , [ ] , 5 , G1829 ,
G1830 , G1831 ) e v e n t ( 5 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 4 ) , t i m e ( 6 0 0 0 , 9 0 0 0 ) ,
4 , [ ] , 6 , G1879 , G1880 , G1881 ) e v e n t ( 6 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 4 ) ,
t i m e ( 9 0 0 0 , 1 2 0 0 0 ) , 5 , [ ] , 7 , G1917 , G1918 , G1919 ) e v e n t ( 7 , 1 ,
p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 1 2 0 0 0 , 1 3 5 0 0 ) , 6 , [ ] , 8 , G1967 , G1968 ,
G1969 ) e v e n t ( 8 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 1 3 5 0 0 , 1 5 0 0 0 ) , 7 ,
[ ] , 9 , G2005 , G2006 , G2007 ) e v e n t ( 9 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) ,
t i m e ( 1 5 0 0 0 , 1 6 5 0 0 ) , 8 , [ ] , 1 0 , G2043 , G2044 , G2045 ) e v e n t ( 1 0 , 1 ,
p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 1 6 5 0 0 , 1 8 0 0 0 ) , 9 , [ ] , 1 1 , G2081 ,
G2082 , G2083 ) e v e n t ( 1 1 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 2 ) , t i m e ( 1 8 0 0 0 ,
2 4 0 0 0 ) , 1 0 , [ ] , 1 2 , G2131 , G2132 , G2133 ) e v e n t ( 1 2 , 1 , p i t c h ( 2 1 ,
1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 4 0 0 0 , 2 5 5 0 0 ) , 1 1 , [ ] , 1 3 , G2181 , G2182 , G2183 )
event ( 1 3 , 1 , p i t c h ( 2 1 , 1 2 ) , dur ( 1 / 8 ) , time ( 2 5 5 0 0 , 2 7 0 0 0 ) , 1 2 , [ ] , 1 4 ,
G2219 , G2220 , G2221 ) e v e n t ( 1 4 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) ,
t i m e ( 2 7 0 0 0 , 2 8 5 0 0 ) , 1 3 , [ ] , 1 5 , G2257 , G2258 , G2259 ) e v e n t ( 1 5 , 1 ,
p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 8 5 0 0 , 3 0 0 0 0 ) , 1 4 , [ ] , 1 6 , G2295 ,
G2296 , G2297 ) e v e n t ( 1 6 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 2 ) , t i m e ( 3 0 0 0 0 ,
3 6 0 0 0 ) , 1 5 , [ ] , 1 7 , G2345 , G2346 , G2347 ) e v e n t ( 1 7 , 1 , p i t c h ( 1 7 ,
1 0 ) , d u r ( 1 / 8 ) , t i m e ( 3 6 0 0 0 , 3 7 5 0 0 ) , 1 6 , [ ] , 1 8 , G2395 , G2396 , G2397 )
event ( 1 8 , 1 , p i t c h ( 1 7 , 1 0 ) , dur ( 1 / 8 ) , time ( 3 7 5 0 0 , 3 9 0 0 0 ) , 1 7 , [ ] , 1 9 ,
G2433 , G2434 , G2435 ) e v e n t ( 1 9 , 1 , p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) ,
t i m e ( 3 9 0 0 0 , 4 0 5 0 0 ) , 1 8 , [ ] , 2 0 , G2471 , G2472 , G2473 ) e v e n t ( 2 0 , 1 ,
p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) , t i m e ( 4 0 5 0 0 , 4 2 0 0 0 ) , 1 9 , [ ] , 2 1 , G2509 ,
G2510 , G2511 ) e v e n t ( 2 1 , 1 , p i t c h ( 1 6 , 9 ) , d u r ( 1 / 4 ) , t i m e ( 4 2 0 0 0 ,
4 5 0 0 0 ) , 2 0 , [ ] , 2 2 , G2559 , G2560 , G2561 ) e v e n t ( 2 2 , 1 , p i t c h ( 1 6 , 9 ) ,
d u r ( 1 / 4 ) , t i m e ( 4 5 0 0 0 , 4 8 0 0 0 ) , 2 1 , [ ] , 2 3 , G2597 , G2598 , G2599 )
event ( 2 3 , 1 , p i t c h ( 1 9 , 1 1 ) , dur ( 1 / 8 ) , time ( 4 8 0 0 0 , 4 9 5 0 0 ) , 2 2 , [ ] , 2 4 ,
G2647 , G2648 , G2649 ) e v e n t ( 2 4 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) ,
t i m e ( 4 9 5 0 0 , 5 1 0 0 0 ) , 2 3 , [ ] , 2 5 , G2685 , G2686 , G2687 ) e v e n t ( 2 5 , 1 ,
p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 5 1 0 0 0 , 5 2 5 0 0 ) , 2 4 , [ ] , 2 6 , G2723 ,
G2724 , G2725 ) e v e n t ( 2 6 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 5 2 5 0 0 ,
5 4 0 0 0 ) , 2 5 , [ ] , 2 7 , G2761 , G2762 , G2763 ) e v e n t ( 2 7 , 1 , p i t c h ( 1 2 , 7 ) ,
d u r ( 1 / 1 ) , t i m e ( 5 4 0 0 0 , 6 6 0 0 0 ) , 2 6 , [ ] , end , G2811 , G2812 , G2813 )
endm ar k e r
Tags :
g l o b a l ( tag ( pi t c h s y s t e m ) , d i a t o n i c ) .
g l o b a l ( tag ( comment style ) , r e f e r e n c e d ) .
g l o b a l ( tag ( meter ) , 2 / 4 ) .

CHAPTER 6. CODE AND TESTING

96

global ( octave , 1 ) .
g l o b a l ( d u r a t i o n , dur ( 1 / 1 ) ) .
global ( part , 2 ) .
g l o b a l ( num parts , 1 ) .
g l o b a l ( tag ( key ) , c ) .
g l o b a l ( tag ( s c a l e ) , c major ) .
G e n e r a t i n g s i m p l e 1 o u t . gmn
Succeeded .

simple2
IN
%
%
%
%

s i m p l e GUIDO e x a m p l e
s e c o n d enhancement
adding a second v o i c e
and t h e s t a f f O f fTag

{ [ \ m e t e r<"2/4">m e t e r c1 1 / 8 d e f g / 4 g a / 8 a a a g /2
a / 8 a a a g / 2 f / 8 f f f e / 4 e g /8
g g g c 1 \ s t a f f O f f ] ,
[ \ m e t e r<"2/4">m e t e r \ c l e f <"bass"> c l e f c0 / 2 c f c f c g c g1 c0 ] }
% s e e s i m p l e 3 . gmn v o r f u r t h e r e n h a c e m e n t s .

OUT
% GUIDO Music N o t a t i o n Language v1 . 0
%
% T h i s f i l e was g e n e r a t e d by t h e PELOG m u s i c a l r u l e s y s t e m
% ( c ) 1 9 9 9 M i c h a e l Droettboom

{
[ \ p i t c h s y s t e m<" diatonic ">p i t c h s y s t e m \ c o m m e n t s t y l e<" referenced ">c o m m e n t s t y l e \
\ k e y<"c">k e y \ s c a l e<"c~ major ">s c a l e \ t e x t<"1">t e x t c 1 1 / 8 \ t e x t<"2">t e x t d 1 1/8
\ t e x t<"3">t e x t e 1 1 / 8 \ t e x t<"4">t e x t f 1 1 / 8 \ t e x t<"5">t e x t g 1 1 / 4 \ t e x t<"6">t e x t g 1 1/
\ t e x t<"7">t e x t a 1 1 / 8 \ t e x t<"8">t e x t a 1 1 / 8 \ t e x t<"9">t e x t a 1 1 / 8 \ t e x t<"10">t e x t a 1 1
\ t e x t<"11">t e x t g 1 1 / 2 \ t e x t<"12">t e x t a 1 1 / 8 \ t e x t<"13">t e x t a 1 1/8
\ t e x t<"14">t e x t a 1 1 / 8 \ t e x t<"15">t e x t a 1 1 / 8 \ t e x t<"16">t e x t g 1 1/2

CHAPTER 6. CODE AND TESTING

97

\ t e x t<"17">t e x t f 1 1 / 8 \ t e x t<"18">t e x t f 1 1 / 8 \ t e x t<"19">t e x t f 1 1/8


\ t e x t<"20">t e x t f 1 1 / 8 \ t e x t<"21">t e x t e 1 1 / 4 \ t e x t<"22">t e x t e 1 1/4
\ t e x t<"23">t e x t g 1 1 / 8 \ t e x t<"24">t e x t g 1 1 / 8 \ t e x t<"25">t e x t g 1 1/8
\ t e x t<"26">t e x t g 1 1 / 8 \ t e x t<"27">t e x t c 1 1 / 1 ] , [ \ p i t c h s y s t e m<" diatonic ">p i t c h s y s
\ c o m m e n t s t y l e<" referenced ">c o m m e n t s t y l e \ m e t e r<"2/4">m e t e r \ k e y<"c">k e y \ s c a l e<"
\ t e x t<"28">t e x t c 0 1 / 2 \ t e x t<"29">t e x t c 0 1 / 2 \ t e x t<"30">t e x t f 0 1/2
\ t e x t<"31">t e x t c 0 1 / 2 \ t e x t<"32">t e x t f 0 1 / 2 \ t e x t<"33">t e x t c 0 1/2
\ t e x t<"34">t e x t g 0 1 / 2 \ t e x t<"35">t e x t c 0 1 / 2 \ t e x t<"36">t e x t g 11/2
\ t e x t<"37">t e x t c 0 1 / 2 ] }

TERMINAL
t e s t p a r s e r g r a m m a r ( simple2 .gmn , simple2out .gmn ) .
T e s t i n g GUIDO P a r s e r /Grammar >
P a r s i n g s i m p l e 2 . gmn
P a r s e OK
Score t r e e :
event ( 1 , 1 , p i t c h ( 1 2 , 7 ) , dur ( 1 / 8 ) , time ( 0 , 1 5 0 0 ) , s t a r t , [ ] , 2 ,
G2297 , G2298 , G2299 ) e v e n t ( 2 , 1 , p i t c h ( 1 4 , 8 ) , d u r ( 1 / 8 ) , t i m e ( 1 5 0 0 ,
3 0 0 0 ) , 1 , [ 2 8 ] , 3 , G2335 , G2336 , G2337 ) e v e n t ( 3 , 1 , p i t c h ( 1 6 , 9 ) ,
d u r ( 1 / 8 ) , t i m e ( 3 0 0 0 , 4 5 0 0 ) , 2 , [ 2 8 ] , 4 , G2373 , G2374 , G2375 )
event ( 4 , 1 , p i t c h ( 1 7 , 1 0 ) , dur ( 1 / 8 ) , time ( 4 5 0 0 , 6 0 0 0 ) , 3 , [ 2 8 ] , 5 ,
G2411 , G2412 , G2413 ) e v e n t ( 5 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 4 ) ,
t i m e ( 6 0 0 0 , 9 0 0 0 ) , 4 , [ ] , 6 , G2461 , G2462 , G2463 ) e v e n t ( 6 , 1 ,
p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 4 ) , t i m e ( 9 0 0 0 , 1 2 0 0 0 ) , 5 , [ 2 9 ] , 7 , G2499 ,
G2500 , G2501 ) e v e n t ( 7 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 1 2 0 0 0 ,
1 3 5 0 0 ) , 6 , [ ] , 8 , G2549 , G2550 , G2551 ) e v e n t ( 8 , 1 , p i t c h ( 2 1 , 1 2 ) ,
d u r ( 1 / 8 ) , t i m e ( 1 3 5 0 0 , 1 5 0 0 0 ) , 7 , [ 3 0 ] , 9 , G2587 , G2588 , G2589 )
event ( 9 , 1 , p i t c h ( 2 1 , 1 2 ) , dur ( 1 / 8 ) , time ( 1 5 0 0 0 , 1 6 5 0 0 ) , 8 , [ 3 0 ] , 1 0 ,
G2625 , G2626 , G2627 ) e v e n t ( 1 0 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) ,
t i m e ( 1 6 5 0 0 , 1 8 0 0 0 ) , 9 , [ 3 0 ] , 1 1 , G2663 , G2664 , G2665 ) e v e n t ( 1 1 , 1 ,
p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 2 ) , t i m e ( 1 8 0 0 0 , 2 4 0 0 0 ) , 1 0 , [ ] , 1 2 , G2713 ,
G2714 , G2715 ) e v e n t ( 1 2 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 4 0 0 0 ,
2 5 5 0 0 ) , 1 1 , [ ] , 1 3 , G2763 , G2764 , G2765 ) e v e n t ( 1 3 , 1 , p i t c h ( 2 1 ,
1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 5 5 0 0 , 2 7 0 0 0 ) , 1 2 , [ 3 2 ] , 1 4 , G2801 , G2802 ,
G2803 ) e v e n t ( 1 4 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 7 0 0 0 , 2 8 5 0 0 ) , 1 3 ,
[ 3 2 ] , 1 5 , G2839 , G2840 , G2841 ) e v e n t ( 1 5 , 1 , p i t c h ( 2 1 , 1 2 ) ,
d u r ( 1 / 8 ) , t i m e ( 2 8 5 0 0 , 3 0 0 0 0 ) , 1 4 , [ 3 2 ] , 1 6 , G2877 , G2878 , G2879 )

CHAPTER 6. CODE AND TESTING

98

event ( 1 6 , 1 , p i t c h ( 1 9 , 1 1 ) , dur ( 1 / 2 ) , time ( 3 0 0 0 0 , 3 6 0 0 0 ) , 1 5 , [ ] , 1 7 ,


G2927 , G2928 , G2929 ) e v e n t ( 1 7 , 1 , p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) ,
t i m e ( 3 6 0 0 0 , 3 7 5 0 0 ) , 1 6 , [ ] , 1 8 , G2977 , G2978 , G2979 ) e v e n t ( 1 8 , 1 ,
p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) , t i m e ( 3 7 5 0 0 , 3 9 0 0 0 ) , 1 7 , [ 3 4 ] , 1 9 , G3015 ,
G3016 , G3017 ) e v e n t ( 1 9 , 1 , p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) , t i m e ( 3 9 0 0 0 ,
4 0 5 0 0 ) , 1 8 , [ 3 4 ] , 2 0 , G3053 , G3054 , G3055 ) e v e n t ( 2 0 , 1 , p i t c h ( 1 7 ,
1 0 ) , d u r ( 1 / 8 ) , t i m e ( 4 0 5 0 0 , 4 2 0 0 0 ) , 1 9 , [ 3 4 ] , 2 1 , G3091 , G3092 ,
G3093 ) e v e n t ( 2 1 , 1 , p i t c h ( 1 6 , 9 ) , d u r ( 1 / 4 ) , t i m e ( 4 2 0 0 0 , 4 5 0 0 0 ) , 2 0 ,
[ ] , 2 2 , G3141 , G3142 , G3143 ) e v e n t ( 2 2 , 1 , p i t c h ( 1 6 , 9 ) , d u r ( 1 / 4 ) ,
t i m e ( 4 5 0 0 0 , 4 8 0 0 0 ) , 2 1 , [ 3 5 ] , 2 3 , G3179 , G3180 , G3181 ) e v e n t ( 2 3 , 1 ,
p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 4 8 0 0 0 , 4 9 5 0 0 ) , 2 2 , [ ] , 2 4 , G3229 ,
G3230 , G3231 ) e v e n t ( 2 4 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 4 9 5 0 0 ,
5 1 0 0 0 ) , 2 3 , [ 3 6 ] , 2 5 , G3267 , G3268 , G3269 ) e v e n t ( 2 5 , 1 , p i t c h ( 1 9 ,
1 1 ) , d u r ( 1 / 8 ) , t i m e ( 5 1 0 0 0 , 5 2 5 0 0 ) , 2 4 , [ 3 6 ] , 2 6 , G3305 , G3306 ,
G3307 ) e v e n t ( 2 6 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 5 2 5 0 0 , 5 4 0 0 0 ) , 2 5 ,
[ 3 6 ] , 2 7 , G3343 , G3344 , G3345 ) e v e n t ( 2 7 , 1 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 1 ) ,
t i m e ( 5 4 0 0 0 , 6 6 0 0 0 ) , 2 6 , [ ] , end , G3393 , G3394 , G3395 ) e v e n t ( 2 8 , 2 ,
p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 0 , 6 0 0 0 ) , s t a r t , [ 1 ] , 2 9 , G3652 , G3653 ,
G3654 ) e v e n t ( 2 9 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 6 0 0 0 , 1 2 0 0 0 ) , 2 8 ,
[ 5 ] , 3 0 , G3690 , G3691 , G3692 ) e v e n t ( 3 0 , 2 , p i t c h ( 5 , 3 ) , d u r ( 1 / 2 ) ,
t i m e ( 1 2 0 0 0 , 1 8 0 0 0 ) , 2 9 , [ 7 ] , 3 1 , G3728 , G3729 , G3730 ) e v e n t ( 3 1 , 2 ,
p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 1 8 0 0 0 , 2 4 0 0 0 ) , 3 0 , [ 1 1 ] , 3 2 , G3766 ,
G3767 , G3768 ) e v e n t ( 3 2 , 2 , p i t c h ( 5 , 3 ) , d u r ( 1 / 2 ) , t i m e ( 2 4 0 0 0 ,
3 0 0 0 0 ) , 3 1 , [ 1 2 ] , 3 3 , G3804 , G3805 , G3806 ) e v e n t ( 3 3 , 2 , p i t c h ( 0 ,
0 ) , d u r ( 1 / 2 ) , t i m e ( 3 0 0 0 0 , 3 6 0 0 0 ) , 3 2 , [ 1 6 ] , 3 4 , G3842 , G3843 ,
G3844 ) e v e n t ( 3 4 , 2 , p i t c h ( 7 , 4 ) , d u r ( 1 / 2 ) , t i m e ( 3 6 0 0 0 , 4 2 0 0 0 ) , 3 3 ,
[ 1 7 ] , 3 5 , G3880 , G3881 , G3882 ) e v e n t ( 3 5 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) ,
t i m e ( 4 2 0 0 0 , 4 8 0 0 0 ) , 3 4 , [ 2 1 ] , 3 6 , G3918 , G3919 , G3920 ) e v e n t ( 3 6 , 2 ,
p i t c h ( 5 , 3 ) , d u r ( 1 / 2 ) , t i m e ( 4 8 0 0 0 , 5 4 0 0 0 ) , 3 5 , [ 2 3 ] , 3 7 , G3956 ,
G3957 , G3958 ) e v e n t ( 3 7 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 5 4 0 0 0 ,
6 0 0 0 0 ) , 3 6 , [ 2 7 ] , end , G3994 , G3995 , G3996 ) endm a r k e r
Tags :
g l o b a l ( tag ( pi t c h s y s t e m ) , d i a t o n i c ) .
g l o b a l ( tag ( comment style ) , r e f e r e n c e d ) .
g l o b a l ( tag ( meter ) , 2 / 4 ) .
g l o b a l ( d u r a t i o n , dur ( 1 / 2 ) ) .
global ( octave , 0 ) .
global ( part , 3 ) .
g l o b a l ( num parts , 2 ) .
g l o b a l ( tag ( key ) , c ) .
g l o b a l ( tag ( s c a l e ) , c major ) .
G e n e r a t i n g s i m p l e 2 o u t . gmn
Succeeded .

simple3
IN
% s i m p l e GUIDO e x a m p l e

CHAPTER 6. CODE AND TESTING

99

% t h i r d enhancement
% adding s l u r s voice
% and n e w L i n eTag
{ [ \ m e t e r<"2/4">m e t e r \ s l u r ( c1 1 / 8 d e f g / 4 g ) \ s l u r ( a / 8 a a a g / 2 )
a / 8 a a a g / 2 f / 8 f f f \ n e w L i n e e / 4 e g /8
g g g c 1 \ s t a f f O f f ] ,
[ \ m e t e r<"2/4">m e t e r \ c l e f <"bass"> c l e f c0 / 2 c f c f c g c g1 c0 ] }
% s e e s i m p l e 4 . gmn v o r f u r t h e r e n h a c e m e n t s .

OUT
% GUIDO Music N o t a t i o n Language v1 . 0
%
% T h i s f i l e was g e n e r a t e d by t h e PELOG m u s i c a l r u l e s y s t e m
% ( c ) 1 9 9 9 M i c h a e l Droettboom

{
[ \ p i t c h s y s t e m<" diatonic ">p i t c h s y s t e m \ c o m m e n t s t y l e<" referenced ">c o m m e n t s t y l e \
\ k e y<"c">k e y \ s c a l e<"c~ major ">s c a l e \ t e x t<"1">t e x t c 1 1 / 8 \ t e x t<"2">t e x t d 1 1/8
\ t e x t<"3">t e x t e 1 1 / 8 \ t e x t<"4">t e x t f 1 1 / 8 \ t e x t<"5">t e x t g 1 1 / 4 \ t e x t<"6">t e x t g 1 1/
\ t e x t<"7">t e x t a 1 1 / 8 \ t e x t<"8">t e x t a 1 1 / 8 \ t e x t<"9">t e x t a 1 1 / 8 \ t e x t<"10">t e x t a 1 1
\ t e x t<"11">t e x t g 1 1 / 2 \ t e x t<"12">t e x t a 1 1 / 8 \ t e x t<"13">t e x t a 1 1/8
\ t e x t<"14">t e x t a 1 1 / 8 \ t e x t<"15">t e x t a 1 1 / 8 \ t e x t<"16">t e x t g 1 1/2
\ t e x t<"17">t e x t f 1 1 / 8 \ t e x t<"18">t e x t f 1 1 / 8 \ t e x t<"19">t e x t f 1 1/8
\ t e x t<"20">t e x t f 1 1 / 8 \ t e x t<"21">t e x t e 1 1 / 4 \ t e x t<"22">t e x t e 1 1/4
\ t e x t<"23">t e x t g 1 1 / 8 \ t e x t<"24">t e x t g 1 1 / 8 \ t e x t<"25">t e x t g 1 1/8
\ t e x t<"26">t e x t g 1 1 / 8 \ t e x t<"27">t e x t c 1 1 / 1 ] , [ \ p i t c h s y s t e m<" diatonic ">p i t c h s y s
\ c o m m e n t s t y l e<" referenced ">c o m m e n t s t y l e \ m e t e r<"2/4">m e t e r \ k e y<"c">k e y \ s c a l e<"
\ t e x t<"28">t e x t c 0 1 / 2 \ t e x t<"29">t e x t c 0 1 / 2 \ t e x t<"30">t e x t f 0 1/2
\ t e x t<"31">t e x t c 0 1 / 2 \ t e x t<"32">t e x t f 0 1 / 2 \ t e x t<"33">t e x t c 0 1/2
\ t e x t<"34">t e x t g 0 1 / 2 \ t e x t<"35">t e x t c 0 1 / 2 \ t e x t<"36">t e x t g 11/2
\ t e x t<"37">t e x t c 0 1 / 2 ] }

CHAPTER 6. CODE AND TESTING

100

TERMINAL
7 ? t e s t p a r s e r g r a m m a r ( simple3 .gmn , simple3out .gmn ) .
T e s t i n g GUIDO P a r s e r /Grammar >
P a r s i n g s i m p l e 3 . gmn
P a r s e OK
Score t r e e :
event ( 1 , 1 , p i t c h ( 1 2 , 7 ) , dur ( 1 / 8 ) , time ( 0 , 1 5 0 0 ) , s t a r t , [ ] , 2 ,
G2519 , G2520 , G2521 ) e v e n t ( 2 , 1 , p i t c h ( 1 4 , 8 ) , d u r ( 1 / 8 ) , t i m e ( 1 5 0 0 ,
3 0 0 0 ) , 1 , [ 2 8 ] , 3 , G2557 , G2558 , G2559 ) e v e n t ( 3 , 1 , p i t c h ( 1 6 , 9 ) ,
d u r ( 1 / 8 ) , t i m e ( 3 0 0 0 , 4 5 0 0 ) , 2 , [ 2 8 ] , 4 , G2595 , G2596 , G2597 )
event ( 4 , 1 , p i t c h ( 1 7 , 1 0 ) , dur ( 1 / 8 ) , time ( 4 5 0 0 , 6 0 0 0 ) , 3 , [ 2 8 ] , 5 ,
G2633 , G2634 , G2635 ) e v e n t ( 5 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 4 ) ,
t i m e ( 6 0 0 0 , 9 0 0 0 ) , 4 , [ ] , 6 , G2683 , G2684 , G2685 ) e v e n t ( 6 , 1 ,
p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 4 ) , t i m e ( 9 0 0 0 , 1 2 0 0 0 ) , 5 , [ 2 9 ] , 7 , G2721 ,
G2722 , G2723 ) e v e n t ( 7 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 1 2 0 0 0 ,
1 3 5 0 0 ) , 6 , [ ] , 8 , G2786 , G2787 , G2788 ) e v e n t ( 8 , 1 , p i t c h ( 2 1 , 1 2 ) ,
d u r ( 1 / 8 ) , t i m e ( 1 3 5 0 0 , 1 5 0 0 0 ) , 7 , [ 3 0 ] , 9 , G2824 , G2825 , G2826 )
event ( 9 , 1 , p i t c h ( 2 1 , 1 2 ) , dur ( 1 / 8 ) , time ( 1 5 0 0 0 , 1 6 5 0 0 ) , 8 , [ 3 0 ] , 1 0 ,
G2862 , G2863 , G2864 ) e v e n t ( 1 0 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) ,
t i m e ( 1 6 5 0 0 , 1 8 0 0 0 ) , 9 , [ 3 0 ] , 1 1 , G2900 , G2901 , G2902 ) e v e n t ( 1 1 , 1 ,
p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 2 ) , t i m e ( 1 8 0 0 0 , 2 4 0 0 0 ) , 1 0 , [ ] , 1 2 , G2950 ,
G2951 , G2952 ) e v e n t ( 1 2 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 4 0 0 0 ,
2 5 5 0 0 ) , 1 1 , [ ] , 1 3 , G3012 , G3013 , G3014 ) e v e n t ( 1 3 , 1 , p i t c h ( 2 1 ,
1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 5 5 0 0 , 2 7 0 0 0 ) , 1 2 , [ 3 2 ] , 1 4 , G3050 , G3051 ,
G3052 ) e v e n t ( 1 4 , 1 , p i t c h ( 2 1 , 1 2 ) , d u r ( 1 / 8 ) , t i m e ( 2 7 0 0 0 , 2 8 5 0 0 ) , 1 3 ,
[ 3 2 ] , 1 5 , G3088 , G3089 , G3090 ) e v e n t ( 1 5 , 1 , p i t c h ( 2 1 , 1 2 ) ,
d u r ( 1 / 8 ) , t i m e ( 2 8 5 0 0 , 3 0 0 0 0 ) , 1 4 , [ 3 2 ] , 1 6 , G3126 , G3127 , G3128 )
event ( 1 6 , 1 , p i t c h ( 1 9 , 1 1 ) , dur ( 1 / 2 ) , time ( 3 0 0 0 0 , 3 6 0 0 0 ) , 1 5 , [ ] , 1 7 ,
G3176 , G3177 , G3178 ) e v e n t ( 1 7 , 1 , p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) ,
t i m e ( 3 6 0 0 0 , 3 7 5 0 0 ) , 1 6 , [ ] , 1 8 , G3226 , G3227 , G3228 ) e v e n t ( 1 8 , 1 ,
p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) , t i m e ( 3 7 5 0 0 , 3 9 0 0 0 ) , 1 7 , [ 3 4 ] , 1 9 , G3264 ,
G3265 , G3266 ) e v e n t ( 1 9 , 1 , p i t c h ( 1 7 , 1 0 ) , d u r ( 1 / 8 ) , t i m e ( 3 9 0 0 0 ,
4 0 5 0 0 ) , 1 8 , [ 3 4 ] , 2 0 , G3302 , G3303 , G3304 ) e v e n t ( 2 0 , 1 , p i t c h ( 1 7 ,
1 0 ) , d u r ( 1 / 8 ) , t i m e ( 4 0 5 0 0 , 4 2 0 0 0 ) , 1 9 , [ 3 4 ] , 2 1 , G3340 , G3341 ,
G3342 ) e v e n t ( 2 1 , 1 , p i t c h ( 1 6 , 9 ) , d u r ( 1 / 4 ) , t i m e ( 4 2 0 0 0 , 4 5 0 0 0 ) , 2 0 ,
[ ] , 2 2 , G3390 , G3391 , G3392 ) e v e n t ( 2 2 , 1 , p i t c h ( 1 6 , 9 ) , d u r ( 1 / 4 ) ,
t i m e ( 4 5 0 0 0 , 4 8 0 0 0 ) , 2 1 , [ 3 5 ] , 2 3 , G3428 , G3429 , G3430 ) e v e n t ( 2 3 , 1 ,

CHAPTER 6. CODE AND TESTING

101

p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 4 8 0 0 0 , 4 9 5 0 0 ) , 2 2 , [ ] , 2 4 , G3478 ,
G3479 , G3480 ) e v e n t ( 2 4 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 4 9 5 0 0 ,
5 1 0 0 0 ) , 2 3 , [ 3 6 ] , 2 5 , G3516 , G3517 , G3518 ) e v e n t ( 2 5 , 1 , p i t c h ( 1 9 ,
1 1 ) , d u r ( 1 / 8 ) , t i m e ( 5 1 0 0 0 , 5 2 5 0 0 ) , 2 4 , [ 3 6 ] , 2 6 , G3554 , G3555 ,
G3556 ) e v e n t ( 2 6 , 1 , p i t c h ( 1 9 , 1 1 ) , d u r ( 1 / 8 ) , t i m e ( 5 2 5 0 0 , 5 4 0 0 0 ) , 2 5 ,
[ 3 6 ] , 2 7 , G3592 , G3593 , G3594 ) e v e n t ( 2 7 , 1 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 1 ) ,
t i m e ( 5 4 0 0 0 , 6 6 0 0 0 ) , 2 6 , [ ] , end , G3642 , G3643 , G3644 ) e v e n t ( 2 8 , 2 ,
p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 0 , 6 0 0 0 ) , s t a r t , [ 1 ] , 2 9 , G3874 , G3875 ,
G3876 ) e v e n t ( 2 9 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 6 0 0 0 , 1 2 0 0 0 ) , 2 8 ,
[ 5 ] , 3 0 , G3912 , G3913 , G3914 ) e v e n t ( 3 0 , 2 , p i t c h ( 5 , 3 ) , d u r ( 1 / 2 ) ,
t i m e ( 1 2 0 0 0 , 1 8 0 0 0 ) , 2 9 , [ 7 ] , 3 1 , G3950 , G3951 , G3952 ) e v e n t ( 3 1 , 2 ,
p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 1 8 0 0 0 , 2 4 0 0 0 ) , 3 0 , [ 1 1 ] , 3 2 , G3988 ,
G3989 , G3990 ) e v e n t ( 3 2 , 2 , p i t c h ( 5 , 3 ) , d u r ( 1 / 2 ) , t i m e ( 2 4 0 0 0 ,
3 0 0 0 0 ) , 3 1 , [ 1 2 ] , 3 3 , G4026 , G4027 , G4028 ) e v e n t ( 3 3 , 2 , p i t c h ( 0 ,
0 ) , d u r ( 1 / 2 ) , t i m e ( 3 0 0 0 0 , 3 6 0 0 0 ) , 3 2 , [ 1 6 ] , 3 4 , G4064 , G4065 ,
G4066 ) e v e n t ( 3 4 , 2 , p i t c h ( 7 , 4 ) , d u r ( 1 / 2 ) , t i m e ( 3 6 0 0 0 , 4 2 0 0 0 ) , 3 3 ,
[ 1 7 ] , 3 5 , G4102 , G4103 , G4104 ) e v e n t ( 3 5 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) ,
t i m e ( 4 2 0 0 0 , 4 8 0 0 0 ) , 3 4 , [ 2 1 ] , 3 6 , G4140 , G4141 , G4142 ) e v e n t ( 3 6 , 2 ,
p i t c h ( 5 , 3 ) , d u r ( 1 / 2 ) , t i m e ( 4 8 0 0 0 , 5 4 0 0 0 ) , 3 5 , [ 2 3 ] , 3 7 , G4178 ,
G4179 , G4180 ) e v e n t ( 3 7 , 2 , p i t c h ( 0 , 0 ) , d u r ( 1 / 2 ) , t i m e ( 5 4 0 0 0 ,
6 0 0 0 0 ) , 3 6 , [ 2 7 ] , end , G4216 , G4217 , G4218 )
endm ar k e r
Tags :
g l o b a l ( tag ( pi t c h s y s t e m ) , d i a t o n i c ) .
g l o b a l ( tag ( comment style ) , r e f e r e n c e d ) .
g l o b a l ( tag ( meter ) , 2 / 4 ) .
g l o b a l ( d u r a t i o n , dur ( 1 / 2 ) ) .
global ( octave , 0 ) .
global ( part , 3 ) .
g l o b a l ( num parts , 2 ) .
g l o b a l ( tag ( key ) , c ) .
g l o b a l ( tag ( s c a l e ) , c major ) .
G e n e r a t i n g s i m p l e 3 o u t . gmn
Succeeded .

6.4
6.4.1

The Pelog library


main.pl

Code
%%%%%%%%%%%%%%%%%%%%%%%
% LIBRARIES MAIN MODULE
%%%%%%%%%%%%%%%%%%%%%%%
% Simply e ns u r e s that a l l the Pelog user l i b r a r i e s are loaded
: e n s u r e l o a d e d ( comments ) .

CHAPTER 6. CODE AND TESTING


:
:
:
:
:
:
:
:
:

ensure
ensure
ensure
ensure
ensure
ensure
ensure
ensure
ensure

6.4.2

102

loaded ( event ) .
loaded ( global ).
loaded ( interval ).
loaded ( parts ).
loaded ( penalty ).
loaded ( pitch ).
loaded ( range ) .
loaded ( scale ).
l o a d e d ( time ) .

comments.pl

Code
/
COMMENTS MANAGER
M i c h a e l Droettboom
1999

T h i s l i b r a r y p r o v i d e s a mechanism t o s t o r e comment s t r i n g s w i t h e a c h e v e n t .
These comments can be u s e d t o p r o v i d e f e e d b a c k a b o u t e v e n t s w i t h i n t h e
score .
Comments a r e added t o t h e o u t p u t f i l e i n t h e s t y l e s p e c i f i e d by t h e
comment style tag i n the input f i l e .
/

/
USER PREDICATE
comment(+ e v e n t , + comment )
I f comment i s i n s t a n t i a t e d , comment i s added t o e v e n t s
event l i s t .
I f comment i s a v a r i a b l e , u n i f i e s w i t h any comment c u r r e n t l y
i n e v e n t s comment l i s t
/
: op ( 1 5 0 , x f x , comment ) .
comment ( E , Comment ) :
n o n v a r ( Comment ) ,
add comment ( E , Comment ) .
comment ( E , Comment ) :
v a r ( Comment ) ,
in comment ( E , Comment ) .

CHAPTER 6. CODE AND TESTING

103

ADDING COMMENTS TO EVENTS


Comment l i s t s a r e i m p l e m e n t e d a s i m p e r f e c t P r o l o g l i s t s ( where
the l a s t element in the l i s t i s a v a r i a b l e ) .
/
add
add
add
add

comment ( E , Comment ) : e comment ( E , [ Comment | ] ) , ! .


comment ( E , Comment ) : e comment ( E , X ) , add comment0 ( Comment , X ) , ! .
comment0 ( Comment , [ Comment | ] ) : ! .
comment0 ( Comment , [ | T a i l ] ) : add comment0 ( Comment , T a i l ) .

/
CHECKING FOR THE EXISTENCE OF A COMMENT
/
in comment ( E , Comment ) :
e comment ( E , CommentList ) ,
in comment0 ( Comment , CommentList ) .
in comment0 ( , [ Head | ] ) :
v a r ( Head ) , ! ,
fail .
in comment0 ( Comment , [ Head | T a i l ] ) :
Comment = Head
;
in comment0 ( Comment , T a i l ) .

/
OUTPUT A LIST OF COMMENTS IN USERREADABLE FORM
e p r e t t y c o m m e n t (+E , Comment )
Comment i s a P r o l o g s t r i n g c o n t a i n i n g t h e e v e n t s comment
l i s t i s a u s e rr e a d a b l e form
/
e p r e t t y c o m m e n t ( E , Comment ) :
e comment ( E , L ) ,
e p r e t t y c o m m e n t 0 ( L , , Comment ) .
e p r e t t y c o m m e n t 0 ( Var ,
, ) :
% I f t h e l i s t e l e m e n t i s a v a r i a b l e , t h e r e was an e r r o r
% i n t h e l i s t c r e a t i o n and we s h o u l d s t o p r e c u r s i o n
v a r ( Var ) , ! .
e p r e t t y c o m m e n t 0 ( [ Head | T a i l ] , X , Y) :
% The l a s t good e l e m e n t i n t h e l i s t
var ( Tail ) , ! ,
c o n c a t (X , Head , Y ) .
e p r e t t y c o m m e n t 0 ( [ Head | T a i l ] , X , Y) :
% Concatenate each element to the output s t r i n g

CHAPTER 6. CODE AND TESTING

104

% and r e c u r s e
c o n c a t (X , Head , Y0 ) ,
c o n c a t ( Y0 , , , Y1 ) ,
e p r e t t y c o m m e n t 0 ( T a i l , Y1 , Y ) .

/
ADDING EXTRA INFORMATION TO EVENTS
T h i s p r e d i c a t e ad d s a n o t h e r b i t o f a r b i t r a r y i n f o r m a t i o n
to a given event .
There i s ( a s o f y e t ) no p r a c t i c a l u s e f o r t h i s f e a t u r e and
i t i s included f o r the sake of e x p a n d i b i l i t y only
/
a d d e x t r a (E , Extra ) : e e x t r a (E , [ Extra | ] ) , ! .
a d d e x t r a ( E , E x t r a ) : e e x t r a ( E , X ) , add comment0 ( E x t r a , X ) , ! .

/
TEST CODE
/
test comment :
add comment ( E , Bach ) ,
add comment ( E , Beethoven ) ,
w r i t e ( Event with comments Bach & Beethoven : ) , n l ,
w r i t e (E ) , n l ,
in comment ( E , Bach ) ,
w r i t e ( in_comment check for Bach succeeded ) , n l ,
in comment ( E , Beethoven ) ,
w r i t e ( in_comment check for Beethoven succeeded ) , n l ,
n o t in comment ( E , Brahms ) ,
w r i t e ( in_comment check for Brahms failed ) , n l ,
e pretty comment (E , P r e t t y ) ,
w r i t e ( Pretty comment : ) , n l ,
write ( Pretty ).

Testing
? t e s t c o m m e n t .
E v e n t w i t h comments Bach & B e e t h o v e n :
e v e n t ( G327 , G328 , G329 , G330 , G331 , G332 ,
[ Bach , B e e t h o v e n | G340 ] , G336 , G337 )
in comment c h e c k f o r Bach s u c c e e d e d
in comment c h e c k f o r B e e t h o v e n s u c c e e d e d
in comment c h e c k f o r Brahms f a i l e d
P r e t t y comment :
Bach , B e e t h o v e n
Yes

G333 ,

G334 ,

CHAPTER 6. CODE AND TESTING

6.4.3

105

event.pl

Code
/
EVENT LIBRARY
M i c h a e l Droettboom
May 1 9 9 9

T h i s i s t h e c o r e module o f t h e e v e n t d a t a s t r u c t u r e . For more


i n f o r m a t i o n on t h i s d a t a s t r u c t u r e , p l e a s e s e e t h e i m p l e m e n t a t i o n
documentation .
/
/
REQUIRED LIBRARIES
/
:
:
:
:

ensure
ensure
ensure
ensure

l o a d e d ( time ) .
loaded ( pitch ).
l o a d e d ( ../ core/ trees .pl ) .
l o a d e d ( comments ) .

/
USER PREDICATES
f i r s t e v e n t (+ e v e n t )
succeeds i f event i s the f i r s t event in i t s part
l a s t e v e n t (+ e v e n t )
succeeds i f event i s the l a s t event in i t s part
p e n u l t i m a t e e v e n t (+ e v e n t s )
succeeds i f event i s the penultimate event in i t s part
e v e n t s i s o f t h e form ( T , E ) where T i s t h e s c o r e t r e e and
E i s the c u r r e n t event . This i s handled behind the scenes
by t h e r u l e a p p l i c a t i o n mechanism .
/
: op ( 1 5 0 , x f , f i r s t e v e n t ) .
: op ( 1 5 0 , x f , l a s t e v e n t ) .
: op ( 1 5 0 , x f , p e n u l t i m a t e e v e n t ) .
f i r s t e v e n t (E) :
e p r e v (E , S ) ,
nonvar (S ) ,

CHAPTER 6. CODE AND TESTING

106

S = start .
l a s t e v e n t (E) :
e n e x t (E , S ) ,
nonvar (S ) ,
S = end .
p e n u l t i m a t e e v e n t ( (T , E ) ) :
g e t n e x t e v e n t (T , E , EN ) ,
EN \= end ,
l a s t e v e n t (EN ) .

/
DATA STRUCTURE DEFINITION
These p r e d i c a t e s r e t r i e v e d i f f e r e n t f i e l d s o f t h e d a t a s t r u c t u r e
The d a t a s t r u c t u r e i s d e f i n e d a s f o l l o w s :
e v e n t ( I n d e x , P a r t , p i t c h (CPC , CNC ) , d u r (Num , Den ) ,
t i m e ( S t a r t , End ) , P r e v I n d e x , C o n c u r r e n t I n d e x L i s t , N e x t I n d e x ,
[ Comment ] , P e n a l t y , [ E x t r a ] ) .
Note t h a t t h e s e p r e d i c a t e s c o u l d have been d e f i n e d u s i n g t h e
b u i l ti n p r e d i c a t e a r g / 3 , b u t t h a t would n o t a l l o w t h e s y s t e m
t o g e n e r a t e new e v e n t s ont h e f l y . For i n s t a n c e , t h e q u e r y
e i n d e x (E , 1 )
c r e a t e s a new e v e n t w i t h i n d e x 1 :
E = e v e n t ( 1 , G352 , G353 ,
G359 , G360 , G361 )

G354 ,

G355 ,

G356 ,

G357 ,

G358 ,

The e q u i v a l e n t i m p l e m e n t a t i o n u s i n g a r g / 3 :
e i n d e x (E , Index ) : arg ( 1 , E , Index ) .
would c a u s e an e r r o r i f E were a v a r i a b l e :
[ WARNING : a r g / 3 : Arguments a r e n o t s u f f i c i e n t l y i n s t a n t i a t e d ]
Exception : (
8 ) a r g ( 1 , G265 , 1 ) ?
/
e
e
e
e
e
e
e

index ( event ( Index ,


,
,
,
,
p a r t ( event ( , Part ,
,
,
,
,
pitch ( event ( ,
, Pitch ,
,
,
duration ( event ( ,
,
, Duration
time ( event ( ,
,
,
, Time ,
,
prev ( event ( ,
,
,
,
, Prev ,
v e r t i c a l ( event ( ,
,
,
,
,
,

) , Index
) , Part ) .
,
,
,
,
,
) , Pitch
,
,
,
,
,
,
,
),
,
,
,
,
) , Time ) .
,
,
,
,
) , Prev ) .
Vertical ,
,
,
,
),
,

).

).
Duration ) .

Vertical ).

CHAPTER 6. CODE AND TESTING


e
e
e
e

107

next ( event ( ,
,
,
,
,
,
, Next ,
,
,
) , Next ) .
comment ( e v e n t ( ,
,
,
,
,
,
,
, Comment ,
,
) , Comment ) .
penalty ( event ( ,
,
,
,
,
,
,
,
, Penalty ,
) , Penalty ) .
extra ( event ( ,
,
,
,
,
,
,
,
,
, Extra ) , Extra ) .

/
EVENT TRAVERSAL
g e t n e x t e v e n t (+ s c o r et r e e , + e v e n t , n e x t )
g e t p r e v e v e n t (+ s c o r et r e e , + e v e n t , p r e v )
Note t h a t i f t h e r e i s no n e x t o r p r e v i o u s e v e n t i n t h e g i v e n
p a r t ( i . e . e v e n t i s t h e f i r s t o r l a s t e v e n t ) r e t u r n s t h e atoms
s t a r t o r end .
/
g e t n e x t e v e n t ( , end , end ) : ! .
g e t n e x t e v e n t (T , E , EN) :
e n e x t (E , INext ) ,
g e t n e x t e v e n t 0 (T , I N e x t , EN ) .
g e t n e x t e v e n t 0 ( , end , end ) : ! .
g e t n e x t e v e n t 0 (T , N , EN) :
i n t e g e r (N ) , ! ,
g e t l a b e l (N , T , EN ) .

get prev event ( , start , start ) : !.


g e t p r e v e v e n t (T , E , EP ) :
e p r e v (E , IPre v ) ,
g e t p r e v e v e n t 0 (T , I P r e v , EP ) .
get prev event0 ( , start , start ) : !.
g e t p r e v e v e n t 0 (T , N , EP ) :
i n t e g e r (N ) , ! ,
g e t l a b e l (N , T , EP ) .

/
VERTICAL RELATIONSHIPS
/
g e t v e r t i c a l (T , E , EV ) :
e v e r t i c a l (E , VList ) ,
member (N , V L i s t ) ,
g e t l a b e l (N , T , EV ) .
get vertical ( , E, []) :
e v e r t i c a l (E , [ ] ) .

CHAPTER 6. CODE AND TESTING

6.4.4

108

global.pl

Code
/
GLOBAL VARIABLE MANAGER
M i c h a e l Droettboom
1999

L i b r a r y f o r s i m u l a t i n g the concept of g l o b a l v a r i a b l e s .
v a r i a b l e s may h o l d o n l y one v a l u e a t a g i v e n t i m e .

Global

The g l o b a l l i b r a r y can be u s e d t o e x t e n d t h e GUIDO p a r s e r t o


r e c o g n i z e new t a g s . S i m p l y add a g l o b a l d e f a u l t p r e d i c a t e t o y o u r
Pelog r u l e s e t f i l e .
g l o b a l d e f a u l t p r e d i c a t e s a r e o f t h e form :
g l o b a l d e f a u l t ( t a g ( tagname ) , d e f a u l t )
tagname i s t h e name o f t h e t a g
d e f a u l t i s the d e f a u l t v a l u e f o r the tag i f the tag i s not
present in the input f i l e
The v a l u e o f t h e t a g can be u s e d i n y o u r r u l e s a t r u n t i m e u s i n g
the tag p r e d i c a t e .
/
/
PREDICATE SETTINGS
/
: m u l t i f i l e
global /2,
g l o b a l d e f a u l t /2.

/
USER PREDICATES
t a g (+tagname , v a l u e )
S u c c e e d s i f tagname i s a s s i g n e d t o v a l u e .
Tags t h a t a r e a s s i g n e d t o p a r t s ( i . e . n o t g l o b a l t o t h e s c o r e
a r e r e t r i e v e d u s i n g t h e form t a g p a r t where t a g i s t h e tagname
and p a r t i s a p a r t name .
/
: op ( 1 5 0 , x f x , t a g ) .
: op ( 1 0 0 , x f x , ) .

CHAPTER 6. CODE AND TESTING

109

t a g ( Name P a r t , V a l u e ) :
i n t e g e r ( Part ) , ! ,
g l o b a l ( t a g (Name P a r t ) , V a l u e ) .
t a g ( Name P a r t , V a l u e ) :
not i n t e g e r ( Part ) , ! ,
num parts ( Parts ) ,
p a r t d e f ( P a r t , PartNo , P a r t s ) ,
g l o b a l ( t a g (Name PartNo ) , V a l u e ) .
t a g (Name , V a l u e ) :
g l o b a l ( t a g (Name ) , V a l u e ) .

/
SETTING GLOBAL VARIABLES
/
g l o b a l s e t (X , Y) :
r e t r a c t a l l ( g l o b a l (X , ) ) ,
a s s e r t ( g l o b a l (X , Y ) ) .
s e t a l l d e f a u l t s :
f o r a l l ( g l o b a l d e f a u l t (X , Y ) ,
g l o b a l s e t (X , Y ) ) .

Testing
7 ? g l o b a l s e t ( t e s t , Beethoven ) .
Yes
8 ? g l o b a l ( t e s t , X ) .
X = Beethoven ;
No
9 ? g l o b a l s e t ( t e s t , Bach ) .
Yes
10 ? g l o b a l ( t e s t , X ) .
X = Bach ;
No
11 ? g l o b a l s e t ( t a g ( composer ) , Mozart ) .
Yes
12 ? composer t a g X .
X = Mozart ;
No
13 ? X t a g Mozart .

CHAPTER 6. CODE AND TESTING

110

X = composer ;

6.4.5

interval.pl

Code
/
INTERVAL LIBRARY
M i c h a e l Droettboom
1999

L i b r a r y f o r d e a l i n g w i t h t h e r e l a t i o n s h i p between two p i t c h e s .
I n P e l o g , i n t e r v a l s can be s p e c i f i e d i n t h e form t y p e s i z e , where
t y p e i s a t h r e ec h a r a c t e r atom s p e c i f y i n g t h e t y p e o f i n t e r v a l .
The a v a i l a b l e t y p e s a r e l i s t e d b e l o w .
type

maj
min
per
dim
aug
chr
sem
tri

meaning

major
minor
perfect
diminished
augmented
chromatic
semitones ( s i z e f i e l d
tritone

i s # semitones )

For e x a m p l e , min 3 i s a m i n o r t h i r d and maj 7 i s a m a j o r s e v e n t h .


The sem t y p e r e t u r n s t h e s i z e f i e l d a s t h e number o f s e m i t o n e s ,
r e g a r d l e s s o f n o t e names . T h e r e f o r e , maj 3 i s e q u i v a l e n t t o sem 4 .
Note t h a t e i t h e r t h e t y p e o r s i z e f i e l d may be l e f t v a r i a b l e u s i n g
P r o l o g s anonymous v a r i a b l e , t h e u n d e r s c o r e
.
Therefore , 3 w i l l
s p e c i f y dim 3 , min 3 , maj 3 and aug 3 .
I n t e r v a l s can a l s o be s p e c i f i e d u s i n g o t h e r k e y w o r d s :
type

unison
step
skip
consonant
dissonant

meaning

U n i s o n ( t h e two g i v e n n o t e s hav e t h e same p i t c h )


A second
Any i n t e r v a l l a r g e r t h a n a s e c o n d
Any t h i r d s , f i f t h s , s i x t h s o r o c t a v e s t h a t a r e n o t
d i m i n i s h e d o r augmented
Any s e c o n d s , f o u r t h s o r s e v e n t h s

CHAPTER 6. CODE AND TESTING

111

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( p i t c h ) .
: e n s u r e l o a d e d ( ../ core/bi-math.pl ) .
: e n s u r e l o a d e d ( global .pl ) .

/
OPERATORS
/
: op ( 1 2 5 , x f x , ) .
: op ( 1 0 0 , f y , ) .
: op ( 1 2 5 , x f x , : ) .

/
USER FUNCTIONS
a b s o l u t e i n t e r v a l (? eventa : ? eventb , ? i n t e r v a l [ contour ? d i r ] )
S u c c e e d s i f t h e a b s o l u t e i n t e r v a l between two e v e n t s , e v e n t a
and e v e n t b , i s e q u a l t o i n t e r v a l .
i n t e r v a l may be i n any form
s p e c i f i e d i n the i n t r o d u c t i o n to the i n t e r v a l l i b r a r y .
c y c l i c i n t e r v a l (? eventa : ? eventb , ? i n t e r v a l [ contour ? d i r ] )
S u c c e e d s i s t h e c y c l i c i n t e r v a l between two e v e n t s , e v e n t a and
eventb , i s equal to i n t e r v a l .
i n t e r v a l may be i n any form
s p e c i f i e d i n the i n t r o d u c t i o n to the i n t e r v a l l i b r a r y .
The o p t i o n a l c o n t o u r u n i f i e s w i t h up i f e v e n t b i s h i g h e r i n p i t c h
t h a n e v e n t a , down i f e v e n t b i s l o w e r i n p i t c h t h a n e v e n t a , and
s t a t i c i f e v e n t b i s t h e same p i t c h a s e v e n t a .
contour (? eventa : ? eventb , ? d i r )
Determines d i r e c t i o n of i n t e r v a l .
/
: op ( 1 4 9 , x f x , c o n t o u r ) .
: op ( 1 5 0 , x f x , c i n t e r v a l ) .
: op ( 1 5 0 , x f x , c y c l i c i n t e r v a l ) .
c i n t e r v a l ( FromP : ToP , I c o n t o u r D i r ) : ! ,
c y c l i c i n t e r v a l ( FromP , ToP , I , D i r ) .
c i n t e r v a l ( FromP : ToP , I ) :
c y c l i c i n t e r v a l ( FromP , ToP , I ,
).
c y c l i c i n t e r v a l ( FromP : ToP , I c o n t o u r D i r ) : ! ,

CHAPTER 6. CODE AND TESTING

112

c y c l i c i n t e r v a l ( FromP , ToP , I , D i r ) .
c y c l i c i n t e r v a l ( FromP : ToP , I ) :
c y c l i c i n t e r v a l ( FromP , ToP , I ,
).
c y c l i c i n t e r v a l ( FromP , ToP , I , D i r ) :
c o n t p i t c h c l a s s ( FromP , FromCPC ) ,
c o n t n o t e c l a s s ( FromP , FromCNC ) ,
c o n t p i t c h c l a s s ( ToP , ToCPC ) ,
c o n t n o t e c l a s s ( ToP , ToCNC ) ,
c y c l i c i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , I , D i r ) .
% Note : c y c l i c i n t e r v a l c p comes i n m u l t i p l e f l a v o u r s d e p e n d i n g on
% wh ich a r g u m e n t s a r e i n s t a n t i a t e d . When t h e p i t c h e s a r e known , t h e i r
% d i f f e r e n c e s a r e c a l c u l a t e d f i r s t . When t h e i n t e r v a l name i s known ,
% i t s value i s calculated f i r s t .
c y c l i c i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , IC I , D i r ) :
nonvar ( I ) ,
I =< 8,
factor ( I0 , I , 7 ,
),
i n t e r v a l ( IC I 0 , Dir , CPC , CNC ) ,
p l u s ( FromCPC , CPC , ToCPC ) ,
p l u s ( FromCNC , CNC , ToCNC ) .
c y c l i c i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , IC I , Dir ) :
v ar ( I ) ,
p l u s ( FromCPC , CPC , ToCPC ) ,
p l u s ( FromCNC , CNC , ToCNC ) ,
i n t e r v a l ( IC I 1 , Dir , CPC , CNC ) ,
I i s ( I 1 mod 7 ) .
c y c l i c i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , I , Dir ) :
nonvar ( I ) ,
atomic ( I ) ,
i n t e r v a l ( I , Dir , CPC , CNC ) ,
p l u s ( FromCPC , CPC , ToCPC ) ,
p l u s ( FromCNC , CNC , ToCNC ) .
: op ( 1 5 0 , x f x , a b s i n t e r v a l ) .
: op ( 1 5 0 , x f x , a i n t e r v a l ) .
a b s o l u t e i n t e r v a l ( FromP : ToP , I c o n t o u r Dir ) : ! ,
a b s o l u t e i n t e r v a l ( FromP , ToP , I , Dir ) .
a b s o l u t e i n t e r v a l ( FromP : ToP , I ) :
a b s o l u t e i n t e r v a l ( FromP , ToP , I ,
).
a i n t e r v a l ( FromP : ToP , I c o n t o u r Dir ) : ! ,
a b s o l u t e i n t e r v a l ( FromP , ToP , I , Dir ) .
a i n t e r v a l ( FromP : ToP , I ) :
a b s o l u t e i n t e r v a l ( FromP , ToP , I ,
).
a b s o l u t e i n t e r v a l ( FromP , ToP , I , Dir ) :
c o n t p i t c h c l a s s ( FromP , FromCPC ) ,
c o n t n o t e c l a s s ( FromP , FromCNC ) ,
c o n t p i t c h c l a s s (ToP , ToCPC ) ,

CHAPTER 6. CODE AND TESTING

113

c o n t n o t e c l a s s ( ToP , ToCNC ) ,
a b s o l u t e i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , I , Dir ) .
% Note : a b s o l u t e i n t e r v a l c p comes i n m u l t i p l e f l a v o u r s d e p e n d i n g on
% whi c h a r g u m e n t s a r e i n s t a n t i a t e d . When t h e p i t c h e s a r e known , t h e i r
% d i f f e r e n c e s a r e c a l c u l a t e d f i r s t . When t h e i n t e r v a l name i s known ,
% i t s value i s calculated f i r s t .
a b s o l u t e i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , I , Dir ) :
nonvar ( FromCPC ) , nonvar ( FromCNC ) , nonvar (ToCPC ) , nonvar (ToCNC ) , ! ,
p l u s ( FromCPC , CPC , ToCPC ) ,
p l u s ( FromCNC , CNC , ToCNC ) ,
i n t e r v a l ( I , Dir , CPC , CNC ) .
a b s o l u t e i n t e r v a l c p ( FromCPC , FromCNC , ToCPC , ToCNC , I , Dir ) :
nonvar ( I ) ,
i n t e r v a l ( I , Dir , CPC , CNC ) ,
p l u s ( FromCPC , CPC , ToCPC ) ,
p l u s ( FromCNC , CNC , ToCNC ) .
c o n t o u r ( FromP : ToP , Dir ) :
c o n t o u r ( FromP , ToP , Dir ) .
c o n t o u r ( FromP , ToP , Dir ) :
c o n t p i t c h c l a s s ( FromP , FromCPC ) ,
c o n t p i t c h c l a s s ( ToP , ToCPC ) ,
c o n t o u r c p ( FromCPC , ToCPC , Dir ) .
c o n t o u r c p ( FromCPC , ToCPC , Dir ) :
(ToCPC > = FromCPC >
D i r = up
;
ToCPC = FromCPC >
Dir = s t a t i c
;
D i r = down ) .

PRIVATE PREDICATES

/
INTERVAL DEFINITION
i n t e r v a l ( ? I n t e r v a l C l a s s ? I n t e r v a l , ? PC , ? NC)
I n t e r v a l i s t h e i n t e r v a l from ( 0 , 0 ) t o ( PC , NC)
/
i n t e r v a l ( u n i s , up , 0 , 0 ) .

CHAPTER 6. CODE AND TESTING


i n t e r v a l ( I , up , PC , NC) :
( g l o b a l ( tag ( p it c h s y s t e m ) , d i a t o n i c )
;
g l o b a l ( t a g ( p i t c h s y s t e m ) , german )
;
g l o b a l ( tag ( pi t c h s y s t e m ) , s o l f e g e )
;
g l o b a l ( tag ( pi t c h s y s t e m ) , a l l ) ) ,
i n t e r v a l t o n a l ( I , PC , NC ) .
i n t e r v a l ( sem I , up , I , ) :
g l o b a l ( tag ( pi t c h s y s t e m ) , atonal ) .
i n t e r v a l ( sem I , down , PC , ) :
g l o b a l ( tag ( pi t c h s y s t e m ) , atonal ) ,
o p p o s i t e ( I , PC ) .
i n t e r v a l ( I , down , PC , NC) :
% a u t o m a t i c a l l y r e f l e c t i n t e r v a l s to
% downard c o n t o u r
i n t e r v a l ( I , up , PC1 , NC1 ) ,
o p p o s i t e ( PC1 , PC ) ,
o p p o s i t e (NC1 , NC ) .
% BASIC INTERVALS WITHIN THE FIRST OCTAVE
i n t e r v a l b a s i c ( per 1 , 0 , 0 ) . % p e r f e c t unison
i n t e r v a l b a s i c ( chr 1 , 1 , 0 ) . % chromatic i n t e r v a l
interval
interval
interval
interval

b a s i c ( dim 2 ,
b a s i c ( min 2 ,
b a s i c ( maj 2 ,
b a s i c ( aug 2 ,

0,
1,
2,
3,

1)
1)
1)
1)

.
.
.
.

interval
interval
interval
interval

b a s i c ( dim 3 ,
b a s i c ( min 3 ,
b a s i c ( maj 3 ,
b a s i c ( aug 3 ,

2,
3,
4,
5,

2)
2)
2)
2)

.
.
.
.

i n t e r v a l b a s i c ( dim 4 , 4 , 3 ) .
i n t e r v a l b a s i c ( per 4 , 5 , 3 ) .
i n t e r v a l b a s i c ( aug 4 , 6 , 3 ) .
i n t e r v a l b a s i c ( t r i 4 , 6 , 3 ) . % t r i t o n e ( aug 4 )
i n t e r v a l b a s i c ( t r i 5 , 6 , 4 ) . % t r i t o n e ( dim 5 )
i n t e r v a l b a s i c ( dim 5 , 6 , 4 ) .
i n t e r v a l b a s i c ( per 5 , 7 , 4 ) .
i n t e r v a l b a s i c ( aug 5 , 8 , 4 ) .
interval
interval
interval
interval

b a s i c ( dim 6 ,
b a s i c ( min 6 ,
b a s i c ( maj 6 ,
b a s i c ( aug 6 ,

7, 5) .
8, 5) .
9, 5) .
10, 5) .

114

CHAPTER 6. CODE AND TESTING

interval
interval
interval
interval

b a s i c ( dim 7 ,
b a s i c ( min 7 ,
b a s i c ( maj 7 ,
b a s i c ( aug 7 ,

9, 6) .
10, 6) .
11, 6) .
12, 6) .

i n t e r v a l b a s i c ( dim 8 , 1 1 , 7 ) .
i n t e r v a l b a s i c ( per 8 , 1 2 , 7 ) .
i n t e r v a l b a s i c ( aug 8 , 1 3 , 7 ) .
% TONAL INTERVALS
i n t e r v a l t o n a l ( IC , PC , NC) :
i n t e r v a l b a s i c ( IC , PC , NC ) .
% COMPOUND INTERVALS
i n t e r v a l t o n a l ( IC I , PC , NC) :
n o n v a r (PC ) , n o n v a r (NC ) , v a r ( IC ) , v a r ( I ) ,
PC > 1 2 ,
f a c t o r (PC , PC1 , 1 2 , Oct ) ,
f a c t o r (NC , NC1 , 7 , Oct ) ,
i n t e r v a l b a s i c ( IC I L o c a l , PC1 , NC1 ) ,
I i s I L o c a l + ( Oct 8 ) 1 .
i n t e r v a l t o n a l ( IC I , PC , NC) :
n o n v a r ( IC ) , n o n v a r ( I ) , v a r (PC ) , v a r (NC ) ,
I L o c a l 0 i s ( I mod 7 ) ,
( I L o c a l 0 = 1 > I L o c a l = 8 ; I L o c a l i s I L o c a l 0 ) ,
Oct i s I / / 8 ,
i n t e r v a l b a s i c ( IC I L o c a l , PC1 , NC1 ) ,
f a c t o r (PC , PC1 , 1 2 , Oct ) ,
PC > 1 2 ,
f a c t o r (NC , NC1 , 7 , Oct ) .
i n t e r v a l t o n a l ( IC I , PC , NC) :
i n t e r v a l b a s i c ( IC I L o c a l , PC1 , NC1 ) ,
f a c t o r (PC , PC1 , 1 2 , Oct ) ,
PC > 1 2 ,
f a c t o r (NC , NC1 , 7 , Oct ) ,
I i s I L o c a l + ( Oct 7 ) .
% SKIPS AND STEPS
i n t e r v a l t o n a l ( s t e p , PC , 1 ) :
i n t e r v a l b a s i c ( 2 , PC , 1 ) .
i n t e r v a l t o n a l ( s k i p , PC , NC) :
i n t e r v a l t o n a l ( , PC , NC ) ,
NC > 1 , PC > 2 .
% CONSONANCES AND DISSONANCES
i n t e r v a l t o n a l ( c o n s o n a n t , PC , NC) :

115

CHAPTER 6. CODE AND TESTING


g e t i n t e g e r (F ) ,
member ( I L o c a l , [ 3 , 5 , 6 , 8 ] ) ,
factor ( I , ILocal , 7 , F),
i n t e r v a l t o n a l ( Type I , PC , NC ) ,
Type \= dim ,
Type \= aug ,
Type \= t r i .
i n t e r v a l t o n a l ( d i s s o n a n t , PC , NC) :
i n t e r v a l t o n a l ( t r i , PC , NC ) .
i n t e r v a l t o n a l ( d i s s o n a n t , PC , NC) :
g e t i n t e g e r (F ) ,
member ( I L o c a l , [ 2 , 4 , 7 ] ) ,
factor ( I , ILocal , 7 , F),
i n t e r v a l t o n a l ( I , PC , NC ) .

Testing
? g l o b a l s e t ( t a g ( p i t c h s y s t e m ) , d i a t o n i c ) .
Yes
? p i t c h ( 0 , 0 ) : p i t c h ( 7 , 5 ) c i n t e r v a l I c o n t o u r C .
I = dim 6
C = up ;
No
? p i t c h ( 0 , 0 ) : X c i n t e r v a l dim 6 c o n t o u r up .
X = pitch ( 7 , 5 ) ;
X = pitch (19, 12) ;
X = pitch (19, 12) ;
X = pitch (31, 19) ;
X = pitch (31, 19) ;
X = pitch (43, 26) ;
X = pitch (43, 26) ;
X = pitch (55, 33) ;
X = pitch (55, 33) ;
X = pitch (67, 40) ;
X = pitch (67, 40) ;
X = pitch (79, 47) ;

116

CHAPTER 6. CODE AND TESTING

X = pitch (79, 47) ;


X = pitch (79, 47) ;
X = pitch (91, 54) ;
X = pitch (91, 54) ;
X = pitch (103, 61) ;
X = pitch (103, 61) ;
X = pitch (115, 68) ;
X = pitch (115, 68) ;
X = pitch (127, 75) ;
X = pitch (127, 75) ;
X = pitch (139, 82) ;
X = pitch (139, 82) ;
X = pitch (151, 89) ;
No
? p i t c h ( 1 2 , 7 ) : X a i n t e r v a l dim 6 c o n t o u r C .
X = pitch (19, 12)
C = up ;
X = pitch (5, 2)
C = down ;
No
? p i t c h ( 0 , 0 ) : p i t c h ( 2 , 1 ) a i n t e r v a l I .
I = maj 2 c o n t o u r up ;
I = s t e p c o n t o u r up ;
I = d i s s o n a n t c o n t o u r up ;
No
? p i t c h ( 0 , 0 ) : p i t c h ( 4 , 2 ) a i n t e r v a l I .
I = maj 3 c o n t o u r up ;
I = s k i p c o n t o u r up ;

117

CHAPTER 6. CODE AND TESTING

I = c o n s o n a n t c o n t o u r up ;
No
? p i t c h ( 0 , 0 ) : X a i n t e r v a l c o n s o n a n t .
X = pitch ( 3 , 2 ) ;
X = pitch ( 4 , 2 ) ;
X = pitch ( 7 , 4 ) ;
X = pitch ( 8 , 5 ) ;
X = pitch ( 9 , 5 ) ;
X = pitch (15, 9) ;
X = pitch (16, 9) ;
X = pitch (19, 11) ;
X = pitch (20, 12) ;
X = pitch (21, 12) ;
;
X = pitch (27, 16)

Yes
? p i t c h ( 0 , 0 ) : p i t c h ( 4 , 2 ) c o n t o u r C .
C = up

Yes
? p i t c h ( 4 , 2 ) : p i t c h ( 0 , 0 ) c o n t o u r C .
C = down ;
No

6.4.6

parts.pl

Code
/
PARTS MANAGER
M i c h a e l Droettboom
1999

118

CHAPTER 6. CODE AND TESTING

119

This l i b r a r y d e a l s with i n f o r m a t i o n about i n d i v i d u a l p a r t s


( monophonic l i n e s ) i n t h e s c o r e .
A p a r t can be r e f e r e n c e d t o by number , i n w h ic h c a s e t h e t o p p a r t
i s p a r t 1 , t h e n e x t h i g h e s t i s p a r t 2 , e t c . A p a r t can a l s o be
r e f e r e n c e d by a name d e f i n e d by t h e p a r t d e f f a c t p r e d i c a t e .
A d d i t i o n a l p a r t names can be d e f i n e d by a d d i n g p a r t d e f p r e d i c a t e s
t o t h e r u l e s e t f i l e . p a r t d e f p r e d i c a t e s hav e t h e form :
p a r t d e f ( name , p a r t n o , numparts )
name i s t h e name o f t h e p a r t
p a r t n o i s t h e c o r r e s p o n d i n g p a r t number
numparts i s t h e number o f p a r t s
/
/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( event ) .

/
USER FUNCTIONS
/
: op ( 1 5 0 , x f x , p a r t ) .
: op ( 1 5 0 , f x , n u m p a r t s ) .
p a r t ( E , Number ) :
e p a r t ( E , Number ) .
p a r t ( E , Text ) :
n o t i n t e g e r ( Text ) ,
e p a r t (E , P ) ,
p a r t (P , Text ) .
p a r t (P , Text ) :
i n t e g e r (P ) ,
n u m p a r t s (NP ) ,
p a r t d e f ( Text , P , NP ) .
num parts ( Parts ) :
g l o b a l ( num parts , Parts ) .
part
part
part
part
part

def ( only ,
, 1) : !.
d e f ( top , 1 ,
).
d e f ( bottom , X , X ) : X > 1 .
d e f ( c a n t u s f i r m u s , X , X) : X > 1.
d e f ( i n n e r , X , Y) :

CHAPTER 6. CODE AND TESTING

part
part
part
part

120

X > 1,
X \= Y .
d e f ( soprano , 1 , 4 ) .
def ( alto , 2 , 4 ) .
def ( tenor , 3 , 4 ) .
def ( bass , 4 , 4 ) .

Testing
? g l o b a l s e t ( n u m p a r t s , 1 ) .
Yes ? e p a r t ( E , 1 ) , p a r t ( E , X ) .
E = e v e n t ( G459 , 1 , G461 ,
G468 , G469 ) X = 1 ;

G462 ,

G463 ,

G464 ,

G465 ,

G466 ,

G467 ,

E = e v e n t ( G459 , 1 , G461 ,
G468 , G469 ) X = o n l y ;

G462 ,

G463 ,

G464 ,

G465 ,

G466 ,

G467 ,

G448 ,

G449 ,

G450 ,

G451 ,

G452 ,

No ? e p a r t ( E , 4 ) , p a r t ( E , 4 ) .
E = e v e n t ( G444 , 4 ,
G453 , G454 ) ;

G446 ,

G447 ,

No ? e p a r t ( E , 4 ) , p a r t ( E , X ) .
E = e v e n t ( G459 , 4 , G461 ,
G468 , G469 ) X = 4 ;

G462 ,

G463 ,

G464 ,

G465 ,

G466 ,

G467 ,

E = e v e n t ( G459 , 4 , G461 ,
G468 , G469 ) X = o n l y ;

G462 ,

G463 ,

G464 ,

G465 ,

G466 ,

G467 ,

No

6.4.7

pitch.pl

Code
/
PITCH LIBRARY
M i c h a e l Droettboom
1999

I n P e l o g , p i t c h names f o l l o w t h e same c o n v e n t i o n a s GUIDO ( s e e


t h e GUIDO Doc u me nt ati on f o r more i n f o r m a t i o n . ) A p i t c h name i s
d e f i n e d a s a l e t t e r { c , d , e , f , g , a , b } o p t i o n a l l y f o l l o w e d by an
a c c i d e n t a l {#, &, ##, &&}.
An o c t a v e i s an i n t e g e r where o c t a v e 1 i s t h e o c t a v e c o n t a i n i n g
A440Hz .

CHAPTER 6. CODE AND TESTING

121

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( ../ core/bi-math.pl ) .
: e n s u r e l o a d e d ( global .pl ) .

/
OPTIONS
These g l o b a l o p t i o n s a r e s e t by t h e GUI s y s t e m .
/
% p i t c h s y s t e m ( { d i a t o n i c | german | a t o n a l | s o l f e g e | a l l } )
g l o b a l d e f a u l t ( tag ( pi t c h s y s t e m ) , d i a t o n i c ) .

/
USER PREDICATES t o be u s e d w i t h i n r u l e d e f i n i t i o n s
e q u i v a l e n t p i t c h ( ? e v e n t a , ? e v e n t b ) ( i . e . c# == d&)
S u c c e e d s i f e v e n t a s o u n d s a t t h e same p i t c h a s e v e n t b .
octave (? event , ? octave )
Succeeds i f event i s in octave .
p i t c h n a m e ( ? e v e n t , ? name ) ( eq . t o p i t c h )
S u c c e e d s i f f r i e n d l y GUIDO name o f e v e n t i s name
/
: op ( 1 5 0 , x f x , o c t a v e ) .
o c t a v e ( E , Oct ) : e p i t c h ( E , P ) , o c t a v e (P , Oct ) .
o c t a v e ( p i t c h ( , CNC ) , Oct ) : ! , f a c t o r (CNC ,
, 7 , Oct ) .
: op ( 1 5 0 , x f x , p i t c h n a m e ) .
p i t c h n a m e (P , T) :
n o n v a r (T ) ,
p i t c h n a m e (T , PC , NC ) ,
p i t c h n o t e c l a s s (P , PC , NC ) .
p i t c h n a m e (P , T) :
v a r (T ) ,
p i t c h n o t e c l a s s (P , PC , NC ) ,
p i t c h n a m e (T , PC , NC ) .
: op ( 1 5 0 , x f , e q u i v a l e n t p i t c h ) .

CHAPTER 6. CODE AND TESTING

122

e q u i v a l e n t p i t c h ( P1 : P2 ) :
p i t c h c l a s s ( P1 , PC ) ,
p i t c h c l a s s ( P2 , PC ) .

/
OPERATORS
/
% & f l a t , # s h a r p , && d o u b l e f l a t , ## s h a r p
% i s atonal
:
:
:
:

op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,

yf ,
yf ,
yf ,
yf ,

&).
#).
&&).
##).

/
DATA STRUCTURE
p i t c h (CPC , CNC)
where CPC i s c o n t i n u o u s p i t c h c l a s s
where CNC i s c o n t i n u o u s n o t e c l a s s
a s d e f i n e d i n [ Brinkmann . ]
/
) , CPC ) : ! .
c o n t p i t c h c l a s s ( p i t c h (CPC ,
c o n t p i t c h c l a s s ( E , CPC ) : e p i t c h ( E , p i t c h (CPC ,

)), !.

c o n t n o t e c l a s s ( p i t c h ( , CNC ) , CNC ) : ! .
c o n t n o t e c l a s s ( E , CNC ) : e p i t c h ( E , p i t c h ( , CNC ) ) , ! .

/
INFORMATION
p i t c h c l a s s ( ? P i t c h , ? P i t c hC l a s s ) % nonc o n t i n u o u s p i t c hc l a s s
n o t e c l a s s ( ? P i t c h , ? NoteC l a s s ) % nonc o n t i n u o u s n o t ec l a s s
p i t c h i n f o ( ? P i t c h , ? PC , ? NC , ? Oct , ? CPC , ? CNC , ? Name)
v a l i d p i t c h (? Pitch )
s u c c e e d s i f P i t c h i s v a l i d ( i . e . a v a l i d ( PC , NC)
relationship )
/
p i t c h n a m e o c t a v e (P , T , O) :
v a r (T ) , v a r (O ) ,
p i t c h n o t e c l a s s o c t a v e (P , PC , NC , O ) ,
p i t c h n a m e (T , PC , NC ) .
p i t c h n a m e o c t a v e (P , T , O) :
n o n v a r (T ) ,
n o n v a r (O ) ,
p i t c h n a m e (T , PC , NC ) ,

CHAPTER 6. CODE AND TESTING

123

CPC i s PC + (O 1 2 ) ,
CNC i s NC + (O 7 ) ,
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) .
p i t c h c l a s s (P , PC) :
c o n t p i t c h c l a s s (P , CPC ) ,
f a c t o r (CPC , PC , 1 2 ,
).
n o t e c l a s s (P , NC) :
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CNC , NC , 7 ,
).
p i t c h n o t e c l a s s (P , PC , NC) :
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CPC , PC , 1 2 , Oct ) ,
f a c t o r (CNC , NC , 7 , Oct ) .
p i t c h n o t e c l a s s o c t a v e (P , PC , NC , Oct ) :
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CPC , PC , 1 2 , Oct ) ,
f a c t o r (CNC , NC , 7 , Oct ) .
p i t c h i n f o (P , PC , NC , Oct , CPC , CNC , Name ) :
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CPC , PC , 1 2 , Oct ) ,
f a c t o r (CNC , NC , 7 , Oct ) ,
p i t c h n a m e (Name , PC , NC ) .
v a l i d p i t c h (P ) :
p i t c h c l a s s (P , PC ) ,
n o t e c l a s s (P , NC ) ,
p i t c h ( , PC , NC ) .

PRIVATE PREDICATES

/
USER and GUIDO PITCH NAMES
p i t c h n a m e ( ? Text , ? P i t c hC l a s s , ? NoteC l a s s )
s u c c e e d s when t e x t d e s c r i p t i o n o f n o t e s , c o m p a t i b l e w i t h

CHAPTER 6. CODE AND TESTING

124

GUIDO N o t a t i o n Language ( Hoos / Hamel ( 1 9 9 7 ) ) , matches t h e


c o r r e s p o n d i n g p i t c h and n o t e c l a s s s y s t e m .
( b a s e d on Brinkman ( 1 9 9 0 ) )
Pitch c l a s s e s are i n t e g e r s in the range { 0 . . . 1 2 }
Note c l a s s e s a r e i n t e g e r s i n t h e r a n g e { 0 . . . 6 }
compatible with the f o l l o w i n g systems :
d i a t o n i c { c , d , e , f , g , a , b } w i t h s h a r p s and f l a t s
german { c , d , e , f , g , a , h } w i t h s h a r p s and f l a t s
a t o n a l { c , c i s , d , d i s , e , f , f i s , g , g i s , a , a i s , b}
s o l f e g e { do , d i , r a , r e , r i , me , mi , f a , f i , s e , s o l , s i ,
l e , l a , l i , t e , t i } ( u s i n g f i x e d do c o n v e n t i o n )
choose the c u r r e n t system with g l o b a l v a r i a b l e p i t c h s y s t e m .
( See t h e g l o b a l l i b r a r y f o r i n f o on g l o b a l v a r i a b l e s . )
/
p i t c h n a m e (T , PC , NC) :
p i t c h t o n a l (T , PC , NC ) .
p i t c h n a m e (T , PC , NC) :
( g l o b a l ( tag ( p it c h s y s t e m ) , atonal )
;
g l o b a l ( tag ( p it c h s y s t e m ) , a l l ) ) ,
p i t c h a t o n a l (T , PC , NC ) .
p i t c h n a m e (T , PC , NC) :
g l o b a l ( tag ( pi t c h s y s t e m ) , s o l f e g e ) ,
p i t c h s o l f e g e (T , PC , NC ) .

% BASIC PITCHES f o r DIATONIC , GERMAN and ATONAL .


pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch

basic (c , 0, 0).
basic (d , 2 , 1 ) .
basic (e , 4, 2).
basic ( f , 5, 3).
basic (g , 7 , 4 ) .
basic (a , 9, 5).
basic (b , 1 1 , 6 ) .
basic (h , 11, 6) :
g l o b a l ( t a g ( p i t c h s y s t e m ) , german ) .

% DIATONIC and GERMAN


p i t c h t o n a l (T , PC , NC) :
p i t c h b a s i c (T , PC , NC ) .
p i t c h t o n a l ( (T) & , PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 1 ) mod 1 2 .
p i t c h t o n a l ( (T) # , PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,

CHAPTER 6. CODE AND TESTING


PC i s ( PC1 + 1 3 ) mod 1 2 .
p i t c h t o n a l ( (T)&&, PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 0 ) mod 1 2 .
p i t c h t o n a l ( (T)##, PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 4 ) mod 1 2 .

% ATONAL
p i t c h a t o n a l (T , PC , NC) :
p i t c h b a s i c (T , PC , NC ) .
pitch atonal ( cis , 1, 0).
pitch atonal ( cis , 1, 1).
pitch atonal ( dis , 3, 1).
pitch atonal ( dis , 3, 2).
pitch atonal ( f is , 6, 3).
pitch atonal ( f is , 6, 4).
pitch atonal ( gis , 8, 4).
pitch atonal ( gis , 8, 5).
pitch atonal ( ais , 10, 5).
pitch atonal ( ais , 10, 6).

% SOLFEGE
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch

s o l f e g e ( do ,
solfege ( di ,
s o l f e g e ( ra ,
s o l f e g e ( re ,
solfege ( ri ,
s o l f e g e (me ,
s o l f e g e ( mi ,
s o l f e g e ( fa ,
solfege ( fi ,
s o l f e g e ( se ,
solfege ( sol
s o l f e g e ( so ,
solfege ( si ,
solfege ( le ,
solfege ( la ,
solfege ( l i ,
s o l f e g e ( te ,
solfege ( ti ,

0, 0) .
1, 0) .
1, 1) .
2, 1) .
3, 1) .
3, 2) .
4, 2) .
5, 3) .
6, 3) .
6, 4) .
, 7, 4) .
7, 4) .
8, 4) .
8, 5) .
9, 5) .
10, 5) .
10, 6) .
11, 6).

Testing
/
PITCH LIBRARY
M i c h a e l Droettboom

125

CHAPTER 6. CODE AND TESTING

126

1999

I n P e l o g , p i t c h names f o l l o w t h e same c o n v e n t i o n a s GUIDO ( s e e


t h e GUIDO Doc u m e n ta t io n f o r more i n f o r m a t i o n . ) A p i t c h name i s
d e f i n e d a s a l e t t e r { c , d , e , f , g , a , b } o p t i o n a l l y f o l l o w e d by an
a c c i d e n t a l {#, &, ##, &&}.
An o c t a v e i s an i n t e g e r where o c t a v e 1 i s t h e o c t a v e c o n t a i n i n g
A440Hz .
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( ../ core/bi-math.pl ) .
: e n s u r e l o a d e d ( global .pl ) .

/
OPTIONS
These g l o b a l o p t i o n s a r e s e t by t h e GUI s y s t e m .
/
% p i t c h s y s t e m ( { d i a t o n i c | german | a t o n a l | s o l f e g e | a l l } )
g l o b a l d e f a u l t ( tag ( pi t c h s y s t e m ) , d i a t o n i c ) .

/
USER PREDICATES t o be u s e d w i t h i n r u l e d e f i n i t i o n s
e q u i v a l e n t p i t c h ( ? e v e n t a , ? e v e n t b ) ( i . e . c# == d&)
S u c c e e d s i f e v e n t a s o u n d s a t t h e same p i t c h a s e v e n t b .
octave (? event , ? octave )
Succeeds i f event i s in octave .
p i t c h n a m e ( ? e v e n t , ? name ) ( eq . t o p i t c h )
S u c c e e d s i f f r i e n d l y GUIDO name o f e v e n t i s name
/
: op ( 1 5 0 , x f x , o c t a v e ) .
o c t a v e ( E , Oct ) : e p i t c h ( E , P ) , o c t a v e (P , Oct ) .
o c t a v e ( p i t c h ( , CNC ) , Oct ) : ! , f a c t o r (CNC ,
, 7 , Oct ) .

CHAPTER 6. CODE AND TESTING

127

: op ( 1 5 0 , x f x , p i t c h n a m e ) .
p i t c h n a m e (P , T) :
n o n v a r (T ) ,
p i t c h n a m e (T , PC , NC ) ,
p i t c h n o t e c l a s s (P , PC , NC ) .
p i t c h n a m e (P , T) :
v a r (T ) ,
p i t c h n o t e c l a s s (P , PC , NC ) ,
p i t c h n a m e (T , PC , NC ) .
: op ( 1 5 0 , x f , e q u i v a l e n t p i t c h ) .
e q u i v a l e n t p i t c h ( P1 : P2 ) :
p i t c h c l a s s ( P1 , PC ) ,
p i t c h c l a s s ( P2 , PC ) .

/
OPERATORS
/
% & f l a t , # s h a r p , && d o u b l e f l a t , ## s h a r p
% i s atonal
:
:
:
:

op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,

yf ,
yf ,
yf ,
yf ,

&).
#).
&&).
##).

/
DATA STRUCTURE
p i t c h (CPC , CNC)
where CPC i s c o n t i n u o u s p i t c h c l a s s
where CNC i s c o n t i n u o u s n o t e c l a s s
a s d e f i n e d i n [ Brinkmann . ]
/
) , CPC ) : ! .
c o n t p i t c h c l a s s ( p i t c h (CPC ,
c o n t p i t c h c l a s s ( E , CPC ) : e p i t c h ( E , p i t c h (CPC ,

)), !.

c o n t n o t e c l a s s ( p i t c h ( , CNC ) , CNC ) : ! .
c o n t n o t e c l a s s ( E , CNC ) : e p i t c h ( E , p i t c h ( , CNC ) ) , ! .

/
INFORMATION
p i t c h c l a s s ( ? P i t c h , ? P i t c hC l a s s ) % nonc o n t i n u o u s p i t c hc l a s s
n o t e c l a s s ( ? P i t c h , ? NoteC l a s s ) % nonc o n t i n u o u s n o t ec l a s s
p i t c h i n f o ( ? P i t c h , ? PC , ? NC , ? Oct , ? CPC , ? CNC , ? Name)
v a l i d p i t c h (? Pitch )
s u c c e e d s i f P i t c h i s v a l i d ( i . e . a v a l i d ( PC , NC)

CHAPTER 6. CODE AND TESTING

128

relationship )
/
p i t c h n a m e o c t a v e (P , T , O) :
v a r (T ) , v a r (O ) ,
p i t c h n o t e c l a s s o c t a v e (P , PC , NC , O ) ,
p i t c h n a m e (T , PC , NC ) .
p i t c h n a m e o c t a v e (P , T , O) :
n o n v a r (T ) ,
n o n v a r (O ) ,
p i t c h n a m e (T , PC , NC ) ,
CPC i s PC + (O 1 2 ) ,
CNC i s NC + (O 7 ) ,
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) .
p i t c h c l a s s (P , PC) :
c o n t p i t c h c l a s s (P , CPC ) ,
f a c t o r (CPC , PC , 1 2 ,
).
n o t e c l a s s (P , NC) :
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CNC , NC , 7 ,
).
p i t c h n o t e c l a s s (P , PC , NC) :
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CPC , PC , 1 2 , Oct ) ,
f a c t o r (CNC , NC , 7 , Oct ) .
p i t c h n o t e c l a s s o c t a v e (P , PC , NC , Oct ) :
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CPC , PC , 1 2 , Oct ) ,
f a c t o r (CNC , NC , 7 , Oct ) .
p i t c h i n f o (P , PC , NC , Oct , CPC , CNC , Name ) :
c o n t p i t c h c l a s s (P , CPC ) ,
c o n t n o t e c l a s s (P , CNC ) ,
f a c t o r (CPC , PC , 1 2 , Oct ) ,
f a c t o r (CNC , NC , 7 , Oct ) ,
p i t c h n a m e (Name , PC , NC ) .
v a l i d p i t c h (P ) :
p i t c h c l a s s (P , PC ) ,
n o t e c l a s s (P , NC ) ,
p i t c h ( , PC , NC ) .

CHAPTER 6. CODE AND TESTING

129

PRIVATE PREDICATES

/
USER and GUIDO PITCH NAMES
p i t c h n a m e ( ? Text , ? P i t c hC l a s s , ? NoteC l a s s )
s u c c e e d s when t e x t d e s c r i p t i o n o f n o t e s , c o m p a t i b l e w i t h
GUIDO N o t a t i o n Language ( Hoos / Hamel ( 1 9 9 7 ) ) , matches t h e
c o r r e s p o n d i n g p i t c h and n o t e c l a s s s y s t e m .
( b a s e d on Brinkman ( 1 9 9 0 ) )
Pitch c l a s s e s are i n t e g e r s in the range { 0 . . . 1 2 }
Note c l a s s e s a r e i n t e g e r s i n t h e r a n g e { 0 . . . 6 }
compatible with the f o l l o w i n g systems :
d i a t o n i c { c , d , e , f , g , a , b } w i t h s h a r p s and f l a t s
german { c , d , e , f , g , a , h } w i t h s h a r p s and f l a t s
a t o n a l { c , c i s , d , d i s , e , f , f i s , g , g i s , a , a i s , b}
s o l f e g e { do , d i , r a , r e , r i , me , mi , f a , f i , s e , s o l , s i ,
l e , l a , l i , t e , t i } ( u s i n g f i x e d do c o n v e n t i o n )
choose the c u r r e n t system with g l o b a l v a r i a b l e p i t c h s y s t e m .
( See t h e g l o b a l l i b r a r y f o r i n f o on g l o b a l v a r i a b l e s . )
/
p i t c h n a m e (T , PC , NC) :
p i t c h t o n a l (T , PC , NC ) .
p i t c h n a m e (T , PC , NC) :
( g l o b a l ( tag ( p it c h s y s t e m ) , atonal )
;
g l o b a l ( tag ( p it c h s y s t e m ) , a l l ) ) ,
p i t c h a t o n a l (T , PC , NC ) .
p i t c h n a m e (T , PC , NC) :
g l o b a l ( tag ( pi t c h s y s t e m ) , s o l f e g e ) ,
p i t c h s o l f e g e (T , PC , NC ) .

% BASIC PITCHES f o r DIATONIC , GERMAN and ATONAL .


pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch

basic (c , 0, 0).
basic (d , 2 , 1 ) .
basic (e , 4, 2).
basic ( f , 5, 3).
basic (g , 7 , 4 ) .
basic (a , 9, 5).
basic (b , 1 1 , 6 ) .
basic (h , 11, 6) :
g l o b a l ( t a g ( p i t c h s y s t e m ) , german ) .

CHAPTER 6. CODE AND TESTING

% DIATONIC and GERMAN


p i t c h t o n a l (T , PC , NC) :
p i t c h b a s i c (T , PC , NC ) .
p i t c h t o n a l ( (T) & , PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 1 ) mod 1 2 .
p i t c h t o n a l ( (T) # , PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 3 ) mod 1 2 .
p i t c h t o n a l ( (T)&&, PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 0 ) mod 1 2 .
p i t c h t o n a l ( (T)##, PC , NC) :
p i t c h b a s i c (T , PC1 , NC ) ,
PC i s ( PC1 + 1 4 ) mod 1 2 .

% ATONAL
p i t c h a t o n a l (T , PC , NC) :
p i t c h b a s i c (T , PC , NC ) .
pitch atonal ( cis , 1, 0).
pitch atonal ( cis , 1, 1).
pitch atonal ( dis , 3, 1).
pitch atonal ( dis , 3, 2).
pitch atonal ( f is , 6, 3).
pitch atonal ( f is , 6, 4).
pitch atonal ( gis , 8, 4).
pitch atonal ( gis , 8, 5).
pitch atonal ( ais , 10, 5).
pitch atonal ( ais , 10, 6).

% SOLFEGE
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch
pitch

s o l f e g e ( do ,
solfege ( di ,
s o l f e g e ( ra ,
s o l f e g e ( re ,
solfege ( ri ,
s o l f e g e (me ,
s o l f e g e ( mi ,
s o l f e g e ( fa ,
solfege ( fi ,
s o l f e g e ( se ,
solfege ( sol
s o l f e g e ( so ,
solfege ( si ,

0, 0) .
1, 0) .
1, 1) .
2, 1) .
3, 1) .
3, 2) .
4, 2) .
5, 3) .
6, 3) .
6, 4) .
, 7, 4) .
7, 4) .
8, 4) .

130

CHAPTER 6. CODE AND TESTING


pitch
pitch
pitch
pitch
pitch

6.4.8

solfege ( le
solfege ( la
solfege ( l i
s o l f e g e ( te
solfege ( ti

,
,
,
,
,

131

8, 5) .
9, 5) .
10, 5) .
10, 6) .
11, 6).

range.pl

Code
/
RANGE LIBRARY
M i c h a e l Droettboom
1999

Range can be s p e c i f i e d i n a number o f ways .


As an i n t e r v a l . See t h e i n t e r v a l l i b r a r y ( page 2 2 ) f o r more
information .
As a s e t o f two e v e n t s l o w e v e n t : h i g h e v e n t .
must be i n s t a n t i a t e d .

These two e v e n t s

By name
Please note that a l l e v e n t s used with the p r e d i c a t e s i n t h i s l i b r a r y
must be i n s t a n t i a t e d . The p r e d i c a t e s w i l l n o t g e n e r a t e e v e n t s .
A d d i t i o n a l named r a n g e s can be d e f i n e d by a d d i n g r a n g e d e f p r e d i c a t e s
to the Pelog r u l e s e t f i l e .
r a n g e d e f p r e d i c a t e s have t h e form :
r a n g e d e f ( name , l o w p i t c h , h i g h p i t c h )
name i s t h e name o f t h e r a n g e .
l o w p i t c h i s t h e l o w e s t p i t c h i n t h e r a n g e , g i v e n i n t h e form
p i t c h n a m e o c t a v e . See t h e p i t c h l i b r a r y f o r d e t a i l s .
h i gh p i t c h i s the hi g he s t pitch in the range
/

/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( event ) .

CHAPTER 6. CODE AND TESTING

132

: e n s u r e l o a d e d ( ../ core/ trees .pl ) .


: e n s u r e l o a d e d ( i n t e r v a l ) .

/
USER PREDICATES
i n r a n g e (+ e v e n t , + r a n g e )
Succeeds i f event i s in range .
e v e n t must be i n s t a n t i a t e d . i n r a n g e w i l l n o t g e n e r a t e
pitches .
r a n g e must be a named r a n g e o r two i n s t a n t i a t e d p i t c h e s . I t
may n o t be d e f i n e d a s a r o a m i n g i n t e r v a l .
r a n g e i s (+ e v e n t s , ? r a n g e )
S u c c e e d s i f t h e r a n g e o f a l l e v e n t s up t o and i n c l u d i n g t h e
c u r r e n t event i s e x a c t l y equal to range .
r a n g e w i t h i n (+ e v e n t s , + r a n g e )
S u c c e e d s i f t h e r a n g e o f a l l e v e n t s up t o and i n c l u d i n g t h e
c u r r e n t e v e n t i s w i t h i n r a n g e . The e v e n t s must be
instantiated .
/
: op ( 1 5 0 , x f x , r a n g e w i t h i n ) .
: op ( 1 2 5 , x f x , : ) .
% Named r a n g e
r a n g e w i t h i n ( ( T , E ) , Range ) :
n o n v a r ( Range ) ,
r a n g e d e f 0 ( Range , LP , UP ) ,
f i n d r a n g e (T , E , FLP , FUP ) , ! ,
r a n g e i n r a n g e ( FLP : FUP , LP : UP ) .
% E x p l i c i t range
r a n g e w i t h i n ( ( T , E ) , LN : UN) :
n o n v a r (LN ) ,
n o n v a r (UN ) ,
f i n d r a n g e (T , E , FLP , FUP ) , ! ,
r a n g e i n r a n g e ( FLP : FUP , LN : UN ) .
% Roaming i n t e r v a l r a n g e
r a n g e w i t h i n ( (T , E ) , I ) :
f i n d r a n g e (T , E , FLP , FUP ) , ! ,
a b s o l u t e i n t e r v a l ( FLP : IU , I c o n t o u r up ) ,
c o n t o u r ( IU , FUP , down ) .
: op ( 1 5 0 , x f x , r a n g e i s ) .
% Named Range
r a n g e i s ( ( T , E ) , Range ) :
n o n v a r ( Range ) ,
r a n g e d e f 0 ( Range , LP , UP ) ,
f i n d r a n g e (T , E , LP , UP ) .
% E x p l i c i t Range

CHAPTER 6. CODE AND TESTING

133

r a n g e i s ( ( T , E ) , FLP : FUP ) :
f i n d r a n g e (T , E , FLP , FUP ) .
% I n t e r v a l Range
r a n g e i s ( (T , E ) , I ) :
f i n d r a n g e (T , E , FLP , FUP ) ,
a b s o l u t e i n t e r v a l ( FLP : FUP , I ) .

: op ( 1 5 0 , x f x , i n r a n g e ) .
% Named Range
i n r a n g e ( E , Range ) :
n o n v a r ( Range ) , n o n v a r ( E ) ,
r a n g e d e f 0 ( Range , LP , UP ) ,
r a n g e i n r a n g e ( E : E , LP : UP ) .
% E x p l i c i t Range
i n r a n g e ( E , LP : UP) :
r a n g e i n r a n g e ( E : E , LP : UP ) .

/
HELPER PREDICATES
/
r a n g e i n r a n g e ( I L : IU , RL
f i x r a n g e ( I L : IU ,
f i x r a n g e ( RL : RU ,
( c o n t o u r ( I L 0 , RL0 ,
( c o n t o u r ( IU0 , RU0 ,

: RU)
IL0 :
RL0 :
down )
up ) ;

:
IU0 ) ,
RU0 ) ,
; c o n t o u r ( I L 0 , RL0 , s t a t i c ) ) ,
c o n t o u r ( IU0 , RU0 , s t a t i c ) ) .

f i x r a n g e ( L : U , L0 : U0 ) :
( c o n t o u r ( L , U , down) >
( L0 = U ,
U0 = L )
;
( L0 = L ,
U0 = U ) ) , ! .
f i n d r a n g e (T , E , FLP , FUP ) :
f i n d r a n g e (T , E , E , FLP , E , FUP ) , ! .
f i n d r a n g e (T , E , X , X , Y , Y) :
g e t p r e v e v e n t (T , E , s t a r t ) .
f i n d r a n g e (T , E , FLPIn , FLPOut , FUPIn , FUPOut ) :
g e t p r e v e v e n t (T , E , P r e v ) ,
( c o n t o u r ( FLPIn , P r e v , down) >
FLP0 = P r e v ;
FLP0 = FLPIn ) ,
( c o n t o u r ( FUPIn , P r e v , up) >
FUP0 = P r e v ;
FUP0 = FUPIn ) ,
f i n d r a n g e (T , P r e v , FLP0 , FLPOut , FUP0 , FUPOut ) .

CHAPTER 6. CODE AND TESTING

134

/
NAMED RANGE DEFINITIONS
/
% C o n v e r t s t h e r a n g e d e f s u s i n g GUIDO p i t c h names t o t h e i n t e r n a l
% (PC , NC ) r e p r e s e n t a t i o n u s e d t h r o u g h o u t P e l o g . The r e s u l t i s
% a s s e r t e d s o t h a t i t d o e s n o t have t o be computed t w i c e .
r a n g e d e f 0 ( Range , LP , UP) :
r a n g e d e f ( Range , LPNLO , UPNUO ) ,
p i t c h n a m e o c t a v e ( LP , LPN , LO ) ,
p i t c h n a m e o c t a v e (UP , UPN , UO ) ,
a s s e r t a ( r a n g e d e f 0 ( Range , LP , UP ) ) .
range
range
range
range

def ( soprano , c 1 , c 3).


def ( alto , f 0, g 2).
def ( tenor , c 0, a 1).
def ( bass , f 1, f 1).

/
TEST
/
t e s t r a n g e i s ( F i l e , I n d e x , Range ) :
load score ( File , T,
),
g e t l a b e l ( Index , T, E ) , ! ,
r a n g e i s ( ( T , E ) , Range ) .
t e s t r a n g e w i t h i n ( F i l e , I n d e x , Range ) :
load score ( File , T,
),
g e t l a b e l ( Index , T, E ) , ! ,
r a n g e w i t h i n ( ( T , E ) , Range ) .

Testing
? t e s t r a n g e i s ( ../ example .gmn , 8 , X ) .

P a r s e OK

X = event ( 4 , 1 , p i t c h ( 5 , 3 ) , dur ( 1 / 4 ) , time ( 9 0 0 0 , 1 2 0 0 0 ) , 3 , [ 1 0 , 1 2 ] ,


5 , G1331 , G1332 , G1333 ) : e v e n t ( 8 , 1 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 2 ) ,
t i m e ( 1 6 5 0 0 , 2 2 5 0 0 ) , 7 , [ 1 3 , 1 0 ] , end , G1507 , G1508 , G1509 ) ;
X = p e r 5 c o n t o u r up ;
X = s k i p c o n t o u r up ;
X = c o n s o n a n t c o n t o u r up ;
No ? t e s t r a n g e i s ( ../ example .gmn , 3 , X ) .

P a r s e OK

X = event ( 3 , 1 , p i t c h ( 9 , 5 ) , dur ( 1 / 4 ) , time ( 6 0 0 0 , 9 0 0 0 ) , 2 , [ 1 2 ] , 4 ,


G1272 , G1273 , G1274 ) : e v e n t ( 1 , 1 , p i t c h ( 1 2 , 7 ) , d u r ( 1 / 4 ) , t i m e ( 0 ,
3 0 0 0 ) , s t a r t , [ ] , 2 , G1196 , G1197 , G1198 ) ;

CHAPTER 6. CODE AND TESTING

135

X = min 3 c o n t o u r up ;
X = s k i p c o n t o u r up ;
X = c o n s o n a n t c o n t o u r up ;
No ? t e s t r a n g e i s ( ../ example .gmn , 1 1 , X ) .

P a r s e OK

X = event ( 1 1 , 2 , p i t c h ( 0 , 0 ) , dur ( 1 / 2 ) , time ( 1 8 0 0 0 , 2 4 0 0 0 ) , 1 0 , [ 8 ,


1 3 ] , end , G1699 , G1700 , G1701 ) : e v e n t ( 9 , 2 , p i t c h ( 4 , 2 ) , d u r ( 1 / 2 ) ,
t i m e ( 0 , 6 0 0 0 ) , s t a r t , [ 1 ] , 1 0 , G1599 , G1600 , G1601 ) ;
X = maj 3 c o n t o u r up ;
X = s k i p c o n t o u r up ;
X = c o n s o n a n t c o n t o u r up ;
No ? t e s t r a n g e w i t h i n ( ../ example .gmn , 1 1 , a l t o ) .

P a r s e OK

No 1 2 ? t e s t r a n g e w i t h i n ( ../ example .gmn , 1 1 , t e n o r ) .

P a r s e OK

Yes 1 3 ? i n r a n g e ( p i t c h ( 1 2 , 7 ) , s o p r a n o ) .
Yes 1 4 ? i n r a n g e ( p i t c h ( 1 2 , 7 ) , p i t c h ( 1 3 , 8 ) : p i t c h ( 1 1 , 6 ) ) .
Yes 1 5 ? i n r a n g e ( p i t c h ( 1 2 , 7 ) , p i t c h ( 1 3 , 8 ) : p i t c h ( 1 5 , 9 ) ) .
No

6.4.9

scale.pl

Code
/
SCALE LIBRARY
M i c h a e l Droettboom
1999

The l i b r a r y h a n d l e s c o l l e c t i o n s o f p i t c h e s . I n t h i s c o n t e x t , t h e
words s c a l e and mode a r e u s e d i n t e r c h a n g e a b l y .
I n P e l o g , s c a l e s names a r e s p e c i f i e d i n t h e form t o n i c p i t c h s e t ,
where t o n i c i s t h e s t a r t i n g p i t c h o f t h e s c a l e , and p i t c h s e t t h e
name o f a s e t o f p i t c h e s .
t o n i c can be any v a l i d p i t c h name .
information .

See t h e p i t c h l i b r a r y f o r more

CHAPTER 6. CODE AND TESTING

136

p i t c h s e t can be any p i t c h s e t name d e f i n e d i n a s c a l e d e f c l a u s e .


The P e l o g r u l e s e t programmer can add more s c a l e d e f i n i t i o n s by
adding s c a l e d e f f a c t p r e d i c a t e s to the r u l e s e t f i l e .
scale def
p r e d i c a t e s hav e t h e form :
s c a l e d e f ( name , p i t c h l i s t , k e y i n t e r v a l ) .
name i s t h e name o f t h e s c a l e .
p i t c h l i s t i s a l i s t o f p i t c h names t h a t make up t h e s c a l e .
T h i s l i s t s h o u l d be p r o v i d e d w i t h c a s t o n i c . The s c a l e l i b r a r y
w i l l do t h e work o f t r a n s p o s i n g t h e s c a l e t o t o n i c s o t h e r t h a n c .
k e y i n t e r v a l i s t h e i n t e r v a l be tween t h e s c a l e s t o n i c and t h e t o n i c
o f t h e m a j o r k e y s i g n a t u r e t h a t t h i s s c a l e s h o u l d be w r i t t e n i n .
For e x a m p l e , t h e t o n i c o f t h e d o r i a n mode i s on t h e s e c o n d
d e g r e e o f t h e m a j o r s c a l e , s o t h e k e y i n t e r v a l v a l u e i s maj 2 .
The s c a l e l i b r a r y a l s o h a n d l e s s c a l e d e g r e e s . S c a l e d e g r e e s can be
s p e c i f i e d a s an i n t e g e r where t h e t o n i c o f t h e s c a l e i s e q u a l t o 1 .
S c a l e d e g r e e s can a l s o be s p e c i f i e d by name .
The P e l o g r u l e programmer can add more s p e c i a l t o n e d e f i n i t i o n s by
adding s p e c i a l t o n e d e f f a c t p r e d i c a t e s to the Pelog r u l e s e t f i l e .
s p e c i a l t o n e d e f p r e d i c a t e s hav e t h e form :
s p e c i a l t o n e d e f ( name , p i t c h s e t , p i t c h ) .
name i s t h e name o f t h e s p e c i a l t o n e .
p i t c h s e t s p e c i f i e s the s c a l e s that the s p e c i a l tone a p p l i e s to .
p i t c h i s the s p e c i a l tone i n that s c a l e with c as t o n i c .
/
/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( pitch ) .
: e n s u r e l o a d e d ( interval ) .
: m u l t i f i l e
s c a l e d e f /3,
s p e c i a l t o n e d e f /3.

/
OPTIONS
These g l o b a l o p t i o n s a r e s e t by t h e GUI s y s t e m .
/
% g l o b a l d e f a u l t ( tag ( s c a l e ) , c major ) .

CHAPTER 6. CODE AND TESTING

137

/
OPERATORS
/
:
:
:
:
:

op ( 1 2 5 ,
op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,

yfx
yf ,
yf ,
yf ,
yf ,

, ).
&).
#).
&&).
##).

/
USER PREDICATES
c u r r e n t s c a l e (? s c a l e )
Succeeds i f s c a l e i s the c u r r e n t s c a l e
i n s c a l e (? event , + Tonic S c a l e )
Succeeds i f event i s i n Tonic S c a l e
s c a l e d e g ( ? e v e n t , + s c a l e , ? deg )
S u c c e e d s i f e v e n t i s t h e degt h d e g r e e o f s c a l e
s c a l e d e g ( ? e v e n t , ? deg )
S u c c e e d s i f e v e n t i s t h e Degt h d e g r e e o f t h e c u r r e n t s c a l e
l e a d i n g t o n e (? Pitch , + Scale )
l e a d i n g t o n e (? Pitch )
t o n i c (? Pitch , + Scale )
t o n i c (? Pitch )
/
: op ( 1 5 0 , x f , s c a l e ) .
: op ( 1 5 0 , x f x , s c a l e ) .
s c a l e (P ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s c a l e (P , S c a l e ) .
s c a l e (P , S c a l e ) :
v a r (P ) , % P i t c h i s v a r i a b l e , s o g e n e r a t e p i t c h e s
get scale ( Scale , Pitches ),
member ( Octave , [ 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 ,
5, 5, 6, 6, 7, 7, 8, 8]),
member ( ( PC , NC ) , P i t c h e s ) ,
p i t c h n o t e c l a s s o c t a v e (P , PC , NC , Octave ) .
s c a l e (P , S c a l e ) :
n o n v a r (P ) , % P i t c h e s i s n o t v a r i a b l e , s o s i m p l e v e r i f y
get scale ( Scale , Pitches ),
p i t c h n o t e c l a s s (P , PC , NC ) ,
member ( ( PC , NC ) , P i t c h e s ) .
: op ( 1 5 0 , f x , c u r r e n t s c a l e ) .
c u r r e n t s c a l e ( Scale ) :
g l o b a l ( tag ( s c a l e ) , Scale ) .

CHAPTER 6. CODE AND TESTING

: op ( 1 5 0 , x f x , s c a l e d e g r e e ) .
% r a i s e d s c al e degree
s c a l e d e g r e e ( P i t c h , r a i s e d Deg ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , 1 ) .
% lowered s c a l e degree
s c a l e d e g r e e ( P i t c h , l o w e r e d Deg ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , 1 ) .
% numbered s c a l e d e g r e e
s c a l e d e g r e e ( P i t c h , Deg ) :
i n t e g e r ( Deg ) ,
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s c a l e d e g r e e ( P i t c h , S c a l e , Deg ) .
% named s c a l e d e g r e e ( a s d e f i n e d i n s p e c i a l t o n e d e f )
s c a l e d e g r e e ( P i t c h , Deg ) :
n o t i n t e g e r ( Deg ) ,
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s p e c i a l t o n e ( P i t c h , S c a l e , Deg ) .
% s c a l e d e g r e e i n a nonc u r r e n t s c a l e
s c a l e d e g r e e ( P i t c h , S c a l e , Deg ) :
i n t e g e r ( Deg ) ,
p i t c h n o t e c l a s s ( P i t c h , PC , NC ) ,
get scale ( Scale , Pitches ),
n t h 1 ( Deg , P i t c h e s , ( PC , NC ) ) .
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , Mod ) :
i n t e g e r ( Deg ) ,
nonvar ( Pitch ) ,
p i t c h n o t e c l a s s ( P i t c h , PC1 , NC ) ,
get scale ( Scale , Pitches ),
PC i s PC1 Mod ,
n t h 1 ( Deg , P i t c h e s , ( PC , NC ) ) .
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , Mod ) :
i n t e g e r ( Deg ) ,
var ( Pitch ) ,
get scale ( Scale , Pitches ),
n t h 1 ( Deg , P i t c h e s , ( PC , NC ) ) ,
PC1 i s PC + Mod ,
p i t c h n o t e c l a s s ( P i t c h , PC1 , NC ) .
s p e c i a l t o n e ( Pitch , Special ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s p e c i a l t o n e ( Pitch , Scale , Special ) .
s p e c i a l t o n e ( Pitch , Scale , Special ) :
s p e c i a l t o n e ( S c a l e , S p e c i a l , PC , NC ) ,
p i t c h n o t e c l a s s ( P i t c h , PC , NC ) .
s p e i c a l t o n e ( c S c a l e , S p e c i a l , PC , NC) :
s p e c i a l t o n e d e f ( S p e c i a l , S c a l e , STN ) ,
p i t c h n a m e (STN , PC , NC ) .

138

CHAPTER 6. CODE AND TESTING

139

s p e c i a l t o n e ( T o n i c S c a l e , S p e c i a l , PC , NC) :
p i t c h n a m e ( T o n i c , TPC , TNC ) ,
s p e c i a l t o n e d e f ( S p e c i a l , S c a l e , STN ) ,
p i t c h n a m e (STN , SPC , SNC ) ,
PC i s ( SPC + TPC ) mod 1 2 ,
NC i s ( SNC + TNC ) mod 7 ,
a s s e r t a ( ( s p e c i a l t o n e ( T o n i c S c a l e , S p e c i a l , PC , NC ) : ! ) ) .
: op ( 1 5 0 , x f , l e a d i n g t o n e ) .
leading tone ( Pitch ) :
s p e c i a l t o n e ( Pitch , leading ) .
: op ( 1 5 0 , x f , t o n i c ) .
tonic ( Pitch ) :
s p e c i a l t o n e ( Pitch , tonic ) .
: op ( 1 5 0 , x f , f i n a l ) .
f i n a l ( Pitch ) :
s p e c i a l t o n e ( Pitch , f i n a l ) .

/
SCALE DEFINITIONS
/
% Converts the s c a l e d e f i n e d i n s c a l e d e f to the binomial r e p r e s e n t a t i o n
% used throughout Pelog . Also t r a n s p o s e s the s c a l e s to t o n i c s ot he r
% t h e n c . The r e s u l t i s a s s e r t e d t o t h e d a t a b a s e t o a v o i d r ec o m p u t a t i o n .
g e t s c a l e ( Tonic S c a l e , P i t c h e s ) :
s c a l e d e f ( S c a l e , BPNs ,
),
p i t c h n a m e l i s t ( BPNs , BPs ) ,
p i t c h n a m e ( T o n i c , TPC , TNC ) ,
c r e a t e s c a l e ( BPs , TPC , TNC , P i t c h e s ) , ! ,
a s s e r t a ( ( g e t s c a l e ( Tonic S c a l e , P i t c h e s ) : ! ) ) .
pitch name list ( [ ] , [ ] ) .
p i t c h n a m e l i s t ( [ PN | N T a i l ] , [ ( PC , NC ) | P T a i l ] ) :
p i t c h n a m e (PN , PC , NC ) ,
p i t c h n a m e l i s t ( NTail , PTail ) .
c r e a t e s c a l e ( [ ( BPC , BNC ) | BT ] , TPC , TNC , [ ( PC , NC ) | T ] ) :
PC i s ( BPC + TPC ) mod 1 2 ,
NC i s ( BNC + TNC ) mod 7 ,
c r e a t e s c a l e (BT , TPC , TNC , T ) .
create scale ( [ ] ,
,
, []).

: m u l t i f i l e
s c a l e d e f /3.
s c a l e d e f ( major , [ c , d , e , f , g , a , b ] , per 1 ) : ! .

CHAPTER 6. CODE AND TESTING

140

s c a l e d e f ( h a r m o n i c m i n o r , [ c , d , e & , f , g , a & , b ] , maj 6 ) : ! .


s c a l e d e f ( n a t u r a l m i n o r , [ c , d , e & , f , g , a & , b & ] , maj 6 ) : ! .
s c a l e d e f ( m e l o d i c m i n o r , [ c , d , e & , f , g , a & , a , b & , b ] , maj 6 ) : ! .
s c a l e d e f ( d o r i a n , [ c , d , e & , f , g , a , b & , b ] , maj 2 ) : ! .
s c a l e d e f ( hypo dorian , X , Y) : s c a l e d e f ( dorian , X , Y ) , ! .
s c a l e d e f ( p h r y g i a n , [ c , d & , e & , f , g , a & , b & ] , maj 3 ) : ! .
s c a l e d e f ( hypo phrygian , X , Y) : s c a l e d e f ( phrygian , X , Y ) , ! .
s c a l e d e f ( l y d i a n , [ c , d , e , f #, g , a , b ] , p e r 4 ) : ! .
s c a l e d e f ( h y p o l y d i a n , X , Y) : s c a l e d e f ( l y d i a n , X , Y ) , ! .
s c a l e d e f ( m i x o l y di a n , [ c , d , e , f , g , a , b &, b ] , per 5 ) : ! .
s c a l e d e f ( hypo mixolydian , X , Y) : s c a l e d e f ( mixolydian , X , Y ) , ! .
s c a l e d e f ( a e o l i a n , [ c , d , e & , f , g , a & , b & , a , b ] , maj 6 ) : ! .
s c a l e d e f ( hy po ae o li an , X , Y) : s c a l e d e f ( a e o l i a n , X , Y ) , ! .
% Note : l o c r i a n w i l l n o t work w i t h modal c o u n t e r p o i n t : i t h a s
% no p e r f e c t f i f t h .
s c a l e d e f ( l o c r i a n , [ c , d & , e & , f , g & , a & , b & ] , maj 7 ) : ! .
s c a l e d e f ( i o n i a n , X , Y) : s c a l e d e f ( major , X , Y ) , ! .
s c a l e d e f ( hypo ionian , X , Y) : s c a l e d e f ( i o n i a n , X , Y ) , ! .
s c a l e d e f ( c h r o m a t i c , [ c , c #, d , d #, e , f , f #, g , g #, a , a #, b ] , none ) : ! .
s c a l e d e f ( chromatic sharps , X , Y) : s c a l e d e f ( chromatic , X , Y ) , ! .
s c a l e d e f ( c h r o m a t i c f l a t s , [ c , d & , d , e & , e , f , g & , g , a & , a , b & , b ] , none ) : ! .
s c a l e d e f ( w h o l e t o n e , [ c , d , e , f #, g #, a # ] , none ) : ! .
s c a l e d e f ( whole tone sharps , X , Y) : s c a l e d e f ( whole tone , X , Y ) , ! .
s c a l e d e f ( w h o l e t o n e f l a t s , [ c , d , e , g & , a & , b & ] , none ) : ! .
s c a l e d e f ( g y p s y , [ c , d , e & , f #, g , a , a & , b ] , u n i s ) : ! .
scale def ( pentatonic , [ c , d , f , g , a ] , unis ) : !.
s c a l e d e f ( p e n t a t o n i c a , X , Y) : s c a l e d e f ( pentatonic , X , Y ) , ! .
scale def ( pentatonic b , [ c , d , e , g , a ] , unis ) : !.
scale
scale
scale
scale
scale
scale
scale
scale
scale

d e f ( o c t a t o n i c , [ c , c #, d #, e , f #, g , a , a # ] , none ) : ! .
d e f ( o c t a t o n i c s h a r p s , X , Y) : s c a l e d e f ( o c t a t o n i c , X , Y) .
d e f ( o c t a t o n i c f l a t s , [ c , d & , e & , e , g & , g , a , b & ] , none ) : ! .
d e f ( h a l f w h o l e , X , Y) : s c a l e d e f ( o c t a t o n i c , X , Y ) , ! .
d e f ( h a l f w h o l e s h a r p s , Y , Z ) : s c a l e d e f ( o c t a t o n i cs h a r p s , Y , Z ) , ! .
d e f ( h a l f w h o l e f l a t s , Y , Z ) : s c a l e d e f ( o c t a t o n i c f l a t s , Y , Z ) , ! .
d e f ( w h o l e h a l f , [ c , d , d #, f , f #, g #, a , b ] , none ) : ! .
d e f ( w h o l e h a l f s h a r p s , X , Y) : s c a l e d e f ( w h o l e h a l f , X , Y) .
d e f ( w h o l e h a l f f l a t s , [ c , d , e & , f , g & , a & , a , b ] , none ) : ! .

s c a l e d e f ( b l u e s , [ c , d , e &, e , f , g &, g , a , b & ] , u n i s ) : ! .

/
RELATIONSHIP OF SCALE TO KEY
Given a S c a l e , r e t u r n s the c o r r e s p o n d i n g key .
T h i s i n f o r m a t i o n i s u s e d by t h e GUIDO p a r s e r .

CHAPTER 6. CODE AND TESTING

141

/
scale key ( Scale , 0) :
scale def ( Scale ,
, none ) .
s c a l e k e y ( T o n i c S c a l e , Key ) :
scale def ( Scale ,
, D e g re e ) ,
g e t s c a l e ( T o n i c S c a l e , [ ( PC , NC ) | R e s t ] ) ,
a b s o l u t e i n t e r v a l c p (PC , NC , KPC , KNC , D e g re e , down ) ,
p i t c h n a m e ( p i t c h (KPC , KNC ) , Key ) , ! .
/
NUMBER OF SHARPS AND FLATS FOR MAJOR KEYS
n e e d e d f o r GUIDO t r a n s l a t i o n
/
key
key
key
key
key
key
key
key
key
key
key
key
key
key
key

sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps

flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats

(0, c ).
(1, g).
(2, d).
(3, a ).
(4, e ).
(5, b).
( 6 , f #).
( 7 , c #).
(1, f ) .
( 2 , b &).
( 3 , e &).
( 4 , a &).
( 5 , d &).
( 6 , g &).
( 7 , c &).

/
SPECIAL TONE DEFINITIONS
/
: m u l t i f i l e
s p e c i a l t o n e d e f /3.
special tone def ( final ,

, c ) : !.

s p e c i a l t o n e d e f ( ambitus , X , Y) : s p e c i a l t o n e d e f ( t o n i c , X , Y) .
s p e c i a l t o n e d e f ( t e n o r , hypo , e ) : ! .
special tone def ( tenor ,
, g) : !.
s p e c i a l t o n e d e f ( t o n i c , hypo , g ) : ! .
special tone def ( tonic ,
, c ) : !.
s p e c i a l t o n e d e f ( leading , phrygian , b &) : !.
s p e c i a l t o n e d e f ( l e a d i n g , hypol y d i a n , f # ) : ! .

CHAPTER 6. CODE AND TESTING

142

s p e c i a l t o n e d e f ( l e a d i n g , hypo , f ) : ! .
s p e c i a l t o n e d e f ( l e a d i n g , n a t u r a lm i n o r , b & ) : ! .
special tone def ( leading ,
, b) : !.
% Only u s e d i n TONAL h a r m o n i c p r a c t i c e
s p e c i a l t o n e d e f ( s u p e r t o n i c , major , d ) : ! .
s p e c i a l t o n e d e f ( s u p e r t o n i c , m i n o r , d ) : ! .
s p e c i a l t o n e d e f ( mediant , major , e ) : ! .
s p e c i a l t o n e d e f ( m e d i a n t , m i n o r , e & ) : ! .
s p e c i a l t o n e d e f ( subdominant , major , f ) : ! .
s p e c i a l t o n e d e f ( s u b d o m i n a n t , m i n o r , f ) : ! .
s p e c i a l t o n e d e f ( dominant , m a j o r , g ) : ! .
s p e c i a l t o n e d e f ( dominant , m i n o r , g ) : ! .
s p e c i a l t o n e d e f ( submediant , major , a ) : ! .
s p e c i a l t o n e d e f ( s u b m e d i a n t , m e l o d i cm i n o r , a ) : ! .
s p e c i a l t o n e d e f ( s u b m e d i a n t , m i n o r , a & ) : ! .
s p e c i a l t o n e d e f ( s u b t o n i c , major , b ) : ! .
s p e c i a l t o n e d e f ( s u b t o n i c , n a t u r a lm i n o r , b & ) : ! .
s p e c i a l t o n e d e f ( s u b t o n i c , m i n o r , b ) : ! .

Testing
/
SCALE LIBRARY
M i c h a e l Droettboom
1999

The l i b r a r y h a n d l e s c o l l e c t i o n s o f p i t c h e s . I n t h i s c o n t e x t , t h e
words s c a l e and mode a r e u s e d i n t e r c h a n g e a b l y .
I n P e l o g , s c a l e s names a r e s p e c i f i e d i n t h e form t o n i c p i t c h s e t ,
where t o n i c i s t h e s t a r t i n g p i t c h o f t h e s c a l e , and p i t c h s e t t h e
name o f a s e t o f p i t c h e s .
t o n i c can be any v a l i d p i t c h name .
information .

See t h e p i t c h l i b r a r y f o r more

p i t c h s e t can be any p i t c h s e t name d e f i n e d i n a s c a l e d e f c l a u s e .


The P e l o g r u l e s e t programmer can add more s c a l e d e f i n i t i o n s by
adding s c a l e d e f f a c t p r e d i c a t e s to the r u l e s e t f i l e .
scale def
p r e d i c a t e s hav e t h e form :
s c a l e d e f ( name , p i t c h l i s t , k e y i n t e r v a l ) .

CHAPTER 6. CODE AND TESTING

143

name i s t h e name o f t h e s c a l e .
p i t c h l i s t i s a l i s t o f p i t c h names t h a t make up t h e s c a l e .
T h i s l i s t s h o u l d be p r o v i d e d w i t h c a s t o n i c . The s c a l e l i b r a r y
w i l l do t h e work o f t r a n s p o s i n g t h e s c a l e t o t o n i c s o t h e r t h a n c .
k e y i n t e r v a l i s t h e i n t e r v a l be tween t h e s c a l e s t o n i c and t h e t o n i c
o f t h e m a j o r k e y s i g n a t u r e t h a t t h i s s c a l e s h o u l d be w r i t t e n i n .
For e x a m p l e , t h e t o n i c o f t h e d o r i a n mode i s on t h e s e c o n d
d e g r e e o f t h e m a j o r s c a l e , s o t h e k e y i n t e r v a l v a l u e i s maj 2 .
The s c a l e l i b r a r y a l s o h a n d l e s s c a l e d e g r e e s . S c a l e d e g r e e s can be
s p e c i f i e d a s an i n t e g e r where t h e t o n i c o f t h e s c a l e i s e q u a l t o 1 .
S c a l e d e g r e e s can a l s o be s p e c i f i e d by name .
The P e l o g r u l e programmer can add more s p e c i a l t o n e d e f i n i t i o n s by
adding s p e c i a l t o n e d e f f a c t p r e d i c a t e s to the Pelog r u l e s e t f i l e .
s p e c i a l t o n e d e f p r e d i c a t e s hav e t h e form :
s p e c i a l t o n e d e f ( name , p i t c h s e t , p i t c h ) .
name i s t h e name o f t h e s p e c i a l t o n e .
p i t c h s e t s p e c i f i e s the s c a l e s that the s p e c i a l tone a p p l i e s to .
p i t c h i s the s p e c i a l tone i n that s c a l e with c as t o n i c .
/
/
REQUIRED LIBRARIES
/
: e n s u r e l o a d e d ( pitch ) .
: e n s u r e l o a d e d ( interval ) .
: m u l t i f i l e
s c a l e d e f /3,
s p e c i a l t o n e d e f /3.

/
OPTIONS
These g l o b a l o p t i o n s a r e s e t by t h e GUI s y s t e m .
/
% g l o b a l d e f a u l t ( tag ( s c a l e ) , c major ) .

/
OPERATORS
/
: op ( 1 2 5 , y f x , ) .

CHAPTER 6. CODE AND TESTING


:
:
:
:

op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,
op ( 1 0 0 ,

yf ,
yf ,
yf ,
yf ,

144

&).
#).
&&).
##).

/
USER PREDICATES
c u r r e n t s c a l e (? s c a l e )
Succeeds i f s c a l e i s the c u r r e n t s c a l e
i n s c a l e (? event , + Tonic S c a l e )
Succeeds i f event i s i n Tonic S c a l e
s c a l e d e g ( ? e v e n t , + s c a l e , ? deg )
S u c c e e d s i f e v e n t i s t h e degt h d e g r e e o f s c a l e
s c a l e d e g ( ? e v e n t , ? deg )
S u c c e e d s i f e v e n t i s t h e Degt h d e g r e e o f t h e c u r r e n t s c a l e
l e a d i n g t o n e (? Pitch , + Scale )
l e a d i n g t o n e (? Pitch )
t o n i c (? Pitch , + Scale )
t o n i c (? Pitch )
/
: op ( 1 5 0 , x f , s c a l e ) .
: op ( 1 5 0 , x f x , s c a l e ) .
s c a l e (P ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s c a l e (P , S c a l e ) .
s c a l e (P , S c a l e ) :
v a r (P ) , % P i t c h i s v a r i a b l e , s o g e n e r a t e p i t c h e s
get scale ( Scale , Pitches ),
member ( Octave , [ 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 ,
5, 5, 6, 6, 7, 7, 8, 8]),
member ( ( PC , NC ) , P i t c h e s ) ,
p i t c h n o t e c l a s s o c t a v e (P , PC , NC , Octave ) .
s c a l e (P , S c a l e ) :
n o n v a r (P ) , % P i t c h e s i s n o t v a r i a b l e , s o s i m p l e v e r i f y
get scale ( Scale , Pitches ),
p i t c h n o t e c l a s s (P , PC , NC ) ,
member ( ( PC , NC ) , P i t c h e s ) .
: op ( 1 5 0 , f x , c u r r e n t s c a l e ) .
c u r r e n t s c a l e ( Scale ) :
g l o b a l ( tag ( s c a l e ) , Scale ) .
: op ( 1 5 0 , x f x , s c a l e d e g r e e ) .
% r a i s e d s c al e degree
s c a l e d e g r e e ( P i t c h , r a i s e d Deg ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , 1 ) .
% lowered s c a l e degree

CHAPTER 6. CODE AND TESTING

145

s c a l e d e g r e e ( P i t c h , l o w e r e d Deg ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , 1 ) .
% numbered s c a l e d e g r e e
s c a l e d e g r e e ( P i t c h , Deg ) :
i n t e g e r ( Deg ) ,
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s c a l e d e g r e e ( P i t c h , S c a l e , Deg ) .
% named s c a l e d e g r e e ( a s d e f i n e d i n s p e c i a l t o n e d e f )
s c a l e d e g r e e ( P i t c h , Deg ) :
n o t i n t e g e r ( Deg ) ,
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s p e c i a l t o n e ( P i t c h , S c a l e , Deg ) .
% s c a l e d e g r e e i n a nonc u r r e n t s c a l e
s c a l e d e g r e e ( P i t c h , S c a l e , Deg ) :
i n t e g e r ( Deg ) ,
p i t c h n o t e c l a s s ( P i t c h , PC , NC ) ,
get scale ( Scale , Pitches ),
n t h 1 ( Deg , P i t c h e s , ( PC , NC ) ) .
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , Mod ) :
i n t e g e r ( Deg ) ,
nonvar ( Pitch ) ,
p i t c h n o t e c l a s s ( P i t c h , PC1 , NC ) ,
get scale ( Scale , Pitches ),
PC i s PC1 Mod ,
n t h 1 ( Deg , P i t c h e s , ( PC , NC ) ) .
m o d i f i e d s c a l e d e g r e e ( P i t c h , S c a l e , Deg , Mod ) :
i n t e g e r ( Deg ) ,
var ( Pitch ) ,
get scale ( Scale , Pitches ),
n t h 1 ( Deg , P i t c h e s , ( PC , NC ) ) ,
PC1 i s PC + Mod ,
p i t c h n o t e c l a s s ( P i t c h , PC1 , NC ) .
s p e c i a l t o n e ( Pitch , Special ) :
g l o b a l ( tag ( s c a l e ) , Scale ) ,
s p e c i a l t o n e ( Pitch , Scale , Special ) .
s p e c i a l t o n e ( Pitch , Scale , Special ) :
s p e c i a l t o n e ( S c a l e , S p e c i a l , PC , NC ) ,
p i t c h n o t e c l a s s ( P i t c h , PC , NC ) .
s p e i c a l t o n e ( c S c a l e , S p e c i a l , PC , NC) :
s p e c i a l t o n e d e f ( S p e c i a l , S c a l e , STN ) ,
p i t c h n a m e (STN , PC , NC ) .
s p e c i a l t o n e ( T o n i c S c a l e , S p e c i a l , PC , NC) :
p i t c h n a m e ( T o n i c , TPC , TNC ) ,
s p e c i a l t o n e d e f ( S p e c i a l , S c a l e , STN ) ,
p i t c h n a m e (STN , SPC , SNC ) ,
PC i s ( SPC + TPC ) mod 1 2 ,
NC i s ( SNC + TNC ) mod 7 ,
a s s e r t a ( ( s p e c i a l t o n e ( T o n i c S c a l e , S p e c i a l , PC , NC ) : ! ) ) .

CHAPTER 6. CODE AND TESTING

146

: op ( 1 5 0 , x f , l e a d i n g t o n e ) .
leading tone ( Pitch ) :
s p e c i a l t o n e ( Pitch , leading ) .
: op ( 1 5 0 , x f , t o n i c ) .
tonic ( Pitch ) :
s p e c i a l t o n e ( Pitch , tonic ) .
: op ( 1 5 0 , x f , f i n a l ) .
f i n a l ( Pitch ) :
s p e c i a l t o n e ( Pitch , f i n a l ) .

/
SCALE DEFINITIONS
/
% Converts the s c a l e d e f i n e d i n s c a l e d e f to the binomial r e p r e s e n t a t i o n
% used throughout Pelog . Also t r a n s p o s e s the s c a l e s to t o n i c s ot he r
% t h e n c . The r e s u l t i s a s s e r t e d t o t h e d a t a b a s e t o a v o i d r ec o m p u t a t i o n .
g e t s c a l e ( Tonic S c a l e , P i t c h e s ) :
s c a l e d e f ( S c a l e , BPNs ,
),
p i t c h n a m e l i s t ( BPNs , BPs ) ,
p i t c h n a m e ( T o n i c , TPC , TNC ) ,
c r e a t e s c a l e ( BPs , TPC , TNC , P i t c h e s ) , ! ,
a s s e r t a ( ( g e t s c a l e ( Tonic S c a l e , P i t c h e s ) : ! ) ) .
pitch name list ( [ ] , [ ] ) .
p i t c h n a m e l i s t ( [ PN | N T a i l ] , [ ( PC , NC ) | P T a i l ] ) :
p i t c h n a m e (PN , PC , NC ) ,
p i t c h n a m e l i s t ( NTail , PTail ) .
c r e a t e s c a l e ( [ ( BPC , BNC ) | BT ] , TPC , TNC , [ ( PC , NC ) | T ] ) :
PC i s ( BPC + TPC ) mod 1 2 ,
NC i s ( BNC + TNC ) mod 7 ,
c r e a t e s c a l e (BT , TPC , TNC , T ) .
create scale ( [ ] ,
,
, []).

: m u l t i f i l e
s c a l e d e f /3.
scale
scale
scale
scale

d e f ( major , [ c , d , e , f , g , a , b ] , per 1 ) : ! .
d e f ( h a r m o n i c m i n o r , [ c , d , e & , f , g , a & , b ] , maj 6 ) : ! .
d e f ( n a t u r a l m i n o r , [ c , d , e & , f , g , a & , b & ] , maj 6 ) : ! .
d e f ( m e l o d i c m i n o r , [ c , d , e & , f , g , a & , a , b & , b ] , maj 6 ) : ! .

s c a l e d e f ( d o r i a n , [ c , d , e & , f , g , a , b & , b ] , maj 2 ) : ! .


s c a l e d e f ( hypo dorian , X , Y) : s c a l e d e f ( dorian , X , Y ) , ! .
s c a l e d e f ( p h r y g i a n , [ c , d & , e & , f , g , a & , b & ] , maj 3 ) : ! .

CHAPTER 6. CODE AND TESTING

147

s c a l e d e f ( hypo phrygian , X , Y) : s c a l e d e f ( phrygian , X , Y ) , ! .


s c a l e d e f ( l y d i a n , [ c , d , e , f #, g , a , b ] , p e r 4 ) : ! .
s c a l e d e f ( h y p o l y d i a n , X , Y) : s c a l e d e f ( l y d i a n , X , Y ) , ! .
s c a l e d e f ( m i x o l y di a n , [ c , d , e , f , g , a , b &, b ] , per 5 ) : ! .
s c a l e d e f ( hypo mixolydian , X , Y) : s c a l e d e f ( mixolydian , X , Y ) , ! .
s c a l e d e f ( a e o l i a n , [ c , d , e & , f , g , a & , b & , a , b ] , maj 6 ) : ! .
s c a l e d e f ( hy po ae o li an , X , Y) : s c a l e d e f ( a e o l i a n , X , Y ) , ! .
% Note : l o c r i a n w i l l n o t work w i t h modal c o u n t e r p o i n t : i t h a s
% no p e r f e c t f i f t h .
s c a l e d e f ( l o c r i a n , [ c , d & , e & , f , g & , a & , b & ] , maj 7 ) : ! .
s c a l e d e f ( i o n i a n , X , Y) : s c a l e d e f ( major , X , Y ) , ! .
s c a l e d e f ( hypo ionian , X , Y) : s c a l e d e f ( i o n i a n , X , Y ) , ! .
s c a l e d e f ( c h r o m a t i c , [ c , c #, d , d #, e , f , f #, g , g #, a , a #, b ] , none ) : ! .
s c a l e d e f ( chromatic sharps , X , Y) : s c a l e d e f ( chromatic , X , Y ) , ! .
s c a l e d e f ( c h r o m a t i c f l a t s , [ c , d & , d , e & , e , f , g & , g , a & , a , b & , b ] , none ) : ! .
s c a l e d e f ( w h o l e t o n e , [ c , d , e , f #, g #, a # ] , none ) : ! .
s c a l e d e f ( whole tone sharps , X , Y) : s c a l e d e f ( whole tone , X , Y ) , ! .
s c a l e d e f ( w h o l e t o n e f l a t s , [ c , d , e , g & , a & , b & ] , none ) : ! .
s c a l e d e f ( g y p s y , [ c , d , e & , f #, g , a , a & , b ] , u n i s ) : ! .
scale def ( pentatonic , [ c , d , f , g , a ] , unis ) : !.
s c a l e d e f ( p e n t a t o n i c a , X , Y) : s c a l e d e f ( pentatonic , X , Y ) , ! .
scale def ( pentatonic b , [ c , d , e , g , a ] , unis ) : !.
scale
scale
scale
scale
scale
scale
scale
scale
scale

d e f ( o c t a t o n i c , [ c , c #, d #, e , f #, g , a , a # ] , none ) : ! .
d e f ( o c t a t o n i c s h a r p s , X , Y) : s c a l e d e f ( o c t a t o n i c , X , Y) .
d e f ( o c t a t o n i c f l a t s , [ c , d & , e & , e , g & , g , a , b & ] , none ) : ! .
d e f ( h a l f w h o l e , X , Y) : s c a l e d e f ( o c t a t o n i c , X , Y ) , ! .
d e f ( h a l f w h o l e s h a r p s , Y , Z ) : s c a l e d e f ( o c t a t o n i cs h a r p s , Y , Z ) , ! .
d e f ( h a l f w h o l e f l a t s , Y , Z ) : s c a l e d e f ( o c t a t o n i c f l a t s , Y , Z ) , ! .
d e f ( w h o l e h a l f , [ c , d , d #, f , f #, g #, a , b ] , none ) : ! .
d e f ( w h o l e h a l f s h a r p s , X , Y) : s c a l e d e f ( w h o l e h a l f , X , Y) .
d e f ( w h o l e h a l f f l a t s , [ c , d , e & , f , g & , a & , a , b ] , none ) : ! .

s c a l e d e f ( b l u e s , [ c , d , e &, e , f , g &, g , a , b & ] , u n i s ) : ! .

/
RELATIONSHIP OF SCALE TO KEY
Given a S c a l e , r e t u r n s the c o r r e s p o n d i n g key .
T h i s i n f o r m a t i o n i s u s e d by t h e GUIDO p a r s e r .
/
scale key ( Scale , 0) :
scale def ( Scale ,
, none ) .
s c a l e k e y ( T o n i c S c a l e , Key ) :
scale def ( Scale ,
, D e g re e ) ,
g e t s c a l e ( T o n i c S c a l e , [ ( PC , NC ) | R e s t ] ) ,

CHAPTER 6. CODE AND TESTING

148

a b s o l u t e i n t e r v a l c p (PC , NC , KPC , KNC , D e g re e , down ) ,


p i t c h n a m e ( p i t c h (KPC , KNC ) , Key ) , ! .
/
NUMBER OF SHARPS AND FLATS FOR MAJOR KEYS
n e e d e d f o r GUIDO t r a n s l a t i o n
/
key
key
key
key
key
key
key
key
key
key
key
key
key
key
key

sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps
sharps

flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats
flats

(0, c ).
(1, g).
(2, d).
(3, a ).
(4, e ).
(5, b).
( 6 , f #).
( 7 , c #).
(1, f ) .
( 2 , b &).
( 3 , e &).
( 4 , a &).
( 5 , d &).
( 6 , g &).
( 7 , c &).

/
SPECIAL TONE DEFINITIONS
/
: m u l t i f i l e
s p e c i a l t o n e d e f /3.
, c ) : !.

special tone def ( final ,

s p e c i a l t o n e d e f ( ambitus , X , Y) : s p e c i a l t o n e d e f ( t o n i c , X , Y) .
s p e c i a l t o n e d e f ( t e n o r , hypo , e ) : ! .
special tone def ( tenor ,
, g) : !.
s p e c i a l t o n e d e f ( t o n i c , hypo , g ) : ! .
special tone def ( tonic ,
, c ) : !.
special
special
special
special
special

tone
tone
tone
tone
tone

def ( leading
def ( leading
def ( leading
def ( leading
def ( leading

,
,
,
,
,

phrygian , b &) : !.
hypol y d i a n , f # ) : ! .
hypo , f ) : ! .
n a t u r a lm i n o r , b & ) : ! .
, b) : !.

% Only u s e d i n TONAL h a r m o n i c p r a c t i c e
s p e c i a l t o n e d e f ( s u p e r t o n i c , major , d ) : ! .
s p e c i a l t o n e d e f ( s u p e r t o n i c , m i n o r , d ) : ! .

CHAPTER 6. CODE AND TESTING

s p e c i a l t o n e d e f ( mediant , major , e ) : ! .
s p e c i a l t o n e d e f ( m e d i a n t , m i n o r , e & ) : ! .
s p e c i a l t o n e d e f ( subdominant , major , f ) : ! .
s p e c i a l t o n e d e f ( s u b d o m i n a n t , m i n o r , f ) : ! .
s p e c i a l t o n e d e f ( dominant , m a j o r , g ) : ! .
s p e c i a l t o n e d e f ( dominant , m i n o r , g ) : ! .
s p e c i a l t o n e d e f ( submediant , major , a ) : ! .
s p e c i a l t o n e d e f ( s u b m e d i a n t , m e l o d i cm i n o r , a ) : ! .
s p e c i a l t o n e d e f ( s u b m e d i a n t , m i n o r , a & ) : ! .
s p e c i a l t o n e d e f ( s u b t o n i c , major , b ) : ! .
s p e c i a l t o n e d e f ( s u b t o n i c , n a t u r a lm i n o r , b & ) : ! .
s p e c i a l t o n e d e f ( s u b t o n i c , m i n o r , b ) : ! .

149

II
Visual Pelog

150

Motivations for a visual language


This chapter is a modified version of some mid-project reflections.

7.1

Summary of core Pelog system

The Pelog system provides a language in which to specify musical constraints. These
constraints are applied by an interpreter to generate or evaluate musical scores.

7.2

The next step

There are three main avenues for improvement:


1. The library of predicates for musical relationships that support the Pelog
language.
2. The interpreter that applies sets of rules to musical scores.
3. The design of the Pelog language itself.
Of these areas, the one with perhaps the least room for improvement is the
library. Most of these predicates are direct translations of Brinkmans [4] research
on musical data structures. While there is certainly room for extensions to the
library, the core predicates as they are now are unlikely to change significantly.
The interpreter is currently quite cumbersome. The fundamental algorithm is
quite complex and still performs a lot of redundant checking despite the high factors
of improvement that have been achieved through more intelligent backtracking. One
possible solution is to apply constraint programming theory to the problem [39].
This task is by no means trivial, as it would likely involve porting the existing
code to a flavour of Prolog that supports constraints. Another solution that was
discussed was to make the interpreter more interactive. A user would be able to
151

CHAPTER 7. MOTIVATIONS FOR A VISUAL LANGUAGE

152

guide the solution-searching at various points in the process. This would change
the focus of the program from a counterpoint generator to a tutorial expert system.
Such an approach may have limited utility, however, as most musicians tend to
develop melodies as complete phrases rather than note-by-note. Since the ability to
test complete phrases for integrity within a set of rules already exists in the present
system, the interactive approach is perhaps merely delegating computation to the
user rather than provided the user with a valuable educational experience.
The area that interests me the most is the Pelog language design itself. The
original genesis of this project was the realisation that the logical programming
paradigm is analogous to the specification of musical relationships. Distilling Fux
[11] into finite musical relatinships proved to be the most interesting musicological
part of the project.

7.3

The problem

The Pelog language gains its power from the fact that it is a declarative logical
language derived from Prolog. It is relatively easy to concisely specify musical relationships using Pelog, as proven by the process of specifying modal counterpoint.
However, what is easy to someone with a background in programming is not necessarily easy for the average musician. For example, take the following code for the
rule that specifies a step followed by a step:
r u l e ( Step -$prev2
$prev1
$prev2

step , contour , 1 ) :
: $prev1 a i n t e r v a l step ,
: $event a i n t e r v a l step ,
comment Step followed by a step .

This states that a) the absolute interval between the two notes before the current
note must be a step, b) the absolute interval between current note and the note
before it must be a step, and c) add a comment to the note two notes before this
one reading Step followed by a step. This description is not readily apparent from
the code to someone who has not studied the Pelog library reference. A possible
improvement might be to make it more like English:
t h e a b s o l u t e i n t e r v a l between $ p r e v 2 and $ p r e v 1 i s a s t e p ,
t h e a b s o l u t e i n t e r v a l between $ p r e v 1 and $ e v e n t i s a s t e p ,
add comment t o $ p r e v 2 t h a t r e a d s S t e p f o l l o w e d by a s t e p .

The trouble with this approach is that there are often multiple English sentences
that express the same meaning. One could just as easily have chosen the following
expression:
between $ p r e v 1 and $ p r e v 2 t h e r e i s an a b s o l u t e i n t e r v a l o f a s t e p ,
between $ p r e v 2 and $ e v e n t t h e r e i s an a b s o l u t e i n t e r v a l o f a s t e p ,
S t e p f o l l o w e d by a s t e p s h o u l d be added a s a comment t o $ p r e v 2 .

This is an extreme example, but it illustrates that a natural language approach


does not eliminate the necessity for a programmer to study and follow certain conventions.

CHAPTER 7. MOTIVATIONS FOR A VISUAL LANGUAGE

7.4

153

The solution

The best solution would be do develop a visual programming1 environment. It will


have two primary functions:
1. Organize rules within the heirarchy of levels, classes and rule sets. (Rule
Manager)
2. Visually edit the musical relationships within each rule. (Rule Editor)
A visual environment will push the Pelog project further toward its original goal
of providing an easy-to-use environment in which musicians can specify musical
relationships. While the core interpreter still requires a significant amount of work,
this new direction is more interesting from a research perspective and more likely
to break new ground.

Visual programming, as defined here, refers to programming using graphical rather than
textual means. It should not be confused with Microsofts definition of Visual which refers
to a GUI-builder environment on top of a traditional text-based programming system. Visual
programming is not a new idea, but is experiencing somewhat of a renaissance, particularly in
database application development [18], and was the featured topic of a recent issue of Dr. Dobbs
Journal [21].

The Visual Pelog language


The Visual Pelog language is a visual extension of the text-based Pelog musical
constraint language (page 2). Each problem domain, (eg. Modal Counterpoint,
Twelve-Tone Counterpoint) is defined by a rule-set. A rule-set is made of up of
a number of individual rules. The rules within the rule set can be categorized by
priority, class and a number of other factors which determine how the rules will be
applied by the interpreter to a given musical score.

8.1

Musical Constraint Graphs

Each rule is defined using a special case of a directed graph called a musical constraint graph. These graphs define relationships between musical events. These
events include a current event, the previous eight events in its part and the previous eight events in another part that is occuring simultaneously. These events
correspond to the metavariables used in the text-based Pelog Language. For the
rationale behind this choice, see the Pelog Language Reference (page 2).
The user is initially presented with a set of notes representing these eighteen
events known as the note template (Figure 8.11 ).
Relationships are drawn between these notes in a manner that should be familiar
to musicians with a background in common music notation. Relationships between
events that occur sequentially are drawn horizontally (Figure 8.2), and relationships
between events that occur simultaneously are drawn vertically (Figure 8.3).
More complex (compound) relationships can also be created. (Figure 8.4).
1

All of the example diagrams were produced directly from Visual Pelog by converting then to
Postscript using Tks psprint function.

154

CHAPTER 8. THE VISUAL PELOG LANGUAGE

Figure 8.1: The basic note template.

Figure 8.2: An example horizontal relationship

Figure 8.3: An example vertical relationship

155

CHAPTER 8. THE VISUAL PELOG LANGUAGE

156

Figure 8.4: An example compound relationship

8.2

Constraint primitives

There are a number of different constraint types available that correspond to the
library predicates defined in the text-based version of the Pelog language. These
include constraints for interval, pitch, range and scale.
There are also logical constraints. These include:
OR: OR is a binary relationship that connects two other constraints. It allows
either or both of the constraints to be active. It may be created between any
two contraints.
NOT: NOT negates the effect of the constraint. The rule succeeds when the
constraint attached to NOT does not succeed. It may be applied to any
constraint.
GREATER/LESS: GREATER/LESS is true when the value of one constraint
is greater or less than another constraint. The GREATER/LESS constraint
can only be drawn between two comparable constraints. For example, two
interval constraints can be constrained so one interval is smaller than the
other. However, it makes no sense for an interval constraint to be compared
to a scale constraint, for example. The same constraint functions as both
greater and less depending on the direction in which one drags when creating
the constraint. Always draw from the constraint with the greater value to the
constraint with the lesser value.
EQUAL: EQUAL is true when the value of two contraints are equal. Like the
GREATER/LESS constraint, the EQUAL constraint may only be drawn
between comparable constraints.

CHAPTER 8. THE VISUAL PELOG LANGUAGE

8.3

Some examples

8.3.1

Step-wise motion

157

To constrain all melodic intervals to step-wise motion, you want to constrain the
current note and the previous note to the interval of a step.
Draw an interval constraint between the current note and the previous note and
set its value to a step using the on-screen musical keyboard. The resulting rule is
in Figure 8.5.

Figure 8.5: Rule for step-wise motion.

8.3.2

Step-wise or third-wise motion

Constraining all melodic intervals to a step can be quite limiting. Suppose you want
to also allow melodic intervals of a third. One can use the OR logical constructor
to compose the constraint that melodic intervals can be steps or thirds.
Starting with the step-wise rule defined above, add another interval constraint
between the same two nodes and specify the interval of a third using the on-screen
musical keyboard. Then, draw an OR constraint between the two interval constraints. The resulting rule is in Figure 8.6.

CHAPTER 8. THE VISUAL PELOG LANGUAGE

Figure 8.6: Step-wise or third-wise melodic motion

158

CHAPTER 8. THE VISUAL PELOG LANGUAGE

8.3.3

159

Avoiding parallel intervals

Suppose you want to avoid any parallel intervals, (i.e. where a given harmonic
interval is repeated on two consecutive beats.) This would imply that the current
harmonic interval and the preceding harmonic interval are note equal.
Draw an interval constraint vertically between the current note and the note
occuring simultaneously. Draw another one between the two notes preceding those.
The interval type does not need to be specified, because we dont care what intervals they are, only that they are equal. The equality is specified by drawing an
EQUALS logical constraint between the two interval constraints. Finally, we want
to disallow this equality, so we can apply the NOT constraint to the EQUALS
constraint.

Figure 8.7: No parallel intervals rule.

8.4

Future extensions

The most obvious next step to improve the Visual Pelog language is the implement
the remaining constraints that are part of the core Pelog library.
As well, it may be useful to implement metaconstraints to handle commonly
occuring combinations of the logical constraints such as parallelism or repetition.

The Visual Pelog environment


9.1

Introduction

For this section, it is assumed you already have some basic knowledge of Pelogs
concept of musical constraints. For this, please see The Pelog language on page 2.
This section does not describe how to specify musical constraints using the Visual
Pelog language. For that, please see The Visual Pelog language on page 154.
At the present time, the Visual Pelog system is a prototype with many unimplemented features. Most notably missing is the conduit between Visual Pelog and the
Pelog systems that would allow interpretation of musical scores. In this chapter, all
unimplemented features are marked with a diamond ().
The Visual Pelog environment allows the user to edit Pelog rule sets. This
comprises two main activities:
Organizing the heirarchy of rules: Changing the order and priority of rules
within the rule set.
Editing the musical constraints (rules): Changing the semantics of each rule
in the rule set.

9.2

A Visual Pelog session

The Visual Pelog environment (Figure 9.1 is made up of a number of separate


windows:
Rule manager: The rule manager is a heirarchical tree of all the rules in the rule
set.
Rule editor: The rule editor allows editing of the semantics of individual rules.
Tool palette: The tool palette contains a number of items that can be added to
rules in the rule editor.
160

CHAPTER 9. THE VISUAL PELOG ENVIRONMENT

Figure 9.1: A Visual Pelog session

161

CHAPTER 9. THE VISUAL PELOG ENVIRONMENT

162

Help browser: The help browser provides on-line help on the tools in the tool
palette.

9.3

Opening and saving rule sets

 Not implemented.

9.4

Rule manager

The rule manager displays the rules in the rule set in a heirarchical tree similar to
Microsoft Windows Explorer. The tree display is collapsible, meaning you can hide
and show categories of rules by clicking on the open/close folder button ( ).

9.4.1

Selecting rules

To select a rule, click on it once. Only one rule can be selected at a given time.

9.4.2

Adding rules

To add a new rule to the rule set, click on rule class (group of rules) and press the
Insert key. A new rule called Untitled Rule is added to the rule class.

9.4.3

Moving rules

To move rules from one rule class to another, simply drag them with the mouse to
their new location. To cancel the drag operation, simply drag it over any whitespace
in the Rule Manager.

9.4.4

Deleting rules

To delete a rule, select it and then press the Delete key.

9.4.5

Editing rules

To edit a rule, select it and press the Enter / Return key, or simply double-click it.

9.5

Rule editor

The rule editor window (Figure 9.2) is made up of four sections:


Description: Entry for a natural-language description of what the rule does.
Graph editor: An area where the rule itself is edited.
Status bar: Displays information about the item on the graph that the mouse is
currently over.
Musical keyboard: Used to specify properties about items in the graph.

CHAPTER 9. THE VISUAL PELOG ENVIRONMENT

163

Figure 9.2: The rule editor window.

9.5.1

Graph editor

For information on creating musical constraints using musical constraint graphs,


please see The Visual Pelog language on page 154.
Adding constraints
To add constraints, make sure that the desired constraint type is selected in the
tool palette (Figure 9.5). If the constraint is a unary constraint (i.e. constrains one
event on its own) simply click on the event you wish to constrain. If the constraint
is a binary constraint (i.e. constrains the relationship between two notes) click and
drag between the two events you wish to constrain. After creating the constraint,
you will be able to edit specifics of that constraint using the musical keyboard at
the bottom of the rule editor window.
Selecting constraints
Choose the Selection Tool ( ) in the tool palette. Click on the constraint you wish
to select. The selected constraint will appear in reverse video (Figure 9.3). There
can only be only selected constraint at a given time.

CHAPTER 9. THE VISUAL PELOG ENVIRONMENT

164

Figure 9.3: A selected constraint.

Obtaining information about a constraint


Move the mouse over a constraint. Detailed information about that constraint will
be displayed in the status bar.
Deleting constraints
Select the constraint you wish to delete and press the Delete key.

9.5.2

Using the musical keyboard

The musical keyboard (Figure 9.4) allows the user to modify properties about the
selected constraint. For example, with the Interval constraint type, the musical
keyboard allows the user to select the specific interval to which the events will be
constrained.

Figure 9.4: The musical keyboard

As you slide the mouse over the keys, the pitch of the key at the mouse cursor
is displayed on the left-hand side.
The musical keyboard acts differently based on context. There are three main
modes of operation (consult the constraints on-line documentation in the tool
palette to determine which mode is used for different types of constraints):
Single: Click on a key to select a pitch. Only one key can be selected at a given
time.
Multi: Click on a key to toggle its status. You can select as many keys as necessary.
Range: Click on a starting note and drag to an ending note to select an unbroken
range of keys.
To make the keys themselves larger, press the zoom-in button (
them to their original size, press the zoom-out button ( ).

). To restore

CHAPTER 9. THE VISUAL PELOG ENVIRONMENT

165

Figure 9.5: The tool palette.

9.6

Tool palette

Different types of constraints can be selected from the tool palette. The set of
tools is based on the plug-ins installed on your system. In this way, new types of
constraints can be added to the system at a later date.
 Currently, only the basic logical constraints (OR, NOT, =, >) and the Interval Tool are implemented.
Selecting a tool
To select a tool, click on it.
Getting help on a tool
As you bring your mouse over a tool, its name is displayed in the status bar at
bottom of the Tool Palette window. For more detailed help on a given tool, select
the tool and press the question mark button ( ). A description of the tool and its
associated constraint is opened in the on-line help browser.

10

A specialized graph layout


algorithm for musical constraint
graphs
Graph layout theory is a large and active field of study [10] [8]. In general, research
has indicated that while GLAs can be classified into a small number of categories,
extensive customization is usually necessary for each unique graph problem domain.
Recent attempts at generalized graph drawing systems have been successful only
due to their support for numerous different layout algorithms (GraphEd [13]) or
their ability to perform minute customizations on individual constraints (EDGE
[25]). Likewise, the present graph layout system is specific to the problem of what
shall be called musical constraint graphs.

10.1

Specification of problem

The graph layout algorithm (GLA) used by the Visual Pelog rule editor is the most
computationally complex part of the visual system. Its musical constraint graphs
define relationships between a given set of musical events. For a definition of a
musical constraint graph, see page 154.
Specifically, a graph layout algorithm supporting this definition must coordinate
the following constraints.
All layout decisions should be automated. The user need only be concerned
with the semantics of the graph.
All nodes must be visible, therefore no two nodes may overlap.
Spatial relationships between nodes must be meaningful: eg., a node representing a relationship between two other nodes must physically reside as close
as possible to the center of those two nodes.
166

CHAPTER 10. GRAPH LAYOUT ALGORITHM

167

The algorithm must work in two dimensions: vertical relationships must be


drawn vertically, and horizontal relationships horizontally.
Changes to the graph must be made elegantly, so that the user does not get
lost.

10.2

Background

10.2.1

Sugiyama algorithm

The graph layout algorithm used by Visual Pelog is loosely based on the classic
Sugiyama graph layout algorithm [35] as summarized by Ivan Bowman [3]. This
algorithm was designed for laying out heirarchical tree-like structures. It results in
graphs which have the following properties (as summarized in Bowman [3]):
hierarchical layout of vertices
minimized edge crossings
connected vertices are close together
balanced layout of edges
The algorithm proceeds in three steps:
1. Perform a topological sort on the nodes, assigning each to a level. Unlike the
classical definition of a topological sort [7], more than one node may occupy
the same level.
2. Within each level, the vertices are permuted to minimize line crossings.
3. Assign positions to maximize barycenters (i.e. so that children are centered
under their parents and vice versa.)
There are some disadvantages to the basic Sugiyama algorithm that, on its own,
make it unsuitable for the present task.
No constraints. Certain nodes, such as those that make up the note template
need to be constrained. (i.e. The notes that make up each part should be vertically aligned, and the notes occuring simultaneously should be horizontally
aligned.)
Poor stability. Dramatic changes to the layout can occur when minor
changes are made to the graph itself. When too many nodes move, the user
is easily confused and can lose a sense of orientation within the graph.
One-dimensional hierarchical relationship. It assumes a straightforward
one-dimensional heirarchy between all nodes. For the problem at hand, one
needs to specify heirarchies that work in either the horizontal or vertical direction: in effect a double Sugiyama algorithm.

CHAPTER 10. GRAPH LAYOUT ALGORITHM

10.2.2

168

Constraint-based extensions to the Sugiyama algorithm

Paulisch [25] describes a way to extend the Sugiyama algorithm to support constraints. In his extension, the position of every node can be constrained based the
positions of an arbitrary number of other nodes. Paulisch suggests a number of
constraint types, both simple and exotic. The constraints deemed necessary for the
present problem are as follows:
equal: The node must be on the same level as some other nodes.
less: The node must be on a lesser (northwesterly) level than some other
nodes.
greater: The node must be on a greater (southeasterly) level than some other
nodes.
near: The node must be as close as possible to the artihmetic mean (center)
of the listed nodes.
To support the extension to constraints, the topological sort step of the Sugiyama
algorithm is replaced with a constraint solver. In fact, a topological sort can be performed without using a classical topological sort algorithm [7] simply by converting
all of the parent-child relationships to greater and less constraints.

10.2.3

The problem of graph stability

Another observation made by Paulisch is that the layout of graphs using the
Sugiyama algorithm tends to change dramatically when simple changes are made
to the graph. When too many nodes change positions by too much, users can easily
lose their bearings. This is an important consideration for the present project, as
the graph is to be edited interactively. Paulisch proposed an elaborate solution
to this problem where the allowed movement of a given node is limited by some
-constant and the number of nodes that can move in any iteration is also limited.
While a similar solution may be incorporated in the future, at present, the
stability problem is solved using animation. When a change is made to the graph,
the nodes are gradually animated from their original positions to their new positions.
While occasionally the movement of nodes can be drastic, for the most part this
more nave approach is quite acceptable. The movement of nodes also represents,
in an entertaining way, the dynamic nature of the graph.

10.2.4

The problem of two-dimensionality

To apply the constraint-based Sugiyama algorithm in two dimensions, the


constraint-based sort is simply performed on one axis, and then the results are
applied to a sort on the other axis.

CHAPTER 10. GRAPH LAYOUT ALGORITHM

10.3

Implementation of the algorithm

10.3.1

General design

169

The present GLA is implemented in an object-oriented style. The algorithm, on


the most basic level, is an interaction between the rule editor, which maintains a
list of nodes and operates globally on the nodes, and the nodes themselves, which
perform their own topological sorting and constraint resolution.
An attempt was also made to uncouple the algorithm itself from the underlying
graphics architecture (in this case Tkinter.) This allows ths system to be trivially
ported to other graphics libraries. One of the less obvious of such abstractions is
the differentiaion between logical and physical coordinates. Logical coordinates are
the location of the node based on the topological sort and constraint resolution
steps. These logical coordinates are mapped to physical coordinates, in a manner
that avoids any collisions, only after all of the basic layout has been completed.

10.3.2

The top-level

The main process is as follows:


1. Enter a loop that continues until there are no conflicts.
Support the near constraint by moving all nodes to the average (center)
of their near nodes.
Perform a constraint-based topological sort on each of the nodes.
Resolve any conflicts (where two nodes occupy the same location.)
2. Map the logical coordinates from step 1 to physical coordinates.

10.3.3

The near constraint

Before performing the constraint-based topological sort, nodes are moved to their
ideal near locations which is the average (center) of the locations of all the nodes
on the near constraint list.

10.3.4

Constraint-based topological sort

The topological sort is performed by resolving the equal, greater and less constraints.
The success of this constraint resolution is dependent on how the constraints are
determined at the time of node creation. (see Creating nodes on 170).
At the top-level, all nodes are sorted first on the x axis, then on the y axis.
As the constraints are solved, nodes are always moved southeast, so that the
graph expands outward. If nodes were allowed to move in arbitrary directions, the
graph might implode and explode on itself, creating deadlock in an infinite loop.
For example, to solve the equal constraint, the position of the current node and
the nodes on its equal list are compared. The node with the more northwesterly
position is moved to the location of the node with the more southeasterly position.

CHAPTER 10. GRAPH LAYOUT ALGORITHM

10.3.5

170

Conflict resolution

Once the constraint-based topological sort has assigned a level in both dimensions
to all nodes, the entire set of nodes is scanned for any conflicts. A conflict exists
when two or more nodes occupy the same location.
A search is performed for nodes occupying the same location. When a pair is
found, the Node instance will move itself or its partner. The choice of which Node
to move is determined by the average location of all nodes on the near constraint
list. For example, if node a has a more leftward near average than node b, then node
a will move left.

10.3.6

Mapping physical coordinates to logical coordinates

The grid layout subalgorithm maps the logical coordinates as determined by the
topological sort to the physical coordinates on the canvas. Since different nodes
have different physical sizes, the maximum size of the nodes in each row and column
must be determined before assigning physical values to each row and column.

10.3.7

Animation

Once the new physical coordinates of all the nodes has been determined, the nodes
are moved incrementally from their old locations to their new ones while updating
the display. The number of animation steps is determined by a constant.

10.3.8

Creating nodes

An important part of the graph layout algorithm is how the constraints are assigned
when nodes are created. The Visual Pelog system uses three families of nodes, from
which all other nodes are inherited.
Note nodes
Note nodes make up the note template. They must be aligned to other nodes, both
vertically and horizontally, to represent the sequential and concurrent relationships
of the note in the note template. This is performed using the equals constraint. For
example, the current note node and the note that occurs simultaneously must
appear directly below it. Therefore, it is on the equals constraint list on the x-axis.
Binary relations
A binary relation node is used to specify a relationship between two nodes. In
practice, the interval constraint is an example of a binary relation.
If a binary relation node x is defined between nodes a and b, and the relative
location of a and b are a < b, then the relative locations of all three must be
a < x < b. The less and greater constraints are assigned accordingly. The node x
should also be as close as possible to the centre of the two nodes a and b. Therefore,
a and b are added to the near constraint list of x.

CHAPTER 10. GRAPH LAYOUT ALGORITHM

171

Unary relations
A unary relation node is used to define a relation involving only one node. In
practice, constraints involving pitch class are often unary relation nodes.
Unary relation nodes should be as close as possible to the nodes they are related
to. If a unary relation node x is defined on a node a, then a is added to the near
constraint list of x.

10.4

Code related to the graph layout algorithm

10.4.1

RuleEditor.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# R u l e E d i t o r . py
#
#
Top l e v e l c l a s s e s f o r e d i t i n g r u l e s
# V i s u a l Pelog imports
from U t i l i m p o r t
from C o n s t a n t s i m p o r t
i m p o r t Nodes , P a l e t t e , M u s i c W i d g e t s
# Tkinter imports
i m p o r t T k i n t e r , Canvas
##################################################
# RuleEditor
#
# The f r a m e i n wh ic h t h e r u l e e d i t o r r e s i d e s .
#
c l a s s RuleEditorWindow ( T k i n t e r . T o p l e v e l ) :
#
# I n i t i a l i z a t i o n routines
def
i n i t ( s e l f , p a l e t t e , p a r e n t=None , r u l e=None ) :
self . rule = rule
Tkinter . Toplevel .
init (
s e l f , parent ,
c l a s s = RuleEditor )
s e l f . t i t l e ( "Rule Editor " )
# A r e f e r e n c e to the t o o l p a l e t t e that w i l l i n t e r a c t with
# this rule editor
self . palette = palette
# Make t h e w i d g e t s t h a t a r e p u t o f t h e r u l e e d i t o r window
s e l f . make widgets ( r u l e )

CHAPTER 10. GRAPH LAYOUT ALGORITHM

172

# P r e s s i n g p w i l l export the canvas as a P o s t S c r i p t f i l e


s e l f . b i n d ( s e q u e n c e=<p> ,
f u n c= s e l f . c a n v a s . p s p r i n t )
# A l l other k e y s t r o k e s are sent to the c u r r e n t l y s e l e c t e d t o o l
s e l f . b i n d ( s e q u e n c e=< KeyPress > ,
f u n c=p a l e t t e . t o o l k e y p r e s s )
# C r e a t e s t h e w i d g e t s i n t h e r u l e e d i t o r window
def
make widgets ( s e l f , r u l e ) :
# The t e x t box u s e d t o d e s c r i b e t h e r u l e i n E n g l i s h
s e l f . t e x t = T k i n t e r . Text ( h e i g h t = 3 , m a s t e r= s e l f )
if self . rule :
s e l f . t e x t . i n s e r t ( 0.0 , r e p r ( s e l f . r u l e ) )
s e l f . t e x t . g r i d ( row = 0 , column = 0 , s t i c k y=nwe )
s e l f . t e x t . b i n d ( s e q u e n c e="< KeyRelease >" , f u n c= s e l f . update name )
# The m u s i c a l k e y b o a r d
s e l f . k e y b o a r d = M u s i c W i d g e t s . Keyboard ( s e l f )
s e l f . keyboard . g r i d (
row = 5 , column = 0 , s t i c k y=nesw , c olumnspan =2)
# The r u l e e d i t o r i t s e l f and i t s c o r r e s p o n d i n g s c r o l l b a r s
s e l f . canvas = RuleEditorCanvas (
p a l e t t e= s e l f . p a l e t t e ,
m a s t e r= s e l f ,
k e y b o a r d= s e l f . k e y b o a r d , r u l e=r u l e )
s e l f . c a n v a s . g r i d ( row = 1 , column = 0 , s t i c k y=nesw )
s e l f . v s c r o l l = Tkinter . Scrollbar (
s e l f , o r i e n t= vertical ,
command= s e l f . c a n v a s . y v i e w )
s e l f . v s c r o l l . g r i d ( row = 1 , column = 1 , s t i c k y=ns )
s e l f . h s c r o l l = Tkinter . Scrollbar (
s e l f , o r i e n t= horizontal ,
command= s e l f . c a n v a s . x v i e w )
s e l f . h s c r o l l . g r i d ( row = 2 , column = 0 , s t i c k y=we )
s e l f . g r i d c o l u m n c o n f i g u r e ( 0 , w e i g h t =1)
s e l f . g r i d r o w c o n f i g u r e ( 1 , w e i g h t =1)
s e l f . c a n v a s [ xscrollcommand ] = s e l f . s c r o l l X
s e l f . c a n v a s [ yscrollcommand ] = s e l f . s c r o l l Y
# The s t a t u s b a r
s e l f . status = Tkinter . Label (
m a s t e r= s e l f , r e l i e f = sunken )
s e l f . s t a t u s . g r i d ( row = 4 , column = 0 , c olum nspan = 2 , s t i c k y=we )
#
# Event h a n d l e r s

CHAPTER 10. GRAPH LAYOUT ALGORITHM

173

def s c r o l l X ( s e l f , f i r s t , l a s t ) :
s e l f . hscroll . set ( f i r s t , last )
def s c r o l l Y ( s e l f , f i r s t , l a s t ) :
s e l f . v s c r o ll . set ( f i r s t , last )
d e f update name ( s e l f , a r g s ) :
s e l f . r u l e . update name ( s e l f . t e x t . g e t ( 0.0 , end ) )
##################################################
# RuleEditorCanvas
#
# The r u l e g r a p h d i s p l a y w i d g e t
#
c l a s s R u l e E d i t o r C a n v a s ( C u r s o r M a n a g e r , T k i n t e r . Canvas ) :
#
# I n i t i a l i z a t i o n routines
def
i n i t ( s e l f , p a l e t t e=None , m a s t e r=None , k e y b o a r d=None , r u l e=None ) :
self . rule = rule
T k i n t e r . Canvas .
init (
s e l f , master ,
b a c k g r o u n d=RE COLOR CANVAS BACKGROUND ,
c o n f i n e=TRUE)
s e l f . make rubber ()
s e l f . b i n d ( s e q u e n c e="< Motion >" , f u n c= s e l f . motion )
CursorManager .
init ( self )
# A r e f e r e n c e to the t o o l p a l e t t e that w i l l i n t e r a c t with
# this rule editor
self . palette = palette
# A r e f e r e n c e t o t h e ons c r e e n m u s i c a l k e y b o a r d t h a t w i l l
# i n t e r a c t with t h i s e d i t o r
s e l f . keyboard = keyboard
# The two n o d e s t h a t a r e d e t e r m i n e d by d r a g g i n g between
# two n o d e s . These n o d e s a r e s e n t t o t h e c u r r e n t t o o l t o
# c r e a t e new p r e d i c a t e s .
s e l f . s t a r t n o d e = s e l f . f i n i s h n o d e = None
# A l i s t of a l l the nodes i n the canvas
s e l f . nodeList = rule . nodeList
# Create the b o i l e r p l a t e note nodes . . .
i f s e l f . nodeList == []:
s e l f . create basic nodes ()
else :
f o r node i n s e l f . n o d e L i s t :
node . m a s t e r = s e l f

CHAPTER 10. GRAPH LAYOUT ALGORITHM

174

node . v i s u a l i z e ( )
# . . . and a n i m a t e them t o t h e i r f i n a l p o s i t i o n
f o r node i n s e l f . n o d e L i s t :
node . f i n a l i z e c o o r d ( )
# C r e a t e s t h e r u b b e r band l i n e
def
make rubber ( s e l f ) :
s e l f . r u b b e r = Canvas . L i n e (
s e l f , 5, 5, 5, 5)
s e l f . r u b b e r [ width ] = 2 . 0
s e l f . r u b b e r [ stipple ] = gray50
# C r e at e s the b o i l e r p l a t e note nodes
def
create basic nodes ( self ):
p r e v 8 = Nodes . NoteNode ( s e l f , $prev8 )
s e l f . r u l e . Top row = p r e v 8
p r e v 7 = Nodes . NoteNode ( s e l f , $prev7 , prevX=p r e v 8 )
p r e v 6 = Nodes . NoteNode ( s e l f , $prev6 , prevX=p r e v 7 )
p r e v 5 = Nodes . NoteNode ( s e l f , $prev5 , prevX=p r e v 6 )
p r e v 4 = Nodes . NoteNode ( s e l f , $prev4 , prevX=p r e v 5 )
p r e v 3 = Nodes . NoteNode ( s e l f , $prev3 , prevX=p r e v 4 )
p r e v 2 = Nodes . NoteNode ( s e l f , $prev2 , prevX=p r e v 3 )
p r e v 1 = Nodes . NoteNode ( s e l f , $prev1 , prevX=p r e v 2 )
e v e n t = Nodes . NoteNode ( s e l f , $event , prevX=p r e v 1 , box=TRUE)
v e r t 8 = Nodes . NoteNode ( s e l f , $vert8 , prevY=p r e v 8 )
s e l f . r u l e . Bottom row = v e r t 8
v e r t 7 = Nodes . NoteNode ( s e l f , $vert7 , prevX=v e r t 8 , prevY=p r e v 7 )
v e r t 6 = Nodes . NoteNode ( s e l f , $vert6 , prevX=v e r t 7 , prevY=p r e v 6 )
v e r t 5 = Nodes . NoteNode ( s e l f , $vert5 , prevX=v e r t 6 , prevY=p r e v 5 )
v e r t 4 = Nodes . NoteNode ( s e l f , $vert4 , prevX=v e r t 5 , prevY=p r e v 4 )
v e r t 3 = Nodes . NoteNode ( s e l f , $vert3 , prevX=v e r t 4 , prevY=p r e v 3 )
v e r t 2 = Nodes . NoteNode ( s e l f , $vert2 , prevX=v e r t 3 , prevY=p r e v 2 )
v e r t 1 = Nodes . NoteNode ( s e l f , $vert1 , prevX=v e r t 2 , prevY=p r e v 1 )
v e r t = Nodes . NoteNode ( s e l f , $vert , prevX=v e r t 1 , prevY=e v e n t )
#
# Event h a n d l e r s
def psprint ( s e l f , args ) :
self . postscript (
f i l e ="e:/ Pelog / Visual /test.ps" )
# P l a c e s d e s c r i p t i v e t e x t a b o u t t h e node i n t h e s t a t u s b a r
d e f d e s c r i b e n o d e ( s e l f , node ) :
s e l f . m a s t e r . s t a t u s [ text ] = node
def undescribe node ( s e l f ) :
s e l f . m a s t e r . s t a t u s [ text ] =
d e f s e t s t a r t n o d e ( s e l f , node ) :
s e l f . rubber . coords ([(5,5), (5,5)])

CHAPTER 10. GRAPH LAYOUT ALGORITHM

175

s e l f . s t a r t n o d e = node
d e f s e t f i n i s h n o d e ( s e l f , node ) :
s e l f . rubber . coords ([(5,5), (5,5)])
s e l f . f i n i s h n o d e = node
d e f motion ( s e l f , a r g s ) :
i f s e l f . start node :
s e l f . rubber . coords (
[ ( s e l f . s t a r t n o d e . pt [ 0 ] ,
s e l f . s t a r t n o d e . pt [ 1 ] ) ,
( s e l f . canvasx ( args . x ) ,
s e l f . canvasy ( args . y ) ) ] )
# R e t u r n s t h e node a t p h y s i c a l c o o r d i n a t e s ( x , y )
# I f none e x i s t s , r e t u r n s None
def find node ( s e l f , x , y ) :
x = s e l f . canvasx ( x )
y = s e l f . canvasy ( y )
s e l f . rubber . coords ([(5, 5), (5, 5)])
z = self . find overlapping (x , y , x , y)
if z != ():
r e t u r n s e l f . i t e m s [ z [ 0 ] ] . node
else :
r e t u r n None
# P e r f o r m s an a c t i o n b a s e d on t h e c u r r e n t l y s e l e c t e d t o o l on
# t h e two n o d e s s t a r t n o d e and f i n i s h n o d e
def run tool ( s e l f ) :
d e b u g p r i n t ( " Running tool on:" , s e l f . s t a r t n o d e , s e l f . f i n i s h n o d e )
s e l f . palette . run tool ( s e l f , s e l f . start node , s e l f . finish node )
s e l f . animate ()
s e l f . s t a r t n o d e = s e l f . f i n i s h n o d e = None
#
# User f u n c t i o n s
d e f a d d n o d e ( s e l f , node ) :
s e l f . n o d e L i s t . append ( node )
s e l f . layout nodes ()
# s e l f . animate ()
d e f r e m o v e n o d e ( s e l f , node ) :
s e l f . n o d e L i s t . remove ( node )
s e l f . r e m o v e r e f e r e n c e s ( node )
s e l f . layout nodes ()
s e l f . animate ()
d e f r e m o v e r e f e r e n c e s ( s e l f , node ) :
for n in s e l f . nodeList :
i f n . a == node o r n . b == node :

CHAPTER 10. GRAPH LAYOUT ALGORITHM

176

n . delete ()

#
# Graph l a y o u t f u n c t i o n s
# T h i s i s t h e t o p l e v e l g r a p h l a y o u t f u n c t i o n .
# F o l l o w i n g t h e o b j e c to r i e n t e d p a r a d i g m , most o f t h e s m a r t s o f t h e
# a l g o r i t h m a r e embedded i n t h e node o b j e c t s t h e m s e l v e s .
d e f l a y o u t n o d e s ( s e l f , draw =0):
d e b u g p r i n t ( " LAYING OUT NODES ===============" ) # Debugging i n f o r m a t i o n
c o n f l i c t s = TRUE # Assume we w i l l f i n d c o n f l i c t s
while c o n f l i c t s :
# I . Gu e s s a t a good s t a r t i n g l o c a t i o n f o r t h e n o d e s by moving them
#
t o t h e a v e r a g e l o c a t i o n o f t h e n o d e s s p e c i f i e d by t h e n e a r
#
constraint
f o r node i n s e l f . n o d e L i s t :
node . g o t o n e a r ( )
# I I . T o p o l o g i c a l l y s o r t n o d e s b a s e d on t h e r e l a t i o n s h i p s and
#
c o n s t r a i n t s d e f i n e d w i t h e a c h node , t h e n r e s o l v e any c o n f l i c t s
#
( where two n o d e s o c c u p y t h e same c e l l ) . R e p e a t p r o c e s s u n t i l
#
t h e r e a r e no c o n f l i c t s . Each node i s a s s i g n e d a l o c a t i o n on
#
a LOGICAL g r i d .
while c o n f l i c t s :
s e l f . t o p o l o g i c a l s o r t ()
c o n f l i c t s = s e l f . resolve ()
s e l f . t o p o l o g i c a l s o r t ()
c o n f l i c t s = s e l f . resolve ()
# I I I . Map t h e r e s u l t s o f t h e t o p o l o g i c a l s o r t ( l o c a t i o n on a LOGICAL
#
GRID ) t o a l o c a t i o n on t h e PHYSICAL GRID .
s e l f . grid layout ()
# Runs a t o p o l o g i c a l s o r t on a l l n o d e s i n t h e g r a p h
# The a c t u a l work o f c o n s t r a i n t r e a l i z a t i o n i s p e r f o r m e d by t h e
# nodes t h e m s e l v e s
def t o p o l o g i c a l s o r t ( s e l f ) :
d e b u g p r i n t ( " TOPOLOGICAL SORT ===============" )
f o r dimen i n [ 0 , 1 ] : # S o r t x t h e n y a x i
d e b u g p r i n t ( " Dimension " , dimen , " ---------->" )
f o r node i n s e l f . n o d e L i s t :
d e b u g p r i n t ( " Starting at root node :" , node )
node . t o p o l o g i c a l s o r t ( dimen , RE MINGRID , RE MAXGRID)
# r e s o l v e c o n f l i c t s ( when two n o d e s o c c u p y t h e same c e l l )
def r e s o l v e ( s e l f ) :
d e b u g p r i n t ( " RESOLVING ===============" )

CHAPTER 10. GRAPH LAYOUT ALGORITHM

177

c o n f l i c t s = FALSE # no c o n f l i c t s y e t . . .
f o r node1 i n s e l f . n o d e L i s t :
# Compare e a c h node i n g r a p h . . .
f o r node2 i n s e l f . n o d e L i s t : # . . . w i t h e v e r y o t h e r node
# I f t h e s e n o d e s a r e n o t t h e SAME node , b u t have t h e same
# l o c a t i o n on t h e LOGICAL GRID . . .
i f n o t ( node1 i s node2 ) and node1 . t p t == node2 . t p t :
c o n f l i c t s = TRUE # . . . t h e n we ve f o u n d a c o n f l i c t !
d e b u g p r i n t ( " Resolving " , node1 , "vs ." , node2 )
node1 . r e s o l v e ( node2 ) # L e t t h e n o d e s t h e m s e l v e s duke i t o u t
break
i f c o n f l i c t s : break
return conflicts
# A d j us t s the l o g i c a l l o c a t i o n s of a l l the nodes i n the graph so that
# e v e r y node i s g r e a t e r t h a n ( 0 , 0 ) . R e t u r n s t h e maximum s o t h a t t h e
# g r i d l a y o u t a l g o r i t h m can do i t s magic .
def adjust min max ( s e l f ) :
gmax = [ RE MINGRID , RE MINGRID ]
gmin = [ RE MAXGRID , RE MAXGRID ]
f o r dimen i n [ 0 , 1 ] : # Do x t h e n y a x i s
# F i n d t h e minimum and maximum i n t h i s d i m e n s i o n
f o r node i n s e l f . n o d e L i s t :
gmin [ dimen ] = min ( node . t p t [ dimen ] , gmin [ dimen ] )
gmax [ dimen ] = max ( node . t p t [ dimen ] , gmax [ dimen ] )
# S h i f t a l l n o d e s by t h e minimum v a l u e
f o r node i n s e l f . n o d e L i s t :
node . t p t [ dimen ] = node . t p t [ dimen ] + 1 ( gmin [ dimen ] )
# C o r r e c t t h e maximum f o r t h i s s h i f t
gmax [ dimen ] = gmax [ dimen ] + 1 ( gmin [ dimen ] )
r e t u r n gmax
# D e t e r m i n e s t h e PHYSICAL c o o r d i n a t e s o f e v e r y node b a s e d on t h e i r
# LOGICAL c o o r d i n a t e s . S i n c e n o d e s a r e n o t o f a f i x e d s i z e , t h i s f u n c t i o n
# e n s u r e s t h a t n o d e s do n o t o v e r l a p e a c h o t h e r .
d e f g r i d l a y o u t ( s e l f , draw =0):
# S h i f t t h e g r a p h t o t h e ( 0 , 0 ) o r i g i n , and o b t a i n t h e o v e r a l l s i z e
gmax = s e l f . a d j u s t m i n m a x ( )
# C r e a t e two empty n e s t e d l i s t s t o s t o r e v a l u e s i n t o
rowcolmax = [ [ ] , [ ] ] # t h e maximum node s i z e i n e a c h row o r column
rowc olsum = [ [ ] , [ ] ] # t h e r u n n i n g sum o / t s i z e s o f e a c h row o r column
f o r dimen i n [ 0 , 1 ] : # do x t h e n y a x i s
f o r x i n x r a n g e ( gmax [ dimen ] + 1 ) :
rowcolmax [ dimen ] . append ( 0 )
rowcols um [ dimen ] . append ( 0 )
# d e t e r m i n e t h e maximum node s i z e i n e a c h row and column
f o r dimen i n [ 0 , 1 ] : # do x t h e n y a x i s
f o r node i n s e l f . n o d e L i s t :
n o d e a t = node . t p t [ dimen ]
rowcolmax [ dimen ] [ n o d e a t ] = (

CHAPTER 10. GRAPH LAYOUT ALGORITHM

178

max ( node . s p t [ dimen ] , rowcolmax [ dimen ] [ n o d e a t ] ) )


# S t o r e a r u n n i n g t o t a l o f e a c h row o r column s i z e , a d d i n g p a d d i n g
# a l o n g t h e way .
# row ( x ) = sum ( row ( 1 ) . . . row ( x 1 ) ) + 2 ( p a d d i n g x )
overallsum = [0,0]
f o r dimen i n [ 0 , 1 ] : # do x t h e n y a x i s
sum = RE CANVAS PAD [ dimen ]
f o r x i n x r a n g e ( gmax [ dimen ] + 1 ) :
sum = sum + ( rowcolmax [ dimen ] [ x ] / 2 ) + RE PAD [ dimen ]
rowcols um [ dimen ] [ x ] = sum
sum = sum + ( rowcolmax [ dimen ] [ x ] / 2 ) + RE PAD [ dimen ]
o v e r a l l s u m [ dimen ] = sum + RE CANVAS PAD [ dimen ]
# S t o r e t h e c o r r e s p o n d i n g PHYSICAL c o o r d i n a t e s w i t h e a c h node
f o r dimen i n [ 0 , 1 ] :
f o r node i n s e l f . n o d e L i s t :
node . n p t [ dimen ] = ro w c o l sum [ dimen ] [ node . t p t [ dimen ] ]
s e l f [ scrollregion ] = s e l f . bbox ( all )
# A n i m a t e s a l l t h e n o d e s from t h e i r o l d PHYSICAL p o s i t i o n t o t h e i r
# new PHYSICAL p o s i t i o n
def animate ( s e l f ) :
# I t e r a t i o n f o r each frame of the animation
f o r f a c t o r i n x r a n g e ( RE ANIMATION FRAMES ) :
f = ( f a c t o r / RE ANIMATION FRAMES)
f o r node i n s e l f . n o d e L i s t :
node . m o v e b y f a c t o r ( f )
s e l f . m a s t e r . u p d a t e ( ) # u p d a t e T k i n t e r window
# We r e done a n i m a t i n g , s o u p d a t e a l l t h e o l d c o o r d i n a t e s
# t o t h e new c o o r d i n a t e s
f o r node i n s e l f . n o d e L i s t :
node . f i n a l i z e c o o r d ( )
d e b u g p r i n t ( node , ":" , node . t p t )

10.4.2

Nodes.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# Nodes . py
#
#
Node c l a s s e s
# V i s u a l Pelog imports
from U t i l i m p o r t
from C o n s t a n t s i m p o r t
import CanvasItems , RuleEditor

CHAPTER 10. GRAPH LAYOUT ALGORITHM

179

# Tkinter imports
i m p o r t Canvas
# Standard l i b r a r y imports
i m p o r t math

##################################################
# VisualObject
#
# Any o b j e c t t h a t h a s a v i s u a l r e p r e s e n t a t i o n
# on t h e r u l e e d i t o r c a n v a s i n h e r i t s from V i s u a l O b j e c t
class VisualObject :
spt = (32, 32) # s i z e of object
#
# I n i t i a l i z a t i o n functions
init ( self ):
def
s e l f . pt = [ 0 , 0 ]
# Absolute coordinate of object
s e l f . npt = [ 0 , 0 ]
# New c o o r d i n a t e o f o b j e c t t o a n i m a t e t o
s e l f . s e l e c t e d = FALSE
s e l f . h i g h l i g h t e d = FALSE
def v i s u a l i z e ( s e l f ) :
self . visual = []
def d e v i s u a l i z e ( s e l f ) :
f o r item i n s e l f . v i s u a l :
item . d e l e t e ()
#
# User f u n c t i o n s
def update visual ( s e l f ) :
f o r element in s e l f . v i s u a l :
element . update ( )
def move visual ( s e l f ) :
f o r element in s e l f . v i s u a l :
element . m o v e t o f i n a l ()
def move by factor ( s e l f , factor ) :
f o r element in s e l f . v i s u a l :
element . move by factor ( f a c t o r )

##################################################
# GenericNode
#
# Any o b j e c t t h a t must be p o s i t i o n e d on t h e g r a p h
# u s i n g the graph l a y o u t a l g o r i t h m
c l a s s GenericNode ( V i s u a l O b j e c t ) :

CHAPTER 10. GRAPH LAYOUT ALGORITHM

180

#
# I n i t i a l i z a t i o n functions
EQUALITY TYPE = ""
COMPARISON TYPE = ""
n o d e t y p e = generic
a , b = None , None
def

init

( s e l f , m a s t e r , t e x t="-" , e q u a l = ( [ ] , [ ] ) ,
g r e a t e r = ( [ ] , [ ] ) , l e s s = ( [ ] , [ ] ) , near = [ ] ) :
VisualObject .
init ( self )
# R e f e r e n c e t o t h e r u l e e d i t o r c a n v a s t h a t owns t h i s node
s e l f . master = master
s e l f . text = text
s e l f . tpt = [0,0]

# l a b e l t o be drawn on node
# T o p o l o g i c a l c o o r d i n a t e o f node

# constraints
s e l f . equal = equal
self . less = less
self . greater = greater
s e l f . near = near
# t h e d i r e c t i o n i n w hi c h t h e l a s t r e s o l v e o p e r a t i o n
# bouncedo u t a n o t h e r node
self . last resolve = 2
# add t h i s node t o t h e r u l e e d i t o r c a n v a s
s e l f . master . add node ( s e l f )
# c r e a t e a l l t h e v i s u a l e l e m e n t s r e l a t e d t o t h i s node
s e l f . v i s u a l i z e ()
def delete ( s e l f ) :
# remove t h e v i s u a l e l e m e n t s from t h e c a n v a s
s e l f . d e v i s u a l i z e ()
# remove s e l f from node l i s t
s e l f . master . remove node ( s e l f )
# s t r i n g r e p r e s e n t a t i o n o f t h i s node . D i s p l a y e d i n t h e
# s t a t u s b a r when t h e node i s h i g h l i g h t e d
def
repr ( self ):
return s e l f . text
#
# Event h a n d l e r s
# When t h e b u t t o n i s d e p r e s s e d , s e l f i s t h e s t a r t i n g node
# of the drag o p e r a t i o n
def buttonpress ( s e l f , args ) :

CHAPTER 10. GRAPH LAYOUT ALGORITHM

181

s e l f . master . s e t s t a r t n o d e ( s e l f )
# When t h e b u t t o n i s r e l e a s e d , we must f i n d t h e node t h a t i t
# was r e l e a s e d on t o d e t e r m i n e t h e f i n i s h i n g node o f t h e d r a g
# operation
def buttonrelease ( s e l f , args ) :
s e l f . master . s e t f i n i s h n o d e ( s e l f . master . f i n d n o d e ( args . x , args . y ))
s e l f . master . r u n t o o l ()
s e l f . m a s t e r . s e t s t a r t n o d e ( None )
s e l f . m a s t e r . s e t f i n i s h n o d e ( None )
def h i g h l i g h t ( s e l f , args ) :
pass
def unhighlight ( s e l f , args ) :
pass
def s e l e c t ( s e l f ) :
pass
def unselect ( s e l f ) :
pass
#
# Graph L a y o u t A l g o r i t h m f u n c t i o n s
# r e t u r n s t h e a v e r a g e LOGICAL p o s i t i o n o f t h e n o d e s
def f i n d a v e r a g e ( s e l f , nodes ) :
i f nodes = = [ ] :
r e t u r n [ RE MAXGRID , RE MAXGRID ]
else :
sum = [ 0 , 0 ]
f o r dimen i n [ 0 , 1 ] : # x t h e n y a x i s
p r i n t nodes
f o r node i n n o d e s :
# b u i l d up sum o f a x i s c o o r d i n a t e s
p r i n t " Summing " , node
sum [ dimen ] = sum [ dimen ] + node . t p t [ dimen ]
# d i v i d e by t h e number o f n o d e s
# r e s u l t must be an i n t e g e r ( LOGICAL c o o r d i n a t e s a r e
# always i n t e g e r s )
sum [ dimen ] = i n t ( math . c e i l ( sum [ dimen ] / l e n ( n o d e s ) ) )
r e t u r n sum
# Moves t h e node t o t h e a v e r a g e LOGICAL p o s i t i o n o f t h e n o d e s
# s p e c i f i e d by t h e n e a r c o n s t r a i n t , i f any .
def go to near ( s e l f ) :
avg = s e l f . f i n d a v e r a g e ( s e l f . n e a r )
i f avg ! = [ RE MAXGRID , RE MAXGRID ] :
f o r dimen i n [ 0 , 1 ] :
s e l f . t p t [ dimen ] = avg [ dimen ]
d e b u g p r i n t ( s e l f , " going to average of :" ,
s e l f . n e a r , "=" , s e l f . t p t [ 0 ] , s e l f . t p t [ 1 ] )

CHAPTER 10. GRAPH LAYOUT ALGORITHM

182

# A s s i g n s t h i s node a l o g i c a l c o o r d i n a t e b a s e d on i t s r e l a t i o n s h i p
# t o o t h e r n o d e s s p e c i f i e d by t h e c o n s t r a i n t s g r e a t e r , l e s s and e q u a l .
# C o n s t r a i n t s a r e a l w a y s s a t i s f i e d by moving n o d e s o u t w a rd ( SE ) . T h i s
# makes t h e g r a p h s somewhat l e s s v i s u a ls p a c e e f f i c i e n t , b u t e l i m i n a t e s
# future conflicts
d e f t o p o l o g i c a l s o r t ( s e l f , dimen , tmin , tmax ) :
# E n s u r e t h i s node i n between tmin and tmax
s e l f . t p t [ dimen ] = min ( max ( s e l f . t p t [ dimen ] , tmin ) , tmax )
s e l f . s o l v e e q u a l c o n s t r a i n t ( dimen )
s e l f . s o l v e g r e a t e r c o n s t r a i n t ( dimen )
s e l f . s o l v e l e s s c o n s t r a i n t ( dimen )
d e b u g p r i n t ( s e l f . t e x t , " sorted to:" , s e l f . t p t [ 0 ] , s e l f . t p t [ 1 ] )
# T h i s node must hav e t h e same l o g i c a l c o o r d i n a t e a s e v e r y node l i s t e d i n i t s
# equal constraint .
d e f s o l v e e q u a l c o n s t r a i n t ( s e l f , dimen ) :
f o r e q u a l i n s e l f . e q u a l [ dimen ] : # For e a c h e q u a l c o n s t r a i n t
# I f t h i s node i s c u r r e n t l y n o t m e e t i n g i t s e q u a l c o n s t r a i n t . . .
i f e q u a l . t p t [ dimen ] ! = s e l f . t p t [ dimen ] :
d e b u g p r i n t ( " Equal constraint " )
# t h e n move t h e l e s s e r node (NW) o u tw a rd ( SE ) t o s a t i s f y t h e
# constraint
i f e q u a l . t p t [ dimen ] < s e l f . t p t [ dimen ] :
e q u a l . t o p o l o g i c a l s o r t ( dimen , s e l f . t p t [ dimen ] ,
s e l f . t p t [ dimen ] )
else :
s e l f . t o p o l o g i c a l s o r t ( dimen , e q u a l . t p t [ dimen ] ,
e q u a l . t p t [ dimen ] )
# T h i s node must hav e a g r e a t e r ( SE ) l o g i c a l c o o r d i n a t e t h a n a l l t h e n o d e s
# l i s t e d in i t s greater constraint
def s o l v e g r e a t e r c o n s t r a i n t ( s e l f , dimen ) :
f o r g r e a t e r i n s e l f . g r e a t e r [ dimen ] : # For e a c h g r e a t e r c o n s t r a i n t
# I f t h i s node i s n o t m e e t i n g i t s g r e a t e r c o n s t r a i n t . . .
i f s e l f . t p t [ dimen ] <= g r e a t e r . t p t [ dimen ] :
d e b u g p r i n t ( " Greater constraint " )
# move i t s o i t i s g r e a t e r
s e l f . t o p o l o g i c a l s o r t ( dimen , g r e a t e r . t p t [ dimen ] + 1 ,
RE MAXGRID)
# T h i s node must hav e a l e s s e r (NW) l o g i c a l c o o r d i n a t e t h a n a l l t h e n o d e s
# l i s t e d in i t s l e s s e r constraint
def s o l v e l e s s c o n s t r a i n t ( s e l f , dimen ) :
f o r l e s s i n s e l f . l e s s [ dimen ] : # For e a c h l e s s c o n s t r a i n t
# I f the c o n s t r a i n t i s not being s a t i s f i e d . . .
i f s e l f . t p t [ dimen ] > ] = l e s s . t p t [ dimen ] :
# . . . move t h e o t h e r node o ut ward s o i t i s g r e a t e r t h a n t h i s node
l e s s . t o p o l o g i c a l s o r t ( dimen , s e l f . t p t [ dimen ] + 1 ,
RE MAXGRID)

CHAPTER 10. GRAPH LAYOUT ALGORITHM

183

# D e c i d e s where t o move n o d e s when two n o d e s o c c u p y t h e same


# LOGICAL c o o r d i n a t e b a s e d on t h e a v e r a g e p o s i t i o n o f t h e
# n o d e s t h a t t h i s node i s c o n s t r a i n e d t o be n e a r .
resolve options def = [
( selfavgy > node2avgy , # Up
node2 . topological_sort (1, RE_MINGRID , self.tpt [1] - 1) ) ,
( selfavgx < node2avgx , # R i g h t
node2 . topological_sort (0, node2 .tpt [0] + 1, RE_MAXGRID ) ) ,
( selfavgy < node2avgy , # Down
node2 . topological_sort (1, node2 .tpt [1] + 1, RE_MAXGRID ) ) ]
# p r ec o m p i l e t h e s e o p t i o n s
resolve options = []
for option in res olve options def :
e x p r e s s i o n = c o m p i l e ( o p t i o n [ 0 ] , dynamic code , eval )
s t a t e m e n t = c o m p i l e ( o p t i o n [ 1 ] , dynamic code , exec )
r e s o l v e o p t i o n s . append ( ( e x p r e s s i o n , s t a t e m e n t ) )
d e f r e s o l v e ( s e l f , node2 ) :
s e l f a v g x , s e l f a v g y = s e l f . f i n d a v e r a g e ( s e l f . near )
n o d e 2 a v g x , n o d e 2 a v g y = s e l f . f i n d a v e r a g e ( node2 . n e a r )
# Try a l l t h e p o s s i b l e o p t i o n s i n o r d e r , s t a r t i n g w i t h
# the d i r e c t i o n a f t e r s e l f . l a s t r e s o l v e
r e s o l v e d = FALSE
f o r d i r in range ( 0 , len ( s e l f . r e s o l v e o p t i o n s ) ) :
go = ( d i r + s e l f . l a s t r e s o l v e + 1 ) % 3
i f e v a l ( s e l f . r e s o l v e o p t i o n s [ go ] [ 0 ] ) :
e x e c s e l f . r e s o l v e o p t i o n s [ go ] [ 1 ]
s e l f . l a s t r e s o l v e = go
r e s o l v e d = TRUE
break
# i f none o f t h e o p t i o n s were t a k e n , j u s t p i c k t h e
# f i r s t one
i f not r e s o l v e d :
go = ( s e l f . l a s t r e s o l v e + 1 ) % 3
e x e c s e l f . r e s o l v e o p t i o n s [ go ] [ 1 ]
s e l f . l a s t r e s o l v e = go
# U p da t e s t h e new p o s i t i o n i n t o t h e o l d p o s i t i o n
def f i n a l i z e c o o r d ( s e l f ) :
i f ( s e l f . npt != s e l f . pt ) :
s e l f . pt [ 0 ] , s e l f . pt [ 1 ] = s e l f . npt [ 0 ] , s e l f . npt [ 1 ]
s e l f . move visual ()

##################################################
# HightlightableNode
#

CHAPTER 10. GRAPH LAYOUT ALGORITHM

184

# s u p e r c l a s s o f n o d e s t h a t can be h i g h l i g h t e d
# ( become b o l d when t h e mouse h o v e r s )
class HighlightableNode :
def h i g h l i g h t ( s e l f , args ) :
s e l f . h i g h l i g h t e d = TRUE
s e l f . update visual ()
s e l f . master . d e s c r i b e n o d e ( s e l f )
s e l f . m a s t e r . s e t c u r s o r ( hand2 )
def unhighlight ( s e l f , args ) :
s e l f . h i g h l i g h t e d = FALSE
s e l f . update visual ()
s e l f . master . undescribe node ()
s e l f . master . r e s t o r e c u r s o r ()
##################################################
# SelectableNode
#
# s u p e r c l a s s o f n o d e s t h a t can be s e l e c t e d u s i n g
# the S e l e c t i o n t o o l .
c lass SelectableNode :
def s e l e c t ( s e l f ) :
s e l f . s e l e c t e d = TRUE
s e l f . update visual ()
s e l f . master . keyboard . s e t c a l l b a c k ( s e l f . k e y b o a r d c a l l b a c k )
s e l f . i n i t e d i t w i d g e t s ()
def unselect ( s e l f ) :
s e l f . s e l e c t e d = FALSE
s e l f . update visual ()
s e l f . m a s t e r . k e y b o a r d . s e t c a l l b a c k ( None )
s e l f . destroy edit widgets ()
def i n i t e d i t w i d g e t s ( s e l f ) :
pass
def destroy edit widgets ( s e l f ) :
pass
def keyboard callback ( s e l f , noteList ) :
pass
##################################################
# NoteNode
#
# The s t a t i c n o t e n o d e s i n t h e g r a p h
c l a s s NoteNode ( H i g h l i g h t a b l e N o d e , G e n e r i c N o d e ) :
EQUALITY TYPE = " pitch "
COMPARISON TYPE = " pitch "
n o d e t y p e = note

CHAPTER 10. GRAPH LAYOUT ALGORITHM

185

spt = (16, 16)


def

init

( s e l f , master , m e t a v a r i a b l e ,
prevX=None , prevY=None , box=FALSE ) :
# The name o f t h e m e t a v a r i a b l e t h i s node r e p r e s e n t s
s e l f . metavariable = metavariable
# I s t h i s t h e c u r r e n t node ?
s e l f . box = box
# The prevX
i f prevX ==
prevX =
else :
prevX =

and prevY a r g u m e n t s a r e c o n v e r t e d i n t o l e s s c o n s t r a i n t s
None :
[]
[ prevX ]

i f prevY == None :
prevY = [ ]
else :
prevY = [ prevY ]
GenericNode .

init

( s e l f , master ,
t e x t=m e t a v a r i a b l e ,
g r e a t e r =( prevX , prevY ) ,
e q u a l =( prevY , prevX ) )

def v i s u a l i z e ( s e l f ) :
i f s e l f . box :
self . visual = [
CanvasItems . SolidNoteBitmap ( s e l f ) ]
else :
self . visual = [
C a n v a s I t e m s . HollowNoteBitmap ( s e l f ) ]
##################################################
# BinaryRelation
#
# A g e n e r i c b i n a r y r e l a t i o n node from w h i c h o t h e r
# binary operators are derived
c l a s s B i n a r y R e l a t i o n ( HighlightableNode , SelectableNode , GenericNode ) :
bitmap = binary .xbm
# C r e a t e s a new B i n a r y R e l a t i o n between n o d e s a and b
def
i n i t ( s e l f , m a s t e r , a , b , t e x t="-" ) :
self .a, self .b = a, b
less = [[],[]]
greater = [ [ ] , [ ] ]
# R e l a t i o n s h i p s between two n o t e s i n t h e same row s h o u l d
# be c o n s t r a i n e d s o t h e y don t c r o s s t h e o t h e r row
i f a . tpt [1] == b . tpt [ 1 ] : # H o r i z o n t a l r e l a t i o n s h i p
i f a . t p t [ 1 ] = = m a s t e r . r u l e . Top row . t p t [ 1 ] :

CHAPTER 10. GRAPH LAYOUT ALGORITHM

186

l e s s = [ [ ] , [ m a s t e r . r u l e . Bottom row ] ]
else :
g r e a t e r = [ [ ] , [ m a s t e r . r u l e . Top row ] ]
# T h i s node s h o u l d be c o n s t r a i n e d between t h e o t h e r
# two n o d e s
f o r dimen i n [ 0 , 1 ] :
i f a . t p t [ dimen ] ! = b . t p t [ dimen ] :
i f a . t p t [ dimen ] < b . t p t [ dimen ] :
g r e a t e r [ dimen ] . append ( a )
l e s s [ dimen ] . append ( b )
else :
g r e a t e r [ dimen ] . append ( b )
l e s s [ dimen ] . append ( a )
GenericNode .

init

( s e l f , m a s t e r , t e x t=t e x t ,
g r e a t e r=g r e a t e r ,
l e s s=l e s s ,
n e a r =[ a , b ] )

# S e t t h e s t a r t i n g p o s i t i o n t o t h e a v e r a g e o f t h e n o d e s a and b
f o r dimen i n [ 0 , 1 ] :
s e l f . p t [ dimen ] = ( a . p t [ dimen ] + b . p t [ dimen ] ) / 2
def v i s u a l i z e ( s e l f ) :
self . visual = [
C a n v a s I t e m s . Edge ( s e l f , s e l f , s e l f . a ) ,
C a n v a s I t e m s . Edge ( s e l f , s e l f , s e l f . b ) ,
CanvasItems . NodeRectangle ( s e l f ) ,
CanvasItems . Label ( s e l f ) ,
C a n v a s I t e m s . NodeBitmap ( s e l f , s e l f . bitm ap ) ]

##################################################
# UnaryRelation
#
c l a s s UnaryRelation ( HighlightableNode , SelectableNode , GenericNode ) :
bitmap = unary .xbm
def

i n i t ( s e l f , m a s t e r , a , t e x t="-" ) :
self .a = a
# I f t h i s node i s c o n n e c t e d t o a n o t e n o d e i n t h e t o p row ,
# we don t want t h i s node t o go b e l o w t h e bottom row , and
# vice versa
i f a . t p t [ 1 ] = = m a s t e r . r u l e . Top row . t p t [ 1 ] :
l e s s = [ [ ] , [ m a s t e r . r u l e . Bottom row ] ]
else :
g r e a t e r = [ [ ] , [ m a s t e r . r u l e . Top row ] ]
GenericNode .

init

( s e l f , m a s t e r , t e x t=t e x t ,

CHAPTER 10. GRAPH LAYOUT ALGORITHM


n e a r =[ a ] )
# s e t t h e s t a r t i n g p o s i t i o n t o t h a t o f node a
f o r dimen i n [ 0 , 1 ] :
s e l f . p t [ dimen ] = a . p t [ dimen ]
def v i s u a l i z e ( s e l f ) :
self . visual = [
C a n v a s I t e m s . Edge ( s e l f , s e l f , s e l f . a ) ,
CanvasItems . NodeRectangle ( s e l f ) ,
CanvasItems . Label ( s e l f ) ,
C a n v a s I t e m s . NodeBitmap ( s e l f , s e l f . bitm ap ) ]

10.4.3

CanvasItems.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# C a n v a s I t e m s . py
#
#
V a r i o u s g e o m e t r i c i t e m s t h a t a r e p l a c e d on t h e
#
Rule E d i t o r canvas
#
#
A l l o f t h e s e i t e m s a r e i n h e r i t e d from t h e i r c o r r e s p o n d i n g
#
T k i n t e r c l a s s e s , b u t have been e x t e n d e d t o automate
#
t h e i r r e l a t i o n s h i p to nodes i n the graph l a y o u t a l g o r i t h m
# V i s u a l Pelog imports
from U t i l i m p o r t
from C o n s t a n t s i m p o r t
# Tkinter imports
i m p o r t Canvas
from T k C o n s t a n t s i m p o r t
##################################################
# CanvasItem
#
# The b a s e c l a s s e s f o r a l l c l a s s e s i n t h i s module
c l a s s CanvasItem :
def
i n i t ( s e l f , node ) :
# The node t h a t t h i s c a n v a s i t e m b e l o n g s t o
s e l f . node = node
# Move t h e i t e m t o i t s c o r r e c t p o s i t i o n
s e l f . move to final ()
# Update any c o l o r o r a p p e a r a n c e a t t r i b u t e s
s e l f . update ( )
# Bind e v e n t s t o t h e c o r r e s p o n d i n g node

187

CHAPTER 10. GRAPH LAYOUT ALGORITHM


s e l f . b i n d ( s e q u e n c e="< Enter >" ,
command= s e l f . node . h i g h l i g h t )
s e l f . b i n d ( s e q u e n c e="< Leave >" ,
command= s e l f . node . u n h i g h l i g h t )
s e l f . b i n d ( s e q u e n c e="< ButtonRelease >" ,
command= s e l f . node . b u t t o n r e l e a s e )
s e l f . b i n d ( s e q u e n c e="< ButtonPress >" ,
command= s e l f . node . b u t t o n p r e s s )
# Move t h i s i t e m t o t h e l o c a t i o n o f t h e node
def move to final ( s e l f ) :
s e l f . move ( s e l f . node . p t [ 0 ] , s e l f . node . p t [ 1 ] )
# Move between t h e o l d and new p o s i t i o n o f t h e node
# by a g i v e n f a c t o r . Used f o r a n i m a t i o n .
def move by factor ( s e l f , factor ) :
s e l f . move (
( ( s e l f . node . n p t [ 0 ] s e l f . node . p t [ 0 ] )
f a c t o r + s e l f . node . p t [ 0 ] ) ,
( ( s e l f . node . n p t [ 1 ] s e l f . node . p t [ 1 ] )
f a c t o r + s e l f . node . p t [ 1 ] ) )
# Move t h e i t e m t o a g i v e n p o i n t
d e f move ( s e l f , x , y ) :
s e l f . coords ( [ ( x , y ) ] )
# Update any c o l o r o r a p p e a r a n c e a t t r i b u t e s b a s e d on
# t h e c u r r e n t s t a t e o f t h e c o r r e s p o n d i n g node
def update ( s e l f ) :
pass
# Does some c o l o u r s e l e c t i o n magic
def g e t c o l o r s ( s e l f ) :
i f s e l f . node . s e l e c t e d :
i f s e l f . node . h i g h l i g h t e d :
o u t l i n e = RE COLOR HIGHSELECT
else :
o u t l i n e = RE COLOR SELECTED
f i l l = RE COLOR NODE SELECTED
t e x t = RE COLOR TEXT SELECTED
width = 2
else :
i f s e l f . node . h i g h l i g h t e d :
o u t l i n e = RE COLOR HIGHLIGHTED
width = 2
text = outline
else :
o u t l i n e = RE COLOR NORMAL
width = 1
t e x t = RE COLOR TEXT
f i l l = RE COLOR NODE

188

CHAPTER 10. GRAPH LAYOUT ALGORITHM


r e t u r n o u t l i n e , width ,

f i l l , text

##################################################
# Bitmap
#
c l a s s Bitmap ( C a n v a s I t e m , Canvas . Bitmap ) :
bitmap = " default .xbm"
def

i n i t ( s e l f , node ) :
Canvas . Bitmap .
init (
s e l f , node . m a s t e r ,
0, 0,
bitmap= @images /+ s e l f . bitmap )
CanvasItem .
i n i t ( s e l f , node )

def update ( s e l f ) :
x , y , z , color = s e l f . get colors ()
s e l f [ foreground ] = c o l o r
c l a s s HollowNoteBitmap ( Bitmap ) :
bitmap = " hollownote .xbm"
c l a s s S o l i d N o t e B i t m a p ( Bitmap ) :
bitmap = " solidnote .xbm"
c l a s s NodeBitmap ( Bitmap ) :
def
i n i t ( s e l f , node , bitmap ) :
s e l f . bitmap = bitmap
Bitmap .
i n i t ( s e l f , node )
##################################################
# NodeRectangle
#
c l a s s N o d e R e c t a n g l e ( C a n v a s I t e m , Canvas . R e c t a n g l e ) :
def
i n i t ( s e l f , node ) :
Canvas . R e c t a n g l e .
init (
s e l f , node . m a s t e r ,
0, 0, 0, 0)
CanvasItem .
i n i t ( s e l f , node )
d e f move ( s e l f , x , y ) :
c1x = x ( s e l f . node . s p t [ 0 ] / 2 )
c1y = y ( s e l f . node . s p t [ 1 ] / 2 )
c2x = c1x + s e l f . node . s p t [ 0 ]
c2y = c1y + s e l f . node . s p t [ 1 ]
s e l f . c o o r d s ( [ ( c1x , c1y ) , ( c2x , c 2y ) ] )
def update ( s e l f ) :
o u t l i n e , width , f i l l , x = s e l f . g e t c o l o r s ()
s e l f [ fill ] = f i l l
s e l f [ outline ] = o u t l i n e

189

CHAPTER 10. GRAPH LAYOUT ALGORITHM

190

s e l f [ width ] = w i d t h
##################################################
# Label
#
c l a s s L a b e l ( C a n v a s I t e m , Canvas . Canvas Text ) :
def
i n i t ( s e l f , node ) :
Canvas . CanvasTe xt .
init (
s e l f , node . m a s t e r ,
0, 0,
j u s t i f y = center ,
f i l l =RE COLOR TEXT)
CanvasItem .
i n i t ( s e l f , node )
d e f move ( s e l f , x , y ) :
s e l f . c o o r d s ( [ ( x , y + ( s e l f . node . s p t [ 1 ] / 2 ) 5 ) ] )
def update ( s e l f ) :
x , y , z , color = s e l f . get colors ()
s e l f . c o n f i g ( t e x t= s e l f . node . t e x t )
##################################################
# Edge
#
c l a s s Edge ( C a n v a s I t e m , Canvas . L i n e ) :
i n i t ( s e l f , node , a , b ) :
def
Canvas . L i n e .
init (
s e l f , node . m a s t e r ,
0, 0, 0, 0)
s e l f . lower ()
self .a = a
self .b = b
CanvasItem .
i n i t ( s e l f , node )
def move to final ( s e l f ) :
x1 , y1 = t u p l e ( s e l f . a . p t )
x2 , y2 = t u p l e ( s e l f . b . p t )
s e l f . move ( x1 , y1 , x2 , y2 )
def move by factor ( s e l f ,
s e l f . move (
( s e l f . a . npt [ 0 ]
( s e l f . a . npt [ 1 ]
( s e l f . b . npt [ 0 ]
( s e l f . b . npt [ 1 ]

factor ):
self
self
self
self

. a . pt
. a . pt
. b . pt
. b . pt

[0])
[1])
[0])
[1])

factor
factor
factor
factor

d e f move ( s e l f , x1 , y1 , x2 , y2 ) :
s e l f . c o o r d s ( [ ( x1 , y1 ) , ( x2 , y2 ) ] )
def update ( s e l f ) :
o u t l i n e , width ,

f i l l , x = s e l f . get colors ()

+
+
+
+

self
self
self
self

. a . pt [ 0 ] ,
. a . pt [ 1 ] ,
. b . pt [ 0 ] ,
. b . pt [ 1 ] )

CHAPTER 10. GRAPH LAYOUT ALGORITHM


s e l f [ fill ] = o u t l i n e
s e l f [ width ] = w i d t h

191

11

A collection of custom
Megawidgets implemented in
Python/Tkinter
One of the unexpected bottlenecks in the Visual Pelog project was the number of
custom megawidgets1 that were required. While pure Tcl/Tk is now quite mature
and has a number of ready-made megawidgets bundled with the distribution and
available on the internet, Python/Tkinter remains somewhat lacking in this respect.
The most promising attempt to remedy this situation is Pmw (Python Megawidgets).2 While this project aims to fill some holes, it does not yet offer many of the
standard types of controls one expects from a fully mature GUI toolkit.
The megawidgets that were implemented specifically for this project were:
Tree widget: a heirarchical tree display similar to that found in Microsoft
Windows Explorer.
Help browser: an on-line help browser that supports a very tiny subset of
HTML.
On-screen musical keyboard: a standard Western musical keyboard that can
be used to display and enter pitch-related information.
1

Megawidgets are user-interface elements implemented in the very high-level language itself.
They are made up of the basic widgets that come standard with of the GUI system.
2
Python Megawidgets http://www.dscpl.com.au/pmw/

192

CHAPTER 11. CUSTOM MEGAWIDGETS

11.1

193

Tree Widget3

The tree widget as implemented here is quite general and is well-suited to extention
using object-oriented inheritance.

11.1.1

End user usage

Figure 11.1: An example tree widget.


The tree widget displays structured information in a vertically-oriented heirarchical tree (Figure 11.1). This type of widget should be familiar to users of Microsoft
Windows, as it mimics the behaviour of Microsoft Windows Explorer as closely as
possible.
Nodes appear as two types:
Leaf:

A node that contains data. (In the case of Visual Pelog, it contains a rule.)
When the node is double-clicked on or the Enter / Return key is pressed, the
data in the node is launched or opened for editing.

Branch:
A branch node contains other nodes. It can either be open (displaying all its children nodes) or closed. Clicking on the open/close box toggles
the open/close state of the branch.
Nodes are selected by clicking on them. A selected node appears inside a box (Figure
11.2. The selected node can be dragged to other locations in the heirarchy, or it
can be deleted by pressing the Delete key.
3

A fairly exhaustive internet-search revealed no ready-made general purpose tree widgets of


this type implemented in Python. Ironically, within days of creating my own implemntation from
scratch, two separate tree widgets appeared on the Python language website. www.python.org

CHAPTER 11. CUSTOM MEGAWIDGETS

194

Figure 11.2: The appearance of a selected node.

11.1.2

Developer usage

It is important to note the distinction between the class that creates a Tkinter
widget, TreeWidget, and the class that handles the data in the widget, TreeNode.
TreeWidget class
The TreeWidget class inherits from the Tkinter.Canvas widget. To create a new
TreeWidget, simply use the default constructor:
newTree = Tree . TreeWidget ( m a s t e r )

where master is the window that will own the TreeWidget.


TreeNode
To add nodes to the TreeWidget, you must first get the root TreeNode from the
TreeWidget:
r o o t = newTree . g e t n o d e ( )

Then, nodes can be arbitrarily added or deleted to the heirarchy using the following
functions. All changes made to the TreeNode structure are automatically reflected
in the TreeWidget. Note that TreeNode is completely polymorphic: leaf nodes can
contain any type of data. The text displayed to the user on the TreeWidget is its
text representation (i.e. returned by the built-in repr() function).
node.append(data)
Adds a leaf containing data to the list nodes
children.
node a.append node(node b) Adds the arbitrary sub-tree node b to the list
of node as children.
node.remove(data)
Removes a node containing data from the list
of nodes children.
node a.remove node(node b) Removes the arbitrary sub-tree node b from
the list of node as children.
When the Enter key is pressed, a leaf node is launched. Launching could entail
opening the data, editing the data or performing some other action. In order to
support this functionality, the data type stored at a leaf must contain a launch()
function. If the data type does not contain a launch() function, the TreeWidget
gracefully does nothing.

11.1.3
Code

Tree.py

CHAPTER 11. CUSTOM MEGAWIDGETS

195

# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com


############################################################
# Tree . py
#
#
A g e n e r a lp u r p o s e t r e e w i d g e t f o r T k i n t e r
from U t i l i m p o r t
# Tkinter imports
i m p o r t T k i n t e r , Canvas
from T k C o n s t a n t s i m p o r t
from C o n s t a n t s i m p o r t
#
# Constants
Y STEP = 2
X STEP = 18
FONT = - adobe - helvetica - medium -r- normal -*-11-80-100-100- p-56- iso8859 -1
LINE = 5
OPEN BOX = 5
ICON = 22
TEXT = 35
OPENBM MASKDATA = "" "# define solid_width 9\n# define solid_height 9
s t a t i c unsigned char s o l i d b i t s [] = {
0 x f f , 0 x01 , 0 x f f , 0 x01 , 0 x f f , 0 x01 , 0 x f f , 0 x01 , 0 x f f , 0 x01 , 0 x f f , 0 x01 ,
0 x f f , 0 x01 , 0 x f f , 0 x01 , 0 x f f , 0 x01
} ; """
OPENBM DATA = "" "# define open_width 9\n# define open_height 9
s t a t i c unsigned char open bits [] = {
0 x f f , 0 x01 , 0 x01 , 0 x01 , 0 x01 , 0 x01 , 0 x01 , 0 x01 , 0 x7d , 0 x01 , 0 x01 , 0 x01 ,
0 x01 , 0 x01 , 0 x01 , 0 x01 , 0 x f f , 0 x01
} ; """
CLOSEDBM DATA = "" "# define closed_width 9\n# define closed_height 9
s t a t i c unsigned char c l o s e d b i t s [] = {
0 x f f , 0 x01 , 0 x01 , 0 x01 , 0 x11 , 0 x01 , 0 x11 , 0 x01 , 0 x7d , 0 x01 , 0 x11 , 0 x01 ,
0 x11 , 0 x01 , 0 x01 , 0 x01 , 0 x f f , 0 x01
} ; """
OVER = None
DRAGGING = FALSE
##################################################
# TreeWidget
#
# A widget that d i s p l a y s a nested s t r u c t u r e of
# TreeNodes i n a h e i r a r c h i c a l manner
#

CHAPTER 11. CUSTOM MEGAWIDGETS

196

c l a s s TreeWidget ( T k i n t e r . T o p l e v e l ) :
#
# I n i t i a l i z a t i o n routines
def
i n i t ( s e l f , m a s t e r=None ) :
Tkinter . Toplevel .
i n i t ( s e l f , master )
s e l f . make widgets ()
s e l f . load images ()
s e l f . b i n d ( s e q u e n c e=< KeyPress > ,
f u n c= s e l f . k e y p r e s s )
# The t r e e t h a t c o n t a i n s t h e d a t a t o be d i s p l a y e d
s e l f . t r e e = TreeNode ( p a r e n t= s e l f , d a t a="Top" )
# Have t h e t r e e b u i l d i c o n s i n t h i s c a n v a s
self . tree . set parent ( self )
s e l f . s e l e c t e d = None
# d i s p l a y the t r e e
s e l f . build ()
def make widgets ( s e l f ) :
# The w i d g e t a s a w h o l e c o n s i s t s o f a c a n v a s and a s c r o l l b a r
s e l f . c a n v a s = T k i n t e r . Canvas (
s e l f , b a c k g r o u n d= white )
s e l f . v s c r o l l = Tkinter . Scrollbar (
s e l f , o r i e n t= vertical ,
command= s e l f . c a n v a s . y v i e w )
s e l f . c a n v a s [ yscrollcommand ] = s e l f . s c r o l l Y
s e l f . c a n v a s . g r i d ( row = 0 , column = 0 , s t i c k y=nesw )
s e l f . v s c r o l l . g r i d ( row = 0 , column = 1 , s t i c k y=ns )
s e l f . g r i d c o l u m n c o n f i g u r e ( 0 , w e i g h t =1)
s e l f . c a n v a s . b i n d ( s e q u e n c e="< ButtonRelease >" ,
f u n c= s e l f . r e l e a s e )
s e l f . c a n v a s . b i n d ( s e q u e n c e="< Motion >" ,
f u n c= s e l f . motion )
# T h i s l o a d s t h e r e q u i r e d i m a g e s from d i s k
def load images ( s e l f ) :
s e l f .OPENBM = T k i n t e r . Image (
bitmap , d a t a=OPENBM DATA , maskdata=OPENBM MASKDATA,
f o r e g r o u n d= black , b a c k g r o u n d= white )
s e l f . CLOSEDBM = T k i n t e r . Image (
bitmap , d a t a=CLOSEDBM DATA , maskdata=OPENBM MASKDATA,
f o r e g r o u n d= black , b a c k g r o u n d= white )
s e l f . FOLDEROPEN = T k i n t e r . Image (
photo , f o r m a t=GIF , f i l e = images / folderopen .gif )
s e l f . FOLDERCLOSED = T k i n t e r . Image (

CHAPTER 11. CUSTOM MEGAWIDGETS

197

photo , f o r m a t=GIF , f i l e = images / folderclosed .gif )


s e l f . LEAF = T k i n t e r . Image (
photo , f o r m a t=GIF , f i l e = images /leaf.gif )
def s c r o l l Y ( s e l f , f i r s t , l a s t ) :
s e l f . v s c r o ll . set ( f i r s t , last )
#
# Event h a n d l e r s
# Moves a l e a f node a s i t i s b e i n g d r a g g e d
d e f motion ( s e l f , a r g s ) :
i f s e l f . s e l e c t e d and DRAGGING :
s e l f . s e l e c t e d . move ( a r g s . x , a r g s . y )
# Drops a l e a f node i n t o i t s new p a r e n t
def r e l e a s e ( s e l f , args ) :
g l o b a l DRAGGING
DRAGGING = FALSE
if self . selected :
i f OVER and OVER ! = s e l f . s e l e c t e d :
s e l f . t r e e . remove node ( s e l f . s e l e c t e d )
OVER . a p p e n d n o d e ( s e l f . s e l e c t e d )
else :
s e l f . selected . restore pos ()
def keypress ( s e l f , args ) :
i f a r g s . keysym == Delete : # D e l e t e a node
if self . selected :
s e l f . t r e e . remove node ( s e l f . s e l e c t e d )
e l i f a r g s . keysym == Return : # E d i t a r u l e
if self . selected :
s e l f . s e l e c t e d . l a u n c h ( None )
e l i f a r g s . keysym == Insert : # I n s e r t a r u l e
if self . selected :
s e l f . selected . i n s e r t ()
#
# Information gathering
def get tree ( s e l f ) :
return s e l f . tree
def g e t s e l e c t i o n ( s e l f ) :
r e t u r n s e l f . s e l e c t e d . data
d e f s e t s e l e c t i o n ( s e l f , node ) :
s e l f . s e l e c t e d = node
s e l f . build ()
#
# Methods
# Draws t h e t r e e on t h e c a n v a s

CHAPTER 11. CUSTOM MEGAWIDGETS


def build ( s e l f ) :
# D e l e t e a l l t h e c a n v a s i t e m s from b e f o r e
s e l f . c a n v a s . d e l e t e ( all )
s e l f . t r e e . b u i l d l a y e r ( s e l f . canvas , s e l f . s e l e c t e d , 0 , 0 )
s e l f . c a n v a s [ scrollregion ] = s e l f . c a n v a s . bbox ( all )
##################################################
# TreeNode
#
# Each b r a n c h o r l e a f o f t h e t r e e i s a TreeNode
# Each node c o n t a i n s d a t a . For b r a n c h e s , t h i s
# i s most o f t e n a s t r i n g . The name shown f o r
# t h e node i s r e p r ( d a t a ) .
c l a s s TreeNode :
#
# I n i t i a l i z a t i o n routines
i n i t ( s e l f , p a r e n t=None , d a t a="NO DATA" , i c o n=None ) :
def
s e l f . parent = [ parent ]
self . children = []
s e l f . open = FALSE
s e l f . icon = icon
s e l f . data = data
s e l f . set images ()
def set images ( s e l f ) :
i f s e l f . parent [ 0 ] :
s e l f .OPENBM = s e l f . p a r e n t [ 0 ] . OPENBM
s e l f . CLOSEDBM = s e l f . p a r e n t [ 0 ] . CLOSEDBM
s e l f . LEAF = s e l f . p a r e n t [ 0 ] . LEAF
s e l f . FOLDEROPEN = s e l f . p a r e n t [ 0 ] . FOLDEROPEN
s e l f . FOLDERCLOSED = s e l f . p a r e n t [ 0 ] . FOLDERCLOSED
else :
s e l f .OPENBM = s e l f . CLOSEDBM = s e l f . LEAF = None
s e l f . FOLDEROPEN = s e l f . FOLDERCLOSED = None
def s e t c h i l d r e n f r o m l i s t ( s e l f , l i s t ) :
if list :
f o r item i n l i s t :
i f l e n ( item ) == 0:
node = s e l f . append ( i t e m )
else :
node = s e l f . append ( i t e m [ 0 ] )
node . s e t c h i l d r e n f r o m l i s t ( i t e m [ 1 ] )
def set parent ( s e l f , parent ) :
s e l f . p a r e n t . append ( p a r e n t )
s e l f . set images ()
def s et ic on ( s e l f , icon ) :
s e l f . icon = icon

198

CHAPTER 11. CUSTOM MEGAWIDGETS


#
# Node methods
d e f append ( s e l f , d a t a ) :
node = TreeNode ( p a r e n t= s e l f , d a t a=d a t a )
s e l f . a p p e n d n o d e ( node )
r e t u r n node
d e f a p p e n d n o d e ( s e l f , node ) :
s e l f . c h i l d r e n . append ( node )
s e l f . children . sort ()
print self . children
s e l f . build ()
d e f remove ( s e l f , d a t a ) :
f o r node i n s e l f . c h i l d r e n :
i f node . d a t a == d a t a :
s e l f . c h i l d r e n . remove ( node )
else :
node . remove ( d a t a )
s e l f . build ()
d e f r e m o v e n o d e ( s e l f , node ) :
for n in s e l f . children :
i f n == node :
s e l f . c h i l d r e n . remove ( node )
else :
n . r e m o v e n o d e ( node )
s e l f . build ()
def

repr ( self ):
r e t u r n s e l f . data

#
# Event h a n d l e r s
# Opens o r c l o s e s a b r a n c h
def open close ( s e l f , args ) :
i f s e l f . open :
s e l f . open = FALSE
else :
s e l f . open = TRUE
s e l f . build ()
def press ( s e l f , args ) :
g l o b a l DRAGGING
self . set selection ( self )
s e l f . o f f s e t = (3, args . y s e l f . loc [ 1 ] )
DRAGGING = TRUE
def enter ( s e l f , args ) :
g l o b a l OVER

199

CHAPTER 11. CUSTOM MEGAWIDGETS

200

OVER = s e l f
s e l f . t e x t [ fill ] = blue
def leave ( s e l f , args ) :
g l o b a l OVER
OVER = None
s e l f . t e x t [ fill ] = black
def launch ( s e l f , args ) :
i f s e l f . data :
i f launch i n s e l f . d a t a .
s e l f . data . launch ()

class

dict

. keys ( ) :

def i n s e r t ( s e l f ) :
pass
#
# Drawing methods
# B u i l d s t h e e n t i r e t r e e by t u n n e l i n gup t o t h e r o o t
def build ( s e l f ) :
for parent in s e l f . parent :
i f parent :
parent . build ()
# I f a node s p e c i f i c i c o n h a s n o t been s e t , u s e one o f t h e
# standard ones
def get icon ( s e l f ) :
i f s e l f . icon :
return s e l f . icon
else :
i f s e l f . children == []:
r e t u r n s e l f . LEAF
else :
i f s e l f . open :
r e t u r n s e l f . FOLDEROPEN
else :
r e t u r n s e l f . FOLDERCLOSED
# B u i l d s t h i s node a t l o c a t i o n ( x , y )
d e f b u i l d l a y e r ( s e l f , c a n v a s , s e l e c t e d , x , y , p r e v y=None ) :
i f not prevy :
prevy = y
# Draw t h e h o r i z o n t a l l i n e
Canvas . L i n e ( c a n v a s , x + LINE , y , x + ICON , y ,
# Draw t h e i c o n
icon = s e l f . get icon ()
s e l f . bitmap = Canvas . I m a g e I t e m (
c a n v a s , x + ICON , y , image=i c o n )

f i l l = gray50 )

CHAPTER 11. CUSTOM MEGAWIDGETS

201

# Draw t h e t e x t
s e l f . t e x t = Canvas . Canvas Text (
c a n v a s , x + TEXT , y , a n c h o r=w )
# Draw t h e s e l e c t i o n box , i f f t h i s node i s t h e s e l e c t e d node
i f s e l f == s e l e c t e d :
box = s e l f . t e x t . bbox ( )
s e l f . r e c t a n g l e = Canvas . R e c t a n g l e (
c a n v a s , box , f i l l =COLOR REF HIGHLIGHT ,
o u t l i n e=COLOR REF HIGHLIGHT )
s e l f . rectangle . lower ()
else :
s e l f . r e c t a n g l e = None
# Update t h e t e x t and t h e s i z e o f t h e s e l e c t i o n box
s e l f . update text ()
# Save t h e p h y s i c a l l o c a t i o n o f t h i s node s o t h a t e v e n t s can
# d e t e r m i n e wh i c h node was d r o p p e d on .
s e l f . loc = (x , y)
# A s s i g n e v e n t s t o t h e v i s u a l node i t e m s
f o r w i d g e t i n s e l f . bitm ap , s e l f . t e x t :
w i d g e t . b i n d ( s e q u e n c e="< ButtonPress >" ,
command= s e l f . p r e s s )
w i d g e t . b i n d ( s e q u e n c e="< Double - ButtonPress >" ,
command= s e l f . l a u n c h )
w i d g e t . b i n d ( s e q u e n c e="< Enter >" ,
command= s e l f . e n t e r )
w i d g e t . b i n d ( s e q u e n c e="< Leave >" ,
command= s e l f . l e a v e )
# Draw t h i s node s c h i l d r e n , i f t h e b r a n c h i s open
n e x t y = y + i c o n . h e i g h t ( ) + Y STEP
if self . children != []:
i f s e l f . open :
open = Canvas . I m a g e I t e m (
c a n v a s , x + OPEN BOX , y , image= s e l f .OPENBM)
py = y
for child in s e l f . children :
newy = c h i l d . b u i l d l a y e r ( c a n v a s , s e l e c t e d ,
x + X STEP , n e x t y , p r e v y=py )
py , n e x t y = n e x t y , newy
else :
open = Canvas . I m a g e I t e m (
c a n v a s , x + OPEN BOX , y , image= s e l f . CLOSEDBM)
open . t k r a i s e ( )
open . b i n d ( s e q u e n c e="< ButtonPress >" ,
command= s e l f . o p e n c l o s e )

CHAPTER 11. CUSTOM MEGAWIDGETS

202

# Draw t h e v e r t i c a l l i n e
Canvas . L i n e (
c a n v a s , x + LINE , y ,
x + LINE , p r e v y , f i l l = gray50 ) . l o w e r ( )
return nexty
# U p da t e s t h e t e x t t h a t i s d i s p l a y e d w i t h t h e node
# I f the r e p r ( data ) i s g r e a t e r than 3 0 c h a r a c t e r s , the t e x t
# i s truncated .
def update text ( s e l f ) :
i f s e l f . text :
x = remove cr ( repr ( s e l f ))
i f len (x ) > 40:
x = x [ 0 : 3 9 ] + "..."
s e l f . t e x t [ text ] = x
i f self . rectangle :
box = s e l f . t e x t . bbox ( )
s e l f . r e c t a n g l e . c o o r d s ( box )
s e l f . rectangle . lower ()
# Moves t h e node t o a new l o c a t i o n . Used when d r a g g i n g
d e f move ( s e l f , x , y ) :
s e l f . bitmap . t k r a i s e ( )
s e l f . text . t k r a i s e ()
s e l f . bitmap . c o o r d s ( [ ( x + ICON s e l f . o f f s e t [ 0 ] ,
y self . offset [1])])
s e l f . t e x t . c o o r d s ( [ ( x + TEXT s e l f . o f f s e t [ 0 ] ,
y self . offset [1])])
# R e s t o r e node t o i t s p r o p e r p o s i t i o n . Used when a d r a g g i n g
# operation is cancelled .
def restore pos ( s e l f ) :
s e l f . offset = (0, 0)
s e l f . move ( s e l f . l o c [ 0 ] , s e l f . l o c [ 1 ] )
# S e t s t h i s node a s t h e s e l e c t e d node
d e f s e t s e l e c t i o n ( s e l f , node ) :
for parent in s e l f . parent :
i f parent :
p a r e n t . s e t s e l e c t i o n ( node )
##################################################
# TESTING CODE
if

n a m e == " __main__ " :


import Rule
w = TreeWidget ( )
n = w. g e t t r e e ()

CHAPTER 11. CUSTOM MEGAWIDGETS

203

f o r x in xrange ( 1 , 2 0 ) :
r = R u l e . RuleNode ( p a r e n t=n )
n . append node ( r )
w. mainloop ()

11.2

Help Browser Widget

11.2.1

End user usage

Figure 11.3: The Help Browser displaying an example document.

The Help widget is a very basic on-line help browser (Figure 11.3) similar in
functionality to a web browser (eg. Mosaic, Netscape Communicator, Microsoft
Internet Explorer.)
Help documents are displayed using a mixture of typefaces. Links to other help
documents are displayed in a box. Clicking on this box opens the other document.
Clicking on the Back button returns to the previously viewed document.

11.2.2

Developer usage

This help browser implementation is quite low-featured, but is quite fast with a
small memory footprint. An alternative solution may have also been to provide a
platform-independant way of opening the help docuemnts in a standard web-browser
as a sub-process of the application.
The help browser manages a global database of help documents stored in memory
as strings. There can only be one database per instance of an application and each
document in the database must have a unique string identifier. Documents can be

CHAPTER 11. CUSTOM MEGAWIDGETS

204

added to the database (usually at application start-up) using the add function:
Help . add ( s u b j e c t , document )

where subject is the unique document identifier and document is a string containing
the body of the document.
Documents are displayed using the show function:
Help . show ( s u b j e c t )

where subject is a document identifier already in the database. Only one help
browser window will be created per application. Therefore, calling show twice will
change the subject of the already existing help browser, and not open a new help
browser all together.
The document strings themselves are formatted using a very tiny subset of
HTML. The supported tags are:
<b>
bold text
<i>
italic text
<bi>
bold and italic text
<tt>
monospaced text
<title>
title-sized text
<a href=subject> reference to another help document

11.2.3

Help.py

Code
# Help B r o w s e r F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# Help . py
#
#
T h i s module p r o v i d e s a v e r y b a s i c Help b r o w s e r .
# App s p e c i f i c i m p o r t s
from U t i l i m p o r t
from C o n s t a n t s i m p o r t
# Python i m p o r t s
import s g m l l i b
# Tkinter imports
import Tkinter
from T k C o n s t a n t s i m p o r t
# To s t o r e a r e f e r e n c e t o a h e l p window , i f any .
helpWindow = None
# Help On Help D e f a u l t page
documents = {
Default :
( "< title >Help Browser </ title >\n"
"<b>(c ) 1999 Michael Droettboom </b>\n"

CHAPTER 11. CUSTOM MEGAWIDGETS

205

"This is a very basic help browsing system that manages a "


" collection of internal document strings and displays them "
"with hyperlinks in a window .\n"
"The document strings are formatted using a very tiny subset "
"of HTML . The Supported tags are :\n\n"
""
"\t<b>b</b >: < b>bold </b> text\n"
"\t<b>i</b >: < i> italic </i> text\n"
"\t<b>bi </b >: < bi>bold and italic </ bi> text\n"
"\t<b>tt </b >: < tt> monospaced </tt > ( teletype ) text\n"
"\t<b>title </b >: < title >title -sized </ title > text\n"
"\t<b>a href =< bi> subject </ bi ></b>: < a href = Default Page 2>"
" reference </a> to another subject page\n" ) ,
Default Page 2 :
( "< title >Help Browser Page 2</ title >\n"
" Congratulations ! Youve made it to the next page .\n"
"<a href = Default > Click here </a> to go back .\n" ) }
########################################
# D i s p l a y s t h e g i v e n s u b j e c t i n a h e l p window .
# Only one h e l p window i s open f o r one a p p l i c a t i o n
# at a time .
d e f show ( s u b j e c t ) :
g l o b a l helpWindow
i f helpWindow == None :
helpWindow = Window ( s u b j e c t )
helpWindow . m a i n l o o p ( )
else :
helpWindow . s e t s u b j e c t ( s u b j e c t )
########################################
# Add a h e l p s u b j e c t t o t h e d a t a b a s e
d e f add ( s u b j e c t , document ) :
g l o b a l documents
documents [ s u b j e c t ] = document
##################################################
# Window
#
# The main window t o d i s p l a y h e l p
#
c l a s s Window ( T k i n t e r . T o p l e v e l ) :
def
i n i t ( s e l f , s u b j e c t=" Default " ) :
# C r e a t e t h e main Help window
Tkinter . Toplevel .
init (
s e l f , None ,
c l a s s =Help )
s e l f . make widgets ()
s e lf . set subject ( subject )

CHAPTER 11. CUSTOM MEGAWIDGETS

def

206

make widgets ( s e l f ) :
# A b a s e d t e x t and v e r t i c a l s c r o l l b a r c o m b i n a t i o n
s e l f . s c r o l l = Tkinter . Scrollbar (
s e l f , o r i e n t= vertical )
s e l f . t e x t = TkinterHTML ( s e l f , s e l f . s c r o l l )
s e l f . s c r o l l . c o n f i g u r e ( command= s e l f . t e x t . y v i e w )
# A t o o l b a r , c o n t a i n i n g o n l y t h e b u t t o n Back
f r a m e = T k i n t e r . Frame ( s e l f )
s e l f . back = T k i n t e r . Button (
f r a m e , t e x t="Back" ,
command= s e l f . t e x t . back )
s e l f . back . pack ( s i d e=left )
f r a m e . pack ( s i d e=top , expand=yes , f i l l =x )
s e l f . s c r o l l . pack ( s i d e=right , f i l l =y )
s e l f . t e x t . pack ( expand=yes , f i l l =both )

def s e t s u b j e c t ( s e l f , subject ) :
s e l f . t e x t . d i s p l a y t e x t ( s u b j e c t , documents [ s u b j e c t ] )
def s e t t i t l e ( s e l f , subject ) :
s e l f . t i t l e (APP NAME + " Help on " + s u b j e c t )
##################################################
# TkinterHTML
#
# D i s p l a y s f o r m a t t e d t e x t u s i n g a t i n y s u b s e t o f HTML
# The s u p p o r t e d t a g s a r e :
#
<b > : b o l d t e x t
#
<i > : i t a l i c t e x t
#
<b i > : b o l d and i t a l i c t e x t
#
<t t > : monospaced ( t y p e w r i t e r ) t e x t
#
< t i t l e > : t i t l e s i z e d t e x t
#
<a h r e f= s u b j e c t > : r e f e r e n c e t o a n o t h e r s u b j e c t page
#
c l a s s TkinterHTML ( T k i n t e r . Text , s g m l l i b . SGMLParser , C u r s o r M a n a g e r ) :
i n i t ( s e l f , parent , s c r o l l ) :
def
T k i n t e r . Text .
init (
s e l f , parent ,
y s c r o l l c o m m a n d= s c r o l l . s e t ,
s e t g r i d=TRUE ,
w i d t h = 7 0 , h e i g h t = 3 2 , wrap=word )
CursorManager .
init ( self )
s e l f . parent = parent
# Store s a h i s t o r y of v i s i t e d pages .
# Makes t h e Back b u t t o n p o s s i b l e
s e lf . historyStack = []

CHAPTER 11. CUSTOM MEGAWIDGETS

207

s e l f . reset ()
s e l f . s e t d i s p l a y t a g s ()
# S e t s up t a g s i n Tk w i t h v a r i o u s f o r m a t t i n g p r o p e r t i e s
def
set display tags ( self ):
s e l f . t a g c o n f i g ( default , f o n t=" Helvetica 10" ,
s p a c i n g 1 = 3 , l m a r g i n 1 = 2 , l m a r g i n 2 =2,
r m a r g i n =2)
s e l f . t a g c o n f i g ( title , f o n t=" Helvetica 16 bold" )
s e l f . t a g c o n f i g ( b , f o n t=" Helvetica 10 bold" )
s e l f . t a g c o n f i g ( i , f o n t=" Helvetica 10 italic " )
s e l f . t a g c o n f i g ( bi , f o n t=" Helvetica 10 bold italic " )
s e l f . t a g c o n f i g ( tt , f o n t=" Courier 10" , l m a r g i n 2 =2)
s e l f . t a g c o n f i g ( a , f o n t=" Helvetica 10 bold" ,
b a c k g r o u n d=COLOR REFERENCE)
s e l f . k n o w n t a g s = [ default , title , b , i , bi , tt , a ]
def

add tag ( s e l f , tag ) :


s e l f . t a g S t a c k . append ( t a g )

def

remove tag ( s e l f , tag ) :


s e l f . t a g S t a c k . remove ( t a g )

# F u n n e l s d a t a from t h e SGMLParser t o t h e T k i n t e r Text box


def handle data ( s e l f , data ) :
s e l f . i n s e r t ( end , d a t a , t u p l e ( s e l f . t a g S t a c k ) )
# C a l l e d by t h e SGMLParser when i t e n c o u n t e r s a s t a r t t a g ( eg . < b>)
def unknown starttag ( s e l f , tag , a t t r s ) :
# I f i t s one o f t h e t a g s we s u p p o r t , p u t i t on t h e t a g s t a c k
i f tag i n s e l f . known tags :
s e l f . add tag ( tag )
# I f t h i s i s a r e f e r e n c e , we have some work t o do
i f t a g == "a" :
for x in attrs :
i f x [ 0 ] = = href :
s e l f . make reference tag (x [1])
def

make reference tag ( s e l f , ref ):


# Make a new t a g w i t h t h e same name a s t h e
# s u b j e c t being r e f e r e n c e d to
s e l f . add tag ( ref )
self . reference = ref
# The g e n e r i c b i n d i n g s t o b i n d t h i s t a g t o
make = [ ( set_subject , < ButtonPress > ) ,
( highlight_ref , < Enter > ) ,
( unhighlight_ref , < Leave > ) ]
# Make t h e b i n d i n g s s p e c i f i c t o t h i s p a r t i c u l a r

CHAPTER 11. CUSTOM MEGAWIDGETS

208

# r e f e r e n c e by c r e a t i n g lambda f u n c t i o n s t h a t w i l l
# c a l l the handler f u n c t i o n s
f o r b i n d i n g i n make :
s e l f . tag bind (
ref , binding [ 1 ] ,
e v a l ( lambda attr : attr. widget . +
b i n d i n g [ 0 ] + (" + r e f + ") ) )
break
# C a l l e d by SGMLParser when i t e n c o u n t e r s an end t a g ( eg . < / b>)
d e f unknown endtag ( s e l f , t a g ) :
i f tag i n s e l f . known tags :
s e l f . remove tag ( tag )
i f t a g == "a" :
s e l f . remove tag ( s e l f . reference )

def s e t s u b j e c t ( s e l f , subject ) :
s e l f . parent . s e t s u b j e c t ( subject )
# E x t e r n a l : Loads t e x t i n t o t h e w i d g e t
def d i s p l a y t e x t ( s e l f , subject , text ) :
# Add t h e new s u b j e c t t o t h e h i s t o r y s t a c k
s e lf . historyStack = [ subject ] + s e l f . historyStack
# c l e a r t h e t a g s t a c k ( we want t o d e f a u l t t o t h e
# default text style )
s e l f . t a g S t a c k = [ default ]
# Change t h e t i t l e o f t h e p a r e n t window t o r e f l e c t
# t h e new s u b j e c t
i f s e l f . parent . s e t t i t l e :
s e l f . parent . s e t t i t l e ( subject )
# Allow changes to the textbox
s e l f . c o n f i g u r e ( s t a t e= normal )
# Delete a l l t e xt in the textbox
s e l f . d e l e t e ( 1.0 , end )
# P a r s e t h e HTML
s e l f . feed ( text )
s e l f . close ()
# Disallow a l l changes to the textbox
s e l f . c o n f i g u r e ( s t a t e= disabled )
# Change t h e s t a t e o f t h e back b u t t o n t o r e f l e c t
# the s i z e of the h i s t o r y stack
s e l f . update back button ()

CHAPTER 11. CUSTOM MEGAWIDGETS

209

# Go back t o t h e p r e v i o u s s u b j e c t i n t h e h i s t o r y s t a c k
d e f back ( s e l f ) :
i f len ( s e l f . historyStack ) > 1:
newSubject = s e l f . h i s t o r y S t a c k [ 1 ]
s e lf . historyStack = s e lf . historyStack [ 2 : ]
s e l f . s e t s u b j e c t ( newSubject )
s e l f . update back button
# Change t h e s t a t e o f t h e back b u t t o n b a s e d on t h e e m p t i n e s s
# of the h i s t o r y stack
def
update back button ( s e l f ):
i f len ( s e l f . historyStack ) > 1:
s e l f . p a r e n t . back . c o n f i g u r e ( s t a t e= normal )
else :
s e l f . p a r e n t . back . c o n f i g u r e ( s t a t e= disabled )
# H i g h l i g h t t h e r e f e r e n c e when t h e mouse c u r s o r e n t e r s
def h i g h l i g h t r e f ( s e l f , tag ) :
s e l f . s e t c u r s o r ( hand2 )
s e l f . t a g c o n f i g ( t a g , b a c k g r o u n d=COLOR REF HIGHLIGHT )
# U n h i g h l i g h t t h e r e f e r e n c e when t h e mouse c u r s o r l e a v e s
def u n h i g h l i g h t r e f ( s e l f , tag ) :
s e l f . restore cursor ()
s e l f . t a g c o n f i g ( t a g , b a c k g r o u n d=COLOR REFERENCE)
############################################################
# SELF TEST : D i s p l a y t h e d e f a u l t Help s u b j e c t .
if
n a m e == " __main__ " :
show ( " Default " )

11.3

On-screen musical keyboard Widget

11.3.1

End user usage

Figure 11.4: Keyboard widget showing SINGLE selection

Figure 11.5: Keyboard widget showing MULTI selection

CHAPTER 11. CUSTOM MEGAWIDGETS

210

Figure 11.6: Keyboard widget showing RANGE selection

Figure 11.7: Keyboard widget in disabled mode

The on-screen musical keyboard widget (Figures 11.4 through 11.8) allows the
user to enter pitch-related information in a manner that is intuitive to most users
with a background in Western art music.
The user is presented with a standard musical keyboard. As you slide the mouse
cursor over the keyboard, the pitch name of the key currently being pointed at is
displayed on the left-hand side of the keyboard. The scale of the keyboard can be
increased or decreased using the zoom-in ( ) and zoom-out ( ) buttons (Figure
11.8).
By clicking or dragging on the keyboard, the user can select pitches. How pitches
are selected is determined by the current mode of the keyboard:
SINGLE: (Figure 11.4) Clicking on any key selects that key. Only one key can be
selected at a time.
MULTI: (Figure 11.5) Click a key to select it. Click a selected key to unselect it.
An arbitrary number of keys may be selected at any given time.
RANGE: (Figure 11.6) Click on a starting key and drag to an ending key to select
a contiguous range of keys. Only one range may be selected at any time.

11.3.2

Developer usage

The Keyboard class inherits from the Tkinter Frame class and can be used as such.
To create a new Keyboard instance, use the default constructor:
kb = M u s i c W i d g e t s . Keyboard ( p a r e n t , mode )

Figure 11.8: Keyboard widget zoomed in

CHAPTER 11. CUSTOM MEGAWIDGETS

211

where parent is the widget that owns the keyboard widget and mode is one of the
constants KB SINGLE, KB MULTI or KB RANGE corresponding to one of the input
modes described above.
To retrieve information from the Keyboard widget, assign a callback function
using:
kb . s e t c a l l b a c k ( f u n c t i o n )

This callback function is called everytime there is a change made to the set of
selected keys. This function must have one argument. Depending on the mode, the
argument will contain:
KB SINGLE: The pitch name of the selected key as a string.
KB MULTI: A list of the pitch names of all the selected keys, each given as a string.
KB RANGE: A list of two pitches. The first element is the highest pitch in the
range, the second element is the lowest pitch in the range.
The widget as a whole can be enabled and disabled (Figure 11.7) like any other
Tkinter widget using:
kb [ state ] = disabled
bk [ state ] = normal

11.3.3

Future plans

The on-screen musical keyboard can be used as the basis for a number of generalpurpose widgets for musical applications. I plan to develop a MIDI library for
Python (see Introduction on page ii) which will allow the Keyboard widget to include:
Selected notes and ranges using an attached MIDI keyboard as well as with
the mouse on-screen.
Visual playback of MIDI sequences by flashing the keys on the on-screen keyboard.

11.3.4

MusicWidgets.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# M u s i c W i d g e t s . py
#
#
A c o l l e c t i o n o f T k i n t e r MegaWidgets r e l a t e d t o m u s i c
# V i s u a l Pelog imports
from U t i l i m p o r t
from C o n s t a n t s i m p o r t

CHAPTER 11. CUSTOM MEGAWIDGETS

212

# Tkinter imports
from T k C o n s t a n t s i m p o r t
import Tkinter
i m p o r t Canvas
# Constants
WKEY WIDTH = 1 0 # w i d t h o f w h i t e k e y s
WKEY HEIGHT = 4 5 # h e i g h t o f w h i t e k e y s
BKEY WIDTH = 6 # w i d t h o f b l a c k k e y s
BKEY HEIGHT = 3 0 # h e i g h t o f b l a c k k e y s
BKEY HALF = BKEY WIDTH / 2 # h a l f o f t h e w i d t h o f b l a c k k e y s
ZOOM FACTOR = 1 . 2 5 # The f a c t o r t o zoom/unzoom by
############################################################
# Keyboard Widget
#
#
The k e y b o a r d w i d g e t c r e a t e s an ons c r e e n m u s i c a l
#
k e y b o a r d . N ot e s on t h e k e y b o a r d can be s e l e c t e d i n
#
one o f t h e f o l l o w i n g modes :
#
KB SINGLE : One n o t e a t a t i m e , by c l i c k i n g on t h e
#
key
#
KB MULTI : M u l t i p l e n o t e s , by t o g g l i n g k e y s on / o f f
#
KB RANGE : A c o n t i g u o u s r a n g e o f n o t e s by c l i c k i n g
#
and d r a g g i n g on t h e k e y b o a r d .
#
ZOOMIN DATA = "" "# define solid_width 13
#d e f i n e s o l i d h e i g h t 1 3
s t a t i c char s o l i d b i t s [] = {
0 x30 , 0 x00 , 0 x c c , 0 x00 , 0 x32 , 0 x01 , 0 x32 , 0 x01 , 0 x f d , 0 x02 , 0 x f d , 0 x02 ,
0 x32 , 0 x01 , 0 x32 , 0 x01 , 0 x c c , 0 x02 , 0 x30 , 0 x05 , 0 x00 , 0 x0a , 0 x00 , 0 x14 ,
0 x00 , 0 x08 } ;
"""
ZOOMOUT DATA = "" "# define solid_width 13
#d e f i n e s o l i d h e i g h t 1 3
s t a t i c char s o l i d b i t s [] = {
0 x30 , 0 x00 , 0 x c c , 0 x00 , 0 x02 , 0 x01 , 0 x02 , 0 x01 , 0 x79 , 0 x02 , 0 x79 , 0 x02 ,
0 x02 , 0 x01 , 0 x02 , 0 x01 , 0 x c c , 0 x02 , 0 x30 , 0 x05 , 0 x00 , 0 x0a , 0 x00 , 0 x14 ,
0 x00 , 0 x08 } ;
"""
c l a s s Keyboard ( T k i n t e r . Frame ) :
#
# I n i t i a l i z a t i o n routines
def
i n i t ( s e l f , p a r e n t=None , mode=KB SINGLE ) :
s e l f . mode = mode
T k i n t e r . Frame .
init (
s e l f , p a r e n t , h e i g h t =50,
r e l i e f = sunken )

CHAPTER 11. CUSTOM MEGAWIDGETS


s e l f . make widgets ()
s e l f . s t a t e a l l ( disabled )
def

make widgets ( s e l f ) :
# C r e a t e t h e two s t a t u s l a b e l s on t h e l e f t hand s i d e
f r a m e 1 = T k i n t e r . Frame ( s e l f )
s e l f . mousestatus = Tkinter . Label (
m a s t e r=f r a m e 1 , r e l i e f = sunken ,
w i d t h =4)
s e l f . widgetstatus = Tkinter . Label (
m a s t e r=f r a m e 1 , r e l i e f = sunken )
s e l f . m o u s e s t a t u s . g r i d ( row = 0 , column = 0 , s t i c k y=nesw )
s e l f . w i d g e t s t a t u s . g r i d ( row = 1 , column = 0 , s t i c k y=nesw )
# C r e a t e t h e zoomi n / zoomo u t b u t t o n s
f r a m e 2 = T k i n t e r . Frame ( f r a m e 1 )
s e l f . zoomout image = T k i n t e r . Image (
bitmap , d a t a=ZOOMOUT DATA , maskdata=ZOOMOUT DATA,
f o r e g r o u n d=black , b a c k g r o u n d= white )
s e l f . zoomout = T k i n t e r . Button (
f r a m e 2 , command= s e l f . zoom out ,
image= s e l f . zoomout image )
s e l f . z o o m i n i m a g e = T k i n t e r . Image (
bitmap , d a t a=ZOOMIN DATA , maskdata=ZOOMIN DATA ,
f o r e g r o u n d=black , b a c k g r o u n d= white )
s e l f . zoomin = T k i n t e r . Button (
f r a m e 2 , command= s e l f . z o o m i n ,
image= s e l f . z o o m i n i m a g e )
s e l f . zoomout . g r i d ( row = 0 , column = 0 , s t i c k y=nesw )
s e l f . zoomin . g r i d ( row = 0 , column = 1 , s t i c k y=nesw )
f r a m e 2 . g r i d ( row = 2 , column = 0 , s t i c k=nesw )
f r a m e 1 . g r i d r o w c o n f i g u r e ( 0 , w e i g h t =1)
f r a m e 1 . g r i d r o w c o n f i g u r e ( 1 , w e i g h t =1)
# C r e a t e t h e k e y b o a r d c a n v a s i t s e l f and t h e s c r o l l b a r
f r a m e 3 = T k i n t e r . Frame ( s e l f )
s e l f . c a n v a s = Ke yboardCanvas (
f r a m e 3 , mode= s e l f . mode ,
m o u s e s t a t u s= s e l f . m o u s e s t a t u s ,
w i d g e t s t a t u s= s e l f . w i d g e t s t a t u s )
s e l f . h s c r o l l = Tkinter . Scrollbar (
f r a m e 3 , o r i e n t= horizontal ,
command= s e l f . c a n v a s . x v i e w )
s e l f . c a n v a s [ xscrollcommand ] = s e l f . s c r o l l X
s e l f . h s c r o l l . g r i d ( row = 1 , column = 0 , s t i c k y=ew )
s e l f . c a n v a s . g r i d ( row = 0 , column = 0 , s t i c k y=ew )
f r a m e 3 . g r i d c o l u m n c o n f i g u r e ( 0 , w e i g h t =1)
f r a m e 1 . g r i d ( row = 0 , column = 0 , s t i c k y=ns )

213

CHAPTER 11. CUSTOM MEGAWIDGETS


f r a m e 3 . g r i d ( row = 0 , column = 1 , s t i c k y=ew )
s e l f . g r i d c o l u m n c o n f i g u r e ( 1 , w e i g h t =1)
#
# Event h a n d l e r s
def s c r o l l X ( s e l f , f i r s t , l a s t ) :
s e l f . hscroll . set ( f i r s t , last )
def zoom in ( s e l f ) :
s e l f . c a n v a s . s c a l e (ZOOM FACTOR)
d e f zoom out ( s e l f ) :
s e l f . c a n v a s . s c a l e ( 1 / ZOOM FACTOR)
#
# Settings
def
s e t i t e m ( s e l f , key , v a l u e ) :
i f k e y == state :
s e l f . s t a t e a l l ( value )
else :
T k i n t e r . Frame . s e t i t e m ( s e l f , k e y , v a l u e )
def s t a t e a l l ( s e l f , value ) :
s e l f . zoomin [ state ] = v a l u e
s e l f . zoomout [ state ] = v a l u e
s e l f . c a n v a s [ state ] = v a l u e
def s e t c a l l b a c k ( s e l f , func ) :
s e l f . canvas . c a l l b a c k = func
d e f s e t m o d e ( s e l f , mode ) :
s e l f . c a n v a s . s e t m o d e ( mode )
# Keyboard Canvas h e l p e r c l a s s
c l a s s KeyboardCanvas ( T k i n t e r . Canvas ) :
#
# I n i t i a l i z a t i o n routines
def
i n i t ( s e l f , p a r e n t=None , mode=KB SINGLE ,
m o u s e s t a t u s=None , w i d g e t s t a t u s=None ) :
s e l f . mouse status = mouse status
s e l f . widget status = widget status
s e l f . x w i d t h = WKEY WIDTH 4 9 + 1
T k i n t e r . Canvas .
init (
s e l f , parent ,
w i d t h=WKEY WIDTH 4 9 ,
h e i g h t=WKEY HEIGHT ,
s c r o l l r e g i o n = ( 0 , 0 , s e l f . x w i d t h , WKEY HEIGHT ) )
s e l f [ cursor ] = hand1
s e l f . make keys ( )
s e l f . s e t m o d e ( mode )

214

CHAPTER 11. CUSTOM MEGAWIDGETS

215

def make keys ( s e l f ) :


k e y s = [ c , c# , d , d# , e , f , f# , g , g# , a , a# , b ]
location = 0
self . keyList = []
f o r octave in xrange (3, 4):
f o r i v o r y in keys :
i f l e n ( i v o r y ) == 2:
k = BlackKey ( s e l f ,
l o c a t i o n WKEY WIDTH BKEY HALF ,
i v o r y + "~" + r e p r ( o c t a v e ) )
e l i f l e n ( i v o r y ) == 1:
k = WhiteKey ( s e l f ,
l o c a t i o n WKEY WIDTH ,
i v o r y + "~" + r e p r ( o c t a v e ) )
location = location + 1
s e l f . k e y L i s t . append ( k )
def scale ( s e l f , factor ) :
T k i n t e r . Canvas . s c a l e ( s e l f , all , 0 , 0 , f a c t o r , 1 )
s e l f . xwidth = s e l f . xwidth f a c t o r
s e l f [ scrollregion ] = ( 0 , 0 , s e l f . x w i d t h , WKEY HEIGHT)
#
# Event h a n d l e r s
d e f c l i c k e d o n ( s e l f , key ) :
i f s e l f . mode == KB SINGLE :
if self . selected != []:
s e l f . s e l e c t e d [ 0 ] . unmark ( )
s e l f . s e l e c t e d = [ key ]
k e y . mark ( )
e l i f s e l f . mode == KB MULTI :
i f k e y . marked :
s e l f . s e l e c t e d . remove ( k e y )
k e y . unmark ( )
else :
s e l f . s e l e c t e d . append ( k e y )
k e y . mark ( )
e l i f s e l f . mode == KB RANGE :
s e l f . top = key
s e l f . bottom = k e y
for k in s e l f . keyList :
k . unmark ( )
k e y . mark ( )
s e l f . d r a g g i n g = TRUE
d e f motion on ( s e l f , key ) :
i f k e y ! = None and s e l f . m o u s e s t a t u s ! = None :
s e l f . m o u s e s t a t u s [ text ] = k e y . name
i f s e l f . mode == KB RANGE and s e l f . d r a g g i n g and k e y ! = None :
s e l f . bottom = k e y
k e y . mark ( )

CHAPTER 11. CUSTOM MEGAWIDGETS


mark = FALSE
for k in s e l f . keyList :
i f mark :
k . mark ( )
else :
k . unmark ( )
i f k == s e l f . t o p :
mark = n o t mark
k . mark ( )
i f k == s e l f . bottom :
mark = n o t mark
k . mark ( )
d e f r e l e a s e d o n ( s e l f , key ) :
s e l f . bottom = k e y
s e l f . d r a g g i n g = FALSE
i f self . callback :
i f s e l f . mode == KB RANGE :
s e l f . c a l l b a c k ( [ s e l f . t o p , s e l f . bottom ] )
else :
self . callback ( self . selected )
def find key ( s e l f , x , y ) :
x = s e l f . canvasx ( x )
z = self . find overlapping (x , y , x , y)
if z != ():
return s e l f . items [ z [1]]
else :
r e t u r n None
#
# Settings
d e f s e t m o d e ( s e l f , mode ) :
s e l f . mode = mode
self . selected = []
i f mode == KB RANGE :
s e l f . t o p = None
s e l f . bottom = None
s e l f . d r a g g i n g = FALSE
for k in s e l f . keyList :
k . unmark ( )
def

s e t i t e m ( s e l f , key , v a l u e ) :
i f k e y == state :
for k in s e l f . keyList :
k . unmark ( )
k [ state ] = v a l u e
else :
T k i n t e r . Canvas . s e t i t e m ( s e l f , k e y , v a l u e )

# Key h e l p e r c l a s s

216

CHAPTER 11. CUSTOM MEGAWIDGETS

217

c l a s s Key ( Canvas . R e c t a n g l e ) :
#
# I n i t i a l i z a t i o n routines
def
i n i t ( s e l f , p a r e n t , x , name ) :
s e l f . parent = parent
s e l f . en = TRUE
s e l f . build item ( parent , x )
s e l f . b i n d ( s e q u e n c e=< Enter > , command= s e l f . e n t e r )
s e l f . b i n d ( s e q u e n c e=< Leave > , command= s e l f . l e a v e )
s e l f . b i n d ( s e q u e n c e=< ButtonPress > , command= s e l f . c l i c k )
s e l f . b i n d ( s e q u e n c e=< ButtonRelease > , command= s e l f . r e l e a s e )
s e l f . b i n d ( s e q u e n c e=< Motion > , command= s e l f . motion )
s e l f . marked = FALSE
s e l f . name = name
#
# Event h a n d l e r s
def enter ( s e l f , args ) :
i f s e l f . en :
s e l f [ fill ] = COLOR REFERENCE
def leave ( s e l f , args ) :
i f s e l f . en :
i f s e l f . marked :
s e l f [ fill ] = COLOR REF HIGHLIGHT
else :
s e l f [ fill ] = white
def c l i c k ( s e l f , args ) :
i f s e l f . en :
s e l f . parent . clicked on ( s e l f )
def r e l e a s e ( s e l f , args ) :
i f s e l f . en :
s e l f . parent . released on ( s e l f . parent . find key ( args . x , args . y ))
d e f motion ( s e l f , a r g s ) :
i f s e l f . en :
s e l f . parent . motion on ( s e l f . parent . f i n d k e y ( args . x , args . y ))
#
# Settings
def s e t s t a t e ( s e l f , value ) :
i f v a l u e == normal :
i f s e l f . name == "c~0" :
s e l f [ stipple ] = gray25
else :
s e l f [ stipple ] =
s e l f . en = TRUE
e l i f v a l u e == disabled :
s e l f [ stipple ] = error

CHAPTER 11. CUSTOM MEGAWIDGETS

218

s e l f . en = FALSE
def

repr ( self ):
r e t u r n s e l f . name

def

s e t i t e m ( s e l f , key , v a l u e ) :
i f k e y == state :
s e l f . set state ( value )
else :
Canvas . R e c t a n g l e . s e t i t e m

( s e l f , key , v a l u e )

d e f mark ( s e l f ) :
s e l f . marked = TRUE
s e l f . leave ( [ ] )
d e f unmark ( s e l f ) :
s e l f . marked = FALSE
s e l f . leave ( [ ] )
# WhiteKey h e l p e r c l a s s
c l a s s WhiteKey ( Key ) :
def build item ( s e l f , parent , x ) :
Canvas . R e c t a n g l e .
init (
s e l f , p a r e n t , x , 0 , x + WKEY WIDTH , WKEY HEIGHT 1 )
s e l f [ fill ] = white
s e l f [ outline ] = black
s e l f . lower ()
# BlackKey h e l p c l a s s
c l a s s BlackKey ( Key ) :
def build item ( s e l f , parent , x ) :
Canvas . R e c t a n g l e .
init (
s e l f , p a r e n t , x , 0 , x + BKEY WIDTH , BKEY HEIGHT )
s e l f [ fill ] = black
s e l f [ outline ] = black
s e l f . tkraise ()
def leave ( s e l f , args ) :
i f s e l f . marked :
s e l f [ fill ] = COLOR REF HIGHLIGHT
else :
s e l f [ fill ] = black

##################################################
# TESTING ROUTINES
c l a s s Test ( T k i n t e r . T o p l e v e l ) :
def
init ( self ):
Tkinter . Toplevel .
init (
s e l f , None )
s e l f . make widgets ()

CHAPTER 11. CUSTOM MEGAWIDGETS

219

def make widgets ( s e l f ) :


s e l f . k e y b o a r d = Keyboard ( s e l f , mode=KB MULTI )
s e l f . k e y b o a r d . pack ( expand=true , a n c h o r=NW, f i l l =both )
s e l f . k e y b o a r d [ state ] = normal
if

n a m e == " __main__ " :


t = Test ( )
t . mainloop ()

12

Implementation of other
components
12.1

Tool palette

The tool palette is implemented as two interacting classes. Palette organizes all the
tools into a window and coordinates the behaviour of all the tools. All tools in the
palette derive from the Tool class. The tool defines its appearance in the palette
window as well as the behaviours performed when new constraints are created etc.
The tools in the palette can be expanded by adding more source files to the plugin directory. These files are automatically imported by the Palette class and added
to the tool palette. In this way, it is possible to expand the system without editing
the system itself. The example plug-in shown below, IntervalTool, implements the
interval constraint.

12.1.1

Palette.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# P a l e t t e . py
#
#
The Tool P a l e t t e Window
from U t i l i m p o r t
i m p o r t T o o l s , Help
from T k C o n s t a n t s i m p o r t
import Tkinter
import sys

220

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

221

QUESTION DATA = "" "# define E__Pelog_Visual_images_q_width 10


#d e f i n e E P e l o g V i s u a l i m a g e s q h e i g h t 1 0
s t a t i c char E P e l o g V i s u a l i m a g e s q b i t s [] = {
0 x78 , 0 x00 , 0 x f c , 0 x00 , 0 x c c , 0 x00 , 0 xc0 , 0 x00 , 0 x60 , 0 x00 , 0 x30 , 0 x00 ,
0 x30 , 0 x00 , 0 x00 , 0 x00 , 0 x30 , 0 x00 , 0 x30 , 0 x00 } ; """
##################################################
# PaletteWindow
#
c l a s s PaletteWindow ( T k i n t e r . T o p l e v e l ) :
standard = [ Tools . S e l e c t , Tools . Equals , Tools . Greater ,
T o o l s . Or , T o o l s . Not ]
i f s y s . p l a t f o r m == win32 :
c o lu m ns = 2
else :
c o lu m ns = 1
def

i n i t ( s e l f , p a r e n t=None ) :
Tkinter . Toplevel .
init (
s e l f , parent ,
c l a s s = Palette )
s e l f . t i t l e ( Tools )
s e l f . w m r e s i z a b l e ( w i d t h = 0 , h e i g h t =0)
s e l f . r e a d y = FALSE
s e l f . make widgets ()
s e l f . r e a d y = TRUE

def make widgets ( s e l f ) :


s e l f . cur column = 0
s e l f . cur row = 0
# C r e a t e a l i s t o f t h e t o o l s t h a t w i l l be
# added
plugins = s e l f . get plugins ()
tools = s e l f . standard + plugins
# Add t h e t o o l s t o t h e f r a m e
f r a m e 1 = T k i n t e r . Frame ( m a s t e r= s e l f )
for tool in tools :
s e l f . a d d t o o l ( frame1 , t o o l )
f r a m e 1 . pack ( f i l l =BOTH , expand = 1 , a n c h o r=N)
# C r e a t e t h e s t a t u s b a r and h e l p b u t t o n
f r a m e 2 = T k i n t e r . Frame ( m a s t e r= s e l f )
f r a m e 2 . g r i d c o l u m n c o n f i g u r e ( 1 , w e i g h t =1)
f r a m e 2 . pack ( f i l l =X , expand = 1 , a n c h o r=S )
s e l f . q u e s t i o n i m a g e = T k i n t e r . Image (
bitmap , d a t a=QUESTION DATA , maskdata=QUESTION DATA ,
f o r e g r o u n d=black , b a c k g r o u n d= white )

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

222

s e l f . h e l p b u t t o n = T k i n t e r . Button (
m a s t e r=f r a m e 2 , image= s e l f . q u e s t i o n i m a g e ,
command= s e l f . h e l p o n t o o l )
s e l f . h e l p b u t t o n . g r i d ( row = 0 , column = 0 , s t i c k y= )
s e l f . l a b e l = Tkinter . Label (
m a s t e r=f r a m e 2 , r e l i e f = sunken )
s e l f . l a b e l . g r i d ( row = 0 , column = 1 , s t i c k y=we )
s e l f . c u r t o o l = None
# I m p o r t s a l l o f t h e p l u g g a b l e Tool m o dul e s
# A l l nonb u i l ti n t o o l s i n t h e p a l e t t e a r e s t o r e d i n
# t h e i r own module i n t h e p l u gi n d i r e c t o r y .
p l u g i n d i r = "plug-ins"
def get plugins ( s e l f ) :
import glob
import os . path
i m p o r t imp
# Get a d i r e c t o r y l i s t i n g o f t h e p l u gi n m o d u l e s
p l u g i n s = g l o b . g l o b ( s e l f . p l u g i n d i r+/*. py )
result = []
# For e a c h , i m p o r t t h e module , and add t h e name o f
# i t s c orresponding t o o l c l a s s to the r e s u l t l i s t
for plugin in plugins :
head , pluginName = o s . p a t h . s p l i t ( p l u g i n )
pluginName = s e l f . p l u g i n d i r + "/" + pluginName [ 0 : 3 ]
d e b u g p r i n t ( " Loading plug-in :" , pluginName )
a , b , c = imp . f i n d m o d u l e ( pluginName )
module = imp . l o a d m o d u l e ( pluginName , a , b , c )
r e s u l t . append ( module . Tool )
return result
# Adds a t o o l t o t h e p a l e t t e
def a d d t o o l ( s e l f , frame , t o o l ) :
newtool = t o o l ( s e l f , frame )
newbutton = n e w t o o l . c r e a t e b u t t o n ( )
newbutton . g r i d ( row= s e l f . c u r r o w , column= s e l f . c u r c o l u m n , s t i c k y=nesw )
s e l f . cur column = s e l f . cur column + 1
i f s e l f . cur column > s e l f . columns :
s e l f . cur column = 0
s e l f . cur row = s e l f . cur row + 1
# C a l l e d when a new t o o l i s s e l e c t e d
def change tool ( s e l f , new tool ) :
i f s e l f . c u r t o o l ! = None :
s e l f . cur tool . deactivate ()

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS


s e l f . cur tool = new tool
new tool . activate ()
def describe ( s e l f , s t r i n g ) :
s e l f . l a b e l [ text ] = s t r i n g
def undescribe ( s e l f ) :
i f s e l f . c u r t o o l ! = None :
self . cur tool . describe ([])
else :
s e l f . l a b e l [ text ] =
def help on tool ( s e l f ) :
i f s e l f . c u r t o o l ! = None :
s e l f . cur tool . help on tool ()
# Caused by c l i c k o r d r a g on t h e R u l e E d i t o r
# Most o f t e n , t h i s w i l l c r e a t e a new node i n t h e e d i t o r
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
i f s e l f . c u r t o o l ! = None :
self . cur tool . run tool ( ruleEditor , start , finish )
def tool keypress ( s e l f , args ) :
i f s e l f . c u r t o o l ! = None :
s e l f . c u r t o o l . k e y p r e s s ( a r g s . keysym )
p a l e t t e = PaletteWindow ( )

12.1.2

Tools.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# T o o l s . py
#
#
The g e n e r i c c l a s s from w hi c h a l l t o o l s a r e d e r i v e d
from U t i l i m p o r t
i m p o r t Nodes
import Tkinter
i m p o r t Help
##################################################
# Tool
#
#
Base c l a s s o f a l l t o o l s
c l a s s Tool :
b u t t o n b i t m a p=" default .xbm"
t o o l n a m e=" Default Tool"

223

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS


def

i n i t ( s e l f , p a le t te , buttons ) :
self . palette = palette
s e l f . buttons = buttons

def

repr ( self ):
return s e l f . tool name

224

def create button ( s e l f ) :


s e l f . button = ToolButton ( s e l f , s e l f . buttons , s e l f . button bitmap )
r e t u rn s e l f . button
def choose ( s e l f ) :
s e l f . palette . change tool ( s e l f )
def describe ( s e l f , args ) :
self . palette . describe ( self )
def undescribe ( s e l f , args ) :
s e l f . palette . undescribe ()
def activate ( s e l f ) :
s e l f . b u t t o n . c o n f i g u r e ( r e l i e f = sunken )
s e l f . s e l e c t e d = None
s e l f . i n i t i a l i z e t o o l ()
def deactivate ( s e l f ) :
s e l f . b u t t o n . c o n f i g u r e ( r e l i e f = raised )
if self . selected :
s e l f . selected . unselect ()
s e l f . s e l e c t e d = None
s e l f . c l e a r t o o l ()
def i n i t i a l i z e t o o l ( s e l f ) :
pass
def c l e a r t o o l ( s e l f ) :
pass
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
d e b u g p r i n t ( "Drawn from" , s t a r t , "to" , f i n i s h )
def help on tool ( s e l f ) :
Help . show ( s e l f . t o o l n a m e + " Tool" )
d e f k e y p r e s s ( s e l f , key ) :
i f k e y == Delete :
d e b u g p r i n t ( " Deleting :" , s e l f . s e l e c t e d )
s e l f . selected . delete ()
s e l f . s e l e c t e d = None
d e f s e l e c t ( s e l f , node ) :

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

225

if

self . selected :
s e l f . selected . unselect ()
s e l f . s e l e c t e d = node
i f node :
node . s e l e c t ( )

##################################################
# ToolButton
#
#
The b u t t o n i n t h e t o o l p a l e t t e t h a t s e l e c t s
#
the t o o l
#
c l a s s T o o l B u t t o n ( T k i n t e r . Button ) :
spt = (24, 24)
def

i n i t ( s e l f , t o o l , p a l e t t e , bitmap ) :
self . tool = tool
self . palette = palette
T k i n t e r . Button .
init (
self ,
m a s t e r=p a l e t t e ,
d e f a u l t= disabled ,
bitmap= @images /+bitmap ,
command=t o o l . c h o o s e ,
r e l i e f = raised ,
w i d t h= s e l f . s p t [ 0 ] ,
h e i g h t= s e l f . s p t [ 1 ] )
s e l f . b i n d ( s e q u e n c e="< Enter >" ,
f u n c=t o o l . d e s c r i b e )
s e l f . b i n d ( s e q u e n c e="< Leave >" ,
f u n c=t o o l . u n d e s c r i b e )

##################################################
# Select
#
#
The s e l e c t i o n t o o l i s u s e d t o s e l e c t and
#
e d i t or d e l e t e p r e v i o u s l y c r e a t e d nodes .
#
Help . add (
" Selection Tool" ,
( "< title > Selection Tool </ title >\n"
"The <b> Selection Tool </b> allows you to re-edit relationships "
"that have already been added to the rule .\n"
"With the <b> Selection Tool </b> enabled , you can click on "
" existing relationships to bring up a dialog box containing "
"more specific options . For instance , clicking on an <b> interval </b> "
" relationship will allow you to select a specific kind of interval .\n"
"When a relationship is selected , it is displayed in reverse video .\n"
" Pressing the <b> Delete </b> key removes the selected relationship "
"from the rule editor ." ) )

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

226

c l a s s S e l e c t ( Tool ) :
b u t t o n b i t m a p=" select .xbm"
t o o l n a m e=" Selection "
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
self . select ( start )
##################################################
# Equals
#
#
The E q u a l s Tool i s u s e d t o c r e a t e e q u a l s n o d e s
#
t h a t can c o n s t r a i n t h e r e s u l t s o f two o t h e r
#
n o d e s t o be e q u a l .
#
Help . add ( " Equals Tool" ,
( "< title > Equals Tool </ title >\n"
"The <b> Equals Tool </b> constrains the results of two nodes "
"to be equal .\n"
"An <b> equals </b> relationship can only be set up between two "
" nodes with the same result type ." ) )
c l a s s E q u a l s ( Tool ) :
b u t t o n b i t m a p=" equals .xbm"
t o o l n a m e=" Equals "
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
i f s t a r t and s t a r t ! = f i n i s h :
i f ( s t a r t . EQUALITY TYPE ! = and
s t a r t . EQUALITY TYPE == f i n i s h . EQUALITY TYPE ) :
node = E quals N o de ( r u l e E d i t o r , s t a r t , f i n i s h )
s e l f . s e l e c t ( node )
else :
error message (
"The nodes specified are not comparable .\n"
"The Equals relationship may only be created between "
"two nodes with the same result type .\n"
"In this case , the result types were :\n"
"
First node : " + s t a r t . EQUALITY TYPE +
"
Second node : " + f i n i s h . EQUALITY TYPE +
"\ nPlease try again or see the help page on the " +
" Equals Tool for more information ." )
c l a s s Eq uals N ode ( Nodes . B i n a r y R e l a t i o n ) :
n o d e t y p e = equals
bitmap = equals .xbm
##################################################
# Greater
#
#
The G r e a t e r Than Tool c r e a t e s G r e a t e r Than

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS


#
#
#

227

n o d e s t h a t c o n s t r a i n t h e r e s u l t o f one node
t o be g r e a t e r t h a n o r l e s s t h a n a n o t h e r node .
The two n o d e s must have t h e same r e s u l t t y p e .

Help . add (
" Greater Than Tool" ,
( "< title > Greater Than Tool </ title >\n"
"The <b> Greater Than Tool </b> constrains the result of one node to be "
" greater than that of another .\n"
"A <b> Greater Than </b> relationship can only be set up between "
"two nodes with the same result type .\n"
"<b>Tip :</b> Drag <i>from </i> the node that you wish to have "
"the greater result <i>to</i> the node that you wish to have "
"the lesser result . In this way , the <b> Greater Than </b> tool"
" doubles as a <b>Less Than </b> tool ." ) )
c l a s s G r e a t e r ( Tool ) :
b u t t o n b i t m a p=" greater .xbm"
t o o l n a m e=" Greater Than"

def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :


i f s t a r t and s t a r t ! = f i n i s h :
i f ( s t a r t . COMPARISON TYPE ! = and
s t a r t . COMPARISON TYPE == f i n i s h . COMPARISON TYPE ) :
node = G r e a t e r N o d e ( r u l e E d i t o r , s t a r t , f i n i s h )
s e l f . s e l e c t ( node )
else :
error message (
"The nodes specified are not comparable .\n"
"The Greater Than relationship may only be created between "
"two nodes with the same result type .\n"
"In this case , the result types were :\n"
"
First node : " + s t a r t . COMPARISON TYPE +
"
Second node : " + f i n i s h . COMPARISON TYPE +
"\ nPlease try again or see the help page on the " +
" Greater Than Tool for more information ." )
c l a s s G r e a t e r N o d e ( Nodes . B i n a r y R e l a t i o n ) :
n o d e t y p e = greater
bitmap = greater .xbm
def v i s u a l i z e ( s e l f ) :
Nodes . B i n a r y R e l a t i o n . v i s u a l i z e ( s e l f )
s e l f . update arrow dir ()
def move visual ( s e l f ) :
Nodes . B i n a r y R e l a t i o n . m o v e v i s u a l ( s e l f )
s e l f . update arrow dir ()
def update arrow dir ( s e l f ) :
i f s e l f . a . pt [0] >= s e l f . b . pt [ 0 ] :

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

228

s e l f . v i s u a l [ 4 ] [ bitmap ] = @images /less.xbm


else :
s e l f . v i s u a l [ 4 ] [ bitmap ] = @images / greater .xbm

##################################################
# Or
#
#
The Or Tool
Help . add (
"Or Tool" ,
( "< title >Or Tool </ title >\n"
"The <b>Or Tool </b> allows two constraints to be applied "
" independently .\n"
"An <b>Or Node </b> may be created between any two nodes , "
"with the exception of nodes in the note template .\n" ) )
c l a s s Or ( Tool ) :
b u t t o n b i t m a p="or.xbm"
t o o l n a m e="Or"
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
i f s t a r t and s t a r t ! = f i n i s h :
i f s t a r t . n o d e t y p e ! = note and f i n i s h . n o d e t y p e ! = note :
node = OrNode ( r u l e E d i t o r , s t a r t , f i n i s h )
s e l f . s e l e c t ( node )
else :
error message (
"The Or relationship may only be created "
" between nodes that are not part of the note "
" template .\n"
" Please try again or see the help page on the "
" Or Tool for more information ." )
c l a s s OrNode ( Nodes . B i n a r y R e l a t i o n ) :
n o d e t y p e = or
bitmap = or.xbm
##################################################
# Not
#
#
The Not Tool
Help . add (
"Not Tool" ,
( "< title >Or Tool </ title >\n"
"\n"
"An <b>Or Node </b> may be created between any two nodes , "
"with the exception of nodes in the note template .\n" ) )
c l a s s Not ( Tool ) :

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

229

b u t t o n b i t m a p="not.xbm"
t o o l n a m e="Not"
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
if start :
i f s t a r t . n o d e t y p e ! = note :
node = NotNode ( r u l e E d i t o r , s t a r t )
s e l f . s e l e c t ( node )
else :
error message (
"The Not node may only be created "
"on a node that is not part of the note "
" template .\n"
" Please try again or see the help page on the "
" Not Tool for more information ." )
c l a s s NotNode ( Nodes . U n a r y R e l a t i o n ) :
n o d e t y p e = not
bitmap = not.xbm

12.1.3

IntervalTool.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# I n t e r v a l T o o l . py
#
#
A t o o l f o r c o n s t r a i n i n g t h e i n t e r v a l between two n o t e s
i m p o r t T o o l s , Nodes , Help
from C o n s t a n t s i m p o r t
Help . add ( " Interval Tool" ,
"" "< title > Interval Tool </ title >
The <b> I n t e r v a l Tool</b>Tool c o n s t r a i n s t h e i n t e r v a l between two n o t e s .
on t h e m u s i c a l k e y b o a r d t o c h o o s e t h e i n t e r v a l . """)
c l a s s Tool ( T o o l s . Tool ) :
b u t t o n b i t m a p=" interval .xbm"
t o o l n a m e=" Interval "
def run tool ( s e l f , ru l e E di t o r , s t a r t , f i n i s h ) :
i f s t a r t ! = None and s t a r t ! = f i n i s h :
node = I n t e r v a l N o d e ( r u l e E d i t o r , s t a r t , f i n i s h )
s e l f . s e l e c t ( node )
c l a s s I n t e r v a l N o d e ( Nodes . B i n a r y R e l a t i o n ) :
EQUALITY TYPE = " interval "
COMPARISON TYPE = " interval "

Drag a r

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

230

def i n i t e d i t w i d g e t s ( s e l f ) :
s e l f . m a s t e r . k e y b o a r d [ state ] = normal
s e l f . m a s t e r . k e y b o a r d . s e t m o d e (KB RANGE)
def destroy edit widgets ( s e l f ) :
s e l f . m a s t e r . k e y b o a r d [ state ] = disabled
def keyboard callback ( s e l f , keyList ) :
print keyList

12.2

Constants and Utility functions

12.2.1

Constants.py

Code
# V i s u a l P e l o g F a l l 1 9 9 9 M i c h a e l Droettboom mdboom@mail . com
############################################################
# C o n s t a n t s . py
#
#
Constants used throughout the V i s u a l Pelog system
##################################################
# Used by t h e g r i d l a y o u t a l g o r i t h m
# ( x , y ) p a i r : The amount o f p a d d i n g between n o d e s
RE PAD = ( 6 , 6 )
# ( x , y ) p a i r : The amount o f p a d d i n g a r o u n d t h e g r a p h
RE CANVAS PAD = ( 4 , 4 )
# The minimum and maximum p o s s i b l e v a l u e s f o r t h e t o p o l o g i c a l s o r t
RE MAXGRID = 10000
RE MINGRID = 10000
##################################################
# Used by t h e r u l e e d i t o r a n i m a t o r
RE ANIMATION FRAMES = 1 0 . 0
##################################################
# C o l o r s ( Yes , i t s t h e U . S . s p e l l i n g , t o be
#
c o n s i s t e n t w i t h Tk )
RE COLOR CANVAS BACKGROUND = white
RE COLOR NORMAL = #0000 AA
RE COLOR NODE = # eeeeee

# Canvas b a c k g r o u n d c o l o u r

# o u t l i n e u n s e l e c t e d nodes
# f i l l u n s e l e c t e d nodes

RE COLOR SELECTED = # AA0000


RE COLOR NODE SELECTED = black

# o u t l i n e s e l e c t e d nodes
# f i l l i n s e l e c t e d nodes

RE COLOR HIGHLIGHTED = #0000 ff


RE COLOR HIGHSELECT = # FF0000

# o u t l i n e h i g h l i g h t e d nodes
# o u t l i n e h i g h l i g h t e d s e l e c t e d nodes

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

RE COLOR TEXT = black


RE COLOR TEXT SELECTED = white

# Used f o r t h e c o l o r o f t e x t on n o d e s
# Used f o r t h e c o l o r o f t e x t on s e l e c t e d n o d e s

COLOR REFERENCE = # DDDDFF


COLOR REF HIGHLIGHT = # AAAAFF
##################################################
# Ons c r e e n k e y b o a r d c o n s t a n t s
KB SINGLE = 0
KB MULTI = 1
KB RANGE = 2

12.2.2

Util.py

Code
from T k C o n s t a n t s i m p o r t
i m p o r t tkMessageBox
DEBUG = TRUE
APP NAME = " Visual Pelog "
APP VERSION = "0.0001 a"
APP AUTHOR = " Michael Droettboom "
d e f d e b u g p r i n t ( kp ) :
i f DEBUG :
f o r x i n kp :
print x ,
p r i n t "\n" ,
def f l i p ( x ) :
i f x == 1:
return 0
else :
return 1
c l a s s CursorManager :
def
init ( self ):
s e l f . c u r s o r = s e l f . c g e t ( cursor )
def s e t c u r s o r ( s e l f , cursor ) :
s e l f . c u r s o r = s e l f . c g e t ( cursor )
s e l f . c o n f i g u r e ( c u r s o r=c u r s o r )
def r e s t o r e c u r s o r ( s e l f ) :
s e l f . c o n f i g u r e ( c u r s o r= s e l f . c u r s o r )
def error message ( s t r i n g ) :
tkMessageBox . s h o w e r r o r (APP NAME , s t r i n g )
def bin2hex ( bin ) :

231

CHAPTER 12. IMPLEMENTATION OF OTHER COMPONENTS

232

hx =
for c in bin :
hx = hx + hex ( o r d ( c ) ) [ 2 : ]
r e t u r n hx
d e f h e x 2 b i n ( hx ) :
b i n =
f o r i i n x r a n g e ( 0 , l e n ( hx ) / 2 ) :
b i n = b i n + c h r ( e v a l ( 0x + hx [ i 2 : ( i 2 ) + 2 ] ) )
return bin
def b i n 2 h e x f i l e ( b i n f i l e , h e x f i l e ) :
open ( h e x f i l e , w ) . w r i t e ( b i n 2 h e x ( open ( b i n f i l e , r ) . r e a d ( ) ) )
def h e x 2 b i n f i l e ( h e x f i l e , b i n f i l e ) :
open ( b i n f i l e , w ) . w r i t e ( h e x 2 b i n ( open ( h e x f i l e , r ) . r e a d ( ) ) )
def f i l t e r c r ( s ) :
i f s == \n o r s == \r o r s == \t :
r e t u r n
else :
return s
def remove cr ( s ) :
return f i l t e r ( f i l t e r c r , s )

III
Appendices

233

Tcl/Tk-Prolog Connector System


A.1

The problem

Now that two suitable development tools have been selected, a method of connecting
them must be chosen. Both Tcl/Tk and SWI-Prolog have built-in C-language interfaces. Intuitively, one might assume that connecting the two systems is a simple
matter of calling the others external C procedures. However, certain peculiarities around how SWI-Prologs external interface works make direct communication
through function calls impossible. Some type of connector must be provided.
SWI-Prolog has an extensive set of C functions for doing everything from creating new goals to running queries. However, these functions use custom user-defined
types to send parameters in and out. These types have names like term t and functor t that directly represent Prolog-specific data structures. Unfortunately, Tcl/Tk
has no support for user-defined C data types, and thus can not call these functions
directly.
In order to call C from Prolog, you must provide a specially-designed C function
which will set-up what SWI calls foreign language predicates. These predicates
appear to the Prolog programmer as regular Prolog predicates, but are in fact
linked to external code. The necessary elements of this system, such as callbacks
and function pointers, can not be provided from Tcl/Tk.

A.2

C-language connector method

What appears to be needed, therefore, is a small dynamically-linked library written


in C that will provide an interface between Tcl/Tk and Prolog. It would have
wrapper functions, around the external functions of both SWI-Prolog and Tcl/Tk,
that would perform all of the necessary data conversion.
Not surprisingly, there are a number of products that do exactly that. SICStus
Prolog includes a Tcl/Tk interface right out of the box. There is also a product

234

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

235

for Eclipse-Prolog called ProTcXl. Unfortunately, even if this interface were freely
available, it would require considerable effort to make it work with SWI-Prolog.
The external C functions provided by SWI-Prolog are entirely different from those
provided by any other Prolog, even though the general concept is the same. The
connector required for this project, therefore, must be SWI-Prolog-specific.

A.2.1

Tcl/Tk-Prolog Connection

[http://ace.ulyssis.student.kuleuven.ac.be/ jeans/tcltk/conn.html]
Jan Struyf, an undergraduate student in Belgium has created a connection between SWI-Prolog and Tcl/Tk. It allows you to send commands in either direction
that are passed as-is and evaluated by the respective interpreters. A list of the
functions is in Table A.1.
Tcl/Tk Side
p r o l o g e v e n t EventName ( P a r a m e t e r s , . . . )

Prolog Side
t c l n e w ( I n t e r p )
t c l d e l e t e (+ I n t e r p )
t c l e v a l (+ I n t e r p ,+Command, R e s u l t )
tk
tk
tk
tk

n e w (+Opts , I n t e r p )
do one event
n e x t e v e n t (+ I n t e r p , E v e n t )
main loop

tcl
tcl
tcl
tcl
tcl
tcl

e v a l f i l e /1
a b o u t /0
r e s u l t /0
s t r i n g /2
a r i t y /2
e x i t /0

Table A.1: Functions provided by Jan Struyfs Tcl/Tk-Prolog Connection


This system seems quite robust, works with a number of different versions of
Tcl/Tk and SWI-Prolog, and full source code is provided. Unfortunately, the system
is currently not ported to anything but MS-Windows. Committing to using this
system, therefore, means not only committing to a specific implementation and
version of Prolog, but also to the Windows platform.

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

A.3

236

Piping method

Another solution to the Prolog-Tcl/Tk connector problem is presented in an article


by Andrea DellAmico at the University of Pisa1 . This article describes a connection
between BinProlog and Tcl/Tk using piping. It takes advantage of the fact that
both Prolog and Tcl/Tk have strong meta-programming elements.

A.3.1

Interaction Model

The Prolog system is run as a subprocess under Tcl/Tk. (Therefore, Prolog is a


slave to Tcl/Tk, though this could easily be reversed.) Assertions or queries are sent
to Prolog along the pipe just as a user would enter them from the keyboard. When
a query is sent to Prolog, Tcl/Tk listens to the pipe and retrieves and interprets
the input. If the input begins with the keyword call tcl, it is sent directly to Tcls
eval function, which runs the rest of the input as a Tcl command.
While this may not seem as elegant as the C-language connector solution, it
does have some distinct advantages:
implemented entirely in Tcl/Tk and Prolog.
uses only standard methods of input/output, avoiding the external C functions
which change across implementations and platforms.
provides as equally as much functionality as the Struyf C-language connector
implementation.
The primary disadvantage to this approach is that all interactions between Prolog
and Tcl/Tk must go through an additional parsing step. DellAmico shows, however,
that this performance hit is quite insignificant relative to the system as a whole.
The higher flexibility over how and when data is sent, the tighter integration within
the core tools and the almost instant portability of this system makes it superior to
a highly Prolog-specific C-language connector.

A.3.2

Design

The design of a portable piping interface between Tcl/Tk and Prolog is described
in an article by Andrea DellAmico2 .

A.3.3

Usage

The majority of this usage definition is defined in DellAmico. See Table A.2 for a
list of user functions.

A.3.4

Implementation

Implementing the above solution turned out to be a relatively straight-forward task,


though there were a few problems to solve.
1
2

http://www.cli.di.unipi.it/help/prolog/BinProlog/tcl
http://www.cli.di.unipi.it/help/prolog/BinProlog/tcl

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

Tcl-Tk Side
start prolog $process
halt prolog
start test

p AssertPrologGoal
q Query

Prolog Side
start tcl server().
The listener loop reacts to the following terms:
assert prolog(X).
query prolog(X).
halt.
call tcl(X).

237

...
starts the Prolog process. The process must
have the tcl interface library loaded.
halts the Prolog process.
allows the Tcl/Tk side to be run without the
Prolog side for testing what is being sent to
Prolog
adds a goal to the Prolog database
initiates a Prolog query. The result is read in
and interpreted by Tcl. All lines from Prolog
starting with the keyword call tcl are interpreted using eval.
...
starts the listener loop on stdio
adds the goal X to the Prolog database
runs query X and displays the result on stdio.
halts the Prolog system.
provides a convenient way to run Tcl commands. The first element of the list X must
be a Tcl command. The rest of the elements
in the list are sent as arguments to that command.

Table A.2: User functions of SWI-Prolog-Tcl/Tk Piping Interface

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

238

Problem 1: Poor piping implementation in Windows 9x


The piping implementation proposed by DellAmico uses Tcls addinput3 command
to call a procedure whenever there is data available to be read from the Prolog process. This is an event-driven approach: whenever there is something to read, read
it. Unfortunately, event-driven input from pipes is not supported under Windows
9x. Under UNIX and Windows NT, piping is an entirely transparent process that
is analogous to reading and writing from a file. In Windows 9x, however, there are
restrictions on what you can do with pipes. There are no events triggered when
the buffer contains data. Also, there is no end-of-file marker when there is nothing
in the buffer. There is a workaround for this, however. In the following Windows
9x-compatible implementation, data is only read from Prolog after running a query.
The result of every query, whether it fails or not, ends in the keyword stop tcl. This
keyword is used as a virtual end-of-file marker, telling Tcl to stop reading from the
Prolog process. The problem with this workaround is that Prolog can not trigger
Tcl events in the background. For the purposes of this project, this is not a major
drawback, since the interaction model is primarily:
1. user initiates a query
2. Tcl sends query to Prolog
3. Prolog responds to query, outputting a result
4. Tcl retrieves the result of the query and displays it to the user
Note also that my workarounds for Windows 9x are theoretically upward-compatible
to Windows NT and UNIX.
Another option would be to use Microsofts DDE (Dynamic Data Exchange)
standard in place of piping. DDE is supported by both SWI-Prolog and Tcl/Tk.
However, using it would eliminate any of the portability advantages of the piping
method.
Problem 2: Using stdio in SWI-Prolog
SWI-Prolog is a somewhat non-standard console application. It does not use stdio,
(the stream which is most often used for piping,) for normal user interaction. You
cannot, for example, create an user input file and pipe it into SWI-Prolog at the
command line. A workaround for this is to create a listener-loop that reads input
from stdio. Terms read by the listener-loop are then interpreted as standard Prolog
terms. Output from queries must likewise be explicitly printed to stdio using the
write/1 predicate.
Once these two snags were worked out, the rest of the connector implementation
followed easily. To ensure maximum portability, only library predicates defined as
part of the ISO standard Prolog were used.
3

renamed fileevent as of Tcl/Tk v 8.0

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

A.3.5

239

pl interface.tcl

Code
##############################################################
# T c l /Tk s i d e o f t h e SWIP r o l o g <> T c l /Tk i n t e r f a c e
#
# W r i t t e n by : M i c h a e l Droettboom
#
yu143345@yorku . ca
# Date :
April , 1999
# Based on an a r t i c l e d e s c r i b i n g a B i n P r o l o g <> T c l /Tk
# i n t e r f a c e by Andrea D e l l Amico
#
h t t p : / /www. c l i . d i . u n i p i . i t / h e l p / p r o l o g / B i n P r o l o g / t c l
##############################################################
# USAGE :
#
# % s t a r t p r o l o g plcon . exe
OR
% start test
# % q { PrologQuery }
# % p { QuietPrologGoal }
# % halt prolog
#
global f
# t e s t s what T c l would s e n d t o P r o l o g
proc s t a r t t e s t {} {
global f
set f stdout
}
# terminates the Prolog process
proc h a l t p r o l o g {} {
global f
w r i t e p r o l o g "halt"
catch { removeinput $f }
catch { c l o s e $f }
}
# c o n n e c t s t o a new P r o l o g p r o c e s s
#
p r o c e s s a s t a n da l o n e P r o l o g e x e c u t a b l e
#
t h i s P r o l o g must c o n t a i n t h e l i b r a r y t c l i n t e r f a c e . p l
proc s t a r t p r o l o g { process } {
global f
s e t f [ open "| $process " r +]
f c o n f i g u r e $f b u f f e r i n g l i n e
puts $f { s t a r t t c l s e r v e r .}
f l u s h $f
puts [ gets $f ]
return $f

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

240

}
# w r i t e s Something . t o P r o l o g
proc w r i t e p r o l o g { s } {
global f
puts $f $s .
}
# s e n d s t o p r o l o g a g o a l t o be q u i e t l y e v a l u a t e d
proc a s s e r t p r o l o g { l } {
w r i t e p r o l o g " assert_prolog (( $l ))"
}
# s e n d s t o p r o l o g a q u e r y w i t h v a r i a b l e s t o be a n s w e r e d
proc query prolog { l } {
w r i t e p r o l o g " query_prolog (( $l ))"
read prolog
}
# shorthand for c a l l p r o l o g
proc p { l } {
assert prolog $l
}
# shorthand for query prolog
proc q { l } {
query prolog $l
}
# t r u e i f t h e g i v e n l i n e i s t o be e v a l u a t e d a s a t c l command coming from P r o l o g
proc t c l c a l l { l } {
r e t u r n [ e x p r 0 = = [ s t r i n g f i r s t " call_tcl " [ s t r i n g t r i m l e f t $ l ] ] ]
}
# d e t e c t s i f an i n p u t l i n e c o n t a i n s a t c l c a l l
# ( i . e . when i t s t a r t s w i t h c a l l t c l )
# i f yes then i t executes the l i n e
# o t h e r w i s e i t p r i n t s i t out
proc e v a l l i n e { l } {
i f {[ t c l c a l l $l ]} {
return [ eval $l ]
} else {
puts $l
}
}
# r e a d s i n c o m i n g d a t a from P r o l o g u n t i l e n c o u n t e r i n g a s t o p t c l command
proc r e a d p ro l o g {} {
global f
i f { $ f ! = " stdout " } {
v a r i a b l e l go

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

241

f o r { } { $ l ! = " tcl_stop " } { } {


f l u s h $f
gets $f l
eval line $l
}
}
}

A.3.6

tcl interface.pl

Code
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% P r o l o g s i d e o f t h e SWIP r o l o g <> T c l /Tk i n t e r f a c e
%
%
% W r i t t e n by : M i c h a e l Droettboom
%
yu143345@yorku . ca
% Date :
April , 1999
% Based on an a r t i c l e d e s c r i b i n g a B i n P r o l o g <> T c l /Tk
% i n t e r f a c e by Andrea D e l l Amico
%
h t t p : / /www. c l i . d i . u n i p i . i t / h e l p / p r o l o g / B i n P r o l o g / t c l
%%%%%%%%%%%%%%%%%%%%%%%%
% OPERATOR DEFINITIONS
%
$ i s n e e d e d t o s e n d T c l v a r i a b l e s t o t h e T c l i n t e r p r e t e r
: op ( 2 0 0 , f x , ( $ ) ) .
%%%%%%%%%%%%%%%%%%%%%%%%
% MAIN ENTRY POINT
%
%
USAGE : s t a r t t c l s e r v e r .
%
%
S t a r t s a l i s t e n e r l o o p t o r e a c t t o commands from T c l
% main : s t a r t t c l s e r v e r .
s t a r t t c l s e r v e r :
f o r m a t ( TCL Server started . ) , n l , ! ,
prompt ( , ) , % c r e a t e s a quiet prompt
repeat ,
n l , w r i t e ( tcl_stop . ) , n l ,
t c l i n (X , Vs ) ,
r e a c t (X , Vs ) ,
!.
%%%%%%%%%%%%%%%%%%%%%%%%
% INPUT
%
%
USAGE :

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM


%
%
%
%
%
%

242

There a r e f o u r commands t h a t a r e a c c e p t e d from T c l :


a s s e r t p r o l o g (X ) . add a g o a l t o t h e d a t a b a s e
q u e r y p r o l o g (X ) .
r u n a p r o l o g q u e r y
t h e keyword " tcl_stop " i s added
t o t h e end o f a l l o u t p u t .
h a l t or
e n d o f f i l e q u i t s t h e i n t e r p r e t e r

r e a c t ( a s s e r t p r o l o g (X ) , ) : ! , a s s e r t (X ) , ! , f a i l .
r e a c t ( q u e r y p r o l o g (X ) , Vs ): q u e r y p r o l o g (X , Vs ) , f a i l .
react ( end of file , ):!.
r e a c t ( h a l t , ): h a l t .
% r u n s a q u e r y , and d i s p l a y s t h e r e s u l t s o f a l l f r e e v a r i a b l e s .
% ad d s " tcl_stop " t o t h e end .
q u e r y p r o l o g (X , Vs ):
X , member (A , Vs ) , w r i t e (A ) , n l , f a i l .
query prolog ( , ):!.
% r e a d s t e r m s from s t d i n
t c l i n (T): t c l i n (T , ) .
t c l i n (T , Vs ):
r e a d t e r m ( L , [ v a r i a b l e n a m e s ( Vs ) ] ) ,
!,
t c l i n 1 (L ,T) .
t c l i n 1 ( [ X | Xs ] , T):
!,
member (T , [ X | Xs ] ) .
t c l i n 1 (T , T ) .
none ( none ) .
%%%%%%%%%%%%%%%%%%%%%%%%
% OUTPUT
%
%
USAGE : c a l l t c l ( L i n e ) .
%
C o n v e r t s t h e term L i n e i n t o o u t p u t t h a t w i l l be p r o c e s s e d a s
%
a T c l command . The f i r s t e l e m e n t o f L i n e must t h e r e f o r e
%
be a p r o c e d u r e o r v a r i a b l e .
c a l l t c l ( L i n e ):
w r i t e ( call_tcl ) , t c l o u t ( L i n e ) .
t c l o u t ( [ X | Xs ] ) :
! , t c l o u t l i s t ( [ X | Xs ] ) .
t c l o u t (T): t c l o u t t e r m (T ) .
t c l o u t l i s t ( L ): member (T , L ) , t c l o u t t e r m (T ) , f a i l .
tcl out list ( ).

APPENDIX A. TCL/TK-PROLOG CONNECTOR SYSTEM

243

t c l o u t t e r m (T): p l 2 t c l (T ) , ! .
p l 2 t c l (T): p l 2 t c l (T , L ) , w r i t e l i s t ( L ) .
p l 2 t c l (X , L ): p l 2 t c l (X , L , [ ] ) .
% s e n d i n g f r e e v a r i a b l e s t o T c l makes no s e m a n t i c s e n s e , s o e r r o r .
p l 2 t c l (X)>{v a r (X ) } , ! , { w r i t e ( unexpected input : variable ) , t r u e } , [ error ] .
% s e n d atoms as is .
p l 2 t c l (X)>{a t o m i c (X ) } , ! , [ X ] .
% s e n d atoms p r e c e d e d by $ a s T c l v a r i a b l e r e f e r e n c e s
p l 2 t c l ( $ (X))>!,{V=$ (X ) } , ! , [ V ] .
% send l i s t s i n s i d e { }
p l 2 t c l ( [ X | Xs ]) > ! , [ { ] , p l 2 t c l (X ) , t c l c m d ( Xs ) , [ } ] .
% send e x p l i c i t l y d e f i n e d { }
p l 2 t c l ( { Xs })>!,[ { ] , p l 2 t c l ( Xs ) , [ } ] .
% send e x p l i c i t y d e f i n e d ( )
p l 2 t c l ( ( X , Xs ))>!, p l 2 t c l (X ) , p l 2 t c l ( Xs ) .
t c l c m d ( [ X | Xs ])>!, p l 2 t c l (X ) , t c l c m d ( Xs ) .
tcl cmd ([])>[].
w r i t e l i s t ( L ):
member (X , L ) ,
t c l w r i t e (X ) , w r i t e ( ) ,
fail .
writelist ( ).
t c l w r i t e ( $ (X ) ) : ! , w r i t e ( $ ) , w r i t e (X ) .
t c l w r i t e (X): w r i t e (X ) .
% : s t a r t t c l s e r v e r .

The graphical user interface

This graphical user interface was designed to automate usage of the core Pelog
system. This application will be made obsolete with the completion of the Visual
Pelog system.
The Pelog system includes a very basic user interface for performing the following
tasks:
editing GUIDO Music Notation files that are used as input and output by the
Pelog system
viewing the files as conventional music notation (cmn)
listening to the files through the MIDI hardware
applying a given set of musical rules to the file using the Pelog system
The Pelog-GUI is designed for the interactive application of pre-defined sets of
musical rules. It does not directly provide the ability to customize or develop rule
sets for Pelog. For that youre better of using an editor with some advanced Prolog
support such as GNU Emacs. Please see The Pelog Language (page 2) for more
information.
For information on how to write GUIDO Music Notation files for the Pelog
system, see GUIDO Music Notation (page 35).

B.1

Installing the GUI

The installation instructions given here are Microsoft Windows-specific. Please see
Notes about Portability (page 251) for more information.
The Pelog-GUI requires that the following freely available applications are installed on your system:

244

APPENDIX B. THE GRAPHICAL USER INTERFACE

245

Tcl/Tk version 8.1


http://www.scriptics.com/
SWI-Prolog
http://www.swi.psy.uva.nl/projects/SWI-Prolog/
GUIDO NoteViewer
http://www.informatik.tu-darmstadt.de/AFS/GUIDO
gmn2midi
http://www.informatik.tu-darmstadt.de/AFS/GUIDO
Once all these applications have been successfully installed on your system, you will
need to ensure that Pelog-GUI can find all of the required executables by including
them in your PATH environment variable. The executables that the Pelog-GUI needs
are:
wish81.exe (Tcl/Tk)
plcon.exe (SWI-Prolog)
gmnview.exe (GUIDO NoteViewer)
gmn2midi.exe (gmn2midi)
An example PATH environment variable might look like this:
PATH C:\PROGRA~1\TCL\BIN;
C:\PROGRA~1\SWI-PR~1\BIN;
C:\PROGRA~1\GUIDO
Please consult the Microsoft Windows on-line documentation for more information
on setting your PATH environment variable.

B.2

Running the GUI

To run the Pelog-GUI, simply run the provided batch file pelog.bat. The easiest
way to do this is by double-clicking on it in the Explorer. This script will start the
Pelog-GUI under Tcl/Tks wish interpreter. This will bring up the opening splash
screen in Figure B.1.
Click anywhere on the splash window to continue to the Pelog-GUI.

B.3

A quick introduction

The section will guide you through all of the basic functionality of the Pelog-GUI.
The Pelog-GUI is very similar to many standard text editors such as Microsoft
Notepad included with Windows. The difference is that the Pelog-GUI is specifically
tailored to edit musical scores used as input to the Pelog interpreter. For more
information about these files, see GUIDO Music Notation (page 35).
Lets enter an score by typing the following line into the editor:

APPENDIX B. THE GRAPHICAL USER INTERFACE

246

Figure B.1: The Pelog-GUI splash window

{ [ c0 d e f g a b c1 ] }

The outer set of curly brackets { } is used to denote an entire score. The inner
set of square brackets [ ] is used to denote an individual voice (or part) within
that score. You can see that this score contains only one voice. In that voice there
are five notes each represented by a letter. The numbers after the letters select the
octave.
To look at the score as common music notation (cmn) press the button
on
the toolbar. Figure B.2 shows the GUIDO NoteViewer displaying the score that
we have entered:
You can also listen to the score through any available MIDI hardware by pressing
the

button on the toolbar.


So far, we havent done anything terribly exciting. The real power of the Pelog
system is in applying musical rules to scores. Before we can do that, however, we
will need to insert some variable notes into our score. Variable notes are notes
with unspecified pitch that the interpreter will fill-in in by applying musical rules.
Variable notes are represented with the tilde ~ character. Change the score to
look like this:
{ [ c c2 ] }

This score indicates that we want the starting and ending notes to be c, but the
interpreter can fill-in the rest of the notes as it pleases. It is important to note that
the tilde character is not supported by the view or look functions, and therefore you
cannot view or listen to a score containing note variables.
The next step is to select a set of rules to use when filling in the variable notes.
Pressing the

button in the toolbar brings up a dialog box containing

APPENDIX B. THE GRAPHICAL USER INTERFACE

247

Figure B.2: The GUIDO NoteViewer displaying the example score

all of the available rule sets on your system1 . For now, lets choose the modal
counterpoint rule set. When you return from the dialog box you will notice that
the text box on the right-hand side of the toolbar shows the name of the rule set
we just selected.
Now we are ready to apply the rules. Press the
button on the toolbar. The
Pelog system will load your score and run it through the rule set. Be patient. This
particular example took 30 seconds to solve on my Intel Pentium 166MHz machine.
When the Pelog interpreter is finished, the results of its work are loaded into a new
Pelog-GUI window.
This output score can be viewed or listened to just like the input score. You
can even revise it, including adding variable notes, and then run it through the
interpreter again.
1

For a detailed explanation of rule sets, see The Pelog Language (page 2.)

APPENDIX B. THE GRAPHICAL USER INTERFACE

B.4

Visual elements

B.4.1

The main window

248

The main window should seem quite familiar to anyone accustomed to typical
Windows or Macintosh applications. It consists of a menubar, toolbar and text
editor.

B.4.2

The menubar

B.4.3

The toolbar2

2
These toolbar graphics are from a set developed by IBM/Lotus and freely available from
IBM Ease of Use http://www.ibm.com/easy/. The toolbar images developed by Microsoft are
perhaps more standard, but are licensed for use only in conjunction with Microsofts own software
development products.

APPENDIX B. THE GRAPHICAL USER INTERFACE

B.4.4

249

The editor

The editor is a standard text editor identical in functionality and behaviour to


Microsoft Windows Notepad.

B.5
B.5.1

Functions
Basic file and editing functions

The basic file and editing functions should be old hat to anyone familiar with Windows. The behaviour is identical to Microsoft Windows Notepad.

B.5.2

Look

The Look function is available from the View menu or by pressing


on the toolbar.
This opens the GUIDO NoteViewer to display the contents of the editor in
common music notation (cmn). Notice that Look displays exactly what is in the
editor when it is activated, not the saved version of the file.

Figure B.3: The GUIDO NoteViewer window opened by the View Look function
For information about using the GUIDO NoteViewer, please see the online help
on the Help menu in the NoteViewer window.

B.5.3

Hear

The Hear function is available from the View menu or by pressing


on the toolbar.
This opens the Windows Media Player to play the contents of the editor. Like
the Look function, Hear plays exactly what is in the editor when it is activated.

APPENDIX B. THE GRAPHICAL USER INTERFACE

250

Figure B.4: The Windows Media Player opened by the View Hear function

The Windows Media Player plays MIDI files on currently selected MIDI device.
This MIDI hardware could be a sound card, such as the popular Sound Blaster, or
an external MIDI device such as a keyboard or sound module. This device can be
selected using the Multimedia applet in the Windows Control Panel. See the online
help in the Media Player itself for more information on using Microsoft Windows
Media Player.
Note: At the present time, the gmn2midi application that converts
GUIDO Music Notation format files into standard MIDI files is still
in beta testing. Just from my own informal usage, it has proven to
be quite weak in certain areas. For instance, when playing the Happy
Birthday example above, the bass part is consistently a full beat ahead
of the treble.

B.5.4

Go

The Go function is available from the Rules menu or by pressing the


toolbar
button.
This function applies the currently selected Pelog rule set to the currently opened
input file.
The rule set can be chosen with a file dialog by selecting the Rules Select Rule
Set... menu item or pressing the
toolbar button. The rule set name
can also be typed in directly to the text box at the right-hand side of the toolbar.

APPENDIX B. THE GRAPHICAL USER INTERFACE

251

After the Pelog interpreter is finished applying rules to the file (which may take
quite some time depending on the complexity of the rule set and the number of
variables in the input file), the result is opened in a new Pelog-GUI window. The
name of the results file is identical to the name of the input file with out appended
to the end.

B.6

Notes about portability

At the time of this writing, the Pelog-GUI has only been tested on the 32-bit
Microsoft Windows platform. While it is theoretically portable to any platform
Tcl/Tk runs on, certain functions remain Windows-specific:
The Look function uses the GUIDO NoteViewer to display the music in common music notation. This application is currently only available for Windows.3
The Hear function uses two external applications. The first, gmn2midi converts GUIDO music notation format files into Standard MIDI files. This program is currently available for many platforms including Windows, Macintosh
and UNIX. 4 The second application is the Media Player supplied with Windows to play standard MIDI files. This application could be substituted with
any of the commonly available MIDI players available on many platforms.

3
4

See http://www.informatik.tu-darmstadt.de/AFS/GUIDO for more information.


See http://www.informatik.tu-darmstadt.de/AFS/GUIDO for more information.

Bibliography
[1] , Tcl/Tk Reference Manual. www.scriptics.com
[2] ,
Max:
object-oriented programming environment for
sic and multimedia. Mountain View, CA: Opcode Systems,
http://www.opcode.com/products/max/

muInc.

[3] Bowman, Ivan. Lecture Notes on Methods for Visual Understanding of


Heirarchical Systems Sugiyama et al. University of Waterloo, ON, CS746G.
http://plg.uwaterloo.ca/ itbowman/CS746G/Notes/Sugiyama1981/
[4] Brinkman, Alexander. Pascal Programming for Music Research. Chicago, IL:
University of Chicago Press, 1990.
[5] Clocksin, William F. & Christopher S. Mellish. Programming in Prolog. New
York: Springer-Verlag, 1987.
[6] Cope, David. Experiments in Musical Intelligence. Madison, WI: A-R Editions,
1989.
[7] Cormen, Thomas H. & Leiserson, Charles E. & Rivest, Ronald L. Introduction
to Algorithms. Cambridge, MA: MIT Press: 1997.
[8] Di Battista, Giuseppe & Peter Eades & Roberto Tamassia & Ioannis G. Tollis.
Graph Drawing: Algorithms for the Visualization of Graphs. Englewood Cliffs,
NJ: Prentice Hall, 1999.
[9] Eades, Peter & Kang Zhang, eds. Software Visualization. Singapore: World
Scientific, 1996.
[10] Eppstein, David. Geometry in Action. University of California Irvine.
http://www.ics.uci.edu/ eppstein/gina/gdraw.html
[11] Fux, Johann Joseph. (1725) The Study of Counterpoint from Johann Joseph
Fuxs Gradus ad Parnassum, translated and edited by Alfred Mann. New York:
W. W. Norton & Co., 1943.
[12] Haus, Goffredo. Music Processing. Madison, WI: A-R Editions, 1992.

252

BIBLIOGRAPHY

253

[13] Himsolt, M. GraphEd - A Graphical Platform for the Implementation


of Graph Algorithms Graph Drawing, Proceedings of DIMACS International Workshop GD94. Lecture Notes in Computer Science 894. New York:
Springer-Verlag, 1995.
[14] Hoos, Holger & Keith Hamel & K. Renz & J. Kilian. The GUIDO Music
Notation Format - A Novel Approach for Adequately Representing Score-level
Music. ICMC98 Proceedings.
[15] Hoos, Holger H. & Keith Hamel. The GUIDO Music Notation Format
Version 1.0: Specification Part I: Basic GUIDO. Technical Report TI
20/97. Darmstadt, Germany: Technische Universitat Darmstadt, 1997.
http://www.informatik.tu-darmstadt.de/AFS/GUIDO/
[16] Horton, William. The Icon Book: Visual Symbols for Computer Systems and
Documentation. New York: John Wiley & Sons, 1994.
[17] Hylands, Christopher & Edward A. Lee & H. John Reekie. The Tycho User
Interface System. Tcl/Tk Workshop 97 Proceedings. Ed. Joseph A. Konstan
& Brent Welch. Berkeley, CA: USENIX Association, 1997.
[18] Ibrahim, Bertrand. Visual Languages and Visual Programming. WWW Virtual Library.
[19] Kamada, Tomihisa. Visualizing Abstract Objects and Relations A
Constraint-Based Approach. Singapore: World Scientific, 1989.
[20] Krenek, Ernst. (1953) Modal Counterpoint in the Style of the Sixteenth Century.
New York: Boosey & Hawkes, 1959.
[21] Lafever, Dave. Visual Programming and Assistive Technology. Dr. Dobbs
Journal. Aug. 1999.
[22] Lee, Geoff. Object-Oriented GUI Application Development. Englewood Cliffs,
NJ: PTR Prentice Hall, 1993.
[23] Lutz, Mark. Programming Python. Sebastopol, CA: OReilly & Associates,
1996.
[24] OKeefe, Richard. The Craft of Prolog. Cambridge, MA: MIT Press, 1990.
[25] Paulisch, Frances Newbery. EDGE: The Design of an Extendible Graph Editor. Lecture Notes in Computer Science Series, Ed. Gerhard Goos & Juris
Hartmanis. New York: Springer-Verlag, 1991.
[26] Randel, Don Michael, ed. The New Harvard Dictionary of Music. Cambridge,
MA: Belknap Harvard, 1996.
[27] Reekie, H. John & Edward A. Lee. The Tycho Slate: Complex Drawing
and Editing in Tcl/Tk. Tcl/Tk Workshop 98 Proceedings. Ed. Don Libes
& Michael McLennan. Berkeley, CA: USENIX Association, 1998.

BIBLIOGRAPHY

254

[28] Ross, Peter. Advanced Prolog: Techniques and Examples. Don Mills, ON:
Addison-Wesley, 1989.
[29] Rowe, Neil C. Artificial Intelligence Through Prolog. Englewood Cliffs, NJ:
Prentice Hall, 1988.
[30] Salus, Peter H., ed. Handbook of Programming Languages: Volume III: Little Languages and Tools. Indianapolis, IN: Macmillan Technical Publications,
1998.
[31] Salus, Peter H., ed. Handbook of Programming Languages: Volume IV: Functional and Logic Programming Languages. Indianapolis, IN: Macmillan Technical Publications, 1998.
[32] Schaffer, John & Deron McGee. Knowledge-Based Programming for Music Research. Madison, WI: A-R Editions, 1997.
[33] Schottstaedt, William. (1984) Automatic Counterpoint. Current Directions
in Computer Music Research. Ed. Max V. Mathews and John R. Pierce. Cambridge, MA: MIT Press, 1989.
[34] Sterling, Leon & Ehud Shapiro. The Art of Prolog: Advanced Programming
Techniques. Cambridge, MA: MIT Press, 1986.
[35] Sugiyama, Kozo & Shojiro, Tagawa & Toda, Mitsuhiko. Models for Visual
Understanding of Heirarchical System Structures. IEEE Trans. on Systems,
Man and Cybernetics. Vol SMC-11, No 2, Feb 1981, pp 109-125.
[36] Taube, Rick. Introduction to Common Music.
http://cm.stanford.edu/CCRMA/Software/cm/cm.htm
[37] Thakar, Markand. (1990) Counterpoint: Fundamentals of Music Making. New
Haven, CN: Yale University Press, 1990.
[38] Thompson, Tim. Reference Manual for the KeyKit Language Version 6.2c. San
Jose, CA: AT&T, 1997.
[39] Van Hentenryck, Pascal. Constraint Satisfaction in Logic Programming. Cambridge, MA: MIT Press, 1989.
[40] Vercoe, Barry. CSound Reference Manual. Cambridge, MA: MIT Machine Listening Group, 1999.
[41] Wielemaker, Jan. SWI-Prolog 3.2 Reference Manual. Amsterdam: Department of Social Science Informatics (SWI), University of Amsterdam, 1999.
http://www.swi.psy.uva.nl/projects/SWI-Prolog/
All musical examples were rendered using GUIDO NoteServer for Windows v0.2.
http://www.informatik.tu-darmstadt.de/AFS/GUIDO

BIBLIOGRAPHY

255

All musical constraint graphs were rendered directly using Visual Pelog.
This document was typeset using LATEX and Carl Heinz exceptional listings package.

You might also like