musa-dsl 0.26.5 → 0.26.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gem-test-and-push.yml +6 -20
- data/Gemfile +3 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +10 -10
- data/lib/musa-dsl/datasets/e.rb +1 -1
- data/lib/musa-dsl/datasets/gdvd.rb +1 -1
- data/lib/musa-dsl/generative/{backboner.rb → rules.rb} +34 -21
- data/lib/musa-dsl/generative.rb +1 -1
- data/lib/musa-dsl/music/chord-definition.rb +20 -6
- data/lib/musa-dsl/music/chord-definitions.rb +10 -2
- data/lib/musa-dsl/music/chords.rb +135 -241
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +28 -28
- data/lib/musa-dsl/music/scales.rb +22 -12
- data/lib/musa-dsl/neumalang/neumalang.citrus +10 -4
- data/lib/musa-dsl/neumalang/neumalang.rb +14 -3
- data/lib/musa-dsl/repl/repl.rb +7 -3
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +78 -70
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +18 -11
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +8 -8
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +1 -3
- data/lib/musa-dsl/series/main-serie-operations.rb +10 -3
- data/lib/musa-dsl/transport/input-midi-clock.rb +12 -1
- data/lib/musa-dsl.rb +2 -2
- data/musa-dsl.gemspec +3 -3
- metadata +6 -6
@@ -3,319 +3,213 @@ require_relative 'chord-definition'
|
|
3
3
|
|
4
4
|
module Musa
|
5
5
|
module Chords
|
6
|
-
using Musa::Extension::Arrayfy
|
7
|
-
|
8
6
|
class Chord
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
position: nil,
|
21
|
-
move: nil,
|
22
|
-
duplicate: nil,
|
23
|
-
add: nil,
|
24
|
-
drop: nil,
|
25
|
-
#
|
26
|
-
_source: nil)
|
27
|
-
|
28
|
-
# Preparing notes and pitches Arrays: they will we used to collect further notes and pitches
|
29
|
-
#
|
30
|
-
if notes
|
31
|
-
notes = notes.collect do |n|
|
32
|
-
case n
|
33
|
-
when Scales::NoteInScale
|
34
|
-
n
|
35
|
-
when Numeric, Symbol
|
36
|
-
scale[n]
|
7
|
+
|
8
|
+
using Musa::Extension::Arrayfy
|
9
|
+
|
10
|
+
def self.with_root(root_note_or_pitch_or_symbol, scale: nil, allow_chromatic: false, name: nil, move: nil, duplicate: nil, **features)
|
11
|
+
root =
|
12
|
+
case root_note_or_pitch_or_symbol
|
13
|
+
when Scales::NoteInScale
|
14
|
+
root_note_or_pitch_or_symbol
|
15
|
+
when Numeric
|
16
|
+
if scale
|
17
|
+
scale.note_of_pitch(root_note_or_pitch_or_symbol, allow_chromatic: allow_chromatic)
|
37
18
|
else
|
38
|
-
|
19
|
+
scale = Musa::Scales::Scales.default_system.default_tuning[root_note_or_pitch_or_symbol].major
|
20
|
+
scale.note_of_pitch(root_note_or_pitch_or_symbol)
|
39
21
|
end
|
22
|
+
when Symbol
|
23
|
+
raise ArgumentError, "Missing scale parameter to calculate root note for #{root_note_or_pitch_or_symbol}" unless scale
|
24
|
+
|
25
|
+
scale[root_note_or_pitch_or_symbol]
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Unexpected #{root_note_or_pitch_or_symbol}"
|
40
28
|
end
|
41
|
-
end
|
42
29
|
|
43
|
-
|
30
|
+
scale ||= root.scale
|
44
31
|
|
45
|
-
|
46
|
-
|
32
|
+
if name
|
33
|
+
raise ArgumentError, "Received name parameter with value #{name}: features parameter is not allowed" if features.any?
|
47
34
|
|
48
|
-
|
35
|
+
chord_definition = ChordDefinition[name]
|
49
36
|
|
50
|
-
|
37
|
+
elsif features.any?
|
38
|
+
chord_definition = Helper.find_definition_by_features(root.pitch, features, scale, allow_chromatic: allow_chromatic)
|
51
39
|
|
52
|
-
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Don't know how to find a chord definition without name or features parameters"
|
42
|
+
end
|
53
43
|
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
unless chord_definition
|
45
|
+
raise ArgumentError,
|
46
|
+
"Unable to find chord definition for root #{root}" \
|
47
|
+
"#{" with name #{name}" if name}" \
|
48
|
+
"#{" with features #{features}" if features.any?}"
|
57
49
|
end
|
58
50
|
|
59
|
-
|
51
|
+
source_notes_map = Helper.compute_source_notes_map(root, chord_definition, scale)
|
52
|
+
|
53
|
+
Chord.new(root, scale, chord_definition, move, duplicate, source_notes_map)
|
54
|
+
end
|
60
55
|
|
61
|
-
|
56
|
+
class Helper
|
57
|
+
def self.compute_source_notes_map(root, chord_definition, scale)
|
58
|
+
chord_definition.pitch_offsets.transform_values do |offset|
|
59
|
+
pitch = root.pitch + offset
|
60
|
+
[scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
|
61
|
+
end.tap { |_| _.values.each(&:freeze) }.freeze
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
name = name_or_notes_or_pitches
|
71
|
-
|
72
|
-
when Array
|
73
|
-
name_or_notes_or_pitches.each do |note_or_pitch|
|
74
|
-
case note_or_pitch
|
75
|
-
when Scales::NoteInScale
|
76
|
-
notes ||= [] << note_or_pitch
|
77
|
-
when Numeric
|
78
|
-
if scale
|
79
|
-
notes ||= [] << scale[note_or_pitch]
|
80
|
-
else
|
81
|
-
pitches ||= [] << note_or_pitch
|
82
|
-
end
|
83
|
-
when Symbol
|
84
|
-
raise ArgumentError, "Don't know how to recognize #{note_or_pitch} in parameter list #{name_or_notes_or_pitches}: it's a symbol but the scale is not provided" unless scale
|
85
|
-
|
86
|
-
notes ||= [] << scale[note_or_pitch]
|
87
|
-
else
|
88
|
-
raise ArgumentError, "Can't recognize #{note_or_pitch} in parameter list #{name_or_notes_or_pitches}"
|
64
|
+
def self.find_definition_by_features(root_pitch, features, scale, allow_chromatic:)
|
65
|
+
featured_chord_definitions = ChordDefinition.find_by_features(**features)
|
66
|
+
|
67
|
+
unless allow_chromatic
|
68
|
+
featured_chord_definitions.reject! do |chord_definition|
|
69
|
+
chord_definition.pitches(root_pitch).find { |chord_pitch| scale.note_of_pitch(chord_pitch).nil? }
|
89
70
|
end
|
90
71
|
end
|
91
72
|
|
92
|
-
|
93
|
-
# nothing happens
|
94
|
-
else
|
95
|
-
raise ArgumentError, "Can't recognize #{name_or_notes_or_pitches}"
|
73
|
+
featured_chord_definitions.first
|
96
74
|
end
|
75
|
+
end
|
97
76
|
|
98
|
-
|
99
|
-
#
|
100
|
-
|
101
|
-
@notes = if _source.nil?
|
102
|
-
compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
103
|
-
else
|
104
|
-
compute_notes_from_source(_source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
105
|
-
end
|
77
|
+
private_constant :Helper
|
106
78
|
|
107
|
-
|
108
|
-
#
|
79
|
+
ChordGradeNote = Struct.new(:grade, :note, keyword_init: true)
|
109
80
|
|
110
|
-
|
111
|
-
case to_add
|
112
|
-
when NoteInScale
|
113
|
-
@notes << to_add
|
114
|
-
when Numeric # pitch increment
|
115
|
-
pitch = root_pitch + to_add
|
116
|
-
@notes << scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)
|
117
|
-
when Symbol # interval name
|
118
|
-
pitch = root_pitch + scale.offset_of_interval(to_add)
|
119
|
-
@notes << scale.note_of_pitch(pitch)
|
120
|
-
else
|
121
|
-
raise ArgumentError, "Can't recognize element to add #{to_add}"
|
122
|
-
end
|
123
|
-
end
|
81
|
+
private_constant :ChordGradeNote
|
124
82
|
|
125
|
-
|
126
|
-
|
127
|
-
|
83
|
+
private def initialize(root, scale, chord_definition, move, duplicate, source_notes_map)
|
84
|
+
@root = root
|
85
|
+
@scale = scale
|
86
|
+
@chord_definition = chord_definition
|
87
|
+
@move = move.dup.freeze || {}
|
88
|
+
# TODO: ojo esto implica que sólo se puede duplicar una vez cada grado! permitir múltiples?
|
89
|
+
@duplicate = duplicate.dup.freeze || {}
|
90
|
+
@source_notes_map = source_notes_map.dup.freeze
|
91
|
+
@notes_map = compute_moved_and_duplicated(source_notes_map, move, duplicate)
|
128
92
|
|
129
|
-
#
|
93
|
+
# Calculate sorted notes: from lower to higher notes
|
130
94
|
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
move.each do |position, octave|
|
136
|
-
@notes[position][0] = @notes[position][0].octave(octave)
|
95
|
+
@sorted_notes = []
|
96
|
+
@notes_map.each_pair do |name, array_of_notes|
|
97
|
+
array_of_notes.each do |note|
|
98
|
+
@sorted_notes << ChordGradeNote.new(grade: name, note: note).freeze
|
137
99
|
end
|
138
100
|
end
|
139
101
|
|
140
|
-
|
141
|
-
|
102
|
+
@sorted_notes.sort_by! { |chord_grade_note| chord_grade_note.note.pitch }
|
103
|
+
@sorted_notes.freeze
|
142
104
|
|
143
|
-
|
144
|
-
|
145
|
-
|
105
|
+
# Add getters for grades
|
106
|
+
#
|
107
|
+
@notes_map.each_key do |chord_grade_name|
|
108
|
+
define_singleton_method chord_grade_name do |all: false|
|
109
|
+
if all
|
110
|
+
@notes_map[chord_grade_name]
|
111
|
+
else
|
112
|
+
@notes_map[chord_grade_name].first
|
146
113
|
end
|
147
114
|
end
|
148
115
|
end
|
149
116
|
|
150
|
-
#
|
117
|
+
# Add getters for the features values
|
151
118
|
#
|
119
|
+
@chord_definition.features.each_key do |feature_name|
|
120
|
+
define_singleton_method feature_name do
|
121
|
+
@chord_definition.features[feature_name]
|
122
|
+
end
|
123
|
+
end
|
152
124
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
define_singleton_method name do
|
159
|
-
featuring(name)
|
125
|
+
# Add navigation methods to other chords based on changing a feature
|
126
|
+
#
|
127
|
+
ChordDefinition.feature_keys.each do |feature_name|
|
128
|
+
define_singleton_method "with_#{feature_name}".to_sym do |feature_value, allow_chromatic: true|
|
129
|
+
featuring(allow_chromatic: allow_chromatic, **{ feature_name => feature_value })
|
160
130
|
end
|
161
131
|
end
|
162
132
|
end
|
163
133
|
|
164
|
-
attr_reader :
|
134
|
+
attr_reader :scale, :chord_definition, :move, :duplicate
|
165
135
|
|
166
|
-
def
|
167
|
-
|
168
|
-
@chord_definition&.name
|
169
|
-
else
|
170
|
-
Chord.new(_source: self, name: name)
|
171
|
-
end
|
136
|
+
def notes
|
137
|
+
@sorted_notes
|
172
138
|
end
|
173
139
|
|
174
|
-
def
|
175
|
-
@
|
140
|
+
def pitches(*grades)
|
141
|
+
grades = @notes_map.keys if grades.empty?
|
142
|
+
@sorted_notes.select { |_| grades.include?(_.grade) }.collect { |_| _.note.pitch }
|
176
143
|
end
|
177
144
|
|
178
|
-
def
|
179
|
-
|
180
|
-
|
145
|
+
def features
|
146
|
+
@chord_definition.features
|
147
|
+
end
|
181
148
|
|
149
|
+
def featuring(*values, allow_chromatic: false, **hash)
|
150
|
+
# create a new list of features based on current features but
|
151
|
+
# replacing the values for the new ones and adding the new features
|
152
|
+
#
|
153
|
+
features = @chord_definition.features.dup
|
182
154
|
ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }
|
183
155
|
|
184
|
-
|
185
|
-
end
|
156
|
+
chord_definition = Helper.find_definition_by_features(@root.pitch, features, @scale, allow_chromatic: allow_chromatic)
|
186
157
|
|
187
|
-
|
188
|
-
if root.nil?
|
189
|
-
@notes[:root]
|
190
|
-
else
|
191
|
-
Chord.new(_source: self, root: root)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def [](position)
|
196
|
-
case position
|
197
|
-
when Numeric
|
198
|
-
@notes.values[position]
|
199
|
-
when Symbol
|
200
|
-
@notes[position]
|
201
|
-
end
|
202
|
-
end
|
158
|
+
raise ArgumentError, "Unable to find a chord definition for #{features}" unless chord_definition
|
203
159
|
|
204
|
-
|
205
|
-
Chord.new(_source: self, move: octaves)
|
206
|
-
end
|
160
|
+
source_notes_map = Helper.compute_source_notes_map(@root, chord_definition, @scale)
|
207
161
|
|
208
|
-
|
209
|
-
|
162
|
+
Chord.new(@root,
|
163
|
+
(@scale if chord_definition.in_scale?(@scale, chord_root_pitch: @root.pitch)),
|
164
|
+
chord_definition,
|
165
|
+
@move, @duplicate,
|
166
|
+
source_notes_map)
|
210
167
|
end
|
211
168
|
|
212
|
-
def
|
213
|
-
|
214
|
-
|
215
|
-
|
169
|
+
def octave(octave)
|
170
|
+
source_notes_map = @source_notes_map.transform_values do |notes|
|
171
|
+
notes.collect { |note| note.octave(octave) }.freeze
|
172
|
+
end.freeze
|
216
173
|
|
217
|
-
|
218
|
-
def as_scale
|
174
|
+
Chord.new(@root.octave(octave), @scale, chord_definition, @move, @duplicate, source_notes_map)
|
219
175
|
end
|
220
176
|
|
221
|
-
def
|
222
|
-
|
223
|
-
allow_chromatic ||= false
|
224
|
-
|
225
|
-
note_sets = {}
|
226
|
-
scales.each do |scale|
|
227
|
-
note_sets[scale] = if allow_chromatic
|
228
|
-
@notes.values.flatten(1).collect { |n| n.on(scale) || n.on(scale.chromatic) }
|
229
|
-
else
|
230
|
-
@notes.values.flatten(1).collect { |n| n.on(scale) }
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
note_sets_in_scale = note_sets.values.reject { |notes| notes.include?(nil) }
|
235
|
-
note_sets_in_scale.collect { |notes| Chord.new(notes: notes) }
|
177
|
+
def move(**octaves)
|
178
|
+
Chord.new(@root, @scale, @chord_definition, @move.merge(octaves), @duplicate, @source_notes_map)
|
236
179
|
end
|
237
180
|
|
238
|
-
def
|
239
|
-
|
240
|
-
project_on_all(*scales, allow_chromatic: allow_chromatic).first
|
181
|
+
def duplicate(**octaves)
|
182
|
+
Chord.new(@root, @scale, @chord_definition, @move, @duplicate.merge(octaves), @source_notes_map)
|
241
183
|
end
|
242
184
|
|
243
185
|
def ==(other)
|
244
|
-
self.class == other.class &&
|
186
|
+
self.class == other.class &&
|
187
|
+
@sorted_notes == other.notes &&
|
188
|
+
@chord_definition == other.chord_definition
|
245
189
|
end
|
246
190
|
|
247
191
|
def inspect
|
248
|
-
"<Chord
|
192
|
+
"<Chord #{@name} root #{@root} notes #{@sorted_notes.collect { |_| "#{_.grade}=#{_.note.grade}|#{_.note.pitch} "} }>"
|
249
193
|
end
|
250
194
|
|
251
195
|
alias to_s inspect
|
252
196
|
|
253
|
-
private
|
254
|
-
|
255
|
-
def compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
256
|
-
if name && root_pitch && scale && !(notes || pitches || features)
|
257
|
-
|
258
|
-
chord_definition = ChordDefinition[name]
|
259
|
-
|
260
|
-
raise ArgumentError, "Unrecognized #{name} chord" unless chord_definition
|
261
|
-
|
262
|
-
chord_definition.pitch_offsets.transform_values do |offset|
|
263
|
-
pitch = root_pitch + offset
|
264
|
-
[scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
|
265
|
-
end
|
266
|
-
|
267
|
-
elsif root_pitch && features && scale && !(name || notes || pitches)
|
197
|
+
private def compute_moved_and_duplicated(notes_map, moved, duplicated)
|
198
|
+
notes_map = notes_map.transform_values(&:dup)
|
268
199
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
chord_definitions.reject! do |chord_definition|
|
273
|
-
chord_definition.pitches(root_pitch).find { |chord_pitch| scale.note_of_pitch(chord_pitch).nil? }
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
selected = chord_definitions.first
|
278
|
-
|
279
|
-
unless selected
|
280
|
-
raise ArgumentError, "Don't know how to create a chord with root pitch #{root_pitch}"\
|
281
|
-
" and features #{features} based on scale #{scale.kind.class} with root on #{scale.root}: "\
|
282
|
-
" no suitable definition found (allow_chromatic is #{allow_chromatic})"
|
283
|
-
end
|
200
|
+
moved&.each do |position, octave|
|
201
|
+
notes_map[position][0] = notes_map[position][0].octave(octave)
|
202
|
+
end
|
284
203
|
|
285
|
-
|
286
|
-
|
287
|
-
[
|
204
|
+
duplicated&.each do |position, octave|
|
205
|
+
octave.arrayfy.each do |octave|
|
206
|
+
notes_map[position] << notes_map[position][0].octave(octave)
|
288
207
|
end
|
289
|
-
|
290
|
-
elsif (notes || pitches && scale) && !(name || root_pitch || features)
|
291
|
-
|
292
|
-
notes ||= []
|
293
|
-
|
294
|
-
notes += pitches.collect { |p| scale.note_of_pitch(p) } if pitches
|
295
|
-
|
296
|
-
chord_definition = ChordDefinition.find_by_pitches(notes.collect(&:pitch))
|
297
|
-
|
298
|
-
raise "Can't find a chord definition for pitches #{pitches} on scale #{scale.kind.id} based on #{scale.root}" unless chord_definition
|
299
|
-
|
300
|
-
chord_definition.named_pitches(notes, &:pitch)
|
301
|
-
else
|
302
|
-
pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
|
303
|
-
raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
|
304
208
|
end
|
305
|
-
end
|
306
209
|
|
307
|
-
|
308
|
-
if !(name || root_pitch || scale || notes || pitches || features)
|
309
|
-
source.notes
|
310
|
-
|
311
|
-
elsif features && !(name || root_pitch || scale || notes || pitches)
|
312
|
-
compute_notes(nil, source.root.first.pitch, source.root.first.scale, nil, nil, features, allow_chromatic)
|
313
|
-
|
314
|
-
else
|
315
|
-
pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
|
316
|
-
raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
|
317
|
-
end
|
210
|
+
notes_map.tap { |_| _.values.each(&:freeze) }.freeze
|
318
211
|
end
|
319
212
|
end
|
320
213
|
end
|
321
214
|
end
|
215
|
+
|
@@ -70,31 +70,31 @@ module Musa
|
|
70
70
|
class MajorScaleKind < ScaleKind
|
71
71
|
class << self
|
72
72
|
@@pitches =
|
73
|
-
[{ functions: %i[I _1 tonic],
|
73
|
+
[{ functions: %i[I _1 tonic first],
|
74
74
|
pitch: 0 },
|
75
|
-
{ functions: %i[II _2 supertonic],
|
75
|
+
{ functions: %i[II _2 supertonic second],
|
76
76
|
pitch: 2 },
|
77
|
-
{ functions: %i[III _3 mediant],
|
77
|
+
{ functions: %i[III _3 mediant third],
|
78
78
|
pitch: 4 },
|
79
|
-
{ functions: %i[IV _4 subdominant],
|
79
|
+
{ functions: %i[IV _4 subdominant fourth],
|
80
80
|
pitch: 5 },
|
81
|
-
{ functions: %i[V _5 dominant],
|
81
|
+
{ functions: %i[V _5 dominant fifth],
|
82
82
|
pitch: 7 },
|
83
|
-
{ functions: %i[VI _6 submediant relative relative_minor],
|
83
|
+
{ functions: %i[VI _6 submediant relative relative_minor sixth],
|
84
84
|
pitch: 9 },
|
85
|
-
{ functions: %i[VII _7 leading],
|
85
|
+
{ functions: %i[VII _7 leading seventh],
|
86
86
|
pitch: 11 },
|
87
|
-
{ functions: %i[VIII _8],
|
87
|
+
{ functions: %i[VIII _8 eighth],
|
88
88
|
pitch: 12 },
|
89
|
-
{ functions: %i[IX _9],
|
89
|
+
{ functions: %i[IX _9 ninth],
|
90
90
|
pitch: 12 + 2 },
|
91
|
-
{ functions: %i[X _10],
|
91
|
+
{ functions: %i[X _10 tenth],
|
92
92
|
pitch: 12 + 4 },
|
93
|
-
{ functions: %i[XI _11],
|
93
|
+
{ functions: %i[XI _11 eleventh],
|
94
94
|
pitch: 12 + 5 },
|
95
|
-
{ functions: %i[XII _12],
|
95
|
+
{ functions: %i[XII _12 twelfth],
|
96
96
|
pitch: 12 + 7 },
|
97
|
-
{ functions: %i[XIII _13],
|
97
|
+
{ functions: %i[XIII _13 thirteenth],
|
98
98
|
pitch: 12 + 9 }].freeze
|
99
99
|
|
100
100
|
def pitches
|
@@ -113,34 +113,34 @@ module Musa
|
|
113
113
|
EquallyTempered12ToneScaleSystem.register MajorScaleKind
|
114
114
|
end
|
115
115
|
|
116
|
-
class
|
116
|
+
class MinorNaturalScaleKind < ScaleKind
|
117
117
|
class << self
|
118
118
|
@@pitches =
|
119
|
-
[{ functions: %i[i _1 tonic],
|
119
|
+
[{ functions: %i[i _1 tonic first],
|
120
120
|
pitch: 0 },
|
121
|
-
{ functions: %i[ii _2 supertonic],
|
121
|
+
{ functions: %i[ii _2 supertonic second],
|
122
122
|
pitch: 2 },
|
123
|
-
{ functions: %i[iii _3 mediant relative relative_major],
|
123
|
+
{ functions: %i[iii _3 mediant relative relative_major third],
|
124
124
|
pitch: 3 },
|
125
|
-
{ functions: %i[iv _4 subdominant],
|
125
|
+
{ functions: %i[iv _4 subdominant fourth],
|
126
126
|
pitch: 5 },
|
127
|
-
{ functions: %i[v _5 dominant],
|
127
|
+
{ functions: %i[v _5 dominant fifth],
|
128
128
|
pitch: 7 },
|
129
|
-
{ functions: %i[vi _6 submediant],
|
129
|
+
{ functions: %i[vi _6 submediant sixth],
|
130
130
|
pitch: 8 },
|
131
|
-
{ functions: %i[vii _7],
|
131
|
+
{ functions: %i[vii _7 seventh],
|
132
132
|
pitch: 10 },
|
133
|
-
{ functions: %i[viii _8],
|
133
|
+
{ functions: %i[viii _8 eighth],
|
134
134
|
pitch: 12 },
|
135
|
-
{ functions: %i[ix _9],
|
135
|
+
{ functions: %i[ix _9 ninth],
|
136
136
|
pitch: 12 + 2 },
|
137
|
-
{ functions: %i[x _10],
|
137
|
+
{ functions: %i[x _10 tenth],
|
138
138
|
pitch: 12 + 3 },
|
139
|
-
{ functions: %i[xi _11],
|
139
|
+
{ functions: %i[xi _11 eleventh],
|
140
140
|
pitch: 12 + 5 },
|
141
|
-
{ functions: %i[xii _12],
|
141
|
+
{ functions: %i[xii _12 twelfth],
|
142
142
|
pitch: 12 + 7 },
|
143
|
-
{ functions: %i[xiii _13],
|
143
|
+
{ functions: %i[xiii _13 thirteenth],
|
144
144
|
pitch: 12 + 8 }].freeze
|
145
145
|
|
146
146
|
def pitches
|
@@ -156,7 +156,7 @@ module Musa
|
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
159
|
-
EquallyTempered12ToneScaleSystem.register
|
159
|
+
EquallyTempered12ToneScaleSystem.register MinorNaturalScaleKind
|
160
160
|
end
|
161
161
|
|
162
162
|
class MinorHarmonicScaleKind < ScaleKind
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'chords'
|
2
|
+
|
1
3
|
module Musa
|
2
4
|
module Scales
|
3
5
|
module Scales
|
@@ -180,8 +182,6 @@ module Musa
|
|
180
182
|
end
|
181
183
|
|
182
184
|
class ScaleKind
|
183
|
-
extend Forwardable
|
184
|
-
|
185
185
|
def initialize(tuning)
|
186
186
|
@tuning = tuning
|
187
187
|
@scales = {}
|
@@ -194,6 +194,10 @@ module Musa
|
|
194
194
|
@scales[root_pitch]
|
195
195
|
end
|
196
196
|
|
197
|
+
def default_root
|
198
|
+
self[60]
|
199
|
+
end
|
200
|
+
|
197
201
|
def absolut
|
198
202
|
self[0]
|
199
203
|
end
|
@@ -277,9 +281,10 @@ module Musa
|
|
277
281
|
end
|
278
282
|
end
|
279
283
|
|
284
|
+
freeze
|
280
285
|
end
|
281
286
|
|
282
|
-
def_delegators :@kind, :
|
287
|
+
def_delegators :@kind, :tuning
|
283
288
|
|
284
289
|
attr_reader :kind, :root_pitch
|
285
290
|
|
@@ -406,10 +411,6 @@ module Musa
|
|
406
411
|
@kind.tuning.offset_of_interval(interval_name)
|
407
412
|
end
|
408
413
|
|
409
|
-
def chord_of(*grades_or_symbols)
|
410
|
-
Chord.new(notes: grades_or_symbols.collect { |g| self[g] })
|
411
|
-
end
|
412
|
-
|
413
414
|
def ==(other)
|
414
415
|
self.class == other.class &&
|
415
416
|
@kind == other.kind &&
|
@@ -454,13 +455,13 @@ module Musa
|
|
454
455
|
@scale.kind.class.pitches[grade][:functions]
|
455
456
|
end
|
456
457
|
|
457
|
-
def octave(octave = nil)
|
458
|
+
def octave(octave = nil, absolute: false)
|
458
459
|
if octave.nil?
|
459
460
|
@octave
|
460
461
|
else
|
461
462
|
raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
|
462
463
|
|
463
|
-
@scale[@grade + (@octave + octave) * @scale.kind.class.grades]
|
464
|
+
@scale[@grade + ((absolute ? 0 : @octave) + octave) * @scale.kind.class.grades]
|
464
465
|
end
|
465
466
|
end
|
466
467
|
|
@@ -558,11 +559,20 @@ module Musa
|
|
558
559
|
scale.note_of_pitch @pitch
|
559
560
|
end
|
560
561
|
|
561
|
-
def chord(*feature_values,
|
562
|
+
def chord(*feature_values,
|
563
|
+
allow_chromatic: nil,
|
564
|
+
move: nil,
|
565
|
+
duplicate: nil,
|
566
|
+
**features_hash)
|
567
|
+
|
562
568
|
features = { size: :triad } if feature_values.empty? && features_hash.empty?
|
563
|
-
features ||= ChordDefinition.features_from(feature_values, features_hash)
|
569
|
+
features ||= Musa::Chords::ChordDefinition.features_from(feature_values, features_hash)
|
564
570
|
|
565
|
-
Musa::Chords::Chord.
|
571
|
+
Musa::Chords::Chord.with_root(self,
|
572
|
+
allow_chromatic: allow_chromatic,
|
573
|
+
move: move,
|
574
|
+
duplicate: duplicate,
|
575
|
+
**features)
|
566
576
|
end
|
567
577
|
|
568
578
|
def ==(other)
|