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,8 +1,109 @@
|
|
|
1
1
|
require_relative '../core-ext/with'
|
|
2
2
|
|
|
3
3
|
module Musa
|
|
4
|
+
# Evolutionary selection algorithm based on fitness evaluation.
|
|
5
|
+
#
|
|
6
|
+
# Darwin implements a selection algorithm inspired by natural selection,
|
|
7
|
+
# evaluating and ranking a population of objects based on defined measures
|
|
8
|
+
# (features and dimensions) and weights. Objects are measured, scored, and
|
|
9
|
+
# sorted by fitness for evolutionary algorithms or optimization.
|
|
10
|
+
#
|
|
11
|
+
# ## Core Concepts
|
|
12
|
+
#
|
|
13
|
+
# - **Population**: Collection of objects to evaluate
|
|
14
|
+
# - **Measures**: Evaluation criteria for each object
|
|
15
|
+
# - **Features**: Boolean flags (present/absent)
|
|
16
|
+
# - **Dimensions**: Numeric values (continuous)
|
|
17
|
+
# - **Die**: Mark object as non-viable (eliminated)
|
|
18
|
+
# - **Weights**: Importance multipliers for features/dimensions
|
|
19
|
+
# - **Fitness**: Calculated score from normalized dimensions and features
|
|
20
|
+
# - **Selection**: Population sorted by fitness (highest first)
|
|
21
|
+
#
|
|
22
|
+
# ## Evaluation Process
|
|
23
|
+
#
|
|
24
|
+
# 1. **Measure**: Evaluate each object with measures block
|
|
25
|
+
# 2. **Normalize**: Scale dimension values to 0-1 range
|
|
26
|
+
# 3. **Weight**: Apply weights to dimensions and features
|
|
27
|
+
# 4. **Score**: Calculate total fitness for each object
|
|
28
|
+
# 5. **Sort**: Order population by fitness (descending)
|
|
29
|
+
#
|
|
30
|
+
# ## Musical Applications
|
|
31
|
+
#
|
|
32
|
+
# - Select best harmonic progressions from generated candidates
|
|
33
|
+
# - Rank melodic variations by aesthetic criteria
|
|
34
|
+
# - Optimize rhythmic patterns for complexity/simplicity
|
|
35
|
+
# - Evolve musical structures through iterative selection
|
|
36
|
+
#
|
|
37
|
+
# @example Basic selection with features and dimensions
|
|
38
|
+
# darwin = Musa::Darwin::Darwin.new do
|
|
39
|
+
# measures do |object|
|
|
40
|
+
# # Kill objects with unwanted property
|
|
41
|
+
# die if object[:bad_property]
|
|
42
|
+
#
|
|
43
|
+
# # Binary features
|
|
44
|
+
# feature :has_alpha if object[:type] == :alpha
|
|
45
|
+
# feature :has_beta if object[:type] == :beta
|
|
46
|
+
#
|
|
47
|
+
# # Numeric dimension (negative to prefer lower values)
|
|
48
|
+
# dimension :complexity, -object[:complexity].to_f
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# # Weight contributions to fitness
|
|
52
|
+
# weight complexity: 2.0, has_alpha: 1.0, has_beta: -0.5
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# population = generate_candidates()
|
|
56
|
+
# selected = darwin.select(population)
|
|
57
|
+
# # Returns population sorted by fitness (best first)
|
|
58
|
+
#
|
|
59
|
+
# @example Musical chord progression selection
|
|
60
|
+
# darwin = Musa::Darwin::Darwin.new do
|
|
61
|
+
# measures do |progression|
|
|
62
|
+
# # Eliminate progressions with parallel fifths
|
|
63
|
+
# die if has_parallel_fifths?(progression)
|
|
64
|
+
#
|
|
65
|
+
# # Prefer smooth voice leading
|
|
66
|
+
# dimension :voice_leading, -total_voice_leading_distance(progression)
|
|
67
|
+
#
|
|
68
|
+
# # Prefer certain cadences
|
|
69
|
+
# feature :authentic_cadence if ends_with_V_I?(progression)
|
|
70
|
+
# feature :plagal_cadence if ends_with_IV_I?(progression)
|
|
71
|
+
#
|
|
72
|
+
# # Penalize excessive chromaticism
|
|
73
|
+
# dimension :chromaticism, -count_chromatic_notes(progression)
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# weight voice_leading: 3.0,
|
|
77
|
+
# authentic_cadence: 2.0,
|
|
78
|
+
# plagal_cadence: 1.0,
|
|
79
|
+
# chromaticism: 1.5
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# candidates = generate_progressions()
|
|
83
|
+
# best = darwin.select(candidates).first(10) # Top 10 progressions
|
|
84
|
+
#
|
|
85
|
+
# @see Darwin Main evolutionary selector class
|
|
86
|
+
# @see Musa::Extension::With DSL context management for evaluation blocks
|
|
87
|
+
# @see https://en.wikipedia.org/wiki/Evolutionary_algorithm Evolutionary algorithm (Wikipedia)
|
|
88
|
+
# @see https://en.wikipedia.org/wiki/Fitness_function Fitness function (Wikipedia)
|
|
4
89
|
module Darwin
|
|
90
|
+
# Evolutionary selector for population-based optimization.
|
|
91
|
+
#
|
|
92
|
+
# Evaluates population using measures and weights, returning sorted
|
|
93
|
+
# population by fitness score.
|
|
5
94
|
class Darwin
|
|
95
|
+
# Creates Darwin selector with evaluation rules.
|
|
96
|
+
#
|
|
97
|
+
# @yield evaluation DSL block
|
|
98
|
+
# @yieldreturn [void]
|
|
99
|
+
#
|
|
100
|
+
# @raise [ArgumentError] if no block given
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# darwin = Darwin.new do
|
|
104
|
+
# measures { |obj| dimension :value, obj[:score] }
|
|
105
|
+
# weight value: 1.0
|
|
106
|
+
# end
|
|
6
107
|
def initialize(&block)
|
|
7
108
|
raise ArgumentError, 'block is needed' unless block
|
|
8
109
|
|
|
@@ -12,6 +113,22 @@ module Musa
|
|
|
12
113
|
@weights = main_context._weights
|
|
13
114
|
end
|
|
14
115
|
|
|
116
|
+
# Selects and ranks population by fitness.
|
|
117
|
+
#
|
|
118
|
+
# Evaluates each object with measures, normalizes dimensions across
|
|
119
|
+
# population, applies weights, and returns population sorted by fitness
|
|
120
|
+
# (highest first). Objects marked as died are excluded.
|
|
121
|
+
#
|
|
122
|
+
# @param population [Array] objects to evaluate
|
|
123
|
+
#
|
|
124
|
+
# @return [Array] population sorted by fitness (descending)
|
|
125
|
+
#
|
|
126
|
+
# @example
|
|
127
|
+
# candidates = [obj1, obj2, obj3, ...]
|
|
128
|
+
# ranked = darwin.select(candidates)
|
|
129
|
+
# best = ranked.first # Highest fitness
|
|
130
|
+
# worst = ranked.last # Lowest fitness
|
|
131
|
+
# top10 = ranked.first(10) # Top 10
|
|
15
132
|
def select(population)
|
|
16
133
|
measured_objects = []
|
|
17
134
|
|
|
@@ -46,7 +163,8 @@ module Musa
|
|
|
46
163
|
|
|
47
164
|
measure.dimensions.each do |dimension_name, value|
|
|
48
165
|
limit = limits[dimension_name]
|
|
49
|
-
measure.normalized_dimensions[dimension_name] =
|
|
166
|
+
measure.normalized_dimensions[dimension_name] =
|
|
167
|
+
limit[:range].zero? ? 0.5 : (value - limit[:min]) / limit[:range]
|
|
50
168
|
end
|
|
51
169
|
|
|
52
170
|
# warn "Darwin.select: #{measured_object[:object]} #{measured_object[:measure]} weight=#{measured_object[:measure].evaluate_weight(@weights).round(2)}"
|
|
@@ -61,20 +179,34 @@ module Musa
|
|
|
61
179
|
measure_b.evaluate_weight(@weights) <=> measure_a.evaluate_weight(@weights)
|
|
62
180
|
end
|
|
63
181
|
|
|
182
|
+
# DSL context for Darwin configuration.
|
|
183
|
+
#
|
|
184
|
+
# @api private
|
|
64
185
|
class MainContext
|
|
65
186
|
include Musa::Extension::With
|
|
66
187
|
|
|
188
|
+
# @return [Proc] measures evaluation block
|
|
189
|
+
# @return [Hash] weight assignments
|
|
67
190
|
attr_reader :_measures, :_weights
|
|
68
191
|
|
|
192
|
+
# @api private
|
|
69
193
|
def initialize(&block)
|
|
70
194
|
@_weights = {}
|
|
71
195
|
with &block
|
|
72
196
|
end
|
|
73
197
|
|
|
198
|
+
# Defines measures evaluation block.
|
|
199
|
+
#
|
|
200
|
+
# @yield [object] measures DSL block
|
|
201
|
+
# @api private
|
|
74
202
|
def measures(&block)
|
|
75
203
|
@_measures = block
|
|
76
204
|
end
|
|
77
205
|
|
|
206
|
+
# Assigns weights to features/dimensions.
|
|
207
|
+
#
|
|
208
|
+
# @param feature_or_dimension_weights [Hash] name => weight pairs
|
|
209
|
+
# @api private
|
|
78
210
|
def weight(**feature_or_dimension_weights)
|
|
79
211
|
feature_or_dimension_weights.each do |name, value|
|
|
80
212
|
@_weights[name] = value
|
|
@@ -82,39 +214,73 @@ module Musa
|
|
|
82
214
|
end
|
|
83
215
|
end
|
|
84
216
|
|
|
217
|
+
# DSL context for object measurement.
|
|
218
|
+
#
|
|
219
|
+
# @api private
|
|
85
220
|
class MeasuresEvalContext
|
|
86
221
|
include Musa::Extension::With
|
|
87
222
|
|
|
223
|
+
# @api private
|
|
88
224
|
def initialize
|
|
89
225
|
@_features = {}
|
|
90
226
|
@_dimensions = {}
|
|
91
227
|
@_died = false
|
|
92
228
|
end
|
|
93
229
|
|
|
230
|
+
# Returns measure result.
|
|
231
|
+
#
|
|
232
|
+
# @return [Measure] measurement
|
|
233
|
+
# @api private
|
|
94
234
|
def _measure
|
|
95
235
|
Measure.new @_features, @_dimensions, @_died
|
|
96
236
|
end
|
|
97
237
|
|
|
238
|
+
# Marks object as having a feature.
|
|
239
|
+
#
|
|
240
|
+
# @param feature_name [Symbol] feature identifier
|
|
241
|
+
# @api private
|
|
98
242
|
def feature(feature_name)
|
|
99
243
|
@_features[feature_name] = true
|
|
100
244
|
end
|
|
101
245
|
|
|
246
|
+
# Records dimensional measurement.
|
|
247
|
+
#
|
|
248
|
+
# @param dimension_name [Symbol] dimension identifier
|
|
249
|
+
# @param value [Numeric] measured value
|
|
250
|
+
# @api private
|
|
102
251
|
def dimension(dimension_name, value)
|
|
103
252
|
@_dimensions[dimension_name] = value
|
|
104
253
|
end
|
|
105
254
|
|
|
255
|
+
# Marks object as non-viable (to be excluded).
|
|
256
|
+
#
|
|
257
|
+
# @api private
|
|
106
258
|
def die
|
|
107
259
|
@_died = true
|
|
108
260
|
end
|
|
109
261
|
|
|
262
|
+
# Checks if object marked as died.
|
|
263
|
+
#
|
|
264
|
+
# @return [Boolean]
|
|
265
|
+
# @api private
|
|
110
266
|
def died?
|
|
111
267
|
@_died
|
|
112
268
|
end
|
|
113
269
|
end
|
|
114
270
|
|
|
271
|
+
# Measurement result for an object.
|
|
272
|
+
#
|
|
273
|
+
# Contains features, dimensions, and viability status.
|
|
274
|
+
#
|
|
275
|
+
# @attr_reader features [Hash{Symbol => Boolean}] feature flags
|
|
276
|
+
# @attr_reader dimensions [Hash{Symbol => Numeric}] raw dimension values
|
|
277
|
+
# @attr_reader normalized_dimensions [Hash{Symbol => Float}] normalized (0-1) dimensions
|
|
278
|
+
#
|
|
279
|
+
# @api private
|
|
115
280
|
class Measure
|
|
116
281
|
attr_reader :features, :dimensions, :normalized_dimensions
|
|
117
282
|
|
|
283
|
+
# @api private
|
|
118
284
|
def initialize(features, dimensions, died)
|
|
119
285
|
@features = features
|
|
120
286
|
@dimensions = dimensions
|
|
@@ -123,10 +289,23 @@ module Musa
|
|
|
123
289
|
@normalized_dimensions = {}
|
|
124
290
|
end
|
|
125
291
|
|
|
292
|
+
# Checks if object is non-viable.
|
|
293
|
+
#
|
|
294
|
+
# @return [Boolean]
|
|
295
|
+
# @api private
|
|
126
296
|
def died?
|
|
127
297
|
@died
|
|
128
298
|
end
|
|
129
299
|
|
|
300
|
+
# Calculates weighted fitness score.
|
|
301
|
+
#
|
|
302
|
+
# Sums weighted normalized dimensions and features.
|
|
303
|
+
#
|
|
304
|
+
# @param weights [Hash{Symbol => Numeric}] weight assignments
|
|
305
|
+
#
|
|
306
|
+
# @return [Float] total fitness score
|
|
307
|
+
#
|
|
308
|
+
# @api private
|
|
130
309
|
def evaluate_weight(weights)
|
|
131
310
|
total = 0.0
|
|
132
311
|
|