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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +233 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +180 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
- data/lib/musa-dsl/generative/markov.rb +133 -3
- data/lib/musa-dsl/generative/rules.rb +258 -4
- data/lib/musa-dsl/generative/variatio.rb +217 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
- data/lib/musa-dsl/midi/midi-voices.rb +265 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +308 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
- data/lib/musa-dsl/music/scales.rb +957 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +48 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
- data/lib/musa-dsl/series/queue-serie.rb +65 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +1 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +12 -10
- metadata +87 -8
|
@@ -1,12 +1,64 @@
|
|
|
1
|
-
|
|
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.
|
|
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|
|