File Coverage

blib/lib/Music/AtonalUtil.pm
Criterion Covered Total %
statement 533 587 90.8
branch 274 328 83.5
condition 78 87 89.6
subroutine 55 56 98.2
pod 45 45 100.0
total 985 1103 89.3


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # Code for atonal music analysis and composition (and a certain
4             # accumulation of somewhat related utility code, in the best fashion of
5             # that kitchen drawer).
6              
7             package Music::AtonalUtil;
8              
9 3     3   330072 use 5.010;
  3         38  
10 3     3   13 use strict;
  3         5  
  3         144  
11 3     3   15 use warnings;
  3         7  
  3         91  
12              
13             # as Math::Combinatorics does not preserve input order in return values
14 3     3   1399 use Algorithm::Combinatorics qw/combinations/;
  3         10018  
  3         260  
15 3     3   33 use Carp qw/croak/;
  3         6  
  3         127  
16 3     3   16 use List::Util qw/shuffle uniqnum/;
  3         5  
  3         309  
17 3     3   19 use Scalar::Util qw/looks_like_number refaddr/;
  3         5  
  3         21756  
18              
19             our $VERSION = '1.18';
20              
21             my $DEG_IN_SCALE = 12;
22              
23             # Forte Number to prime form mapping. These are mostly in agreement with
24             # Appendix 2, Table II in "Basic Atonal Theory" (rahn1980) by John Rahn
25             # (p.140-143), and also against Appendix 1 in "The Structure of Atonal
26             # Music" (forte1973) by Allen Forte (p.179-181), though Rahn and Forte use
27             # different methods and thus calculate different prime forms in a few
28             # cases. See t/forte2pcs2forte.t for tests of these against what
29             # prime_form() calculates. This code uses the Rahn method (though still
30             # calls them "Forte Numbers" instead of the perhaps more appropriate
31             # "Rahn Number").
32             #
33             # By mostly, my calculation disagrees with rahn1980 for 7-Z18, 7-20, and
34             # 8-26 (by eyeball inspection). These three look to be typos in
35             # rahn1980, as in each case Rahn used the Forte form.
36             #
37             # sorting is to align with the table in rahn1980
38             our %FORTE2PCS = (
39             # trichords (complement nonachords)
40             '3-1' => [ 0, 1, 2 ],
41             '3-2' => [ 0, 1, 3 ],
42             '3-3' => [ 0, 1, 4 ],
43             '3-4' => [ 0, 1, 5 ],
44             '3-5' => [ 0, 1, 6 ],
45             '3-6' => [ 0, 2, 4 ],
46             '3-7' => [ 0, 2, 5 ],
47             '3-8' => [ 0, 2, 6 ],
48             '3-9' => [ 0, 2, 7 ],
49             '3-10' => [ 0, 3, 6 ],
50             '3-11' => [ 0, 3, 7 ],
51             '3-12' => [ 0, 4, 8 ],
52             # nonachords (trichords)
53             '9-1' => [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ],
54             '9-2' => [ 0, 1, 2, 3, 4, 5, 6, 7, 9 ],
55             '9-3' => [ 0, 1, 2, 3, 4, 5, 6, 8, 9 ],
56             '9-4' => [ 0, 1, 2, 3, 4, 5, 7, 8, 9 ],
57             '9-5' => [ 0, 1, 2, 3, 4, 6, 7, 8, 9 ],
58             '9-6' => [ 0, 1, 2, 3, 4, 5, 6, 8, 10 ],
59             '9-7' => [ 0, 1, 2, 3, 4, 5, 7, 8, 10 ],
60             '9-8' => [ 0, 1, 2, 3, 4, 6, 7, 8, 10 ],
61             '9-9' => [ 0, 1, 2, 3, 5, 6, 7, 8, 10 ],
62             '9-10' => [ 0, 1, 2, 3, 4, 6, 7, 9, 10 ],
63             '9-11' => [ 0, 1, 2, 3, 5, 6, 7, 9, 10 ],
64             '9-12' => [ 0, 1, 2, 4, 5, 6, 8, 9, 10 ],
65             # tetrachords (octachords)
66             '4-1' => [ 0, 1, 2, 3 ],
67             '4-2' => [ 0, 1, 2, 4 ],
68             '4-4' => [ 0, 1, 2, 5 ],
69             '4-5' => [ 0, 1, 2, 6 ],
70             '4-6' => [ 0, 1, 2, 7 ],
71             '4-3' => [ 0, 1, 3, 4 ],
72             '4-11' => [ 0, 1, 3, 5 ],
73             '4-13' => [ 0, 1, 3, 6 ],
74             '4-Z29' => [ 0, 1, 3, 7 ],
75             '4-7' => [ 0, 1, 4, 5 ],
76             '4-Z15' => [ 0, 1, 4, 6 ],
77             '4-18' => [ 0, 1, 4, 7 ],
78             '4-19' => [ 0, 1, 4, 8 ],
79             '4-8' => [ 0, 1, 5, 6 ],
80             '4-16' => [ 0, 1, 5, 7 ],
81             '4-20' => [ 0, 1, 5, 8 ],
82             '4-9' => [ 0, 1, 6, 7 ],
83             '4-10' => [ 0, 2, 3, 5 ],
84             '4-12' => [ 0, 2, 3, 6 ],
85             '4-14' => [ 0, 2, 3, 7 ],
86             '4-21' => [ 0, 2, 4, 6 ],
87             '4-22' => [ 0, 2, 4, 7 ],
88             '4-24' => [ 0, 2, 4, 8 ],
89             '4-23' => [ 0, 2, 5, 7 ],
90             '4-27' => [ 0, 2, 5, 8 ],
91             '4-25' => [ 0, 2, 6, 8 ],
92             '4-17' => [ 0, 3, 4, 7 ],
93             '4-26' => [ 0, 3, 5, 8 ],
94             '4-28' => [ 0, 3, 6, 9 ],
95             # octachords (tetrachords)
96             '8-1' => [ 0, 1, 2, 3, 4, 5, 6, 7 ],
97             '8-2' => [ 0, 1, 2, 3, 4, 5, 6, 8 ],
98             '8-4' => [ 0, 1, 2, 3, 4, 5, 7, 8 ],
99             '8-5' => [ 0, 1, 2, 3, 4, 6, 7, 8 ],
100             '8-6' => [ 0, 1, 2, 3, 5, 6, 7, 8 ],
101             '8-3' => [ 0, 1, 2, 3, 4, 5, 6, 9 ],
102             '8-11' => [ 0, 1, 2, 3, 4, 5, 7, 9 ],
103             '8-13' => [ 0, 1, 2, 3, 4, 6, 7, 9 ],
104             '8-Z29' => [ 0, 1, 2, 3, 5, 6, 7, 9 ],
105             '8-7' => [ 0, 1, 2, 3, 4, 5, 8, 9 ],
106             '8-Z15' => [ 0, 1, 2, 3, 4, 6, 8, 9 ],
107             '8-18' => [ 0, 1, 2, 3, 5, 6, 8, 9 ],
108             '8-19' => [ 0, 1, 2, 4, 5, 6, 8, 9 ],
109             '8-8' => [ 0, 1, 2, 3, 4, 7, 8, 9 ],
110             '8-16' => [ 0, 1, 2, 3, 5, 7, 8, 9 ],
111             '8-20' => [ 0, 1, 2, 4, 5, 7, 8, 9 ],
112             '8-9' => [ 0, 1, 2, 3, 6, 7, 8, 9 ],
113             '8-10' => [ 0, 2, 3, 4, 5, 6, 7, 9 ],
114             '8-12' => [ 0, 1, 3, 4, 5, 6, 7, 9 ],
115             '8-14' => [ 0, 1, 2, 4, 5, 6, 7, 9 ],
116             '8-21' => [ 0, 1, 2, 3, 4, 6, 8, 10 ],
117             '8-22' => [ 0, 1, 2, 3, 5, 6, 8, 10 ],
118             '8-24' => [ 0, 1, 2, 4, 5, 6, 8, 10 ],
119             '8-23' => [ 0, 1, 2, 3, 5, 7, 8, 10 ],
120             '8-27' => [ 0, 1, 2, 4, 5, 7, 8, 10 ],
121             '8-25' => [ 0, 1, 2, 4, 6, 7, 8, 10 ],
122             '8-17' => [ 0, 1, 3, 4, 5, 6, 8, 9 ],
123             '8-26' => [ 0, 1, 3, 4, 5, 7, 8, 10 ],
124             # '8-26' => [ 0, 1, 2, 4, 5, 7, 9, 10 ], # rahn1980
125             '8-28' => [ 0, 1, 3, 4, 6, 7, 9, 10 ],
126             # pentachords (septachords)
127             '5-1' => [ 0, 1, 2, 3, 4 ],
128             '5-2' => [ 0, 1, 2, 3, 5 ],
129             '5-4' => [ 0, 1, 2, 3, 6 ],
130             '5-5' => [ 0, 1, 2, 3, 7 ],
131             '5-3' => [ 0, 1, 2, 4, 5 ],
132             '5-9' => [ 0, 1, 2, 4, 6 ],
133             '5-Z36' => [ 0, 1, 2, 4, 7 ],
134             '5-13' => [ 0, 1, 2, 4, 8 ],
135             '5-6' => [ 0, 1, 2, 5, 6 ],
136             '5-14' => [ 0, 1, 2, 5, 7 ],
137             '5-Z38' => [ 0, 1, 2, 5, 8 ],
138             '5-7' => [ 0, 1, 2, 6, 7 ],
139             '5-15' => [ 0, 1, 2, 6, 8 ],
140             '5-10' => [ 0, 1, 3, 4, 6 ],
141             '5-16' => [ 0, 1, 3, 4, 7 ],
142             '5-Z17' => [ 0, 1, 3, 4, 8 ],
143             '5-Z12' => [ 0, 1, 3, 5, 6 ],
144             '5-24' => [ 0, 1, 3, 5, 7 ],
145             '5-27' => [ 0, 1, 3, 5, 8 ],
146             '5-19' => [ 0, 1, 3, 6, 7 ],
147             '5-29' => [ 0, 1, 3, 6, 8 ],
148             '5-31' => [ 0, 1, 3, 6, 9 ],
149             '5-Z18' => [ 0, 1, 4, 5, 7 ],
150             '5-21' => [ 0, 1, 4, 5, 8 ],
151             '5-30' => [ 0, 1, 4, 6, 8 ],
152             '5-32' => [ 0, 1, 4, 6, 9 ],
153             '5-22' => [ 0, 1, 4, 7, 8 ],
154             '5-20' => [ 0, 1, 5, 6, 8 ], # 0,1,3,7,8 forte1973
155             '5-8' => [ 0, 2, 3, 4, 6 ],
156             '5-11' => [ 0, 2, 3, 4, 7 ],
157             '5-23' => [ 0, 2, 3, 5, 7 ],
158             '5-25' => [ 0, 2, 3, 5, 8 ],
159             '5-28' => [ 0, 2, 3, 6, 8 ],
160             '5-26' => [ 0, 2, 4, 5, 8 ],
161             '5-33' => [ 0, 2, 4, 6, 8 ],
162             '5-34' => [ 0, 2, 4, 6, 9 ],
163             '5-35' => [ 0, 2, 4, 7, 9 ],
164             '5-Z37' => [ 0, 3, 4, 5, 8 ],
165             # septachords (pentachords)
166             '7-1' => [ 0, 1, 2, 3, 4, 5, 6 ],
167             '7-2' => [ 0, 1, 2, 3, 4, 5, 7 ],
168             '7-4' => [ 0, 1, 2, 3, 4, 6, 7 ],
169             '7-5' => [ 0, 1, 2, 3, 5, 6, 7 ],
170             '7-3' => [ 0, 1, 2, 3, 4, 5, 8 ],
171             '7-9' => [ 0, 1, 2, 3, 4, 6, 8 ],
172             '7-Z36' => [ 0, 1, 2, 3, 5, 6, 8 ],
173             '7-13' => [ 0, 1, 2, 4, 5, 6, 8 ],
174             '7-6' => [ 0, 1, 2, 3, 4, 7, 8 ],
175             '7-14' => [ 0, 1, 2, 3, 5, 7, 8 ],
176             '7-Z38' => [ 0, 1, 2, 4, 5, 7, 8 ],
177             '7-7' => [ 0, 1, 2, 3, 6, 7, 8 ],
178             '7-15' => [ 0, 1, 2, 4, 6, 7, 8 ],
179             '7-10' => [ 0, 1, 2, 3, 4, 6, 9 ],
180             '7-16' => [ 0, 1, 2, 3, 5, 6, 9 ],
181             '7-Z17' => [ 0, 1, 2, 4, 5, 6, 9 ],
182             '7-Z12' => [ 0, 1, 2, 3, 4, 7, 9 ],
183             '7-24' => [ 0, 1, 2, 3, 5, 7, 9 ],
184             '7-27' => [ 0, 1, 2, 4, 5, 7, 9 ],
185             '7-19' => [ 0, 1, 2, 3, 6, 7, 9 ],
186             '7-29' => [ 0, 1, 2, 4, 6, 7, 9 ],
187             '7-31' => [ 0, 1, 3, 4, 6, 7, 9 ],
188             '7-Z18' => [ 0, 1, 4, 5, 6, 7, 9 ], # 0,1,2,3,5,8,9 forte1973
189             # '7-Z18' => [ 0, 1, 2, 3, 5, 8, 9 ], # rahn1980
190             '7-21' => [ 0, 1, 2, 4, 5, 8, 9 ],
191             '7-30' => [ 0, 1, 2, 4, 6, 8, 9 ],
192             '7-32' => [ 0, 1, 3, 4, 6, 8, 9 ],
193             '7-22' => [ 0, 1, 2, 5, 6, 8, 9 ],
194             '7-20' => [ 0, 1, 2, 5, 6, 7, 9 ], # 0,1,2,4,7,8,9 forte1973
195             # '7-20' => [ 0, 1, 2, 4, 7, 8, 9 ], # rahn1980
196             '7-8' => [ 0, 2, 3, 4, 5, 6, 8 ],
197             '7-11' => [ 0, 1, 3, 4, 5, 6, 8 ],
198             '7-23' => [ 0, 2, 3, 4, 5, 7, 9 ],
199             '7-25' => [ 0, 2, 3, 4, 6, 7, 9 ],
200             '7-28' => [ 0, 1, 3, 5, 6, 7, 9 ],
201             '7-26' => [ 0, 1, 3, 4, 5, 7, 9 ],
202             '7-33' => [ 0, 1, 2, 4, 6, 8, 10 ],
203             '7-34' => [ 0, 1, 3, 4, 6, 8, 10 ],
204             '7-35' => [ 0, 1, 3, 5, 6, 8, 10 ],
205             '7-Z37' => [ 0, 1, 3, 4, 5, 7, 8 ],
206             # hexachords
207             '6-1' => [ 0, 1, 2, 3, 4, 5 ],
208             '6-2' => [ 0, 1, 2, 3, 4, 6 ],
209             '6-Z36' => [ 0, 1, 2, 3, 4, 7 ],
210             '6-Z3' => [ 0, 1, 2, 3, 4, 7 ], # 0,1,2,3,5,6 forte1973
211             '6-Z37' => [ 0, 1, 2, 3, 4, 8 ],
212             '6-Z4' => [ 0, 1, 2, 3, 4, 8 ], # 0,1,2,4,5,6 forte1973
213             '6-9' => [ 0, 1, 2, 3, 5, 7 ],
214             '6-Z40' => [ 0, 1, 2, 3, 5, 8 ],
215             '6-Z11' => [ 0, 1, 2, 3, 5, 8 ], # 0,1,2,4,5,7 forte1973
216             '6-5' => [ 0, 1, 2, 3, 6, 7 ],
217             '6-Z41' => [ 0, 1, 2, 3, 6, 8 ],
218             '6-Z12' => [ 0, 1, 2, 3, 6, 8 ], # 0,1,2,4,6,7 forte1973
219             '6-Z42' => [ 0, 1, 2, 3, 6, 9 ],
220             '6-Z13' => [ 0, 1, 2, 3, 6, 9 ], # 0,1,3,4,6,7 forte1973
221             '6-Z38' => [ 0, 1, 2, 3, 7, 8 ],
222             '6-Z6' => [ 0, 1, 2, 3, 7, 8 ], # 0,1,3,5,6,7 forte1973
223             '6-15' => [ 0, 1, 2, 4, 5, 8 ],
224             '6-22' => [ 0, 1, 2, 4, 6, 8 ],
225             '6-Z46' => [ 0, 1, 2, 4, 6, 9 ],
226             '6-Z24' => [ 0, 1, 2, 4, 6, 9 ], # 0,1,3,4,6,8 forte1973
227             '6-Z17' => [ 0, 1, 2, 4, 7, 8 ],
228             '6-Z43' => [ 0, 1, 2, 4, 7, 8 ], # 0,1,2,5,6,8 forte1973
229             '6-Z47' => [ 0, 1, 2, 4, 7, 9 ],
230             '6-Z25' => [ 0, 1, 2, 4, 7, 9 ], # 0,1,3,5,6,8 forte1973
231             '6-Z44' => [ 0, 1, 2, 5, 6, 9 ],
232             '6-Z19' => [ 0, 1, 2, 5, 6, 9 ], # 0,1,3,4,7,8 forte1973
233             '6-18' => [ 0, 1, 2, 5, 7, 8 ],
234             '6-Z48' => [ 0, 1, 2, 5, 7, 9 ],
235             '6-Z26' => [ 0, 1, 2, 5, 7, 9 ], # 0,1,3,5,7,8 forte1973
236             '6-7' => [ 0, 1, 2, 6, 7, 8 ],
237             '6-Z10' => [ 0, 1, 3, 4, 5, 7 ],
238             '6-Z39' => [ 0, 1, 3, 4, 5, 7 ], # 0,2,3,4,5,8 forte1973
239             '6-14' => [ 0, 1, 3, 4, 5, 8 ],
240             '6-27' => [ 0, 1, 3, 4, 6, 9 ],
241             '6-Z49' => [ 0, 1, 3, 4, 7, 9 ],
242             '6-Z28' => [ 0, 1, 3, 4, 7, 9 ], # 0,1,3,5,6,9 forte1973
243             '6-34' => [ 0, 1, 3, 5, 7, 9 ],
244             '6-31' => [ 0, 1, 4, 5, 7, 9 ], # 0,1,3,5,8,9 forte1973
245             '6-30' => [ 0, 1, 3, 6, 7, 9 ],
246             '6-Z29' => [ 0, 2, 3, 6, 7, 9 ], # 0,1,3,6,8,9 forte1973
247             '6-Z50' => [ 0, 2, 3, 6, 7, 9 ], # 0,1,4,6,7,9 forte1973
248             '6-16' => [ 0, 1, 4, 5, 6, 8 ],
249             '6-20' => [ 0, 1, 4, 5, 8, 9 ],
250             '6-8' => [ 0, 2, 3, 4, 5, 7 ],
251             '6-21' => [ 0, 2, 3, 4, 6, 8 ],
252             '6-Z45' => [ 0, 2, 3, 4, 6, 9 ],
253             '6-Z23' => [ 0, 2, 3, 4, 6, 9 ], # 0,2,3,5,6,8 forte1973
254             '6-33' => [ 0, 2, 3, 5, 7, 9 ],
255             '6-32' => [ 0, 2, 4, 5, 7, 9 ],
256             '6-35' => [ 0, 2, 4, 6, 8, 10 ],
257             );
258              
259             # Hexchords here are problematic on account of mutual complementary sets
260             # (different Forte Numbers for the same pitch set).
261             # TODO review and use what Rahn lists as first in table on p.142-3.
262             # TODO Rahn puts 6-Z36 and 6-Z3 together, but my code is producing
263             # two different prime forms for those...
264             #
265             # sorting is to align with the table in rahn1980
266             our %PCS2FORTE = (
267             # trichords (complement nonachords)
268             '0,1,2' => '3-1',
269             '0,1,3' => '3-2',
270             '0,1,4' => '3-3',
271             '0,1,5' => '3-4',
272             '0,1,6' => '3-5',
273             '0,2,4' => '3-6',
274             '0,2,5' => '3-7',
275             '0,2,6' => '3-8',
276             '0,2,7' => '3-9',
277             '0,3,6' => '3-10',
278             '0,3,7' => '3-11',
279             '0,4,8' => '3-12',
280             # nonachords (trichords)
281             '0,1,2,3,4,5,6,7,8' => '9-1',
282             '0,1,2,3,4,5,6,7,9' => '9-2',
283             '0,1,2,3,4,5,6,8,9' => '9-3',
284             '0,1,2,3,4,5,7,8,9' => '9-4',
285             '0,1,2,3,4,6,7,8,9' => '9-5',
286             '0,1,2,3,4,5,6,8,10' => '9-6',
287             '0,1,2,3,4,5,7,8,10' => '9-7',
288             '0,1,2,3,4,6,7,8,10' => '9-8',
289             '0,1,2,3,5,6,7,8,10' => '9-9',
290             '0,1,2,3,4,6,7,9,10' => '9-10',
291             '0,1,2,3,5,6,7,9,10' => '9-11',
292             '0,1,2,4,5,6,8,9,10' => '9-12',
293             # tetrachords (octachords)
294             '0,1,2,3' => '4-1',
295             '0,1,2,4' => '4-2',
296             '0,1,2,5' => '4-4',
297             '0,1,2,6' => '4-5',
298             '0,1,2,7' => '4-6',
299             '0,1,3,4' => '4-3',
300             '0,1,3,5' => '4-11',
301             '0,1,3,6' => '4-13',
302             '0,1,3,7' => '4-Z29',
303             '0,1,4,5' => '4-7',
304             '0,1,4,6' => '4-Z15',
305             '0,1,4,7' => '4-18',
306             '0,1,4,8' => '4-19',
307             '0,1,5,6' => '4-8',
308             '0,1,5,7' => '4-16',
309             '0,1,5,8' => '4-20',
310             '0,1,6,7' => '4-9',
311             '0,2,3,5' => '4-10',
312             '0,2,3,6' => '4-12',
313             '0,2,3,7' => '4-14',
314             '0,2,4,6' => '4-21',
315             '0,2,4,7' => '4-22',
316             '0,2,4,8' => '4-24',
317             '0,2,5,7' => '4-23',
318             '0,2,5,8' => '4-27',
319             '0,2,6,8' => '4-25',
320             '0,3,4,7' => '4-17',
321             '0,3,5,8' => '4-26',
322             '0,3,6,9' => '4-28',
323             # octachords (tetrachords)
324             '0,1,2,3,4,5,6,7' => '8-1',
325             '0,1,2,3,4,5,6,8' => '8-2',
326             '0,1,2,3,4,5,7,8' => '8-4',
327             '0,1,2,3,4,6,7,8' => '8-5',
328             '0,1,2,3,5,6,7,8' => '8-6',
329             '0,1,2,3,4,5,6,9' => '8-3',
330             '0,1,2,3,4,5,7,9' => '8-11',
331             '0,1,2,3,4,6,7,9' => '8-13',
332             '0,1,2,3,5,6,7,9' => '8-Z29',
333             '0,1,2,3,4,5,8,9' => '8-7',
334             '0,1,2,3,4,6,8,9' => '8-Z15',
335             '0,1,2,3,5,6,8,9' => '8-18',
336             '0,1,2,4,5,6,8,9' => '8-19',
337             '0,1,2,3,4,7,8,9' => '8-8',
338             '0,1,2,3,5,7,8,9' => '8-16',
339             '0,1,2,4,5,7,8,9' => '8-20',
340             '0,1,2,3,6,7,8,9' => '8-9',
341             '0,2,3,4,5,6,7,9' => '8-10',
342             '0,1,3,4,5,6,7,9' => '8-12',
343             '0,1,2,4,5,6,7,9' => '8-14',
344             '0,1,2,3,4,6,8,10' => '8-21',
345             '0,1,2,3,5,6,8,10' => '8-22',
346             '0,1,2,4,5,6,8,10' => '8-24',
347             '0,1,2,3,5,7,8,10' => '8-23',
348             '0,1,2,4,5,7,8,10' => '8-27',
349             '0,1,2,4,6,7,8,10' => '8-25',
350             '0,1,3,4,5,6,8,9' => '8-17',
351             '0,1,3,4,5,7,8,10' => '8-26', # buggy in rahn1980
352             '0,1,3,4,6,7,9,10' => '8-28',
353             # pentachords (septachords)
354             '0,1,2,3,4' => '5-1',
355             '0,1,2,3,5' => '5-2',
356             '0,1,2,3,6' => '5-4',
357             '0,1,2,3,7' => '5-5',
358             '0,1,2,4,5' => '5-3',
359             '0,1,2,4,6' => '5-9',
360             '0,1,2,4,7' => '5-Z36',
361             '0,1,2,4,8' => '5-13',
362             '0,1,2,5,6' => '5-6',
363             '0,1,2,5,7' => '5-14',
364             '0,1,2,5,8' => '5-Z38',
365             '0,1,2,6,7' => '5-7',
366             '0,1,2,6,8' => '5-15',
367             '0,1,3,4,6' => '5-10',
368             '0,1,3,4,7' => '5-16',
369             '0,1,3,4,8' => '5-Z17',
370             '0,1,3,5,6' => '5-Z12',
371             '0,1,3,5,7' => '5-24',
372             '0,1,3,5,8' => '5-27',
373             '0,1,3,6,7' => '5-19',
374             '0,1,3,6,8' => '5-29',
375             '0,1,3,6,9' => '5-31',
376             '0,1,4,5,7' => '5-Z18',
377             '0,1,4,5,8' => '5-21',
378             '0,1,4,6,8' => '5-30',
379             '0,1,4,6,9' => '5-32',
380             '0,1,4,7,8' => '5-22',
381             '0,1,5,6,8' => '5-20',
382             '0,2,3,4,6' => '5-8',
383             '0,2,3,4,7' => '5-11',
384             '0,2,3,5,7' => '5-23',
385             '0,2,3,5,8' => '5-25',
386             '0,2,3,6,8' => '5-28',
387             '0,2,4,5,8' => '5-26',
388             '0,2,4,6,8' => '5-33',
389             '0,2,4,6,9' => '5-34',
390             '0,2,4,7,9' => '5-35',
391             '0,3,4,5,8' => '5-Z37',
392             # septachords (pentachords)
393             '0,1,2,3,4,5,6' => '7-1',
394             '0,1,2,3,4,5,7' => '7-2',
395             '0,1,2,3,4,6,7' => '7-4',
396             '0,1,2,3,5,6,7' => '7-5',
397             '0,1,2,3,4,5,8' => '7-3',
398             '0,1,2,3,4,6,8' => '7-9',
399             '0,1,2,3,5,6,8' => '7-Z36',
400             '0,1,2,4,5,6,8' => '7-13',
401             '0,1,2,3,4,7,8' => '7-6',
402             '0,1,2,3,5,7,8' => '7-14',
403             '0,1,2,4,5,7,8' => '7-Z38',
404             '0,1,2,3,6,7,8' => '7-7',
405             '0,1,2,4,6,7,8' => '7-15',
406             '0,1,2,3,4,6,9' => '7-10',
407             '0,1,2,3,5,6,9' => '7-16',
408             '0,1,2,4,5,6,9' => '7-Z17',
409             '0,1,2,3,4,7,9' => '7-Z12',
410             '0,1,2,3,5,7,9' => '7-24',
411             '0,1,2,4,5,7,9' => '7-27',
412             '0,1,2,3,6,7,9' => '7-19',
413             '0,1,2,4,6,7,9' => '7-29',
414             '0,1,3,4,6,7,9' => '7-31',
415             '0,1,4,5,6,7,9' => '7-Z18', # buggy in rahn1980
416             '0,1,2,4,5,8,9' => '7-21',
417             '0,1,2,4,6,8,9' => '7-30',
418             '0,1,3,4,6,8,9' => '7-32',
419             '0,1,2,5,6,8,9' => '7-22',
420             '0,1,2,5,6,7,9' => '7-20', # buggy in rahn1980
421             '0,2,3,4,5,6,8' => '7-8',
422             '0,1,3,4,5,6,8' => '7-11',
423             '0,2,3,4,5,7,9' => '7-23',
424             '0,2,3,4,6,7,9' => '7-25',
425             '0,1,3,5,6,7,9' => '7-28',
426             '0,1,3,4,5,7,9' => '7-26',
427             '0,1,2,4,6,8,10' => '7-33',
428             '0,1,3,4,6,8,10' => '7-34',
429             '0,1,3,5,6,8,10' => '7-35',
430             '0,1,3,4,5,7,8' => '7-Z37',
431             # hexachords, by first column and then sparse 2nd column
432             '0,1,2,3,4,5' => '6-1',
433             '0,1,2,3,4,6' => '6-2',
434             '0,1,2,3,4,7' => '6-Z36',
435             '0,1,2,3,4,8' => '6-Z37',
436             '0,1,2,3,5,7' => '6-9',
437             '0,1,2,3,5,8' => '6-Z40',
438             '0,1,2,3,6,7' => '6-5',
439             '0,1,2,3,6,8' => '6-Z41',
440             '0,1,2,3,6,9' => '6-Z42',
441             '0,1,2,3,7,8' => '6-Z38',
442             '0,1,2,4,5,8' => '6-15',
443             '0,1,2,4,6,8' => '6-22',
444             '0,1,2,4,6,9' => '6-Z46',
445             '0,1,2,4,7,8' => '6-Z17',
446             '0,1,2,4,7,9' => '6-Z47',
447             '0,1,2,5,6,9' => '6-Z44',
448             '0,1,2,5,7,8' => '6-18',
449             '0,1,2,5,7,9' => '6-Z48',
450             '0,1,2,6,7,8' => '6-7',
451             '0,1,3,4,5,7' => '6-Z10',
452             '0,1,3,4,5,8' => '6-14',
453             '0,1,3,4,6,9' => '6-27',
454             '0,1,3,4,7,9' => '6-Z49',
455             '0,1,3,5,7,9' => '6-34',
456             '0,1,4,5,7,9' => '6-31',
457             '0,1,3,6,7,9' => '6-30',
458             '0,2,3,6,7,9' => '6-Z29',
459             '0,1,4,5,6,8' => '6-16',
460             '0,1,4,5,8,9' => '6-20',
461             '0,2,3,4,5,7' => '6-8',
462             '0,2,3,4,6,8' => '6-21',
463             '0,2,3,4,6,9' => '6-Z45',
464             '0,2,3,5,7,9' => '6-33',
465             '0,2,4,5,7,9' => '6-32',
466             '0,2,4,6,8,10' => '6-35',
467             '0,1,2,3,5,6' => '6-Z3',
468             '0,1,2,4,5,6' => '6-Z4',
469             '0,1,2,4,5,7' => '6-Z11',
470             '0,1,2,4,6,7' => '6-Z12',
471             '0,1,3,4,6,7' => '6-Z13',
472             '0,1,2,5,6,7' => '6-Z6',
473             '0,1,3,4,6,8' => '6-Z24',
474             '0,1,2,5,6,8' => '6-Z43',
475             '0,1,3,5,6,8' => '6-Z25',
476             '0,1,3,4,7,8' => '6-Z19',
477             '0,1,3,5,7,8' => '6-Z26',
478             '0,2,3,4,5,8' => '6-Z39',
479             '0,1,3,5,6,9' => '6-Z28',
480             '0,1,4,6,7,9' => '6-Z50',
481             '0,2,3,5,6,8' => '6-Z23',
482             );
483              
484             # NOTE may need [AB]? at end for what I call "half prime" forms, as
485             # wikipedia has switched to using that form.
486             my $FORTE_NUMBER_RE = qr/[3-9]-[zZ]?\d{1,2}/;
487              
488             ########################################################################
489             #
490             # SUBROUTINES
491              
492             # Utility method for check_melody - takes melody, a list of pitches,
493             # optionally how many notes (beyond that of pitches to audit) to check,
494             # and a code reference that will accept a selection of the melody and
495             # return something that will be tested against the list of pitches
496             # (second argument) for equality: true if match, false if not (and then
497             # a bunch of references containing what failed).
498             sub _apply_melody_rule {
499 8     8   18 my ( $self, $melody, $check_set, $note_count, $code, $flag_sort ) = @_;
500 8   100     21 $flag_sort //= 0;
501              
502             # make equal to the set if less than the set. no high value test as
503             # loop will abort if note_count exceeds the length of melody, below.
504 8   100     16 $note_count //= 0;
505 8 100       15 $note_count = @$check_set if $note_count < @$check_set;
506              
507             # rule is too large for the melody, skip
508 8 50       11 return 1, {} if @$check_set > @$melody;
509              
510 8         16 for my $i ( 0 .. @$melody - @$check_set ) {
511 19         34 my @selection = @{$melody}[ $i .. $i + @$check_set - 1 ];
  19         32  
512              
513 19         30 my $sel_audit = $code->( $self, \@selection );
514 19 100       40 @$sel_audit = sort { $a <=> $b } @$sel_audit if $flag_sort;
  1         4  
515 19 100       49 if ( "@$sel_audit" eq "@$check_set" ) {
516 4         27 return 0, { index => $i, selection => \@selection };
517             }
518              
519 15 100       31 if ( $note_count > @$check_set ) {
520 2         5 for my $count ( @$check_set + 1 .. $note_count ) {
521 7 50       46 last if $i + $count - 1 > $#$melody;
522              
523 7         14 @selection = @{$melody}[ $i .. $i + $count - 1 ];
  7         15  
524 7         17 my $iter = combinations( \@selection, scalar @$check_set );
525              
526 7         257 while ( my $subsel = $iter->next ) {
527 64         456 $sel_audit = $code->( $self, $subsel );
528 64 50       89 @$sel_audit = sort { $a <=> $b } @$sel_audit if $flag_sort;
  0         0  
529 64 100       165 if ( "@$sel_audit" eq "@$check_set" ) {
530 2         14 return 0, { context => \@selection, index => $i, selection => $subsel };
531             }
532             }
533             }
534             }
535             }
536              
537 2         5 return 1, {};
538             }
539              
540             # Like interval class content (ICC) but instead only calculates adjacent
541             # intervals. -- "The Geometry of Musical Rhythm", G.T. Toussaint.
542             # (Perhaps more suitable for rhythm as the adjacent intervals there are
543             # probably more audible than some harmonic between inner voices.)
544             sub adjacent_interval_content {
545 8     8 1 54 my $self = shift;
546 8 100       23 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
547              
548 8         57 my @nset = sort { $a <=> $b } uniqnum @$pset;
  56         69  
549 8 100       47 croak 'pitch set must contain at least two elements' if @nset < 2;
550              
551 7         8 my %aic;
552 7         16 for my $i ( 1 .. $#nset ) {
553 28         55 $aic{ ( $nset[$i] - $nset[ $i - 1 ] ) % $self->{_DEG_IN_SCALE} }++;
554             }
555             # and the wrap-around adjacent interval
556 7 50       15 if ( @nset > 2 ) {
557             $aic{ ( $nset[0] + $self->{_DEG_IN_SCALE} - $nset[-1] )
558 7         15 % $self->{_DEG_IN_SCALE} }++;
559             }
560              
561 7         8 my @aiv;
562 7         17 for my $ics ( 1 .. int( $self->{_DEG_IN_SCALE} / 2 ) ) {
563 56   100     120 push @aiv, $aic{$ics} || 0;
564             }
565              
566 7 100       44 return wantarray ? ( \@aiv, \%aic ) : \@aiv;
567             }
568              
569             # what bands of the Bark scale do the given frequencies belong to? there
570             # are several formula for this in the Wikipedia article, this one
571             # follows Traunmüller 1990 to avoid pulling in trig functions. not sure
572             # if one is supposed to int() or sprintf() round the result to get to an
573             # integer value
574             sub bark_scale {
575 1     1 1 500 shift;
576 1         3 map { ( 26.81 * $_ ) / ( 1960 + $_ ) - 0.53 } @_;
  1         19  
577             }
578              
579             # Utility, converts a scale_degrees-bit number into a pitch set.
580             # 7 3 0
581             # 137 -> 000010001001 -> [0,3,7]
582             sub bits2pcs {
583 1     1 1 617 my ( $self, $bs ) = @_;
584              
585 1         3 my @pset;
586 1         4 for my $p ( 0 .. $self->{_DEG_IN_SCALE} - 1 ) {
587 12 100       22 push @pset, $p if $bs & ( 1 << $p );
588             }
589 1         5 return \@pset;
590             }
591              
592             # Audits a sequence of pitches for suitability, per various checks
593             # passed in via the params hash (based on Smith-Brindle Reginald's
594             # "Serial Composition" discussion of atonal melody construction).
595             sub check_melody {
596 7     7 1 14 my $self = shift;
597 7         10 my $params = shift;
598 7 100       22 my $melody = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
599              
600 7         10 my $rules_applied = 0;
601              
602 7         8 my ( %intervals, @intervals );
603 7         16 for my $i ( 1 .. $#$melody ) {
604 35         56 my $ival = abs $melody->[$i] - $melody->[ $i - 1 ];
605 35         47 $intervals{$ival}++;
606 35         57 push @intervals, $ival;
607             }
608              
609 7 100       15 if ( exists $params->{dup_interval_limit} ) {
610 1         4 for my $icount ( values %intervals ) {
611 1 50       4 if ( $icount >= $params->{dup_interval_limit} ) {
612 1 50       7 return wantarray ? ( 0, "dup_interval_limit" ) : 0;
613             }
614             }
615 0         0 $rules_applied++;
616             }
617              
618 6 100       8 for my $ruleset ( @{ $params->{exclude_interval} || [] } ) {
  6         20  
619             croak "no interval set in exclude_interval rule"
620             if not exists $ruleset->{iset}
621 3 50 33     15 or ref $ruleset->{iset} ne 'ARRAY';
622 3 50       4 next if @{ $ruleset->{iset} } > @intervals;
  3         8  
623              
624             # check (magnitude of the) intervals of the melody. code ref just
625             # returns the literal intervals to compare against what is in the
626             # iset. (other options might be to ICC the intervals, or fold them
627             # into a single register, etc. but that would take more coding.)
628             my ( $ret, $results ) = $self->_apply_melody_rule(
629             \@intervals, $ruleset->{iset}, $ruleset->{in},
630 73     73   74 sub { [ @{ $_[1] } ] },
  73         127  
631 3 100       19 $ruleset->{sort} ? 1 : 0
632             );
633 3 50       15 if ( $ret != 1 ) {
634 3 50       32 return wantarray ? ( 0, "exclude_interval", $results ) : 0;
635             }
636 0         0 $rules_applied++;
637             }
638              
639 3         9 for my $ps_ref ( [qw/exclude_prime prime_form/],
640             [qw/exclude_half_prime half_prime_form/] ) {
641 4         7 my $ps_rule = $ps_ref->[0];
642 4         7 my $ps_method = $ps_ref->[1];
643              
644 4 100       4 for my $ruleset ( @{ $params->{$ps_rule} || [] } ) {
  4         12  
645             croak "no pitch set in $ps_rule rule"
646             if not exists $ruleset->{ps}
647 3 50 33     16 or ref $ruleset->{ps} ne 'ARRAY';
648              
649             # for intervals code, not necessary for pitch set operations, all of
650             # which sort the pitches as part of the calculations involved
651 3         12 delete $ruleset->{sort};
652              
653             # excludes from *any* subset for the given subset magnitudes of the
654             # parent pitch set
655 3 100       5 for my $ss_mag ( @{ $ruleset->{subsets} || [] } ) {
  3         11  
656             croak "subset must be of lesser magnitude than pitch set"
657 2 50       4 if $ss_mag >= @{ $ruleset->{ps} };
  2         14  
658 2   50     9 my $in_ss = $ruleset->{in} // 0;
659 2         3 $in_ss = @{ $ruleset->{ps} }
660 2 50       3 if $in_ss < @{ $ruleset->{ps} };
  2         5  
661             # except scale down to fit smaller subset pitch set
662 2         3 $in_ss -= @{ $ruleset->{ps} } - $ss_mag;
  2         2  
663              
664 2 100       5 next if $in_ss > @$melody;
665              
666 1         4 my $all_subpsets = $self->subsets( $ss_mag, $ruleset->{ps} );
667 1         205 my %seen_s_pset;
668 1         4 for my $s_pset (@$all_subpsets) {
669 3         8 my $s_prime = $self->$ps_method($s_pset);
670 3 50       10 next if $seen_s_pset{"@$s_prime"}++;
671             my ( $ret, $results ) =
672             $self->_apply_melody_rule( $melody, $s_prime,
673 3     3   12 $in_ss, sub { $_[0]->$ps_method( $_[1] ) } );
  3         7  
674 3 100       11 if ( $ret != 1 ) {
675 1 50       16 return wantarray ? ( 0, $ps_rule, $results ) : 0;
676             }
677             }
678 0         0 $rules_applied++;
679             }
680              
681             my ( $ret, $results ) =
682             $self->_apply_melody_rule( $melody, $ruleset->{ps}, $ruleset->{in},
683 2     7   17 sub { $_[0]->$ps_method( $_[1] ) } );
  7         21  
684 2 50       9 if ( $ret != 1 ) {
685 2 50       22 return wantarray ? ( 0, $ps_rule, $results ) : 0;
686             }
687              
688 0         0 $rules_applied++;
689             }
690             }
691              
692 0 0       0 if ( $rules_applied == 0 ) {
693 0 0       0 return wantarray ? ( 0, "no rules applied" ) : 0;
694             }
695 0 0       0 return wantarray ? ( 1, "ok" ) : 1;
696             }
697              
698             sub circular_permute {
699 474     474 1 615 my $self = shift;
700 474 100       1122 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
701 474 100       723 croak 'pitch set must contain something' if !@$pset;
702              
703 473         541 my @perms;
704 473         819 for my $i ( 0 .. $#$pset ) {
705 2700         3392 for my $j ( 0 .. $#$pset ) {
706 16866         22961 $perms[$i][$j] = $pset->[ ( $i + $j ) % @$pset ];
707             }
708             }
709 473         765 return \@perms;
710             }
711              
712             sub complement {
713 2     2 1 5 my $self = shift;
714 2 100       7 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
715              
716 2         4 my %seen;
717 2         8 @seen{@$pset} = ();
718 2         7 return [ grep { !exists $seen{$_} } 0 .. $self->{_DEG_IN_SCALE} - 1 ];
  24         47  
719             }
720              
721 1     1 1 7 sub fnums { \%FORTE2PCS }
722              
723             sub forte_number_re {
724 1     1 1 11 return $FORTE_NUMBER_RE;
725             }
726              
727             sub forte2pcs {
728 2     2 1 5 my ( $self, $forte_number ) = @_;
729 2         13 return $FORTE2PCS{ uc $forte_number };
730             }
731              
732             # simple wrapper around check_melody to create something to work with,
733             # depending on the params.
734             sub gen_melody {
735 0     0 1 0 my ( $self, %params ) = @_;
736              
737 0         0 my $attempts = 1000; # enough for Helen, enough for us
738 0   0     0 my $max_interval = $params{melody_max_interval} || 16; # tessitura of a 10th
739 0         0 delete $params{melody_max_interval};
740              
741 0 0       0 if ( !keys %params ) {
742             # based on Reginald's ideas (insofar as those can be represented by
743             # the rules system I've cobbled together)
744 0         0 %params = (
745             exclude_half_prime => [
746             { ps => [ 0, 4, 5 ] }, # leading tone/tonic/dominant
747             ],
748             exclude_interval => [
749             { iset => [ 5, 5 ], }, # adjacent fourths ("cadential basses")
750             ],
751             exclude_prime => [
752             { ps => [ 0, 3, 7 ], in => 4 }, # major or minor triad, any guise
753             { ps => [ 0, 2, 5, 8 ], }, # 7th, any guise, exact
754             { ps => [ 0, 2, 4, 6 ], in => 5 }, # whole tone formation
755             # 7-35 (major/minor scale) but also excluding from all 5-x or
756             # 6-x subsets of said set
757             { ps => [ 0, 1, 3, 5, 6, 8, 10 ], subsets => [ 6, 5 ] },
758             ],
759             );
760             }
761              
762 0         0 my $got_melody = 0;
763 0         0 my @melody;
764 0         0 eval {
765 0         0 ATTEMPT: while ( $attempts-- > 0 ) {
766 0         0 my %seen;
767 0         0 my @pitches = 0 .. $self->{_DEG_IN_SCALE} - 1;
768 0         0 @melody = splice @pitches, rand @pitches, 1;
769 0         0 $seen{ $melody[0] } = 1;
770 0         0 my $melody_low = $melody[0];
771 0         0 my $melody_high = $melody[0];
772              
773 0         0 while (@pitches) {
774             my @potential = grep {
775 0         0 my $base_pitch = $_ % 12;
  0         0  
776 0         0 my $ret = 0;
777 0         0 for my $p (@pitches) {
778 0 0       0 if ( $base_pitch == $p ) { $ret = 1; last }
  0         0  
  0         0  
779             }
780             $ret
781 0         0 } $melody_high - $max_interval .. $melody_low + $max_interval;
782 0         0 my $choice = $potential[ rand @potential ];
783 0         0 my $base_choice = $choice % 12;
784 0         0 @pitches = grep $_ != $base_choice, @pitches;
785 0         0 push @melody, $choice;
786              
787 0 0       0 $melody_low = $choice if $choice < $melody_low;
788 0 0       0 $melody_high = $choice if $choice > $melody_high;
789             }
790              
791             # but negative pitches are awkward for various reasons
792 0 0       0 if ( $melody_low < 0 ) {
793 0         0 $melody_low = abs $melody_low;
794 0         0 $_ += $melody_low for @melody;
795             }
796              
797 0         0 ( $got_melody, my $msg ) = $self->check_melody( \%params, \@melody );
798 0 0       0 next ATTEMPT if $got_melody != 1;
799              
800 0         0 last;
801             }
802             };
803 0 0       0 croak $@ if $@;
804 0 0       0 croak "could not generate a melody" unless $got_melody;
805              
806 0         0 return \@melody;
807             }
808              
809             # copied from Music::NeoRiemannianTonnetz 'normalize', see perldocs
810             # for differences between this and prime_form and normal_form
811             sub half_prime_form {
812 8     8 1 756 my $self = shift;
813 8 100       26 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
814              
815 8 100       24 croak 'pitch set must contain something' if !@$pset;
816              
817 7         8 my %origmap;
818 7         11 for my $p (@$pset) {
819 21         22 push @{ $origmap{ $p % $self->{_DEG_IN_SCALE} } }, $p;
  21         50  
820             }
821 7 50       15 if ( keys %origmap == 1 ) {
822 0 0       0 return wantarray ? ( keys %origmap, \%origmap ) : keys %origmap;
823             }
824 7         32 my @nset = sort { $a <=> $b } keys %origmap;
  20         37  
825              
826 7         10 my @equivs;
827 7         15 for my $i ( 0 .. $#nset ) {
828 21         26 for my $j ( 0 .. $#nset ) {
829 63         95 $equivs[$i][$j] = $nset[ ( $i + $j ) % @nset ];
830             }
831             }
832 7         16 my @order = reverse 1 .. $#nset;
833              
834 7         7 my @normal;
835 7         9 for my $i (@order) {
836 7         11 my $min_span = $self->{_DEG_IN_SCALE};
837 7         7 my @min_span_idx;
838              
839 7         10 for my $eidx ( 0 .. $#equivs ) {
840             my $span =
841 21         31 ( $equivs[$eidx][$i] - $equivs[$eidx][0] ) % $self->{_DEG_IN_SCALE};
842 21 100       37 if ( $span < $min_span ) {
    50          
843 12         13 $min_span = $span;
844 12         18 @min_span_idx = $eidx;
845             } elsif ( $span == $min_span ) {
846 0         0 push @min_span_idx, $eidx;
847             }
848             }
849              
850 7 50       9 if ( @min_span_idx == 1 ) {
851 7         18 @normal = @{ $equivs[ $min_span_idx[0] ] };
  7         17  
852 7         12 last;
853             } else {
854 0         0 @equivs = @equivs[@min_span_idx];
855             }
856             }
857              
858 7 50       13 if ( !@normal ) {
859             # nothing unique, pick lowest starting pitch, which is first index
860             # by virtue of the numeric sort performed above.
861 0         0 @normal = @{ $equivs[0] };
  0         0  
862             }
863              
864             # but must map (and anything else not ) so b is 0,
865             # dis 4, etc. and also update the original pitch mapping - this is
866             # the major addition to the otherwise stock normal_form code.
867 7 100       12 if ( $normal[0] != 0 ) {
868 6         8 my $trans = $self->{_DEG_IN_SCALE} - $normal[0];
869 6         7 my %newmap;
870 6         17 for my $i (@normal) {
871 18         21 my $prev = $i;
872 18         22 $i = ( $i + $trans ) % $self->{_DEG_IN_SCALE};
873 18         30 $newmap{$i} = $origmap{$prev};
874             }
875 6         19 %origmap = %newmap;
876             }
877              
878 7 100       41 return wantarray ? ( \@normal, \%origmap ) : \@normal;
879             }
880              
881             sub interval_class_content {
882 7     7 1 36 my $self = shift;
883 7 100       20 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
884              
885 7         74 my @nset = sort { $a <=> $b } uniqnum @$pset;
  34         59  
886 7 100       28 croak 'pitch set must contain at least two elements' if @nset < 2;
887              
888 6         7 my %icc;
889 6         14 for my $i ( 1 .. $#nset ) {
890 19         32 for my $j ( 0 .. $i - 1 ) {
891             $icc{
892             $self->pitch2intervalclass(
893             ( $nset[$i] - $nset[$j] ) % $self->{_DEG_IN_SCALE}
894             )
895 49         81 }++;
896             }
897             }
898              
899 6         8 my @icv;
900 6         12 for my $ics ( 1 .. int( $self->{_DEG_IN_SCALE} / 2 ) ) {
901 36   100     71 push @icv, $icc{$ics} || 0;
902             }
903              
904 6 100       29 return wantarray ? ( \@icv, \%icc ) : \@icv;
905             }
906              
907             sub intervals2pcs {
908 4     4 1 33 my $self = shift;
909 4         6 my $start_pitch = shift;
910 4 100       12 my $iset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
911 4 100       15 croak 'interval set must contain something' if !@$iset;
912              
913 3   100     9 $start_pitch //= 0;
914 3         4 $start_pitch = int $start_pitch;
915              
916 3         6 my @pset = $start_pitch;
917 3         6 for my $i (@$iset) {
918 13         25 push @pset, ( $pset[-1] + $i ) % $self->{_DEG_IN_SCALE};
919             }
920              
921 3         16 return \@pset;
922             }
923              
924             sub invariance_matrix {
925 3     3 1 31 my $self = shift;
926 3 100       10 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
927 3 100       13 croak 'pitch set must contain something' if !@$pset;
928              
929 2         4 my @ivm;
930 2         6 for my $i ( 0 .. $#$pset ) {
931 8         10 for my $j ( 0 .. $#$pset ) {
932 32         48 $ivm[$i][$j] = ( $pset->[$i] + $pset->[$j] ) % $self->{_DEG_IN_SCALE};
933             }
934             }
935              
936 2         22 return \@ivm;
937             }
938              
939             sub invert {
940 254     254 1 385 my $self = shift;
941 254         276 my $axis = shift;
942 254 100       427 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
943 254 100       395 croak 'pitch set must contain something' if !@$pset;
944              
945 253   100     458 $axis //= 0;
946 253         273 $axis = int $axis;
947              
948 253         465 my @inverse = @$pset;
949 253         320 for my $p (@inverse) {
950 1427         1702 $p = ( $axis - $p ) % $self->{_DEG_IN_SCALE};
951             }
952              
953 253         465 return \@inverse;
954             }
955              
956             # Utility routine to get the last few elements of a list (but never more
957             # than the whole list, etc).
958             sub lastn {
959 7     7 1 93 my ( $self, $pset, $n ) = @_;
960 7 100 100     42 croak 'cannot get elements of nothing'
961             if !defined $pset
962             or ref $pset ne 'ARRAY';
963              
964 5 100       14 return unless @$pset;
965              
966 4   66     12 $n //= $self->{_lastn};
967 4 100       29 croak 'n of lastn must be number' unless looks_like_number $n;
968              
969 3         5 my $len = @$pset;
970 3 100       7 $len = $n if $len > $n;
971 3         5 $len *= -1;
972 3         4 return @{$pset}[ $len .. -1 ];
  3         18  
973             }
974              
975             sub mininterval {
976 9     9 1 68 my ( $self, $from, $to ) = @_;
977 9         14 my $dir = 1;
978              
979 9 100       34 croak 'from pitch must be a number' unless looks_like_number $from;
980 8 100       22 croak 'to pitch must be a number' unless looks_like_number $to;
981              
982 7         12 $from %= $self->{_DEG_IN_SCALE};
983 7         9 $to %= $self->{_DEG_IN_SCALE};
984              
985 7 100       16 if ( $from > $to ) {
986 3         5 ( $from, $to ) = ( $to, $from );
987 3         5 $dir = -1;
988             }
989 7         8 my $interval = $to - $from;
990 7 100       32 if ( $interval > $self->{_DEG_IN_SCALE} / 2 ) {
991 4         7 $dir *= -1;
992 4         6 $from += $self->{_DEG_IN_SCALE};
993 4         6 $interval = $from - $to;
994             }
995              
996 7         28 return $interval * $dir;
997             }
998              
999             sub multiply {
1000 3     3 1 31 my $self = shift;
1001 3         5 my $factor = shift;
1002 3 100       11 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1003 3 100       14 croak 'pitch set must contain something' if !@$pset;
1004              
1005 2   100     7 $factor //= 1;
1006 2         4 $factor = int $factor;
1007              
1008 2         4 return [ map { my $p = $_ * $factor % $self->{_DEG_IN_SCALE}; $p } @$pset ];
  8         12  
  8         19  
1009             }
1010              
1011             # Utility methods for get/check/reset of each element in turn of a given
1012             # array reference, with wrap-around. Handy if pulling sequential
1013             # elements off a list, but have much code between the successive calls.
1014             {
1015             my %seen;
1016              
1017             # get the iterator value for a ref
1018             sub geti {
1019 4     4 1 56 my ( $self, $ref ) = @_;
1020 4 100 100     32 croak 'need an array reference' if !defined $ref or ref $ref ne 'ARRAY';
1021 2   100     15 return $seen{ refaddr $ref} || 0;
1022             }
1023              
1024             # grabi(42, \@array) obtains 42 elements from array, looping to
1025             # fill if necessary
1026             sub grabi {
1027 8     8 1 113 my ( $self, $count, $ref ) = @_;
1028 8 100 100     45 croak 'need an array reference' if !defined $ref or ref $ref ne 'ARRAY';
1029 6 100 100     37 croak 'count must be non-negative integer'
1030             if !looks_like_number($count)
1031             or $count < 0;
1032 4 100 100     26 return if @$ref == 0 or $count == 0;
1033 2   100     11 $seen{ refaddr $ref} ||= 0;
1034 2         2 my @results;
1035 2         6 while ( $count > 0 ) {
1036 8         15 push @results, $ref->[ $seen{ refaddr $ref} ];
1037 8         18 $seen{ refaddr $ref } = ( $seen{ refaddr $ref } + 1 ) % @$ref;
1038 8         13 $count--;
1039             }
1040 2         12 return @results;
1041             }
1042              
1043             # nexti(\@array) - returns subsequent elements of array on each
1044             # successive call
1045             sub nexti {
1046 5     5 1 83 my ( $self, $ref ) = @_;
1047 5 100 100     34 croak 'need an array reference' if !defined $ref or ref $ref ne 'ARRAY';
1048 3   100     13 $seen{ refaddr $ref} ||= 0;
1049 3         12 $seen{ refaddr $ref } = ( $seen{ refaddr $ref } + 1 ) % @$ref;
1050 3         13 $ref->[ $seen{ refaddr $ref} ];
1051             }
1052              
1053             # reseti(\@array) - resets counter
1054             sub reseti {
1055 3     3 1 92 my ( $self, $ref ) = @_;
1056 3 100 100     26 croak 'need an array reference' if !defined $ref or ref $ref ne 'ARRAY';
1057 1         6 $seen{ refaddr $ref} = 0;
1058             }
1059              
1060             # set the iterator for a ref
1061             sub seti {
1062 4     4 1 83 my ( $self, $ref, $i ) = @_;
1063 4 100 100     30 croak 'need an array reference' if !defined $ref or ref $ref ne 'ARRAY';
1064 2 100       14 croak 'iterator must be number'
1065             unless looks_like_number($i);
1066 1         4 $seen{ refaddr $ref} = $i;
1067             }
1068              
1069             # returns current element, but does not advance pointer
1070             sub whati {
1071 4     4 1 56 my ( $self, $ref ) = @_;
1072 4 100 100     29 croak 'need an array reference' if !defined $ref or ref $ref ne 'ARRAY';
1073 2   100     10 $seen{ refaddr $ref} ||= 0;
1074 2         10 $ref->[ $seen{ refaddr $ref} % @$ref ];
1075             }
1076             }
1077              
1078             sub new {
1079 7     7 1 512 my ( $class, %param ) = @_;
1080 7         13 my $self = {};
1081              
1082 7   66     36 $self->{_DEG_IN_SCALE} = int( $param{DEG_IN_SCALE} // $DEG_IN_SCALE );
1083 7 100       19 if ( $self->{_DEG_IN_SCALE} < 2 ) {
1084 1         10 croak 'degrees in scale must be greater than one';
1085             }
1086              
1087 6 100       17 if ( exists $param{lastn} ) {
1088             croak 'lastn must be number'
1089 2 100       14 unless looks_like_number $param{lastn};
1090 1         3 $self->{_lastn} = $param{lastn};
1091             } else {
1092 4         7 $self->{_lastn} = 2;
1093             }
1094              
1095             # XXX packing not implemented beyond "right" method (via www.mta.ca docs)
1096 5         10 $self->{_packing} = 'right'; # $param{PACKING} // 'right';
1097              
1098 5         9 bless $self, $class;
1099 5         34 return $self;
1100             }
1101              
1102             sub normal_form {
1103 472     472 1 659 my $self = shift;
1104 472 100       807 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1105              
1106 472 100       726 croak 'pitch set must contain something' if !@$pset;
1107              
1108 471         558 my %origmap;
1109 471         730 for my $p (@$pset) {
1110 2703         2744 push @{ $origmap{ $p % $self->{_DEG_IN_SCALE} } }, $p;
  2703         5116  
1111             }
1112 471 50       849 if ( keys %origmap == 1 ) {
1113 0 0       0 return wantarray ? ( [ keys %origmap ], \%origmap ) : [ keys %origmap ];
1114             }
1115 471         1416 my @nset = sort { $a <=> $b } keys %origmap;
  4549         5805  
1116              
1117 471         1031 my $equivs = $self->circular_permute( \@nset );
1118 471         862 my @order = 1 .. $#nset;
1119             # NOTE this only performs 'right' packing, see commits
1120             # 9f0c33f8260af9584d38c92af4e7a6a39f7e2769 and prior for long since
1121             # unimplemented notes on this topic
1122 471         583 @order = reverse @order;
1123              
1124 471         481 my @normal;
1125 471         633 for my $i (@order) {
1126 761         921 my $min_span = $self->{_DEG_IN_SCALE};
1127 761         767 my @min_span_idx;
1128              
1129 761         1089 for my $eidx ( 0 .. $#$equivs ) {
1130             my $span =
1131 3451         4353 ( $equivs->[$eidx][$i] - $equivs->[$eidx][0] ) % $self->{_DEG_IN_SCALE};
1132 3451 100       5301 if ( $span < $min_span ) {
    100          
1133 1065         1083 $min_span = $span;
1134 1065         1432 @min_span_idx = $eidx;
1135             } elsif ( $span == $min_span ) {
1136 516         662 push @min_span_idx, $eidx;
1137             }
1138             }
1139              
1140 761 100       1120 if ( @min_span_idx == 1 ) {
1141 446         452 @normal = @{ $equivs->[ $min_span_idx[0] ] };
  446         925  
1142 446         666 last;
1143             } else {
1144 315         349 @$equivs = @{$equivs}[@min_span_idx];
  315         733  
1145             }
1146             }
1147              
1148 471 100       700 if ( !@normal ) {
1149             # nothing unique, pick lowest starting pitch, which is first index
1150             # by virtue of the numeric sort performed above.
1151 25         28 @normal = @{ $equivs->[0] };
  25         53  
1152             }
1153              
1154 471         1127 $_ += 0 for @normal; # KLUGE avoid Test::Differences seeing '4' vs. 4
1155              
1156 471 100       1988 return wantarray ? ( \@normal, \%origmap ) : \@normal;
1157             }
1158              
1159             # Utility, converts a pitch set into a scale_degrees-bit number:
1160             # 7 3 0
1161             # [0,3,7] -> 000010001001 -> 137
1162             sub pcs2bits {
1163 4     4 1 33 my $self = shift;
1164 4 100       13 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1165              
1166 4 100       16 croak 'pitch set must contain something' if !@$pset;
1167              
1168 3         3 my $bs = 0;
1169 3         14 for my $p ( map $_ % $self->{_DEG_IN_SCALE}, @$pset ) {
1170 9         13 $bs |= 1 << $p;
1171             }
1172 3         12 return $bs;
1173             }
1174              
1175             sub pcs2forte {
1176 6     6 1 35 my $self = shift;
1177 6 100       21 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1178              
1179 6 100       20 croak 'pitch set must contain something' if !@$pset;
1180              
1181 5         6 return $PCS2FORTE{ join ',', @{ $self->prime_form($pset) } };
  5         11  
1182             }
1183              
1184             sub pcs2intervals {
1185 1     1 1 28 my $self = shift;
1186 1 50       8 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1187              
1188 1 50       5 croak 'pitch set must contain at least two elements' if @$pset < 2;
1189              
1190 1         2 my @intervals;
1191 1         2 for my $i ( 1 .. $#{$pset} ) {
  1         4  
1192 2         6 push @intervals, $pset->[$i] - $pset->[ $i - 1 ];
1193             }
1194              
1195 1         7 return \@intervals;
1196             }
1197              
1198             sub pcs2str {
1199 4     4 1 31 my $self = shift;
1200 4 100       16 croak 'must supply a pitch set' if !defined $_[0];
1201              
1202 3         5 my $str;
1203 3 100       14 if ( ref $_[0] eq 'ARRAY' ) {
    100          
1204 1         2 $str = '[' . join( ',', @{ $_[0] } ) . ']';
  1         4  
1205             } elsif ( $_[0] =~ m/,/ ) {
1206 1         4 $str = '[' . $_[0] . ']';
1207             } else {
1208 1         5 $str = '[' . join( ',', @_ ) . ']';
1209             }
1210 3         10 return $str;
1211             }
1212              
1213             sub pitch2intervalclass {
1214 56     56 1 74 my ( $self, $pitch ) = @_;
1215              
1216             # ensure member of the tone system, otherwise strange results
1217 56         63 $pitch %= $self->{_DEG_IN_SCALE};
1218              
1219             return $pitch > int( $self->{_DEG_IN_SCALE} / 2 )
1220 56 100       148 ? $self->{_DEG_IN_SCALE} - $pitch
1221             : $pitch;
1222             }
1223              
1224             # XXX tracking of original pitches would be nice, though complicated, as
1225             # ->invert would need to be modified or a non-modulating version used
1226             sub prime_form {
1227 234     234 1 284151 my $self = shift;
1228 234 100       571 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1229              
1230 234 100       493 croak 'pitch set must contain something' if !@$pset;
1231              
1232 233         427 my @forms = scalar $self->normal_form($pset);
1233 233         488 push @forms, scalar $self->normal_form( $self->invert( 0, $forms[0] ) );
1234              
1235 233         370 for my $set (@forms) {
1236 466 100       980 $set = $self->transpose( $self->{_DEG_IN_SCALE} - $set->[0], $set )
1237             if $set->[0] != 0;
1238             }
1239              
1240 233         243 my @prime;
1241 233 100       264 if ( "@{$forms[0]}" eq "@{$forms[1]}" ) {
  233         556  
  233         496  
1242 90         97 @prime = @{ $forms[0] };
  90         203  
1243             } else {
1244             # look for most compact to the left
1245 143         206 my @sums = ( 0, 0 );
1246             PITCH:
1247 143         221 for my $i ( 0 .. $#$pset ) {
1248 349         448 for my $j ( 0 .. 1 ) {
1249 698         868 $sums[$j] += $forms[$j][$i];
1250             }
1251 349 100       595 if ( $sums[0] < $sums[1] ) {
    100          
1252 136         136 @prime = @{ $forms[0] };
  136         317  
1253 136         231 last PITCH;
1254             } elsif ( $sums[0] > $sums[1] ) {
1255 7         8 @prime = @{ $forms[1] };
  7         14  
1256 7         21 last PITCH;
1257             }
1258             }
1259             }
1260              
1261 233         697 return \@prime;
1262             }
1263              
1264             # Utility, "mirrors" a pitch to be within supplied min/max values as
1265             # appropriate for how many times the pitch "reflects" back within those
1266             # limits, which will depend on which limit is broken and by how much.
1267             sub reflect_pitch {
1268 25     25 1 3570 my ( $self, $v, $min, $max ) = @_;
1269 25 100       58 croak 'pitch must be a number' if !looks_like_number $v;
1270 24 100 100     112 croak 'limits must be numbers and min less than max'
      100        
1271             if !looks_like_number $min
1272             or !looks_like_number $max
1273             or $min >= $max;
1274 21 100 100     48 return $v if $v <= $max and $v >= $min;
1275              
1276 17         27 my ( @origins, $overshoot, $direction );
1277 17 100       24 if ( $v > $max ) {
1278 5         5 @origins = ( $max, $min );
1279 5         8 $overshoot = abs( $v - $max );
1280 5         5 $direction = -1;
1281             } else {
1282 12         18 @origins = ( $min, $max );
1283 12         13 $overshoot = abs( $min - $v );
1284 12         14 $direction = 1;
1285             }
1286 17         20 my $range = abs( $max - $min );
1287 17         24 my $register = int( $overshoot / $range );
1288 17 100       26 if ( $register % 2 == 1 ) {
1289 9         11 @origins = reverse @origins;
1290 9         12 $direction *= -1;
1291             }
1292 17         18 my $remainder = $overshoot % $range;
1293              
1294 17         35 return $origins[0] + $remainder * $direction;
1295             }
1296              
1297             sub retrograde {
1298 3     3 1 29 my $self = shift;
1299 3 100       11 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1300              
1301 3 100       14 croak 'pitch set must contain something' if !@$pset;
1302              
1303 2         9 return [ reverse @$pset ];
1304             }
1305              
1306             sub rotate {
1307 10     10 1 91 my $self = shift;
1308 10         13 my $r = shift;
1309 10 100       25 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1310              
1311 10 100 100     80 croak 'rotate value must be integer'
1312             if !defined $r
1313             or $r !~ /^-?\d+$/;
1314 8 100       24 croak 'pitch set must contain something' if !@$pset;
1315              
1316 7         9 my @rot;
1317 7 100       12 if ( $r == 0 ) {
1318 1         3 @rot = @$pset;
1319             } else {
1320 6         15 for my $i ( 0 .. $#$pset ) {
1321 30         61 $rot[$i] = $pset->[ ( $i - $r ) % @$pset ];
1322             }
1323             }
1324              
1325 7         37 return \@rot;
1326             }
1327              
1328             # Utility method to rotate a list to a named element (for example "gis"
1329             # in a list of note names, see my etude no.2 for results of heavy use of
1330             # such rotations).
1331             sub rotateto {
1332 6     6 1 141 my $self = shift;
1333 6         9 my $what = shift;
1334 6         9 my $dir = shift;
1335 6 100       17 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1336              
1337 6 100       20 croak 'nothing to search on' unless defined $what;
1338 5 100       15 croak 'nothing to rotate on' if !@$pset;
1339              
1340 4         11 my @idx = 0 .. $#$pset;
1341              
1342 4   100     11 $dir //= 1;
1343 4 100       9 @idx = reverse @idx if $dir < 0;
1344              
1345 4         6 for my $i (@idx) {
1346 12 100       21 next unless $pset->[$i] eq $what;
1347 3         7 return $self->rotate( -$i, $pset );
1348             }
1349 1         10 croak "no such element $what";
1350             }
1351              
1352             # XXX probably should disallow changing this on the fly, esp. if allow
1353             # method chaining, as it could throw off results in wacky ways.
1354             sub scale_degrees {
1355 9     9 1 1120 my ( $self, $dis ) = @_;
1356 9 100       20 if ( defined $dis ) {
1357 4 100 100     43 croak 'scale degrees value must be positive integer greater than 1'
1358             if $dis !~ /^\d+$/
1359             or $dis < 2;
1360 2         5 $self->{_DEG_IN_SCALE} = $dis;
1361             }
1362 7         22 return $self->{_DEG_IN_SCALE};
1363             }
1364              
1365             sub set_complex {
1366 3     3 1 31 my $self = shift;
1367 3 100       11 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1368              
1369 3 100       15 croak 'pitch set must contain something' if !@$pset;
1370              
1371 2         5 my $iset = $self->invert( 0, $pset );
1372 2         5 my $dis = $self->scale_degrees;
1373              
1374 2         4 my @plex = $pset;
1375 2         5 for my $i ( 1 .. $#$pset ) {
1376 22         28 for my $j ( 0 .. $#$pset ) {
1377 264 100       301 if ( $j == 0 ) {
1378 22         35 $plex[$i][0] = $iset->[$i];
1379             } else {
1380 242         316 $plex[$i][$j] = ( $pset->[$j] + $iset->[$i] ) % $dis;
1381             }
1382             }
1383             }
1384              
1385 2         56 return \@plex;
1386             }
1387              
1388             sub subsets {
1389 8     8 1 114 my $self = shift;
1390 8         10 my $len = shift;
1391 8 100       22 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1392              
1393 8         15 my @nset = uniqnum map { $_ % $self->{_DEG_IN_SCALE} } @$pset;
  25         76  
1394 8 100       32 croak 'pitch set must contain two or more unique pitches' if @nset < 2;
1395              
1396 7 100       14 if ( defined $len ) {
1397 6 100 100     33 croak 'length must be less than size of pitch set (but not zero)'
1398             if $len >= @nset
1399             or $len == 0;
1400 4 100       10 if ( $len < 0 ) {
1401 1         2 $len = @nset + $len;
1402 1 50       9 croak 'negative length exceeds magnitude of pitch set' if $len < 1;
1403             }
1404             } else {
1405 1         2 $len = @nset - 1;
1406             }
1407              
1408 4         12 return [ combinations( \@nset, $len ) ];
1409             }
1410              
1411             sub tcis {
1412 1     1 1 2 my $self = shift;
1413 1 50       5 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1414              
1415 1 50       4 croak 'pitch set must contain something' if !@$pset;
1416              
1417 1         1 my %seen;
1418 1         4 @seen{@$pset} = ();
1419              
1420 1         2 my @tcis;
1421 1         5 for my $i ( 0 .. $self->{_DEG_IN_SCALE} - 1 ) {
1422 12         16 $tcis[$i] = 0;
1423 12         13 for my $p ( @{ $self->transpose_invert( $i, 0, $pset ) } ) {
  12         17  
1424 48 100       76 $tcis[$i]++ if exists $seen{$p};
1425             }
1426             }
1427 1         6 return \@tcis;
1428             }
1429              
1430             sub tcs {
1431 2     2 1 29 my $self = shift;
1432 2 50       10 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1433              
1434 2 100       13 croak 'pitch set must contain something' if !@$pset;
1435              
1436 1         1 my %seen;
1437 1         5 @seen{@$pset} = ();
1438              
1439 1         3 my @tcs = scalar @$pset;
1440 1         4 for my $i ( 1 .. $self->{_DEG_IN_SCALE} - 1 ) {
1441 11         13 $tcs[$i] = 0;
1442 11         12 for my $p ( @{ $self->transpose( $i, $pset ) } ) {
  11         13  
1443 44 100       84 $tcs[$i]++ if exists $seen{$p};
1444             }
1445             }
1446 1         5 return \@tcs;
1447             }
1448              
1449             sub transpose {
1450 254     254 1 351 my $self = shift;
1451 254         288 my $t = shift;
1452 254 100       395 my @tset = ref $_[0] eq 'ARRAY' ? @{ $_[0] } : @_;
  251         486  
1453              
1454 254 100       433 croak 'transpose value not set' if !defined $t;
1455 253 100       357 croak 'pitch set must contain something' if !@tset;
1456              
1457 252         285 $t = int $t;
1458 252         317 for my $p (@tset) {
1459 1407         1700 $p = ( $p + $t ) % $self->{_DEG_IN_SCALE};
1460             }
1461 252         508 return \@tset;
1462             }
1463              
1464             sub transpose_invert {
1465 17     17 1 93 my $self = shift;
1466 17         20 my $t = shift;
1467 17         19 my $axis = shift;
1468 17 100       33 my $pset = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
1469              
1470 17 100       31 croak 'transpose value not set' if !defined $t;
1471 16 100       32 croak 'pitch set must contain something' if !@$pset;
1472              
1473 15   100     23 $axis //= 0;
1474 15         21 my $tset = $self->invert( $axis, $pset );
1475              
1476 15         19 $t = int $t;
1477 15         17 for my $p (@$tset) {
1478 58         70 $p = ( $p + $t ) % $self->{_DEG_IN_SCALE};
1479             }
1480 15         26 return $tset;
1481             }
1482              
1483             sub variances {
1484 6     6 1 114 my ( $self, $pset1, $pset2 ) = @_;
1485              
1486 6 100       23 croak 'pitch set must be array ref' unless ref $pset1 eq 'ARRAY';
1487 5 100       15 croak 'pitch set must contain something' if !@$pset1;
1488 4 100       16 croak 'pitch set must be array ref' unless ref $pset2 eq 'ARRAY';
1489 3 100       12 croak 'pitch set must contain something' if !@$pset2;
1490              
1491 2         2 my ( @union, @intersection, @difference, %count );
1492 2         5 for my $p ( @$pset1, @$pset2 ) {
1493 16         25 $count{$p}++;
1494             }
1495 2         11 for my $p ( sort { $a <=> $b } keys %count ) {
  18         24  
1496 12         19 push @union, $p;
1497 12 100       12 push @{ $count{$p} > 1 ? \@intersection : \@difference }, $p;
  12         21  
1498             }
1499 2 100       26 return wantarray ? ( \@intersection, \@difference, \@union ) : \@intersection;
1500             }
1501              
1502             sub zrelation {
1503 6     6 1 112 my ( $self, $pset1, $pset2 ) = @_;
1504              
1505 6 100       23 croak 'pitch set must be array ref' unless ref $pset1 eq 'ARRAY';
1506 5 100       15 croak 'pitch set must contain something' if !@$pset1;
1507 4 100       14 croak 'pitch set must be array ref' unless ref $pset2 eq 'ARRAY';
1508 3 100       12 croak 'pitch set must contain something' if !@$pset2;
1509              
1510 2         3 my @ic_vecs;
1511 2         3 for my $ps ( $pset1, $pset2 ) {
1512 4         9 push @ic_vecs, scalar $self->interval_class_content($ps);
1513             }
1514 2 100       5 return ( "@{$ic_vecs[0]}" eq "@{$ic_vecs[1]}" ) ? 1 : 0;
  2         5  
  2         18  
1515             }
1516              
1517             1;
1518             __END__