File Coverage

blib/lib/Music/Chord/Progression/NRO.pm
Criterion Covered Total %
statement 27 99 27.2
branch 0 26 0.0
condition n/a
subroutine 9 17 52.9
pod 2 2 100.0
total 38 144 26.3


line stmt bran cond sub pod time code
1             package Music::Chord::Progression::NRO;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Generate Neo-Riemann chord progressions - DEPRECATED
5              
6             our $VERSION = '0.0400_1';
7              
8 1     1   1209 use Moo;
  1         10939  
  1         5  
9 1     1   1890 use strictures 2;
  1         1572  
  1         42  
10 1     1   684 use Algorithm::Combinatorics qw(variations);
  1         3520  
  1         63  
11 1     1   7 use Carp qw(croak);
  1         2  
  1         43  
12 1     1   544 use Data::Dumper::Compact qw(ddc);
  1         13053  
  1         4  
13 1     1   605 use Music::NeoRiemannianTonnetz ();
  1         3619  
  1         27  
14 1     1   466 use Music::Chord::Note ();
  1         1075  
  1         28  
15 1     1   444 use Music::Chord::Namer qw(chordname);
  1         2465  
  1         58  
16 1     1   441 use namespace::clean;
  1         9576  
  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 base_scale => (
36             is => 'ro',
37             isa => sub { croak "$_[0] is not a valid scale" unless $_[0] =~ /^(?:major|minor)$/ },
38             default => sub { 'major' },
39             );
40              
41              
42             has base_chord => (
43             is => 'lazy',
44             );
45              
46             sub _build_base_chord {
47 0     0     my ($self) = @_;
48 0           my $cn = Music::Chord::Note->new;
49 0 0         my $quality = $self->base_scale eq 'major' ? '' : 'm';
50 0           my @chord = $cn->chord_with_octave($self->base_note . $quality, $self->base_octave);
51 0           return \@chord;
52             }
53              
54              
55             has format => (
56             is => 'ro',
57             isa => sub { croak "$_[0] is not a valid format" unless $_[0] =~ /^(?:ISO|midinum)$/ },
58             default => sub { 'ISO' },
59             );
60              
61              
62             has max => (
63             is => 'ro',
64             isa => sub { croak "$_[0] is not a valid maximum" unless $_[0] =~ /^[1-9]\d*$/ },
65             default => sub { 4 },
66             );
67              
68              
69             has transform => (
70             is => 'ro',
71             isa => sub { croak "$_[0] is not a valid transform" unless ref $_[0] eq 'ARRAY' || $_[0] =~ /^[1-9]\d*$/ },
72             default => sub { 4 },
73             );
74              
75              
76             has verbose => (
77             is => 'ro',
78             isa => sub { croak "$_[0] is not a boolean" unless $_[0] =~ /^[01]$/ },
79             default => sub { 0 },
80             );
81              
82              
83             has nrt => (
84             is => 'lazy',
85             );
86              
87             sub _build_nrt {
88 0     0     return Music::NeoRiemannianTonnetz->new;
89             }
90              
91              
92             sub generate {
93 0     0 1   my ($self) = @_;
94              
95 0           my ($pitches, $notes) = $self->_get_pitches;
96              
97 0           my @transform = $self->_build_transform;
98              
99 0 0         $self->_initial_conditions(@transform) if $self->verbose;
100              
101 0           my @generated;
102 0           my $i = 0;
103              
104 0           for my $token (@transform) {
105 0           $i++;
106              
107 0           my $transformed = $self->_build_chord($token, $pitches, $notes);
108              
109 0           my @notes = map { $self->pitchname($_) } @$transformed;
  0            
110 0           my @base = map { s/^([A-G][#b]?)\d/$1/r } @notes; # for chord-name
  0            
111              
112 0 0         push @generated, $self->format eq 'ISO' ? \@notes : $transformed;
113              
114 0 0         printf "%d. %s: %s %s %s\n",
115             $i, $token,
116             ddc($transformed), ddc(\@notes),
117             scalar chordname(@base)
118             if $self->verbose;
119              
120 0           $notes = $transformed;
121             }
122              
123 0           return \@generated;
124             }
125              
126              
127             sub circular {
128 0     0 1   my ($self) = @_;
129              
130 0           my ($pitches, $notes) = $self->_get_pitches;
131              
132 0           my @transform = $self->_build_transform;
133              
134 0 0         $self->_initial_conditions(@transform) if $self->verbose;
135              
136 0           my @generated;
137 0           my $posn = 0;
138              
139 0           for my $i (1 .. $self->max) {
140 0           my $token = $transform[ $posn % @transform ];
141              
142 0           my $transformed = $self->_build_chord($token, $pitches, $notes);
143              
144 0           my @notes = map { $self->pitchname($_) } @$transformed;
  0            
145 0           my @base = map { s/^([A-G][#b]?)\d/$1/r } @notes; # for chord-name
  0            
146              
147 0 0         push @generated, $self->format eq 'ISO' ? \@notes : $transformed;
148              
149 0 0         printf "%d. %s (%d): %s %s %s\n",
150             $i, $token, $posn % @transform,
151             ddc($transformed), ddc(\@notes),
152             scalar chordname(@base)
153             if $self->verbose;
154              
155 0           $notes = $transformed;
156              
157 0 0         $posn = int rand 2 ? $posn + 1 : $posn - 1;
158             }
159              
160 0           return \@generated;
161             }
162              
163             sub _get_pitches {
164 0     0     my ($self) = @_;
165 0           my @pitches = map { $self->pitchnum($_) } @{ $self->base_chord };
  0            
  0            
166 0           return \@pitches, [ @pitches ];
167             }
168              
169             sub _initial_conditions {
170 0     0     my ($self, @transform) = @_;
171 0           printf "Initial: %s%s %s\nTransforms: %s\n",
172             $self->base_note, $self->base_octave, $self->base_scale,
173             join(',', @transform);
174             }
175              
176             sub _build_transform {
177 0     0     my ($self) = @_;
178              
179 0           my @transform;
180              
181 0 0         if (ref $self->transform eq 'ARRAY') {
    0          
182 0           @transform = @{ $self->transform };
  0            
183             }
184             elsif ($self->transform =~ /^\d+$/) {
185 0           my @alphabet = qw(P R L);
186 0           my @nro = @alphabet;
187              
188 0           my $iter = variations(\@alphabet, 2);
189 0           while (my $v = $iter->next) {
190 0           push @nro, join('', @$v);
191             }
192 0           $iter = variations(\@alphabet, 3);
193 0           while (my $v = $iter->next) {
194 0           push @nro, join('', @$v);
195             }
196              
197 0           @transform = ('I', map { $nro[ int rand @nro ] } 1 .. $self->transform - 1);
  0            
198             }
199              
200 0           return @transform;
201             }
202              
203             sub _build_chord {
204 0     0     my ($self, $token, $pitches, $notes) = @_;
205              
206 0           my $chord;
207              
208 0 0         if ($token =~ /^I$/) {
209 0           $chord = $pitches; # no transformation
210             }
211             else {
212 0 0         my $task = $self->nrt->taskify_tokens($token) if length $token > 1;
213 0 0         my $op = defined $task ? $task : $token;
214              
215 0           $chord = $self->nrt->transform($op, $notes);
216             }
217              
218 0           return $chord;
219             }
220              
221             1;
222              
223             __END__