music_set_theory 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,406 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # filename: music_set_theory/chords.rb
4
+ #
5
+
6
+
7
+ #-*- coding: UTF-8 -*-
8
+ # chords.py: Defines (and contains) the classes for chords.
9
+ #
10
+ # Copyright (c) 2008-2020 Peter Murphy <peterkmurphy@gmail.com>
11
+ # All rights reserved.
12
+ #
13
+ # Redistribution and use in source and binary forms, with or without
14
+ # modification, are permitted provided that the following conditions are met:
15
+ # * Redistributions of source code must retain the above copyright
16
+ # notice, this list of conditions and the following disclaimer.
17
+ # * Redistributions in binary form must reproduce the above copyright
18
+ # notice, this list of conditions and the following disclaimer in the
19
+ # documentation and/or other materials provided with the distribution.
20
+ # * The names of its contributors may not be used to endorse or promote
21
+ # products derived from this software without specific prior written
22
+ # permission.
23
+ #
24
+ # THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ''AS IS'' AND ANY
25
+ # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ # DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
28
+ # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+
35
+
36
+ require_relative "./musutility"
37
+ require_relative "./temperament"
38
+ require_relative "./scales"
39
+
40
+ # import unittest;
41
+ #
42
+ # from .musutility import enl_seq;
43
+ # from .temperament import temperament, WestTemp, seq_dict, NSEQ_SCALE, \
44
+ # NSEQ_CHORD, M_SHARP, M_FLAT;
45
+ # from .scales import noteseq, noteseq_scale;
46
+ #
47
+
48
+
49
+ # Structure.
50
+ #
51
+ #
52
+ # A specialisation of noteseq used exclusively for defining chords -
53
+ # especially chords associated with heptatonic/diotonic scales.
54
+ #
55
+ module MusicSetTheory
56
+
57
+ class NoteSeqChord < NoteSeq; end
58
+
59
+ end
60
+
61
+
62
+ # Constructor.
63
+ #
64
+ #
65
+ module MusicSetTheory
66
+
67
+ # A specialisation of noteseq used exclusively for defining chords -
68
+ # especially chords associated with heptatonic/diotonic scales.
69
+ #
70
+ class NoteSeqChord
71
+
72
+ #
73
+ # ==== Args
74
+ # nseq_name:: name of chord.
75
+ # nseq_temp:: temperament for chord.
76
+ # nseq_posn:: position of notes in chord.
77
+ # nseq_nat_posns:: natural note positions for notes in scales.
78
+ # nseq_abbrev:: main abbreviation for chord.
79
+ # ==== See Also
80
+ # - `NoteSeq#initialize()`
81
+ #
82
+ def initialize( nseq_name, nseq_temp, nseq_posn, nseq_nat_posns,
83
+ nseq_abbrev )
84
+ super(nseq_name, NSEQ_CHORD, nseq_temp, nseq_posn,
85
+ nseq_nat_posns, nseq_abbrev: nseq_abbrev)
86
+ end
87
+
88
+ def to_s
89
+ self.nseq_name.to_s + ":" + self.nseq_abbrev.to_s + ":" +
90
+ self.nseq_posn.to_s
91
+ end
92
+
93
+ end
94
+ end
95
+
96
+
97
+ #
98
+ #
99
+ #
100
+ module MusicSetTheory
101
+
102
+ # Original comments:
103
+ # This dictionary contains a list of different nseq_nat_posns values
104
+ # for different types of chords in the Western tradition. Note that
105
+ # all arrays are stored base 0, not base 1, so a "seventh" chord is
106
+ # stored as [0, 2, 4, 6] - not [1, 3, 5, 7].
107
+ #
108
+ # ==== Attention
109
+ #! in degree, but base is 0, not 1.
110
+ #
111
+ CHORDTYPE_DICT = {
112
+ "Triad" => [0, 2, 4],
113
+ "Seventh" => [0, 2, 4, 6],
114
+ "Ninth" => [0, 2, 4, 6, 8],
115
+ "Eleventh" => [0, 2, 4, 6, 8, 10],
116
+ "Thirteenth" => [0, 2, 4, 6, 8, 10, 12],
117
+ "Added Ninth" => [0, 2, 4, 8],
118
+ "Suspended" => [0, 3, 4],
119
+ "Suspended Seventh" => [0, 3, 4, 6],
120
+ "Sixth" => [0, 2, 4, 5],
121
+ "Sixth/Ninth" => [0, 2, 4, 5, 8],
122
+ "Added Eleventh" => [0, 2, 4, 10],
123
+ "Fifth" => [0, 4],
124
+ }
125
+
126
+ end
127
+
128
+
129
+ #
130
+ #
131
+ #
132
+ module MusicSetTheory
133
+
134
+ # This function generates most of the chords in the Western tradition
135
+ # (and some that are not really chords at all).
136
+ # ==== Args
137
+ # west_temp:: temperament.
138
+ #
139
+ # ==== Returns
140
+ # Chord table (Array of NoteSeqChord).
141
+ #
142
+ # ==== Warns
143
+ #! this methods modifies west_temp. Be careful...
144
+ #
145
+ def generate_west_chords( west_temp = MusicSetTheory::Temperament.
146
+ WestTempNew() )
147
+ chordseq = []
148
+ bases = [[0, 3], [0, 4]]
149
+ triads = enl_seq(bases, [6, 7, 8])
150
+
151
+
152
+ # PKM2014 - add two pseudo suspended chords
153
+ suspendeds = [[0, 5, 6], [0, 5, 7], [0, 5, 8], [0, 6, 7], [0, 6, 8]]
154
+ sixths = enl_seq(triads, [8, 9])
155
+ sevenths = enl_seq(triads, [10, 11])
156
+ suspended_sevenths = enl_seq(suspendeds, [9, 10, 11])
157
+
158
+ ninths = enl_seq(sevenths, [13, 14, 15])
159
+ elevenths = enl_seq(ninths, [17, 18])
160
+ thirteenths = enl_seq(elevenths, [20, 21])
161
+
162
+ add_ninths = enl_seq(triads, [13, 14, 15])
163
+ six_ninths = enl_seq(sixths, [13, 14, 15])
164
+
165
+ sevenths_and_above = sevenths + ninths + elevenths + thirteenths +
166
+ suspended_sevenths
167
+
168
+ # We add the power chords.
169
+ chordseq.append(NoteSeqChord.new("Power Fifth", west_temp, [0, 7],
170
+ CHORDTYPE_DICT["Fifth"], "5"))
171
+ chordseq.append(NoteSeqChord.new("Tritone", west_temp, [0, 6],
172
+ CHORDTYPE_DICT["Fifth"], "T"))
173
+ chordseq.append(NoteSeqChord.new("Power Sharp Fifth", west_temp, [0, 8],
174
+ CHORDTYPE_DICT["Fifth"], "+5"))
175
+
176
+
177
+ # We add the diminished chords, as they take special handling.
178
+ chordseq.append(NoteSeqChord.new("Diminish 7th", west_temp, [0, 3, 6, 9],
179
+ CHORDTYPE_DICT["Seventh"], "dim7"))
180
+ chordseq.append(NoteSeqChord.new("Diminish 9th", west_temp, [0, 3, 6, 9, 13],
181
+ CHORDTYPE_DICT["Ninth"], "dim9"))
182
+ chordseq.append(NoteSeqChord.new("Diminish 11th", west_temp,
183
+ [0, 3, 6, 9, 13, 16], CHORDTYPE_DICT["Eleventh"], "dim11"))
184
+ chordseq.append(NoteSeqChord.new("Diminish 13th", west_temp,
185
+ [0, 3, 6, 9, 13, 16, 20], CHORDTYPE_DICT["Thirteenth"], "dim13"))
186
+
187
+
188
+ # We loop over the triads and the added ninths together. For this
189
+ # reason, we set up some maps for the ease of iteration.
190
+ #
191
+ triad_names = {
192
+ 3 =>{ 6 =>"Diminish 5th", 7 =>"Minor", 8 =>"Minor Sharp 5th", },
193
+ 4 =>{ 6 =>"Major Flat 5th", 7 =>"Major", 8 =>"Augment", }, }
194
+ triad_abbrv = {
195
+ 3 =>{ 6 =>"dim5", 7 =>"min", 8 =>"min+5", },
196
+ 4 =>{ 6 =>"-5", 7 =>"maj", 8 =>"+", }, }
197
+ add9_names = {
198
+ 13 =>" Add Flat 9th", 14 =>" Add 9th", 15 =>" Add Sharp 9th", }
199
+ add9_abbrv = {
200
+ 13 =>" add-9", 14 =>" add9", 15 =>" add+9", }
201
+
202
+ add11_names = {17 =>" Add 11th", 18 =>" Add Sharp 11th", }
203
+ add11_abbrv = {17 =>" add11", 18 =>" add+11", }
204
+
205
+ add6_names = {20 =>" Add Flat 6th", 21 =>" Add 6th", }
206
+ add6_abbrv = {20 =>" add-6", 21 =>" add6", }
207
+
208
+
209
+ #
210
+ for i in [3, 4]
211
+ for j in [6, 7, 8]
212
+ our_tname = triad_names[i][j]
213
+ our_tabbr = triad_abbrv[i][j]
214
+ chordseq.append(NoteSeqChord.new(
215
+ our_tname, west_temp, [0, i, j],
216
+ CHORDTYPE_DICT["Triad"], our_tabbr))
217
+
218
+ #
219
+ for k in [13, 14, 15]
220
+ if i == 3 && k == 15
221
+ # pass;
222
+ else
223
+ chordseq.append(NoteSeqChord.new(our_tname + add9_names[k],
224
+ west_temp, [0, i, j, k],
225
+ CHORDTYPE_DICT["Added Ninth"],
226
+ our_tabbr+add9_abbrv[k]))
227
+ end
228
+ end
229
+
230
+ #
231
+ for k in [17, 18]
232
+ if k == 18 && j == 6
233
+ # pass;
234
+ else
235
+ chordseq.append(NoteSeqChord.new(our_tname + add11_names[k],
236
+ west_temp, [0, i, j, k], CHORDTYPE_DICT["Added Eleventh"],
237
+ our_tabbr+add11_abbrv[k]))
238
+ end
239
+ end
240
+
241
+ #
242
+ for k in [20, 21]
243
+ if i == 3 && j == 6 && k == 21
244
+ # pass; # Pattern already taken by Diminished 7 chord.
245
+ else
246
+ chordseq.append(NoteSeqChord.new(our_tname + add6_names[k],
247
+ west_temp, [0, i, j, k], CHORDTYPE_DICT["Sixth"],
248
+ our_tabbr+add6_abbrv[k]))
249
+ end
250
+
251
+ for l in [13, 14, 15]
252
+ if i == 3 && l == 15
253
+ # pass
254
+ else
255
+ chordseq.append(NoteSeqChord.new(
256
+ our_tname + add6_names[k] + add9_names[l],
257
+ west_temp, [0, i, j, k, l],
258
+ CHORDTYPE_DICT["Sixth/Ninth"],
259
+ our_tabbr+add6_abbrv[k]+add9_abbrv[l]))
260
+ end
261
+ end # endof l
262
+
263
+ end # endof k
264
+
265
+ end # endof j
266
+ end # endof i
267
+
268
+ # Then we add the suspended chords. They are treated differently
269
+ # from other chords, as one can't put 9th, 11th or 13th on there.
270
+ #
271
+ chordseq.append(NoteSeqChord.new("Suspend Flat 5th",
272
+ west_temp, [0, 5, 6], CHORDTYPE_DICT["Suspended"], "sus-5"))
273
+ chordseq.append(NoteSeqChord.new("Suspend",
274
+ west_temp, [0, 5, 7], CHORDTYPE_DICT["Suspended"], "sus"))
275
+ chordseq.append(NoteSeqChord.new("Suspend Sharp 5th",
276
+ west_temp, [0, 5, 8], CHORDTYPE_DICT["Suspended"], "sus+5"))
277
+
278
+ # PKM2014 - add two pseudo suspended chords
279
+
280
+ chordseq.append(NoteSeqChord.new("Suspend Sharp 4th",
281
+ west_temp, [0, 6, 7], CHORDTYPE_DICT["Suspended"], "sus+4"))
282
+ chordseq.append(NoteSeqChord.new("Suspend Sharp 4th Sharp 5th",
283
+ west_temp, [0, 6, 8], CHORDTYPE_DICT["Suspended"], "sus+4+5"))
284
+
285
+
286
+ # Afterwards, we add the 7th (including sus 7th), 9th, 11th and
287
+ # 13th chords.
288
+
289
+
290
+ for i in sevenths_and_above
291
+ suspendsharpfourth = false
292
+ abbrev = ""
293
+ name = ""
294
+
295
+ if i.include? 5
296
+ abbrev += "sus / "
297
+ name += "Suspend "
298
+ end
299
+
300
+ # PKM2014 - add code for two pseudo suspended 7th chords
301
+ if i.size == 4 && i.include?(6) && ((i.include? 7) || (i.include? 8))
302
+ abbrev += "sus+4 / ";
303
+ name += "Suspend Sharp 4th "
304
+ suspendsharpfourth = true
305
+ end
306
+ if i.include? 3
307
+ if i.include? 11
308
+ abbrev += "maj / min";
309
+ name += "Major / Minor "
310
+ else
311
+ abbrev += "min";
312
+ name += "Minor "
313
+ end
314
+ else
315
+ if 11 in i:
316
+ abbrev += "maj";
317
+ name += "Major "
318
+
319
+ #PKM2014 - this is for suspended diminished chords.
320
+
321
+ elsif i.include? 9 && i.size == 4 &&
322
+ ((i.include? 5) || (i.include? 6 &&
323
+ ((i.include? 7) || (i.include? 8))))
324
+ abbrev += "dim";
325
+ name += "Diminish "
326
+ end
327
+ end
328
+
329
+ if i.include? 21
330
+ abbrev += "13";
331
+ name += "13th "
332
+ elsif i.include? 17
333
+ abbrev += "11";
334
+ name += "11th "
335
+ elsif i.include? 14
336
+ abbrev += "9";
337
+ name += "9th "
338
+ else
339
+ abbrev += "7";
340
+ name += "7th "
341
+ end
342
+
343
+ if i.include? 20
344
+ abbrev += "-13";
345
+ name += "Flat 13th "
346
+ end
347
+ if i.include? 18
348
+ abbrev += "+11";
349
+ name += "Sharp 11th "
350
+ end
351
+ if i.include? 15
352
+ abbrev += "+9";
353
+ name += "Sharp 9th "
354
+ end
355
+ if i.include? 13
356
+ abbrev += "-9";
357
+ name += "Flat 9th "
358
+ end
359
+ if i.include? 8
360
+ abbrev += "+5";
361
+ name += "Sharp 5th "
362
+ end
363
+ if i.include? 6 && !(suspendsharpfourth)
364
+ abbrev += "-5";
365
+ name += "Flat 5th ";
366
+ end
367
+
368
+ # We eliminate chords that consist of the same note seperated by
369
+ # an octave!
370
+
371
+ if i.include?(3) && i.include?(15)
372
+ # pass;
373
+ elsif i.include?(5) && i.include?(17)
374
+ # pass;
375
+ elsif i.include?(6) && i.include?(18)
376
+ # pass;
377
+ elsif i.include?(8) && i.include?(20)
378
+ # pass
379
+ else
380
+ name = name.rstrip
381
+
382
+ if i.include? 5
383
+ our_chordtype = CHORDTYPE_DICT["Suspended Seventh"]
384
+ elsif i.include?(20) || i.include?(21)
385
+ our_chordtype = CHORDTYPE_DICT["Thirteenth"]
386
+ elsif i.include?(17) || i.include?(18)
387
+ our_chordtype = CHORDTYPE_DICT["Eleventh"]
388
+ elsif i.include?(13) || i.include?(14) || i.include?(15)
389
+ our_chordtype = CHORDTYPE_DICT["Ninth"]
390
+ else
391
+ our_chordtype = CHORDTYPE_DICT["Seventh"]
392
+ chordseq.append(NoteSeqChord.new(name,
393
+ west_temp, i, our_chordtype, abbrev))
394
+ end
395
+ end
396
+
397
+ end
398
+
399
+ return chordseq
400
+ end
401
+
402
+
403
+ end
404
+
405
+
406
+ #### endof filename: music_set_theory/chords.rb
@@ -0,0 +1,284 @@
1
+ #
2
+ # frozen_string_literal: true
3
+ #
4
+ # filename: music_set_theory/musutility.rb
5
+ #
6
+
7
+
8
+ # Defines utility functions not properly part of other modules.
9
+ #
10
+ #
11
+
12
+ #-*- coding: UTF-8 -*-
13
+ # musutility.py: Defines utility functions not properly part of other modules.
14
+ #
15
+ # Copyright (c) 2008-2020 Peter Murphy <peterkmurphy@gmail.com>
16
+ # All rights reserved.
17
+ #
18
+ # Redistribution and use in source and binary forms, with or without
19
+ # modification, are permitted provided that the following conditions are met:
20
+ # * Redistributions of source code must retain the above copyright
21
+ # notice, this list of conditions and the following disclaimer.
22
+ # * Redistributions in binary form must reproduce the above copyright
23
+ # notice, this list of conditions and the following disclaimer in the
24
+ # documentation and/or other materials provided with the distribution.
25
+ # * The names of its contributors may not be used to endorse or promote
26
+ # products derived from this software without specific prior written
27
+ # permission.
28
+ #
29
+ # THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ''AS IS'' AND ANY
30
+ # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32
+ # DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
33
+ # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
35
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
36
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
+
40
+
41
+ # import sys;
42
+ # import codecs;
43
+ # import unittest;
44
+
45
+ #
46
+ #
47
+ #
48
+ module MusicSetTheory
49
+
50
+ module MusUtility; end
51
+
52
+ extend MusUtility
53
+ include MusUtility
54
+ end
55
+
56
+
57
+ #
58
+ #
59
+ #
60
+ module MusicSetTheory
61
+ module MusUtility
62
+
63
+
64
+ # One of the common problems with python 2.x is that its print function does
65
+ # not handle Unicode strings with characters values greater than 127. For this
66
+ # reason, we provide our own. It is used only for debugging.
67
+
68
+ # def printunicode(value, encoding = "utf-8"):
69
+ # """ Prints an unescaped unicode string to the system console. """
70
+ # uval = str(value);
71
+ # sys.stdout.write(codecs.encode(uval + '\n', encoding));
72
+
73
+
74
+ #
75
+ # Make sequences representable as strings.
76
+ #
77
+ # Writes a sequence as a comma delimited string.
78
+ def seqtostr( value )
79
+ ary = value
80
+ ary = ary.to_a unless ary.is_a? Array
81
+ ary.join(", ")
82
+ end
83
+ #
84
+ # def seqtostr(value):
85
+ # """ Writes a sequence as a comma delimited string. """
86
+ # output = "";
87
+ # if not value:
88
+ # return output;
89
+ # for i in value:
90
+ # output += str(i) + ", ";
91
+ #
92
+ # Trim the last two characters.
93
+ #
94
+ # return output[:-2];
95
+
96
+
97
+ # The next few functions are for rotating sequences and choosing "multiple
98
+ # element slices" See multislice for more information.
99
+ #
100
+
101
+ #
102
+ # Original comments:
103
+ # Rotates a seq to the left by offset places; the elements shifted off
104
+ # are inserted back at the end. By setting offset to the negative number -N,
105
+ # this function rotates the sequence N places.
106
+ #
107
+ # Note: this algorithm is a modification of one originally provided by
108
+ # Thorsten Kampe; this version handles zero sequences and right shifts. See:
109
+ # http://bytes.com/topic/python/answers/36070-rotating-lists
110
+ def rotate( seq, offset )
111
+ seq.rotate(offset)
112
+ end
113
+ # if (len(seq) == 0) or (offset == 0):
114
+ # return seq;
115
+ # else:
116
+ # offsetmod = offset % len(seq);
117
+ # return seq[offsetmod:] + seq[:offsetmod];
118
+
119
+
120
+ #
121
+ # Original comments:
122
+ # Returns the result of adding scalar (a number) to all elements in seq.
123
+ # If mod is not 0, then all addition occurs modulo mod.
124
+ #
125
+ # Note: this function is an "analogue" of scalar multiplication - an
126
+ # operation where every element in a sequence is multiplied by a scalar.
127
+ # We can also use this function to perform "scalar subtraction" - the
128
+ # subtraction of a scalar from all elements in a sequence.
129
+ # """
130
+ def scalar_addition( seq, scalar, mod = 0 )
131
+ seq.map{|index|
132
+ if 0 == mod
133
+ index + scalar
134
+ else
135
+ (index + scalar) % mod
136
+ end
137
+ }
138
+ end
139
+
140
+
141
+ #
142
+ # Original comments:
143
+ # Returns the result of:
144
+ # (a) rotating a sequence to a given offset; and then,
145
+ # (b) scalar subtracting the resulting first element from the rotated
146
+ # sequence.
147
+ # The result always begins with zero (except when seq is []).
148
+ # If mod is not 0, then all subtraction occurs modulo mod.
149
+ #
150
+ # Note: this function is useful in generating different modes of a scale
151
+ # and different synonyms of a chord.
152
+ #
153
+ def rotate_and_zero( seq, offset, mod = 0, debug_f: false )
154
+ return seq if seq.empty?
155
+
156
+ rotated_seq = rotate(seq, offset)
157
+ first_elem = rotated_seq[0]
158
+ $stderr.puts "{#{__method__}} seq: #{seq}, offset: #{offset}" if debug_f
159
+ $stderr.puts "{#{__method__}} rotated: #{rotated_seq}," +
160
+ " 1st: #{first_elem.inspect}" if debug_f
161
+
162
+ return scalar_addition(rotated_seq, 0 - first_elem, mod)
163
+ end
164
+
165
+
166
+ #
167
+ # The multislice function takes a multiple-element-slice (see below) of
168
+ # a sequence, rotates it if necessary afterward, and returns it. The
169
+ # arguments:
170
+ # ==== Args
171
+ # seq:: a sequence to slice and/or rotate.
172
+ # slice:: another sequence which specifies the elements preserved in
173
+ # seq. This consists of integers, which indicate the indices of
174
+ # elements in seq to preserve. If i is in slice, then seq[i] is
175
+ # preserved; otherwise, it is discarded. (Slicing always happens
176
+ # before rotation).
177
+ # mod:: if present, then all indices in slice are taken modulo this
178
+ # number. I.e., seq[j] is preserved if and only if j some k modulo
179
+ # mod, where k is in slice.
180
+ # offset:: after slicing, this sequence is rotated this number of places.
181
+ #
182
+ # def multislice( seq, slice, mod = 0, offset = 0 )
183
+ def multislice( seq, slice, mod: 0, offset: 0 )
184
+ ret = nil
185
+ if 0 == mod
186
+ ret = slice.map{|index| rotate(seq, offset)[index] }
187
+ else
188
+ ret = slice.map{|index| rotate(seq, offset)[index % mod] }
189
+ end
190
+ ret
191
+ end
192
+
193
+
194
+ # Generates a unicode string from the items in seq delimited by commas.
195
+ # For example repseq([1, 2, 3]) produces "1, 2, 3". Arguments:
196
+ # seq: a sequence of items.
197
+ # pre_process: a function taking one argument and returning another. The
198
+ # function will execute this on each item in seq before concatenating
199
+ # them together. By default, nothing is done to each argument.
200
+ #
201
+ SPACECOMMA = ", "
202
+ def repseq( seq, pre_process = lambda{|x| x} )
203
+ if seq.size == 0
204
+ return ""
205
+ end
206
+ #str_out = ''.join([(str(pre_process(item)) + SPACECOMMA)
207
+ # for item in seq[:-1]]) + str(pre_process(seq[-1]));
208
+
209
+ # ret = (seq[0...-1].map{|item| pre_process.(item).to_s }.join(SPACECOMMA)) +
210
+ # + SPACECOMMA + pre_process.(seq[-1]).to_s
211
+ ret = (seq[0...-1].map{|item| pre_process.(item).to_s } +
212
+ [pre_process.(seq[-1]).to_s]).join(SPACECOMMA)
213
+
214
+ ret
215
+ end
216
+
217
+
218
+ # Returns a sequence of sequences, with each element consisting of an
219
+ # element from otherentries appended to a sequence from seq_of_seq.
220
+ #
221
+ # Note: this is used for building chords.
222
+ #
223
+ def enl_seq( seq_of_seq, otherentries )
224
+ ret = []
225
+ for i in seq_of_seq
226
+ for j in otherentries
227
+ ret.append(i + [j])
228
+ end
229
+ end
230
+ ret
231
+ end
232
+
233
+
234
+ # This function takes a sequence (of numbers) and "normalizes" it. To
235
+ # be specific, it evaluates all numbers inside seq modulo mod. The
236
+ # result is then sorted, and any duplicates are removed.
237
+ #
238
+ # Note: this function is useful for looking up chords by their patterns.
239
+ #
240
+ def norm_seq( seq, mod )
241
+
242
+ #sorted_seq = sorted([x % mod for x in seq])
243
+ sorted_seq = seq.map{|x| x % mod }.sort
244
+
245
+ # We remove duplicates. The following code is straight outta the Python FAQ.
246
+
247
+ #
248
+ last = sorted_seq[-1]
249
+ # for i in range(len(sorted_seq)-2, -1, -1)
250
+ (sorted_seq.size-2).step(0, -1) do |i|
251
+ if last == sorted_seq[i]
252
+ # del sorted_seq[i]
253
+ sorted_seq.delete_at(i)
254
+ else
255
+ last = sorted_seq[i]
256
+ end
257
+ end
258
+
259
+ sorted_seq
260
+ end
261
+
262
+ end
263
+ end
264
+
265
+ require 'active_support'
266
+ require 'active_support/core_ext/object/deep_dup'
267
+ module MusicSetTheory
268
+ module MusUtility
269
+
270
+ def deep_freeze( obj )
271
+ case obj
272
+ when Hash
273
+ obj.each { |k, v| deep_freeze(k); deep_freeze(v) }
274
+ when Array
275
+ obj.each { |e| deep_freeze(e) }
276
+ end
277
+ obj.freeze
278
+ end
279
+
280
+ end
281
+ end
282
+
283
+
284
+ #### endof filename: music_set_theory/musutility.rb