File Coverage

blib/lib/Music/MelodicDevice/Transposition.pm
Criterion Covered Total %
statement 45 45 100.0
branch 10 12 83.3
condition 2 3 66.6
subroutine 11 11 100.0
pod 1 1 100.0
total 69 72 95.8


line stmt bran cond sub pod time code
1             package Music::MelodicDevice::Transposition;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Apply chromatic and diatonic transposition to notes
5              
6             our $VERSION = '0.0601';
7              
8 2     2   770563 use Moo;
  2         21367  
  2         14  
9 2     2   5168 use strictures 2;
  2         4381  
  2         109  
10 2     2   2424 use Data::Dumper::Compact qw(ddc);
  2         35080  
  2         11  
11 2     2   1376 use List::SomeUtils qw(first_index);
  2         27060  
  2         266  
12 2     2   1039 use Music::Scales qw(get_scale_MIDI is_scale);
  2         11418  
  2         201  
13 2     2   1105 use namespace::clean;
  2         22728  
  2         14  
14              
15             with('Music::PitchNum');
16              
17 2     2   711 use constant OCTAVES => 10;
  2         4  
  2         1820  
18              
19              
20             has scale_note => (
21             is => 'ro',
22             isa => sub { die "$_[0] is not a valid note" unless $_[0] =~ /^[A-G][#b]?$/ },
23             default => sub { 'C' },
24             );
25              
26              
27             has scale_name => (
28             is => 'ro',
29             isa => sub { die "$_[0] is not a valid scale name" unless is_scale($_[0]) },
30             default => sub { 'chromatic' },
31             );
32              
33             has _scale => (
34             is => 'lazy',
35             init_args => undef,
36             );
37              
38             sub _build__scale {
39 2     2   23 my ($self) = @_;
40              
41 2         9 my @scale = map { get_scale_MIDI($self->scale_note, $_, $self->scale_name) } -1 .. OCTAVES - 1;
  22         1307  
42 2 50       138 print 'Scale: ', ddc(\@scale) if $self->verbose;
43              
44 2         40 return \@scale;
45             }
46              
47              
48             has notes => (
49             is => 'rw',
50             isa => sub { die "$_[0] is not a valid list" unless ref($_[0]) eq 'ARRAY' },
51             default => sub { [] },
52             );
53              
54              
55             has verbose => (
56             is => 'ro',
57             isa => sub { die "$_[0] is not a valid boolean" unless $_[0] =~ /^[01]$/ },
58             default => sub { 0 },
59             );
60              
61              
62             sub transpose {
63 19     19 1 34208 my ($self, $offset, $notes) = @_;
64              
65 19   66     87 $notes ||= $self->notes;
66              
67 19 100       107 my $named = $notes->[0] =~ /[A-G]/ ? 1 : 0;
68              
69 19         78 my @transposed;
70              
71 19         53 for my $n (@$notes) {
72 93         1487 my ($i, $pitch) = $self->_find_pitch($n);
73 93 100       266 if ($i == -1) {
74 2         8 push @transposed, undef;
75             }
76             else {
77 91 100       223 if ($named) {
78 47         1307 push @transposed, $self->pitchname($self->_scale->[ $i + $offset ]);
79             }
80             else {
81 44         1160 push @transposed, $self->_scale->[ $i + $offset ];
82             }
83             }
84             }
85 19 50       386 print 'Transposed: ', ddc(\@transposed) if $self->verbose;
86              
87 19         91 return \@transposed;
88             }
89              
90             sub _find_pitch {
91 93     93   210 my ($self, $pitch) = @_;
92              
93 93 100       413 $pitch = $self->pitchnum($pitch)
94             if $pitch =~ /[A-G]/;
95              
96 93     4774   3294 my $i = first_index { $_ == $pitch } @{ $self->_scale };
  4774         8542  
  93         2602  
97              
98 93         513 return $i, $pitch;
99             }
100              
101             1;
102              
103             __END__