musa-dsl 0.30.2 → 0.40.0

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.version +6 -0
  4. data/.yardopts +7 -0
  5. data/README.md +227 -6
  6. data/docs/README.md +83 -0
  7. data/docs/api-reference.md +86 -0
  8. data/docs/getting-started/quick-start.md +93 -0
  9. data/docs/getting-started/tutorial.md +58 -0
  10. data/docs/subsystems/core-extensions.md +316 -0
  11. data/docs/subsystems/datasets.md +465 -0
  12. data/docs/subsystems/generative.md +290 -0
  13. data/docs/subsystems/matrix.md +63 -0
  14. data/docs/subsystems/midi.md +123 -0
  15. data/docs/subsystems/music.md +233 -0
  16. data/docs/subsystems/musicxml-builder.md +264 -0
  17. data/docs/subsystems/neumas.md +71 -0
  18. data/docs/subsystems/repl.md +135 -0
  19. data/docs/subsystems/sequencer.md +98 -0
  20. data/docs/subsystems/series.md +302 -0
  21. data/docs/subsystems/transcription.md +152 -0
  22. data/docs/subsystems/transport.md +177 -0
  23. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
  24. data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
  25. data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
  26. data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
  27. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
  28. data/lib/musa-dsl/core-ext/extension.rb +53 -0
  29. data/lib/musa-dsl/core-ext/hashify.rb +162 -1
  30. data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
  31. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
  32. data/lib/musa-dsl/core-ext/with.rb +114 -0
  33. data/lib/musa-dsl/datasets/dataset.rb +109 -0
  34. data/lib/musa-dsl/datasets/delta-d.rb +78 -0
  35. data/lib/musa-dsl/datasets/e.rb +186 -2
  36. data/lib/musa-dsl/datasets/gdv.rb +279 -2
  37. data/lib/musa-dsl/datasets/gdvd.rb +201 -0
  38. data/lib/musa-dsl/datasets/helper.rb +75 -0
  39. data/lib/musa-dsl/datasets/p.rb +177 -2
  40. data/lib/musa-dsl/datasets/packed-v.rb +91 -0
  41. data/lib/musa-dsl/datasets/pdv.rb +136 -1
  42. data/lib/musa-dsl/datasets/ps.rb +134 -4
  43. data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
  44. data/lib/musa-dsl/datasets/score/render.rb +105 -1
  45. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
  46. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
  47. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
  48. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
  49. data/lib/musa-dsl/datasets/score.rb +279 -0
  50. data/lib/musa-dsl/datasets/v.rb +88 -0
  51. data/lib/musa-dsl/generative/darwin.rb +180 -1
  52. data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
  53. data/lib/musa-dsl/generative/markov.rb +133 -3
  54. data/lib/musa-dsl/generative/rules.rb +258 -4
  55. data/lib/musa-dsl/generative/variatio.rb +217 -2
  56. data/lib/musa-dsl/logger/logger.rb +267 -2
  57. data/lib/musa-dsl/matrix/matrix.rb +256 -10
  58. data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
  59. data/lib/musa-dsl/midi/midi-voices.rb +265 -4
  60. data/lib/musa-dsl/music/chord-definition.rb +233 -1
  61. data/lib/musa-dsl/music/chord-definitions.rb +33 -6
  62. data/lib/musa-dsl/music/chords.rb +308 -2
  63. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
  64. data/lib/musa-dsl/music/scales.rb +957 -40
  65. data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
  66. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
  67. data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
  68. data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
  69. data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
  70. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
  71. data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
  72. data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
  73. data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
  74. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
  75. data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
  76. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
  77. data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
  78. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
  79. data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
  80. data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
  81. data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
  82. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
  83. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
  84. data/lib/musa-dsl/neumas/neumas.rb +67 -0
  85. data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
  86. data/lib/musa-dsl/repl/repl.rb +550 -0
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
  89. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
  91. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
  92. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
  93. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
  94. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
  95. data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
  96. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
  97. data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
  98. data/lib/musa-dsl/series/array-to-serie.rb +37 -1
  99. data/lib/musa-dsl/series/base-series.rb +843 -5
  100. data/lib/musa-dsl/series/buffer-serie.rb +48 -0
  101. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
  102. data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
  103. data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
  104. data/lib/musa-dsl/series/proxy-serie.rb +67 -0
  105. data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
  106. data/lib/musa-dsl/series/queue-serie.rb +65 -0
  107. data/lib/musa-dsl/series/series-composer.rb +701 -0
  108. data/lib/musa-dsl/series/timed-serie.rb +473 -28
  109. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
  110. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
  111. data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
  112. data/lib/musa-dsl/transcription/transcription.rb +265 -0
  113. data/lib/musa-dsl/transport/clock.rb +125 -0
  114. data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
  115. data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
  116. data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
  117. data/lib/musa-dsl/transport/timer-clock.rb +183 -1
  118. data/lib/musa-dsl/transport/timer.rb +83 -0
  119. data/lib/musa-dsl/transport/transport.rb +318 -0
  120. data/lib/musa-dsl/version.rb +1 -1
  121. data/lib/musa-dsl.rb +132 -25
  122. data/musa-dsl.gemspec +12 -10
  123. metadata +87 -8
@@ -1,21 +1,172 @@
1
1
  require_relative '../core-ext/smart-proc-binder'
2
2
  require_relative '../core-ext/with'
3
3
 
4
- # TODO: 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)
5
- # TODO: esto mismo sería aplicable en otros generadores? variatio/darwin? generative-grammar? markov?
6
- # TODO: optimizar la llamada a .with que internamente genera cada vez un SmartProcBinder; podría generarse sólo una vez por cada &block
7
-
8
4
  module Musa
5
+ # Rule-based production system with growth and pruning.
6
+ #
7
+ # Rules implements a production system that generates tree structures
8
+ # by applying growth rules to produce branches and pruning rules to
9
+ # eliminate invalid paths. Similar to L-systems and production systems
10
+ # in formal grammars, but with validation and constraint satisfaction.
11
+ #
12
+ # ## Core Concepts
13
+ #
14
+ # - **Grow Rules**: Transform objects into new possibilities (branches)
15
+ # - **Cut Rules**: Prune branches that violate constraints
16
+ # - **End Condition**: Mark branches as complete
17
+ # - **Tree**: Hierarchical structure of all valid possibilities
18
+ # - **History**: Path from root to current node
19
+ # - **Combinations**: All valid complete paths through tree
20
+ #
21
+ # ## Generation Process
22
+ #
23
+ # 1. **Seed**: Start with initial object(s)
24
+ # 2. **Grow**: Apply grow rules sequentially to create branches
25
+ # 3. **Validate**: Apply cut rules to prune invalid branches
26
+ # 4. **Check End**: Mark branches meeting end condition
27
+ # 5. **Recurse**: Continue growing non-ended branches
28
+ # 6. **Collect**: Gather all valid complete paths
29
+ #
30
+ # ## Rule Application
31
+ #
32
+ # Rules are applied in definition order. Each grow rule can produce
33
+ # multiple branches via `branch`. Cut rules can prune with `prune`.
34
+ # The system tracks history (path to current node) for context-aware
35
+ # rule application.
36
+ #
37
+ # ## Musical Applications
38
+ #
39
+ # - Generate harmonic progressions with voice leading rules
40
+ # - Create melodic variations with contour constraints
41
+ # - Produce rhythmic patterns following metric rules
42
+ # - Build counterpoint with species rules
43
+ # - Generate chord voicings with spacing constraints
44
+ #
45
+ # @example Basic chord progression rules
46
+ # rules = Musa::Rules::Rules.new do
47
+ # # Generate possible next chords
48
+ # grow 'next chord' do |chord, history|
49
+ # case chord
50
+ # when :I then branch(:ii); branch(:IV); branch(:V)
51
+ # when :ii then branch(:V); branch(:vii)
52
+ # when :IV then branch(:I); branch(:V)
53
+ # when :V then branch(:I); branch(:vi)
54
+ # when :vi then branch(:ii); branch(:IV)
55
+ # when :vii then branch(:I)
56
+ # end
57
+ # end
58
+ #
59
+ # # Avoid parallel fifths
60
+ # cut 'parallel fifths' do |chord, history|
61
+ # prune if has_parallel_fifths?(history + [chord])
62
+ # end
63
+ #
64
+ # # End after 4 chords
65
+ # ended_when do |chord, history|
66
+ # history.size == 4
67
+ # end
68
+ # end
69
+ #
70
+ # tree = rules.apply([:I])
71
+ # progressions = tree.combinations
72
+ # # => [[:I, :ii, :V, :I], [:I, :IV, :V, :I], ...]
73
+ #
74
+ # @example Melodic contour rules with parameters
75
+ # rules = Musa::Rules::Rules.new do
76
+ # grow 'next note' do |pitch, history, max_interval:|
77
+ # # Try intervals within max_interval
78
+ # (-max_interval..max_interval).each do |interval|
79
+ # branch pitch + interval unless interval.zero?
80
+ # end
81
+ # end
82
+ #
83
+ # cut 'range limit' do |pitch, history|
84
+ # prune if pitch < 60 || pitch > 84 # C4 to C6
85
+ # end
86
+ #
87
+ # cut 'no large leaps' do |pitch, history|
88
+ # prune if history.last && (pitch - history.last).abs > 7
89
+ # end
90
+ #
91
+ # ended_when do |pitch, history|
92
+ # history.size == 8 # 8-note melody
93
+ # end
94
+ # end
95
+ #
96
+ # tree = rules.apply([60], max_interval: 3)
97
+ # melodies = tree.combinations
98
+ #
99
+ # @example Rhythm pattern generation
100
+ # rules = Musa::Rules::Rules.new do
101
+ # grow 'add duration' do |pattern, history, remaining:|
102
+ # [1/4r, 1/8r, 1/16r].each do |dur|
103
+ # if dur <= remaining
104
+ # branch pattern + [dur]
105
+ # end
106
+ # end
107
+ # end
108
+ #
109
+ # cut 'too many sixteenths' do |pattern, history|
110
+ # sixteenths = pattern.count { |d| d == 1/16r }
111
+ # prune if sixteenths > 4
112
+ # end
113
+ #
114
+ # ended_when do |pattern, history, remaining:|
115
+ # pattern.sum >= remaining
116
+ # end
117
+ # end
118
+ #
119
+ # tree = rules.apply([], remaining: 1r) # One bar
120
+ # rhythms = tree.combinations
121
+ #
122
+ # @see Rules Main rule-based generator class
123
+ # @see Musa::Extension::SmartProcBinder Smart procedure binding for rule evaluation
124
+ # @see Musa::Extension::Arrayfy Array conversion for seed objects
125
+ # @see Musa::Extension::With DSL context management for rule definitions
126
+ # @see https://en.wikipedia.org/wiki/Production_system_(computer_science) Production systems (Wikipedia)
127
+ # @see https://en.wikipedia.org/wiki/L-system L-systems (Wikipedia)
9
128
  module Rules
129
+ # TODO: 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)
130
+ # TODO: esto mismo sería aplicable en otros generadores? variatio/darwin? generative-grammar? markov?
131
+ # TODO: optimizar la llamada a .with que internamente genera cada vez un SmartProcBinder; podría generarse sólo una vez por cada &block
132
+
10
133
  using Musa::Extension::Arrayfy
11
134
 
135
+ # Rule-based generator with growth and pruning.
136
+ #
137
+ # Applies grow/cut rules to generate tree of valid possibilities.
12
138
  class Rules
13
139
  include Musa::Extension::With
14
140
 
141
+ # Creates rule system with defined rules.
142
+ #
143
+ # @yield rule definition DSL block
144
+ # @yieldreturn [void]
145
+ #
146
+ # @example
147
+ # rules = Rules.new do
148
+ # grow 'generate' { |obj| branch new_obj }
149
+ # cut 'validate' { |obj| prune if invalid?(obj) }
150
+ # ended_when { |obj| complete?(obj) }
151
+ # end
15
152
  def initialize(&block)
16
153
  @dsl = RulesEvalContext.new(&block)
17
154
  end
18
155
 
156
+ # Generates possibility tree from object.
157
+ #
158
+ # Recursively applies grow rules to create branches, cut rules to
159
+ # prune invalid paths, and end conditions to mark complete branches.
160
+ #
161
+ # @param object [Object] object to expand
162
+ # @param confirmed_node [Node, nil] confirmed parent node (for history)
163
+ # @param node [Node, nil] current node being built
164
+ # @param grow_rules [Array<GrowRule>, nil] rules to apply
165
+ # @param parameters [Hash] additional parameters for rules
166
+ #
167
+ # @return [Node] root node of possibility tree
168
+ #
169
+ # @api private
19
170
  def generate_possibilities(object, confirmed_node = nil, node = nil, grow_rules = nil, **parameters)
20
171
  node ||= Node.new
21
172
  grow_rules ||= @dsl._grow_rules
@@ -53,6 +204,25 @@ module Musa
53
204
  node
54
205
  end
55
206
 
207
+ # Applies rules to seed objects sequentially.
208
+ #
209
+ # Processes list of seed objects in sequence, generating possibilities
210
+ # from each confirmed endpoint of previous seed. Returns tree of all
211
+ # valid combination paths.
212
+ #
213
+ # @param object_or_list [Object, Array] seed object(s) to process
214
+ # @param node [Node, nil] root node (creates if nil)
215
+ # @param parameters [Hash] additional parameters for rules
216
+ #
217
+ # @return [Node] root node with all combination paths
218
+ #
219
+ # @example Single seed
220
+ # tree = rules.apply(:I)
221
+ # tree.combinations # => [[:I, :ii, :V, :I], ...]
222
+ #
223
+ # @example Multiple seeds
224
+ # tree = rules.apply([:I, :ii, :V])
225
+ # tree.combinations # => combinations starting from each seed
56
226
  def apply(object_or_list, node = nil, **parameters)
57
227
  list = object_or_list.arrayfy.clone
58
228
 
@@ -76,27 +246,51 @@ module Musa
76
246
  node
77
247
  end
78
248
 
249
+ # DSL context for rule definitions.
250
+ #
251
+ # @api private
79
252
  class RulesEvalContext
80
253
  include Musa::Extension::With
81
254
 
255
+ # @return [Array<GrowRule>] grow rules
256
+ # @return [Proc, nil] end condition
257
+ # @return [Array<CutRule>] cut rules
82
258
  attr_reader :_grow_rules, :_ended_when, :_cut_rules
83
259
 
260
+ # @api private
84
261
  def initialize(&block)
85
262
  @_grow_rules = []
86
263
  @_cut_rules = []
87
264
  with &block
88
265
  end
89
266
 
267
+ # Defines grow rule.
268
+ #
269
+ # @param name [String] rule name for debugging
270
+ # @yield [object, history, **params] rule block
271
+ # @return [self]
272
+ # @api private
90
273
  def grow(name, &block)
91
274
  @_grow_rules << GrowRule.new(name, &block)
92
275
  self
93
276
  end
94
277
 
278
+ # Defines end condition.
279
+ #
280
+ # @yield [object, history, **params] condition block
281
+ # @return [self]
282
+ # @api private
95
283
  def ended_when(&block)
96
284
  @_ended_when = block
97
285
  self
98
286
  end
99
287
 
288
+ # Defines cut/pruning rule.
289
+ #
290
+ # @param reason [String] rejection reason
291
+ # @yield [object, history, **params] pruning block
292
+ # @return [self]
293
+ # @api private
100
294
  def cut(reason, &block)
101
295
  @_cut_rules << CutRule.new(reason, &block)
102
296
  self
@@ -191,9 +385,21 @@ module Musa
191
385
 
192
386
  private_constant :RulesEvalContext
193
387
 
388
+ # Tree node representing possibility in generation.
389
+ #
390
+ # Nodes form tree structure of generation possibilities.
391
+ # Each node has object, parent, children, rejection status, and end flag.
392
+ #
393
+ # @attr_reader parent [Node, nil] parent node
394
+ # @attr_reader children [Array<Node>] child nodes
395
+ # @attr_reader object [Object, nil] node object
396
+ # @attr_reader rejected [String, Array<String>, nil] rejection reason(s)
397
+ #
398
+ # @api private
194
399
  class Node
195
400
  attr_reader :parent, :children, :object, :rejected
196
401
 
402
+ # @api private
197
403
  def initialize(object = nil, parent = nil)
198
404
  @parent = parent
199
405
  @children = []
@@ -203,15 +409,31 @@ module Musa
203
409
  @rejected = nil
204
410
  end
205
411
 
412
+ # Adds child node.
413
+ #
414
+ # @param object [Object] child object
415
+ # @return [Node] created child node
416
+ # @api private
206
417
  def add(object)
207
418
  Node.new(object, self).tap { |n| @children << n }
208
419
  end
209
420
 
421
+ # Marks node as rejected.
422
+ #
423
+ # @param rejection [String, Array<String>] rejection reason(s)
424
+ # @return [self]
425
+ # @api private
210
426
  def reject!(rejection)
211
427
  @rejected = rejection
212
428
  self
213
429
  end
214
430
 
431
+ # Marks node as ended/complete.
432
+ #
433
+ # Propagates rejection if all children rejected.
434
+ #
435
+ # @return [self]
436
+ # @api private
215
437
  def mark_as_ended!
216
438
  @children.each(&:update_rejection_by_children!)
217
439
 
@@ -224,10 +446,18 @@ module Musa
224
446
  self
225
447
  end
226
448
 
449
+ # Checks if node is ended.
450
+ #
451
+ # @return [Boolean]
452
+ # @api private
227
453
  def ended?
228
454
  @ended
229
455
  end
230
456
 
457
+ # Returns path from root to this node.
458
+ #
459
+ # @return [Array<Object>] history of objects
460
+ # @api private
231
461
  def history
232
462
  objects = []
233
463
  n = self
@@ -239,6 +469,13 @@ module Musa
239
469
  objects.reverse
240
470
  end
241
471
 
472
+ # Collects objects from ended leaf nodes.
473
+ #
474
+ # Recursively gathers objects from all valid ended branches,
475
+ # excluding rejected paths.
476
+ #
477
+ # @return [Array<Object>] objects from valid endpoints
478
+ # @api private
242
479
  def fish
243
480
  fished = []
244
481
 
@@ -255,6 +492,23 @@ module Musa
255
492
  fished
256
493
  end
257
494
 
495
+ # Returns all valid combination paths.
496
+ #
497
+ # Recursively builds complete paths from root to all valid
498
+ # leaf nodes, excluding rejected branches.
499
+ #
500
+ # @param parent_combination [Array, nil] parent path
501
+ #
502
+ # @return [Array<Array<Object>>] all valid complete paths
503
+ #
504
+ # @example
505
+ # tree.combinations
506
+ # # => [
507
+ # # [:I, :ii, :V, :I],
508
+ # # [:I, :IV, :V, :I],
509
+ # # ...
510
+ # # ]
511
+ # @api private
258
512
  def combinations(parent_combination = nil)
259
513
  parent_combination ||= []
260
514
 
@@ -2,14 +2,147 @@ require_relative '../core-ext/smart-proc-binder'
2
2
  require_relative '../core-ext/arrayfy'
3
3
  require_relative '../core-ext/with'
4
4
 
5
- # TODO: permitir definir un variatio a través de llamadas a métodos y/o atributos, además de a través del block del constructor
6
-
7
5
  module Musa
6
+ # Combinatorial variation generator with Cartesian product.
7
+ #
8
+ # Variatio generates all possible combinations of parameter values across
9
+ # defined fields, creating comprehensive variation sets. Uses Cartesian
10
+ # product to produce exhaustive parameter combinations, then constructs
11
+ # objects and applies attribute modifications.
12
+ #
13
+ # ## Core Concepts
14
+ #
15
+ # - **Fields**: Named parameters with option sets
16
+ # - **Fieldsets**: Nested field groups with their own options
17
+ # - **Constructor**: Creates base objects from field values
18
+ # - **with_attributes**: Modifies objects with field/fieldset values
19
+ # - **Finalize**: Post-processes completed objects
20
+ # - **Variations**: All Cartesian product combinations
21
+ #
22
+ # ## Generation Process
23
+ #
24
+ # 1. **Define**: Specify fields, fieldsets, constructor, attributes, finalize
25
+ # 2. **Combine**: Calculate Cartesian product of all field options
26
+ # 3. **Construct**: Create objects using constructor with each combination
27
+ # 4. **Attribute**: Apply with_attributes blocks for each combination
28
+ # 5. **Finalize**: Run finalize block on completed objects
29
+ # 6. **Return**: Array of all generated variations
30
+ #
31
+ # ## Musical Applications
32
+ #
33
+ # - Generate all variations of a musical motif
34
+ # - Create comprehensive parameter sweeps for synthesis
35
+ # - Produce complete harmonic permutations
36
+ # - Build exhaustive rhythm pattern combinations
37
+ #
38
+ # @example Basic field variations
39
+ # variatio = Musa::Variatio::Variatio.new :chord do
40
+ # field :root, [60, 64, 67] # C, E, G
41
+ # field :type, [:major, :minor]
42
+ #
43
+ # constructor do |root:, type:|
44
+ # { root: root, type: type }
45
+ # end
46
+ # end
47
+ #
48
+ # variations = variatio.run
49
+ # # => [
50
+ # # { root: 60, type: :major },
51
+ # # { root: 60, type: :minor },
52
+ # # { root: 64, type: :major },
53
+ # # { root: 64, type: :minor },
54
+ # # { root: 67, type: :major },
55
+ # # { root: 67, type: :minor }
56
+ # # ]
57
+ # # 3 roots × 2 types = 6 variations
58
+ #
59
+ # @example Override field options at runtime
60
+ # variatio = Musa::Variatio::Variatio.new :object do
61
+ # field :a, 1..10
62
+ # field :b, [:alfa, :beta, :gamma]
63
+ #
64
+ # constructor { |a:, b:| { a: a, b: b } }
65
+ # end
66
+ #
67
+ # # Override :a to limit variations
68
+ # variatio.on(a: 1..3)
69
+ # # => 3 × 3 = 9 variations instead of 10 × 3 = 30
70
+ #
71
+ # @example Nested fieldsets with attributes
72
+ # variatio = Musa::Variatio::Variatio.new :synth do
73
+ # field :wave, [:saw, :square]
74
+ # field :cutoff, [500, 1000, 2000]
75
+ #
76
+ # constructor do |wave:, cutoff:|
77
+ # { wave: wave, cutoff: cutoff, lfo: {} }
78
+ # end
79
+ #
80
+ # # Nested fieldset for LFO parameters
81
+ # fieldset :lfo, [:vibrato, :tremolo] do
82
+ # field :rate, [4, 8]
83
+ # field :depth, [0.1, 0.5]
84
+ #
85
+ # with_attributes do |synth:, lfo:, rate:, depth:|
86
+ # synth[:lfo][lfo] = { rate: rate, depth: depth }
87
+ # end
88
+ # end
89
+ # end
90
+ #
91
+ # variations = variatio.run
92
+ # # => 2 waves × 3 cutoffs × 2 lfo types × 2 rates × 2 depths = 48 variations
93
+ #
94
+ # @example With finalize block
95
+ # variatio = Musa::Variatio::Variatio.new :note do
96
+ # field :pitch, [60, 62, 64]
97
+ # field :velocity, [64, 96, 127]
98
+ #
99
+ # constructor { |pitch:, velocity:| { pitch: pitch, velocity: velocity } }
100
+ #
101
+ # finalize do |note:|
102
+ # note[:loudness] = note[:velocity] / 127.0
103
+ # note[:dynamics] = case note[:velocity]
104
+ # when 0..48 then :pp
105
+ # when 49..80 then :mf
106
+ # else :ff
107
+ # end
108
+ # end
109
+ # end
110
+ #
111
+ # @see Variatio Main combinatorial variation generator class
112
+ # @see Musa::Extension::SmartProcBinder Smart procedure binding for constructor/finalize blocks
113
+ # @see Musa::Extension::Arrayfy Array conversion utilities for field options
114
+ # @see Musa::Extension::With DSL context management for field definitions
115
+ # @see https://en.wikipedia.org/wiki/Cartesian_product Cartesian product (Wikipedia)
116
+ # @see https://en.wikipedia.org/wiki/Variation_(mathematics) Variation in mathematics (Wikipedia)
117
+ #
118
+ # @api public
8
119
  module Variatio
9
120
  using Musa::Extension::Arrayfy
10
121
  using Musa::Extension::ExplodeRanges
11
122
 
123
+ # TODO: permitir definir un variatio a través de llamadas a métodos y/o atributos, además de a través del block del constructor
124
+
125
+ # Combinatorial variation generator.
126
+ #
127
+ # Generates all combinations of field values using Cartesian product,
128
+ # constructs objects, applies attributes, and optionally finalizes.
12
129
  class Variatio
130
+ # Creates variation generator with field definitions.
131
+ #
132
+ # @param instance_name [Symbol] name for object parameter in blocks
133
+ #
134
+ # @yield DSL block defining fields, constructor, attributes, finalize
135
+ # @yieldreturn [void]
136
+ #
137
+ # @raise [ArgumentError] if instance_name not a symbol
138
+ # @raise [ArgumentError] if no block given
139
+ #
140
+ # @example
141
+ # variatio = Variatio.new :obj do
142
+ # field :x, [1, 2, 3]
143
+ # field :y, [:a, :b]
144
+ # constructor { |x:, y:| { x: x, y: y } }
145
+ # end
13
146
  def initialize(instance_name, &block)
14
147
  raise ArgumentError, 'instance_name should be a symbol' unless instance_name.is_a?(Symbol)
15
148
  raise ArgumentError, 'block is needed' unless block
@@ -23,6 +156,29 @@ module Musa
23
156
  @finalize = main_context._finalize
24
157
  end
25
158
 
159
+ # Generates variations with runtime field value overrides.
160
+ #
161
+ # Allows overriding field options at generation time, useful for
162
+ # limiting variation space or parameterizing generation.
163
+ #
164
+ # @param values [Hash{Symbol => Array, Range}] field overrides
165
+ # Keys are field names, values are option arrays or ranges
166
+ #
167
+ # @return [Array] all generated variation objects
168
+ #
169
+ # @example Override field values
170
+ # variatio = Variatio.new :obj do
171
+ # field :x, 1..10
172
+ # field :y, [:a, :b, :c]
173
+ # constructor { |x:, y:| { x: x, y: y } }
174
+ # end
175
+ #
176
+ # # Default: 10 × 3 = 30 variations
177
+ # variatio.run.size # => 30
178
+ #
179
+ # # Override :x to limit variations
180
+ # variatio.on(x: 1..3).size # => 3 × 3 = 9
181
+ # variatio.on(x: [5], y: [:a]).size # => 1 × 1 = 1
26
182
  def on(**values)
27
183
  constructor_binder = Musa::Extension::SmartProcBinder::SmartProcBinder.new @constructor
28
184
  finalize_binder = Musa::Extension::SmartProcBinder::SmartProcBinder.new @finalize if @finalize
@@ -60,6 +216,25 @@ module Musa
60
216
  combinations
61
217
  end
62
218
 
219
+ # Generates all variations with default field values.
220
+ #
221
+ # Equivalent to calling {#on} with no overrides.
222
+ #
223
+ # @return [Array] all generated variation objects
224
+ #
225
+ # @example
226
+ # variatio = Variatio.new :obj do
227
+ # field :x, [1, 2, 3]
228
+ # field :y, [:a, :b]
229
+ # constructor { |x:, y:| { x: x, y: y } }
230
+ # end
231
+ #
232
+ # variations = variatio.run
233
+ # # => [
234
+ # # { x: 1, y: :a }, { x: 1, y: :b },
235
+ # # { x: 2, y: :a }, { x: 2, y: :b },
236
+ # # { x: 3, y: :a }, { x: 3, y: :b }
237
+ # # ]
63
238
  def run
64
239
  on
65
240
  end
@@ -199,6 +374,12 @@ module Musa
199
374
 
200
375
  private_constant :A2
201
376
 
377
+ # Internal tree node for attribute application phase.
378
+ #
379
+ # Manages execution of `with_attributes` blocks during variation generation.
380
+ # Coordinates attribute application across field hierarchy.
381
+ #
382
+ # @api private
202
383
  class B
203
384
  attr_reader :parameter_name, :options, :affected_field_names, :blocks, :inner
204
385
 
@@ -245,26 +426,46 @@ module Musa
245
426
  private
246
427
  end
247
428
 
429
+ # DSL context for fieldset definition.
430
+ #
431
+ # @api private
248
432
  class FieldsetContext
249
433
  include Musa::Extension::With
250
434
 
435
+ # @return [Fieldset] defined fieldset
251
436
  attr_reader :_fieldset
252
437
 
438
+ # @api private
253
439
  def initialize(name, options = nil, &block)
254
440
  @_fieldset = Fieldset.new name, options.arrayfy.explode_ranges
255
441
 
256
442
  with &block
257
443
  end
258
444
 
445
+ # Defines a field with options.
446
+ #
447
+ # @param name [Symbol] field name
448
+ # @param options [Array, Range, nil] field option values
449
+ # @api private
259
450
  def field(name, options = nil)
260
451
  @_fieldset.components << Field.new(name, options.arrayfy.explode_ranges)
261
452
  end
262
453
 
454
+ # Defines nested fieldset.
455
+ #
456
+ # @param name [Symbol] fieldset name
457
+ # @param options [Array, Range, nil] fieldset option values
458
+ # @yield fieldset DSL block
459
+ # @api private
263
460
  def fieldset(name, options = nil, &block)
264
461
  fieldset_context = FieldsetContext.new name, options, &block
265
462
  @_fieldset.components << fieldset_context._fieldset
266
463
  end
267
464
 
465
+ # Adds attribute modification block.
466
+ #
467
+ # @yield attribute modification block
468
+ # @api private
268
469
  def with_attributes(&block)
269
470
  @_fieldset.with_attributes << block
270
471
  end
@@ -272,9 +473,15 @@ module Musa
272
473
 
273
474
  private_constant :FieldsetContext
274
475
 
476
+ # DSL context for main Variatio configuration.
477
+ #
478
+ # @api private
275
479
  class MainContext < FieldsetContext
480
+ # @return [Proc] constructor block
481
+ # @return [Proc, nil] finalize block
276
482
  attr_reader :_constructor, :_finalize
277
483
 
484
+ # @api private
278
485
  def initialize(&block)
279
486
  @_constructor = nil
280
487
  @_finalize = nil
@@ -282,10 +489,18 @@ module Musa
282
489
  super :_maincontext, [nil], &block
283
490
  end
284
491
 
492
+ # Defines object constructor.
493
+ #
494
+ # @yield constructor block receiving field values
495
+ # @api private
285
496
  def constructor(&block)
286
497
  @_constructor = block
287
498
  end
288
499
 
500
+ # Defines finalize block.
501
+ #
502
+ # @yield finalize block receiving completed object
503
+ # @api private
289
504
  def finalize(&block)
290
505
  @_finalize = block
291
506
  end