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