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.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/Rakefile +14 -0
- data/examples/mst_chords.rb +25 -0
- data/examples/mst_temperament.rb +46 -0
- data/lib/music_set_theory/chord_generator.rb +331 -0
- data/lib/music_set_theory/chords.rb +406 -0
- data/lib/music_set_theory/musutility.rb +284 -0
- data/lib/music_set_theory/scales.rb +456 -0
- data/lib/music_set_theory/temperament.rb +783 -0
- data/lib/music_set_theory/version.rb +11 -0
- data/lib/music_set_theory.rb +24 -0
- data/sig/music_theory.rbs +4 -0
- metadata +73 -0
@@ -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
|