musa-dsl 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +15 -1
  4. data/docs/README.md +1 -0
  5. data/docs/subsystems/datasets.md +75 -0
  6. data/docs/subsystems/generative.md +92 -6
  7. data/docs/subsystems/music.md +33 -14
  8. data/docs/subsystems/transport.md +26 -0
  9. data/lib/musa-dsl/datasets/dataset.rb +2 -0
  10. data/lib/musa-dsl/datasets/gdv.rb +3 -3
  11. data/lib/musa-dsl/datasets/p.rb +1 -1
  12. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +4 -2
  13. data/lib/musa-dsl/datasets/score.rb +3 -1
  14. data/lib/musa-dsl/generative/generative-grammar.rb +3 -1
  15. data/lib/musa-dsl/generative/markov.rb +1 -1
  16. data/lib/musa-dsl/midi/midi-voices.rb +3 -1
  17. data/lib/musa-dsl/music/chord-definition.rb +7 -5
  18. data/lib/musa-dsl/music/chord-definitions.rb +37 -0
  19. data/lib/musa-dsl/music/chords.rb +69 -47
  20. data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +1 -1
  21. data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +1 -1
  22. data/lib/musa-dsl/music/scales.rb +219 -107
  23. data/lib/musa-dsl/musicxml/builder/note.rb +31 -92
  24. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +33 -94
  25. data/lib/musa-dsl/musicxml/builder/rest.rb +30 -91
  26. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +31 -91
  27. data/lib/musa-dsl/neumas/array-to-neumas.rb +1 -1
  28. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +2 -2
  29. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +367 -3
  30. data/lib/musa-dsl/series/base-series.rb +250 -240
  31. data/lib/musa-dsl/series/buffer-serie.rb +10 -5
  32. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +6 -3
  33. data/lib/musa-dsl/series/main-serie-constructors.rb +19 -15
  34. data/lib/musa-dsl/series/main-serie-operations.rb +74 -29
  35. data/lib/musa-dsl/series/proxy-serie.rb +5 -1
  36. data/lib/musa-dsl/series/quantizer-serie.rb +4 -2
  37. data/lib/musa-dsl/series/queue-serie.rb +2 -1
  38. data/lib/musa-dsl/series/series-composer.rb +5 -2
  39. data/lib/musa-dsl/series/timed-serie.rb +8 -4
  40. data/lib/musa-dsl/transport/timer-clock.rb +4 -2
  41. data/lib/musa-dsl/transport/timer.rb +27 -4
  42. data/lib/musa-dsl/version.rb +1 -2
  43. data/musa-dsl.gemspec +0 -2
  44. metadata +1 -1
@@ -187,251 +187,55 @@ module Musa
187
187
 
188
188
  include Constructors
189
189
 
190
- # Serie module factory providing configurable serie modules.
190
+ # Serie mixins for building serie implementations.
191
191
  #
192
- # Creates modules dynamically based on requirements:
192
+ # Provides composable modules for serie classes:
193
193
  #
194
- # - **Serie.base**: Minimal module without dependencies
195
- # - **Serie.with**: Configured module with source/sources/block
194
+ # - {Base} - Core functionality without dependencies
195
+ # - {WithSource} - Single upstream serie dependency
196
+ # - {WithSources} - Multiple upstream serie dependencies
197
+ # - {WithBlock} - Simple proc storage
198
+ # - {WithSmartBlock} - SmartProcBinder-wrapped proc
196
199
  #
197
- # ## Factory Pattern
198
- #
199
- # Serie.with generates modules at runtime with specific features:
200
+ # ## Usage Pattern
200
201
  #
201
202
  # ```ruby
202
- # # Generate module with source support
203
- # include Serie.with(source: true, source_as: :upstream)
203
+ # class MySerie
204
+ # include Serie::Base
205
+ # include Serie::WithSource
204
206
  #
205
- # # Now has @source, #upstream, #upstream= methods
206
- # ```
207
+ # def initialize(source)
208
+ # self.source = source
209
+ # init
210
+ # mark_as_prototype!
211
+ # end
207
212
  #
208
- # ## Configuration Options
213
+ # private def _next_value
214
+ # @source.next_value&.transform
215
+ # end
216
+ # end
217
+ # ```
209
218
  #
210
- # - **source**: Single upstream serie dependency
211
- # - **sources**: Multiple upstream serie dependencies
212
- # - **block**: Block/proc attribute
213
- # - **smart_block**: SmartProcBinder-wrapped block
219
+ # ## Custom Accessor Names
214
220
  #
215
- # ## Musical Applications
221
+ # Use `alias` to create custom accessor names:
216
222
  #
217
- # Used internally by serie implementations to declare dependencies
218
- # and automatically handle prototype/instance propagation.
223
+ # ```ruby
224
+ # class MySerie
225
+ # include Serie::Base
226
+ # include Serie::WithSource
227
+ # alias upstream source
228
+ # alias upstream= source=
229
+ # end
230
+ # ```
219
231
  #
220
- # @see SerieImplementation Core serie protocol
232
+ # @see Base Core serie module
233
+ # @see WithSource Single source dependency
234
+ # @see WithSources Multiple sources dependency
235
+ # @see WithBlock Simple proc storage
236
+ # @see WithSmartBlock SmartProcBinder-wrapped proc
221
237
  # @see Prototyping Prototype/instance state management
222
- #
223
- # @api private
224
238
  module Serie
225
- # Creates base serie module without dependencies.
226
- #
227
- # Minimal module for series that generate values without upstream
228
- # sources (e.g., array-backed series, value generators).
229
- #
230
- # @return [Module] base module with SerieImplementation
231
- #
232
- # @example Base serie
233
- # class SimpleSerie
234
- # include Serie.base
235
- #
236
- # def _next_value
237
- # # Generate value
238
- # end
239
- # end
240
- #
241
- # @api private
242
- def self.base
243
- Module.new do
244
- include SerieImplementation
245
-
246
- def has_source; false; end
247
- private def mandatory_source; false; end
248
-
249
- def has_sources; false; end
250
- private def mandatory_sources; false; end
251
- end
252
- end
253
-
254
- # Creates configurable serie module with specified features.
255
- #
256
- # Factory method generating modules with:
257
- #
258
- # - Single source dependency (source: true)
259
- # - Multiple sources dependency (sources: true)
260
- # - Block attribute (block: true, smart_block: true)
261
- #
262
- # ## Source Support
263
- #
264
- # **source: true** adds:
265
- #
266
- # - `@source` instance variable
267
- # - `#source` getter (or custom name via source_as:)
268
- # - `#source=` setter with state validation
269
- # - Automatic prototype/instance propagation
270
- #
271
- # ## Sources Support
272
- #
273
- # **sources: true** adds:
274
- #
275
- # - `@sources` instance variable (Hash or Array)
276
- # - `#sources` getter/setter
277
- # - Automatic state resolution from all sources
278
- #
279
- # ## Block Support
280
- #
281
- # **block: true** - Simple proc attribute
282
- # **smart_block: true** - SmartProcBinder-wrapped block
283
- #
284
- # ## State Propagation
285
- #
286
- # Sources automatically propagate state:
287
- #
288
- # - Setting source to :prototype → marks self as :prototype
289
- # - Setting source to :instance → marks self as :instance
290
- # - Cloning propagates through source/sources
291
- #
292
- # @param source [Boolean] add single source dependency
293
- # @param source_as [Symbol, nil] custom name for source attribute
294
- # @param private_source [Boolean, nil] make source methods private
295
- # @param mandatory_source [Boolean, nil] require source to be set
296
- # @param sources [Boolean] add multiple sources dependency
297
- # @param sources_as [Symbol, nil] custom name for sources attribute
298
- # @param private_sources [Boolean, nil] make sources methods private
299
- # @param mandatory_sources [Boolean, nil] require sources to be set
300
- # @param smart_block [Boolean] add SmartProcBinder block support
301
- # @param block [Boolean] add simple block support
302
- # @param block_as [Symbol, nil] custom name for block attribute
303
- #
304
- # @return [Module] configured module with SerieImplementation
305
- #
306
- # @example Serie with single source
307
- # class ReverseSerie
308
- # include Serie.with(source: true)
309
- #
310
- # def _next_value
311
- # # Process source.next_value
312
- # end
313
- # end
314
- #
315
- # @example Serie with block
316
- # class MapSerie
317
- # include Serie.with(source: true, smart_block: true)
318
- #
319
- # def _next_value
320
- # value = source.next_value
321
- # value ? @block.call(value) : nil
322
- # end
323
- # end
324
- #
325
- # @api private
326
- def self.with(source: false,
327
- source_as: nil,
328
- private_source: nil,
329
- mandatory_source: nil,
330
- sources: false,
331
- sources_as: nil,
332
- private_sources: nil,
333
- mandatory_sources: nil,
334
- smart_block: false,
335
- block: false,
336
- block_as: nil)
337
-
338
- source_as ||= :source
339
- source_setter = (source_as.to_s + '=').to_sym
340
- _mandatory_source = source if mandatory_source.nil?
341
-
342
- sources_as ||= :sources
343
- sources_setter = (sources_as.to_s + '=').to_sym
344
- _mandatory_sources = sources if mandatory_sources.nil?
345
-
346
- block_as ||= :proc
347
- block_setter = (block_as.to_s + '=').to_sym
348
-
349
- Module.new do
350
- include SerieImplementation
351
-
352
- if source
353
- private def has_source; true; end
354
- define_method(:mandatory_source) { _mandatory_source }
355
- private :mandatory_source
356
-
357
- define_method source_as do
358
- @source
359
- end
360
-
361
- define_method source_setter do |serie|
362
- unless @source.nil? || @source.undefined? || serie.state == @source.state
363
- raise ArgumentError, "New serie for #{source_as} should be a #{@state} instead of a #{serie.state}"
364
- end
365
-
366
- @source = serie
367
- mark_regarding! @source
368
- end
369
-
370
- if private_source
371
- private source_as
372
- private source_setter
373
- end
374
- else
375
- private def has_source; false; end
376
- private def mandatory_source; false; end
377
- end
378
-
379
- if sources
380
- private def has_sources; true; end
381
- define_method(:mandatory_sources) { _mandatory_sources }
382
- private :mandatory_source
383
-
384
- define_method sources_as do
385
- @sources
386
- end
387
-
388
- define_method sources_setter do |series|
389
- unless series.is_a?(Hash) || series.is_a?(Array)
390
- raise ArgumentError, "New series for #{sources_as} should be a Hash or an Array instead of a #{series.class.name}"
391
- end
392
-
393
- @sources = series
394
- try_to_resolve_undefined_state_if_needed
395
- end
396
-
397
- if private_sources
398
- private sources_as
399
- private sources_setter
400
- end
401
- else
402
- private def has_sources; false; end
403
- private def mandatory_sources; false; end
404
- end
405
-
406
- if smart_block
407
- define_method block_as do |&block|
408
- if block
409
- @block = Extension::SmartProcBinder::SmartProcBinder.new(block)
410
- else
411
- @block.proc
412
- end
413
- end
414
-
415
- define_method block_setter do |block|
416
- @block = Extension::SmartProcBinder::SmartProcBinder.new(block)
417
- end
418
-
419
- elsif block
420
- define_method block_as do |&block|
421
- if block
422
- @block = block
423
- else
424
- @block
425
- end
426
- end
427
-
428
- define_method block_setter do |block|
429
- @block = block
430
- end
431
- end
432
- end
433
- end
434
-
435
239
  # Prototype/instance state management for Series.
436
240
  #
437
241
  # Implements the prototype/instance pattern enabling reusable serie
@@ -651,14 +455,14 @@ module Musa
651
455
 
652
456
  # Converts serie and dependencies to prototype state.
653
457
  #
654
- # Called automatically during cloning. By default, handles @source and
655
- # @sources attributes automatically. Subclasses can override to add
458
+ # Called automatically during cloning. By default, handles +@source+ and
459
+ # +@sources+ attributes automatically. Subclasses can override to add
656
460
  # custom prototyping logic.
657
461
  #
658
462
  # ## Default Behavior
659
463
  #
660
- # - Calls `.prototype` on @source if present
661
- # - Calls `.prototype` on all @sources elements (Array or Hash)
464
+ # - Calls +.prototype+ on +@source+ if present
465
+ # - Calls +.prototype+ on all +@sources+ elements (Array or Hash)
662
466
  #
663
467
  # @return [void]
664
468
  #
@@ -677,13 +481,13 @@ module Musa
677
481
  # Converts serie and dependencies to instance state.
678
482
  #
679
483
  # Called automatically during instance creation. By default, handles
680
- # @source and @sources attributes automatically. Subclasses can
484
+ # +@source+ and +@sources+ attributes automatically. Subclasses can
681
485
  # override to add custom instancing logic.
682
486
  #
683
487
  # ## Default Behavior
684
488
  #
685
- # - Calls `.instance` on @source if present
686
- # - Calls `.instance` on all @sources elements (Array or Hash)
489
+ # - Calls +.instance+ on +@source+ if present
490
+ # - Calls +.instance+ on all +@sources+ elements (Array or Hash)
687
491
  #
688
492
  # @return [void]
689
493
  #
@@ -801,7 +605,7 @@ module Musa
801
605
  # Attempts to resolve undefined state from sources.
802
606
  #
803
607
  # Called automatically before state queries. Resolves state based on
804
- # @source and @sources dependencies:
608
+ # +@source+ and +@sources+ dependencies:
805
609
  #
806
610
  # - All sources :prototype → :prototype
807
611
  # - All sources :instance → :instance
@@ -1314,6 +1118,212 @@ module Musa
1314
1118
  end
1315
1119
 
1316
1120
  private_constant :SerieImplementation
1121
+
1122
+ # Base module for all Serie implementations.
1123
+ #
1124
+ # Provides the core Serie functionality without source or block support.
1125
+ # Include this module in classes that don't need source dependencies.
1126
+ #
1127
+ # @example Basic usage
1128
+ # class MySerie
1129
+ # include Serie::Base
1130
+ #
1131
+ # def initialize
1132
+ # init
1133
+ # mark_as_prototype!
1134
+ # end
1135
+ #
1136
+ # private def _next_value
1137
+ # # implementation
1138
+ # end
1139
+ # end
1140
+ #
1141
+ # @see WithSource For series with single source dependency
1142
+ # @see WithSources For series with multiple source dependencies
1143
+ module Base
1144
+ include SerieImplementation
1145
+
1146
+ private def has_source; false; end
1147
+ private def mandatory_source; false; end
1148
+ private def has_sources; false; end
1149
+ private def mandatory_sources; false; end
1150
+ end
1151
+
1152
+ # Mixin for series with single source dependency.
1153
+ #
1154
+ # Provides `source` and `source=` accessors for series that depend on
1155
+ # one upstream source serie.
1156
+ #
1157
+ # @example Usage
1158
+ # class ProcessorSerie
1159
+ # include Serie::Base
1160
+ # include Serie::WithSource
1161
+ #
1162
+ # def initialize(source)
1163
+ # self.source = source
1164
+ # init
1165
+ # end
1166
+ # end
1167
+ #
1168
+ # @example Custom accessor name using alias
1169
+ # class MyCustomSerie
1170
+ # include Serie::Base
1171
+ # include Serie::WithSource
1172
+ # alias my_source source
1173
+ # alias my_source= source=
1174
+ # end
1175
+ #
1176
+ # @see WithSources For multiple source dependencies
1177
+ module WithSource
1178
+ private def has_source; true; end
1179
+ private def mandatory_source; true; end
1180
+
1181
+ # @return [Serie, nil] the upstream source serie
1182
+ def source
1183
+ @source
1184
+ end
1185
+
1186
+ # Sets the source serie.
1187
+ #
1188
+ # @param serie [Serie] source serie (must match current state)
1189
+ # @raise [ArgumentError] if state mismatch between source and this serie
1190
+ def source=(serie)
1191
+ unless @source.nil? || @source.undefined? || serie.state == @source.state
1192
+ raise ArgumentError, "New serie for source should be a #{@state} instead of a #{serie.state}"
1193
+ end
1194
+ @source = serie
1195
+ mark_regarding! @source
1196
+ end
1197
+ end
1198
+
1199
+ # Mixin for series with multiple source dependencies.
1200
+ #
1201
+ # Provides `sources` and `sources=` accessors for series that depend on
1202
+ # multiple upstream series (as Array or Hash).
1203
+ #
1204
+ # @example Usage with Array
1205
+ # class MergeSerie
1206
+ # include Serie::Base
1207
+ # include Serie::WithSources
1208
+ #
1209
+ # def initialize(*series)
1210
+ # self.sources = series
1211
+ # init
1212
+ # end
1213
+ # end
1214
+ #
1215
+ # @example Usage with Hash
1216
+ # class NamedSourcesSerie
1217
+ # include Serie::Base
1218
+ # include Serie::WithSources
1219
+ #
1220
+ # def initialize(melody:, rhythm:)
1221
+ # self.sources = { melody: melody, rhythm: rhythm }
1222
+ # init
1223
+ # end
1224
+ # end
1225
+ #
1226
+ # @see WithSource For single source dependency
1227
+ module WithSources
1228
+ private def has_sources; true; end
1229
+ private def mandatory_sources; true; end
1230
+
1231
+ # @return [Array<Serie>, Hash{Symbol => Serie}, nil] upstream source series
1232
+ def sources
1233
+ @sources
1234
+ end
1235
+
1236
+ # Sets the sources series.
1237
+ #
1238
+ # @param series [Array<Serie>, Hash{Symbol => Serie}] source series
1239
+ # @raise [ArgumentError] if series is not a Hash or Array
1240
+ def sources=(series)
1241
+ unless series.is_a?(Hash) || series.is_a?(Array)
1242
+ raise ArgumentError, "New series for sources should be a Hash or an Array instead of a #{series.class.name}"
1243
+ end
1244
+ @sources = series
1245
+ send(:try_to_resolve_undefined_state_if_needed)
1246
+ end
1247
+ end
1248
+
1249
+ # Mixin for series with block/proc attribute (simple version).
1250
+ #
1251
+ # Provides `proc` accessor for series that use a Proc directly
1252
+ # without SmartProcBinder wrapping.
1253
+ #
1254
+ # @example Usage
1255
+ # class MapperSerie
1256
+ # include Serie::Base
1257
+ # include Serie::WithBlock
1258
+ #
1259
+ # def initialize(&block)
1260
+ # self.proc = block
1261
+ # init
1262
+ # end
1263
+ # end
1264
+ #
1265
+ # @see WithSmartBlock For SmartProcBinder-wrapped blocks
1266
+ module WithBlock
1267
+ # Gets or sets the proc.
1268
+ #
1269
+ # @overload proc
1270
+ # @return [Proc, nil] the stored proc
1271
+ # @overload proc(&block)
1272
+ # @param block [Proc] block to store
1273
+ # @return [Proc] the stored block
1274
+ def proc(&block)
1275
+ block ? (@block = block) : @block
1276
+ end
1277
+
1278
+ # Sets the proc directly.
1279
+ #
1280
+ # @param block [Proc] block to store
1281
+ def proc=(block)
1282
+ @block = block
1283
+ end
1284
+ end
1285
+
1286
+ # Mixin for series with SmartProcBinder-wrapped block attribute.
1287
+ #
1288
+ # Similar to {WithBlock} but wraps procs in SmartProcBinder for
1289
+ # flexible parameter binding.
1290
+ #
1291
+ # @example Usage
1292
+ # class SmartMapperSerie
1293
+ # include Serie::Base
1294
+ # include Serie::WithSmartBlock
1295
+ #
1296
+ # def initialize(&block)
1297
+ # self.proc = block
1298
+ # init
1299
+ # end
1300
+ # end
1301
+ #
1302
+ # @see WithBlock For simple proc storage without wrapping
1303
+ # @see Extension::SmartProcBinder::SmartProcBinder
1304
+ module WithSmartBlock
1305
+ # Gets or sets the proc with SmartProcBinder wrapping.
1306
+ #
1307
+ # @overload proc
1308
+ # @return [Proc, nil] the underlying proc (unwrapped)
1309
+ # @overload proc(&block)
1310
+ # @param block [Proc] block to wrap and store
1311
+ # @return [Extension::SmartProcBinder::SmartProcBinder] the wrapped binder
1312
+ def proc(&block)
1313
+ if block
1314
+ @block = Extension::SmartProcBinder::SmartProcBinder.new(block)
1315
+ else
1316
+ @block&.proc
1317
+ end
1318
+ end
1319
+
1320
+ # Sets the proc with SmartProcBinder wrapping.
1321
+ #
1322
+ # @param block [Proc] block to wrap and store
1323
+ def proc=(block)
1324
+ @block = Extension::SmartProcBinder::SmartProcBinder.new(block)
1325
+ end
1326
+ end
1317
1327
  end
1318
1328
  end
1319
1329
  end
@@ -57,7 +57,8 @@ module Musa
57
57
  end
58
58
 
59
59
  class SyncBufferSerie
60
- include Series::Serie.with(source: true)
60
+ include Series::Serie::Base
61
+ include Series::Serie::WithSource
61
62
 
62
63
  def initialize(serie)
63
64
  self.source = serie
@@ -97,7 +98,7 @@ module Musa
97
98
  end
98
99
 
99
100
  class Buffer
100
- include Series::Serie.base
101
+ include Series::Serie::Base
101
102
 
102
103
  def initialize(history)
103
104
  @history = history
@@ -145,7 +146,8 @@ module Musa
145
146
  # modo fill_on_restart: cuando una serie hace restart, las demás no se ven afectadas porque siguen recibiendo
146
147
  # todos los elementos de la serie original
147
148
 
148
- include Series::Serie.with(source: true)
149
+ include Series::Serie::Base
150
+ include Series::Serie::WithSource
149
151
 
150
152
  def initialize(serie)
151
153
  self.source = serie
@@ -243,10 +245,13 @@ module Musa
243
245
  end
244
246
 
245
247
  class Buffer
246
- include Series::Serie.with(source: true, private_source: true)
248
+ include Series::Serie::Base
249
+ include Series::Serie::WithSource
250
+
251
+ private :source, :source=
247
252
 
248
253
  def initialize(base)
249
- self.source = base
254
+ send(:source=, base)
250
255
  init
251
256
  end
252
257
 
@@ -47,7 +47,8 @@ module Musa
47
47
  end
48
48
 
49
49
  class Splitter
50
- include Series::Serie.with(source: true)
50
+ include Series::Serie::Base
51
+ include Series::Serie::WithSource
51
52
  include Enumerable
52
53
 
53
54
  private def has_source; true; end
@@ -74,7 +75,7 @@ module Musa
74
75
  # @param key_or_index [Symbol, Integer] hash key or array index
75
76
  #
76
77
  # @return [Split] component serie
77
- def [](key_or_index)
78
+ def get(key_or_index)
78
79
  raise "Can't get a component because Splitter is a prototype. To get a component you need a Splitter instance." unless instance?
79
80
 
80
81
  if @series.key?(key_or_index)
@@ -84,6 +85,8 @@ module Musa
84
85
  end
85
86
  end
86
87
 
88
+ alias_method :[], :get
89
+
87
90
  # Iterates over component series.
88
91
  #
89
92
  # @yield [key, split] for hash mode, [split] for array mode
@@ -233,7 +236,7 @@ module Musa
233
236
  private_constant :SplitterProxy
234
237
 
235
238
  class Split
236
- include Series::Serie.base
239
+ include Series::Serie::Base
237
240
 
238
241
  def initialize(proxy, key_or_index)
239
242
  @proxy = proxy