musa-dsl 0.30.2 → 0.41.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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -1
  3. data/.version +6 -0
  4. data/.yardopts +7 -0
  5. data/Gemfile +0 -1
  6. data/README.md +227 -6
  7. data/docs/README.md +83 -0
  8. data/docs/api-reference.md +86 -0
  9. data/docs/getting-started/quick-start.md +93 -0
  10. data/docs/getting-started/tutorial.md +58 -0
  11. data/docs/subsystems/core-extensions.md +316 -0
  12. data/docs/subsystems/datasets.md +465 -0
  13. data/docs/subsystems/generative.md +290 -0
  14. data/docs/subsystems/matrix.md +63 -0
  15. data/docs/subsystems/midi.md +123 -0
  16. data/docs/subsystems/music.md +544 -0
  17. data/docs/subsystems/musicxml-builder.md +264 -0
  18. data/docs/subsystems/neumas.md +71 -0
  19. data/docs/subsystems/repl.md +135 -0
  20. data/docs/subsystems/sequencer.md +98 -0
  21. data/docs/subsystems/series.md +302 -0
  22. data/docs/subsystems/transcription.md +152 -0
  23. data/docs/subsystems/transport.md +177 -0
  24. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
  25. data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
  26. data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
  27. data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
  28. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
  29. data/lib/musa-dsl/core-ext/extension.rb +53 -0
  30. data/lib/musa-dsl/core-ext/hashify.rb +162 -1
  31. data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
  32. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
  33. data/lib/musa-dsl/core-ext/with.rb +114 -0
  34. data/lib/musa-dsl/datasets/dataset.rb +109 -0
  35. data/lib/musa-dsl/datasets/delta-d.rb +78 -0
  36. data/lib/musa-dsl/datasets/e.rb +186 -2
  37. data/lib/musa-dsl/datasets/gdv.rb +279 -2
  38. data/lib/musa-dsl/datasets/gdvd.rb +201 -0
  39. data/lib/musa-dsl/datasets/helper.rb +75 -0
  40. data/lib/musa-dsl/datasets/p.rb +177 -2
  41. data/lib/musa-dsl/datasets/packed-v.rb +91 -0
  42. data/lib/musa-dsl/datasets/pdv.rb +136 -1
  43. data/lib/musa-dsl/datasets/ps.rb +134 -4
  44. data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
  45. data/lib/musa-dsl/datasets/score/render.rb +105 -1
  46. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
  47. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
  48. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
  49. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
  50. data/lib/musa-dsl/datasets/score.rb +279 -0
  51. data/lib/musa-dsl/datasets/v.rb +88 -0
  52. data/lib/musa-dsl/generative/darwin.rb +215 -1
  53. data/lib/musa-dsl/generative/generative-grammar.rb +387 -0
  54. data/lib/musa-dsl/generative/markov.rb +135 -3
  55. data/lib/musa-dsl/generative/rules.rb +312 -4
  56. data/lib/musa-dsl/generative/variatio.rb +286 -2
  57. data/lib/musa-dsl/logger/logger.rb +267 -2
  58. data/lib/musa-dsl/matrix/matrix.rb +256 -10
  59. data/lib/musa-dsl/midi/midi-recorder.rb +113 -2
  60. data/lib/musa-dsl/midi/midi-voices.rb +275 -4
  61. data/lib/musa-dsl/music/chord-definition.rb +233 -1
  62. data/lib/musa-dsl/music/chord-definitions.rb +33 -6
  63. data/lib/musa-dsl/music/chords.rb +353 -2
  64. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -206
  65. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
  66. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
  67. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
  68. data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
  69. data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
  70. data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
  71. data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
  72. data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
  73. data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
  74. data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
  75. data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
  76. data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
  77. data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
  78. data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
  79. data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
  80. data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
  81. data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
  82. data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
  83. data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
  84. data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
  85. data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
  86. data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
  87. data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
  88. data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
  89. data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
  90. data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
  91. data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
  92. data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
  93. data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
  94. data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
  95. data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
  96. data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
  97. data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
  98. data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
  99. data/lib/musa-dsl/music/scales.rb +1384 -40
  100. data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
  101. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
  102. data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
  103. data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
  104. data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
  105. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
  106. data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
  107. data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
  108. data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
  109. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
  110. data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
  111. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
  112. data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
  113. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
  114. data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
  115. data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
  116. data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
  117. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
  118. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
  119. data/lib/musa-dsl/neumas/neumas.rb +67 -0
  120. data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
  121. data/lib/musa-dsl/repl/repl.rb +550 -0
  122. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
  123. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
  124. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
  125. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
  126. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
  127. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
  128. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
  129. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
  130. data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
  131. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
  132. data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
  133. data/lib/musa-dsl/series/array-to-serie.rb +37 -1
  134. data/lib/musa-dsl/series/base-series.rb +843 -5
  135. data/lib/musa-dsl/series/buffer-serie.rb +54 -0
  136. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +64 -0
  137. data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
  138. data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
  139. data/lib/musa-dsl/series/proxy-serie.rb +67 -0
  140. data/lib/musa-dsl/series/quantizer-serie.rb +57 -7
  141. data/lib/musa-dsl/series/queue-serie.rb +78 -0
  142. data/lib/musa-dsl/series/series-composer.rb +701 -0
  143. data/lib/musa-dsl/series/timed-serie.rb +473 -28
  144. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
  145. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
  146. data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
  147. data/lib/musa-dsl/transcription/transcription.rb +265 -0
  148. data/lib/musa-dsl/transport/clock.rb +125 -0
  149. data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
  150. data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
  151. data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
  152. data/lib/musa-dsl/transport/timer-clock.rb +183 -1
  153. data/lib/musa-dsl/transport/timer.rb +83 -0
  154. data/lib/musa-dsl/transport/transport.rb +318 -0
  155. data/lib/musa-dsl/version.rb +2 -1
  156. data/lib/musa-dsl.rb +132 -25
  157. data/musa-dsl.gemspec +25 -18
  158. metadata +158 -16
@@ -1,8 +1,87 @@
1
+ require_relative 'extension'
1
2
  require_relative 'deep-copy'
2
3
 
3
4
  module Musa
4
5
  module Extension
6
+ # Refinement that converts any object to an array, with optional repetition and defaults.
7
+ #
8
+ # This refinement is essential for normalizing parameters in the DSL, allowing users
9
+ # to provide either single values or arrays and have them processed uniformly.
10
+ #
11
+ # ## Core Behavior
12
+ #
13
+ # - **Object**: Wraps in array; nil becomes []
14
+ # - **Array**: Returns clone or cycles to requested size
15
+ # - **size parameter**: Repeats/cycles to achieve target length
16
+ # - **default parameter**: Replaces nil values
17
+ #
18
+ # ## Use Cases
19
+ #
20
+ # - Normalizing velocity parameters (single value or per-note array)
21
+ # - Ensuring consistent array handling in DSL methods
22
+ # - Cycling patterns to fill required lengths
23
+ # - Providing default values for missing data
24
+ #
25
+ # @example Basic object wrapping
26
+ # using Musa::Extension::Arrayfy
27
+ #
28
+ # 5.arrayfy # => [5]
29
+ # nil.arrayfy # => []
30
+ # [1, 2, 3].arrayfy # => [1, 2, 3]
31
+ #
32
+ # @example Repetition with size
33
+ # using Musa::Extension::Arrayfy
34
+ #
35
+ # 5.arrayfy(size: 3) # => [5, 5, 5]
36
+ # [1, 2].arrayfy(size: 5) # => [1, 2, 1, 2, 1]
37
+ # [1, 2, 3].arrayfy(size: 2) # => [1, 2]
38
+ #
39
+ # @example Default values for nil
40
+ # using Musa::Extension::Arrayfy
41
+ #
42
+ # nil.arrayfy(size: 3, default: 0) # => [0, 0, 0]
43
+ # [1, nil, 3].arrayfy(size: 5, default: -1) # => [1, -1, 3, 1, -1]
44
+ #
45
+ # @example Musical application - velocity normalization
46
+ # using Musa::Extension::Arrayfy
47
+ #
48
+ # # User provides single velocity for chord
49
+ # velocities = 90.arrayfy(size: 3) # => [90, 90, 90]
50
+ #
51
+ # # User provides array of velocities that cycles
52
+ # velocities = [80, 100].arrayfy(size: 5) # => [80, 100, 80, 100, 80]
53
+ #
54
+ # @see Musa::MIDIVoices::MIDIVoice#note Uses arrayfy for velocity normalization
55
+ # @note This refinement must be activated with `using Musa::Extension::Arrayfy`
56
+ # @note Arrays are cloned and singleton class modules are preserved
57
+ #
58
+ # ## Methods Added
59
+ #
60
+ # ### Object
61
+ # - {Object#arrayfy} - Converts any object into an array, optionally repeated to a target size
62
+ #
63
+ # ### Array
64
+ # - {Array#arrayfy} - Clones or cycles the array to achieve the target size, with nil replacement
5
65
  module Arrayfy
66
+ # @!method arrayfy(size: nil, default: nil)
67
+ # Converts any object into an array, optionally repeated to a target size.
68
+ #
69
+ # @note This method is added to Object via refinement. Requires `using Musa::Extension::Arrayfy`.
70
+ #
71
+ # @param size [Integer, nil] target array length. If nil, returns single-element array.
72
+ # @param default [Object, nil] value to use instead of nil.
73
+ #
74
+ # @return [Array] single element repeated size times, or wrapped in array if size is nil.
75
+ #
76
+ # @example With size
77
+ # using Musa::Extension::Arrayfy
78
+ # "hello".arrayfy(size: 3) # => ["hello", "hello", "hello"]
79
+ #
80
+ # @example Nil handling
81
+ # using Musa::Extension::Arrayfy
82
+ # nil.arrayfy(size: 2, default: :empty) # => [:empty, :empty]
83
+ class ::Object; end
84
+
6
85
  refine Object do
7
86
  def arrayfy(size: nil, default: nil)
8
87
  if size
@@ -17,6 +96,37 @@ module Musa
17
96
 
18
97
  # TODO add a refinement for Hash? Should receive a list parameter with the ordered keys
19
98
 
99
+ # @!method arrayfy(size: nil, default: nil)
100
+ # Clones or cycles the array to achieve the target size, with nil replacement.
101
+ #
102
+ # The cycling behavior multiplies the array enough times to reach or exceed
103
+ # the target size, then takes exactly the requested number of elements.
104
+ # Singleton class modules (like P, V dataset extensions) are preserved.
105
+ #
106
+ # @note This method is added to Array via refinement. Requires `using Musa::Extension::Arrayfy`.
107
+ #
108
+ # @param size [Integer, nil] target length. If nil, returns clone of array.
109
+ # @param default [Object, nil] value to replace nil elements with.
110
+ #
111
+ # @return [Array] processed array of exactly the requested size.
112
+ #
113
+ # @example Cycling shorter array
114
+ # using Musa::Extension::Arrayfy
115
+ # [1, 2].arrayfy(size: 5) # => [1, 2, 1, 2, 1]
116
+ #
117
+ # @example Truncating longer array
118
+ # using Musa::Extension::Arrayfy
119
+ # [1, 2, 3, 4, 5].arrayfy(size: 3) # => [1, 2, 3]
120
+ #
121
+ # @example Preserving dataset modules
122
+ # using Musa::Extension::Arrayfy
123
+ # p_sequence = [60, 1, 62].extend(Musa::Datasets::P)
124
+ # p_sequence.arrayfy(size: 6) # Result also extended with P
125
+ #
126
+ # @note The cycling formula: array * (size / array.size + (size % array.size).zero? ? 0 : 1)
127
+ # ensures enough repetitions to reach target size.
128
+ class ::Array; end
129
+
20
130
  refine Array do
21
131
  def arrayfy(size: nil, default: nil)
22
132
  if size
@@ -1,10 +1,52 @@
1
+ require_relative 'extension'
2
+
1
3
  module Musa
2
4
  module Extension
5
+ # Module providing metaprogramming methods for creating DSL builder patterns.
6
+ #
7
+ # AttributeBuilder defines class methods that generate instance methods for
8
+ # creating and managing collections of objects in a DSL-friendly way. It's
9
+ # heavily used throughout Musa DSL to create fluent, expressive APIs.
10
+ #
11
+ # ## Method Categories
12
+ #
13
+ # - **Adders to Hash**: Create methods that build hash-based collections
14
+ # - **Adders to Array**: Create methods that build array-based collections
15
+ # - **Builders**: Create single-object getter/setter DSL methods
16
+ #
17
+ # ## Naming Conventions
18
+ #
19
+ # - `add_item` / `item`: singular form adds one object
20
+ # - `items`: plural form adds multiple or retrieves collection
21
+ # - Automatic pluralization (item → items) unless specified
22
+ #
23
+ # @example Using in a class
24
+ # class Score
25
+ # extend Musa::Extension::AttributeBuilder
26
+ #
27
+ # def initialize
28
+ # @tracks = {}
29
+ # end
30
+ #
31
+ # attr_tuple_adder_to_hash :track, Track
32
+ # end
33
+ #
34
+ # score = Score.new
35
+ # score.add_track :piano, params
36
+ # score.tracks # => { piano: Track(...) }
37
+ #
38
+ # @see Musa::Datasets Score classes use these extensively
3
39
  module AttributeBuilder
4
- # add_thing id, parameter
5
- # things id1: parameter1, id2: parameter2 -> { id1: Thing(id1, parameter1), id2: Thing(id2, parameter2) }
6
- # things -> { id1: Thing(id1, parameter1), id2: Thing(id2, parameter2) }
40
+ # Creates methods for adding id/value tuples to a hash collection.
41
+ #
42
+ # Generates:
43
+ # - `add_#{name}(id, parameter)` → creates instance and adds to hash
44
+ # - `#{plural}(**parameters)` → batch add or retrieve hash
7
45
  #
46
+ # @param name [Symbol] singular name for the item.
47
+ # @param klass [Class] class to instantiate (receives id, parameter).
48
+ # @param plural [Symbol, nil] plural name (defaults to name + 's').
49
+ # @param variable [Symbol, nil] instance variable name (defaults to '@' + plural).
8
50
  def attr_tuple_adder_to_hash(name, klass, plural: nil, variable: nil)
9
51
 
10
52
  plural ||= name.to_s + 's'
@@ -26,10 +68,15 @@ module Musa
26
68
  end
27
69
  end
28
70
 
29
- # add_thing id, parameter
30
- # things id1: parameter1, id2: parameter2 -> [ Thing(id1, parameter1), Thing(id2, parameter2) ]
31
- # things -> [ Thing(id1, parameter1), Thing(id2, parameter2) ]
32
-
71
+ # Creates methods for adding id/value tuples to an array collection.
72
+ #
73
+ # Similar to attr_tuple_adder_to_hash but stores items in an array instead of hash.
74
+ # Useful when order matters or duplicates are allowed.
75
+ #
76
+ # @param name [Symbol] singular name for the item.
77
+ # @param klass [Class] class to instantiate.
78
+ # @param plural [Symbol, nil] plural name.
79
+ # @param variable [Symbol, nil] instance variable name.
33
80
  def attr_tuple_adder_to_array(name, klass, plural: nil, variable: nil)
34
81
 
35
82
  plural ||= name.to_s + 's'
@@ -51,10 +98,14 @@ module Musa
51
98
  end
52
99
  end
53
100
 
54
- # add_thing param1, param2, key1: parameter1, key2: parameter2 -> Thing(...)
55
- # thing param1, param2, key1: parameter1, key2: parameter2 -> Thing(...)
56
- # things -> (collection)
57
-
101
+ # Creates methods for adding complex objects (with multiple parameters) to an array.
102
+ #
103
+ # Supports both positional and keyword arguments when creating instances.
104
+ #
105
+ # @param name [Symbol] singular name.
106
+ # @param klass [Class] class to instantiate.
107
+ # @param plural [Symbol, nil] plural name.
108
+ # @param variable [Symbol, nil] instance variable name.
58
109
  def attr_complex_adder_to_array(name, klass, plural: nil, variable: nil)
59
110
 
60
111
  plural ||= name.to_s + 's'
@@ -86,10 +137,14 @@ module Musa
86
137
  end
87
138
 
88
139
 
89
- # add_thing param1, param2, key1: parameter1, key2: parameter2 -> Thing(...)
90
- # thing param1, param2, key1: parameter1, key2: parameter2 -> Thing(...)
91
- # things -> (collection)
92
-
140
+ # Creates methods for adding complex objects with custom construction logic.
141
+ #
142
+ # The block receives parameters and should construct and add the object.
143
+ #
144
+ # @param name [Symbol] singular name.
145
+ # @param plural [Symbol, nil] plural name.
146
+ # @param variable [Symbol, nil] instance variable name.
147
+ # @yield Constructor block executed in instance context.
93
148
  def attr_complex_adder_to_custom(name, plural: nil, variable: nil, &constructor_and_adder)
94
149
 
95
150
  plural ||= name.to_s + 's'
@@ -121,9 +176,11 @@ module Musa
121
176
  end
122
177
  end
123
178
 
124
- # thing value -> crea Thing(value)
125
- # thing -> Thing(value)
126
-
179
+ # Creates a simple getter/setter DSL method for a single value.
180
+ #
181
+ # @param name [Symbol] attribute name.
182
+ # @param klass [Class, nil] class to instantiate (nil = use value as-is).
183
+ # @param variable [Symbol, nil] instance variable name.
127
184
  def attr_simple_builder(name, klass = nil, variable: nil)
128
185
  variable ||= ('@' + name.to_s).to_sym
129
186
 
@@ -140,34 +197,38 @@ module Musa
140
197
  attr_writer name
141
198
  end
142
199
 
143
- # thing id: value -> crea Thing(id, value)
144
- # thing -> Thing(id, value)
145
-
200
+ # Creates a getter/setter DSL method for a single id/value tuple.
201
+ #
202
+ # @param name [Symbol] attribute name.
203
+ # @param klass [Class] class to instantiate.
204
+ # @param variable [Symbol, nil] instance variable name.
146
205
  def attr_tuple_builder(name, klass, variable: nil)
147
206
  variable ||= ('@' + name.to_s).to_sym
148
207
 
149
208
  define_method name do |**parameters, &block|
150
- raise ArgumentError, "Method #{name} can only create instances with one id: value arguments pattern" unless parameters.size == 1
151
-
152
209
  if parameters.empty?
153
210
  instance_variable_get variable
154
- else
211
+ elsif parameters.size == 1
155
212
  parameter = parameters.first
156
213
  klass.new(*parameter, &block).tap do |object|
157
214
  instance_variable_set variable, object
158
215
  end
216
+ else
217
+ raise ArgumentError, "Method #{name} can only create instances with one id: value arguments pattern"
159
218
  end
160
219
  end
161
220
 
162
221
  attr_writer name
163
222
  end
164
223
 
165
- # thing value1, value2, key1: value3, key2: value4 -> crea Thing(value1, value2, key1: value3, key2: value3)
166
- # thing -> Thing(value1, value2, key1: value3, key2: value4)
167
- # y también...
168
- # thing value1, value2, key1: value3, key2: value4 -> crea Thing(first_parameter, value1, value2, key1: value3, key2: value3)
169
- # thing -> Thing(first_parameter, value1, value2, key1: value3, key2: value4)
170
-
224
+ # Creates a getter/setter DSL method for complex objects with multiple parameters.
225
+ #
226
+ # Supports optional first_parameter that's automatically prepended when constructing.
227
+ #
228
+ # @param name [Symbol] attribute name.
229
+ # @param klass [Class] class to instantiate.
230
+ # @param variable [Symbol, nil] instance variable name.
231
+ # @param first_parameter [Object, nil] parameter automatically prepended to constructor.
171
232
  def attr_complex_builder(name, klass, variable: nil, first_parameter: nil)
172
233
  variable ||= ('@' + name.to_s).to_sym
173
234
 
@@ -1,12 +1,64 @@
1
- # Based on https://github.com/adamluzsi/duplicate.rb/blob/master/lib/duplicate.rb
2
- # Modifications by Javier Sánchez Yeste
1
+ require_relative 'extension'
3
2
 
4
3
  module Musa
5
4
  module Extension
5
+ # Module providing deep copy functionality for complex object graphs.
6
+ #
7
+ # DeepCopy implements recursive copying of objects, handling circular references,
8
+ # instance variables, singleton class modules, and various Ruby data structures.
9
+ #
10
+ # ## Features
11
+ #
12
+ # - Handles circular references via object registry
13
+ # - Preserves singleton class modules (dataset extensions)
14
+ # - Supports both :dup and :clone methods
15
+ # - Special handling for Arrays, Hashes, Ranges, Structs, Procs
16
+ # - Recursively copies instance variables
17
+ # - Optional freeze control for :clone method
18
+ #
19
+ # ## Use Cases
20
+ #
21
+ # - Deep copying musical event structures with complex nesting
22
+ # - Duplicating series configurations without shared state
23
+ # - Preserving dataset module extensions during copy operations
24
+ # - Safe duplication of mutable default values
25
+ #
26
+ # @example Basic deep copy
27
+ # using Musa::Extension::DeepCopy
28
+ #
29
+ # original = { items: [1, 2, 3] }
30
+ # copy = original.dup(deep: true)
31
+ # copy[:items] << 4
32
+ # original[:items] # => [1, 2, 3] (unchanged)
33
+ #
34
+ # @example Preserving modules
35
+ # event = { pitch: 60 }.extend(Musa::Datasets::AbsI)
36
+ # copy = event.dup(deep: true)
37
+ # copy.is_a?(Musa::Datasets::AbsI) # => true
38
+ #
39
+ # @see Arrayfy Uses deep_copy for preserving modules
40
+ # @see Hashify Uses deep_copy for preserving modules
41
+ #
42
+ # Based on https://github.com/adamluzsi/duplicate.rb/blob/master/lib/duplicate.rb
43
+ #
44
+ # Modifications by Javier Sánchez Yeste
6
45
  module DeepCopy
46
+ # Main deep copy module providing class methods.
7
47
  module DeepCopy
8
48
  extend self
9
49
 
50
+ # Creates a deep copy of an object, recursively copying nested structures.
51
+ #
52
+ # Uses an object registry to handle circular references, ensuring each
53
+ # object is copied only once and all references point to the same copy.
54
+ #
55
+ # @param object [Object] object to copy.
56
+ # @param method [Symbol] :dup or :clone.
57
+ # @param freeze [Boolean, nil] for :clone, whether to freeze the copy.
58
+ #
59
+ # @return [Object] deep copy of the object.
60
+ #
61
+ # @raise [ArgumentError] if method is not :dup or :clone.
10
62
  def deep_copy(object, method: :dup, freeze: nil)
11
63
  raise ArgumentError, "deep_copy method can only be :dup or :clone" unless method == :dup || method == :clone
12
64
  register = {}
@@ -14,6 +66,22 @@ module Musa
14
66
  _deep_copy(register, object, method, freeze)
15
67
  end
16
68
 
69
+ # Copies singleton class modules from source to target.
70
+ #
71
+ # This is essential for preserving dataset extensions (P, V, AbsI, etc.)
72
+ # when copying musical data structures. Without this, copied objects
73
+ # would lose their dataset behaviors.
74
+ #
75
+ # @param source [Object] object whose singleton modules to copy.
76
+ # @param target [Object] object to receive the modules.
77
+ #
78
+ # @return [Object] target with modules applied.
79
+ #
80
+ # @example
81
+ # source = [60, 100].extend(Musa::Datasets::V)
82
+ # target = [60, 100]
83
+ # DeepCopy.copy_singleton_class_modules(source, target)
84
+ # target.is_a?(Musa::Datasets::V) # => true
17
85
  def copy_singleton_class_modules(source, target)
18
86
  source.singleton_class.included_modules.each do |m|
19
87
  target.extend m unless target.is_a?(m)
@@ -24,6 +92,8 @@ module Musa
24
92
 
25
93
  protected
26
94
 
95
+ # Retrieves a previously registered copy from the registry.
96
+ # @api private
27
97
  def registered(object, register)
28
98
  register[object.__id__]
29
99
  end
@@ -173,6 +243,59 @@ module Musa
173
243
  end
174
244
  end
175
245
 
246
+ # Refinement adding deep copy support to Object#dup and Object#clone.
247
+ #
248
+ # Adds a `deep:` keyword parameter to both methods, enabling easy deep copying
249
+ # without explicit calls to DeepCopy.deep_copy.
250
+ #
251
+ # ## Methods Added
252
+ #
253
+ # ### Object
254
+ # - {Object#dup} - Enhanced dup with optional deep copy
255
+ # - {Object#clone} - Enhanced clone with optional deep copy
256
+ #
257
+ # @!method dup(deep: false)
258
+ # Enhanced dup with optional deep copy.
259
+ #
260
+ # @note This method is added to Object via refinement. Requires `using Musa::Extension::DeepCopy`.
261
+ #
262
+ # @param deep [Boolean] if true, performs deep copy; if false, standard dup.
263
+ #
264
+ # @return [Object] duplicated object (shallow or deep).
265
+ #
266
+ # @example Shallow dup (default)
267
+ # using Musa::Extension::DeepCopy
268
+ # arr = [[1, 2]]
269
+ # copy = arr.dup
270
+ # copy[0] << 3
271
+ # arr # => [[1, 2, 3]] (inner array shared)
272
+ #
273
+ # @example Deep dup
274
+ # using Musa::Extension::DeepCopy
275
+ # arr = [[1, 2]]
276
+ # copy = arr.dup(deep: true)
277
+ # copy[0] << 3
278
+ # arr # => [[1, 2]] (inner array independent)
279
+ class ::Object; end
280
+
281
+ # @!method clone(freeze: nil, deep: false)
282
+ # Enhanced clone with optional deep copy.
283
+ #
284
+ # @note This method is added to Object via refinement. Requires `using Musa::Extension::DeepCopy`.
285
+ #
286
+ # @param freeze [Boolean, nil] whether to freeze the clone.
287
+ # @param deep [Boolean] if true, performs deep copy; if false, standard clone.
288
+ #
289
+ # @return [Object] cloned object (shallow or deep).
290
+ #
291
+ # @example Deep clone with freeze control
292
+ # using Musa::Extension::DeepCopy
293
+ # hash = { nested: { value: 1 } }
294
+ # copy = hash.clone(deep: true, freeze: true)
295
+ # copy.frozen? # => true
296
+ # copy[:nested].frozen? # => true (deep freeze)
297
+ class ::Object; end
298
+
176
299
  refine Object do
177
300
  def dup(deep: false)
178
301
  if deep
@@ -1,7 +1,40 @@
1
+ require_relative 'extension'
2
+
1
3
  module Musa
2
4
  module Extension
5
+ # Module providing dynamic proxy pattern implementation.
6
+ #
7
+ # DynamicProxy allows creating objects that forward all method calls to a
8
+ # receiver object, which can be set/changed dynamically. This is useful for
9
+ # lazy initialization, placeholder objects, and delegation patterns.
10
+ #
11
+ # ## Features
12
+ #
13
+ # - Transparent method forwarding via method_missing
14
+ # - Dynamic receiver assignment
15
+ # - Type checking delegation (is_a?, kind_of?, instance_of?)
16
+ # - Equality delegation (==, eql?)
17
+ # - Safe handling when receiver is nil
18
+ #
19
+ # @example Basic usage
20
+ # proxy = Musa::Extension::DynamicProxy::DynamicProxy.new
21
+ # proxy.receiver = "Hello"
22
+ # proxy.upcase # => "HELLO" (forwarded to String)
23
+ #
24
+ # @example Lazy initialization
25
+ # proxy = DynamicProxy.new
26
+ # # ... later ...
27
+ # proxy.receiver = expensive_object
28
+ # proxy.some_method # Now forwards to expensive_object
3
29
  module DynamicProxy
30
+ # Mixin module providing dynamic proxy behavior.
31
+ #
32
+ # This module can be included in classes to add proxy capabilities.
33
+ # Requires an @receiver instance variable to be set.
4
34
  module DynamicProxyModule
35
+ # Forwards unknown methods to the receiver.
36
+ #
37
+ # @raise [NoMethodError] if @receiver is nil or doesn't respond to method.
5
38
  def method_missing(method_name, *args, **key_args, &block)
6
39
  raise NoMethodError, "Method '#{method_name}' is unknown because self is a DynamicProxy with undefined receiver" unless @receiver
7
40
 
@@ -12,52 +45,97 @@ module Musa
12
45
  end
13
46
  end
14
47
 
48
+ # Declares which methods the proxy responds to.
49
+ #
50
+ # @return [Boolean] true if receiver responds to method, false otherwise.
15
51
  def respond_to_missing?(method_name, include_private)
16
52
  @receiver&.respond_to?(method_name, include_private) || super
17
53
  end
18
54
 
55
+ # Checks if the proxy has a receiver assigned.
56
+ #
57
+ # @return [Boolean] true if @receiver is not nil.
19
58
  def has_receiver?
20
59
  !@receiver.nil?
21
60
  end
22
61
 
62
+ # Preserve original is_a? for internal use
23
63
  alias _is_a? is_a?
24
64
 
65
+ # Delegates is_a? check to receiver or uses original.
66
+ #
67
+ # @param klass [Class] class to check against.
68
+ # @return [Boolean] true if proxy or receiver is instance of klass.
25
69
  def is_a?(klass)
26
70
  _is_a?(klass) || @receiver&.is_a?(klass)
27
71
  end
28
72
 
73
+ # Preserve original kind_of? for internal use
29
74
  alias _kind_of? kind_of?
30
75
 
76
+ # Delegates kind_of? check to receiver or uses original.
77
+ #
78
+ # @param klass [Class] class to check against.
79
+ # @return [Boolean] true if proxy or receiver is kind of klass.
31
80
  def kind_of?(klass)
32
81
  _kind_of?(klass) || @receiver&.is_a?(klass)
33
82
  end
34
83
 
84
+ # Preserve original instance_of? for internal use
35
85
  alias _instance_of? instance_of?
36
86
 
87
+ # Delegates instance_of? check to receiver or uses original.
88
+ #
89
+ # @param klass [Class] class to check against.
90
+ # @return [Boolean] true if proxy or receiver is instance of klass.
37
91
  def instance_of?(klass)
38
92
  _instance_of?(klass) || @receiver&.instance_of?(klass)
39
93
  end
40
94
 
95
+ # Preserve original == for internal use
41
96
  alias _equalequal ==
42
97
 
98
+ # Delegates equality check to receiver or uses original.
99
+ #
100
+ # @param object [Object] object to compare with.
101
+ # @return [Boolean] true if proxy or receiver equals object.
43
102
  def ==(object)
44
103
  _equalequal(object) || @receiver&.==(object)
45
104
  end
46
105
 
106
+ # Preserve original eql? for internal use
47
107
  alias _eql? eql?
48
108
 
109
+ # Delegates eql? check to receiver or uses original.
110
+ #
111
+ # @param object [Object] object to compare with.
112
+ # @return [Boolean] true if proxy or receiver eql? object.
49
113
  def eql?(object)
50
114
  _eql?(object) || @receiver&.eql?(object)
51
115
  end
52
116
  end
53
117
 
118
+ # Concrete DynamicProxy class ready for instantiation.
119
+ #
120
+ # @example
121
+ # proxy = DynamicProxy.new
122
+ # proxy.receiver = [1, 2, 3]
123
+ # proxy.size # => 3
124
+ # proxy.first # => 1
125
+ # proxy.is_a?(Array) # => true
54
126
  class DynamicProxy
55
127
  include DynamicProxyModule
56
128
 
129
+ # Creates a new dynamic proxy.
130
+ #
131
+ # @param receiver [Object, nil] optional initial receiver object.
57
132
  def initialize(receiver = nil)
58
133
  @receiver = receiver
59
134
  end
60
135
 
136
+ # The object to which methods are delegated.
137
+ #
138
+ # @return [Object, nil] current receiver.
61
139
  attr_accessor :receiver
62
140
  end
63
141
  end
@@ -0,0 +1,53 @@
1
+ module Musa
2
+ # Namespace for core Ruby extensions and utilities used throughout Musa DSL.
3
+ #
4
+ # This module contains fundamental extensions and helper modules that enhance
5
+ # Ruby's capabilities for DSL construction and flexible block handling.
6
+ #
7
+ # ## Included Modules
8
+ #
9
+ # - {With} - Flexible DSL block execution with context switching
10
+ # - {SmartProcBinder::SmartProcBinder} - Intelligent parameter binding for Procs
11
+ # - {AttributeBuilder} - DSL-style attribute builder macros
12
+ # - {DynamicProxy::DynamicProxy} - Dynamic method proxying
13
+ #
14
+ # ## Purpose
15
+ #
16
+ # These extensions enable Musa DSL's characteristic features:
17
+ # - Builder pattern with flexible context switching
18
+ # - Method-style and block-style parameter passing
19
+ # - Dynamic method generation for DSL syntax
20
+ # - Intelligent parameter matching and binding
21
+ #
22
+ # ## Design Philosophy
23
+ #
24
+ # The extensions in this module prioritize:
25
+ # - **Flexibility**: Supporting multiple calling conventions
26
+ # - **Transparency**: Minimal interference with normal Ruby behavior
27
+ # - **Reusability**: General-purpose tools used across Musa DSL
28
+ #
29
+ # @example Using With for DSL blocks
30
+ # class Builder
31
+ # include Musa::Extension::With
32
+ #
33
+ # def initialize(&block)
34
+ # @items = []
35
+ # with(&block) if block
36
+ # end
37
+ #
38
+ # def add(item)
39
+ # @items << item
40
+ # end
41
+ # end
42
+ #
43
+ # builder = Builder.new do
44
+ # add :foo # Executes in Builder's context
45
+ # end
46
+ #
47
+ # @see With The core DSL block execution module
48
+ # @see SmartProcBinder Intelligent Proc parameter handling
49
+ # @see AttributeBuilder DSL attribute builder macros
50
+ # @see DynamicProxy Dynamic method proxying implementation
51
+ module Extension
52
+ end
53
+ end