musa-dsl 0.14.16
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/.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
|