musa-dsl 0.30.2 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/Gemfile +0 -1
- 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 +544 -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 +215 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +387 -0
- data/lib/musa-dsl/generative/markov.rb +135 -3
- data/lib/musa-dsl/generative/rules.rb +312 -4
- data/lib/musa-dsl/generative/variatio.rb +286 -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 +113 -2
- data/lib/musa-dsl/midi/midi-voices.rb +275 -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 +353 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -206
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
- data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
- data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
- data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
- data/lib/musa-dsl/music/scales.rb +1384 -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 +54 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +64 -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 +57 -7
- data/lib/musa-dsl/series/queue-serie.rb +78 -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 +2 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +25 -18
- metadata +158 -16
|
@@ -1,8 +1,87 @@
|
|
|
1
|
+
require_relative 'extension'
|
|
1
2
|
require_relative 'deep-copy'
|
|
2
3
|
|
|
3
4
|
module Musa
|
|
4
5
|
module Extension
|
|
6
|
+
# Refinement that converts any object to an array, with optional repetition and defaults.
|
|
7
|
+
#
|
|
8
|
+
# This refinement is essential for normalizing parameters in the DSL, allowing users
|
|
9
|
+
# to provide either single values or arrays and have them processed uniformly.
|
|
10
|
+
#
|
|
11
|
+
# ## Core Behavior
|
|
12
|
+
#
|
|
13
|
+
# - **Object**: Wraps in array; nil becomes []
|
|
14
|
+
# - **Array**: Returns clone or cycles to requested size
|
|
15
|
+
# - **size parameter**: Repeats/cycles to achieve target length
|
|
16
|
+
# - **default parameter**: Replaces nil values
|
|
17
|
+
#
|
|
18
|
+
# ## Use Cases
|
|
19
|
+
#
|
|
20
|
+
# - Normalizing velocity parameters (single value or per-note array)
|
|
21
|
+
# - Ensuring consistent array handling in DSL methods
|
|
22
|
+
# - Cycling patterns to fill required lengths
|
|
23
|
+
# - Providing default values for missing data
|
|
24
|
+
#
|
|
25
|
+
# @example Basic object wrapping
|
|
26
|
+
# using Musa::Extension::Arrayfy
|
|
27
|
+
#
|
|
28
|
+
# 5.arrayfy # => [5]
|
|
29
|
+
# nil.arrayfy # => []
|
|
30
|
+
# [1, 2, 3].arrayfy # => [1, 2, 3]
|
|
31
|
+
#
|
|
32
|
+
# @example Repetition with size
|
|
33
|
+
# using Musa::Extension::Arrayfy
|
|
34
|
+
#
|
|
35
|
+
# 5.arrayfy(size: 3) # => [5, 5, 5]
|
|
36
|
+
# [1, 2].arrayfy(size: 5) # => [1, 2, 1, 2, 1]
|
|
37
|
+
# [1, 2, 3].arrayfy(size: 2) # => [1, 2]
|
|
38
|
+
#
|
|
39
|
+
# @example Default values for nil
|
|
40
|
+
# using Musa::Extension::Arrayfy
|
|
41
|
+
#
|
|
42
|
+
# nil.arrayfy(size: 3, default: 0) # => [0, 0, 0]
|
|
43
|
+
# [1, nil, 3].arrayfy(size: 5, default: -1) # => [1, -1, 3, 1, -1]
|
|
44
|
+
#
|
|
45
|
+
# @example Musical application - velocity normalization
|
|
46
|
+
# using Musa::Extension::Arrayfy
|
|
47
|
+
#
|
|
48
|
+
# # User provides single velocity for chord
|
|
49
|
+
# velocities = 90.arrayfy(size: 3) # => [90, 90, 90]
|
|
50
|
+
#
|
|
51
|
+
# # User provides array of velocities that cycles
|
|
52
|
+
# velocities = [80, 100].arrayfy(size: 5) # => [80, 100, 80, 100, 80]
|
|
53
|
+
#
|
|
54
|
+
# @see Musa::MIDIVoices::MIDIVoice#note Uses arrayfy for velocity normalization
|
|
55
|
+
# @note This refinement must be activated with `using Musa::Extension::Arrayfy`
|
|
56
|
+
# @note Arrays are cloned and singleton class modules are preserved
|
|
57
|
+
#
|
|
58
|
+
# ## Methods Added
|
|
59
|
+
#
|
|
60
|
+
# ### Object
|
|
61
|
+
# - {Object#arrayfy} - Converts any object into an array, optionally repeated to a target size
|
|
62
|
+
#
|
|
63
|
+
# ### Array
|
|
64
|
+
# - {Array#arrayfy} - Clones or cycles the array to achieve the target size, with nil replacement
|
|
5
65
|
module Arrayfy
|
|
66
|
+
# @!method arrayfy(size: nil, default: nil)
|
|
67
|
+
# Converts any object into an array, optionally repeated to a target size.
|
|
68
|
+
#
|
|
69
|
+
# @note This method is added to Object via refinement. Requires `using Musa::Extension::Arrayfy`.
|
|
70
|
+
#
|
|
71
|
+
# @param size [Integer, nil] target array length. If nil, returns single-element array.
|
|
72
|
+
# @param default [Object, nil] value to use instead of nil.
|
|
73
|
+
#
|
|
74
|
+
# @return [Array] single element repeated size times, or wrapped in array if size is nil.
|
|
75
|
+
#
|
|
76
|
+
# @example With size
|
|
77
|
+
# using Musa::Extension::Arrayfy
|
|
78
|
+
# "hello".arrayfy(size: 3) # => ["hello", "hello", "hello"]
|
|
79
|
+
#
|
|
80
|
+
# @example Nil handling
|
|
81
|
+
# using Musa::Extension::Arrayfy
|
|
82
|
+
# nil.arrayfy(size: 2, default: :empty) # => [:empty, :empty]
|
|
83
|
+
class ::Object; end
|
|
84
|
+
|
|
6
85
|
refine Object do
|
|
7
86
|
def arrayfy(size: nil, default: nil)
|
|
8
87
|
if size
|
|
@@ -17,6 +96,37 @@ module Musa
|
|
|
17
96
|
|
|
18
97
|
# TODO add a refinement for Hash? Should receive a list parameter with the ordered keys
|
|
19
98
|
|
|
99
|
+
# @!method arrayfy(size: nil, default: nil)
|
|
100
|
+
# Clones or cycles the array to achieve the target size, with nil replacement.
|
|
101
|
+
#
|
|
102
|
+
# The cycling behavior multiplies the array enough times to reach or exceed
|
|
103
|
+
# the target size, then takes exactly the requested number of elements.
|
|
104
|
+
# Singleton class modules (like P, V dataset extensions) are preserved.
|
|
105
|
+
#
|
|
106
|
+
# @note This method is added to Array via refinement. Requires `using Musa::Extension::Arrayfy`.
|
|
107
|
+
#
|
|
108
|
+
# @param size [Integer, nil] target length. If nil, returns clone of array.
|
|
109
|
+
# @param default [Object, nil] value to replace nil elements with.
|
|
110
|
+
#
|
|
111
|
+
# @return [Array] processed array of exactly the requested size.
|
|
112
|
+
#
|
|
113
|
+
# @example Cycling shorter array
|
|
114
|
+
# using Musa::Extension::Arrayfy
|
|
115
|
+
# [1, 2].arrayfy(size: 5) # => [1, 2, 1, 2, 1]
|
|
116
|
+
#
|
|
117
|
+
# @example Truncating longer array
|
|
118
|
+
# using Musa::Extension::Arrayfy
|
|
119
|
+
# [1, 2, 3, 4, 5].arrayfy(size: 3) # => [1, 2, 3]
|
|
120
|
+
#
|
|
121
|
+
# @example Preserving dataset modules
|
|
122
|
+
# using Musa::Extension::Arrayfy
|
|
123
|
+
# p_sequence = [60, 1, 62].extend(Musa::Datasets::P)
|
|
124
|
+
# p_sequence.arrayfy(size: 6) # Result also extended with P
|
|
125
|
+
#
|
|
126
|
+
# @note The cycling formula: array * (size / array.size + (size % array.size).zero? ? 0 : 1)
|
|
127
|
+
# ensures enough repetitions to reach target size.
|
|
128
|
+
class ::Array; end
|
|
129
|
+
|
|
20
130
|
refine Array do
|
|
21
131
|
def arrayfy(size: nil, default: nil)
|
|
22
132
|
if size
|
|
@@ -1,10 +1,52 @@
|
|
|
1
|
+
require_relative 'extension'
|
|
2
|
+
|
|
1
3
|
module Musa
|
|
2
4
|
module Extension
|
|
5
|
+
# Module providing metaprogramming methods for creating DSL builder patterns.
|
|
6
|
+
#
|
|
7
|
+
# AttributeBuilder defines class methods that generate instance methods for
|
|
8
|
+
# creating and managing collections of objects in a DSL-friendly way. It's
|
|
9
|
+
# heavily used throughout Musa DSL to create fluent, expressive APIs.
|
|
10
|
+
#
|
|
11
|
+
# ## Method Categories
|
|
12
|
+
#
|
|
13
|
+
# - **Adders to Hash**: Create methods that build hash-based collections
|
|
14
|
+
# - **Adders to Array**: Create methods that build array-based collections
|
|
15
|
+
# - **Builders**: Create single-object getter/setter DSL methods
|
|
16
|
+
#
|
|
17
|
+
# ## Naming Conventions
|
|
18
|
+
#
|
|
19
|
+
# - `add_item` / `item`: singular form adds one object
|
|
20
|
+
# - `items`: plural form adds multiple or retrieves collection
|
|
21
|
+
# - Automatic pluralization (item → items) unless specified
|
|
22
|
+
#
|
|
23
|
+
# @example Using in a class
|
|
24
|
+
# class Score
|
|
25
|
+
# extend Musa::Extension::AttributeBuilder
|
|
26
|
+
#
|
|
27
|
+
# def initialize
|
|
28
|
+
# @tracks = {}
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# attr_tuple_adder_to_hash :track, Track
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# score = Score.new
|
|
35
|
+
# score.add_track :piano, params
|
|
36
|
+
# score.tracks # => { piano: Track(...) }
|
|
37
|
+
#
|
|
38
|
+
# @see Musa::Datasets Score classes use these extensively
|
|
3
39
|
module AttributeBuilder
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
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
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
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
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
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
|
-
#
|
|
125
|
-
#
|
|
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
|
-
#
|
|
144
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
|
|
224
|
+
# Creates a getter/setter DSL method for complex objects with multiple parameters.
|
|
225
|
+
#
|
|
226
|
+
# Supports optional first_parameter that's automatically prepended when constructing.
|
|
227
|
+
#
|
|
228
|
+
# @param name [Symbol] attribute name.
|
|
229
|
+
# @param klass [Class] class to instantiate.
|
|
230
|
+
# @param variable [Symbol, nil] instance variable name.
|
|
231
|
+
# @param first_parameter [Object, nil] parameter automatically prepended to constructor.
|
|
171
232
|
def attr_complex_builder(name, klass, variable: nil, first_parameter: nil)
|
|
172
233
|
variable ||= ('@' + name.to_s).to_sym
|
|
173
234
|
|
|
@@ -1,12 +1,64 @@
|
|
|
1
|
-
|
|
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
|