File Coverage

blib/lib/Music/MelodicDevice/Ornamentation.pm
Criterion Covered Total %
statement 142 142 100.0
branch 38 50 76.0
condition 7 13 53.8
subroutine 19 19 100.0
pod 5 5 100.0
total 211 229 92.1


line stmt bran cond sub pod time code
1             package Music::MelodicDevice::Ornamentation;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Chromatic and diatonic melodic ornamentation
5              
6             our $VERSION = '0.0702';
7              
8 1     1   992 use strictures 2;
  1         1318  
  1         32  
9 1     1   154 use Carp qw(croak);
  1         2  
  1         44  
10 1     1   462 use Data::Dumper::Compact qw(ddc);
  1         10907  
  1         5  
11 1     1   548 use List::SomeUtils qw(first_index);
  1         10717  
  1         79  
12 1     1   541 use MIDI::Simple ();
  1         17046  
  1         38  
13 1     1   420 use Moo;
  1         6770  
  1         5  
14 1     1   1581 use Music::Duration ();
  1         316  
  1         23  
15 1     1   356 use Music::Scales qw(get_scale_MIDI is_scale);
  1         4190  
  1         67  
16 1     1   386 use namespace::clean;
  1         6424  
  1         7  
17              
18             with('Music::PitchNum');
19              
20 1     1   305 use constant TICKS => 96;
  1         2  
  1         41  
21 1     1   5 use constant OCTAVES => 10;
  1         2  
  1         1449  
22              
23              
24             has scale_note => (
25             is => 'ro',
26             isa => sub { die "$_[0] is not a valid note" unless $_[0] =~ /^[A-G][#b]?$/ },
27             default => sub { 'C' },
28             );
29              
30              
31             has scale_name => (
32             is => 'ro',
33             isa => sub { die "$_[0] is not a valid scale name" unless is_scale($_[0]) },
34             default => sub { 'chromatic' },
35             );
36              
37             has _scale => (
38             is => 'lazy',
39             init_args => undef,
40             );
41              
42             sub _build__scale {
43 3     3   24 my ($self) = @_;
44              
45 3         8 my @scale = map { get_scale_MIDI($self->scale_note, $_, $self->scale_name) } -1 .. OCTAVES - 1;
  33         1165  
46 3 50       131 print 'Scale: ', ddc(\@scale) if $self->verbose;
47              
48 3         17 return \@scale;
49             }
50              
51              
52             has verbose => (
53             is => 'ro',
54             isa => sub { die "$_[0] is not a valid boolean" unless $_[0] =~ /^[01]$/ },
55             default => sub { 0 },
56             );
57              
58              
59              
60             sub grace_note {
61 18     18 1 11251 my ($self, $duration, $pitch, $offset) = @_;
62              
63 18   50     44 $offset //= 1; # Default one note above
64              
65 18 100       68 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
66              
67 18         37 (my $i, $pitch) = $self->_find_pitch($pitch);
68 18         295 my $grace_note = $self->_scale->[ $i + $offset ];
69              
70 18 100       123 if ($named) {
71 9         27 $pitch = $self->pitchname($pitch);
72 9         121 $grace_note = $self->pitchname($grace_note);
73             }
74              
75             # Compute the ornament durations
76 18         98 my $x = $MIDI::Simple::Length{$duration} * TICKS;
77 18         31 my $y = $MIDI::Simple::Length{xn} * TICKS; # Thirty-second note
78 18         112 my $z = sprintf '%0.f', $x - $y;
79 18 50       59 print "Durations: $x, $y, $z\n" if $self->verbose;
80 18         38 $y = 'd' . $y;
81 18         31 $z = 'd' . $z;
82              
83 18         51 my @grace_note = ([$y, $grace_note], [$z, $pitch]);
84 18 50       39 print 'Grace note: ', ddc(\@grace_note) if $self->verbose;
85              
86 18         57 return \@grace_note;
87             }
88              
89              
90             sub turn {
91 12     12 1 10390 my ($self, $duration, $pitch, $offset) = @_;
92              
93 12         22 my $number = 4; # Number of notes in the ornament
94 12   50     29 $offset //= 1; # Default one note above
95              
96 12 100       44 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
97              
98 12         26 (my $i, $pitch) = $self->_find_pitch($pitch);
99 12         190 my $above = $self->_scale->[ $i + $offset ];
100 12         220 my $below = $self->_scale->[ $i - $offset ];
101              
102 12 100       77 if ($named) {
103 6         16 $pitch = $self->pitchname($pitch);
104 6         74 $above = $self->pitchname($above);
105 6         61 $below = $self->pitchname($below);
106             }
107              
108             # Compute the ornament durations
109 12         62 my $x = $MIDI::Simple::Length{$duration} * TICKS;
110 12         71 my $z = sprintf '%0.f', $x / $number;
111 12 50       35 print "Durations: $x, $z\n" if $self->verbose;
112 12         24 $z = 'd' . $z;
113              
114 12         50 my @turn = ([$z, $above], [$z, $pitch], [$z, $below], [$z, $pitch]);
115 12 50       28 print 'Turn: ', ddc(\@turn) if $self->verbose;
116              
117 12         42 return \@turn;
118             }
119              
120              
121             sub trill {
122 12     12 1 11058 my ($self, $duration, $pitch, $number, $offset) = @_;
123              
124 12   50     29 $number ||= 2; # Number of notes in the ornament
125 12   50     26 $offset //= 1; # Default one note above
126              
127 12 100       47 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
128              
129 12         26 (my $i, $pitch) = $self->_find_pitch($pitch);
130 12         194 my $alt = $self->_scale->[ $i + $offset ];
131              
132 12 100       83 if ($named) {
133 6         28 $pitch = $self->pitchname($pitch);
134 6         74 $alt = $self->pitchname($alt);
135             }
136              
137             # Compute the ornament durations
138 12         73 my $x = $MIDI::Simple::Length{$duration} * TICKS;
139 12         73 my $z = sprintf '%0.f', ($x / $number / 2);
140 12 50       39 print "Durations: $x, $z\n" if $self->verbose;
141 12         21 $z = 'd' . $z;
142              
143 12         19 my @trill;
144              
145 12         70 push @trill, [$z, $pitch], [$z, $alt] for 1 .. $number;
146 12 50       31 print 'Trill: ', ddc(\@trill) if $self->verbose;
147              
148 12         42 return \@trill;
149             }
150              
151              
152             sub mordent {
153 12     12 1 9949 my ($self, $duration, $pitch, $offset) = @_;
154              
155 12         18 my $number = 4; # Finest division needed
156 12   50     28 $offset //= 1; # Default one note above
157              
158 12 100       43 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
159              
160 12         28 (my $i, $pitch) = $self->_find_pitch($pitch);
161 12         189 my $alt = $self->_scale->[ $i + $offset ];
162              
163 12 100       78 if ($named) {
164 6         15 $pitch = $self->pitchname($pitch);
165 6         73 $alt = $self->pitchname($alt);
166             }
167              
168             # Compute the ornament durations
169 12         64 my $x = $MIDI::Simple::Length{$duration} * TICKS;
170 12         69 my $y = sprintf '%0.f', $x / $number;
171 12         45 my $z = sprintf '%0.f', $x - (2 * $y);
172 12 50       40 print "Durations: $x, $y, $z\n" if $self->verbose;
173 12         23 $y = 'd' . $y;
174 12         23 $z = 'd' . $z;
175              
176 12         17 my @mordent;
177              
178 12         41 push @mordent, [$y, $pitch], [$y, $alt], [$z, $pitch];
179 12 50       28 print 'Mordent: ', ddc(\@mordent) if $self->verbose;
180              
181 12         38 return \@mordent;
182             }
183              
184              
185             sub slide {
186 4     4 1 3712 my ($self, $duration, $from, $to) = @_;
187              
188 4         10 my @scale = map { get_scale_MIDI($self->scale_note, $_, 'chromatic') } -1 .. OCTAVES - 1;
  44         1674  
189              
190 4 100       179 my $named = $from =~ /[A-G]/ ? 1 : 0;
191              
192 4         11 (my $i, $from) = $self->_find_pitch($from, \@scale);
193 4         10 (my $j, $to) = $self->_find_pitch($to, \@scale);
194              
195 4         9 my ($start, $end);
196 4 100       9 if ($i <= $j) {
197 2         3 $start = $i;
198 2         3 $end = $j;
199             }
200             else {
201 2         4 $start = $j;
202 2         3 $end = $i;
203             }
204              
205             # Compute the ornament durations
206 4         9 my $x = $MIDI::Simple::Length{$duration} * TICKS;
207 4         7 my $y = $end - $start + 1; # Number of notes in the slide
208 4         24 my $z = sprintf '%0.f', $x / $y;
209 4 50       22 print "Durations: $x, $y, $z\n" if $self->verbose;
210 4         10 $z = 'd' . $z;
211              
212 4         5 my @slide;
213 4 100       9 if ($named) {
214 2         6 @slide = map { [ $z, $self->pitchname($scale[$_]) ] } $start .. $end;
  8         68  
215             }
216             else {
217 2         6 @slide = map { [ $z, $scale[$_] ] } $start .. $end;
  8         18  
218             }
219 4 100       26 @slide = reverse @slide if $j < $i;
220 4 50       10 print 'Slide: ', ddc(\@slide) if $self->verbose;
221              
222 4         28 return \@slide;
223             }
224              
225             sub _find_pitch {
226 62     62   117 my ($self, $pitch, $scale) = @_;
227              
228 62   66     1138 $scale //= $self->_scale;
229              
230 62 100       512 $pitch = $self->pitchnum($pitch)
231             if $pitch =~ /[A-G]/;
232              
233 62     3516   1670 my $i = first_index { $_ eq $pitch } @$scale;
  3516         4084  
234 62 50       179 croak "Unknown pitch: $pitch" if $i < 0;
235              
236 62         158 return $i, $pitch;
237             }
238              
239             1;
240              
241             __END__