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,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
@@ -1,8 +1,98 @@
1
+ require_relative 'extension'
1
2
  require_relative 'deep-copy'
2
3
 
3
4
  module Musa
4
5
  module Extension
6
+ # Refinement that converts objects, arrays, and hashes into hashes with specified keys.
7
+ #
8
+ # This refinement is crucial for normalizing parameters in the DSL, especially when
9
+ # dealing with musical events that can be specified in multiple formats (positional,
10
+ # hash-based, or mixed).
11
+ #
12
+ # ## Core Behavior
13
+ #
14
+ # - **Object**: Creates hash mapping all keys to the same value
15
+ # - **Array**: Maps keys to array elements in order (consuming array)
16
+ # - **Hash**: Filters and reorders according to specified keys
17
+ # - **Preserves singleton class modules** on hash results
18
+ #
19
+ # ## Use Cases
20
+ #
21
+ # - Converting positional parameters to named parameters
22
+ # - Normalizing mixed parameter formats in musical events
23
+ # - Extracting specific keys from larger hashes
24
+ # - Providing defaults for missing event attributes
25
+ #
26
+ # @example Basic object hashification
27
+ # using Musa::Extension::Hashify
28
+ #
29
+ # 100.hashify(keys: [:velocity, :duration])
30
+ # # => { velocity: 100, duration: 100 }
31
+ #
32
+ # @example Array to hash
33
+ # using Musa::Extension::Hashify
34
+ #
35
+ # [60, 100, 0.5].hashify(keys: [:pitch, :velocity, :duration])
36
+ # # => { pitch: 60, velocity: 100, duration: 0.5 }
37
+ #
38
+ # @example Hash filtering and reordering
39
+ # using Musa::Extension::Hashify
40
+ #
41
+ # { pitch: 60, velocity: 100, channel: 0, duration: 1 }
42
+ # .hashify(keys: [:pitch, :velocity])
43
+ # # => { pitch: 60, velocity: 100 }
44
+ #
45
+ # @example With defaults
46
+ # using Musa::Extension::Hashify
47
+ #
48
+ # [60].hashify(keys: [:pitch, :velocity, :duration], default: nil)
49
+ # # => { pitch: 60, velocity: nil, duration: nil }
50
+ #
51
+ # @example Musical event normalization
52
+ # using Musa::Extension::Hashify
53
+ #
54
+ # # User provides just a pitch
55
+ # 60.hashify(keys: [:pitch, :velocity], default: 64)
56
+ # # => { pitch: 60, velocity: 64 }
57
+ #
58
+ # # User provides array [pitch, velocity, duration]
59
+ # [62, 90, 0.5].hashify(keys: [:pitch, :velocity, :duration])
60
+ # # => { pitch: 62, velocity: 90, duration: 0.5 }
61
+ #
62
+ # @see Musa::Datasets Hash-based event structures
63
+ # @note This refinement must be activated with `using Musa::Extension::Hashify`
64
+ # @note Singleton class modules (dataset extensions) are preserved on hash results
65
+ #
66
+ # ## Methods Added
67
+ #
68
+ # ### Object
69
+ # - {Object#hashify} - Creates a hash mapping all specified keys to this object's value
70
+ #
71
+ # ### Array
72
+ # - {Array#hashify} - Maps array elements to hash keys in order, consuming the array
73
+ #
74
+ # ### Hash
75
+ # - {Hash#hashify} - Filters and reorders hash to include only specified keys, preserving modules
5
76
  module Hashify
77
+ # @!method hashify(keys:, default: nil)
78
+ # Creates a hash mapping all specified keys to this object's value.
79
+ #
80
+ # Useful for broadcasting a single value across multiple attributes.
81
+ # Nil objects can be replaced with a default value.
82
+ #
83
+ # @note This method is added to Object via refinement. Requires `using Musa::Extension::Hashify`.
84
+ #
85
+ # @param keys [Array<Symbol>] keys for the resulting hash.
86
+ # @param default [Object, nil] value to use if self is nil.
87
+ #
88
+ # @return [Hash] hash with all keys mapped to self (or default if nil).
89
+ #
90
+ # @example Single value to multiple keys
91
+ # using Musa::Extension::Hashify
92
+ # 127.hashify(keys: [:velocity, :pressure])
93
+ # # => { velocity: 127, pressure: 127 }
94
+ class ::Object; end
95
+
6
96
  refine Object do
7
97
  def hashify(keys:, default: nil)
8
98
  keys.collect do |key|
@@ -16,6 +106,36 @@ module Musa
16
106
  end
17
107
  end
18
108
 
109
+ # @!method hashify(keys:, default: nil)
110
+ # Maps array elements to hash keys in order, consuming the array.
111
+ #
112
+ # Elements are assigned to keys sequentially. If the array has fewer elements
113
+ # than keys, remaining keys get nil (or default). The array is cloned before
114
+ # consumption, so the original is unchanged.
115
+ #
116
+ # @note This method is added to Array via refinement. Requires `using Musa::Extension::Hashify`.
117
+ #
118
+ # @param keys [Array<Symbol>] keys for the resulting hash (in order).
119
+ # @param default [Object, nil] value for keys without corresponding array elements.
120
+ #
121
+ # @return [Hash] hash with keys mapped to array elements in order.
122
+ #
123
+ # @example Basic array mapping
124
+ # using Musa::Extension::Hashify
125
+ # [60, 100, 0.25].hashify(keys: [:pitch, :velocity, :duration])
126
+ # # => { pitch: 60, velocity: 100, duration: 0.25 }
127
+ #
128
+ # @example Fewer elements than keys
129
+ # using Musa::Extension::Hashify
130
+ # [60, 100].hashify(keys: [:pitch, :velocity, :duration], default: nil)
131
+ # # => { pitch: 60, velocity: 100, duration: nil }
132
+ #
133
+ # @example More elements than keys (extras ignored)
134
+ # using Musa::Extension::Hashify
135
+ # [60, 100, 0.5, :ignored].hashify(keys: [:pitch, :velocity])
136
+ # # => { pitch: 60, velocity: 100 }
137
+ class ::Array; end
138
+
19
139
  refine Array do
20
140
  def hashify(keys:, default: nil)
21
141
  values = clone
@@ -23,10 +143,51 @@ module Musa
23
143
  value = values.shift
24
144
  [ key,
25
145
  value.nil? ? default : value ]
26
- end.to_hash
146
+ end.to_h
27
147
  end
28
148
  end
29
149
 
150
+ # @!method hashify(keys:, default: nil)
151
+ # Filters and reorders hash to include only specified keys, preserving modules.
152
+ #
153
+ # Creates a new hash with only the requested keys, in the order specified.
154
+ # Missing keys get nil (or default). Singleton class modules (like dataset
155
+ # extensions) are copied to the result.
156
+ #
157
+ # @note This method is added to Hash via refinement. Requires `using Musa::Extension::Hashify`.
158
+ #
159
+ # @param keys [Array<Symbol>] keys to include in result (order matters).
160
+ # @param default [Object, nil] value for keys not present in source hash.
161
+ #
162
+ # @return [Hash] new hash with specified keys, preserving singleton modules.
163
+ #
164
+ # @example Filtering keys
165
+ # using Musa::Extension::Hashify
166
+ # { pitch: 60, velocity: 100, channel: 0 }
167
+ # .hashify(keys: [:pitch, :velocity])
168
+ # # => { pitch: 60, velocity: 100 }
169
+ #
170
+ # @example Reordering keys
171
+ # using Musa::Extension::Hashify
172
+ # { velocity: 100, pitch: 60 }
173
+ # .hashify(keys: [:pitch, :velocity])
174
+ # # => { pitch: 60, velocity: 100 }
175
+ #
176
+ # @example Adding missing keys with default
177
+ # using Musa::Extension::Hashify
178
+ # { pitch: 60 }
179
+ # .hashify(keys: [:pitch, :velocity], default: 80)
180
+ # # => { pitch: 60, velocity: 80 }
181
+ #
182
+ # @example Preserving dataset modules
183
+ # using Musa::Extension::Hashify
184
+ # event = { pitch: 60, velocity: 100 }.extend(Musa::Datasets::AbsI)
185
+ # event.hashify(keys: [:pitch, :velocity])
186
+ # # Result also extended with AbsI
187
+ #
188
+ # @note Singleton class modules are preserved via DeepCopy.copy_singleton_class_modules
189
+ class ::Hash; end
190
+
30
191
  refine Hash do
31
192
  def hashify(keys:, default: nil)
32
193
  keys.collect do |key|