musa-dsl 0.14.31 → 0.21.4

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.
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 +122 -110
  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 +554 -308
  89. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
  90. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +75 -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 +89 -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
@@ -0,0 +1,51 @@
1
+ module Musa::Datasets::Score::ToMXML
2
+ private
3
+
4
+ DynamicsContext = Struct.new(:last_dynamics)
5
+ private_constant :DynamicsContext
6
+
7
+ def process_ps(measure, element, context, logger, do_log)
8
+ context ||= DynamicsContext.new
9
+
10
+ logger.debug "\nprocess_ps #{element}" if do_log
11
+
12
+ case element[:dataset][:type]
13
+ when :crescendo, :diminuendo
14
+ if element[:change] == :start
15
+ dynamics = dynamics_to_string(element[:dataset][:from])
16
+
17
+ if dynamics != context.last_dynamics
18
+ measure.add_dynamics dynamics, placement: 'below' if dynamics && element[:dataset][:from] > 0
19
+ context.last_dynamics = dynamics
20
+ end
21
+
22
+ measure.add_wedge element[:dataset][:type],
23
+ niente: element[:dataset][:type] == :crescendo && element[:dataset][:from] == 0,
24
+ placement: 'below'
25
+ else
26
+ measure.add_wedge 'stop',
27
+ niente: element[:dataset][:type] == :diminuendo && element[:dataset][:to] == 0,
28
+ placement: 'below'
29
+
30
+ dynamics = dynamics_to_string(element[:dataset][:to])
31
+
32
+ measure.add_dynamics dynamics, placement: 'below' if dynamics && element[:dataset][:to] > 0
33
+ context.last_dynamics = dynamics
34
+ end
35
+
36
+ when :dynamics
37
+ dynamics = dynamics_to_string(element[:dataset][:from])
38
+
39
+ if dynamics != context.last_dynamics
40
+ measure.add_dynamics dynamics, placement: 'below'
41
+ context.last_dynamics = dynamics
42
+ end
43
+
44
+ else
45
+ # ignored
46
+ end
47
+
48
+ context
49
+ end
50
+
51
+ end
@@ -0,0 +1,153 @@
1
+ require 'prime'
2
+
3
+ module Musa::Datasets::Score::ToMXML
4
+ private
5
+
6
+ class ElementDurationDecomposition
7
+ def initialize(element, bar, bar_size = 1r) # TODO remove (unused because of bad strategy to time groups)
8
+ @continue_from_previous_bar = element[:start] < bar
9
+ @continue_to_next_bar = element[:finish] >= bar + bar_size
10
+
11
+ @start = continue_from_previous_bar ? 0r : element[:start] - bar
12
+
13
+ @duration = continue_to_next_bar ?
14
+ (1r - @start) :
15
+ (element[:start] + element[:dataset][:duration] - (bar + start))
16
+
17
+ @duration_decomposition = integrate_as_dotteable_durations(decompose_as_sum_of_simple_durations(@duration))
18
+ end
19
+
20
+ attr_reader :continue_from_previous_bar, :continue_to_next_bar, :start, :duration, :duration_decomposition
21
+
22
+ def to_s
23
+ "ElementDurationDecomposition(#{@duration}) = [#{@duration_decomposition}]"
24
+ end
25
+
26
+ alias inspect to_s
27
+ end
28
+
29
+ private_constant :ElementDurationDecomposition
30
+
31
+ def time_and_tuplet_optimize(elements, bar, bar_size = 1r) # TODO remove (unused because of bad strategy to time groups)
32
+ decompositions = elements.collect { |pdv| ElementDurationDecomposition.new(pdv, bar, bar_size) }
33
+
34
+ denominators = decompositions.collect { |g| g.duration_decomposition.collect { |d| d.to_r.denominator } }.flatten.uniq
35
+
36
+ lcm_denominators = denominators.reduce(:lcm)
37
+
38
+ primes = Prime.prime_division(lcm_denominators)
39
+
40
+ factors = primes.collect { |base, exp| [base] * exp }.flatten
41
+
42
+ refactors = all_combinations(factors).collect { |a| a.reduce(&:*) }
43
+
44
+ # Y no se puede seguir con la descomposición
45
+
46
+ nil
47
+ end
48
+
49
+ def decompose_as_sum_of_simple_durations(duration)
50
+ return [] if duration.zero?
51
+
52
+ # TODO mejorar esta descomposición para que tenga menos factores redundantes
53
+ pd = Prime.prime_division(duration.to_r.denominator).collect { |base, exp| (1..exp).collect { |i| base ** i } }.flatten
54
+
55
+ divisors = ([[1]] + all_combinations(pd)).collect { |combination| combination.inject(:*) }
56
+
57
+ summands = []
58
+
59
+ while divisor = divisors.shift
60
+ c = Rational(1, divisor)
61
+ f = (duration / c).floor
62
+ n = f * c
63
+ summands << n unless n.zero?
64
+ duration -= n
65
+ end
66
+
67
+ raise ArgumentError, "#{duration} cannot be further decomposed" unless duration.zero?
68
+
69
+ summands
70
+ end
71
+
72
+ def all_combinations(numbers)
73
+ all_combinations = []
74
+ i = 1
75
+ until (combinations = numbers.combination(i).to_a).empty?
76
+ all_combinations += combinations
77
+ i += 1
78
+ end
79
+
80
+ all_combinations.uniq
81
+ end
82
+
83
+ def integrate_as_dotteable_durations(simple_durations)
84
+ integrated_durations = []
85
+ last = nil
86
+ simple_durations.each do |duration|
87
+ if last && duration == last / 2
88
+ integrated_durations[integrated_durations.size-1] += duration
89
+ else
90
+ integrated_durations << duration
91
+ end
92
+ last = duration
93
+ end
94
+ integrated_durations
95
+ end
96
+
97
+ def type_and_dots_and_tuplet_ratio(noteable_duration)
98
+ r = decompose_as_sum_of_simple_durations(noteable_duration)
99
+ n = r.shift
100
+
101
+ tuplet_ratio = Rational(n.denominator, nearest_lower_power_of_2(n.denominator))
102
+
103
+ type = type_of(nearest_upper_power_of_2(n))
104
+ dots = 0
105
+
106
+ while nn = r.shift
107
+ if nn == n / 2
108
+ dots += 1
109
+ n = nn
110
+ else
111
+ break
112
+ end
113
+ end
114
+
115
+ raise ArgumentError, "#{noteable_duration} cannot be decomposed as a duration with dots" unless r.empty?
116
+
117
+ return type, dots, tuplet_ratio
118
+ end
119
+
120
+ def nearest_upper_power_of_2(number)
121
+ return 0 if number.zero?
122
+
123
+ exp = Math.log2(number)
124
+ exp_floor = exp.floor
125
+ plus = exp > exp_floor ? 1 : 0
126
+
127
+ 2 ** (exp_floor + plus)
128
+ end
129
+
130
+ def nearest_lower_power_of_2(number)
131
+ return 0 if number.zero?
132
+
133
+ exp_floor = Math.log2(number).floor
134
+
135
+ 2 ** exp_floor
136
+ end
137
+
138
+ def type_of(base_type_duration)
139
+ duration_log2i = Math.log2(base_type_duration)
140
+
141
+ raise ArgumentError, "#{base_type_duration} is not a inverse power of 2 (i.e. 2, 1, 1/2, 1/4, 1/8, 1/64, etc)" \
142
+ unless base_type_duration == 2 ** duration_log2i
143
+
144
+ raise ArgumentError, "#{base_type_duration} is not between 1024th and maxima accepted durations" \
145
+ unless duration_log2i >= -10 && duration_log2i <= 3
146
+
147
+ ['1024th', '512th', '256th', '128th',
148
+ '64th', '32nd', '16th', 'eighth',
149
+ 'quarter', 'half', 'whole', 'breve',
150
+ 'long', 'maxima'][duration_log2i + 10]
151
+ end
152
+ end
153
+
@@ -0,0 +1,158 @@
1
+ require_relative '../../../logger'
2
+ require_relative '../../../musicxml'
3
+
4
+ require_relative 'process-time'
5
+ require_relative 'process-pdv'
6
+ require_relative 'process-ps'
7
+
8
+ module Musa::Datasets; class Score
9
+ module ToMXML
10
+ include Musa::MusicXML::Builder
11
+ include Musa::Datasets
12
+
13
+ def to_mxml(beats_per_bar, ticks_per_beat,
14
+ bpm: nil,
15
+ title: nil,
16
+ creators: nil,
17
+ encoding_date: nil,
18
+ parts:,
19
+ logger: nil,
20
+ do_log: nil)
21
+
22
+ bpm ||= 90
23
+ title ||= 'Untitled'
24
+ creators ||= { composer: 'Unknown' }
25
+ logger ||= Musa::Logger::Logger.new
26
+ do_log ||= nil
27
+
28
+ mxml = ScorePartwise.new do |_|
29
+ _.work_title title
30
+ _.creators **creators
31
+ _.encoding_date encoding_date if encoding_date
32
+
33
+ parts.each_pair do |id, part_info|
34
+ _.part id,
35
+ name: part_info&.[](:name),
36
+ abbreviation: part_info&.[](:abbreviation) do |_|
37
+
38
+ _.measure do |_|
39
+ _.attributes do |_|
40
+ _.divisions ticks_per_beat
41
+
42
+ i = 0
43
+ (part_info&.[](:clefs) || { g: 2 }).each_pair do |clef, line|
44
+ i += 1
45
+ _.clef i, sign: clef.upcase, line: line
46
+ _.time i, beats: beats_per_bar, beat_type: 4
47
+ end
48
+ end
49
+
50
+ _.metronome placement: 'above', beat_unit: 'quarter', per_minute: bpm
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ if do_log
57
+ logger.debug"\nscore.to_mxl log:"
58
+ logger.debug "-----------------"
59
+ end
60
+
61
+ parts.each_key do |part_id|
62
+ fill_part mxml.parts[part_id], beats_per_bar * ticks_per_beat, (parts.size > 1 ? part_id : nil), logger, do_log
63
+ end
64
+
65
+ mxml
66
+ end
67
+
68
+ private
69
+
70
+ def fill_part(part, divisions_per_bar, instrument, logger, do_log)
71
+ measure = nil
72
+ dynamics_context = nil
73
+
74
+ (1..finish || 0).each do |bar|
75
+ if do_log
76
+ logger.debug ""
77
+ logger.debug msg = "filling part #{part.name} (#{instrument}): processing bar #{bar}"
78
+ logger.debug "-" * msg.size
79
+ end
80
+
81
+ measure = part.add_measure if measure
82
+ measure ||= part.measures.last
83
+
84
+ pointer = 0r
85
+
86
+ instrument_score = subset { |dataset| dataset[:instrument] == instrument }
87
+
88
+ bar_elements = \
89
+ (instrument_score.changes_between(bar, bar + 1).select { |p| p[:dataset].is_a?(PS) } +
90
+ (pdvs = instrument_score.between(bar, bar + 1).select { |p| p[:dataset].is_a?(PDV) }))
91
+ .sort_by { |e| [ e[:time_in_interval] || e[:start_in_interval],
92
+ e[:dataset].is_a?(PS) ? 0 : 1 ] }
93
+
94
+ if pdvs.empty?
95
+ logger.debug "\nadded full bar silence" if do_log
96
+
97
+ process_pdv(measure, bar, divisions_per_bar,
98
+ { start: bar,
99
+ finish: bar + 1,
100
+ dataset: { pitch: :silence, duration: 1 }.extend(PDV) },
101
+ pointer,
102
+ logger,
103
+ do_log)
104
+ else
105
+ first = bar_elements.first
106
+
107
+ logger.debug "\nfirst element #{first}" if do_log
108
+
109
+ # TODO habrá que arreglar el cálculo de pointer cuando haya avances y retrocesos para que
110
+ # TODO no añada silencios incorrectos al principio o al final
111
+
112
+ if (first[:time_in_interval] || first[:start_in_interval]) > bar
113
+
114
+ silence_duration = first[:start_in_interval] - bar
115
+
116
+ logger.debug "\nadded initial silence for duration #{silence_duration}" if do_log
117
+
118
+ pointer = process_pdv(measure, bar, divisions_per_bar,
119
+ { start: bar,
120
+ finish: first[:start_in_interval],
121
+ dataset: { pitch: :silence, duration: silence_duration }.extend(PDV) },
122
+ pointer,
123
+ logger,
124
+ do_log)
125
+ end
126
+
127
+ bar_elements.each do |element|
128
+ case element[:dataset]
129
+ when PDV
130
+ pointer = process_pdv(measure, bar, divisions_per_bar, element, pointer, logger, do_log)
131
+
132
+ when PS
133
+ dynamics_context = process_ps(measure, element, dynamics_context, logger, do_log)
134
+
135
+ else
136
+ # ignored
137
+ end
138
+ end
139
+
140
+ if pointer < 1r
141
+ silence_duration = 1r - pointer
142
+
143
+ logger.debug "\nadded ending silence for duration #{silence_duration}" if do_log
144
+
145
+ process_pdv(measure, bar, divisions_per_bar,
146
+ { start: bar + pointer,
147
+ finish: bar + 1 - Rational(1, divisions_per_bar),
148
+ dataset: { pitch: :silence, duration: silence_duration }.extend(PDV) },
149
+ pointer,
150
+ logger,
151
+ do_log)
152
+
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end; end
@@ -0,0 +1,23 @@
1
+ require_relative 'dataset'
2
+ require_relative 'packed-v'
3
+
4
+ module Musa::Datasets
5
+ module V
6
+ include AbsI
7
+
8
+ def to_packed_V(mapper)
9
+ case mapper
10
+ when Hash
11
+ pv = {}.extend(PackedV)
12
+ each_index { |i| pv[mapper.keys[i]] = self[i] unless self[i] == mapper.values[i] }
13
+ pv
14
+ when Array
15
+ pv = {}.extend(PackedV)
16
+ each_index { |i| pv[mapper[i]] = self[i] if mapper[i] && self[i] }
17
+ pv
18
+ else
19
+ raise ArgumentError, "Expected Hash or Array as mapper but got a #{mapper.class.name}"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
- require 'musa-dsl/generative/variatio'
2
- require 'musa-dsl/generative/darwin'
3
- require 'musa-dsl/generative/rules'
4
- require 'musa-dsl/generative/markov'
5
- require 'musa-dsl/generative/generative-grammar'
1
+ require_relative 'generative/variatio'
2
+ require_relative 'generative/darwin'
3
+ require_relative 'generative/backboner'
4
+ require_relative 'generative/markov'
5
+ require_relative 'generative/generative-grammar'
@@ -0,0 +1,274 @@
1
+ require_relative '../core-ext/smart-proc-binder'
2
+ require_relative '../core-ext/with'
3
+
4
+ using Musa::Extension::Arrayfy
5
+
6
+ # incluir With -> hecho
7
+ # eliminar method_missing
8
+ # crear rama tb debe recibir la serie de la history -> ya lo hace
9
+ # crear rama puede repetirse (hasta terminar según ended_when) -> no
10
+ #
11
+ # hacer que pueda funcionar en tiempo real? le vas suministrando seeds y le vas diciendo qué opción has elegido (p.ej. para hacer un armonizador en tiempo real)
12
+ # esto mismo sería aplicable en otros generadores? variatio/darwin? generative-grammar? markov?
13
+
14
+ module Musa
15
+ module Backboner
16
+ class Backboner
17
+ include Musa::Extension::With
18
+
19
+ def initialize(&block)
20
+ @context = RulesEvalContext.new(&block)
21
+ end
22
+
23
+ def generate_possibilities(object, confirmed_node = nil, node = nil, grow_rules = nil)
24
+ node ||= Node.new
25
+ grow_rules ||= @context._grow_rules
26
+
27
+ history = confirmed_node.history if confirmed_node
28
+ history ||= []
29
+
30
+ grow_rules = grow_rules.clone
31
+ grow_rule = grow_rules.shift
32
+
33
+ if grow_rule
34
+ grow_rule.generate_possibilities(object, history).each do |new_object|
35
+ new_node = Node.new new_object, node
36
+ new_node.mark_as_ended! if @context._ended? new_object
37
+
38
+ rejection = @context._cut_rules.find { |cut_rule| cut_rule.rejects?(new_object, history) }
39
+ # TODO: include rejection secondary reasons in rejection message
40
+
41
+ new_node.reject! rejection if rejection
42
+
43
+ node.children << new_node
44
+ end
45
+ end
46
+
47
+ unless grow_rules.empty?
48
+ node.children.each do |node|
49
+ generate_possibilities node.object, confirmed_node, node, grow_rules unless node.rejected || node.ended?
50
+ end
51
+ end
52
+
53
+ node
54
+ end
55
+
56
+ def apply(object_or_list, node = nil)
57
+ list = object_or_list.arrayfy.clone
58
+
59
+ node ||= Node.new
60
+
61
+ seed = list.shift
62
+
63
+ if seed
64
+ result = generate_possibilities seed, node
65
+
66
+ fished = result.fish
67
+
68
+ node.reject! 'All children are rejected' if fished.empty?
69
+
70
+ fished.each do |object|
71
+ subnode = node.add(object).mark_as_ended!
72
+ apply list, subnode
73
+ end
74
+ end
75
+
76
+ node
77
+ end
78
+
79
+ class RulesEvalContext
80
+ include Musa::Extension::With
81
+
82
+ attr_reader :_grow_rules, :_ended_when, :_cut_rules
83
+
84
+ def initialize(&block)
85
+ with &block
86
+ end
87
+
88
+ def grow(name, &block)
89
+ @_grow_rules ||= []
90
+ @_grow_rules << GrowRule.new(name, &block)
91
+ self
92
+ end
93
+
94
+ def ended_when(&block)
95
+ @_ended_when = block
96
+ self
97
+ end
98
+
99
+ def cut(reason, &block)
100
+ @_cut_rules ||= []
101
+ @_cut_rules << CutRule.new(reason, &block)
102
+ self
103
+ end
104
+
105
+ def _ended?(object)
106
+ instance_exec object, &@_ended_when
107
+ end
108
+
109
+ class GrowRule
110
+ attr_reader :name
111
+
112
+ def initialize(name, &block)
113
+ @name = name
114
+ @block = block
115
+ end
116
+
117
+ def generate_possibilities(object, history)
118
+ # TODO: optimize context using only one instance for all genereate_possibilities calls
119
+ context = GrowRuleEvalContext.new
120
+ context.with object, history, &@block
121
+
122
+ context._branches
123
+ end
124
+
125
+ class GrowRuleEvalContext
126
+ include Musa::Extension::With
127
+
128
+ attr_reader :_branches
129
+
130
+ def initialize
131
+ @_branches = []
132
+ end
133
+
134
+ def branch(object)
135
+ @_branches << object
136
+ self
137
+ end
138
+ end
139
+
140
+ private_constant :GrowRuleEvalContext
141
+ end
142
+
143
+ private_constant :GrowRule
144
+
145
+ class CutRule
146
+ attr_reader :reason
147
+
148
+ def initialize(reason, &block)
149
+ @reason = reason
150
+ @block = block
151
+ end
152
+
153
+ def rejects?(object, history)
154
+ # TODO: optimize context using only one instance for all rejects? checks
155
+ context = CutRuleEvalContext.new
156
+ context.with object, history, &@block
157
+
158
+ reasons = context._secondary_reasons.collect { |_| ("#{@reason} (#{_})" if _) || @reason }
159
+
160
+ reasons.empty? ? nil : reasons
161
+ end
162
+
163
+ class CutRuleEvalContext
164
+ include Musa::Extension::With
165
+
166
+ attr_reader :_secondary_reasons
167
+
168
+ def initialize
169
+ @_secondary_reasons = []
170
+ end
171
+
172
+ def prune(secondary_reason = nil)
173
+ @_secondary_reasons << secondary_reason
174
+ self
175
+ end
176
+ end
177
+
178
+ private_constant :CutRuleEvalContext
179
+ end
180
+
181
+ private_constant :CutRule
182
+ end
183
+
184
+ private_constant :RulesEvalContext
185
+
186
+ class Node
187
+ attr_reader :parent, :children, :object, :rejected
188
+
189
+ def initialize(object = nil, parent = nil)
190
+ @parent = parent
191
+ @children = []
192
+ @object = object
193
+
194
+ @ended = false
195
+ @rejected = nil
196
+ end
197
+
198
+ def add(object)
199
+ Node.new(object, self).tap { |n| @children << n }
200
+ end
201
+
202
+ def reject!(rejection)
203
+ @rejected = rejection
204
+ self
205
+ end
206
+
207
+ def mark_as_ended!
208
+ @children.each(&:update_rejection_by_children!)
209
+
210
+ if !@children.empty? && !@children.find { |n| !n.rejected }
211
+ reject! "Node rejected because all children are rejected"
212
+ end
213
+
214
+ @ended = true
215
+
216
+ self
217
+ end
218
+
219
+ def ended?
220
+ @ended
221
+ end
222
+
223
+ def history
224
+ objects = []
225
+ n = self
226
+ while n && n.object
227
+ objects << n.object
228
+ n = n.parent
229
+ end
230
+
231
+ objects.reverse
232
+ end
233
+
234
+ def fish
235
+ fished = []
236
+
237
+ @children.each do |node|
238
+ unless node.rejected
239
+ if node.ended?
240
+ fished << node.object
241
+ else
242
+ fished += node.fish
243
+ end
244
+ end
245
+ end
246
+
247
+ fished
248
+ end
249
+
250
+ def combinations(parent_combination = nil)
251
+ parent_combination ||= []
252
+
253
+ combinations = []
254
+
255
+ unless rejected
256
+ if @children.empty?
257
+ combinations << parent_combination
258
+ else
259
+ @children.each do |node|
260
+ node.combinations(parent_combination + [node.object]).each do |object|
261
+ combinations << object
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ combinations
268
+ end
269
+ end
270
+
271
+ private_constant :Node
272
+ end
273
+ end
274
+ end