musa-dsl 0.14.29 → 0.21.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/Gemfile +0 -1
- data/README.md +5 -1
- data/lib/musa-dsl.rb +54 -11
- data/lib/musa-dsl/core-ext.rb +7 -13
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
- data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
- data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
- data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
- data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
- data/lib/musa-dsl/core-ext/with.rb +26 -0
- data/lib/musa-dsl/datasets.rb +8 -3
- data/lib/musa-dsl/datasets/dataset.rb +3 -0
- data/lib/musa-dsl/datasets/delta-d.rb +12 -0
- data/lib/musa-dsl/datasets/e.rb +61 -0
- data/lib/musa-dsl/datasets/gdv.rb +51 -411
- data/lib/musa-dsl/datasets/gdvd.rb +179 -0
- data/lib/musa-dsl/datasets/helper.rb +41 -0
- data/lib/musa-dsl/datasets/p.rb +68 -0
- data/lib/musa-dsl/datasets/packed-v.rb +19 -0
- data/lib/musa-dsl/datasets/pdv.rb +22 -15
- data/lib/musa-dsl/datasets/ps.rb +113 -0
- data/lib/musa-dsl/datasets/score.rb +210 -0
- data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
- data/lib/musa-dsl/datasets/score/render.rb +31 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
- data/lib/musa-dsl/datasets/v.rb +23 -0
- data/lib/musa-dsl/generative.rb +5 -5
- data/lib/musa-dsl/generative/backboner.rb +274 -0
- data/lib/musa-dsl/generative/darwin.rb +102 -96
- data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
- data/lib/musa-dsl/generative/markov.rb +56 -53
- data/lib/musa-dsl/generative/variatio.rb +234 -222
- data/lib/musa-dsl/logger.rb +1 -0
- data/lib/musa-dsl/logger/logger.rb +31 -0
- data/lib/musa-dsl/matrix.rb +1 -0
- data/lib/musa-dsl/matrix/matrix.rb +210 -0
- data/lib/musa-dsl/midi.rb +2 -2
- data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
- data/lib/musa-dsl/midi/midi-voices.rb +187 -182
- data/lib/musa-dsl/music.rb +5 -5
- data/lib/musa-dsl/music/chord-definition.rb +54 -50
- data/lib/musa-dsl/music/chord-definitions.rb +13 -9
- data/lib/musa-dsl/music/chords.rb +236 -238
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
- data/lib/musa-dsl/music/scales.rb +331 -332
- data/lib/musa-dsl/musicxml.rb +1 -0
- data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
- data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
- data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
- data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
- data/lib/musa-dsl/neumalang.rb +1 -1
- data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
- data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
- data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
- data/lib/musa-dsl/neumalang/process.citrus +21 -0
- data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
- data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
- data/lib/musa-dsl/neumas.rb +5 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
- data/lib/musa-dsl/neumas/neumas.rb +37 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
- data/lib/musa-dsl/repl.rb +1 -1
- data/lib/musa-dsl/repl/repl.rb +128 -105
- data/lib/musa-dsl/sequencer.rb +1 -1
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +552 -308
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
- data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
- data/lib/musa-dsl/series.rb +1 -1
- data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
- data/lib/musa-dsl/series/base-series.rb +171 -168
- data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
- data/lib/musa-dsl/series/holder-serie.rb +1 -1
- data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
- data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
- data/lib/musa-dsl/series/proxy-serie.rb +6 -6
- data/lib/musa-dsl/series/queue-serie.rb +5 -5
- data/lib/musa-dsl/series/series.rb +2 -0
- data/lib/musa-dsl/transcription.rb +4 -0
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
- data/lib/musa-dsl/transcription/transcription.rb +55 -0
- data/lib/musa-dsl/transport.rb +6 -6
- data/lib/musa-dsl/transport/clock.rb +26 -26
- data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
- data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
- data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
- data/lib/musa-dsl/transport/timer-clock.rb +72 -71
- data/lib/musa-dsl/transport/timer.rb +28 -26
- data/lib/musa-dsl/transport/transport.rb +111 -93
- data/musa-dsl.gemspec +3 -3
- metadata +73 -24
- data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
- data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
- data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
- data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
- data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
- data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
- data/lib/musa-dsl/generative/rules.rb +0 -282
- data/lib/musa-dsl/neuma.rb +0 -1
- data/lib/musa-dsl/neuma/neuma.rb +0 -181
data/lib/musa-dsl/music.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative 'music/scales'
|
2
|
+
require_relative 'music/equally-tempered-12-tone-scale-system'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
require_relative 'music/chord-definition'
|
5
|
+
require_relative 'music/chords'
|
6
|
+
require_relative 'music/chord-definitions'
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Musa
|
2
|
-
|
3
|
-
class
|
4
|
-
def [](name)
|
2
|
+
module Chords
|
3
|
+
class ChordDefinition
|
4
|
+
def self.[](name)
|
5
5
|
@definitions[name]
|
6
6
|
end
|
7
7
|
|
8
|
-
def register(name, offsets:, **features)
|
8
|
+
def self.register(name, offsets:, **features)
|
9
9
|
definition = ChordDefinition.new(name, offsets: offsets, **features).freeze
|
10
10
|
|
11
11
|
@definitions ||= {}
|
@@ -17,11 +17,11 @@ module Musa
|
|
17
17
|
self
|
18
18
|
end
|
19
19
|
|
20
|
-
def find_by_pitches(pitches)
|
20
|
+
def self.find_by_pitches(pitches)
|
21
21
|
@definitions.values.find { |d| d.matches(pitches) }
|
22
22
|
end
|
23
23
|
|
24
|
-
def features_from(values = nil, hash = nil)
|
24
|
+
def self.features_from(values = nil, hash = nil)
|
25
25
|
values ||= []
|
26
26
|
hash ||= {}
|
27
27
|
|
@@ -31,69 +31,73 @@ module Musa
|
|
31
31
|
features
|
32
32
|
end
|
33
33
|
|
34
|
-
def find_by_features(*values, **hash)
|
34
|
+
def self.find_by_features(*values, **hash)
|
35
35
|
features = features_from(values, hash)
|
36
36
|
@definitions.values.select { |d| features <= d.features }
|
37
37
|
end
|
38
38
|
|
39
|
-
def feature_key_of(feature_value)
|
39
|
+
def self.feature_key_of(feature_value)
|
40
40
|
@features_by_value[feature_value]
|
41
41
|
end
|
42
|
-
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@pitch_offsets = offsets.clone.freeze
|
48
|
-
@pitch_names = offsets.collect { |k, v| [v, k] }.to_h
|
49
|
-
end
|
43
|
+
def self.feature_values
|
44
|
+
@features_by_value.keys
|
45
|
+
end
|
50
46
|
|
51
|
-
|
47
|
+
def initialize(name, offsets:, **features)
|
48
|
+
@name = name
|
49
|
+
@features = features.clone.freeze
|
50
|
+
@pitch_offsets = offsets.clone.freeze
|
51
|
+
@pitch_names = offsets.collect { |k, v| [v, k] }.to_h
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
@pitch_offsets.values.collect { |offset| root_pitch + offset }
|
55
|
-
end
|
54
|
+
attr_reader :name, :features, :pitch_offsets, :pitch_names
|
56
55
|
|
57
|
-
|
58
|
-
|
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
|
56
|
+
def pitches(root_pitch)
|
57
|
+
@pitch_offsets.values.collect { |offset| root_pitch + offset }
|
70
58
|
end
|
71
59
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
60
|
+
def named_pitches(elements_or_pitches, &block)
|
61
|
+
pitches = elements_or_pitches.collect do |element_or_pitch|
|
62
|
+
[if block_given?
|
63
|
+
yield element_or_pitch
|
64
|
+
else
|
65
|
+
element_or_pitch
|
66
|
+
end,
|
67
|
+
element_or_pitch]
|
68
|
+
end.to_h
|
69
|
+
|
70
|
+
root_pitch = pitches.keys.find do |candidate_root_pitch|
|
71
|
+
candidate_pitches = pitches.keys.collect { |p| p - candidate_root_pitch }
|
72
|
+
octave_reduce(candidate_pitches).uniq == octave_reduce(@pitch_offsets.values).uniq
|
73
|
+
end
|
74
|
+
|
75
|
+
# 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
|
76
|
+
|
77
|
+
pitches.collect do |pitch, element|
|
78
|
+
[@pitch_names[pitch - root_pitch], [element]]
|
79
|
+
end.to_h
|
80
|
+
end
|
78
81
|
|
79
|
-
|
80
|
-
|
82
|
+
def matches(pitches)
|
83
|
+
reduced_pitches = octave_reduce(pitches).uniq
|
81
84
|
|
82
|
-
|
83
|
-
|
85
|
+
!!reduced_pitches.find do |candidate_root_pitch|
|
86
|
+
reduced_pitches.sort == octave_reduce(pitches(candidate_root_pitch)).uniq.sort
|
87
|
+
end
|
84
88
|
end
|
85
|
-
end
|
86
89
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
+
def inspect
|
91
|
+
"<ChordDefinition: name = #{@name} features = #{@features} pitch_offsets = #{@pitch_offsets}>"
|
92
|
+
end
|
90
93
|
|
91
|
-
|
94
|
+
alias to_s inspect
|
92
95
|
|
93
|
-
|
96
|
+
protected
|
94
97
|
|
95
|
-
|
96
|
-
|
98
|
+
def octave_reduce(pitches)
|
99
|
+
pitches.collect { |p| p % 12 }
|
100
|
+
end
|
97
101
|
end
|
98
102
|
end
|
99
103
|
end
|
@@ -1,13 +1,17 @@
|
|
1
|
-
|
2
|
-
Musa::ChordDefinition.register :min, quality: :minor, size: :triad, offsets: { root: 0, third: 3, fifth: 7 }
|
1
|
+
require_relative 'chord-definition'
|
3
2
|
|
4
|
-
Musa::
|
5
|
-
Musa::ChordDefinition.register :maj7, quality: :major, size: :seventh, dominant: :dominant , offsets: { root: 0, third: 4, fifth: 7, seventh: 10 }
|
3
|
+
include Musa::Chords
|
6
4
|
|
7
|
-
|
5
|
+
ChordDefinition.register :maj, quality: :major, size: :triad, offsets: { root: 0, third: 4, fifth: 7 }
|
6
|
+
ChordDefinition.register :min, quality: :minor, size: :triad, offsets: { root: 0, third: 3, fifth: 7 }
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
ChordDefinition.register :maj7, quality: :major, size: :seventh, offsets: { root: 0, third: 4, fifth: 7, seventh: 11 }
|
9
|
+
ChordDefinition.register :maj7, quality: :major, size: :seventh, dominant: :dominant , offsets: { root: 0, third: 4, fifth: 7, seventh: 10 }
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
ChordDefinition.register :min7, quality: :minor, size: :seventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11 }
|
12
|
+
|
13
|
+
ChordDefinition.register :maj9, quality: :major, size: :ninth, offsets: { root: 0, third: 4, fifth: 7, seventh: 11, ninth: 14 }
|
14
|
+
ChordDefinition.register :min9, quality: :minor, size: :ninth, offsets: { root: 0, third: 3, fifth: 7, seventh: 11, ninth: 14 }
|
15
|
+
|
16
|
+
ChordDefinition.register :maj11, quality: :major, size: :eleventh, offsets: { root: 0, third: 4, fifth: 7, seventh: 11, ninth: 14, eleventh: 17 }
|
17
|
+
ChordDefinition.register :min11, quality: :minor, size: :eleventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11, ninth: 14, eleventh: 17 }
|
@@ -1,326 +1,324 @@
|
|
1
1
|
require_relative 'scales'
|
2
2
|
require_relative 'chord-definition'
|
3
3
|
|
4
|
+
using Musa::Extension::Arrayfy
|
5
|
+
|
4
6
|
module Musa
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
7
|
+
module Chords
|
8
|
+
class Chord
|
9
|
+
def initialize(name_or_notes_or_pitches = nil, # name | [notes] | [pitches]
|
10
|
+
# definitory
|
11
|
+
name: nil,
|
12
|
+
root: nil, root_grade: nil,
|
13
|
+
notes: nil, pitches: nil,
|
14
|
+
features: nil,
|
15
|
+
# target scale (or scale reference)
|
16
|
+
scale: nil,
|
17
|
+
allow_chromatic: nil,
|
18
|
+
# operations
|
19
|
+
inversion: nil, state: nil,
|
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]
|
37
|
+
else
|
38
|
+
raise ArgumentError, "Can't recognize #{n} in notes list #{notes}"
|
39
|
+
end
|
36
40
|
end
|
37
41
|
end
|
38
|
-
end
|
39
42
|
|
40
|
-
|
43
|
+
pitches = pitches.clone if pitches
|
41
44
|
|
42
|
-
|
43
|
-
|
45
|
+
# Preparing root_pitch
|
46
|
+
#
|
44
47
|
|
45
|
-
|
48
|
+
root_pitch = nil
|
46
49
|
|
47
|
-
|
50
|
+
raise ArgumentError, "Duplicate parameter: root: #{root} and root_grade: #{root_grade}" if root && root_grade
|
48
51
|
|
49
|
-
|
52
|
+
allow_chromatic ||= scale.nil?
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
54
|
+
if root && root.is_a?(Scales::NoteInScale)
|
55
|
+
root_pitch = root.pitch
|
56
|
+
scale ||= root.scale
|
57
|
+
end
|
66
58
|
|
67
|
-
|
59
|
+
raise ArgumentError, "Don't know how to recognize root_grade #{root_grade}: scale is not provided" if root_grade && !scale
|
60
|
+
|
61
|
+
root_pitch = scale[root_grade].pitch if root_grade && scale
|
62
|
+
|
63
|
+
# Parse name_or_notes_or_pitches to name, notes, pitches
|
64
|
+
#
|
65
|
+
#
|
66
|
+
case name_or_notes_or_pitches
|
67
|
+
when Symbol
|
68
|
+
raise ArgumentError, "Duplicate parameter #{name_or_notes_or_pitches} and name: #{name}" if name
|
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
|
68
85
|
|
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
86
|
notes ||= [] << scale[note_or_pitch]
|
77
87
|
else
|
78
|
-
|
88
|
+
raise ArgumentError, "Can't recognize #{note_or_pitch} in parameter list #{name_or_notes_or_pitches}"
|
79
89
|
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
90
|
end
|
87
|
-
end
|
88
91
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
when nil
|
93
|
+
# nothing happens
|
94
|
+
else
|
95
|
+
raise ArgumentError, "Can't recognize #{name_or_notes_or_pitches}"
|
96
|
+
end
|
94
97
|
|
95
|
-
|
96
|
-
|
98
|
+
# Eval definitory atributes
|
99
|
+
#
|
97
100
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
if _source.nil?
|
102
|
+
@notes = compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
103
|
+
else
|
104
|
+
@notes = compute_notes_from_source(_source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
105
|
+
end
|
103
106
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
107
|
+
# Eval adding / droping operations
|
108
|
+
#
|
109
|
+
|
110
|
+
if add
|
111
|
+
add.each do |to_add|
|
112
|
+
case to_add
|
113
|
+
when NoteInScale
|
114
|
+
@notes << to_add
|
115
|
+
when Numeric # pitch increment
|
116
|
+
pitch = root_pitch + to_add
|
117
|
+
@notes << scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)
|
118
|
+
when Symbol # interval name
|
119
|
+
pitch = root_pitch + scale.offset_of_interval(to_add)
|
120
|
+
@notes << scale.note_of_pitch(pitch)
|
121
|
+
else
|
122
|
+
raise ArgumentError, "Can't recognize element to add #{to_add}"
|
123
|
+
end
|
120
124
|
end
|
121
125
|
end
|
122
|
-
end
|
123
126
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
+
# TODO: Missing chord operations: drop, inversion, state, position
|
128
|
+
#
|
129
|
+
raise NotImplementedError, 'Missing chord operations: drop, inversion, state, position' if drop || inversion || state || position
|
127
130
|
|
128
|
-
|
129
|
-
|
131
|
+
# Eval voice increment operations
|
132
|
+
#
|
130
133
|
|
131
|
-
|
132
|
-
|
134
|
+
if move
|
135
|
+
raise ArgumentError, 'move: expected a Hash' unless move.is_a?(Hash)
|
133
136
|
|
134
|
-
|
135
|
-
|
137
|
+
move.each do |position, octave|
|
138
|
+
@notes[position][0] = @notes[position][0].octave(octave)
|
139
|
+
end
|
136
140
|
end
|
137
|
-
end
|
138
141
|
|
139
|
-
|
140
|
-
|
142
|
+
if duplicate
|
143
|
+
raise ArgumentError, 'duplicate: expected a Hash' unless duplicate.is_a?(Hash)
|
141
144
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
+
duplicate.each do |position, octave|
|
146
|
+
octave.arrayfy.each do |octave|
|
147
|
+
@notes[position] << @notes[position][0].octave(octave)
|
148
|
+
end
|
145
149
|
end
|
146
150
|
end
|
147
|
-
end
|
148
151
|
|
149
|
-
|
150
|
-
|
152
|
+
# Identify chord
|
153
|
+
#
|
151
154
|
|
152
|
-
|
155
|
+
@notes.freeze
|
153
156
|
|
154
|
-
|
155
|
-
|
157
|
+
@chord_definition = ChordDefinition.find_by_pitches(@notes.values.flatten(1).collect(&:pitch))
|
158
|
+
|
159
|
+
ChordDefinition.feature_values.each do |name|
|
160
|
+
define_singleton_method name do
|
161
|
+
featuring(name)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
156
165
|
|
157
|
-
|
166
|
+
attr_reader :notes, :chord_definition
|
158
167
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
168
|
+
def name(name = nil)
|
169
|
+
if name.nil?
|
170
|
+
@chord_definition.name if @chord_definition
|
171
|
+
else
|
172
|
+
Chord.new(_source: self, name: name)
|
173
|
+
end
|
164
174
|
end
|
165
|
-
end
|
166
175
|
|
167
|
-
|
168
|
-
|
169
|
-
|
176
|
+
def features
|
177
|
+
@chord_definition.features if @chord_definition
|
178
|
+
end
|
170
179
|
|
171
|
-
|
172
|
-
|
173
|
-
|
180
|
+
def featuring(*values, allow_chromatic: nil, **hash)
|
181
|
+
features = @chord_definition.features.dup if @chord_definition
|
182
|
+
features ||= {}
|
174
183
|
|
175
|
-
|
184
|
+
ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }
|
176
185
|
|
177
|
-
|
178
|
-
|
186
|
+
Chord.new(_source: self, allow_chromatic: allow_chromatic, features: features)
|
187
|
+
end
|
179
188
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
189
|
+
def root(root = nil)
|
190
|
+
if root.nil?
|
191
|
+
@notes[:root]
|
192
|
+
else
|
193
|
+
Chord.new(_source: self, root: root)
|
194
|
+
end
|
185
195
|
end
|
186
|
-
end
|
187
196
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
197
|
+
def [](position)
|
198
|
+
case position
|
199
|
+
when Numeric
|
200
|
+
@notes.values[position]
|
201
|
+
when Symbol
|
202
|
+
@notes[position]
|
203
|
+
end
|
194
204
|
end
|
195
|
-
end
|
196
205
|
|
197
|
-
|
198
|
-
|
199
|
-
|
206
|
+
def move(**octaves)
|
207
|
+
Chord.new(_source: self, move: octaves)
|
208
|
+
end
|
200
209
|
|
201
|
-
|
202
|
-
|
203
|
-
|
210
|
+
def duplicate(**octaves)
|
211
|
+
Chord.new(_source: self, duplicate: octaves)
|
212
|
+
end
|
204
213
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
214
|
+
def scale
|
215
|
+
scales = @notes.values.flatten(1).collect(&:scale).uniq
|
216
|
+
scales.first if scales.size == 1
|
217
|
+
end
|
209
218
|
|
210
|
-
|
211
|
-
|
212
|
-
|
219
|
+
# Converts the chord to a specific scale with the notes in the chord
|
220
|
+
def as_scale
|
221
|
+
end
|
213
222
|
|
214
223
|
|
215
|
-
|
216
|
-
|
217
|
-
|
224
|
+
def project_on_all(*scales, allow_chromatic: nil)
|
225
|
+
# TODO add match to other chords... what does it means?
|
226
|
+
allow_chromatic ||= false
|
218
227
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
228
|
+
note_sets = {}
|
229
|
+
scales.each do |scale|
|
230
|
+
if allow_chromatic
|
231
|
+
note_sets[scale] = @notes.values.flatten(1).collect { |n| n.on(scale) || n.on(scale.chromatic) }
|
232
|
+
else
|
233
|
+
note_sets[scale] = @notes.values.flatten(1).collect { |n| n.on(scale) }
|
234
|
+
end
|
225
235
|
end
|
226
|
-
end
|
227
236
|
|
228
|
-
|
229
|
-
|
230
|
-
|
237
|
+
note_sets_in_scale = note_sets.values.reject { |notes| notes.include?(nil) }
|
238
|
+
note_sets_in_scale.collect { |notes| Chord.new(notes: notes) }
|
239
|
+
end
|
231
240
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
241
|
+
def project_on(*scales, allow_chromatic: nil)
|
242
|
+
allow_chromatic ||= false
|
243
|
+
project_on_all(*scales, allow_chromatic: allow_chromatic).first
|
244
|
+
end
|
236
245
|
|
237
|
-
|
238
|
-
|
239
|
-
|
246
|
+
def ==(other)
|
247
|
+
self.class == other.class && @notes == other.notes
|
248
|
+
end
|
240
249
|
|
241
|
-
|
242
|
-
|
243
|
-
|
250
|
+
def inspect
|
251
|
+
"<Chord: notes = #{@notes}>"
|
252
|
+
end
|
244
253
|
|
245
|
-
|
254
|
+
alias to_s inspect
|
246
255
|
|
247
|
-
|
256
|
+
private
|
248
257
|
|
249
|
-
|
250
|
-
|
258
|
+
def compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
259
|
+
if name && root_pitch && scale && !(notes || pitches || features)
|
251
260
|
|
252
|
-
|
261
|
+
chord_definition = ChordDefinition[name]
|
253
262
|
|
254
|
-
|
263
|
+
raise ArgumentError, "Unrecognized #{name} chord" unless chord_definition
|
255
264
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
265
|
+
chord_definition.pitch_offsets.transform_values do |offset|
|
266
|
+
pitch = root_pitch + offset
|
267
|
+
[scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
|
268
|
+
end
|
260
269
|
|
261
|
-
|
270
|
+
elsif root_pitch && features && scale && !(name || notes || pitches)
|
262
271
|
|
263
|
-
|
272
|
+
chord_definitions = ChordDefinition.find_by_features(**features)
|
264
273
|
|
265
|
-
|
266
|
-
|
267
|
-
|
274
|
+
unless allow_chromatic
|
275
|
+
chord_definitions.reject! do |chord_definition|
|
276
|
+
chord_definition.pitches(root_pitch).find { |chord_pitch| scale.note_of_pitch(chord_pitch).nil? }
|
277
|
+
end
|
268
278
|
end
|
269
|
-
end
|
270
279
|
|
271
|
-
|
280
|
+
selected = chord_definitions.first
|
272
281
|
|
273
|
-
|
274
|
-
|
282
|
+
unless selected
|
283
|
+
raise ArgumentError, "Don't know how to create a chord with root pitch #{root_pitch}"\
|
275
284
|
" and features #{features} based on scale #{scale.kind.class} with root on #{scale.root}: "\
|
276
285
|
" no suitable definition found (allow_chromatic is #{allow_chromatic})"
|
277
|
-
|
286
|
+
end
|
278
287
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
288
|
+
selected.pitch_offsets.transform_values do |offset|
|
289
|
+
pitch = root_pitch + offset
|
290
|
+
[scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
|
291
|
+
end
|
283
292
|
|
284
|
-
|
293
|
+
elsif (notes || pitches && scale) && !(name || root_pitch || features)
|
285
294
|
|
286
|
-
|
295
|
+
notes ||= []
|
287
296
|
|
288
|
-
|
297
|
+
notes += pitches.collect { |p| scale.note_of_pitch(p) } if pitches
|
289
298
|
|
290
|
-
|
299
|
+
chord_definition = ChordDefinition.find_by_pitches(notes.collect(&:pitch))
|
291
300
|
|
292
|
-
|
301
|
+
raise "Can't find a chord definition for pitches #{pitches} on scale #{scale.kind.id} based on #{scale.root}" unless chord_definition
|
293
302
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
303
|
+
chord_definition.named_pitches(notes, &:pitch)
|
304
|
+
else
|
305
|
+
pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
|
306
|
+
raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
|
307
|
+
end
|
298
308
|
end
|
299
|
-
end
|
300
309
|
|
301
|
-
|
302
|
-
|
303
|
-
|
310
|
+
def compute_notes_from_source(source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
|
311
|
+
if !(name || root_pitch || scale || notes || pitches || features)
|
312
|
+
source.notes
|
304
313
|
|
305
|
-
|
306
|
-
|
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
|
314
|
+
elsif features && !(name || root_pitch || scale || notes || pitches)
|
315
|
+
compute_notes(nil, source.root.first.pitch, source.root.first.scale, nil, nil, features, allow_chromatic)
|
313
316
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
super
|
317
|
+
else
|
318
|
+
pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
|
319
|
+
raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
|
320
|
+
end
|
319
321
|
end
|
320
322
|
end
|
321
|
-
|
322
|
-
def respond_to_missing?(method_name, include_private)
|
323
|
-
ChordDefinition.feature_key_of(method_name) || super
|
324
|
-
end
|
325
323
|
end
|
326
324
|
end
|