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.0703';
7              
8 1     1   1200 use strictures 2;
  1         1667  
  1         40  
9 1     1   185 use Carp qw(croak);
  1         2  
  1         52  
10 1     1   558 use Data::Dumper::Compact qw(ddc);
  1         13378  
  1         4  
11 1     1   638 use List::SomeUtils qw(first_index);
  1         12882  
  1         85  
12 1     1   669 use MIDI::Simple ();
  1         21248  
  1         29  
13 1     1   508 use Moo;
  1         8301  
  1         5  
14 1     1   1926 use Music::Duration ();
  1         380  
  1         26  
15 1     1   463 use Music::Scales qw(get_scale_MIDI is_scale);
  1         5212  
  1         72  
16 1     1   535 use namespace::clean;
  1         8002  
  1         9  
17              
18             with('Music::PitchNum');
19              
20 1     1   346 use constant TICKS => 96;
  1         2  
  1         51  
21 1     1   6 use constant OCTAVES => 10;
  1         2  
  1         1795  
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   34 my ($self) = @_;
44              
45 3         8 my @scale = map { get_scale_MIDI($self->scale_note, $_, $self->scale_name) } -1 .. OCTAVES - 1;
  33         1399  
46 3 50       171 print 'Scale: ', ddc(\@scale) if $self->verbose;
47              
48 3         19 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 15052 my ($self, $duration, $pitch, $offset) = @_;
62              
63 18   50     52 $offset //= 1; # Default one note above
64              
65 18 100       100 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
66              
67 18         53 (my $i, $pitch) = $self->_find_pitch($pitch);
68 18         357 my $grace_note = $self->_scale->[ $i + $offset ];
69              
70 18 100       157 if ($named) {
71 9         29 $pitch = $self->pitchname($pitch);
72 9         182 $grace_note = $self->pitchname($grace_note);
73             }
74              
75             # Compute the ornament durations
76 18         117 my $x = $MIDI::Simple::Length{$duration} * TICKS;
77 18         36 my $y = $MIDI::Simple::Length{yn} * TICKS; # 64th note
78 18         140 my $z = sprintf '%0.f', $x - $y;
79 18 50       63 print "Durations: $x, $y, $z\n" if $self->verbose;
80 18         40 $y = 'd' . $y;
81 18         38 $z = 'd' . $z;
82              
83 18         68 my @grace_note = ([$y, $grace_note], [$z, $pitch]);
84 18 50       41 print 'Grace note: ', ddc(\@grace_note) if $self->verbose;
85              
86 18         71 return \@grace_note;
87             }
88              
89              
90             sub turn {
91 12     12 1 13338 my ($self, $duration, $pitch, $offset) = @_;
92              
93 12         37 my $number = 4; # Number of notes in the ornament
94 12   50     36 $offset //= 1; # Default one note above
95              
96 12 100       51 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
97              
98 12         35 (my $i, $pitch) = $self->_find_pitch($pitch);
99 12         239 my $above = $self->_scale->[ $i + $offset ];
100 12         255 my $below = $self->_scale->[ $i - $offset ];
101              
102 12 100       92 if ($named) {
103 6         17 $pitch = $self->pitchname($pitch);
104 6         87 $above = $self->pitchname($above);
105 6         62 $below = $self->pitchname($below);
106             }
107              
108             # Compute the ornament durations
109 12         75 my $x = $MIDI::Simple::Length{$duration} * TICKS;
110 12         89 my $z = sprintf '%0.f', $x / $number;
111 12 50       42 print "Durations: $x, $z\n" if $self->verbose;
112 12         28 $z = 'd' . $z;
113              
114 12         58 my @turn = ([$z, $above], [$z, $pitch], [$z, $below], [$z, $pitch]);
115 12 50       31 print 'Turn: ', ddc(\@turn) if $self->verbose;
116              
117 12         51 return \@turn;
118             }
119              
120              
121             sub trill {
122 12     12 1 14335 my ($self, $duration, $pitch, $number, $offset) = @_;
123              
124 12   50     33 $number ||= 2; # Number of notes in the ornament
125 12   50     29 $offset //= 1; # Default one note above
126              
127 12 100       51 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
128              
129 12         34 (my $i, $pitch) = $self->_find_pitch($pitch);
130 12         229 my $alt = $self->_scale->[ $i + $offset ];
131              
132 12 100       105 if ($named) {
133 6         36 $pitch = $self->pitchname($pitch);
134 6         94 $alt = $self->pitchname($alt);
135             }
136              
137             # Compute the ornament durations
138 12         76 my $x = $MIDI::Simple::Length{$duration} * TICKS;
139 12         91 my $z = sprintf '%0.f', ($x / $number / 2);
140 12 50       45 print "Durations: $x, $z\n" if $self->verbose;
141 12         28 $z = 'd' . $z;
142              
143 12         25 my @trill;
144              
145 12         75 push @trill, [$z, $pitch], [$z, $alt] for 1 .. $number;
146 12 50       32 print 'Trill: ', ddc(\@trill) if $self->verbose;
147              
148 12         55 return \@trill;
149             }
150              
151              
152             sub mordent {
153 12     12 1 12965 my ($self, $duration, $pitch, $offset) = @_;
154              
155 12         25 my $number = 4; # Finest division needed
156 12   50     36 $offset //= 1; # Default one note above
157              
158 12 100       52 my $named = $pitch =~ /[A-G]/ ? 1 : 0;
159              
160 12         34 (my $i, $pitch) = $self->_find_pitch($pitch);
161 12         221 my $alt = $self->_scale->[ $i + $offset ];
162              
163 12 100       101 if ($named) {
164 6         19 $pitch = $self->pitchname($pitch);
165 6         89 $alt = $self->pitchname($alt);
166             }
167              
168             # Compute the ornament durations
169 12         91 my $x = $MIDI::Simple::Length{$duration} * TICKS;
170 12         84 my $y = sprintf '%0.f', $x / $number;
171 12         59 my $z = sprintf '%0.f', $x - (2 * $y);
172 12 50       44 print "Durations: $x, $y, $z\n" if $self->verbose;
173 12         27 $y = 'd' . $y;
174 12         32 $z = 'd' . $z;
175              
176 12         24 my @mordent;
177              
178 12         49 push @mordent, [$y, $pitch], [$y, $alt], [$z, $pitch];
179 12 50       32 print 'Mordent: ', ddc(\@mordent) if $self->verbose;
180              
181 12         53 return \@mordent;
182             }
183              
184              
185             sub slide {
186 4     4 1 4689 my ($self, $duration, $from, $to) = @_;
187              
188 4         13 my @scale = map { get_scale_MIDI($self->scale_note, $_, 'chromatic') } -1 .. OCTAVES - 1;
  44         2016  
189              
190 4 100       224 my $named = $from =~ /[A-G]/ ? 1 : 0;
191              
192 4         13 (my $i, $from) = $self->_find_pitch($from, \@scale);
193 4         12 (my $j, $to) = $self->_find_pitch($to, \@scale);
194              
195 4         10 my ($start, $end);
196 4 100       13 if ($i <= $j) {
197 2         6 $start = $i;
198 2         5 $end = $j;
199             }
200             else {
201 2         5 $start = $j;
202 2         4 $end = $i;
203             }
204              
205             # Compute the ornament durations
206 4         11 my $x = $MIDI::Simple::Length{$duration} * TICKS;
207 4         9 my $y = $end - $start + 1; # Number of notes in the slide
208 4         31 my $z = sprintf '%0.f', $x / $y;
209 4 50       17 print "Durations: $x, $y, $z\n" if $self->verbose;
210 4         10 $z = 'd' . $z;
211              
212 4         8 my @slide;
213 4 100       11 if ($named) {
214 2         7 @slide = map { [ $z, $self->pitchname($scale[$_]) ] } $start .. $end;
  8         98  
215             }
216             else {
217 2         9 @slide = map { [ $z, $scale[$_] ] } $start .. $end;
  8         23  
218             }
219 4 100       58 @slide = reverse @slide if $j < $i;
220 4 50       15 print 'Slide: ', ddc(\@slide) if $self->verbose;
221              
222 4         35 return \@slide;
223             }
224              
225             sub _find_pitch {
226 62     62   137 my ($self, $pitch, $scale) = @_;
227              
228 62   66     1355 $scale //= $self->_scale;
229              
230 62 100       642 $pitch = $self->pitchnum($pitch)
231             if $pitch =~ /[A-G]/;
232              
233 62     3516   2058 my $i = first_index { $_ eq $pitch } @$scale;
  3516         5085  
234 62 50       237 croak "Unknown pitch: $pitch" if $i < 0;
235              
236 62         195 return $i, $pitch;
237             }
238              
239             1;
240              
241             __END__