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
@@ -0,0 +1,177 @@
1
+ # Transport - Timing & Clocks
2
+
3
+ Comprehensive timing infrastructure connecting clock sources to the sequencer. The transport system manages musical playback lifecycle, timing synchronization, and position control.
4
+
5
+ **Architecture:**
6
+ ```
7
+ Clock --ticks--> Transport --tick()--> Sequencer --events--> Music
8
+ ```
9
+
10
+ The system provides precise timing control with support for internal timers, MIDI clock synchronization, and manual control for testing and integration.
11
+
12
+ ## Clock - Timing Sources
13
+
14
+ **Clock** is the abstract base class for timing sources. All clocks generate regular ticks that drive the sequencer forward. Multiple clock implementations are available for different use cases.
15
+
16
+ ### Clock Activation Models
17
+
18
+ Clocks use two different activation models:
19
+
20
+ **Automatic Activation** (DummyClock):
21
+ - Begins generating ticks immediately when `transport.start` is called
22
+ - No external activation required
23
+ - Appropriate for testing, batch processing, simulations
24
+
25
+ **External Activation** (TimerClock, InputMidiClock, ExternalTickClock):
26
+ - Requires external signal/control to begin generating ticks
27
+ - `transport.start` blocks waiting for activation
28
+ - Appropriate for live coding, DAW sync, external control
29
+
30
+ ### Available Clock Types
31
+
32
+ **DummyClock** - Simplified clock for testing (automatic activation):
33
+ - Fast playback without real-time constraints
34
+ - Immediately begins generating ticks
35
+ - Useful for test suites or batch generation
36
+ - No external dependencies
37
+
38
+ **TimerClock** - Internal high-precision timer-based clock (external activation):
39
+ - Standalone compositions with internal timing
40
+ - Requires calling `clock.start()` from another thread
41
+ - Configurable BPM (tempo) and ticks per beat
42
+ - Can dynamically change tempo during playback
43
+ - Appropriate for live coding clients
44
+
45
+ **InputMidiClock** - Synchronized to external MIDI Clock messages (external activation):
46
+ - DAW-synchronized playback
47
+ - Waits for MIDI "Start" (0xFA) message to begin ticks
48
+ - Automatically follows external MIDI Clock Start/Stop/Continue
49
+ - Locked to external timing source
50
+
51
+ **ExternalTickClock** - Manually triggered ticks (external activation):
52
+ - Testing and debugging with precise control
53
+ - Integration with external systems (game engines, etc.)
54
+ - Call `clock.tick()` manually to generate each tick
55
+ - Frame-by-frame control
56
+
57
+ ```ruby
58
+ require 'musa-dsl'
59
+
60
+ # TimerClock - Internal timer-based timing
61
+ timer_clock = Musa::Clock::TimerClock.new(
62
+ bpm: 120, # Beats per minute
63
+ ticks_per_beat: 24 # Resolution
64
+ )
65
+
66
+ # InputMidiClock - Synchronized to external MIDI Clock
67
+ require 'midi-communications'
68
+ midi_input = MIDICommunications::Input.gets # Select MIDI input
69
+
70
+ midi_clock = Musa::Clock::InputMidiClock.new(midi_input)
71
+
72
+ # ExternalTickClock - Manual tick control
73
+ external_clock = Musa::Clock::ExternalTickClock.new
74
+
75
+ # DummyClock - For testing (100 ticks)
76
+ dummy_clock = Musa::Clock::DummyClock.new(100)
77
+ ```
78
+
79
+ ## Transport - Playback Lifecycle Manager
80
+
81
+ **Transport** connects a clock to a sequencer and manages the playback lifecycle. It provides methods for starting/stopping playback, seeking to different positions, and registering callbacks for lifecycle events.
82
+
83
+ **Lifecycle phases:**
84
+ 1. **before_begin** - Run once before first start (initialization)
85
+ 2. **on_start** - Run each time transport starts
86
+ 3. **Running** - Clock generates ticks → sequencer processes events
87
+ 4. **on_change_position** - Run when position jumps/seeks
88
+ 5. **after_stop** - Run when transport stops
89
+
90
+ **Key methods:**
91
+ - `start` - Start playback (blocks while running)
92
+ - `stop` - Stop playback
93
+ - `change_position_to(bars: n)` - Seek to position (in bars)
94
+
95
+ ```ruby
96
+ require 'musa-dsl'
97
+
98
+ # Create clock
99
+ clock = Musa::Clock::TimerClock.new(bpm: 120, ticks_per_beat: 24)
100
+
101
+ # Create transport
102
+ transport = Musa::Transport::Transport.new(
103
+ clock,
104
+ 4, # beats_per_bar (time signature numerator)
105
+ 24 # ticks_per_beat (resolution)
106
+ )
107
+
108
+ # Access sequencer through transport
109
+ sequencer = transport.sequencer
110
+
111
+ # Schedule events
112
+ sequencer.at 1 do
113
+ puts "Starting at bar 1!"
114
+ end
115
+
116
+ sequencer.at 4 do
117
+ puts "Reached bar 4"
118
+ transport.stop
119
+ end
120
+
121
+ # Register lifecycle callbacks
122
+ transport.before_begin do
123
+ puts "Initializing (runs once)..."
124
+ end
125
+
126
+ transport.on_start do
127
+ puts "Transport started!"
128
+ end
129
+
130
+ transport.after_stop do
131
+ puts "Transport stopped, cleaning up..."
132
+ end
133
+
134
+ # IMPORTANT: TimerClock requires external activation
135
+ # Start transport in background thread (it will block waiting)
136
+ thread = Thread.new { transport.start }
137
+ sleep 0.1 # Let transport initialize
138
+
139
+ # Activate clock from external control (e.g., live coding client)
140
+ clock.start # NOW ticks begin generating
141
+
142
+ # Wait for completion
143
+ thread.join
144
+
145
+ # Seeking example (in separate context)
146
+ # transport.change_position_to(bars: 2) # Jump to bar 2
147
+ ```
148
+
149
+ **Complete example with MIDI Clock synchronization:**
150
+
151
+ ```ruby
152
+ require 'musa-dsl'
153
+ require 'midi-communications'
154
+
155
+ # Setup MIDI-synchronized clock
156
+ midi_input = MIDICommunications::Input.gets
157
+ clock = Musa::Clock::InputMidiClock.new(midi_input)
158
+
159
+ # Create transport
160
+ transport = Musa::Transport::Transport.new(clock, 4, 24)
161
+
162
+ # Schedule events
163
+ transport.sequencer.at 1 do
164
+ puts "Synchronized start at bar 1!"
165
+ end
166
+
167
+ # Start and wait for MIDI Clock Start message
168
+ transport.start
169
+ ```
170
+
171
+ ## API Reference
172
+
173
+ **Complete API documentation:**
174
+ - [Musa::Transport](https://rubydoc.info/gems/musa-dsl/Musa/Transport) - Playback lifecycle management
175
+ - [Musa::Clock](https://rubydoc.info/gems/musa-dsl/Musa/Clock) - Timing sources and clock implementations
176
+
177
+ **Source code:** `lib/transport/`
@@ -1,6 +1,74 @@
1
+ require_relative 'extension'
2
+
1
3
  module Musa
2
4
  module Extension
5
+ # Refinement that expands Range objects within arrays into their constituent elements.
6
+ #
7
+ # This is particularly useful in musical contexts where arrays may contain both
8
+ # individual values and ranges (like MIDI note ranges or channel ranges), and you
9
+ # need to work with the fully expanded list.
10
+ #
11
+ # ## Use Cases
12
+ #
13
+ # - Expanding MIDI channel specifications: [0, 2..4, 7] → [0, 2, 3, 4, 7]
14
+ # - Expanding note ranges in chord definitions
15
+ # - Processing mixed literal and range values in musical parameters
16
+ # - Any scenario where ranges need to be flattened for iteration
17
+ #
18
+ # @example Basic usage
19
+ # using Musa::Extension::ExplodeRanges
20
+ #
21
+ # [1, 3..5, 8].explode_ranges
22
+ # # => [1, 3, 4, 5, 8]
23
+ #
24
+ # @example MIDI channels
25
+ # using Musa::Extension::ExplodeRanges
26
+ #
27
+ # channels = [0, 2..4, 7, 9..10]
28
+ # channels.explode_ranges
29
+ # # => [0, 2, 3, 4, 7, 9, 10]
30
+ #
31
+ # @example Mixed with other array methods
32
+ # using Musa::Extension::ExplodeRanges
33
+ #
34
+ # [1..3, 5, 7..9].explode_ranges.map { |n| n * 2 }
35
+ # # => [2, 4, 6, 10, 14, 16, 18]
36
+ #
37
+ # @see Musa::MIDIVoices::MIDIVoices#initialize Uses this for channel expansion
38
+ # @note This refinement must be activated with `using Musa::Extension::ExplodeRanges`
39
+ #
40
+ # ## Methods Added
41
+ #
42
+ # ### Array
43
+ # - {Array#explode_ranges} - Expands all Range objects in the array into their individual elements
3
44
  module ExplodeRanges
45
+ # @!method explode_ranges
46
+ # Expands all Range objects in the array into their individual elements.
47
+ #
48
+ # Iterates through the array and converts any Range objects to their
49
+ # constituent elements via `to_a`, leaving non-Range elements unchanged.
50
+ # The result is a new flat array.
51
+ #
52
+ # @note This method is added to Array via refinement. Requires `using Musa::Extension::ExplodeRanges`.
53
+ #
54
+ # @return [Array] new array with all ranges expanded.
55
+ #
56
+ # @example Empty ranges
57
+ # using Musa::Extension::ExplodeRanges
58
+ # [1, (5..4), 8].explode_ranges # (5..4) is empty
59
+ # # => [1, 8]
60
+ #
61
+ # @example Exclusive ranges
62
+ # using Musa::Extension::ExplodeRanges
63
+ # [1, (3...6), 9].explode_ranges
64
+ # # => [1, 3, 4, 5, 9]
65
+ #
66
+ # @example Nested arrays are NOT expanded recursively
67
+ # using Musa::Extension::ExplodeRanges
68
+ # [1, [2..4], 5].explode_ranges
69
+ # # => [1, [2..4], 5] # Inner range NOT expanded
70
+ class ::Array; end
71
+
4
72
  refine Array do
5
73
  def explode_ranges
6
74
  array = []
@@ -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