line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# -*- Perl -*- |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# "Copian" tension analysis for 12-pitch material in equal temperament |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
package Music::Tension::Cope; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '1.03'; |
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
129977
|
use strict; |
|
2
|
|
|
|
|
17
|
|
|
2
|
|
|
|
|
60
|
|
10
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
58
|
|
11
|
2
|
|
|
2
|
|
12
|
use Carp qw/croak/; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
86
|
|
12
|
2
|
|
|
2
|
|
10
|
use Scalar::Util qw/looks_like_number/; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
81
|
|
13
|
|
|
|
|
|
|
|
14
|
2
|
|
|
2
|
|
1047
|
use parent qw(Music::Tension); |
|
2
|
|
|
|
|
610
|
|
|
2
|
|
|
|
|
12
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my $DEG_IN_SCALE = 12; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
######################################################################## |
19
|
|
|
|
|
|
|
# |
20
|
|
|
|
|
|
|
# METHODS |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub new { |
23
|
9
|
|
|
9
|
1
|
313
|
my ( $class, %param ) = @_; |
24
|
9
|
|
|
|
|
39
|
my $self = $class->SUPER::new(%param); |
25
|
|
|
|
|
|
|
|
26
|
9
|
100
|
|
|
|
20
|
if ( exists $param{duration_weight} ) { |
27
|
|
|
|
|
|
|
croak "duration_weight must be a number" |
28
|
2
|
100
|
|
|
|
18
|
if !looks_like_number $param{duration_weight}; |
29
|
1
|
|
|
|
|
3
|
$self->{_duration_weight} = $param{duration_weight}; |
30
|
|
|
|
|
|
|
} else { |
31
|
7
|
|
|
|
|
22
|
$self->{_duration_weight} = 0.1; |
32
|
|
|
|
|
|
|
} |
33
|
|
|
|
|
|
|
|
34
|
8
|
100
|
|
|
|
14
|
if ( exists $param{metric_weight} ) { |
35
|
|
|
|
|
|
|
croak "metric_weight must be a number" |
36
|
2
|
100
|
|
|
|
15
|
if !looks_like_number $param{metric_weight}; |
37
|
1
|
|
|
|
|
3
|
$self->{_metric_weight} = $param{metric_weight}; |
38
|
|
|
|
|
|
|
} else { |
39
|
6
|
|
|
|
|
14
|
$self->{_metric_weight} = 0.1; |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
7
|
100
|
|
|
|
20
|
if ( exists $param{octave_adjust} ) { |
43
|
|
|
|
|
|
|
croak "octave_adjust must be a number" |
44
|
3
|
100
|
|
|
|
22
|
if !looks_like_number $param{octave_adjust}; |
45
|
2
|
|
|
|
|
5
|
$self->{_octave_adjust} = $param{octave_adjust}; |
46
|
|
|
|
|
|
|
} else { |
47
|
4
|
|
|
|
|
20
|
$self->{_octave_adjust} = -0.02; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
6
|
100
|
|
|
|
13
|
if ( exists $param{tensions} ) { |
51
|
|
|
|
|
|
|
croak "tensions must be a hash reference" |
52
|
5
|
100
|
100
|
|
|
40
|
unless defined $param{tensions} and ref $param{tensions} eq 'HASH'; |
53
|
3
|
|
|
|
|
11
|
for my $i ( 0 .. 11 ) { |
54
|
|
|
|
|
|
|
croak "tensions must include all intervals from 0 through 11" |
55
|
31
|
100
|
|
|
|
65
|
if !exists $param{tensions}->{$i}; |
56
|
|
|
|
|
|
|
} |
57
|
2
|
|
|
|
|
5
|
$self->{_tensions} = $param{tensions}; |
58
|
|
|
|
|
|
|
} else { |
59
|
|
|
|
|
|
|
# default interval tensions taken from "Computer Models of |
60
|
|
|
|
|
|
|
# Musical Creativity", Cope, p.229-230, from least tension (0.0) |
61
|
|
|
|
|
|
|
# to greatest (1.0), less if greater than an octave |
62
|
|
|
|
|
|
|
$self->{_tensions} = { |
63
|
1
|
|
|
|
|
12
|
0 => 0.0, |
64
|
|
|
|
|
|
|
1 => 1.0, |
65
|
|
|
|
|
|
|
2 => 0.8, |
66
|
|
|
|
|
|
|
3 => 0.225, |
67
|
|
|
|
|
|
|
4 => 0.2, |
68
|
|
|
|
|
|
|
5 => 0.55, |
69
|
|
|
|
|
|
|
6 => 0.65, |
70
|
|
|
|
|
|
|
7 => 0.1, |
71
|
|
|
|
|
|
|
8 => 0.275, |
72
|
|
|
|
|
|
|
9 => 0.25, |
73
|
|
|
|
|
|
|
10 => 0.7, |
74
|
|
|
|
|
|
|
11 => 0.9, |
75
|
|
|
|
|
|
|
}; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
3
|
|
|
|
|
7
|
bless $self, $class; |
79
|
3
|
|
|
|
|
9
|
return $self; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# approach tension - horizontal tension, I'm assuming harmonic function, |
83
|
|
|
|
|
|
|
# therefore limit to intervals in same register. |
84
|
|
|
|
|
|
|
sub approach { |
85
|
4
|
|
|
4
|
1
|
69
|
my ( $self, $p1 ) = @_; |
86
|
4
|
100
|
|
|
|
22
|
croak "pitch is required" if !defined $p1; |
87
|
3
|
100
|
|
|
|
25
|
croak "pitch must be integer" if $p1 !~ m/^-?[0-9]+$/; |
88
|
|
|
|
|
|
|
|
89
|
2
|
|
|
|
|
8
|
$self->pitches( 0, abs($p1) % $DEG_IN_SCALE ); |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# tension over durations |
93
|
|
|
|
|
|
|
sub duration { |
94
|
7
|
|
|
7
|
1
|
144
|
my ( $self, $input, $duration ) = @_; |
95
|
|
|
|
|
|
|
|
96
|
7
|
100
|
100
|
|
|
75
|
croak "duration must be a positive value" |
|
|
|
100
|
|
|
|
|
97
|
|
|
|
|
|
|
if !defined $duration |
98
|
|
|
|
|
|
|
or !looks_like_number($duration) |
99
|
|
|
|
|
|
|
or $duration <= 0; |
100
|
|
|
|
|
|
|
|
101
|
4
|
|
|
|
|
7
|
my $tension; |
102
|
4
|
100
|
|
|
|
16
|
if ( ref $input eq 'ARRAY' ) { |
|
|
100
|
|
|
|
|
|
103
|
1
|
|
|
|
|
3
|
$tension = $self->vertical($input); |
104
|
|
|
|
|
|
|
} elsif ( looks_like_number($input) ) { |
105
|
2
|
|
|
|
|
6
|
$tension = $input; |
106
|
|
|
|
|
|
|
} else { |
107
|
1
|
|
|
|
|
9
|
croak "unknown pitch set or prior tension value"; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# p.232-233 [Cope 2005] - this result "is then added to any grouping's |
111
|
|
|
|
|
|
|
# accumulated tension weighting" |
112
|
|
|
|
|
|
|
return $self->{_duration_weight} * $duration + |
113
|
3
|
|
|
|
|
16
|
$self->{_duration_weight} * $tension; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# KLUGE things into whatever is closest equal temperament for now |
117
|
|
|
|
|
|
|
sub frequencies { |
118
|
7
|
|
|
7
|
1
|
208
|
my ( $self, $f1, $f2 ) = @_; |
119
|
7
|
100
|
100
|
|
|
47
|
croak "two frequencies required" if !defined $f1 or !defined $f2; |
120
|
5
|
100
|
100
|
|
|
65
|
croak "frequencies must be positive numbers" |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
121
|
|
|
|
|
|
|
if !looks_like_number $f1 |
122
|
|
|
|
|
|
|
or !looks_like_number $f2 |
123
|
|
|
|
|
|
|
or $f1 < 0 |
124
|
|
|
|
|
|
|
or $f2 < 0; |
125
|
|
|
|
|
|
|
|
126
|
1
|
|
|
|
|
9
|
$self->pitches( map $self->freq2pitch($_), $f1, $f2 ); |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# tension based on where note is within measure p.232 [Cope 2005] |
130
|
|
|
|
|
|
|
sub metric { |
131
|
11
|
|
|
11
|
1
|
223
|
my ( $self, $b, $v ) = @_; |
132
|
11
|
100
|
100
|
|
|
178
|
croak "input must be positive numeric" |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
133
|
|
|
|
|
|
|
if !defined $b |
134
|
|
|
|
|
|
|
or !looks_like_number($b) |
135
|
|
|
|
|
|
|
or $b <= 0 |
136
|
|
|
|
|
|
|
or !defined $v |
137
|
|
|
|
|
|
|
or !looks_like_number($v) |
138
|
|
|
|
|
|
|
or $v <= 0; |
139
|
|
|
|
|
|
|
|
140
|
5
|
|
|
|
|
39
|
return ( $b * $self->{_metric_weight} ) / $v; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# tension for two pitches |
144
|
|
|
|
|
|
|
sub pitches { |
145
|
37
|
|
|
37
|
1
|
227
|
my ( $self, $p1, $p2 ) = @_; |
146
|
37
|
100
|
100
|
|
|
148
|
croak "two pitches required" if !defined $p1 or !defined $p2; |
147
|
34
|
100
|
100
|
|
|
221
|
croak "pitches must be integers" |
148
|
|
|
|
|
|
|
if $p1 !~ m/^-?[0-9]+$/ |
149
|
|
|
|
|
|
|
or $p2 !~ m/^-?[0-9]+$/; |
150
|
|
|
|
|
|
|
|
151
|
32
|
|
|
|
|
59
|
my $interval = abs( $p2 - $p1 ); |
152
|
32
|
|
|
|
|
66
|
my $octave = int( $interval / $DEG_IN_SCALE ); |
153
|
|
|
|
|
|
|
my $tension = |
154
|
|
|
|
|
|
|
$self->{_tensions}->{ $interval % $DEG_IN_SCALE } + |
155
|
32
|
100
|
|
|
|
91
|
( $octave > 0 ? $self->{_octave_adjust} : 0 ); |
156
|
32
|
100
|
|
|
|
66
|
$tension = 0 if $tension < 0; |
157
|
|
|
|
|
|
|
|
158
|
32
|
|
|
|
|
93
|
return $tension; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# tension from first note to all others above it in a passed pitch set. |
162
|
|
|
|
|
|
|
# returns sum, min, max, and an array ref of tensions, unless just the |
163
|
|
|
|
|
|
|
# sum is desired by context |
164
|
|
|
|
|
|
|
sub vertical { |
165
|
7
|
|
|
7
|
1
|
115
|
my ( $self, $pset ) = @_; |
166
|
7
|
100
|
100
|
|
|
50
|
croak "pitch set must be array ref" |
167
|
|
|
|
|
|
|
unless defined $pset and ref $pset eq 'ARRAY'; |
168
|
5
|
100
|
|
|
|
22
|
croak "pitch set must contain multiple elements" if @$pset < 2; |
169
|
4
|
|
|
|
|
13
|
my @pcs = @$pset; |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# reposition pitches upwards if subsequent lower than the |
172
|
|
|
|
|
|
|
# initial pitch |
173
|
4
|
|
|
|
|
11
|
for my $i ( 1 .. $#pcs ) { |
174
|
12
|
100
|
|
|
|
29
|
if ( $pcs[$i] < $pcs[0] ) { |
175
|
8
|
|
|
|
|
23
|
$pcs[$i] += $DEG_IN_SCALE + |
176
|
|
|
|
|
|
|
( int( ( $pcs[0] - $pcs[$i] - 1 ) / $DEG_IN_SCALE ) ) * $DEG_IN_SCALE; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
4
|
|
|
|
|
8
|
my $min = ~0; |
181
|
4
|
|
|
|
|
7
|
my $max = 0; |
182
|
4
|
|
|
|
|
5
|
my ( @tensions, $sum ); |
183
|
4
|
|
|
|
|
9
|
for my $j ( 1 .. $#pcs ) { |
184
|
12
|
|
|
|
|
26
|
my $t = $self->pitches( $pcs[0], $pcs[$j] ); |
185
|
12
|
|
|
|
|
18
|
$sum += $t; |
186
|
12
|
100
|
|
|
|
24
|
$min = $t if $t < $min; |
187
|
12
|
100
|
|
|
|
24
|
$max = $t if $t > $max; |
188
|
12
|
|
|
|
|
23
|
push @tensions, $t; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
4
|
100
|
|
|
|
50
|
return wantarray ? ( $sum, $min, $max, \@tensions ) : $sum; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
1; |
195
|
|
|
|
|
|
|
__END__ |