File Coverage

blib/lib/Music/Chord/Progression/T.pm
Criterion Covered Total %
statement 88 90 97.7
branch 14 20 70.0
condition n/a
subroutine 16 17 94.1
pod 2 2 100.0
total 120 129 93.0


line stmt bran cond sub pod time code
1             package Music::Chord::Progression::T;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Generate transposed chord progressions
5              
6             our $VERSION = '0.0100';
7              
8 1     1   1236 use Moo;
  1         10888  
  1         6  
9 1     1   1865 use strictures 2;
  1         1689  
  1         40  
10 1     1   237 use Carp qw(croak);
  1         3  
  1         44  
11 1     1   783 use Data::Dumper::Compact qw(ddc);
  1         13766  
  1         5  
12 1     1   607 use Music::Chord::Note ();
  1         1106  
  1         39  
13 1     1   521 use Music::Chord::Namer qw(chordname);
  1         2538  
  1         87  
14 1     1   439 use lib map { "$ENV{HOME}/sandbox/$_/lib" } qw(Music-MelodicDevice-Transposition); # local author libs
  1         647  
  1         3  
  1         9  
15 1     1   574 use Music::MelodicDevice::Transposition ();
  1         71340  
  1         36  
16 1     1   7 use namespace::clean;
  1         2  
  1         9  
17              
18             with 'Music::PitchNum';
19              
20              
21             has base_note => (
22             is => 'ro',
23             isa => sub { croak "$_[0] is not a valid note" unless $_[0] =~ /^[A-G][#b]?$/ },
24             default => sub { 'C' },
25             );
26              
27              
28             has base_octave => (
29             is => 'ro',
30             isa => sub { croak "$_[0] is not a valid octave" unless $_[0] =~ /^[1-8]$/ },
31             default => sub { 4 },
32             );
33              
34              
35             has chord_quality => (
36             is => 'ro',
37             isa => sub { croak "$_[0] is not a valid quality" unless $_[0] =~ /^\w*$/ },
38             default => sub { '' },
39             );
40              
41              
42             has base_chord => (
43             is => 'lazy',
44             );
45              
46             sub _build_base_chord {
47 7     7   59 my ($self) = @_;
48 7         29 my $cn = Music::Chord::Note->new;
49 7         64 my @chord = $cn->chord_with_octave(
50             $self->base_note . $self->chord_quality,
51             $self->base_octave
52             );
53 7         742 return \@chord;
54             }
55              
56              
57             has format => (
58             is => 'ro',
59             isa => sub { croak "$_[0] is not a valid format" unless $_[0] =~ /^(?:ISO|midinum)$/ },
60             default => sub { 'ISO' },
61             );
62              
63              
64             has semitones => (
65             is => 'ro',
66             isa => sub { croak "$_[0] is not a valid number of semitones" unless $_[0] =~ /^[1-9]\d*$/ },
67             default => sub { 7 },
68             );
69              
70              
71             has max => (
72             is => 'ro',
73             isa => sub { croak "$_[0] is not a valid maximum" unless $_[0] =~ /^[1-9]\d*$/ },
74             default => sub { 4 },
75             );
76              
77              
78             has transform => (
79             is => 'ro',
80             isa => sub { croak "$_[0] is not a valid transform" unless ref $_[0] eq 'ARRAY' || $_[0] =~ /^[1-9]\d*$/ },
81             default => sub { 4 },
82             );
83              
84              
85             has mdt => (
86             is => 'lazy',
87             );
88              
89             sub _build_mdt {
90 7     7   136 return Music::MelodicDevice::Transposition->new;
91             }
92              
93              
94             has verbose => (
95             is => 'ro',
96             isa => sub { croak "$_[0] is not a boolean" unless $_[0] =~ /^[01]$/ },
97             default => sub { 0 },
98             );
99              
100              
101             sub generate {
102 6     6 1 2561 my ($self) = @_;
103              
104 6         15 my ($pitches, $notes) = $self->_get_pitches;
105              
106 6         16 my @transform = $self->_build_transform;
107              
108 6 50       23 $self->_initial_conditions(@transform) if $self->verbose;
109              
110 6         8 my @generated;
111 6         11 my $i = 0;
112              
113 6         13 for my $token (@transform) {
114 23         38 $i++;
115              
116 23         48 my $transformed = $self->_build_chord($token, $pitches, $notes);
117              
118 23         50 my @notes = map { $self->pitchname($_) } @$transformed;
  73         642  
119 23         232 my @base = map { s/^([A-G][#b]?)\d/$1/r } @notes; # for chord-name
  73         266  
120              
121 23 100       91 push @generated, $self->format eq 'ISO' ? \@notes : $transformed;
122              
123 23 50       60 printf "%d. %s: %s %s %s\n",
124             $i, $token,
125             ddc($transformed), ddc(\@notes),
126             scalar chordname(@base)
127             if $self->verbose;
128              
129 23         66 $notes = $transformed;
130             }
131              
132 6         22 return \@generated;
133             }
134              
135              
136             sub circular {
137 1     1 1 452 my ($self) = @_;
138              
139 1         4 my ($pitches, $notes) = $self->_get_pitches;
140              
141 1         5 my @transform = $self->_build_transform;
142              
143 1 50       5 $self->_initial_conditions(@transform) if $self->verbose;
144              
145 1         2 my @generated;
146 1         2 my $posn = 0;
147              
148 1         4 for my $i (1 .. $self->max) {
149 4         8 my $token = $transform[ $posn % @transform ];
150              
151 4         10 my $transformed = $self->_build_chord($token, $pitches, $notes);
152              
153 4         9 my @notes = map { $self->pitchname($_) } @$transformed;
  12         98  
154 4         40 my @base = map { s/^([A-G][#b]?)\d/$1/r } @notes; # for chord-name
  12         42  
155              
156 4 50       15 push @generated, $self->format eq 'ISO' ? \@notes : $transformed;
157              
158 4 50       12 printf "%d. %s (%d): %s %s %s\n",
159             $i, $token, $posn % @transform,
160             ddc($transformed), ddc(\@notes),
161             scalar chordname(@base)
162             if $self->verbose;
163              
164 4         8 $notes = $transformed;
165              
166 4 100       17 $posn = int rand 2 ? $posn + 1 : $posn - 1;
167             }
168              
169 1         4 return \@generated;
170             }
171              
172             sub _get_pitches {
173 7     7   12 my ($self) = @_;
174 7         9 my @pitches = map { $self->pitchnum($_) } @{ $self->base_chord };
  22         727  
  7         153  
175 7         338 return \@pitches, [ @pitches ];
176             }
177              
178             sub _initial_conditions {
179 0     0   0 my ($self, @transform) = @_;
180 0         0 printf "Initial: %s%s %s\nTransforms: %s\n",
181             $self->base_note, $self->base_octave, $self->chord_quality,
182             join(',', @transform);
183             }
184              
185             sub _build_transform {
186 7     7   12 my ($self) = @_;
187              
188 7         10 my @transform;
189              
190 7 100       39 if (ref $self->transform eq 'ARRAY') {
    50          
191 3         6 @transform = @{ $self->transform };
  3         10  
192             }
193             elsif ($self->transform =~ /^\d+$/) {
194 4         12 @transform = ('I', map { 'T' . int(rand $self->semitones + 1) } 1 .. $self->transform - 1);
  11         79  
195             }
196              
197 7         22 return @transform;
198             }
199              
200             sub _build_chord {
201 27     27   142 my ($self, $token, $pitches, $notes) = @_;
202              
203 27         42 my $chord = [];
204              
205 27 100       74 if ($token =~ /^I$/) {
206 8         16 $chord = $pitches; # no transformation
207             }
208             else {
209 19         78 (my $semitones = $token) =~ s/^T(-?\d+)$/$1/;
210 19         351 $chord = $self->mdt->transpose($semitones, $notes);
211             }
212              
213 27         14965 return $chord;
214             }
215              
216             1;
217              
218             __END__