File Coverage

blib/lib/Music/Chord/Note.pm
Criterion Covered Total %
statement 61 61 100.0
branch 22 22 100.0
condition 5 5 100.0
subroutine 11 11 100.0
pod 7 7 100.0
total 106 106 100.0


line stmt bran cond sub pod time code
1             package Music::Chord::Note;
2              
3 4     4   377567 use warnings;
  4         9  
  4         266  
4 4     4   35 use strict;
  4         36  
  4         140  
5 4     4   25 use Carp qw( croak );
  4         9  
  4         6327  
6              
7             our $VERSION = '0.14';
8              
9             my @tone_list = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B',
10             'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B');
11              
12             our $base_chord_list = {
13             'base' => '0,4,7',
14             '-5' => '0,4,6',
15             '-6' => '0,4,7,8',
16             '6' => '0,4,7,9',
17             '6(9)' => '0,4,7,9,14', '69' => '0,4,7,9,14',
18             'M7' => '0,4,7,11',
19             'M7(9)' => '0,4,7,11,14', 'M79' => '0,4,7,11,14',
20             'M9' => '0,4,7,11,14',
21             'M11' => '0,4,7,11,14,17',
22             'M13' => '0,4,7,11,14,17,21',
23             '7' => '0,4,7,10',
24             '7(b5)' => '0,4,6,10', '7b5' => '0,4,6,10',
25             '7(-5)' => '0,4,6,10', '7-5' => '0,4,6,10',
26             '7(#5)' => '0,4,7,8,10', '7#5' => '0,4,7,8,10',
27             '7(b9)' => '0,4,7,10,13', '7b9' => '0,4,7,10,13',
28             '7(-9)' => '0,4,7,10,13', '7-9' => '0,4,7,10,13',
29             '-9' => '0,4,7,10,13',
30             '-9(#5)' => '0,4,8,10,13', '-9#5' => '0,4,8,10,13',
31             '7(b9,13)' => '0,4,7,10,13,21', '7(-9,13)' => '0,4,7,10,13,21',
32             '7(9,13)' => '0,4,7,10,14,21',
33             '7(#9)' => '0,4,7,10,15', '7#9' => '0,4,7,10,15',
34             '7(#11)' => '0,4,7,10,15,18', '7#11' => '0,4,7,10,15,18',
35             '7(#13)' => '0,4,10,21', '7#13' => '0,4,10,21',
36             '9' => '0,4,7,10,14',
37             '9(b5)' => '0,4,6,10,14', '9b5' => '0,4,6,10,14',
38             '9(-5)' => '0,4,6,10,14', '9-5' => '0,4,6,10,14',
39             '11' => '0,4,7,10,14,17',
40             '13' => '0,4,7,10,14,17,21',
41             'm' => '0,3,7',
42             'madd4' => '0,3,5,7',
43             'm6' => '0,3,7,9',
44             'm6(9)' => '0,3,7,9,14', 'm69' => '0,3,7,9,14',
45             'mM7' => '0,3,7,11',
46             'm7' => '0,3,7,10',
47             'm7(b5)' => '0,3,6,10', 'm7b5' => '0,3,6,10',
48             'm7(-5)' => '0,3,6,10', 'm7-5' => '0,3,6,10',
49             'm7(#5)' => '0,3,8,10', 'm7#5' => '0,3,8,10',
50             'm7(9)' => '0,3,7,10,14', 'm79' => '0,3,7,10,14',
51             'm9' => '0,3,7,10,14',
52             'm7(9,11)' => '0,3,7,10,14,17',
53             'm11' => '0,3,7,10,14,17',
54             'm13' => '0,3,7,10,14,17,21',
55             'dim' => '0,3,6',
56             'dim7' => '0,3,6,9', 'dim6' => '0,3,6,9',
57             'aug' => '0,4,8',
58             'aug7' => '0,4,8,10',
59             'augM7' => '0,4,8,11',
60             'aug9' => '0,4,8,10,14',
61             'sus2' => '0,2,7',
62             'sus' => '0,5,7',
63             'sus4' => '0,5,7',
64             '7sus4' => '0,5,7,10',
65             'add2' => '0,2,4,7',
66             'add4' => '0,4,5,7',
67             'add9' => '0,4,7,14',
68             };
69              
70             my $scalic_value = {
71             'C' => 0,
72             'C#' => 1, 'Db' => 1,
73             'D' => 2,
74             'D#' => 3, 'Eb' => 3,
75             'E' => 4,
76             'E#' => 5, 'Fb' => 4, # joke!
77             'F' => 5,
78             'F#' => 6, 'Gb' => 6,
79             'G' => 7,
80             'G#' => 8, 'Ab' => 8,
81             'A' => 9,
82             'A#' => 10, 'Bb' => 10,
83             'B' => 11,
84             'Cb' => 11, 'B#' => 0, # joke!
85             };
86              
87             sub new
88             {
89 3     3 1 607934 my $class = shift;
90 3         14 bless {}, $class;
91             }
92              
93             sub chord
94             {
95 26     26 1 20404 my ($self, $chord_name) = @_;
96              
97 26 100       321 croak "No CHORD_NAME!" unless $chord_name;
98 25         208 my ($tonic, $kind) = ($chord_name =~ /([A-G][b#]?)(.+)?/);
99 25 100       515 croak "unknown chord $chord_name" unless defined $tonic;
100 22 100       56 $kind = 'base' unless $kind;
101 22         53 my $scalic = $scalic_value->{$tonic};
102             croak "undefined kind of chord $kind($chord_name)"
103 22 100       229 unless defined $base_chord_list->{$kind};
104              
105 21         37 my @keys;
106 21         97 for my $scale ( split /\,/, $base_chord_list->{$kind} ){
107 79         154 my $note = $scale + $scalic;
108 79 100       165 $note = int($note % 24) + 12 if $note > 23;
109 79         163 push @keys, $tone_list[$note];
110             }
111              
112 21         98 return @keys;
113             }
114              
115             sub chord_with_octave
116             {
117 5     5 1 2282 my ($self, $chord_name, $octave) = @_;
118              
119 5   100     14 $octave ||= 4;
120              
121 5         6 return @{ $self->_chord_with_octave([$self->chord($chord_name)], $octave) };
  5         28  
122             }
123              
124             sub _chord_with_octave
125             {
126 5     5   20 my ($self, $chord, $octave) = @_;
127              
128 5 100 100     18 if ($octave < -2 || $octave > 9) {
129 2         262 croak 'octave should be integer between -2 and 9';
130             }
131              
132 3         5 my @position = map { $self->scale($_) } @{$chord};
  11         16  
  3         4  
133 3         4 my @formatted;
134 3         6 my $last_position = -1;
135 3         3 for my $n (0 .. $#{$chord}) {
  3         7  
136 11 100       14 $octave++ if $position[$n] < $last_position;
137 11         17 push @formatted, $chord->[$n] . $octave;
138 11         12 $last_position = $position[$n];
139             }
140              
141 3         29 return \@formatted;
142             }
143              
144             sub chord_num
145             {
146 8     8 1 3394 my ($self, $chord) = @_;
147              
148 8 100       28 $chord = 'base' unless $chord;
149 8 100       471 croak "undefined kind of chord ($chord)" unless defined $base_chord_list->{$chord};
150              
151 6         36 return split /,/, $base_chord_list->{$chord};
152             }
153              
154             sub chord_intervals
155             {
156 4     4 1 3177 my ($self, $name) = @_;
157              
158 4         13 my @num = $self->chord_num($name);
159              
160 3 100       15 my @intervals = reverse map { $_ == 0 ? $num[$_] : $num[$_] - $num[$_ - 1] }
  10         38  
161             reverse 0 .. $#num;
162              
163 3         16 return @intervals;
164             }
165              
166             sub scale
167             {
168 20     20 1 9199 my $self = shift;
169 20         30 my $note = shift;
170              
171 20         54 $note =~ s/^([a-g])/uc($1)/e;
  1         8  
172 20 100       730 croak "wrong note ($note)" if $note !~ /^[A-G](?:[#b])?$/;
173              
174 16         39 return $scalic_value->{$note};
175             }
176              
177             sub all_chords_list
178             {
179 1     1 1 890 my $self = shift;
180              
181 1         6 return [ grep { $_ ne 'base' } keys %{$base_chord_list} ];
  75         143  
  1         25  
182             }
183              
184             1;
185              
186             __END__