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,6 +1,89 @@
1
+ require_relative 'extension'
2
+
1
3
  module Musa
2
4
  module Extension
5
+ # Refinements that provide more readable inspect/to_s output for Hash and Rational.
6
+ #
7
+ # These refinements improve readability of log output and debugging, especially
8
+ # important when working with musical data that heavily uses Rationals for timing
9
+ # and Hashes for event parameters.
10
+ #
11
+ # ## Changes
12
+ #
13
+ # - **Hash**: Compact syntax with symbol keys shown as `key: value`
14
+ # - **Rational**: Musical-friendly format like `3+1/4r` instead of `(13/4)`
15
+ # - **Configurable**: Rational display can switch between simple and detailed modes
16
+ #
17
+ # ## Use Cases
18
+ #
19
+ # - Improving log readability in musical applications
20
+ # - Debugging DSL expressions with cleaner output
21
+ # - Displaying musical time values (bars, durations) naturally
22
+ #
23
+ # @example Hash formatting
24
+ # using Musa::Extension::InspectNice
25
+ #
26
+ # { pitch: 60, velocity: 100 }.inspect
27
+ # # => "{ pitch: 60, velocity: 100 }"
28
+ # # Instead of: "{:pitch=>60, :velocity=>100}"
29
+ #
30
+ # @example Rational formatting (detailed mode)
31
+ # using Musa::Extension::InspectNice
32
+ #
33
+ # (5/4r).inspect # => "1+1/4r"
34
+ # (3/2r).inspect # => "1+1/2r"
35
+ # (2/1r).inspect # => "2r"
36
+ # (-3/4r).inspect # => "-3/4r"
37
+ #
38
+ # @example Rational formatting (simple mode)
39
+ # using Musa::Extension::InspectNice
40
+ #
41
+ # Rational.to_s_as_inspect = false
42
+ # (5/4r).to_s # => "5/4"
43
+ # (2/1r).to_s # => "2"
44
+ #
45
+ # @see Musa::Logger::Logger Uses these refinements for cleaner logs
46
+ # @note These refinements must be activated with `using Musa::Extension::InspectNice`
47
+ #
48
+ # ## Methods Added
49
+ #
50
+ # ### Hash
51
+ # - {Hash#inspect} - Compact, readable inspect output with symbol-key shorthand
52
+ # - {Hash#to_s} - Aliases to_s to inspect for consistency
53
+ #
54
+ # ### Rational (singleton class)
55
+ # - {Rational.to_s_as_inspect} - Controls whether Rational#to_s uses inspect format
56
+ #
57
+ # ### Rational
58
+ # - {Rational#inspect} - Musical-friendly inspect output for Rational numbers
59
+ # - {Rational#to_s} - String representation controlled by Rational.to_s_as_inspect
3
60
  module InspectNice
61
+ # @!method inspect
62
+ # Provides compact, readable inspect output with symbol-key shorthand.
63
+ #
64
+ # Symbol keys are displayed as `key: value` (Ruby 2.0+ syntax) instead of
65
+ # `:key => value`. String/other keys use the fat arrow syntax.
66
+ #
67
+ # @note This method is added to Hash via refinement. Requires `using Musa::Extension::InspectNice`.
68
+ #
69
+ # @return [String] compact hash representation.
70
+ #
71
+ # @example Mixed keys
72
+ # using Musa::Extension::InspectNice
73
+ # { pitch: 60, 'name' => 'C4' }.inspect
74
+ # # => "{ pitch: 60, 'name' => 'C4' }"
75
+ class ::Hash; end
76
+
77
+ # @!method to_s
78
+ # Aliases to_s to inspect for consistency.
79
+ #
80
+ # @note This method is added to Hash via refinement. Requires `using Musa::Extension::InspectNice`.
81
+ #
82
+ # @return [String] compact hash representation.
83
+ #
84
+ # @see Hash#inspect
85
+ class ::Hash; end
86
+
4
87
  refine Hash do
5
88
  def inspect
6
89
  all = collect { |key, value| [', ', key.is_a?(Symbol) ? key.to_s + ': ' : key.inspect + ' => ', value.inspect] }.flatten
@@ -12,10 +95,81 @@ module Musa
12
95
  alias to_s inspect
13
96
  end
14
97
 
98
+ # Adds configuration attribute to Rational singleton class.
99
+ #
100
+ # This allows global control of Rational#to_s behavior.
101
+ #
102
+ # @!attribute [rw] to_s_as_inspect
103
+ # Controls whether Rational#to_s uses inspect format.
104
+ #
105
+ # When true: to_s displays detailed format (e.g., "1+1/4r")
106
+ # When false/nil: to_s displays simple format (e.g., "5/4")
107
+ #
108
+ # @note This attribute is added to Rational's singleton class via refinement. Requires `using Musa::Extension::InspectNice`.
109
+ #
110
+ # @return [Boolean, nil] current mode
111
+ #
112
+ # @example Switching modes
113
+ # using Musa::Extension::InspectNice
114
+ # Rational.to_s_as_inspect = true
115
+ # (5/4r).to_s # => "1+1/4r"
116
+ class ::Rational; end
117
+
15
118
  refine Rational.singleton_class do
16
119
  attr_accessor :to_s_as_inspect
17
120
  end
18
121
 
122
+ # @!method inspect(simple: nil)
123
+ # Provides musical-friendly inspect output for Rational numbers.
124
+ #
125
+ # Two modes:
126
+ # - **Simple**: Just numerator/denominator (e.g., "5/4", "2")
127
+ # - **Detailed**: Mixed number with 'r' suffix (e.g., "1+1/4r", "2r")
128
+ #
129
+ # The detailed format is particularly useful for musical time values,
130
+ # making expressions like "3+1/2r" (3.5 bars) immediately readable.
131
+ #
132
+ # @note This method is added to Rational via refinement. Requires `using Musa::Extension::InspectNice`.
133
+ #
134
+ # @param simple [Boolean, nil] if true, uses simple format; if false/nil, uses detailed.
135
+ #
136
+ # @return [String] formatted rational.
137
+ #
138
+ # @example Detailed format (default for inspect)
139
+ # using Musa::Extension::InspectNice
140
+ # (5/4r).inspect # => "1+1/4r"
141
+ # (7/4r).inspect # => "1+3/4r"
142
+ # (-3/2r).inspect # => "-1-1/2r"
143
+ # (8/4r).inspect # => "2r"
144
+ # (3/4r).inspect # => "3/4r"
145
+ #
146
+ # @example Simple format
147
+ # using Musa::Extension::InspectNice
148
+ # (5/4r).inspect(simple: true) # => "5/4"
149
+ # (8/4r).inspect(simple: true) # => "2"
150
+ class ::Rational; end
151
+
152
+ # @!method to_s
153
+ # Provides string representation, format controlled by Rational.to_s_as_inspect.
154
+ #
155
+ # Delegates to #inspect with the appropriate simple flag based on the
156
+ # global Rational.to_s_as_inspect setting.
157
+ #
158
+ # @note This method is added to Rational via refinement. Requires `using Musa::Extension::InspectNice`.
159
+ #
160
+ # @return [String] formatted rational.
161
+ #
162
+ # @example When to_s_as_inspect is true
163
+ # using Musa::Extension::InspectNice
164
+ # Rational.to_s_as_inspect = true
165
+ # (5/4r).to_s # => "1+1/4r"
166
+ #
167
+ # @example When to_s_as_inspect is false/nil
168
+ # using Musa::Extension::InspectNice
169
+ # Rational.to_s_as_inspect = false
170
+ # (5/4r).to_s # => "5/4"
171
+ class ::Rational; end
172
+
19
173
  refine Rational do
20
174
  def inspect(simple: nil)
21
175
  value = self.abs
@@ -1,17 +1,75 @@
1
+ require_relative 'extension'
2
+
1
3
  module Musa
2
4
  module Extension
5
+ # Module providing smart parameter binding for Proc objects.
6
+ #
7
+ # SmartProcBinder analyzes a Proc's parameter signature and intelligently
8
+ # matches provided arguments to expected parameters, handling both positional
9
+ # and keyword arguments with proper rest parameter support.
10
+ #
11
+ # @see Musa::Extension::With Uses SmartProcBinder for parameter management
3
12
  module SmartProcBinder
13
+ # Wrapper for Proc objects that provides intelligent parameter matching and binding.
14
+ #
15
+ # This class introspects a Proc's parameter list and provides methods to:
16
+ # - Determine which parameters the Proc accepts
17
+ # - Filter provided arguments to match the Proc's signature
18
+ # - Call the Proc with properly matched arguments
19
+ # - Optionally rescue and handle exceptions
20
+ #
21
+ # ## Parameter Types Handled
22
+ #
23
+ # - **:req, :opt**: Required and optional positional parameters
24
+ # - **:rest**: Splat parameter (*args)
25
+ # - **:key, :keyreq**: Optional and required keyword parameters
26
+ # - **:keyrest**: Double splat parameter (**kwargs)
27
+ #
28
+ # ## Use Cases
29
+ #
30
+ # - DSL methods that need flexible parameter passing
31
+ # - Builder patterns with variable block signatures
32
+ # - Wrapper methods that forward arguments intelligently
33
+ # - Error handling for DSL block execution
34
+ #
35
+ # @example Basic usage
36
+ # block = proc { |a, b, c:| [a, b, c] }
37
+ # binder = SmartProcBinder.new(block)
38
+ #
39
+ # binder.call(1, 2, 3, 4, c: 5, d: 6)
40
+ # # => [1, 2, 5]
41
+ # # Only passes parameters that match signature
42
+ #
43
+ # @example With rescue handling
44
+ # error_handler = proc { |e| puts "Error: #{e.message}" }
45
+ # binder = SmartProcBinder.new(block, on_rescue: error_handler)
46
+ #
47
+ # binder.call(invalid_args) # Calls error_handler instead of raising
48
+ #
49
+ # @example Checking parameter support
50
+ # binder.key?(:pitch) # => true/false
51
+ # binder.has_key?(:velocity) # => true/false
4
52
  class SmartProcBinder
53
+ # Creates a new SmartProcBinder wrapping the given block.
54
+ #
55
+ # Introspects the block's parameters and categorizes them for later matching.
56
+ #
57
+ # @param block [Proc] the proc/block to wrap.
58
+ # @param on_rescue [Proc, nil] optional error handler called with exception
59
+ # if block execution fails.
5
60
  def initialize(block, on_rescue: nil)
6
61
  @block = block
7
62
  @on_rescue = on_rescue
8
63
 
64
+ # Track keyword parameters by name
9
65
  @key_parameters = {}
10
66
  @has_key_rest = false
11
67
 
68
+ # Track positional parameter count
12
69
  @value_parameters_count = 0
13
70
  @has_value_rest = false
14
71
 
72
+ # Introspect block's parameter signature
15
73
  block.parameters.each do |parameter|
16
74
  @key_parameters[parameter[1]] = nil if parameter[0] == :key || parameter[0] == :keyreq
17
75
  @has_key_rest = true if parameter[0] == :keyrest
@@ -21,18 +79,36 @@ module Musa
21
79
  end
22
80
  end
23
81
 
82
+ # Returns the wrapped Proc.
83
+ #
84
+ # @return [Proc] the original block.
24
85
  def proc
25
86
  @block
26
87
  end
27
88
 
89
+ # Returns the parameter signature of the wrapped Proc.
90
+ #
91
+ # @return [Array<Array>] array of [type, name] pairs describing parameters.
92
+ # @example
93
+ # proc { |a, b, c:| }.parameters # => [[:req, :a], [:req, :b], [:key, :c]]
28
94
  def parameters
29
95
  @block.parameters
30
96
  end
31
97
 
98
+ # Calls the wrapped Proc with smart parameter matching.
99
+ #
100
+ # @param value_parameters [Array] positional arguments.
101
+ # @param key_parameters [Hash] keyword arguments.
102
+ # @param block [Proc, nil] block to pass to wrapped Proc.
103
+ #
104
+ # @return [Object] result of calling the wrapped Proc.
32
105
  def call(*value_parameters, **key_parameters, &block)
33
106
  _call value_parameters, key_parameters, block
34
107
  end
35
108
 
109
+ # Internal call implementation with error handling.
110
+ #
111
+ # @api private
36
112
  def _call(value_parameters, key_parameters = {}, block = nil)
37
113
  if @on_rescue
38
114
  begin
@@ -63,16 +139,43 @@ module Musa
63
139
  end
64
140
  end
65
141
 
142
+ # Checks if the wrapped Proc accepts a specific keyword parameter.
143
+ #
144
+ # Returns true if the Proc has a keyword parameter with the given name,
145
+ # or if it has a **kwargs rest parameter that accepts any keyword.
146
+ #
147
+ # @param key [Symbol] keyword parameter name to check.
148
+ #
149
+ # @return [Boolean] true if key is accepted, false otherwise.
150
+ #
151
+ # @example
152
+ # proc { |a:, b:, **rest| }.key?(:a) # => true
153
+ # proc { |a:, b:, **rest| }.key?(:unknown) # => true (has **rest)
154
+ # proc { |a:, b:| }.key?(:unknown) # => false
66
155
  def key?(key)
67
156
  @has_key_rest || @key_parameters.include?(key)
68
157
  end
69
158
 
70
159
  alias_method :has_key?, :key?
71
160
 
161
+ # Filters arguments to match the Proc's signature.
162
+ #
163
+ # @param value_parameters [Array] positional arguments to filter.
164
+ # @param key_parameters [Hash] keyword arguments to filter.
165
+ #
166
+ # @return [Array<Array, Hash>] tuple of [filtered_positionals, filtered_keywords].
72
167
  def apply(*value_parameters, **key_parameters)
73
168
  _apply(value_parameters, key_parameters)
74
169
  end
75
170
 
171
+ # Internal implementation of argument filtering.
172
+ #
173
+ # Logic:
174
+ # - Positional: takes first N values (or all if *rest present)
175
+ # - Keywords: includes only expected keys (or all if **rest present)
176
+ # - Pads positional with nils if needed
177
+ #
178
+ # @api private
76
179
  def _apply(value_parameters, key_parameters)
77
180
  value_parameters ||= []
78
181
  key_parameters ||= {}
@@ -99,6 +202,20 @@ module Musa
99
202
  return values_result, hash_result
100
203
  end
101
204
 
205
+ # Returns a string representation of the SmartProcBinder for debugging.
206
+ #
207
+ # Shows the wrapped Proc's parameter signature and internal state, making
208
+ # it easy to understand what parameters the binder expects and how it will
209
+ # match arguments.
210
+ #
211
+ # @return [String] formatted string showing parameters and configuration.
212
+ #
213
+ # @example Inspecting binder state
214
+ # block = proc { |a, b, c:, **rest| }
215
+ # binder = SmartProcBinder.new(block)
216
+ # puts binder.inspect
217
+ # # => "SmartProcBinder: parameters = [[:req, :a], [:req, :b], [:key, :c], [:keyrest, :rest]]
218
+ # # key_parameters = {:c=>nil} has_rest = true"
102
219
  def inspect
103
220
  "SmartProcBinder: parameters = #{parameters} key_parameters = #{@key_parameters} has_rest = #{@has_key_rest}"
104
221
  end
@@ -1,28 +1,142 @@
1
+ require_relative 'extension'
1
2
  require_relative 'smart-proc-binder'
2
3
 
3
4
  module Musa
4
5
  module Extension
6
+ # Module providing the `with` method for flexible DSL block execution.
7
+ #
8
+ # The `with` method is a cornerstone of Musa DSL's builder pattern, allowing
9
+ # objects to execute blocks in either the object's context (for DSL-style
10
+ # configuration) or the caller's context (for traditional Ruby blocks).
11
+ #
12
+ # ## Context Switching Logic
13
+ #
14
+ # The method intelligently determines which context to use based on:
15
+ # 1. The `keep_block_context` parameter (explicit control)
16
+ # 2. Block parameters (implicit control via `_` parameter)
17
+ # 3. Whether parameters are passed to the block
18
+ #
19
+ # ## Modes of Operation
20
+ #
21
+ # - **DSL mode** (`instance_eval`): Block executes in object's context
22
+ # - Used when: no parameters, no `keep_block_context`, no `_` parameter
23
+ # - Enables: direct access to object's instance variables and methods
24
+ #
25
+ # - **Caller context mode** (`call` with self as `_`): Block keeps its context
26
+ # - Used when: block has `_` parameter, or `keep_block_context: true`
27
+ # - Enables: access to both contexts (object via `_`, caller's scope naturally)
28
+ #
29
+ # - **Hybrid mode** (`instance_exec`): Block in object context with parameters
30
+ # - Used when: parameters provided but `keep_block_context` not set
31
+ # - Enables: DSL-style access plus explicit parameters
32
+ #
33
+ # ## Use Cases
34
+ #
35
+ # - Builder pattern DSL methods
36
+ # - Configuration blocks that need object context
37
+ # - Flexible API supporting both DSL and traditional Ruby styles
38
+ # - Initializers that configure objects via blocks
39
+ #
40
+ # @example DSL mode (instance_eval)
41
+ # class Builder
42
+ # include Musa::Extension::With
43
+ #
44
+ # def initialize(&block)
45
+ # @items = []
46
+ # with(&block) if block
47
+ # end
48
+ #
49
+ # def add(item)
50
+ # @items << item
51
+ # end
52
+ # end
53
+ #
54
+ # builder = Builder.new do
55
+ # add :foo
56
+ # add :bar
57
+ # end
58
+ # # Block has direct access to #add method
59
+ #
60
+ # @example Caller context with _ parameter
61
+ # external_var = 42
62
+ #
63
+ # Builder.new do |_|
64
+ # _.add :foo
65
+ # puts external_var # Can access caller's variables
66
+ # end
67
+ # # Block keeps caller's context, object accessed via _
68
+ #
69
+ # @example With parameters
70
+ # class Builder
71
+ # def initialize(name, &block)
72
+ # @name = name
73
+ # with(name, &block) if block
74
+ # end
75
+ # end
76
+ #
77
+ # Builder.new('test') do |name|
78
+ # # Has access to object's context AND receives name parameter
79
+ # puts @name # Works
80
+ # puts name # Also works
81
+ # end
82
+ #
83
+ # @example Explicit keep_block_context
84
+ # Builder.new do |obj|
85
+ # obj.add :item
86
+ # # Block explicitly keeps caller's context
87
+ # end
88
+ #
89
+ # @see SmartProcBinder Used internally for parameter management
90
+ # @see Musa::Datasets DSL builder methods use this extensively
5
91
  module With
92
+ # Executes a block with flexible context and parameter handling.
93
+ #
94
+ # @param value_parameters [Array] positional parameters to pass to block.
95
+ # @param keep_block_context [Boolean, nil] explicit control of context switching:
96
+ # - `true`: always keep caller's context
97
+ # - `false`: always use object's context
98
+ # - `nil`: auto-detect based on `_` parameter
99
+ # @param key_parameters [Hash] keyword parameters to pass to block.
100
+ # @param block [Proc] block to execute.
101
+ #
102
+ # @return [Object] result of block execution.
103
+ #
104
+ # @note The `_` parameter is special: when present, it signals "keep caller's context"
105
+ # and receives `self` (the object) as its value.
106
+ # @note Uses SmartProcBinder internally to handle parameter matching.
6
107
  def with(*value_parameters, keep_block_context: nil, **key_parameters, &block)
108
+ # Wrap block in SmartProcBinder for parameter introspection and management
7
109
  smart_block = Musa::Extension::SmartProcBinder::SmartProcBinder.new(block)
8
110
 
111
+ # Check if first parameter is _ (underscore), which signals "keep caller's context"
9
112
  send_self_as_underscore_parameter = smart_block.parameters[0][1] == :_ unless smart_block.parameters.empty?
10
113
 
114
+ # Determine effective context mode:
115
+ # 1. Use explicit keep_block_context if provided
116
+ # 2. Otherwise, use _ parameter presence as signal
117
+ # 3. Default to false (use object's context)
11
118
  effective_keep_block_context = keep_block_context
12
119
  effective_keep_block_context = send_self_as_underscore_parameter if effective_keep_block_context.nil?
13
120
  effective_keep_block_context = false if effective_keep_block_context.nil?
14
121
 
122
+ # Match provided parameters to block's expected parameters
15
123
  effective_value_parameters, effective_key_parameters = smart_block._apply(value_parameters, key_parameters)
16
124
 
125
+ # Execute block in appropriate context
17
126
  if effective_keep_block_context
127
+ # Keep caller's context: call block normally
18
128
  if send_self_as_underscore_parameter
129
+ # Pass self as first parameter (the _ parameter)
19
130
  smart_block.call(self, *effective_value_parameters, **effective_key_parameters)
20
131
  else
132
+ # Just pass the effective parameters
21
133
  smart_block.call(*effective_value_parameters, **effective_key_parameters)
22
134
  end
23
135
  elsif effective_value_parameters.empty? && effective_key_parameters.empty?
136
+ # DSL mode: no parameters, execute in object's context
24
137
  instance_eval &block
25
138
  else
139
+ # Hybrid mode: execute in object's context with parameters
26
140
  instance_exec *effective_value_parameters, **effective_key_parameters, &block
27
141
  end
28
142
  end
@@ -1,3 +1,112 @@
1
+ # Musical dataset framework for MusaDSL.
2
+ #
3
+ # The Datasets module provides a comprehensive framework for representing and transforming
4
+ # musical events and data structures. It supports multiple representations (MIDI-style,
5
+ # score-style, serialized formats) and conversions between them.
6
+ #
7
+ # ## Architecture
8
+ #
9
+ # The framework consists of several layers:
10
+ #
11
+ # ### 1. Event Types ({E})
12
+ #
13
+ # Hierarchy of event types defining absolute vs. delta encoding:
14
+ #
15
+ # - **{E}**: Base event module
16
+ # - **{Abs}**: Absolute values (actual pitch, duration, etc.)
17
+ # - **{Delta}**: Delta values (incremental changes)
18
+ # - **{AbsI}**: Absolute indexed (array-based)
19
+ # - **{AbsTimed}**: Absolute with time component
20
+ # - **{AbsD}**: Absolute with duration
21
+ # - **{DeltaD}**: Delta duration (absolute/delta/factor)
22
+ #
23
+ # ### 2. Data Structures
24
+ #
25
+ # Basic container types:
26
+ #
27
+ # - **{V}**: Value array - simple ordered values
28
+ # - **{PackedV}**: Packed value hash - named key-value pairs
29
+ # - **{P}**: Pitch series - alternating values and durations
30
+ #
31
+ # ### 3. Musical Datasets
32
+ #
33
+ # Domain-specific musical representations:
34
+ #
35
+ # - **{PS}**: Pitch series (from/to/duration for glissandi)
36
+ # - **{PDV}**: Pitch/Duration/Velocity (MIDI-style representation)
37
+ # - **{GDV}**: Grade/Duration/Velocity (score-style with scale degrees)
38
+ # - **{GDVd}**: Grade/Duration/Velocity delta (incremental encoding)
39
+ #
40
+ # ### 4. Score Container
41
+ #
42
+ # - **{Score}**: Time-indexed container for musical events
43
+ #
44
+ # ## Basic Usage
45
+ #
46
+ # # Create a packed value (hash)
47
+ # pv = { a: 1, b: 2, c: 3 }.extend(Musa::Datasets::PackedV)
48
+ #
49
+ # # Convert to array
50
+ # v = pv.to_V([:c, :b, :a]) # => [3, 2, 1]
51
+ #
52
+ # # Create pitch series
53
+ # p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
54
+ # # [pitch, duration, pitch, duration, pitch]
55
+ #
56
+ # # Convert to timed series
57
+ # timed = p.to_timed_serie
58
+ #
59
+ # ## Conversion Patterns
60
+ #
61
+ # The framework supports rich conversions:
62
+ #
63
+ # - PackedV ↔ V (hash to array and vice versa)
64
+ # - P → PS (pitch series to glissando segments)
65
+ # - PDV ↔ GDV (MIDI to score notation)
66
+ # - GDV ↔ GDVd (absolute to delta encoding)
67
+ # - GDV → Neuma (score notation string format)
68
+ #
69
+ # @example MIDI-style pitch/duration/velocity
70
+ # pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(Musa::Datasets::PDV)
71
+ # pdv.base_duration = 1/4r
72
+ #
73
+ # # Convert to score notation using scale
74
+ # scale = Musa::Scales::Scales.et12[440.0].major[60]
75
+ # gdv = pdv.to_gdv(scale) # Uses scale degrees
76
+ #
77
+ # @example Score-style grade/duration/velocity
78
+ # gdv = { grade: 0, duration: 1.0, velocity: 0 }.extend(Musa::Datasets::GDV)
79
+ # gdv.base_duration = 1/4r
80
+ #
81
+ # # Convert to MIDI using scale
82
+ # scale = Musa::Scales::Scales.et12[440.0].major[60]
83
+ # pdv = gdv.to_pdv(scale) # Converts to MIDI pitches
84
+ #
85
+ # @example Delta encoding for compression
86
+ # scale = Musa::Scales::Scales.et12[440.0].major[60]
87
+ # gdv1 = { grade: 0, duration: 1.0, velocity: 0 }.extend(Musa::Datasets::GDV)
88
+ # gdv2 = { grade: 2, duration: 1.0, velocity: 1 }.extend(Musa::Datasets::GDV)
89
+ #
90
+ # gdvd = gdv2.to_gdvd(scale, previous: gdv1)
91
+ # # => { delta_grade: 2, delta_velocity: 1 }
92
+ # # Duration unchanged, so omitted
93
+ #
94
+ # @example Score container
95
+ # score = Musa::Datasets::Score.new
96
+ # score.at(0, add: { grade: 0, duration: 1.0 }.extend(Musa::Datasets::GDV))
97
+ # score.at(1, add: { grade: 2, duration: 1.0 }.extend(Musa::Datasets::GDV))
98
+ #
99
+ # @see E Base event type
100
+ # @see PDV MIDI-style representation
101
+ # @see GDV Score-style representation
102
+ # @see Score Event container
1
103
  module Musa::Datasets
104
+ # Base marker module for dataset types.
105
+ #
106
+ # Dataset is a simple marker module included by various dataset types
107
+ # to indicate they are part of the dataset framework.
108
+ #
109
+ # @see E Event base module
110
+ # @see P Pitch series dataset
2
111
  module Dataset; end
3
112
  end
@@ -1,9 +1,87 @@
1
1
  require_relative 'e'
2
2
 
3
3
  module Musa::Datasets
4
+ # Delta events with flexible duration encoding.
5
+ #
6
+ # DeltaD (Delta Duration) extends {Delta} events with three different ways to
7
+ # specify duration changes in delta-encoded sequences. This provides flexibility
8
+ # for efficient encoding of musical sequences.
9
+ #
10
+ # ## Duration Encoding Modes
11
+ #
12
+ # **:abs_duration** - Absolute duration override
13
+ #
14
+ # Sets duration to an absolute value, regardless of previous duration.
15
+ # Use when duration changes to a completely different value.
16
+ #
17
+ # { abs_duration: 2.0 }
18
+ # # Sets duration to 2.0, ignoring previous
19
+ #
20
+ # **:delta_duration** - Incremental duration change
21
+ #
22
+ # Adds or subtracts from the previous duration value.
23
+ # Use for small adjustments to duration.
24
+ #
25
+ # { delta_duration: 0.5 } # Add 0.5 to previous
26
+ # { delta_duration: -0.25 } # Subtract 0.25 from previous
27
+ #
28
+ # **:factor_duration** - Multiplicative duration factor
29
+ #
30
+ # Multiplies previous duration by a factor.
31
+ # Use for proportional changes (doubling, halving, etc.).
32
+ #
33
+ # { factor_duration: 2 } # Double previous duration
34
+ # { factor_duration: 0.5 } # Half previous duration
35
+ #
36
+ # ## Natural Keys
37
+ #
38
+ # - **:abs_duration**: Absolute duration value
39
+ # - **:delta_duration**: Duration increment/decrement
40
+ # - **:factor_duration**: Duration multiplication factor
41
+ #
42
+ # Only one duration key should be present at a time.
43
+ #
44
+ # ## Usage in Delta Encoding
45
+ #
46
+ # Used by {GDVd} for efficient delta encoding of musical sequences:
47
+ #
48
+ # @example Different duration encoding modes
49
+ # previous = { duration: 1.0 }
50
+ #
51
+ # # Absolute: set to specific value
52
+ # delta1 = { abs_duration: 2.0 }.extend(DeltaD)
53
+ # # Result: duration becomes 2.0
54
+ #
55
+ # # Delta: add to previous
56
+ # delta2 = { delta_duration: 0.5 }.extend(DeltaD)
57
+ # # Result: duration becomes 1.5 (was 1.0)
58
+ #
59
+ # # Factor: multiply previous
60
+ # delta3 = { factor_duration: 2 }.extend(DeltaD)
61
+ # # Result: duration becomes 2.0 (was 1.0)
62
+ #
63
+ # @example Neuma notation representation
64
+ # # Absolute duration
65
+ # { abs_duration: 1.5 } => "1.5"
66
+ #
67
+ # # Delta duration
68
+ # { delta_duration: 0.5 } => "+0.5"
69
+ # { delta_duration: -0.5 } => "-0.5"
70
+ #
71
+ # # Factor duration
72
+ # { factor_duration: 2 } => "*2"
73
+ #
74
+ # @see Delta Parent delta module
75
+ # @see GDVd Grade/Duration/Velocity delta encoding
76
+ # @see AbsD Absolute duration events
4
77
  module DeltaD
5
78
  include Delta
6
79
 
80
+ # Natural keys for delta duration encoding.
81
+ #
82
+ # Only one of these keys should be present in a delta event.
83
+ #
84
+ # @return [Array<Symbol>] natural duration keys
7
85
  NaturalKeys = [:abs_duration, # absolute duration
8
86
  :delta_duration, # incremental duration
9
87
  :factor_duration # multiplicative factor duration