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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/Gemfile +0 -1
  4. data/README.md +5 -1
  5. data/lib/musa-dsl.rb +54 -11
  6. data/lib/musa-dsl/core-ext.rb +7 -13
  7. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
  8. data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
  9. data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
  10. data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
  11. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
  12. data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
  13. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
  14. data/lib/musa-dsl/core-ext/with.rb +26 -0
  15. data/lib/musa-dsl/datasets.rb +8 -3
  16. data/lib/musa-dsl/datasets/dataset.rb +3 -0
  17. data/lib/musa-dsl/datasets/delta-d.rb +12 -0
  18. data/lib/musa-dsl/datasets/e.rb +61 -0
  19. data/lib/musa-dsl/datasets/gdv.rb +51 -411
  20. data/lib/musa-dsl/datasets/gdvd.rb +179 -0
  21. data/lib/musa-dsl/datasets/helper.rb +41 -0
  22. data/lib/musa-dsl/datasets/p.rb +68 -0
  23. data/lib/musa-dsl/datasets/packed-v.rb +19 -0
  24. data/lib/musa-dsl/datasets/pdv.rb +22 -15
  25. data/lib/musa-dsl/datasets/ps.rb +113 -0
  26. data/lib/musa-dsl/datasets/score.rb +210 -0
  27. data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
  28. data/lib/musa-dsl/datasets/score/render.rb +31 -0
  29. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
  30. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
  31. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
  32. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
  33. data/lib/musa-dsl/datasets/v.rb +23 -0
  34. data/lib/musa-dsl/generative.rb +5 -5
  35. data/lib/musa-dsl/generative/backboner.rb +274 -0
  36. data/lib/musa-dsl/generative/darwin.rb +102 -96
  37. data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
  38. data/lib/musa-dsl/generative/markov.rb +56 -53
  39. data/lib/musa-dsl/generative/variatio.rb +234 -222
  40. data/lib/musa-dsl/logger.rb +1 -0
  41. data/lib/musa-dsl/logger/logger.rb +31 -0
  42. data/lib/musa-dsl/matrix.rb +1 -0
  43. data/lib/musa-dsl/matrix/matrix.rb +210 -0
  44. data/lib/musa-dsl/midi.rb +2 -2
  45. data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
  46. data/lib/musa-dsl/midi/midi-voices.rb +187 -182
  47. data/lib/musa-dsl/music.rb +5 -5
  48. data/lib/musa-dsl/music/chord-definition.rb +54 -50
  49. data/lib/musa-dsl/music/chord-definitions.rb +13 -9
  50. data/lib/musa-dsl/music/chords.rb +236 -238
  51. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
  52. data/lib/musa-dsl/music/scales.rb +331 -332
  53. data/lib/musa-dsl/musicxml.rb +1 -0
  54. data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
  55. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
  56. data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
  57. data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
  58. data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
  59. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
  60. data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
  61. data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
  62. data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
  63. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
  64. data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
  65. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
  66. data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
  67. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
  68. data/lib/musa-dsl/neumalang.rb +1 -1
  69. data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
  70. data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
  71. data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
  72. data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
  73. data/lib/musa-dsl/neumalang/process.citrus +21 -0
  74. data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
  75. data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
  76. data/lib/musa-dsl/neumas.rb +5 -0
  77. data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
  78. data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
  79. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
  80. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
  81. data/lib/musa-dsl/neumas/neumas.rb +37 -0
  82. data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
  83. data/lib/musa-dsl/repl.rb +1 -1
  84. data/lib/musa-dsl/repl/repl.rb +128 -105
  85. data/lib/musa-dsl/sequencer.rb +1 -1
  86. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +552 -308
  89. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
  90. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
  91. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
  92. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
  93. data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
  94. data/lib/musa-dsl/series.rb +1 -1
  95. data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
  96. data/lib/musa-dsl/series/base-series.rb +171 -168
  97. data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
  98. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  99. data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
  100. data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
  101. data/lib/musa-dsl/series/proxy-serie.rb +6 -6
  102. data/lib/musa-dsl/series/queue-serie.rb +5 -5
  103. data/lib/musa-dsl/series/series.rb +2 -0
  104. data/lib/musa-dsl/transcription.rb +4 -0
  105. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
  106. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
  107. data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
  108. data/lib/musa-dsl/transcription/transcription.rb +55 -0
  109. data/lib/musa-dsl/transport.rb +6 -6
  110. data/lib/musa-dsl/transport/clock.rb +26 -26
  111. data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
  112. data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
  113. data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
  114. data/lib/musa-dsl/transport/timer-clock.rb +72 -71
  115. data/lib/musa-dsl/transport/timer.rb +28 -26
  116. data/lib/musa-dsl/transport/transport.rb +111 -93
  117. data/musa-dsl.gemspec +3 -3
  118. metadata +73 -24
  119. data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
  120. data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
  121. data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
  122. data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
  123. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
  124. data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
  125. data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
  126. data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
  127. data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
  128. data/lib/musa-dsl/generative/rules.rb +0 -282
  129. data/lib/musa-dsl/neuma.rb +0 -1
  130. data/lib/musa-dsl/neuma/neuma.rb +0 -181
@@ -1,6 +1,6 @@
1
- require 'musa-dsl/music/scales'
2
- require 'musa-dsl/music/equally-tempered-12-tone-scale-system'
1
+ require_relative 'music/scales'
2
+ require_relative 'music/equally-tempered-12-tone-scale-system'
3
3
 
4
- require 'musa-dsl/music/chord-definition'
5
- require 'musa-dsl/music/chords'
6
- require 'musa-dsl/music/chord-definitions'
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
- class ChordDefinition
3
- class << self
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
- 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
43
+ def self.feature_values
44
+ @features_by_value.keys
45
+ end
50
46
 
51
- attr_reader :name, :features, :pitch_offsets, :pitch_names
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
- def pitches(root_pitch)
54
- @pitch_offsets.values.collect { |offset| root_pitch + offset }
55
- end
54
+ attr_reader :name, :features, :pitch_offsets, :pitch_names
56
55
 
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
56
+ def pitches(root_pitch)
57
+ @pitch_offsets.values.collect { |offset| root_pitch + offset }
70
58
  end
71
59
 
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
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
- def matches(pitches)
80
- reduced_pitches = octave_reduce(pitches).uniq
82
+ def matches(pitches)
83
+ reduced_pitches = octave_reduce(pitches).uniq
81
84
 
82
- !!reduced_pitches.find do |candidate_root_pitch|
83
- reduced_pitches.sort == octave_reduce(pitches(candidate_root_pitch)).uniq.sort
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
- def inspect
88
- "<ChordDefinition: name = #{@name} features = #{@features} pitch_offsets = #{@pitch_offsets}>"
89
- end
90
+ def inspect
91
+ "<ChordDefinition: name = #{@name} features = #{@features} pitch_offsets = #{@pitch_offsets}>"
92
+ end
90
93
 
91
- alias to_s inspect
94
+ alias to_s inspect
92
95
 
93
- protected
96
+ protected
94
97
 
95
- def octave_reduce(pitches)
96
- pitches.collect { |p| p % 12 }
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
- 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 }
1
+ require_relative 'chord-definition'
3
2
 
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 }
3
+ include Musa::Chords
6
4
 
7
- Musa::ChordDefinition.register :min7, quality: :minor, size: :seventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11 }
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
- 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 }
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
- 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 }
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
- 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}"
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
- pitches = pitches.clone if pitches
43
+ pitches = pitches.clone if pitches
41
44
 
42
- # Preparing root_pitch
43
- #
45
+ # Preparing root_pitch
46
+ #
44
47
 
45
- root_pitch = nil
48
+ root_pitch = nil
46
49
 
47
- raise ArgumentError, "Duplicate parameter: root: #{root} and root_grade: #{root_grade}" if root && root_grade
50
+ raise ArgumentError, "Duplicate parameter: root: #{root} and root_grade: #{root_grade}" if root && root_grade
48
51
 
49
- allow_chromatic ||= scale.nil?
52
+ allow_chromatic ||= scale.nil?
50
53
 
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
54
+ if root && root.is_a?(Scales::NoteInScale)
55
+ root_pitch = root.pitch
56
+ scale ||= root.scale
57
+ end
66
58
 
67
- name = name_or_notes_or_pitches
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
- pitches ||= [] << note_or_pitch
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
- when nil
90
- # nothing happens
91
- else
92
- raise ArgumentError, "Can't recognize #{name_or_notes_or_pitches}"
93
- end
92
+ when nil
93
+ # nothing happens
94
+ else
95
+ raise ArgumentError, "Can't recognize #{name_or_notes_or_pitches}"
96
+ end
94
97
 
95
- # Eval definitory atributes
96
- #
98
+ # Eval definitory atributes
99
+ #
97
100
 
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
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
- # 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}"
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
- # 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
+ # 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
- # Eval voice increment operations
129
- #
131
+ # Eval voice increment operations
132
+ #
130
133
 
131
- if move
132
- raise ArgumentError, 'move: expected a Hash' unless move.is_a?(Hash)
134
+ if move
135
+ raise ArgumentError, 'move: expected a Hash' unless move.is_a?(Hash)
133
136
 
134
- move.each do |position, octave|
135
- @notes[position][0] = @notes[position][0].octave(octave)
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
- if duplicate
140
- raise ArgumentError, 'duplicate: expected a Hash' unless duplicate.is_a?(Hash)
142
+ if duplicate
143
+ raise ArgumentError, 'duplicate: expected a Hash' unless duplicate.is_a?(Hash)
141
144
 
142
- duplicate.each do |position, octave|
143
- octave.arrayfy.each do |octave|
144
- @notes[position] << @notes[position][0].octave(octave)
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
- # Identify chord
150
- #
152
+ # Identify chord
153
+ #
151
154
 
152
- @notes.freeze
155
+ @notes.freeze
153
156
 
154
- @chord_definition = ChordDefinition.find_by_pitches(@notes.values.flatten(1).collect(&:pitch))
155
- end
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
- attr_reader :notes, :chord_definition
166
+ attr_reader :notes, :chord_definition
158
167
 
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)
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
- def features
168
- @chord_definition.features if @chord_definition
169
- end
176
+ def features
177
+ @chord_definition.features if @chord_definition
178
+ end
170
179
 
171
- def featuring(*values, allow_chromatic: nil, **hash)
172
- features = @chord_definition.features.dup if @chord_definition
173
- features ||= {}
180
+ def featuring(*values, allow_chromatic: nil, **hash)
181
+ features = @chord_definition.features.dup if @chord_definition
182
+ features ||= {}
174
183
 
175
- ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }
184
+ ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }
176
185
 
177
- Chord.new(_source: self, allow_chromatic: allow_chromatic, features: features)
178
- end
186
+ Chord.new(_source: self, allow_chromatic: allow_chromatic, features: features)
187
+ end
179
188
 
180
- def root(root = nil)
181
- if root.nil?
182
- @notes[:root]
183
- else
184
- Chord.new(_source: self, root: root)
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
- def [](position)
189
- case position
190
- when Numeric
191
- @notes.values[position]
192
- when Symbol
193
- @notes[position]
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
- def move(**octaves)
198
- Chord.new(_source: self, move: octaves)
199
- end
206
+ def move(**octaves)
207
+ Chord.new(_source: self, move: octaves)
208
+ end
200
209
 
201
- def duplicate(**octaves)
202
- Chord.new(_source: self, duplicate: octaves)
203
- end
210
+ def duplicate(**octaves)
211
+ Chord.new(_source: self, duplicate: octaves)
212
+ end
204
213
 
205
- def scale
206
- scales = @notes.values.flatten(1).collect(&:scale).uniq
207
- scales.first if scales.size == 1
208
- end
214
+ def scale
215
+ scales = @notes.values.flatten(1).collect(&:scale).uniq
216
+ scales.first if scales.size == 1
217
+ end
209
218
 
210
- # Converts the chord to a specific scale with the notes in the chord
211
- def as_scale
212
- end
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
- def project_on_all(*scales, allow_chromatic: nil)
216
- # TODO add match to other chords... what does it means?
217
- allow_chromatic ||= false
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
- 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) }
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
- 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
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
- def project_on(*scales, allow_chromatic: nil)
233
- allow_chromatic ||= false
234
- project_on_all(*scales, allow_chromatic: allow_chromatic).first
235
- end
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
- def ==(other)
238
- self.class == other.class && @notes == other.notes
239
- end
246
+ def ==(other)
247
+ self.class == other.class && @notes == other.notes
248
+ end
240
249
 
241
- def inspect
242
- "<Chord: notes = #{@notes}>"
243
- end
250
+ def inspect
251
+ "<Chord: notes = #{@notes}>"
252
+ end
244
253
 
245
- alias to_s inspect
254
+ alias to_s inspect
246
255
 
247
- private
256
+ private
248
257
 
249
- def compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
250
- if name && root_pitch && scale && !(notes || pitches || features)
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
- chord_definition = ChordDefinition[name]
261
+ chord_definition = ChordDefinition[name]
253
262
 
254
- raise ArgumentError, "Unrecognized #{name} chord" unless chord_definition
263
+ raise ArgumentError, "Unrecognized #{name} chord" unless chord_definition
255
264
 
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
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
- elsif root_pitch && features && scale && !(name || notes || pitches)
270
+ elsif root_pitch && features && scale && !(name || notes || pitches)
262
271
 
263
- chord_definitions = ChordDefinition.find_by_features(**features)
272
+ chord_definitions = ChordDefinition.find_by_features(**features)
264
273
 
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? }
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
- selected = chord_definitions.first
280
+ selected = chord_definitions.first
272
281
 
273
- unless selected
274
- raise ArgumentError, "Don't know how to create a chord with root pitch #{root_pitch}"\
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
- end
286
+ end
278
287
 
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
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
- elsif (notes || pitches && scale) && !(name || root_pitch || features)
293
+ elsif (notes || pitches && scale) && !(name || root_pitch || features)
285
294
 
286
- notes ||= []
295
+ notes ||= []
287
296
 
288
- notes += pitches.collect { |p| scale.note_of_pitch(p) } if pitches
297
+ notes += pitches.collect { |p| scale.note_of_pitch(p) } if pitches
289
298
 
290
- chord_definition = ChordDefinition.find_by_pitches(notes.collect(&:pitch))
299
+ chord_definition = ChordDefinition.find_by_pitches(notes.collect(&:pitch))
291
300
 
292
- raise "Can't find a chord definition for pitches #{pitches} on scale #{scale.kind.id} based on #{scale.root}" unless chord_definition
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
- 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}"
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
- 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
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
- 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
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
- 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
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