File Coverage

blib/lib/Music/MelodicDevice/Transposition.pm
Criterion Covered Total %
statement 42 42 100.0
branch 4 6 66.6
condition n/a
subroutine 12 12 100.0
pod 1 1 100.0
total 59 61 96.7


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.0400';
7              
8 1     1   1300 use Data::Dumper::Compact qw(ddc);
  1         13290  
  1         4  
9 1     1   670 use List::SomeUtils qw(first_index);
  1         13135  
  1         78  
10 1     1   456 use Music::Note;
  1         1708  
  1         37  
11 1     1   487 use Music::Scales qw(get_scale_MIDI is_scale);
  1         5313  
  1         67  
12 1     1   564 use Moo;
  1         8638  
  1         5  
13 1     1   2060 use strictures 2;
  1         1643  
  1         43  
14 1     1   727 use namespace::clean;
  1         8392  
  1         9  
15              
16 1     1   332 use constant OCTAVES => 10;
  1         3  
  1         644  
17              
18              
19             has scale_note => (
20             is => 'ro',
21             isa => sub { die "$_[0] is not a valid note" unless $_[0] =~ /^[A-G][#b]?$/ },
22             default => sub { 'C' },
23             );
24              
25              
26             has scale_name => (
27             is => 'ro',
28             isa => sub { die "$_[0] is not a valid scale name" unless is_scale($_[0]) },
29             default => sub { 'chromatic' },
30             );
31              
32             has _scale => (
33             is => 'lazy',
34             init_args => undef,
35             );
36              
37             sub _build__scale {
38 2     2   18 my ($self) = @_;
39              
40 2         5 my @scale = map { get_scale_MIDI($self->scale_note, $_, $self->scale_name) } -1 .. OCTAVES - 1;
  22         1015  
41 2 50       99 print 'Scale: ', ddc(\@scale) if $self->verbose;
42              
43 2         16 return \@scale;
44             }
45              
46              
47             has verbose => (
48             is => 'ro',
49             isa => sub { die "$_[0] is not a valid boolean" unless $_[0] =~ /^[01]$/ },
50             default => sub { 0 },
51             );
52              
53              
54             sub transpose {
55 9     9 1 4925 my ($self, $offset, $notes) = @_;
56              
57 9         19 my @transposed;
58              
59 9         20 for my $n (@$notes) {
60 45         1811 my ($i, $pitch) = $self->_find_pitch($n);
61 45 100       835 push @transposed, $i == -1
62             ? undef
63             : Music::Note->new($self->_scale->[ $i + $offset ], 'midinum')->format('ISO');
64             }
65 9 50       450 print 'Transposed: ', ddc(\@transposed) if $self->verbose;
66              
67 9         35 return \@transposed;
68             }
69              
70             sub _find_pitch {
71 45     45   91 my ($self, $pitch) = @_;
72              
73 45         120 $pitch = Music::Note->new($pitch, 'ISO')->format('midinum');
74              
75 45     2330   2469 my $i = first_index { $_ == $pitch } @{ $self->_scale };
  2330         3460  
  45         864  
76              
77 45         172 return $i, $pitch;
78             }
79              
80             1;
81              
82             __END__