File Coverage

blib/lib/Music/ModalFunction.pm
Criterion Covered Total %
statement 79 79 100.0
branch 38 52 73.0
condition 2 6 33.3
subroutine 16 16 100.0
pod 3 3 100.0
total 138 156 88.4


line stmt bran cond sub pod time code
1             package Music::ModalFunction;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Query for modal and scalar musical functions
5              
6             our $VERSION = '0.0504';
7              
8 2     2   686701 use strictures 2;
  2         4137  
  2         90  
9 2     2   2360 use AI::Prolog ();
  2         736196  
  2         87  
10 2     2   19 use Carp qw(croak);
  2         10  
  2         145  
11 2     2   1085 use MIDI::Util qw(midi_format);
  2         57530  
  2         145  
12 2     2   937 use Moo;
  2         13153  
  2         8  
13 2     2   4304 use Music::Note ();
  2         3979  
  2         70  
14 2     2   1091 use Music::Scales qw(get_scale_notes);
  2         11022  
  2         196  
15 2     2   1113 use namespace::clean;
  2         34049  
  2         22  
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 [qw(verbose use_scales hash_results)] => (
23             is => 'ro',
24             isa => sub { croak "$_[0] is not a boolean" unless $_[0] =~ /^[01]$/ },
25             default => sub { 0 },
26             );
27              
28             has _chord_key => (
29             is => 'ro',
30             default => sub { [qw(method chord_note chord key_note key key_function key_roman)] },
31             );
32              
33             has _pivot_chord_keys => (
34             is => 'ro',
35             default => sub { [qw(method chord_note chord mode_note mode mode_function mode_roman key_note key key_function key_roman)] },
36             );
37              
38             has _roman_key => (
39             is => 'ro',
40             default => sub { [qw(method mode mode_roman key key_roman)] },
41             );
42              
43             has [qw(_modes _scales _database _prolog)] => (
44             is => 'lazy',
45             );
46              
47             sub _build__modes {
48             return {
49 10     10   7593 ionian => [
50             { chord => 'maj', roman => 'r_I', function => 'tonic' },
51             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
52             { chord => 'min', roman => 'r_iii', function => 'mediant' },
53             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
54             { chord => 'maj', roman => 'r_V', function => 'dominant' },
55             { chord => 'min', roman => 'r_vi', function => 'submediant' },
56             { chord => 'dim', roman => 'r_vii', function => 'leading_tone' },
57             ],
58             dorian => [
59             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
60             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
61             { chord => 'maj', roman => 'r_III', function => 'mediant' },
62             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
63             { chord => 'min', roman => 'r_v', function => 'dominant' },
64             { chord => 'dim', roman => 'r_vi', function => 'submediant' },
65             { chord => 'maj', roman => 'r_VII', function => 'subtonic' },
66             ],
67             phrygian => [
68             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
69             { chord => 'maj', roman => 'r_II', function => 'supertonic' },
70             { chord => 'maj', roman => 'r_III', function => 'mediant' },
71             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
72             { chord => 'dim', roman => 'r_v', function => 'dominant' },
73             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
74             { chord => 'min', roman => 'r_vii', function => 'subtonic' },
75             ],
76             lydian => [
77             { chord => 'maj', roman => 'r_I', function => 'tonic' },
78             { chord => 'maj', roman => 'r_II', function => 'supertonic' },
79             { chord => 'min', roman => 'r_iii', function => 'mediant' },
80             { chord => 'dim', roman => 'r_iv', function => 'subdominant' },
81             { chord => 'maj', roman => 'r_V', function => 'dominant' },
82             { chord => 'min', roman => 'r_vi', function => 'submediant' },
83             { chord => 'min', roman => 'r_vii', function => 'leading_tone' },
84             ],
85             mixolydian => [
86             { chord => 'maj', roman => 'r_I', function => 'tonic' },
87             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
88             { chord => 'dim', roman => 'r_iii', function => 'mediant' },
89             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
90             { chord => 'min', roman => 'r_v', function => 'dominant' },
91             { chord => 'min', roman => 'r_vi', function => 'submediant' },
92             { chord => 'maj', roman => 'r_VII', function => 'subtonic' },
93             ],
94             aeolian => [
95             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
96             { chord => 'dim', roman => 'r_ii', function => 'supertonic' },
97             { chord => 'maj', roman => 'r_III', function => 'mediant' },
98             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
99             { chord => 'min', roman => 'r_v', function => 'dominant' },
100             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
101             { chord => 'maj', roman => 'r_VII', function => 'subtonic' },
102             ],
103             locrian => [
104             { chord => 'dim', roman => 'r_i', function => 'parallel_minor' },
105             { chord => 'maj', roman => 'r_II', function => 'supertonic' },
106             { chord => 'min', roman => 'r_iii', function => 'mediant' },
107             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
108             { chord => 'maj', roman => 'r_V', function => 'dominant' },
109             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
110             { chord => 'min', roman => 'r_vii', function => 'subtonic' },
111             ],
112             }
113             }
114              
115             sub _build__scales {
116             return {
117 10     10   1012 harmonic_minor => [
118             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
119             { chord => 'dim', roman => 'r_ii', function => 'supertonic' },
120             { chord => 'aug', roman => 'r_III', function => 'mediant' },
121             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
122             { chord => 'maj', roman => 'r_V', function => 'dominant' },
123             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
124             { chord => 'dim', roman => 'r_vii', function => 'subtonic' },
125             ],
126             melodic_minor => [
127             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
128             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
129             { chord => 'aug', roman => 'r_III', function => 'mediant' },
130             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
131             { chord => 'maj', roman => 'r_V', function => 'dominant' },
132             { chord => 'dim', roman => 'r_vi', function => 'submediant' },
133             { chord => 'dim', roman => 'r_vii', function => 'subtonic' },
134             ],
135             pentatonic => [
136             { chord => 'maj', roman => 'r_I', function => 'tonic' },
137             { chord => 'min', roman => 'r_ii', function => 'supertonic' },
138             { chord => 'min', roman => 'r_iii', function => 'mediant' },
139             { chord => 'maj', roman => 'r_IV', function => 'subdominant' },
140             { chord => 'min', roman => 'r_vi', function => 'submediant' },
141             ],
142             pentatonic_minor => [
143             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
144             { chord => 'dim', roman => 'r_ii', function => 'supertonic' },
145             { chord => 'min', roman => 'r_iv', function => 'subdominant' },
146             { chord => 'min', roman => 'r_v', function => 'dominant' },
147             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
148             ],
149             blues => [
150             { chord => 'min', roman => 'r_i', function => 'parallel_minor' },
151             { chord => 'maj', roman => 'r_III', function => 'supertonic' },
152             { chord => 'sus4', roman => 'r_IV', function => 'subdominant' },
153             { chord => 'maj', roman => 'r_bV', function => 'flat5' },
154             { chord => 'min', roman => 'r_v', function => 'dominant' },
155             { chord => 'sus4', roman => 'r_VII', function => 'leading_tone' },
156             ],
157             diminished => [
158             { chord => 'maj', roman => 'r_I', function => 'tonic' },
159             { chord => 'dim', roman => 'r_bii', function => 'flat2' },
160             { chord => 'maj', roman => 'r_bIII', function => 'flat3' },
161             { chord => 'dim', roman => 'r_iii', function => 'mediant' },
162             { chord => 'maj', roman => 'r_bV', function => 'flat5' },
163             { chord => 'dim', roman => 'r_v', function => 'dominant' },
164             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
165             { chord => 'dim', roman => 'r_vii', function => 'leading_tone' },
166             ],
167             augmented => [
168             { chord => 'aug', roman => 'r_I', function => 'tonic' },
169             { chord => 'aug', roman => 'r_bIII', function => 'flat3' },
170             { chord => 'maj', roman => 'r_III', function => 'mediant' },
171             { chord => 'maj', roman => 'r_V', function => 'dominant' },
172             { chord => 'maj', roman => 'r_VI', function => 'submediant' },
173             { chord => 'aug', roman => 'r_VII', function => 'leading_tone' },
174             ],
175             }
176             }
177              
178             sub _build__database {
179 19     19   267 my ($self) = @_;
180              
181             # consider every note
182 19         160 my @chromatic = get_scale_notes('c', 'chromatic', 0, 'b');
183 19         4208 my $database = '';
184              
185 19 100       763 my $list = $self->use_scales ? $self->_scales : $self->_modes;
186              
187             # build a prolog fact for each base note
188 19         105 for my $base (@chromatic) {
189 228         1061 my ($mode_base) = map { lc } midi_format($base);
  228         8654  
190              
191             # consider each mode or scale properties
192 228         1671 for my $item (sort keys %$list) {
193             # get the notes of the base note mode or scale
194 1596         5709 my @notes = get_scale_notes($base, $item);
195             # warn "Basics: $base $item [@notes]\n" if $self->verbose;
196              
197 1596         354563 my @pitches; # notes suitable for the prolog database
198              
199             # convert the notes to flatted, lower-case
200 1596         3621 for my $note (@notes) {
201 10632         33452 my $n = Music::Note->new($note, 'isobase');
202 10632 100       378155 $n->en_eq('flat') if $note =~ /#/;
203 10632         61297 push @pitches, map { lc } midi_format($n->format('isobase'));
  10632         581303  
204             }
205              
206 1596         3033 my $i = 0; # increment
207              
208 1596         3461 for my $pitch (@pitches) {
209             # get the properties of the given mode or scale
210 10632         16924 my ($chord, $function, $roman);
211 10632 100       30031 if ($self->use_scales) {
212 4752         101599 $chord = $self->_scales->{$item}[$i]{chord};
213 4752         116909 $function = $self->_scales->{$item}[$i]{function};
214 4752         112874 $roman = $self->_scales->{$item}[$i]{roman};
215             }
216             else {
217 5880         139942 $chord = $self->_modes->{$item}[$i]{chord};
218 5880         153728 $function = $self->_modes->{$item}[$i]{function};
219 5880         144153 $roman = $self->_modes->{$item}[$i]{roman};
220             }
221              
222             # append to the database of facts
223 10632 50 33     116481 $database .= "chord_key($pitch, $chord, $mode_base, $item, $function, $roman).\n"
      33        
224             if $chord && $function && $roman;
225              
226 10632         27023 $i++;
227             }
228             }
229             }
230             # append the prolog rules
231 19         74 $database .= <<'RULES';
232             pivot_chord_keys(ChordNote, Chord, Key1Note, Key1, Key1Function, Key1Roman, Key2Note, Key2, Key2Function, Key2Roman) :-
233             % bind the chord to the function of the first key
234             chord_key(ChordNote, Chord, Key1Note, Key1, Key1Function, Key1Roman),
235             % bind the chord to the function of the second key
236             chord_key(ChordNote, Chord, Key2Note, Key2, Key2Function, Key2Roman),
237             % the functions cannot be the same
238             Key1Function \= Key2Function.
239              
240             roman_key(Mode, ModeRoman, Key, KeyRoman) :-
241             chord_key(_, _, _, Mode, ModeFunction, ModeRoman),
242             chord_key(_, _, _, Key, KeyFunction, KeyRoman),
243             ModeFunction \= KeyFunction.
244             RULES
245             # warn "Database: $database\n" if $self->verbose;
246              
247 19         14591 return $database;
248             }
249              
250             sub _build__prolog {
251 18     18   288 my ($self) = @_;
252 18         540 return AI::Prolog->new($self->_database);
253             }
254              
255              
256             sub chord_key {
257 6     6 1 31188 my ($self) = @_;
258 6 50       186 my $query = sprintf 'chord_key(%s, %s, %s, %s, %s, %s).',
    50          
    50          
    50          
    100          
    50          
259             defined $self->chord_note ? $self->chord_note : 'ChordNote',
260             defined $self->chord ? $self->chord : 'Chord',
261             defined $self->key_note ? $self->key_note : 'KeyNote',
262             defined $self->key ? $self->key : 'Key',
263             defined $self->key_function ? $self->key_function : 'KeyFunction',
264             defined $self->key_roman ? $self->key_roman : 'KeyRoman';
265 6         32 return $self->_querydb('chord_key', $query);
266             }
267              
268              
269             sub pivot_chord_keys {
270 11     11 1 76035 my ($self) = @_;
271 11 100       850 my $query = sprintf 'pivot_chord_keys(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s).',
    100          
    100          
    100          
    50          
    50          
    100          
    100          
    100          
    50          
272             defined $self->chord_note ? $self->chord_note : 'ChordNote',
273             defined $self->chord ? $self->chord : 'Chord',
274             defined $self->mode_note ? $self->mode_note : 'ModeNote',
275             defined $self->mode ? $self->mode : 'Mode',
276             defined $self->mode_function ? $self->mode_function : 'ModeFunction',
277             defined $self->mode_roman ? $self->mode_roman : 'ModeRoman',
278             defined $self->key_note ? $self->key_note : 'KeyNote',
279             defined $self->key ? $self->key : 'Key',
280             defined $self->key_function ? $self->key_function : 'KeyFunction',
281             defined $self->key_roman ? $self->key_roman : 'KeyRoman';
282 11         72 return $self->_querydb('pivot_chord_keys', $query);
283             }
284              
285              
286             sub roman_key {
287 1     1 1 683 my ($self) = @_;
288 1 50       48 my $query = sprintf 'roman_key(%s, %s, %s, %s).',
    50          
    50          
    50          
289             defined $self->mode ? $self->mode : 'Mode',
290             defined $self->mode_roman ? $self->mode_roman : 'ModeRoman',
291             defined $self->key ? $self->key : 'Key',
292             defined $self->key_roman ? $self->key_roman : 'KeyRoman';
293 1         15 return $self->_querydb('roman_key', $query);
294             }
295              
296             sub _querydb {
297 18     18   68 my ($self, $method, $query) = @_;
298              
299 18 50       109 warn "$method query: $query\n" if $self->verbose;
300              
301 18         3016 $self->_prolog->query($query);
302              
303 18         46158226 my $attr = '_' . $method;
304              
305 18         69 my @return;
306              
307 18         1099 while (my $result = $self->_prolog->results) {
308             #warn __PACKAGE__,' L',__LINE__,' ',,"R: @$result\n";
309 181 100       9219904 if ($self->hash_results) {
310 17         49 my %result;
311 17         55 @result{ @{ $self->$attr } } = @$result;
  17         299  
312 17         1057 push @return, \%result;
313             }
314             else {
315 164         8131 push @return, $result;
316             }
317             }
318              
319 18         4590366 return \@return;
320             }
321              
322             1;
323              
324             __END__