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