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,9 +1,126 @@
1
1
  module Musa
2
+ # Generative grammar system for creating formal grammars with combinatorial generation.
3
+ #
4
+ # GenerativeGrammar provides a DSL for defining formal grammars that can generate
5
+ # all possible combinations of terminal and non-terminal symbols according to
6
+ # production rules. Similar to context-free grammars in formal language theory.
7
+ #
8
+ # ## Core Concepts
9
+ #
10
+ # - **Nodes (N)**: Terminal or block nodes with content and attributes
11
+ # - **Proxy Nodes (PN)**: Placeholder nodes for recursive grammar definitions
12
+ # - **Operators**: Combine nodes to form production rules
13
+ # - `|` (or): Alternative/choice between nodes
14
+ # - `+` (next): Concatenation/sequence of nodes
15
+ # - **Modifiers**:
16
+ # - `repeat(min:, max:)`: Repeat node multiple times
17
+ # - `limit(&block)`: Filter options by condition
18
+ # - **Options**: Generate all valid combinations matching the grammar
19
+ #
20
+ # ## Grammar Definition Process
21
+ #
22
+ # 1. Define terminal nodes with N(content, **attributes)
23
+ # 2. Combine nodes using operators (|, +)
24
+ # 3. Add repetition and limits as needed
25
+ # 4. Use PN() for recursive grammars
26
+ # 5. Call .options to generate all valid combinations
27
+ #
28
+ # ## Musical Applications
29
+ #
30
+ # - Generate melodic patterns with rhythmic constraints
31
+ # - Create harmonic progressions with voice leading rules
32
+ # - Produce variations of musical motifs
33
+ # - Build algorithmic composition structures
34
+ #
35
+ # @example Simple sequence with alternatives
36
+ # include Musa::GenerativeGrammar
37
+ #
38
+ # a = N('a', size: 1)
39
+ # b = N('b', size: 1)
40
+ # c = N('c', size: 1)
41
+ #
42
+ # # Grammar: (a or b) repeated 3 times, then c
43
+ # grammar = (a | b).repeat(3) + c
44
+ #
45
+ # # Generate all possibilities
46
+ # grammar.options(content: :join)
47
+ # # => ["aaac", "aabc", "abac", "abbc", "baac", "babc", "bbac", "bbbc"]
48
+ #
49
+ # @example Grammar with constraints
50
+ # a = N('a', size: 1)
51
+ # b = N('b', size: 1)
52
+ #
53
+ # # Limit: total size must equal 3
54
+ # grammar = (a | b).repeat.limit { |o| o.collect { |_| _.attributes[:size] }.sum == 3 }
55
+ #
56
+ # # Filter options where size <= 4
57
+ # grammar.options(content: :join) { |o| o.collect { |e| e.attributes[:size] }.sum <= 4 }
58
+ # # => ["aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb"]
59
+ #
60
+ # @example Recursive grammar using proxy nodes
61
+ # a = N('a', size: 1)
62
+ # b = N('b', size: 1)
63
+ # c = N('c', size: 1)
64
+ #
65
+ # # Create proxy for recursion
66
+ # dp = PN()
67
+ #
68
+ # # Grammar: (c + dp) or (a or b), limit size to 3
69
+ # d = (c + dp | (a | b)).repeat.limit(:size, :sum, :==, 3)
70
+ #
71
+ # # Assign recursive reference
72
+ # dp.node = d
73
+ #
74
+ # d.options(:size, :sum, :<=, 4, content: :join)
75
+ # # => ["cca", "ccb", "caa", "cab", "cba", "cbb", "aca", "acb", ...]
76
+ #
77
+ # @example Block nodes for dynamic content
78
+ # a = N(color: :blue) { |parent| 'hola' }
79
+ # b = N(color: :red) { |parent, attributes|
80
+ # OptionElement.new('adios', final: true, **attributes)
81
+ # }
82
+ #
83
+ # grammar = (a | b).repeat(2)
84
+ # grammar.options
85
+ # # => [["hola", "hola"], ["hola", "adios"], ["adios", "hola"], ["adios", "adios"]]
86
+ #
87
+ # @see N Method to create terminal/block nodes
88
+ # @see PN Method to create proxy nodes for recursion
89
+ # @see Musa::Series::Serie Series conversion for grammar options
90
+ # @see https://en.wikipedia.org/wiki/Formal_grammar Formal grammar (Wikipedia)
91
+ # @see https://en.wikipedia.org/wiki/Context-free_grammar Context-free grammar (Wikipedia)
92
+ # @see https://en.wikipedia.org/wiki/Generative_grammar Generative grammar (Wikipedia)
2
93
  module GenerativeGrammar
3
94
  # TODO: refactor & reorganize regarding use of include Musa::GenerativeGrammar problems as default consumption mode (it forces the consumer to have new public methods -P, PN- and class names -OptionElement-)
4
95
 
5
96
  extend self
6
97
 
98
+ # Creates a terminal or block node.
99
+ #
100
+ # Nodes are the basic building blocks of grammars. They can be:
101
+ #
102
+ # - **Terminal nodes**: Fixed content with attributes
103
+ # - **Block nodes**: Dynamic content generated by block
104
+ #
105
+ # @param content [Object, nil] terminal content (if no block given)
106
+ # @param attributes [Hash] attributes attached to node
107
+ #
108
+ # @yieldparam parent [Array<OptionElement>] parent elements in generation
109
+ # @yieldparam attributes [Hash] node attributes
110
+ # @yieldreturn [Object, OptionElement] generated content or element
111
+ #
112
+ # @return [Implementation::FinalNode, Implementation::BlockNode] created node
113
+ #
114
+ # @example Terminal node
115
+ # n = N('a', size: 1)
116
+ #
117
+ # @example Block node
118
+ # n = N(color: :blue) { |parent| "hello" }
119
+ #
120
+ # @example Block returning OptionElement
121
+ # n = N(type: :special) { |parent, attrs|
122
+ # OptionElement.new('value', **attrs)
123
+ # }
7
124
  def N(content = nil, **attributes, &block)
8
125
  if block_given? && content.nil?
9
126
  Implementation::BlockNode.new(attributes, &block)
@@ -12,13 +129,43 @@ module Musa
12
129
  end
13
130
  end
14
131
 
132
+ # Creates a proxy node for recursive grammars.
133
+ #
134
+ # Proxy nodes act as placeholders that can be assigned later,
135
+ # enabling recursive and self-referential grammar definitions.
136
+ #
137
+ # @return [Implementation::ProxyNode] proxy node
138
+ #
139
+ # @example Recursive grammar
140
+ # a = N('a')
141
+ # b = N('b')
142
+ #
143
+ # # Create proxy
144
+ # proxy = PN()
145
+ #
146
+ # # Define grammar referencing itself through proxy
147
+ # grammar = a + proxy | b
148
+ #
149
+ # # Assign recursive reference
150
+ # proxy.node = grammar
15
151
  def PN
16
152
  Implementation::ProxyNode.new
17
153
  end
18
154
 
155
+ # Container for generated option elements.
156
+ #
157
+ # Wraps content and attributes for each element in a generated option.
158
+ # Used internally when generating grammar options.
159
+ #
160
+ # @attr_reader content [Object] element content
161
+ # @attr_reader attributes [Hash] element attributes
19
162
  class OptionElement
20
163
  attr_reader :content, :attributes
21
164
 
165
+ # Creates option element.
166
+ #
167
+ # @param content [Object] element content
168
+ # @param attributes [Hash, nil] element attributes
22
169
  def initialize(content, attributes = nil)
23
170
  @content = content
24
171
  @attributes = attributes || {}
@@ -26,13 +173,54 @@ module Musa
26
173
  end
27
174
 
28
175
  module Implementation
176
+ # Base node class for grammar elements.
177
+ #
178
+ # Provides core operations for combining and transforming nodes.
179
+ # All node types (FinalNode, BlockNode, OrNode, etc.) inherit from this.
180
+ #
181
+ # @api private
29
182
  class Node
183
+ # Creates alternation between this node and another.
184
+ #
185
+ # Generates options where either this node or the other is chosen.
186
+ #
187
+ # @param other [Node] alternative node
188
+ #
189
+ # @return [OrNode] alternation node
190
+ #
191
+ # @example
192
+ # a = N('a')
193
+ # b = N('b')
194
+ # ab = a.or(b) # or a | b
30
195
  def or(other)
31
196
  OrNode.new(self, other)
32
197
  end
33
198
 
34
199
  alias_method :|, :or
35
200
 
201
+ # Repeats this node with optional min/max bounds.
202
+ #
203
+ # Generates options with different repetition counts of this node.
204
+ # Without bounds, generates infinite options (use with limit).
205
+ #
206
+ # @param exactly [Integer, nil] exact repetition count
207
+ # @param min [Integer, nil] minimum repetitions
208
+ # @param max [Integer, nil] maximum repetitions
209
+ #
210
+ # @return [Node] node with repetition
211
+ #
212
+ # @raise [ArgumentError] if both exactly and min/max are specified
213
+ #
214
+ # @example Fixed repetition
215
+ # a = N('a')
216
+ # aaa = a.repeat(3) # exactly 3 times
217
+ # aaa = a.repeat(exactly: 3) # same
218
+ #
219
+ # @example Bounded repetition
220
+ # a_range = a.repeat(min: 2, max: 4) # 2, 3, or 4 times
221
+ #
222
+ # @example Unbounded (use with limit)
223
+ # a_any = a.repeat.limit { |o| o.size <= 5 }
36
224
  def repeat(exactly = nil, min: nil, max: nil)
37
225
  raise ArgumentError, 'Only exactly value or min/max values are allowed' if exactly && (min || max)
38
226
 
@@ -55,6 +243,30 @@ module Musa
55
243
  end
56
244
  end
57
245
 
246
+ # Limits generated options by condition.
247
+ #
248
+ # Filters options to only those satisfying the given condition.
249
+ # Can use simplified arguments or custom block.
250
+ #
251
+ # @param attribute [Symbol, nil] attribute to check (simplified form)
252
+ # @param after_collect_operation [Symbol, nil] operation on collected values
253
+ # @param comparison_method [Symbol, nil] comparison method to apply
254
+ # @param comparison_value [Object, nil] value to compare against
255
+ #
256
+ # @yieldparam option [Array<OptionElement>] option to evaluate
257
+ # @yieldreturn [Boolean] true if option should be included
258
+ #
259
+ # @return [ConditionNode] limited node
260
+ #
261
+ # @raise [ArgumentError] if both simplified arguments and block given
262
+ #
263
+ # @example Block form
264
+ # grammar = (a | b).repeat.limit { |o|
265
+ # o.collect { |e| e.attributes[:size] }.sum == 3
266
+ # }
267
+ #
268
+ # @example Simplified form
269
+ # grammar = (a | b).repeat.limit(:size, :sum, :==, 3)
58
270
  def limit(attribute = nil, after_collect_operation = nil, comparison_method = nil, comparison_value = nil, &block)
59
271
  raise ArgumentError, 'Cannot use simplified arguments and yield block at the same time' if (attribute || after_collect_operation || comparison_method || comparison_value) && @block
60
272
 
@@ -63,12 +275,61 @@ module Musa
63
275
  ConditionNode.new(self, &block)
64
276
  end
65
277
 
278
+ # Creates sequence of this node followed by another.
279
+ #
280
+ # Generates options with this node's content followed by the other's.
281
+ #
282
+ # @param other [Node] node to follow
283
+ #
284
+ # @return [NextNode] sequence node
285
+ #
286
+ # @example
287
+ # a = N('a')
288
+ # b = N('b')
289
+ # ab = a.next(b) # or a + b
66
290
  def next(other)
67
291
  NextNode.new(self, other)
68
292
  end
69
293
 
70
294
  alias_method :+, :next
71
295
 
296
+ # Generates all options from this grammar node.
297
+ #
298
+ # Produces all valid combinations matching the grammar definition.
299
+ # Can filter with condition and control output format.
300
+ #
301
+ # @param attribute [Symbol, nil] attribute for filtering (simplified)
302
+ # @param after_collect_operation [Symbol, nil] operation on collected values
303
+ # @param comparison_method [Symbol, nil] comparison method
304
+ # @param comparison_value [Object, nil] value to compare against
305
+ # @param raw [Boolean] if true, return raw OptionElement arrays
306
+ # @param content [Symbol] how to extract content (:itself, :join, etc.)
307
+ #
308
+ # @yieldparam option [Array<OptionElement>] option to evaluate
309
+ # @yieldreturn [Boolean] true if option should be included
310
+ #
311
+ # @return [Array] generated options
312
+ #
313
+ # @raise [ArgumentError] if simplified arguments and block both given
314
+ # @raise [ArgumentError] if raw and content both specified
315
+ #
316
+ # @example Basic usage
317
+ # grammar = (a | b).repeat(2)
318
+ # grammar.options
319
+ # # => [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]]
320
+ #
321
+ # @example With content formatting
322
+ # grammar.options(content: :join)
323
+ # # => ["aa", "ab", "ba", "bb"]
324
+ #
325
+ # @example With filtering
326
+ # grammar.options { |o| o.size == 2 }
327
+ #
328
+ # @example Simplified filtering
329
+ # grammar.options(:size, :sum, :<=, 4, content: :join)
330
+ #
331
+ # @example Raw OptionElements
332
+ # grammar.options(raw: true)
72
333
  def options(attribute = nil,
73
334
  after_collect_operation = nil,
74
335
  comparison_method = nil,
@@ -92,16 +353,34 @@ module Musa
92
353
  end
93
354
  end
94
355
 
356
+ # Counts number of options generated by this node.
357
+ #
358
+ # @return [Integer] option count
95
359
  def size
96
360
  options.size
97
361
  end
98
362
 
99
363
  alias_method :length, :size
100
364
 
365
+ # Gets option at index as series node.
366
+ #
367
+ # @param index [Integer] option index
368
+ #
369
+ # @return [Node] option converted to node
101
370
  def [](index)
102
371
  options[index].to_serie.to_node
103
372
  end
104
373
 
374
+ # Converts options to series.
375
+ #
376
+ # Generates options and converts them to a {Musa::Series::Serie}.
377
+ #
378
+ # @param flatten [Boolean] flatten nested series
379
+ #
380
+ # @yieldparam option [Array<OptionElement>] option to evaluate
381
+ # @yieldreturn [Boolean] true if option should be included
382
+ #
383
+ # @return [Musa::Series::Serie] series of options
105
384
  def to_serie(flatten: true, &condition)
106
385
  serie = _options(&condition).collect { |o| o.collect(&:content) }.to_serie(of_series: true).merge
107
386
  serie = serie.flatten if flatten
@@ -111,6 +390,16 @@ module Musa
111
390
 
112
391
  alias_method :s, :to_serie
113
392
 
393
+ # Internal method to generate option arrays.
394
+ #
395
+ # @param parent [Array<OptionElement>, nil] parent elements
396
+ #
397
+ # @yieldparam option [Array<OptionElement>] option to evaluate
398
+ # @yieldreturn [Boolean] true if option should be included
399
+ #
400
+ # @return [Array<Array<OptionElement>>] option arrays
401
+ #
402
+ # @api private
114
403
  def _options(parent: nil, &condition)
115
404
  raise NotImplementedError
116
405
  end
@@ -132,23 +421,43 @@ module Musa
132
421
  end
133
422
  end
134
423
 
424
+ # Proxy node for recursive grammar references.
425
+ #
426
+ # Acts as placeholder that delegates to assigned node.
427
+ # Enables recursive and self-referential grammars.
428
+ #
429
+ # @api private
135
430
  class ProxyNode < Node
431
+ # @return [Node, nil] assigned node
136
432
  attr_accessor :node
137
433
 
434
+ # @api private
138
435
  def _options(parent: nil, &condition)
139
436
  @node._options parent: parent, &condition
140
437
  end
141
438
  end
142
439
 
440
+ # Terminal node with fixed content.
441
+ #
442
+ # Represents a leaf node in the grammar tree with constant content.
443
+ #
444
+ # @attr_reader content [Object] node content
445
+ # @attr_reader attributes [Hash] node attributes
446
+ #
447
+ # @api private
143
448
  class FinalNode < Node
144
449
  attr_reader :content
145
450
  attr_reader :attributes
146
451
 
452
+ # @param content [Object] node content
453
+ # @param attributes [Hash] node attributes
454
+ # @api private
147
455
  def initialize(content, attributes)
148
456
  super()
149
457
  @element = OptionElement.new(content, attributes)
150
458
  end
151
459
 
460
+ # @api private
152
461
  def _options(parent: nil, &condition)
153
462
  parent ||= []
154
463
 
@@ -164,12 +473,22 @@ module Musa
164
473
  end
165
474
  end
166
475
 
476
+ # Node with dynamic content generated by block.
477
+ #
478
+ # Executes block at generation time to produce content.
479
+ # Block receives parent elements and attributes.
480
+ #
481
+ # @api private
167
482
  class BlockNode < Node
483
+ # @param attributes [Hash] node attributes
484
+ # @yield [parent, attributes] block to generate content
485
+ # @api private
168
486
  def initialize(attributes, &block)
169
487
  @attributes = attributes
170
488
  @block = block
171
489
  end
172
490
 
491
+ # @api private
173
492
  def _options(parent: nil, &condition)
174
493
  parent ||= []
175
494
 
@@ -188,12 +507,22 @@ module Musa
188
507
  end
189
508
  end
190
509
 
510
+ # Node that filters child node options by condition.
511
+ #
512
+ # Applies condition block to filter generated options.
513
+ # Created by {Node#limit}.
514
+ #
515
+ # @api private
191
516
  class ConditionNode < Node
517
+ # @param node [Node] node to filter
518
+ # @yield [option] condition block
519
+ # @api private
192
520
  def initialize(node, &block)
193
521
  @node = node
194
522
  @block = block
195
523
  end
196
524
 
525
+ # @api private
197
526
  def _options(parent: nil, &condition)
198
527
  parent ||= []
199
528
 
@@ -207,13 +536,23 @@ module Musa
207
536
  end
208
537
  end
209
538
 
539
+ # Node representing alternation between two nodes.
540
+ #
541
+ # Generates options from either node1 or node2.
542
+ # Created by {Node#or} or `|` operator.
543
+ #
544
+ # @api private
210
545
  class OrNode < Node
546
+ # @param node1 [Node] first alternative
547
+ # @param node2 [Node] second alternative
548
+ # @api private
211
549
  def initialize(node1, node2)
212
550
  @node1 = node1
213
551
  @node2 = node2
214
552
  super()
215
553
  end
216
554
 
555
+ # @api private
217
556
  def _options(parent: nil, &condition)
218
557
  parent ||= []
219
558
 
@@ -231,13 +570,23 @@ module Musa
231
570
  end
232
571
  end
233
572
 
573
+ # Node representing sequence of two nodes.
574
+ #
575
+ # Generates options with node followed by after.
576
+ # Created by {Node#next} or `+` operator.
577
+ #
578
+ # @api private
234
579
  class NextNode < Node
580
+ # @param node [Node] first node in sequence
581
+ # @param after [Node] node to follow
582
+ # @api private
235
583
  def initialize(node, after)
236
584
  @node = node
237
585
  @after = after
238
586
  super()
239
587
  end
240
588
 
589
+ # @api private
241
590
  def _options(parent: nil, &condition)
242
591
  parent ||= []
243
592
 
@@ -251,7 +600,16 @@ module Musa
251
600
  end
252
601
  end
253
602
 
603
+ # Node representing repetition of a node.
604
+ #
605
+ # Generates options with different repetition counts.
606
+ # Created by {Node#repeat}.
607
+ #
608
+ # @api private
254
609
  class RepeatNode < Node
610
+ # @param node [Node] node to repeat
611
+ # @param max [Integer, nil] maximum repetitions (nil = infinite)
612
+ # @api private
255
613
  def initialize(node, max = nil)
256
614
  @node = node
257
615
  @max = max
@@ -259,6 +617,7 @@ module Musa
259
617
  super()
260
618
  end
261
619
 
620
+ # @api private
262
621
  def _options(parent: nil, depth: nil, &condition)
263
622
  parent ||= []
264
623
  depth ||= 0
@@ -1,13 +1,118 @@
1
1
  require_relative '../series'
2
2
 
3
3
  module Musa
4
-
5
- # TODO: adapt to series prototyping
6
-
4
+
5
+
6
+ # Markov chain generator for stochastic sequence generation.
7
+ #
8
+ # Implements Markov chains that generate sequences of states based on
9
+ # probabilistic transition rules. Each state transitions to the next
10
+ # based on defined probabilities, creating pseudo-random but structured
11
+ # sequences.
12
+ #
13
+ # ## Theory
14
+ #
15
+ # A Markov chain is a stochastic model describing a sequence of possible
16
+ # events where the probability of each event depends only on the state
17
+ # attained in the previous event (memoryless property).
18
+ #
19
+ # ## Transition Types
20
+ #
21
+ # - **Array**: Equal probability between all options
22
+ # - `{ a: [:b, :c] }` → 50% chance of :b, 50% chance of :c
23
+ #
24
+ # - **Hash**: Weighted probabilities
25
+ # - `{ a: { b: 0.2, c: 0.8 } }` → 20% :b, 80% :c
26
+ # - Probabilities are normalized (don't need to sum to 1.0)
27
+ #
28
+ # - **Proc**: Algorithmic transitions based on history
29
+ # - `{ a: proc { |history| history.size.even? ? :b : :c } }`
30
+ # - Proc receives full history and returns next state
31
+ #
32
+ # ## Musical Applications
33
+ #
34
+ # - Generate melodic sequences with style-based transitions
35
+ # - Create rhythmic patterns with probabilistic variation
36
+ # - Produce chord progressions with weighted likelihood
37
+ # - Build dynamic musical structures with emergent behavior
38
+ #
39
+ # @example Equal probability transitions
40
+ # markov = Musa::Markov::Markov.new(
41
+ # start: :a,
42
+ # finish: :x,
43
+ # transitions: {
44
+ # a: [:b, :c], # 50/50 chance
45
+ # b: [:a, :c],
46
+ # c: [:a, :b, :x]
47
+ # }
48
+ # ).i
49
+ #
50
+ # markov.to_a # => [:a, :c, :b, :a, :b, :c, :x]
51
+ #
52
+ # @example Weighted probability transitions
53
+ # markov = Musa::Markov::Markov.new(
54
+ # start: :a,
55
+ # finish: :x,
56
+ # transitions: {
57
+ # a: { b: 0.2, c: 0.8 }, # 20% b, 80% c
58
+ # b: { a: 0.3, c: 0.7 }, # 30% a, 70% c
59
+ # c: [:a, :b, :x] # Equal probability
60
+ # }
61
+ # ).i
62
+ #
63
+ # @example Algorithmic transitions with history
64
+ # markov = Musa::Markov::Markov.new(
65
+ # start: :a,
66
+ # finish: :x,
67
+ # transitions: {
68
+ # a: { b: 0.2, c: 0.8 },
69
+ # # Transition based on history length
70
+ # b: proc { |history| history.size.even? ? :a : :c },
71
+ # c: [:a, :b, :x]
72
+ # }
73
+ # ).i
74
+ #
75
+ # @example Musical pitch transitions
76
+ # # Create melodic sequence with style-based transitions
77
+ # melody = Musa::Markov::Markov.new(
78
+ # start: 60, # Middle C
79
+ # finish: nil, # Infinite
80
+ # transitions: {
81
+ # 60 => { 62 => 0.4, 64 => 0.3, 59 => 0.3 }, # C → D/E/B
82
+ # 62 => { 60 => 0.3, 64 => 0.4, 67 => 0.3 }, # D → C/E/G
83
+ # 64 => [60, 62, 65, 67], # E → C/D/F/G
84
+ # # ... more transitions
85
+ # }
86
+ # ).i.max_size(16).to_a
87
+ #
88
+ # @see Musa::Series::Serie Series interface for chaining operations
89
+ # @see Musa::Extension::SmartProcBinder Smart procedure binding for history-based transitions
90
+ # @see https://en.wikipedia.org/wiki/Markov_chain Markov chain (Wikipedia)
91
+ # @see https://en.wikipedia.org/wiki/Stochastic_process Stochastic process (Wikipedia)
92
+ # @see https://en.wikipedia.org/wiki/Markov_chain#Music Markov chains in music (Wikipedia)
7
93
  module Markov
94
+ # Markov chain serie generator.
95
+ #
96
+ # Generates sequences of states following probabilistic transition rules.
97
+ # Implements {Musa::Series::Serie} interface for integration with series operations.
8
98
  class Markov
99
+ # TODO: adapt to series prototyping
9
100
  include Musa::Series::Serie.base
10
101
 
102
+ # Creates Markov chain generator.
103
+ #
104
+ # @param transitions [Hash] state transition rules
105
+ # Keys are states, values are next state definitions (Array, Hash, or Proc)
106
+ # @param start [Object] initial state
107
+ # @param finish [Object, nil] terminal state (nil for infinite)
108
+ # @param random [Random, Integer, nil] random number generator or seed
109
+ #
110
+ # @example
111
+ # markov = Markov.new(
112
+ # transitions: { a: [:b, :c], b: [:a, :c], c: [:a, :b, :x] },
113
+ # start: :a,
114
+ # finish: :x
115
+ # )
11
116
  def initialize(transitions:, start:, finish: nil, random: nil)
12
117
  @transitions = transitions.clone.freeze
13
118
 
@@ -23,17 +128,39 @@ module Musa
23
128
  init
24
129
  end
25
130
 
131
+ # @return [Object] starting state
26
132
  attr_accessor :start
133
+
134
+ # @return [Object, nil] finishing state (nil for infinite)
27
135
  attr_accessor :finish
136
+
137
+ # @return [Random] random number generator
28
138
  attr_accessor :random
139
+
140
+ # @return [Hash] transition rules (frozen)
29
141
  attr_accessor :transitions
30
142
 
143
+ # Initializes serie instance state.
144
+ #
145
+ # @api private
31
146
  private def _init
32
147
  @current = nil
33
148
  @finished = false
34
149
  @history = []
35
150
  end
36
151
 
152
+ # Generates next value in Markov chain.
153
+ #
154
+ # Selects next state based on current state's transition rules.
155
+ # Handles Array (equal probability), Hash (weighted), and Proc (algorithmic)
156
+ # transitions.
157
+ #
158
+ # @return [Object, nil] next state, or nil if finished
159
+ #
160
+ # @raise [RuntimeError] if no transition defined for current state
161
+ # @raise [ArgumentError] if transition type is not Array, Hash, or Proc
162
+ #
163
+ # @api private
37
164
  private def _next_value
38
165
  if @finished
39
166
  @current = nil
@@ -75,6 +202,9 @@ module Musa
75
202
  @current
76
203
  end
77
204
 
205
+ # Checks if Markov chain is infinite.
206
+ #
207
+ # @return [Boolean] true if no finish state defined
78
208
  def infinite?
79
209
  @finish.nil?
80
210
  end