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__ |