File Coverage

blib/lib/Music/MelodicDevice/Inversion.pm
Criterion Covered Total %
statement 58 58 100.0
branch 6 10 60.0
condition n/a
subroutine 13 13 100.0
pod 2 2 100.0
total 79 83 95.1


line stmt bran cond sub pod time code
1             package Music::MelodicDevice::Inversion;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Apply melodic inversion to a series of notes
5              
6             our $VERSION = '0.0400';
7              
8 1     1   1321 use Data::Dumper::Compact qw(ddc);
  1         13436  
  1         5  
9 1     1   675 use List::SomeUtils qw(first_index);
  1         13285  
  1         71  
10 1     1   468 use Music::Note;
  1         1648  
  1         39  
11 1     1   471 use Music::Scales qw(get_scale_MIDI is_scale);
  1         5264  
  1         69  
12 1     1   558 use Moo;
  1         8517  
  1         6  
13 1     1   1992 use strictures 2;
  1         1671  
  1         43  
14 1     1   663 use namespace::clean;
  1         8425  
  1         10  
15              
16 1     1   325 use constant OCTAVES => 10;
  1         2  
  1         854  
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   20 my ($self) = @_;
39              
40 2         5 my @scale = map { get_scale_MIDI($self->scale_note, $_, $self->scale_name) } -1 .. OCTAVES - 1;
  22         974  
41 2 50       96 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 intervals {
55 10     10 1 2743 my ($self, $notes) = @_;
56              
57 10         20 my @pitches;
58              
59 10         20 for my $note (@$notes) {
60 74         150 my ($i, $pitch) = $self->_find_pitch($note);
61 74         174 push @pitches, $i;
62             }
63 10 50       33 print 'Pitches: ', ddc(\@pitches) if $self->verbose;
64              
65 10         17 my @intervals;
66             my $last;
67              
68 10         20 for my $pitch (@pitches) {
69 74 100       128 if (defined $last) {
70 64         99 push @intervals, $pitch - $last;
71             }
72 74         116 $last = $pitch;
73             }
74 10 50       27 print 'Intervals: ', ddc(\@intervals) if $self->verbose;
75              
76 10         34 return \@intervals;
77             }
78              
79              
80             sub invert {
81 5     5 1 2980 my ($self, $note, $notes) = @_;
82              
83 5         13 my @inverted = ($note);
84              
85 5         12 my $intervals = $self->intervals($notes);
86              
87 5         11 for my $interval (@$intervals) {
88             # Find the note that is the opposite interval away from the original note
89 32         69 (my $i, $note) = $self->_find_pitch($note);
90 32         529 my $pitch = $self->_scale->[ $i - $interval ];
91              
92 32         239 $note = Music::Note->new($pitch, 'midinum')->format('ISO');
93              
94 32         1435 push @inverted, $note;
95             }
96              
97 5 50       17 print 'Inverted: ', ddc(\@inverted) if $self->verbose;
98              
99 5         21 return \@inverted;
100             }
101              
102             sub _find_pitch {
103 106     106   201 my ($self, $pitch) = @_;
104 106         245 $pitch = Music::Note->new($pitch, 'ISO')->format('midinum');
105 106     5988   5498 my $i = first_index { $_ eq $pitch } @{ $self->_scale };
  5988         8769  
  106         1971  
106 106         379 return $i, $pitch;
107             }
108              
109             1;
110              
111             __END__