File Coverage

blib/lib/Music/Chord/Namer.pm
Criterion Covered Total %
statement 96 101 95.0
branch 37 46 80.4
condition 108 163 66.2
subroutine 10 12 83.3
pod 1 6 16.6
total 252 328 76.8


line stmt bran cond sub pod time code
1             package Music::Chord::Namer;
2              
3 1     1   51623 use 5.008007;
  1         4  
  1         1662  
4 1     1   8 use strict;
  1         1  
  1         41  
5 1     1   5 use warnings;
  1         7  
  1         45  
6 1     1   2876 use subs qw/jws jwn/;
  1         23  
  1         6  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             # Items to export into callers namespace by default. Note: do not export
13             # names by default without a very good reason. Use EXPORT_OK instead.
14             # Do not simply export all your public functions/methods/constants.
15              
16             # This allows declaration use Music::Chord::Namer ':all';
17             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
18             # will save memory.
19             our %EXPORT_TAGS = ( 'all' => [ qw(
20             chordname
21             ) ] );
22              
23             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24              
25             our @EXPORT = qw(
26            
27             );
28              
29             our $VERSION = '0.01';
30              
31              
32             our %NOTES;
33             our $NAME;
34             our $D;
35              
36              
37             sub chordname { # the sub that guesses the name of the chord
38              
39             # convert note names to numbers
40 8     8 1 5377 my %notevalues = ('C'=>0,'C#'=>1,'Db'=>1,'D'=>2,'D#'=>3,'Eb'=>3,'E'=>4,'F'=>5,
41             'F#'=>6,'Gb'=>6,'G'=>7,'G#'=>8,'Ab'=>8,'A'=>9,'A#'=>10,'Bb'=>10,'B'=>11);
42             # convert note numbers back to names
43 8         33 my @value2note = ('C','C#','D','D#','E','F','F#','G','G#','A','A#','B');
44            
45 8         13 my @notes = (); # store notes here...
46            
47 8 50       29 if(@_ > 1){ # if the notes are supplied as a list
    50          
48 0         0 @notes = @_; # ok
49             }
50             elsif($_[0]) { # or as a string
51 8         36 @notes = split(/\s+/, $_[0]); # deal with it!
52             }
53             else {
54 0         0 return; # no notes??
55             }
56            
57 8         14 my @notenumbers = (); # store the corresponding numbers here
58 8         19 foreach my $note(@notes){
59 38 50       99 die "Bad note \"$note\"!" unless defined $notevalues{$note};
60 38         60 my $notenumber = $notevalues{$note};
61             # make sure that it's a higher number than that of the note that preceeded it...
62 38 100       71 if(defined $notenumbers[$#notenumbers]){
63 30         63 while($notenumber < $notenumbers[$#notenumbers]){ $notenumber += 12; }
  9         19  
64             }
65             # add it to the list
66 38         62 push @notenumbers, $notenumber;
67             }
68            
69            
70              
71             # Naming
72              
73             # We need to make some decisions about what to call it a chord...
74             # Lets assume we know no better and we're going to try every possible chord
75             # and see which name is the shortest!
76              
77             # Lets go through every probable root note first... one of the two bass notes must
78             # be the 1, m3, 3, 5 or m7 of the chord. No cheating!
79              
80             # We can then work out the names of these 10 chords...
81              
82             # 1) The bass note is 1
83             # 2) The bass note is m3
84             # 3) The bass note is 3
85             # 4) The bass note is 5
86             # 5) The bass note is m7
87             # 6) The bass note is separate, the next note is 1
88             # 7) The bass note is separate, the next note is m3
89             # 8) The bass note is separate, the next note is 3
90             # 9) The bass note is separate, the next note is 5
91             # 10) The bass note is separate, the next note is m7
92              
93             # notes set to bass note being a certain chord member
94            
95 8         12 my @inversions = ();
96             # name, notes, split, comment
97             # the name depends on what we're saying the bass note is... it could be the root, minor or major 3rd
98             # 5th or minor 7th.
99 38         102 push @inversions,
100 38         83 {name => $value2note[($notevalues{$notes[0]}) % 12], notes => [map { $_ - $notenumbers[0] } @notenumbers], split => '', comment => 'bass 1'},
101 38         98 {name => $value2note[($notevalues{$notes[0]} - 3) % 12], notes => [map { $_ - $notenumbers[0] + 3 } @notenumbers], split => $notes[0], comment => 'bass m3'},
102 38         77 {name => $value2note[($notevalues{$notes[0]} - 4) % 12], notes => [map { $_ - $notenumbers[0] + 4 } @notenumbers], split => $notes[0], comment => 'bass 3'},
103 38         68 {name => $value2note[($notevalues{$notes[0]} + 5) % 12], notes => [map { $_ - $notenumbers[0] - 5 } @notenumbers], split => $notes[0], comment => 'bass 5'},
104 8         42 {name => $value2note[($notevalues{$notes[0]} + 2) % 12], notes => [map { $_ - $notenumbers[0] - 2 } @notenumbers], split => $notes[0], comment => 'bass m7'};
105            
106 8         133 shift(@notenumbers); # get rid of bass note, incase it's a split!
107             # ... and do it all again!
108 30         72 push @inversions,
109 30         81 {name => $value2note[($notevalues{$notes[0]}) % 12], notes => [map { $_ - $notenumbers[0] } @notenumbers], split => $notes[0], comment => 'split 1'},
110 30         67 {name => $value2note[($notevalues{$notes[0]} - 3) % 12], notes => [map { $_ - $notenumbers[0] + 3 } @notenumbers], split => $notes[0], comment => 'split m3'},
111 30         133 {name => $value2note[($notevalues{$notes[0]} - 4) % 12], notes => [map { $_ - $notenumbers[0] + 4 } @notenumbers], split => $notes[0], comment => 'split 3'},
112 30         65 {name => $value2note[($notevalues{$notes[0]} + 5) % 12], notes => [map { $_ - $notenumbers[0] - 5 } @notenumbers], split => $notes[0], comment => 'split 5'},
113 8         21 {name => $value2note[($notevalues{$notes[0]} + 2) % 12], notes => [map { $_ - $notenumbers[0] - 2 } @notenumbers], split => $notes[0], comment => 'split m7'};
114            
115             # ok, here's how it works:
116              
117             # There are these notes:
118              
119             # 0 1 2 3 4 5 6 7 8 9 10 11
120             # 1 b2 2 m3 3 4 b5 5 a5 6 m7 7
121              
122             # 12 13 14 15 16 17 18 19 20 21 22 23
123             # 8 b9 9 m10 10 11 b12 12 b13 13 m14 14
124              
125             # these are the names of the notes we could have in the chord
126 8         45 my @valuenames = qw(
127             1 b2 2 m3 3 4 b5 5 a5 6 m7 7
128             8 b9 9 m10 10 11 b12 12 b13 13 m14 14);
129              
130             # Chord folding
131              
132             # We'll fold our chord into this structure... whichever note is the root can get
133             # set as 0. Any note below it can have 12 added to it until it's above 0. Any
134             # note above 23 can have 12 taken from it until it is 23 or less.
135              
136             # fold each of our inversions of the chord!
137 8         12 foreach my $hash(@inversions){
138 80         93 my $array = $hash->{notes} ;
139 80         141 for(my $i = 0; $i< @$array; $i++){
140 340         611 while($array->[$i] > 23){ $array->[$i] -= 12; } # anything over 23, drop it an octave
  2         73  
141 340         839 while($array->[$i] < 0){ $array->[$i] += 12; } # anything under 0, raise it an octave
  48         114  
142             }
143             }
144              
145             # we'll put the chord names in here:
146 8         12 my @NAMES = ();
147              
148             # now we need to turn them into hashes!!! We'll do all the rest for each hash
149 8         13 foreach my $hash(@inversions){
150             # skip it if the name is the same as the split... this could happen in the "next" inversions... there's
151             # no point to it because it will already have been covered by "bass 1"
152 80 100 100     436 next if $hash->{'split'} && $notevalues{$hash->{'split'}} == $notevalues{$hash->{name}};
153             # the notes...
154 72         97 my $array = $hash->{notes} ;
155 72         213 %NOTES = (); # global, setting it up before calling isset, etc
156 72         104 $NAME = $hash->{name}; # global
157 72         171 for(my $i = 0; $i< @$array; $i++){
158 310         1058 $NOTES{$array->[$i]} = 1; # set up the existence of the notes in the hash
159             }
160              
161             # Duplicate notes
162              
163             # If any note from 0-11 is set then the corresponding note from 12-23 can be
164             # un-set.
165              
166 72         119 foreach (0..11){ # remove notes from upper octave that are already in lower one!
167 864 100       1180 isset($_) and unset($_+12)
168             }
169              
170             # Shifting 1, 3, 5, 7
171              
172             # If none of the 1sts, 3rds, 5ths or 7ths are set in the lower octave then any
173             # corresponding notes in the upper octave can be shifted down.
174              
175 72 100 100     131 isset(0) or (unset(12) and set(0)); # drop 12 to 0 if 0 doesn't exist
176 72 100 100     119 isset(3) or isset(4) # drop either 16 or 15 to 3 or 4 unless 3 or 4 is already set (3rds)
      100        
      66        
      66        
177             or (unset(4+12) and set(4)) or (unset(3+12) and set(3));
178 72 50 50     127 isset(6) or isset(7) or isset(8) # the same for 5ths
      100        
      100        
      33        
      66        
      33        
      33        
179             or (unset(7+12) and set(7)) or (unset(6+12) and set(6)) or (unset(8+12) and set(8));
180 72 50 50     116 isset(10) or isset(11) # and 7ths
      100        
      33        
      66        
181             or (unset(10+12) and set(10)) or (unset(11+12) and set(11));
182              
183             # Now, lets look at what we have...
184              
185             # Is there a root note (0)??? if not, then add "no-root" to the name
186             # Is there a third (3,4)??? if not, then add "no-3rd" to the name
187             # etc...
188              
189             # (if the selection is true, the note concerned is removed so as not to be
190             # evaluated more than once)
191              
192              
193             # Reasoning...
194              
195             # unset returns true if it was able to unset, false otherwise...
196            
197             # special chords:
198 72 50 33     241 $D = lower_octave_is(0,3,6,9) and unset(0,3,6,9) and app('o7');
199 72 50 33     197 $D = $D || lower_octave_is(0,3,6,10) and unset(0,3,6,10) and app('Ø7');
      33        
200 72 100 66     183 $D = $D || lower_octave_is(0,3,6) and unset(0,3,6) and app('o');
      66        
201             # sort out our thirds
202 72 100 100     187 $D or
      66        
      66        
      66        
      66        
      66        
      66        
203             unset(4) or
204             (unset(3) and app('m')) or
205             (unset(5) and app(' sus')) or
206             (unset(2) and app(' sus2')) or
207             app('no-3rd');
208             # sort out 13 11 9 7
209 72 50 100     126 (unset(21,17,14,10) and app('13')) or
      66        
      33        
      66        
      66        
      33        
      33        
      66        
      66        
      33        
      66        
      66        
      66        
      66        
      66        
      66        
      33        
      66        
210             (unset(21,17,14,11) and app('maj13')) or
211             (unset(17,14,10) and app('11')) or
212             (unset(17,14,11) and app('maj11')) or
213             (unset(14,10) and app('9')) or
214             (unset(14,11) and app('maj9')) or
215             (unset(10) and app('7')) or
216             (unset(11) and app('maj7')) or
217             (unset(9,14) and app('6/9')) or
218             (unset(9) and app('-6'));
219             # sort out 5
220 72 100 100     207 $D or
      66        
      66        
      66        
      66        
221             unset(7) or
222             (unset(6) and app(' b5')) or
223             (unset(8) and app(' #5')) or
224             app(' no5');
225             # root
226 72 100 100     201 $D or
227             unset(0) or app(' no-root');
228             # any additional notes
229 72         106 foreach (0..23){
230 1728 100       2756 unset($_) and app(' add'.$valuenames[$_]);
231             }
232             # split
233 72 100       176 if($hash->{split}){ $NAME .= '/'.$hash->{split}; }
  64         119  
234 72         166 push @NAMES, $NAME;
235             }
236 8         68 my @results = sort {length($a) <=> length($b)} @NAMES;
  162         219  
237 8 50       24 if(wantarray){
238 0         0 return @results;
239             }
240             else {
241 8         553 return $results[0];
242             }
243             }
244              
245              
246              
247              
248             # some subs:
249            
250             sub set {
251 13     13 0 26 $NOTES{$_[0]} = 1;
252 13         26 return 1;
253             }
254             sub isset {
255 4334 100   4334 0 7726 if($NOTES{$_[0]}){ return 1; }
  756         2460  
256 3578         7814 else { return 0; }
257             }
258             sub unset {
259 2979     2979 0 4198 foreach (@_){
260 3019 100       4990 if(! isset($_)){ return 0; }
  2683         7814  
261             }
262 296         502 foreach (@_){
263 317         813 $NOTES{$_} = 0;
264             }
265 296         1126 return 1;
266             }
267             sub app {
268 251     251 0 336 $NAME .= $_[0];
269 251         614 return 1;
270             }
271             sub lower_octave_is {
272 216     216 0 332 my %notes = map { ($_ => 1) } @_; # sets up %notes = ($_[0]=>1,$_[1]=>1 ...)
  792         1747  
273 216         472 foreach my $i(0..11){
274 426 100 100     2779 if(($notes{$i} && ! $NOTES{$i}) || # if it's set in one but not the other
      100        
      66        
275             ($NOTES{$i} && ! $notes{$i})){ # or the other way around
276 214         1006 return 0; # then the test returns false
277             }
278             }
279 2         12 return 1;
280             }
281              
282             sub jws {
283 0     0     return join(' ',@_);
284             }
285             sub jwn {
286 0     0     return join("\n",@_);
287             }
288              
289             1;
290              
291              
292              
293              
294              
295             =head1 NAME
296              
297             Music::Chord::Namer - You give it notes, it names the chord.
298              
299             =head1 SYNOPSIS
300              
301             use Music::ChordName qw/chordname/;
302              
303             print chordname(qw/C E G/); # prints C
304             print chordname(q/C E G/); # same (yes, array or string!)
305             print chordname(qw/C Eb G Bb D/); # prints Cm9
306             print chordname(qw/G C Eb Bb D/); # prints Cm9/G
307              
308             =head1 DESCRIPTION
309              
310             Music::ChordName optionally exports one sub, chordname, which accepts some notes as either a string
311             or a list and returns the best chord name it can think of.
312              
313             =head2 EXPORT
314              
315             None by default.
316              
317             =over 4
318              
319             =item $bestnamescalar|@namesarray = chordname($notesstring|@notesarray)
320              
321             chordname() accepts either a string of notes such as "C Eb G A#" or a list of notes such as
322             qw/Ab Bb F Bb D/. In a scalar context it returns the best name it could think of to describe the
323             chord made from the notes you gave it. In an array context it returns all of the names it thought
324             of, sorted from best to worst (shortest to longest!)
325              
326             =head1 EXAMPLES
327              
328              
329             # to print a bunch of guitar chord names with at lest 4 notes each,
330             # all below 5th fret...
331            
332             foreach my $s1(qw/- E F Gb G Ab/){
333             foreach my $s2(qw/- A Bb B C Db/){
334             foreach my $s3(qw/- D Eb E F Gb/){
335             foreach my $s4(qw/- G Ab A Bb/){
336             foreach my $s5(qw/- B C Db D Eb/){
337             foreach my $s6(qw/- E F Gb G Ab/){
338             my @notes = ();
339             push @notes, $s1 unless $s1 eq '-';
340             push @notes, $s2 unless $s2 eq '-';
341             push @notes, $s3 unless $s3 eq '-';
342             push @notes, $s4 unless $s4 eq '-';
343             push @notes, $s5 unless $s5 eq '-';
344             push @notes, $s6 unless $s6 eq '-';
345             if(@notes >= 4){
346             print scalar(chordname(@notes)),' = ',join(' ',@notes),"\n";
347             }
348             }
349             }
350             }
351             }
352             }
353             }
354              
355              
356             =head1 SEE ALSO
357              
358             L could be combined nicely with this module.
359              
360             =head1 AUTHOR
361              
362             Jimi-Carlo Bukowski-Wills, jimi@webu.co.uk
363              
364             =head1 COPYRIGHT AND LICENSE
365              
366             Copyright (C) 2006 by Jimi-Carlo Bukowski-Wills
367              
368             This library is free software; you can redistribute it and/or modify
369             it under the same terms as Perl itself, either Perl version 5.8.7 or,
370             at your option, any later version of Perl 5 you may have available.
371              
372              
373             =cut