File Coverage

blib/lib/Music/ScaleNote.pm
Criterion Covered Total %
statement 72 72 100.0
branch 14 22 63.6
condition 24 38 63.1
subroutine 14 14 100.0
pod 2 2 100.0
total 126 148 85.1


line stmt bran cond sub pod time code
1             package Music::ScaleNote;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Manipulate the position of a note in a scale
5              
6             our $VERSION = '0.0902';
7              
8 2     2   503520 use Moo;
  2         13821  
  2         8  
9 2     2   3499 use strictures 2;
  2         2659  
  2         68  
10 2     2   702 use Carp qw(croak);
  2         2  
  2         80  
11 2     2   989 use Data::Dumper::Compact qw( ddc );
  2         23521  
  2         6  
12 2     2   1017 use Array::Circular ();
  2         1985  
  2         47  
13 2     2   841 use List::SomeUtils qw( first_index );
  2         20782  
  2         166  
14 2     2   844 use MIDI::Util qw(midi_format);
  2         47277  
  2         125  
15 2     2   815 use Music::Note ();
  2         2919  
  2         57  
16 2     2   800 use Music::Scales qw( get_scale_notes get_scale_nums );
  2         8262  
  2         140  
17 2     2   923 use namespace::clean;
  2         19063  
  2         11  
18              
19              
20             has scale_note => (
21             is => 'ro',
22             default => sub { 'C' },
23             );
24              
25              
26             has scale_name => (
27             is => 'ro',
28             default => sub { 'major' },
29             );
30              
31             has _scale => (
32             is => 'lazy',
33             builder => '_build__scale',
34             );
35             sub _build__scale {
36 4     4   24 my ($self) = @_;
37 4         12 my @base = get_scale_nums( $self->scale_name );
38 4         77 my @scale;
39 4         9 for my $i (0 .. 9) {
40 40         40 for my $degree (@base) {
41 240         287 push @scale, $degree + ($i * 12)
42             }
43             }
44             # print ddc \@scale;
45 4         29 return \@scale;
46             }
47              
48              
49             has note_format => (
50             is => 'ro',
51             default => sub { 'midinum' },
52             );
53              
54              
55             has offset => (
56             is => 'ro',
57             isa => sub { die 'Not a negative or positive integer' unless $_[0] =~ /^-?\d+$/ },
58             default => sub { 1 },
59             );
60              
61              
62             has flat => (
63             is => 'ro',
64             default => sub { 0 },
65             );
66              
67              
68             has verbose => (
69             is => 'ro',
70             default => sub { 0 },
71             );
72              
73              
74             sub get_offset {
75 11     11 1 5688 my ( $self, %args ) = @_;
76              
77 11   66     43 my $name = $args{note_name} || $self->scale_note;
78 11   66     36 my $format = $args{note_format} || $self->note_format;
79 11   66     27 my $offset = $args{offset} || $self->offset;
80 11   66     28 my $flat = $args{flat} || $self->flat;
81              
82 11 0 33     16 croak 'note_name, note_format or offset not provided'
      33        
83             unless $name || $format || $offset;
84              
85 11         40 my $note = Music::Note->new( $name, $format );
86              
87 11 100       325 croak "Note not defined for $name and $format!" unless $note->format($format) eq $name;
88              
89 10         222 my $octave = $note->octave;
90              
91 10 50 66     51 $note->en_eq('flat') if $flat && $note->format('isobase') =~ /#/;
92              
93 10 50       34 printf "Given note: %s, Format: %s, ISO: %s, Offset: %d\n",
94             $name, $format, $note->format('ISO'), $offset
95             if $self->verbose;
96              
97 10     328   30 my $posn = first_index { $note->format('midinum') == $_ } @{ $self->_scale };
  328         4594  
  10         207  
98 10 100       196 if ( $posn >= 0 ) {
99 9 50       25 printf "\tPosition: %d, Offset position: %d\n", $posn, $posn + $offset
100             if $self->verbose;
101             }
102             else {
103 1         25 croak 'Scale position not defined!';
104             }
105              
106 9         178 my $n = $self->_scale->[ $posn + $offset ];
107 9         61 $note = Music::Note->new( $n, 'midinum' );
108              
109 9 100 66     253 $note->en_eq('flat') if $flat && $note->format('ISO') =~ /#/;
110              
111 9 50       47 printf "\tOctave: %d, ISO: %s, Formatted: %s\n",
112             $octave, $note->format('ISO'), $note->format($format)
113             if $self->verbose;
114              
115 9         29 return $note;
116             }
117              
118              
119             sub step {
120 7     7 1 4337 my ( $self, %args ) = @_;
121              
122 7   33     20 my $name = $args{note_name} || $self->scale_note;
123 7   66     28 my $format = $args{note_format} || $self->note_format;
124 7   100     12 my $steps = $args{steps} || 1;
125 7   66     20 my $flat = $args{flat} || $self->flat;
126              
127 7         25 my $note = Music::Note->new( $name, $format );
128 7         224 my $num = $note->format('midinum');
129              
130 7 50       162 printf "Given note: %s, ISO: %s, Formatted: %d\n",
131             $name, $note->format('ISO'), $num
132             if $self->verbose;
133              
134 7         22 $num += $steps;
135 7         39 $note = Music::Note->new( $num, 'midinum' );
136 7 100 100     157 $note->en_eq('flat') if $flat && $note->format('isobase') =~ /#/;
137              
138 7 50       83 printf "\tNew steps: %d, ISO: %s, Formatted: %s\n",
139             $steps, $note->format('ISO'), $note->format( $self->note_format )
140             if $self->verbose;
141              
142 7         21 return $note;
143             }
144              
145             1;
146              
147             __END__