File Coverage

blib/lib/Music/ModalFunction.pm
Criterion Covered Total %
statement 69 69 100.0
branch 29 48 60.4
condition n/a
subroutine 15 15 100.0
pod 3 3 100.0
total 116 135 85.9


line stmt bran cond sub pod time code
1             package Music::ModalFunction;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Inspect musical modal functions
5              
6             our $VERSION = '0.0315';
7              
8 1     1   1410 use Moo;
  1         12141  
  1         4  
9 1     1   2009 use strictures 2;
  1         1666  
  1         42  
10 1     1   675 use AI::Prolog ();
  1         301347  
  1         31  
11 1     1   9 use Carp qw(croak);
  1         3  
  1         58  
12 1     1   523 use MIDI::Util qw(midi_format);
  1         38522  
  1         59  
13 1     1   432 use Music::Note ();
  1         1667  
  1         27  
14 1     1   456 use Music::Scales qw(get_scale_notes);
  1         5210  
  1         64  
15 1     1   471 use namespace::clean;
  1         11224  
  1         6  
16              
17              
18             has [qw(chord_note chord mode_note mode mode_function mode_roman key_note key key_function key_roman)] => (
19             is => 'ro',
20             );
21              
22             has verbose => (
23             is => 'ro',
24             isa => sub { croak "$_[0] is not a boolean" unless $_[0] =~ /^[01]$/ },
25             default => sub { 0 },
26             );
27              
28             has _modes => (
29             is => 'lazy',
30             );
31             sub _build__modes {
32             return {
33 6     6   5890 ionian => [
34             { chord => 'maj', roman => 'r_I', function => 'tonic' },
35             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
36             { chord => 'min', roman => 'r_iii', function => 'mediant' },
37             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
38             { chord => 'maj', roman => 'r_V', function => 'dominant' },
39             { chord => 'min', roman => 'r_vi', function => 'submediant' },
40             { chord => 'dim', roman => 'r_vii', function => 'leading_tone' }
41             ],
42             dorian => [
43             { chord => 'min', roman => 'r_i', function => 'tonic' },
44             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
45             { chord => 'maj', roman => 'r_III', function => 'mediant' },
46             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
47             { chord => 'min', roman => 'r_v', function => 'dominant' },
48             { chord => 'dim', roman => 'r_vi', function => 'submediant' },
49             { chord => 'maj', roman => 'r_VII', function => 'subtonic' }
50             ],
51             phrygian => [
52             { chord => 'min', roman => 'r_i', function => 'tonic' },
53             { chord => 'maj', roman => 'r_II', function => 'supertonic' },
54             { chord => 'maj', roman => 'r_III', function => 'mediant' },
55             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
56             { chord => 'dim', roman => 'r_v', function => 'dominant' },
57             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
58             { chord => 'min', roman => 'r_vii', function => 'subtonic' }
59             ],
60             lydian => [
61             { chord => 'maj', roman => 'r_I', function => 'tonic' },
62             { chord => 'maj', roman => 'r_II', function => 'supertonic' },
63             { chord => 'min', roman => 'r_iii', function => 'mediant' },
64             { chord => 'dim', roman => 'r_iv', function => 'subdominant' },
65             { chord => 'maj', roman => 'r_V', function => 'dominant' },
66             { chord => 'min', roman => 'r_vi', function => 'submediant' },
67             { chord => 'min', roman => 'r_vii', function => 'leading_tone' }
68             ],
69             mixolydian => [
70             { chord => 'maj', roman => 'r_I', function => 'tonic' },
71             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
72             { chord => 'dim', roman => 'r_iii', function => 'mediant' },
73             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
74             { chord => 'min', roman => 'r_v', function => 'dominant' },
75             { chord => 'min', roman => 'r_vi', function => 'submediant' },
76             { chord => 'maj', roman => 'r_VII', function => 'subtonic' }
77             ],
78             aeolian => [
79             { chord => 'min', roman => 'r_i', function => 'tonic' },
80             { chord => 'dim', roman => 'r_ii', function => 'supertonic' },
81             { chord => 'maj', roman => 'r_III', function => 'mediant' },
82             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
83             { chord => 'min', roman => 'r_v', function => 'dominant' },
84             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
85             { chord => 'maj', roman => 'r_VII', function => 'subtonic' }
86             ],
87             locrian => [
88             { chord => 'dim', roman => 'r_i', function => 'tonic' },
89             { chord => 'maj', roman => 'r_II', function => 'supertonic' },
90             { chord => 'min', roman => 'r_iii', function => 'mediant' },
91             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
92             { chord => 'maj', roman => 'r_V', function => 'dominant' },
93             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
94             { chord => 'min', roman => 'r_vii', function => 'subtonic' }
95             ]
96             }
97             }
98              
99             has _database => (
100             is => 'lazy',
101             );
102             sub _build__database {
103 6     6   67 my ($self) = @_;
104              
105             # consider every note
106 6         52 my @chromatic = get_scale_notes('c', 'chromatic', 0, 'b');
107 6         1072 my $database = '';
108              
109             # build a prolog fact for each base note
110 6         19 for my $base (@chromatic) {
111 72         211 my ($mode_base) = map { lc } midi_format($base);
  72         1769  
112              
113             # consider each mode's properties
114 72         127 for my $mode (sort keys %{ $self->_modes }) {
  72         1201  
115             # get the 7 notes of the base note mode
116 504         1786 my @notes = get_scale_notes($base, $mode);
117 504 50       87115 warn "Basics: $base $mode [@notes]\n" if $self->verbose;
118              
119 504         796 my @pitches; # notes suitable for the prolog database
120              
121             # convert the notes to flatted, lower-case
122 504         792 for my $note (@notes) {
123 3528         8838 my $n = Music::Note->new($note, 'isobase');
124 3528 100       100374 $n->en_eq('flat') if $note =~ /#/;
125 3528         10832 push @pitches, map { lc } midi_format($n->format('isobase'));
  3528         147063  
126             }
127              
128 504         856 my $i = 0; # increment for each of 7 diatonic modes
129              
130 504         806 for my $pitch (@pitches) {
131             # get the properties of the given mode
132 3528         54710 my $chord = $self->_modes->{$mode}[$i]{chord};
133 3528         69604 my $function = $self->_modes->{$mode}[$i]{function};
134 3528         68964 my $roman = $self->_modes->{$mode}[$i]{roman};
135              
136             # append to the database of facts
137 3528         26401 $database .= "chord_key($pitch, $chord, $mode_base, $mode, $function, $roman).\n";
138              
139 3528         6716 $i++;
140             }
141             }
142             }
143             # append the prolog rules
144 6         23 $database .= <<'RULES';
145             % Can a chord in one key function in a second?
146             pivot_chord_keys(ChordNote, Chord, Key1Note, Key1, Key1Function, Key1Roman, Key2Note, Key2, Key2Function, Key2Roman) :-
147             % bind the chord to the function of the first key
148             chord_key(ChordNote, Chord, Key1Note, Key1, Key1Function, Key1Roman),
149             % bind the chord to the function of the second key
150             chord_key(ChordNote, Chord, Key2Note, Key2, Key2Function, Key2Roman),
151             % the functions cannot be the same
152             Key1Function \= Key2Function.
153              
154             % TODO
155             roman_key(Mode, ModeRoman, Key, KeyRoman) :-
156             chord_key(_, _, _, Mode, ModeFunction, ModeRoman),
157             chord_key(_, _, _, Key, KeyFunction, KeyRoman),
158             ModeFunction \= KeyFunction.
159             RULES
160 6 50       33 warn "Database: $database\n" if $self->verbose;
161              
162 6         2162 return $database;
163             }
164              
165             has _prolog => (
166             is => 'lazy',
167             );
168             sub _build__prolog {
169 5     5   65 my ($self) = @_;
170 5         92 return AI::Prolog->new($self->_database);
171             }
172              
173              
174             sub chord_key {
175 2     2 1 4460 my ($self) = @_;
176 2 50       46 my $query = sprintf 'chord_key(%s, %s, %s, %s, %s, %s).',
    50          
    50          
    50          
    100          
    50          
177             defined $self->chord_note ? $self->chord_note : 'ChordNote',
178             defined $self->chord ? $self->chord : 'Chord',
179             defined $self->key_note ? $self->key_note : 'KeyNote',
180             defined $self->key ? $self->key : 'Key',
181             defined $self->key_function ? $self->key_function : 'KeyFunction',
182             defined $self->key_roman ? $self->key_roman : 'KeyRoman';
183 2         8 return $self->_querydb($query);
184             }
185              
186              
187             sub pivot_chord_keys {
188 2     2 1 4789 my ($self) = @_;
189 2 50       160 my $query = sprintf 'pivot_chord_keys(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s).',
    50          
    100          
    50          
    50          
    50          
    50          
    100          
    100          
    50          
190             defined $self->chord_note ? $self->chord_note : 'ChordNote',
191             defined $self->chord ? $self->chord : 'Chord',
192             defined $self->mode_note ? $self->mode_note : 'ModeNote',
193             defined $self->mode ? $self->mode : 'Mode',
194             defined $self->mode_function ? $self->mode_function : 'ModeFunction',
195             defined $self->mode_roman ? $self->mode_roman : 'ModeRoman',
196             defined $self->key_note ? $self->key_note : 'KeyNote',
197             defined $self->key ? $self->key : 'Key',
198             defined $self->key_function ? $self->key_function : 'KeyFunction',
199             defined $self->key_roman ? $self->key_roman : 'KeyRoman';
200 2         12 return $self->_querydb($query);
201             }
202              
203              
204             sub roman_key {
205 1     1 1 449 my ($self) = @_;
206 1 50       29 my $query = sprintf 'roman_key(%s, %s, %s, %s).',
    50          
    50          
    50          
207             defined $self->mode ? $self->mode : 'Mode',
208             defined $self->mode_roman ? $self->mode_roman : 'ModeRoman',
209             defined $self->key ? $self->key : 'Key',
210             defined $self->key_roman ? $self->key_roman : 'KeyRoman';
211 1         4 return $self->_querydb($query);
212             }
213              
214             sub _querydb {
215 5     5   17 my ($self, $query) = @_;
216              
217 5 50       23 warn "Query: $query\n" if $self->verbose;
218              
219 5         152 $self->_prolog->query($query);
220              
221 5         12422880 my @return;
222              
223 5         232 while (my $result = $self->_prolog->results) {
224 71         2816634 push @return, $result;
225             }
226              
227 5         492879 return \@return;
228             }
229              
230             1;
231              
232             __END__