line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Music::Intervals; |
2
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:GENE'; |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
# ABSTRACT: Breakdown of musical intervals |
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
659
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
25
|
|
7
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
65
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.0903'; |
10
|
|
|
|
|
|
|
|
11
|
1
|
|
|
1
|
|
463
|
use Algorithm::Combinatorics qw( combinations ); |
|
1
|
|
|
|
|
3227
|
|
|
1
|
|
|
|
|
56
|
|
12
|
1
|
|
|
1
|
|
389
|
use Math::Factor::XS qw( prime_factors ); |
|
1
|
|
|
|
|
25366
|
|
|
1
|
|
|
|
|
60
|
|
13
|
1
|
|
|
1
|
|
386
|
use MIDI::Pitch qw( name2freq ); |
|
1
|
|
|
|
|
1266
|
|
|
1
|
|
|
|
|
57
|
|
14
|
1
|
|
|
1
|
|
836
|
use Moo; |
|
1
|
|
|
|
|
7350
|
|
|
1
|
|
|
|
|
4
|
|
15
|
1
|
|
|
1
|
|
1845
|
use Music::Intervals::Ratios; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
31
|
|
16
|
1
|
|
|
1
|
|
463
|
use Number::Fraction (); |
|
1
|
|
|
|
|
42270
|
|
|
1
|
|
|
|
|
32
|
|
17
|
1
|
|
|
1
|
|
470
|
use strictures 2; |
|
1
|
|
|
|
|
1518
|
|
|
1
|
|
|
|
|
111
|
|
18
|
1
|
|
|
1
|
|
1079
|
use namespace::clean; |
|
1
|
|
|
|
|
7946
|
|
|
1
|
|
|
|
|
5
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
has notes => ( |
22
|
|
|
|
|
|
|
is => 'ro', |
23
|
|
|
|
|
|
|
default => sub { [qw( C E G )] }, |
24
|
|
|
|
|
|
|
); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
has _dyads => ( |
27
|
|
|
|
|
|
|
is => 'ro', |
28
|
|
|
|
|
|
|
lazy => 1, |
29
|
|
|
|
|
|
|
builder => 1, |
30
|
|
|
|
|
|
|
); |
31
|
|
|
|
|
|
|
sub _build__dyads { |
32
|
3
|
|
|
3
|
|
37
|
my $self = shift; |
33
|
3
|
|
|
|
|
12
|
my %dyads = $self->dyads($self->notes); |
34
|
3
|
|
|
|
|
13
|
return \%dyads; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
has _octave => ( is => 'ro', default => sub { 4 } ); |
38
|
|
|
|
|
|
|
has _concert => ( is => 'ro', default => sub { 440 } ); |
39
|
|
|
|
|
|
|
has _tonic => ( is => 'ro', default => sub { 'C' } ); |
40
|
|
|
|
|
|
|
has _semitones => ( is => 'ro', default => sub { 12 } ); |
41
|
|
|
|
|
|
|
has _midikey => ( is => 'ro', default => sub { 69 } ); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
has _temper => ( |
44
|
|
|
|
|
|
|
is => 'ro', |
45
|
|
|
|
|
|
|
lazy => 1, |
46
|
|
|
|
|
|
|
builder => 1, |
47
|
|
|
|
|
|
|
); |
48
|
|
|
|
|
|
|
sub _build__temper { |
49
|
1
|
|
|
1
|
|
11
|
my $self = shift; |
50
|
1
|
|
|
|
|
18
|
$self->_semitones * 100 / log(2); |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
has _tonic_frequency => ( |
54
|
|
|
|
|
|
|
is => 'ro', |
55
|
|
|
|
|
|
|
lazy => 1, |
56
|
|
|
|
|
|
|
builder => 1, |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
sub _build__tonic_frequency { |
59
|
2
|
|
|
2
|
|
19
|
my $self = shift; |
60
|
2
|
|
|
|
|
17
|
return name2freq($self->_tonic . $self->_octave); |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
has _note_index => ( |
64
|
|
|
|
|
|
|
is => 'ro', |
65
|
|
|
|
|
|
|
lazy => 1, |
66
|
|
|
|
|
|
|
builder => 1, |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
sub _build__note_index { |
69
|
1
|
|
|
1
|
|
44
|
my $self = shift; |
70
|
1
|
|
|
|
|
2
|
return { map { $_ => eval "$Music::Intervals::Ratios::ratio->{$_}{ratio}" } @{ $self->notes } }; |
|
2
|
|
|
|
|
79
|
|
|
1
|
|
|
|
|
4
|
|
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
has _ratio_index => ( |
74
|
|
|
|
|
|
|
is => 'ro', |
75
|
|
|
|
|
|
|
lazy => 1, |
76
|
|
|
|
|
|
|
builder => 1, |
77
|
|
|
|
|
|
|
); |
78
|
|
|
|
|
|
|
sub _build__ratio_index { |
79
|
3
|
|
|
3
|
|
127
|
my $self = shift; |
80
|
3
|
|
|
|
|
13
|
return { map { $_ => $Music::Intervals::Ratios::ratio->{$_}{ratio} } @{ $self->notes } }; |
|
6
|
|
|
|
|
172
|
|
|
3
|
|
|
|
|
9
|
|
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
has _ratio_name_index => ( |
84
|
|
|
|
|
|
|
is => 'ro', |
85
|
|
|
|
|
|
|
lazy => 1, |
86
|
|
|
|
|
|
|
builder => 1, |
87
|
|
|
|
|
|
|
); |
88
|
|
|
|
|
|
|
sub _build__ratio_name_index { |
89
|
3
|
|
|
3
|
|
27
|
my $self = shift; |
90
|
|
|
|
|
|
|
return { |
91
|
3
|
|
|
|
|
212
|
map { $Music::Intervals::Ratios::ratio->{$_}{ratio} => { |
92
|
|
|
|
|
|
|
symbol => $_, |
93
|
|
|
|
|
|
|
name => $Music::Intervals::Ratios::ratio->{$_}{name} } |
94
|
1341
|
|
|
|
|
4943
|
} keys %$Music::Intervals::Ratios::ratio |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub integer_notation { |
100
|
2
|
|
|
2
|
1
|
274
|
my ($self) = @_; |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
my %integer_notation = map { $_ => sprintf '%.0f', |
103
|
|
|
|
|
|
|
$self->_midikey + $self->_semitones |
104
|
4
|
|
|
|
|
72
|
* log( ($self->_tonic_frequency * (eval $self->_ratio_index->{$_})) / $self->_concert ) / log(2) |
105
|
2
|
|
|
|
|
4
|
} @{ $self->notes }; |
|
2
|
|
|
|
|
7
|
|
106
|
|
|
|
|
|
|
|
107
|
2
|
|
|
|
|
13
|
return \%integer_notation; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub eq_tempered_cents { |
112
|
2
|
|
|
2
|
1
|
270
|
my ($self) = @_; |
113
|
|
|
|
|
|
|
|
114
|
2
|
|
|
|
|
5
|
my %dyads = %{ $self->_dyads }; |
|
2
|
|
|
|
|
40
|
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
my %eq_tempered_cents = map { |
117
|
2
|
|
|
|
|
27
|
$_ => log( $dyads{$_}->{eq_tempered} ) * $self->_temper |
|
3
|
|
|
|
|
54
|
|
118
|
|
|
|
|
|
|
} keys %dyads; |
119
|
|
|
|
|
|
|
|
120
|
2
|
|
|
|
|
19
|
return \%eq_tempered_cents; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub eq_tempered_frequencies { |
125
|
4
|
|
|
4
|
1
|
281
|
my ($self) = @_; |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
my %eq_tempered_frequencies = map { |
128
|
10
|
|
33
|
|
|
147
|
$_ => name2freq( $_ . $self->_octave ) || $self->_concert * $self->_note_index->{$_} |
129
|
4
|
|
|
|
|
8
|
} @{ $self->notes }; |
|
4
|
|
|
|
|
12
|
|
130
|
|
|
|
|
|
|
|
131
|
4
|
|
|
|
|
106
|
return \%eq_tempered_frequencies; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub eq_tempered_intervals { |
136
|
4
|
|
|
4
|
1
|
316
|
my ($self) = @_; |
137
|
|
|
|
|
|
|
|
138
|
4
|
|
|
|
|
6
|
my %dyads = %{ $self->_dyads }; |
|
4
|
|
|
|
|
81
|
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
my %eq_tempered_intervals = map { |
141
|
4
|
|
|
|
|
37
|
$_ => $dyads{$_}->{eq_tempered} |
142
|
9
|
|
|
|
|
20
|
} keys %dyads; |
143
|
|
|
|
|
|
|
|
144
|
4
|
|
|
|
|
62
|
return \%eq_tempered_intervals; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub natural_cents { |
149
|
4
|
|
|
4
|
1
|
292
|
my ($self) = @_; |
150
|
|
|
|
|
|
|
|
151
|
4
|
|
|
|
|
5
|
my %dyads = %{ $self->_dyads }; |
|
4
|
|
|
|
|
79
|
|
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
my %natural_cents = map { |
154
|
4
|
|
|
|
|
47
|
$_ => log( eval $dyads{$_}->{natural} ) * $self->_temper |
|
9
|
|
|
|
|
383
|
|
155
|
|
|
|
|
|
|
} keys %dyads; |
156
|
|
|
|
|
|
|
|
157
|
4
|
|
|
|
|
82
|
return \%natural_cents; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub natural_frequencies { |
162
|
2
|
|
|
2
|
1
|
945
|
my ($self) = @_; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
my %natural_frequencies = map { |
165
|
|
|
|
|
|
|
$_ => { |
166
|
|
|
|
|
|
|
$self->_tonic_frequency * eval $self->_ratio_index->{$_} . '' |
167
|
|
|
|
|
|
|
=> { $self->_ratio_index->{$_} => $Music::Intervals::Ratios::ratio->{$_}{name} } |
168
|
|
|
|
|
|
|
} |
169
|
2
|
|
|
|
|
3
|
} @{ $self->notes }; |
|
4
|
|
|
|
|
109
|
|
|
2
|
|
|
|
|
10
|
|
170
|
|
|
|
|
|
|
|
171
|
2
|
|
|
|
|
58
|
return \%natural_frequencies; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub natural_intervals { |
176
|
3
|
|
|
3
|
1
|
861
|
my ($self) = @_; |
177
|
|
|
|
|
|
|
|
178
|
3
|
|
|
|
|
6
|
my %dyads = %{ $self->_dyads }; |
|
3
|
|
|
|
|
61
|
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
my %natural_intervals = map { |
181
|
3
|
|
|
|
|
6
|
$_ => { |
182
|
|
|
|
|
|
|
$dyads{$_}->{natural} => $self->_ratio_name_index->{ $dyads{$_}->{natural} }{name} |
183
|
|
|
|
|
|
|
} |
184
|
4
|
|
|
|
|
78
|
} keys %dyads; |
185
|
|
|
|
|
|
|
|
186
|
3
|
|
|
|
|
58
|
return \%natural_intervals; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub natural_prime_factors { |
191
|
2
|
|
|
2
|
1
|
270
|
my ($self) = @_; |
192
|
|
|
|
|
|
|
|
193
|
2
|
|
|
|
|
5
|
my %dyads = %{ $self->_dyads }; |
|
2
|
|
|
|
|
40
|
|
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
my %natural_prime_factors = map { |
196
|
2
|
|
|
|
|
18
|
$_ => { |
197
|
|
|
|
|
|
|
$dyads{$_}->{natural} => $self->ratio_factorize( $dyads{$_}->{natural} ) |
198
|
3
|
|
|
|
|
11
|
} |
199
|
|
|
|
|
|
|
} keys %dyads; |
200
|
|
|
|
|
|
|
|
201
|
2
|
|
|
|
|
14
|
return \%natural_prime_factors; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub dyads { |
206
|
5
|
|
|
5
|
1
|
510
|
my $self = shift; |
207
|
5
|
|
|
|
|
19
|
my ($c) = @_; |
208
|
|
|
|
|
|
|
|
209
|
5
|
100
|
|
|
|
14
|
return () if @$c <= 1; |
210
|
|
|
|
|
|
|
|
211
|
4
|
|
|
|
|
15
|
my @pairs = combinations( $c, 2 ); |
212
|
|
|
|
|
|
|
|
213
|
4
|
|
|
|
|
611
|
my %dyads; |
214
|
4
|
|
|
|
|
8
|
for my $i (@pairs) { |
215
|
|
|
|
|
|
|
# Construct our "dyadic" fraction. |
216
|
8
|
|
|
|
|
314
|
my $numerator = Number::Fraction->new( $self->_ratio_index->{ $i->[1] } ); |
217
|
8
|
|
|
|
|
5426
|
my $denominator = Number::Fraction->new( $self->_ratio_index->{ $i->[0] } ); |
218
|
8
|
|
|
|
|
1559
|
my $fraction = $numerator / $denominator; |
219
|
|
|
|
|
|
|
|
220
|
8
|
|
|
|
|
1411
|
my $str = $fraction->to_string; |
221
|
|
|
|
|
|
|
# Handle the octave. |
222
|
8
|
100
|
|
|
|
73
|
$str .= '/1' if $fraction->to_string eq 2; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Calculate both natural and equal temperament values for our ratio. |
225
|
|
|
|
|
|
|
$dyads{"@$i"} = { |
226
|
|
|
|
|
|
|
natural => $str, |
227
|
|
|
|
|
|
|
# The value is either the known pitch ratio or ... |
228
|
|
|
|
|
|
|
eq_tempered => |
229
|
|
|
|
|
|
|
( name2freq( $i->[1] . $self->_octave ) || ( $self->_concert * $self->_note_index->{ $i->[1] } ) ) |
230
|
|
|
|
|
|
|
/ |
231
|
8
|
|
66
|
|
|
85
|
( name2freq( $i->[0] . $self->_octave ) || ( $self->_concert * $self->_note_index->{ $i->[0] } ) ), |
|
|
|
33
|
|
|
|
|
232
|
|
|
|
|
|
|
}; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
4
|
|
|
|
|
180
|
return %dyads; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub ratio_factorize { |
240
|
4
|
|
|
4
|
1
|
1358
|
my ($self, $dyad) = @_; |
241
|
|
|
|
|
|
|
|
242
|
4
|
|
|
|
|
11
|
my ( $numerator, $denominator ) = split /\//, $dyad; |
243
|
4
|
|
|
|
|
16
|
$numerator = [ prime_factors($numerator) ]; |
244
|
4
|
|
|
|
|
10
|
$denominator = [ prime_factors($denominator) ]; |
245
|
|
|
|
|
|
|
|
246
|
4
|
|
|
|
|
63
|
return sprintf( '(%s) / (%s)', |
247
|
|
|
|
|
|
|
join( '*', @$numerator ), |
248
|
|
|
|
|
|
|
join( '*', @$denominator ) |
249
|
|
|
|
|
|
|
); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub by_name { |
254
|
2
|
|
|
2
|
1
|
382
|
my ( $self, $name ) = @_; |
255
|
2
|
|
|
|
|
15
|
return $Music::Intervals::Ratios::ratio->{$name}; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
sub by_ratio { |
260
|
1
|
|
|
1
|
1
|
3
|
my ( $self, $ratio ) = @_; |
261
|
1
|
|
|
|
|
21
|
return $self->_ratio_name_index->{$ratio}; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub by_description { |
266
|
1
|
|
|
1
|
1
|
4
|
my ( $self, $string ) = @_; |
267
|
1
|
|
|
|
|
4
|
$string = lc $string; |
268
|
1
|
|
|
|
|
2
|
my %matches; |
269
|
1
|
|
|
|
|
33
|
for my $ratio (keys %$Music::Intervals::Ratios::ratio) { |
270
|
447
|
|
|
|
|
473
|
my $found = $Music::Intervals::Ratios::ratio->{$ratio}; |
271
|
|
|
|
|
|
|
$matches{$ratio} = $found |
272
|
447
|
100
|
|
|
|
831
|
if lc($found->{name}) =~ /$string/; |
273
|
|
|
|
|
|
|
} |
274
|
1
|
|
|
|
|
23
|
return \%matches; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
1; |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
__END__ |