File Coverage

blib/lib/Music/MelodicDevice/Transposition.pm
Criterion Covered Total %
statement 44 44 100.0
branch 10 12 83.3
condition n/a
subroutine 11 11 100.0
pod 1 1 100.0
total 66 68 97.0


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.0502';
7              
8 1     1   1084 use Data::Dumper::Compact qw(ddc);
  1         10614  
  1         4  
9 1     1   639 use List::SomeUtils qw(first_index);
  1         10345  
  1         87  
10 1     1   483 use Music::Scales qw(get_scale_MIDI is_scale);
  1         4069  
  1         110  
11 1     1   634 use Moo;
  1         6887  
  1         5  
12 1     1   1575 use strictures 2;
  1         1279  
  1         37  
13 1     1   636 use namespace::clean;
  1         6778  
  1         7  
14              
15             with('Music::PitchNum');
16              
17 1     1   334 use constant OCTAVES => 10;
  1         1  
  1         538  
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   18 my ($self) = @_;
40              
41 2         6 my @scale = map { get_scale_MIDI($self->scale_note, $_, $self->scale_name) } -1 .. OCTAVES - 1;
  22         746  
42 2 50       72 print 'Scale: ', ddc(\@scale) if $self->verbose;
43              
44 2         15 return \@scale;
45             }
46              
47              
48             has verbose => (
49             is => 'ro',
50             isa => sub { die "$_[0] is not a valid boolean" unless $_[0] =~ /^[01]$/ },
51             default => sub { 0 },
52             );
53              
54              
55             sub transpose {
56 18     18 1 8081 my ($self, $offset, $notes) = @_;
57              
58 18 100       89 my $named = $notes->[0] =~ /[A-G]/ ? 1 : 0;
59              
60 18         26 my @transposed;
61              
62 18         35 for my $n (@$notes) {
63 90         712 my ($i, $pitch) = $self->_find_pitch($n);
64 90 100       138 if ($i == -1) {
65 2         5 push @transposed, undef;
66             }
67             else {
68 88 100       113 if ($named) {
69 44         509 push @transposed, $self->pitchname($self->_scale->[ $i + $offset ]);
70             }
71             else {
72 44         797 push @transposed, $self->_scale->[ $i + $offset ];
73             }
74             }
75             }
76 18 50       196 print 'Transposed: ', ddc(\@transposed) if $self->verbose;
77              
78 18         57 return \@transposed;
79             }
80              
81             sub _find_pitch {
82 90     90   131 my ($self, $pitch) = @_;
83              
84 90 100       223 $pitch = $self->pitchnum($pitch)
85             if $pitch =~ /[A-G]/;
86              
87 90     4660   1712 my $i = first_index { $_ == $pitch } @{ $self->_scale };
  4660         4840  
  90         1181  
88              
89 90         241 return $i, $pitch;
90             }
91              
92             1;
93              
94             __END__