All posts by Bill
Claude Debussy: Gardens in the Rain (arrangement)
Extended Rhythms
Dance
Chamber Music
Canon for Three
Strict Canon 8 in 2 at the Minor 3rd (in 5/4)
Berceuse
Triads
The simplest chords are called triads, because they are made of three notes a third apart from one another. We can form a triad from any note by adding two notes a third and a fifth (3 plus 3 does equal 5) above it. Here’s a triad built on C.
It’s a major triad because the interval from C (the root) to E (the third) is a major third. If we flattened the E then it would be a minor triad:
Like I said, you can form a triad on any note. Here are all the triads that you can make from the notes of C major:
Notice the roman numerals. Musicians use roman numerals to identify the chords of a key. It’s useful because they are independant of the key itself, and so easier to transpose into other keys. The triad built on the first note of the key (C in this example) is given the number I, and so on. Beyond the simple use of roman numerals thare are many alternative and somewhat conflicting conventions. The one I’ve chosen is to use upper case roman numerals to indicate the major triads, and lower case to indicate the minor ones. The circle superscript of chord vii indicates it is a diminished triad, made up of two consecutive minor thirds.
This pattern of ascending chords: major, minor, minor, major, major, minor, diminished is common to all major keys.
Minor keys are different. The natural minor scale shares the same notes as the major scale a minor third above, so we have the same sequence of chords as a major key, but starting six chords in (or three chords back).
The harmonic minor scale has a raised seventh. As the name suggests it is used for harmonizing melodies in minor keys:
You can see that compared to the harmonic minor, the additional raised sixth makes ii minor instead of diminished, IV major instead of minor, and vi diminished instead of major.
Apart from roman numerals, an alternative scheme which you should also be aware of is that each note of a scale and hence each chord rooted on that note and each key based on that note has a name. Here they are:
- I Tonic
- the “home” of the key
- II Supertonic
- The note/chord/key above the tonic
- III Mediant
- Half way between Tonic and Dominant
- IV Subdominant
- The same interval below the tonic as the Dominant is above (a perfect fifth)
- V Dominant
- The most important note/chord/key after the tonic
- VI Submediant
- As far below the tonic as the mediant is above
- VII Leading Note
- This note “leads” to the Tonic. It can be called Subtonic by analogy with Supertonic, but only if it is rooted on the minor seventh above the tonic (natural minor scale.)
A Slightly Non-Standard Application
Here’s an application of code generation that doesn’t fit the standard Enterprise Application mold: here I’m using code generation to build a class heirarchy for abstract syntax trees.
The idea is pretty simple. An abstract syntax tree is a tree containing the results of parsing a programming language. Nodes of the tree could be function calls, operator application or control flow constructs, and the leaves of the tree would be comstants and variables. Of course these trees cannot be constructed arbitrarily, it doesn’t often make sense to add an integer to a record for example, so the constructors for the nodes need to declare (or, in an untyped language, type-check) their arguments. Each node will furthermore need accessors and probably an accept()
method for the Visitor pattern, so that is a substantial amount of boiler-plate code for something that is easily describeable at a very high level.
Here’s a snapshot of the AST definition from F♮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
abstract Expr() Expr Char(scalar value) # [id] Expr Number(scalar value) Expr WildCard() abstract Expr Symbol() # [id] Symbol DollarName(scalar name) Symbol Name(scalar name) Symbol TypeName(scalar name) abstract Expr List() List Cons(Expr car, List cdr) List Nil() abstract Expr Sequence() Sequence Statement(Expr car, Sequence cdr) Sequence EmptySequence() abstract Expr Constructors() Constructors Constructor(Expr car, Constructors cdr) Constructors EmptyConstructor() Expr Typedef(Expr type, Constructors constructors) Expr Type(Expr type) Expr Apply(Expr op, List args) Expr End(Expr expr) Expr Define(Symbol name, Expr value) Expr Declare(Name name) Expr Lambda(List args, Sequence body) Expr LambdaSwitch(List lambdas) Expr Try(Apply attempt, LambdaSwitch catches) Expr Throw(List exprs) Expr String(scalar value) Expr LambdaLet(List formals, List actuals, Sequence Body) Expr LambdaSwitchLet(List actuals, List lambdas) # these next are rewritten by the RewritingVisitor Expr And(Expr a, Expr b) Expr Or(Expr a, Expr b) Expr Not(Expr) Expr If(Expr test, Sequence consequent, Sequence alternative) Expr Then(Expr first, Expr second) Expr Pairs(List list) Expr Switch(List exprs, LambdaSwitch lambdas) |
This is a high level but complete description of the required classes, all of the details of implementation are merely repetitive, which makes it a perfect candidate for code generation.
So, given this description, and a couple of command-line options to cover class prefixes etc., my treebuilder (tbd) will generate all of the classes required. For example, here’s the generated class definition for Expr If(Expr test, Sequence consequent, Sequence alternative)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# WARNING: Automatically generated from ast.tb on Sat May 29 18:51:18 2010 GMT. DO NOT EDIT! YOUR EDITS WILL BE LOST! package Lang::FN::AST::If; use strict; use warnings; use base qw(Lang::FN::AST::Expr); =head1 NAME Lang::FN::AST::If - a tree node, a type of Lang::FN::AST::Expr. =head1 SYNOPSIS use Lang::FN::AST::API; my $if = If($test, $consequent, $alternative); my $test = $if->getTest(); my $consequent = $if->getConsequent(); my $alternative = $if->getAlternative(); $if->accept(new Lang::FN::AST::Visitor()); =head1 METHODS Individual Lang::FN::AST::If methods are described below. =head2 new my $If = new Lang::FN::AST::If($test, $consequent, $alternative); or better: use Lang::FN::AST::API; my $If = If($test, $consequent, $alternative); The C<If()> subroutine is provided by C<Lang::FN::AST::API> to make it easier to create instances of C<Lang::FN::AST::If>. In either case C<$test> must be a C<Lang::FN::AST::Expr>, C<$consequent> must be a C<Lang::FN::AST::Sequence> and C<$alternative> must be a C<Lang::FN::AST::Sequence>. =cut sub new { die "wrong number of arguments to Lang::FN::AST::If::new(\$test, \$consequent, \$alternative)\n" unless @_ == 4; my ($class, $test, $consequent, $alternative) = @_; die "argument test to If is not a Lang::FN::AST::Expr, it is a ", (ref($test) || 'scalar'), "\n" unless defined($test) && $test->isa('Lang::FN::AST::Expr'); die "argument consequent to If is not a Lang::FN::AST::Sequence, it is a ", (ref($consequent) || 'scalar'), "\n" unless defined($consequent) && $consequent->isa('Lang::FN::AST::Sequence'); die "argument alternative to If is not a Lang::FN::AST::Sequence, it is a ", (ref($alternative) || 'scalar'), "\n" unless defined($alternative) && $alternative->isa('Lang::FN::AST::Sequence'); return bless { _test => $test, _consequent => $consequent, _alternative => $alternative, }, $class; } =head2 getTest my $test = $If->getTest(); gets the value of the C<test> field, which is a C<Lang::FN::AST::Expr>. =cut sub getTest { my ($self) = @_; return $self->{_test}; } =head2 getConsequent my $consequent = $If->getConsequent(); gets the value of the C<consequent> field, which is a C<Lang::FN::AST::Sequence>. =cut sub getConsequent { my ($self) = @_; return $self->{_consequent}; } =head2 getAlternative my $alternative = $If->getAlternative(); gets the value of the C<alternative> field, which is a C<Lang::FN::AST::Sequence>. =cut sub getAlternative { my ($self) = @_; return $self->{_alternative}; } =head2 accept $If->accept(new Lang::FN::AST::Visitor()); Accepts a C<Lang::FN::AST::Visitor> and calls its C<visitIf> method, passing it C<$self> (i.e. a C<Lang::FN::AST::If>) as argument. =cut sub accept { my ($self, $visitor) = @_; return $visitor->visitIf($self); } =head2 eq if ($If->eq($other)) { ... } Returns true if the argument C<$other> is recursively equal to C<$self>. =cut sub eq { my ($self, $other) = @_; if (ref($other) eq 'Lang::FN::AST::If') { return 1 && $self->getTest->eq($other->getTest) && $self->getConsequent->eq($other->getConsequent) && $self->getAlternative->eq($other->getAlternative) } else { return 0; } } =head2 isIf Returns true. =cut sub isIf { 1 } =head1 AUTHOR Automatically generated from ast.tb on Sat May 29 18:51:18 2010 GMT. =head1 SEE ALSO The other generated node classes: L<Lang::FN::AST::And>, L<Lang::FN::AST::Apply>, L<Lang::FN::AST::Char>, L<Lang::FN::AST::Cons>, L<Lang::FN::AST::Constructor>, L<Lang::FN::AST::Constructors>, L<Lang::FN::AST::Declare>, L<Lang::FN::AST::Define>, L<Lang::FN::AST::DollarName>, L<Lang::FN::AST::EmptyConstructor>, L<Lang::FN::AST::EmptySequence>, L<Lang::FN::AST::End>, L<Lang::FN::AST::Expr>, L<Lang::FN::AST::Lambda>, L<Lang::FN::AST::LambdaLet>, L<Lang::FN::AST::LambdaSwitch>, L<Lang::FN::AST::LambdaSwitchLet>, L<Lang::FN::AST::List>, L<Lang::FN::AST::Name>, L<Lang::FN::AST::Nil>, L<Lang::FN::AST::Not>, L<Lang::FN::AST::Number>, L<Lang::FN::AST::Or>, L<Lang::FN::AST::Pairs>, L<Lang::FN::AST::Sequence>, L<Lang::FN::AST::Statement>, L<Lang::FN::AST::String>, L<Lang::FN::AST::Switch>, L<Lang::FN::AST::Symbol>, L<Lang::FN::AST::Then>, L<Lang::FN::AST::Throw>, L<Lang::FN::AST::Try>, L<Lang::FN::AST::Type>, L<Lang::FN::AST::TypeName>, L<Lang::FN::AST::Typedef>, L<Lang::FN::AST::WildCard>, an abstract visitor that can be subclassed to walk them: L<Lang::FN::AST::Visitor>, and the API that makes them available: L<Lang::FN::AST::API>. =cut 1; |
The point of all this is to be able to use these constructors directly in a yapp or similar grammar file. Here’s a snippet of these generated classes in action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
... expr : int | char | string ... | END expr %prec L_5 { End($_[2]) } | expr '=' expr { Apply(Name('='), Cons($_[1], Cons($_[3], Nil))) } | expr THEN expr { Then($_[1], $_[3]) } | expr '&&' expr { And($_[1], $_[3]) } | expr '||' expr { Or($_[1], $_[3]) } | '!' expr { Not($_[2]) } | '(' expr ')' { $_[2] } ... ; ifexpr : IF '(' expr ')' block ELSE alternative { If($_[3], $_[5], $_[7]) } ; alternative : block | ifexpr ; |
One of the visitors over the resulting tree is a rewriting visitor that converts some abstract syntax to simpler forms so that there are fewer constructs that need to be dealt with later on. Here’s the visitor routine that converts If
abstract syntax to a function call:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
sub visitIf { my ($self, $if) = @_; my $test = $if->getTest->accept($self); my $consequent = $if->getConsequent->accept($self); my $alternative = $if->getAlternative->accept($self); Apply( LambdaSwitch( Cons( Lambda( Cons(TypeName('True'), Nil), Statement( End($consequent->getCar), $consequent->getCdr ) ), Cons( Lambda( Cons(TypeName('False'), Nil), $alternative ), Nil ) ) ), Cons($if->getTest->accept($self), Nil) ) } |