musa-dsl 0.14.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +20 -0
- data/LICENSE.md +157 -0
- data/README.md +8 -0
- data/lib/musa-dsl/core-ext/array-apply-get.rb +18 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +29 -0
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +28 -0
- data/lib/musa-dsl/core-ext/array-to-serie.rb +20 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +15 -0
- data/lib/musa-dsl/core-ext/as-context-run.rb +44 -0
- data/lib/musa-dsl/core-ext/duplicate.rb +134 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +55 -0
- data/lib/musa-dsl/core-ext/inspect-nice.rb +28 -0
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +85 -0
- data/lib/musa-dsl/core-ext/proc-nice.rb +13 -0
- data/lib/musa-dsl/core-ext/send-nice.rb +21 -0
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +27 -0
- data/lib/musa-dsl/core-ext.rb +13 -0
- data/lib/musa-dsl/datasets/gdv-decorators.rb +221 -0
- data/lib/musa-dsl/datasets/gdv.rb +499 -0
- data/lib/musa-dsl/datasets/pdv.rb +44 -0
- data/lib/musa-dsl/datasets.rb +5 -0
- data/lib/musa-dsl/generative/darwin.rb +145 -0
- data/lib/musa-dsl/generative/generative-grammar.rb +294 -0
- data/lib/musa-dsl/generative/markov.rb +78 -0
- data/lib/musa-dsl/generative/rules.rb +282 -0
- data/lib/musa-dsl/generative/variatio.rb +331 -0
- data/lib/musa-dsl/generative.rb +5 -0
- data/lib/musa-dsl/midi/midi-recorder.rb +83 -0
- data/lib/musa-dsl/midi/midi-voices.rb +274 -0
- data/lib/musa-dsl/midi.rb +2 -0
- data/lib/musa-dsl/music/chord-definition.rb +99 -0
- data/lib/musa-dsl/music/chord-definitions.rb +13 -0
- data/lib/musa-dsl/music/chords.rb +326 -0
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +204 -0
- data/lib/musa-dsl/music/scales.rb +584 -0
- data/lib/musa-dsl/music.rb +6 -0
- data/lib/musa-dsl/neuma/neuma.rb +181 -0
- data/lib/musa-dsl/neuma.rb +1 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +294 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +179 -0
- data/lib/musa-dsl/neumalang.rb +3 -0
- data/lib/musa-dsl/repl/repl.rb +143 -0
- data/lib/musa-dsl/repl.rb +1 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +189 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +354 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +382 -0
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +261 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +94 -0
- data/lib/musa-dsl/sequencer/sequencer.rb +3 -0
- data/lib/musa-dsl/sequencer.rb +1 -0
- data/lib/musa-dsl/series/base-series.rb +245 -0
- data/lib/musa-dsl/series/hash-serie-splitter.rb +194 -0
- data/lib/musa-dsl/series/holder-serie.rb +87 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +726 -0
- data/lib/musa-dsl/series/main-serie-operations.rb +1151 -0
- data/lib/musa-dsl/series/proxy-serie.rb +69 -0
- data/lib/musa-dsl/series/queue-serie.rb +94 -0
- data/lib/musa-dsl/series/series.rb +8 -0
- data/lib/musa-dsl/series.rb +1 -0
- data/lib/musa-dsl/transport/clock.rb +36 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +47 -0
- data/lib/musa-dsl/transport/external-tick-clock.rb +31 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +124 -0
- data/lib/musa-dsl/transport/timer-clock.rb +102 -0
- data/lib/musa-dsl/transport/timer.rb +40 -0
- data/lib/musa-dsl/transport/transport.rb +137 -0
- data/lib/musa-dsl/transport.rb +9 -0
- data/lib/musa-dsl.rb +17 -0
- data/musa-dsl.gemspec +17 -0
- metadata +174 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
module Musa
|
2
|
+
class ChordDefinition
|
3
|
+
class << self
|
4
|
+
def [](name)
|
5
|
+
@definitions[name]
|
6
|
+
end
|
7
|
+
|
8
|
+
def register(name, offsets:, **features)
|
9
|
+
definition = ChordDefinition.new(name, offsets: offsets, **features).freeze
|
10
|
+
|
11
|
+
@definitions ||= {}
|
12
|
+
@definitions[definition.name] = definition
|
13
|
+
|
14
|
+
@features_by_value ||= {}
|
15
|
+
definition.features.each { |k, v| @features_by_value[v] = k }
|
16
|
+
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by_pitches(pitches)
|
21
|
+
@definitions.values.find { |d| d.matches(pitches) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def features_from(values = nil, hash = nil)
|
25
|
+
values ||= []
|
26
|
+
hash ||= {}
|
27
|
+
|
28
|
+
features = hash.dup
|
29
|
+
values.each { |v| features[@features_by_value[v]] = v }
|
30
|
+
|
31
|
+
features
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_by_features(*values, **hash)
|
35
|
+
features = features_from(values, hash)
|
36
|
+
@definitions.values.select { |d| features <= d.features }
|
37
|
+
end
|
38
|
+
|
39
|
+
def feature_key_of(feature_value)
|
40
|
+
@features_by_value[feature_value]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(name, offsets:, **features)
|
45
|
+
@name = name
|
46
|
+
@features = features.clone.freeze
|
47
|
+
@pitch_offsets = offsets.clone.freeze
|
48
|
+
@pitch_names = offsets.collect { |k, v| [v, k] }.to_h
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :name, :features, :pitch_offsets, :pitch_names
|
52
|
+
|
53
|
+
def pitches(root_pitch)
|
54
|
+
@pitch_offsets.values.collect { |offset| root_pitch + offset }
|
55
|
+
end
|
56
|
+
|
57
|
+
def named_pitches(elements_or_pitches, &block)
|
58
|
+
pitches = elements_or_pitches.collect do |element_or_pitch|
|
59
|
+
[if block_given?
|
60
|
+
yield element_or_pitch
|
61
|
+
else
|
62
|
+
element_or_pitch
|
63
|
+
end,
|
64
|
+
element_or_pitch]
|
65
|
+
end.to_h
|
66
|
+
|
67
|
+
root_pitch = pitches.keys.find do |candidate_root_pitch|
|
68
|
+
candidate_pitches = pitches.keys.collect { |p| p - candidate_root_pitch }
|
69
|
+
octave_reduce(candidate_pitches).uniq == octave_reduce(@pitch_offsets.values).uniq
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO: OJO: problema con las notas duplicadas, con la identificación de inversiones y con las notas a distancias de más de una octava
|
73
|
+
|
74
|
+
pitches.collect do |pitch, element|
|
75
|
+
[@pitch_names[pitch - root_pitch], [element]]
|
76
|
+
end.to_h
|
77
|
+
end
|
78
|
+
|
79
|
+
def matches(pitches)
|
80
|
+
reduced_pitches = octave_reduce(pitches).uniq
|
81
|
+
|
82
|
+
!!reduced_pitches.find do |candidate_root_pitch|
|
83
|
+
reduced_pitches.sort == octave_reduce(pitches(candidate_root_pitch)).uniq.sort
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
"<ChordDefinition: name = #{@name} features = #{@features} pitch_offsets = #{@pitch_offsets}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
alias to_s inspect
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def octave_reduce(pitches)
|
96
|
+
pitches.collect { |p| p % 12 }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Musa::ChordDefinition.register :maj, quality: :major, size: :triad, offsets: { root: 0, third: 4, fifth: 7 }
|
2
|
+
Musa::ChordDefinition.register :min, quality: :minor, size: :triad, offsets: { root: 0, third: 3, fifth: 7 }
|
3
|
+
|
4
|
+
Musa::ChordDefinition.register :maj7, quality: :major, size: :seventh, offsets: { root: 0, third: 4, fifth: 7, seventh: 11 }
|
5
|
+
Musa::ChordDefinition.register :maj7, quality: :major, size: :seventh, dominant: :dominant , offsets: { root: 0, third: 4, fifth: 7, seventh: 10 }
|
6
|
+
|
7
|
+
Musa::ChordDefinition.register :min7, quality: :minor, size: :seventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11 }
|
8
|
+
|
9
|
+
Musa::ChordDefinition.register :maj9, quality: :major, size: :ninth, offsets: { root: 0, third: 4, fifth: 7, seventh: 11, ninth: 14 }
|
10
|
+
Musa::ChordDefinition.register :min9, quality: :minor, size: :ninth, offsets: { root: 0, third: 3, fifth: 7, seventh: 11, ninth: 14 }
|
11
|
+
|
12
|
+
Musa::ChordDefinition.register :maj11, quality: :major, size: :eleventh, offsets: { root: 0, third: 4, fifth: 7, seventh: 11, ninth: 14, eleventh: 17 }
|
13
|
+
Musa::ChordDefinition.register :min11, quality: :minor, size: :eleventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11, ninth: 14, eleventh: 17 }
|
@@ -0,0 +1,326 @@
|
|
1
|
+
require_relative 'scales'
|
2
|
+
require_relative 'chord-definition'
|
3
|
+
|
4
|
+
module Musa
|
5
|
+
class Chord
|
6
|
+
def initialize(name_or_notes_or_pitches = nil, # name | [notes] | [pitches]
|
7
|
+
# definitory
|
8
|
+
name: nil,
|
9
|
+
root: nil, root_grade: nil,
|
10
|
+
notes: nil, pitches: nil,
|
11
|
+
features: nil,
|
12
|
+
# target scale (or scale reference)
|
13
|
+
scale: nil,
|
14
|
+
allow_chromatic: nil,
|
15
|
+
# operations
|
16
|
+
inversion: nil, state: nil,
|
17
|
+
position: nil,
|
18
|
+
move: nil,
|
19
|
+
duplicate: nil,
|
20
|
+
add: nil,
|
21
|
+
drop: nil,
|
22
|
+
#
|
23
|
+
_source: nil)
|
24
|
+
|
25
|
+
# Preparing notes and pitches Arrays: they will we used to collect further notes and pitches
|
26
|
+
#
|
27
|
+
if notes
|
28
|
+
notes = notes.collect do |n|
|
29
|
+
case n
|
30
|
+
when Musa::NoteInScale
|
31
|
+
n
|
32
|
+
when Numeric, Symbol
|
33
|
+
scale[n]
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Can't recognize #{n} in notes list #{notes}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
pitches = pitches.clone if pitches
|
41
|
+
|
42
|
+
# Preparing root_pitch
|
43
|
+
#
|
44
|
+
|
45
|
+
root_pitch = nil
|
46
|
+
|
47
|
+
raise ArgumentError, "Duplicate parameter: root: #{root} and root_grade: #{root_grade}" if root && root_grade
|
48
|
+
|
49
|
+
allow_chromatic ||= scale.nil?
|
50
|
+
|
51
|
+
if root && root.is_a?(Musa::NoteInScale)
|
52
|
+
root_pitch = root.pitch
|
53
|
+
scale ||= root.scale
|
54
|
+
end
|
55
|
+
|
56
|
+
raise ArgumentError, "Don't know how to recognize root_grade #{root_grade}: scale is not provided" if root_grade && !scale
|
57
|
+
|
58
|
+
root_pitch = scale[root_grade].pitch if root_grade && scale
|
59
|
+
|
60
|
+
# Parse name_or_notes_or_pitches to name, notes, pitches
|
61
|
+
#
|
62
|
+
#
|
63
|
+
case name_or_notes_or_pitches
|
64
|
+
when Symbol
|
65
|
+
raise ArgumentError, "Duplicate parameter #{name_or_notes_or_pitches} and name: #{name}" if name
|
66
|
+
|
67
|
+
name = name_or_notes_or_pitches
|
68
|
+
|
69
|
+
when Array
|
70
|
+
name_or_notes_or_pitches.each do |note_or_pitch|
|
71
|
+
case note_or_pitch
|
72
|
+
when Musa::NoteInScale
|
73
|
+
notes ||= [] << note_or_pitch
|
74
|
+
when Numeric
|
75
|
+
if scale
|
76
|
+
notes ||= [] << scale[note_or_pitch]
|
77
|
+
else
|
78
|
+
pitches ||= [] << note_or_pitch
|
79
|
+
end
|
80
|
+
when Symbol
|
81
|
+
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
|
82
|
+
|
83
|
+
notes ||= [] << scale[note_or_pitch]
|
84
|
+
else
|
85
|
+
raise ArgumentError, "Can't recognize #{note_or_pitch} in parameter list #{name_or_notes_or_pitches}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
when nil
|
90
|
+
# nothing happens
|
91
|
+
else
|
92
|
+
raise ArgumentError, "Can't recognize #{name_or_notes_or_pitches}"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Eval definitory atributes
|
96
|
+
#
|
97
|
+
|
98
|
+
if _source.nil?
|
99
|
+
@notes = compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
100
|
+
else
|
101
|
+
@notes = compute_notes_from_source(_source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Eval adding / droping operations
|
105
|
+
#
|
106
|
+
|
107
|
+
if add
|
108
|
+
add.each do |to_add|
|
109
|
+
case to_add
|
110
|
+
when NoteInScale
|
111
|
+
@notes << to_add
|
112
|
+
when Numeric # pitch increment
|
113
|
+
pitch = root_pitch + to_add
|
114
|
+
@notes << scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)
|
115
|
+
when Symbol # interval name
|
116
|
+
pitch = root_pitch + scale.offset_of_interval(to_add)
|
117
|
+
@notes << scale.note_of_pitch(pitch)
|
118
|
+
else
|
119
|
+
raise ArgumentError, "Can't recognize element to add #{to_add}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# TODO: Missing chord operations: drop, inversion, state, position
|
125
|
+
#
|
126
|
+
raise NotImplementedError, 'Missing chord operations: drop, inversion, state, position' if drop || inversion || state || position
|
127
|
+
|
128
|
+
# Eval voice increment operations
|
129
|
+
#
|
130
|
+
|
131
|
+
if move
|
132
|
+
raise ArgumentError, 'move: expected a Hash' unless move.is_a?(Hash)
|
133
|
+
|
134
|
+
move.each do |position, octave|
|
135
|
+
@notes[position][0] = @notes[position][0].octave(octave)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
if duplicate
|
140
|
+
raise ArgumentError, 'duplicate: expected a Hash' unless duplicate.is_a?(Hash)
|
141
|
+
|
142
|
+
duplicate.each do |position, octave|
|
143
|
+
octave.arrayfy.each do |octave|
|
144
|
+
@notes[position] << @notes[position][0].octave(octave)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Identify chord
|
150
|
+
#
|
151
|
+
|
152
|
+
@notes.freeze
|
153
|
+
|
154
|
+
@chord_definition = ChordDefinition.find_by_pitches(@notes.values.flatten(1).collect(&:pitch))
|
155
|
+
end
|
156
|
+
|
157
|
+
attr_reader :notes, :chord_definition
|
158
|
+
|
159
|
+
def name(name = nil)
|
160
|
+
if name.nil?
|
161
|
+
@chord_definition.name if @chord_definition
|
162
|
+
else
|
163
|
+
Chord.new(_source: self, name: name)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def features
|
168
|
+
@chord_definition.features if @chord_definition
|
169
|
+
end
|
170
|
+
|
171
|
+
def featuring(*values, allow_chromatic: nil, **hash)
|
172
|
+
features = @chord_definition.features.dup if @chord_definition
|
173
|
+
features ||= {}
|
174
|
+
|
175
|
+
ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }
|
176
|
+
|
177
|
+
Chord.new(_source: self, allow_chromatic: allow_chromatic, features: features)
|
178
|
+
end
|
179
|
+
|
180
|
+
def root(root = nil)
|
181
|
+
if root.nil?
|
182
|
+
@notes[:root]
|
183
|
+
else
|
184
|
+
Chord.new(_source: self, root: root)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def [](position)
|
189
|
+
case position
|
190
|
+
when Numeric
|
191
|
+
@notes.values[position]
|
192
|
+
when Symbol
|
193
|
+
@notes[position]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def move(**octaves)
|
198
|
+
Chord.new(_source: self, move: octaves)
|
199
|
+
end
|
200
|
+
|
201
|
+
def duplicate(**octaves)
|
202
|
+
Chord.new(_source: self, duplicate: octaves)
|
203
|
+
end
|
204
|
+
|
205
|
+
def scale
|
206
|
+
scales = @notes.values.flatten(1).collect(&:scale).uniq
|
207
|
+
scales.first if scales.size == 1
|
208
|
+
end
|
209
|
+
|
210
|
+
# Converts the chord to a specific scale with the notes in the chord
|
211
|
+
def as_scale
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def project_on_all(*scales, allow_chromatic: nil)
|
216
|
+
# TODO add match to other chords... what does it means?
|
217
|
+
allow_chromatic ||= false
|
218
|
+
|
219
|
+
note_sets = {}
|
220
|
+
scales.each do |scale|
|
221
|
+
if allow_chromatic
|
222
|
+
note_sets[scale] = @notes.values.flatten(1).collect { |n| n.on(scale) || n.on(scale.chromatic) }
|
223
|
+
else
|
224
|
+
note_sets[scale] = @notes.values.flatten(1).collect { |n| n.on(scale) }
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
note_sets_in_scale = note_sets.values.reject { |notes| notes.include?(nil) }
|
229
|
+
note_sets_in_scale.collect { |notes| Chord.new(notes: notes) }
|
230
|
+
end
|
231
|
+
|
232
|
+
def project_on(*scales, allow_chromatic: nil)
|
233
|
+
allow_chromatic ||= false
|
234
|
+
project_on_all(*scales, allow_chromatic: allow_chromatic).first
|
235
|
+
end
|
236
|
+
|
237
|
+
def ==(other)
|
238
|
+
self.class == other.class && @notes == other.notes
|
239
|
+
end
|
240
|
+
|
241
|
+
def inspect
|
242
|
+
"<Chord: notes = #{@notes}>"
|
243
|
+
end
|
244
|
+
|
245
|
+
alias to_s inspect
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
def compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
250
|
+
if name && root_pitch && scale && !(notes || pitches || features)
|
251
|
+
|
252
|
+
chord_definition = ChordDefinition[name]
|
253
|
+
|
254
|
+
raise ArgumentError, "Unrecognized #{name} chord" unless chord_definition
|
255
|
+
|
256
|
+
chord_definition.pitch_offsets.transform_values do |offset|
|
257
|
+
pitch = root_pitch + offset
|
258
|
+
[scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
|
259
|
+
end
|
260
|
+
|
261
|
+
elsif root_pitch && features && scale && !(name || notes || pitches)
|
262
|
+
|
263
|
+
chord_definitions = ChordDefinition.find_by_features(**features)
|
264
|
+
|
265
|
+
unless allow_chromatic
|
266
|
+
chord_definitions.reject! do |chord_definition|
|
267
|
+
chord_definition.pitches(root_pitch).find { |chord_pitch| scale.note_of_pitch(chord_pitch).nil? }
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
selected = chord_definitions.first
|
272
|
+
|
273
|
+
unless selected
|
274
|
+
raise ArgumentError, "Don't know how to create a chord with root pitch #{root_pitch}"\
|
275
|
+
" and features #{features} based on scale #{scale.kind.class} with root on #{scale.root}: "\
|
276
|
+
" no suitable definition found (allow_chromatic is #{allow_chromatic})"
|
277
|
+
end
|
278
|
+
|
279
|
+
selected.pitch_offsets.transform_values do |offset|
|
280
|
+
pitch = root_pitch + offset
|
281
|
+
[scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
|
282
|
+
end
|
283
|
+
|
284
|
+
elsif (notes || pitches && scale) && !(name || root_pitch || features)
|
285
|
+
|
286
|
+
notes ||= []
|
287
|
+
|
288
|
+
notes += pitches.collect { |p| scale.note_of_pitch(p) } if pitches
|
289
|
+
|
290
|
+
chord_definition = ChordDefinition.find_by_pitches(notes.collect(&:pitch))
|
291
|
+
|
292
|
+
raise "Can't find a chord definition for pitches #{pitches} on scale #{scale.kind.id} based on #{scale.root}" unless chord_definition
|
293
|
+
|
294
|
+
chord_definition.named_pitches(notes, &:pitch)
|
295
|
+
else
|
296
|
+
pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
|
297
|
+
raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def compute_notes_from_source(source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
302
|
+
if !(name || root_pitch || scale || notes || pitches || features)
|
303
|
+
source.notes
|
304
|
+
|
305
|
+
elsif features && !(name || root_pitch || scale || notes || pitches)
|
306
|
+
compute_notes(nil, source.root.first.pitch, source.root.first.scale, nil, nil, features, allow_chromatic)
|
307
|
+
|
308
|
+
else
|
309
|
+
pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
|
310
|
+
raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def method_missing(method_name, *args, **key_args, &block)
|
315
|
+
if ChordDefinition.feature_key_of(method_name) && args.empty? && key_args.empty? && !block
|
316
|
+
featuring(method_name)
|
317
|
+
else
|
318
|
+
super
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def respond_to_missing?(method_name, include_private)
|
323
|
+
ChordDefinition.feature_key_of(method_name) || super
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Musa
|
2
|
+
class TwelveSemitonesScaleSystem < ScaleSystem
|
3
|
+
class << self
|
4
|
+
@@intervals = { P0: 0, m2: 1, M2: 2, m3: 3, M3: 4, P4: 5, TT: 6, P5: 7, m6: 8, M6: 9, m7: 10, M7: 11, P8: 12 }
|
5
|
+
|
6
|
+
def id
|
7
|
+
:et12
|
8
|
+
end
|
9
|
+
|
10
|
+
def notes_in_octave
|
11
|
+
12
|
12
|
+
end
|
13
|
+
|
14
|
+
def part_of_tone_size
|
15
|
+
1
|
16
|
+
end
|
17
|
+
|
18
|
+
def intervals
|
19
|
+
@@intervals
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class EquallyTempered12ToneScaleSystem < TwelveSemitonesScaleSystem
|
25
|
+
class << self
|
26
|
+
def frequency_of_pitch(pitch, _root_pitch, a_frequency)
|
27
|
+
(a_frequency * Rational(2)**Rational(pitch - 69, 12)).to_f
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Scales.register EquallyTempered12ToneScaleSystem, default: true
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class ChromaticScaleKind < ScaleKind
|
36
|
+
class << self
|
37
|
+
@@pitches =
|
38
|
+
[{ functions: [:_1], pitch: 0 },
|
39
|
+
{ functions: [:_2], pitch: 1 },
|
40
|
+
{ functions: [:_3], pitch: 2 },
|
41
|
+
{ functions: [:_4], pitch: 3 },
|
42
|
+
{ functions: [:_5], pitch: 4 },
|
43
|
+
{ functions: [:_6], pitch: 5 },
|
44
|
+
{ functions: [:_7], pitch: 6 },
|
45
|
+
{ functions: [:_8], pitch: 7 },
|
46
|
+
{ functions: [:_9], pitch: 8 },
|
47
|
+
{ functions: [:_10], pitch: 9 },
|
48
|
+
{ functions: [:_11], pitch: 10 },
|
49
|
+
{ functions: [:_12], pitch: 11 }].freeze
|
50
|
+
|
51
|
+
def pitches
|
52
|
+
@@pitches
|
53
|
+
end
|
54
|
+
|
55
|
+
def id
|
56
|
+
:chromatic
|
57
|
+
end
|
58
|
+
|
59
|
+
def chromatic?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
EquallyTempered12ToneScaleSystem.register ChromaticScaleKind
|
65
|
+
end
|
66
|
+
|
67
|
+
class MajorScaleKind < ScaleKind
|
68
|
+
class << self
|
69
|
+
@@pitches =
|
70
|
+
[{ functions: %i[I _1 tonic],
|
71
|
+
pitch: 0 },
|
72
|
+
{ functions: %i[II _2 supertonic],
|
73
|
+
pitch: 2 },
|
74
|
+
{ functions: %i[III _3 mediant],
|
75
|
+
pitch: 4 },
|
76
|
+
{ functions: %i[IV _4 subdominant],
|
77
|
+
pitch: 5 },
|
78
|
+
{ functions: %i[V _5 dominant],
|
79
|
+
pitch: 7 },
|
80
|
+
{ functions: %i[VI _6 submediant relative relative_minor],
|
81
|
+
pitch: 9 },
|
82
|
+
{ functions: %i[VII _7 leading],
|
83
|
+
pitch: 11 },
|
84
|
+
{ functions: %i[VIII _8],
|
85
|
+
pitch: 12 },
|
86
|
+
{ functions: %i[IX _9],
|
87
|
+
pitch: 12 + 2 },
|
88
|
+
{ functions: %i[X _10],
|
89
|
+
pitch: 12 + 4 },
|
90
|
+
{ functions: %i[XI _11],
|
91
|
+
pitch: 12 + 5 },
|
92
|
+
{ functions: %i[XII _12],
|
93
|
+
pitch: 12 + 7 },
|
94
|
+
{ functions: %i[XIII _13],
|
95
|
+
pitch: 12 + 9 }].freeze
|
96
|
+
|
97
|
+
def pitches
|
98
|
+
@@pitches
|
99
|
+
end
|
100
|
+
|
101
|
+
def grades
|
102
|
+
7
|
103
|
+
end
|
104
|
+
|
105
|
+
def id
|
106
|
+
:major
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
EquallyTempered12ToneScaleSystem.register MajorScaleKind
|
111
|
+
end
|
112
|
+
|
113
|
+
class MinorScaleKind < ScaleKind
|
114
|
+
class << self
|
115
|
+
@@pitches =
|
116
|
+
[{ functions: %i[i _1 tonic],
|
117
|
+
pitch: 0 },
|
118
|
+
{ functions: %i[ii _2 supertonic],
|
119
|
+
pitch: 2 },
|
120
|
+
{ functions: %i[iii _3 mediant relative relative_major],
|
121
|
+
pitch: 3 },
|
122
|
+
{ functions: %i[iv _4 subdominant],
|
123
|
+
pitch: 5 },
|
124
|
+
{ functions: %i[v _5 dominant],
|
125
|
+
pitch: 7 },
|
126
|
+
{ functions: %i[vi _6 submediant],
|
127
|
+
pitch: 8 },
|
128
|
+
{ functions: %i[vii _7],
|
129
|
+
pitch: 10 },
|
130
|
+
{ functions: %i[viii _8],
|
131
|
+
pitch: 12 },
|
132
|
+
{ functions: %i[ix _9],
|
133
|
+
pitch: 12 + 2 },
|
134
|
+
{ functions: %i[x _10],
|
135
|
+
pitch: 12 + 3 },
|
136
|
+
{ functions: %i[xi _11],
|
137
|
+
pitch: 12 + 5 },
|
138
|
+
{ functions: %i[xii _12],
|
139
|
+
pitch: 12 + 7 },
|
140
|
+
{ functions: %i[xiii _13],
|
141
|
+
pitch: 12 + 8 }].freeze
|
142
|
+
|
143
|
+
def pitches
|
144
|
+
@@pitches
|
145
|
+
end
|
146
|
+
|
147
|
+
def grades
|
148
|
+
7
|
149
|
+
end
|
150
|
+
|
151
|
+
def id
|
152
|
+
:minor
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
EquallyTempered12ToneScaleSystem.register MinorScaleKind
|
157
|
+
end
|
158
|
+
|
159
|
+
class MinorHarmonicScaleKind < ScaleKind
|
160
|
+
class << self
|
161
|
+
@@pitches =
|
162
|
+
[{ functions: %i[i _1 tonic],
|
163
|
+
pitch: 0 },
|
164
|
+
{ functions: %i[ii _2 supertonic],
|
165
|
+
pitch: 2 },
|
166
|
+
{ functions: %i[iii _3 mediant relative relative_major],
|
167
|
+
pitch: 3 },
|
168
|
+
{ functions: %i[iv _4 subdominant],
|
169
|
+
pitch: 5 },
|
170
|
+
{ functions: %i[v _5 dominant],
|
171
|
+
pitch: 7 },
|
172
|
+
{ functions: %i[vi _6 submediant],
|
173
|
+
pitch: 8 },
|
174
|
+
{ functions: %i[vii _7 leading],
|
175
|
+
pitch: 11 },
|
176
|
+
{ functions: %i[viii _8],
|
177
|
+
pitch: 12 },
|
178
|
+
{ functions: %i[ix _9],
|
179
|
+
pitch: 12 + 2 },
|
180
|
+
{ functions: %i[x _10],
|
181
|
+
pitch: 12 + 3 },
|
182
|
+
{ functions: %i[xi _11],
|
183
|
+
pitch: 12 + 5 },
|
184
|
+
{ functions: %i[xii _12],
|
185
|
+
pitch: 12 + 7 },
|
186
|
+
{ functions: %i[xiii _13],
|
187
|
+
pitch: 12 + 8 }].freeze
|
188
|
+
|
189
|
+
def pitches
|
190
|
+
@@pitches
|
191
|
+
end
|
192
|
+
|
193
|
+
def grades
|
194
|
+
7
|
195
|
+
end
|
196
|
+
|
197
|
+
def id
|
198
|
+
:minor_harmonic
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
EquallyTempered12ToneScaleSystem.register MinorHarmonicScaleKind
|
203
|
+
end
|
204
|
+
end
|