musa-dsl 0.40.0 → 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 +2 -0
- data/Gemfile +0 -1
- data/docs/subsystems/music.md +326 -15
- data/lib/musa-dsl/generative/darwin.rb +36 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +28 -0
- data/lib/musa-dsl/generative/markov.rb +2 -0
- data/lib/musa-dsl/generative/rules.rb +54 -0
- data/lib/musa-dsl/generative/variatio.rb +69 -0
- data/lib/musa-dsl/midi/midi-recorder.rb +4 -0
- data/lib/musa-dsl/midi/midi-voices.rb +10 -0
- data/lib/musa-dsl/music/chords.rb +54 -9
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -521
- 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 +427 -0
- data/lib/musa-dsl/series/buffer-serie.rb +6 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +23 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +12 -0
- data/lib/musa-dsl/series/queue-serie.rb +13 -0
- data/lib/musa-dsl/version.rb +2 -1
- data/musa-dsl.gemspec +20 -15
- metadata +85 -22
|
@@ -149,6 +149,8 @@ module Musa
|
|
|
149
149
|
# cut 'validate' { |obj| prune if invalid?(obj) }
|
|
150
150
|
# ended_when { |obj| complete?(obj) }
|
|
151
151
|
# end
|
|
152
|
+
#
|
|
153
|
+
# @return [void]
|
|
152
154
|
def initialize(&block)
|
|
153
155
|
@dsl = RulesEvalContext.new(&block)
|
|
154
156
|
end
|
|
@@ -257,6 +259,7 @@ module Musa
|
|
|
257
259
|
# @return [Array<CutRule>] cut rules
|
|
258
260
|
attr_reader :_grow_rules, :_ended_when, :_cut_rules
|
|
259
261
|
|
|
262
|
+
# @return [void]
|
|
260
263
|
# @api private
|
|
261
264
|
def initialize(&block)
|
|
262
265
|
@_grow_rules = []
|
|
@@ -296,10 +299,24 @@ module Musa
|
|
|
296
299
|
self
|
|
297
300
|
end
|
|
298
301
|
|
|
302
|
+
# Checks if end condition is defined.
|
|
303
|
+
#
|
|
304
|
+
# @return [Boolean] true if ended_when was called
|
|
305
|
+
#
|
|
306
|
+
# @api private
|
|
299
307
|
def _has_ending?
|
|
300
308
|
!@_ended_when.nil?
|
|
301
309
|
end
|
|
302
310
|
|
|
311
|
+
# Evaluates end condition for object.
|
|
312
|
+
#
|
|
313
|
+
# @param object [Object] object to check
|
|
314
|
+
# @param history [Array] object history
|
|
315
|
+
# @param parameters [Hash] additional parameters
|
|
316
|
+
#
|
|
317
|
+
# @return [Boolean] true if object should end
|
|
318
|
+
#
|
|
319
|
+
# @api private
|
|
303
320
|
def _ended?(object, history, **parameters)
|
|
304
321
|
if @_ended_when
|
|
305
322
|
with object, history, **parameters, &@_ended_when
|
|
@@ -311,11 +328,21 @@ module Musa
|
|
|
311
328
|
class GrowRule
|
|
312
329
|
attr_reader :name
|
|
313
330
|
|
|
331
|
+
# @param name [String] rule name
|
|
332
|
+
#
|
|
333
|
+
# @return [void]
|
|
314
334
|
def initialize(name, &block)
|
|
315
335
|
@name = name
|
|
316
336
|
@block = block
|
|
317
337
|
end
|
|
318
338
|
|
|
339
|
+
# Generates possible branched objects.
|
|
340
|
+
#
|
|
341
|
+
# @param object [Object] object to branch
|
|
342
|
+
# @param history [Array] object history
|
|
343
|
+
# @param parameters [Hash] additional parameters
|
|
344
|
+
#
|
|
345
|
+
# @return [Array<Object>] branched objects
|
|
319
346
|
def generate_possibilities(object, history, **parameters)
|
|
320
347
|
# TODO: optimize context using only one instance for all genereate_possibilities calls
|
|
321
348
|
context = GrowRuleEvalContext.new
|
|
@@ -329,10 +356,16 @@ module Musa
|
|
|
329
356
|
|
|
330
357
|
attr_reader :_branches
|
|
331
358
|
|
|
359
|
+
# @return [void]
|
|
332
360
|
def initialize
|
|
333
361
|
@_branches = []
|
|
334
362
|
end
|
|
335
363
|
|
|
364
|
+
# Records a branched object.
|
|
365
|
+
#
|
|
366
|
+
# @param object [Object] branched object
|
|
367
|
+
#
|
|
368
|
+
# @return [self]
|
|
336
369
|
def branch(object)
|
|
337
370
|
@_branches << object
|
|
338
371
|
self
|
|
@@ -347,11 +380,21 @@ module Musa
|
|
|
347
380
|
class CutRule
|
|
348
381
|
attr_reader :reason
|
|
349
382
|
|
|
383
|
+
# @param reason [String] rejection reason
|
|
384
|
+
#
|
|
385
|
+
# @return [void]
|
|
350
386
|
def initialize(reason, &block)
|
|
351
387
|
@reason = reason
|
|
352
388
|
@block = block
|
|
353
389
|
end
|
|
354
390
|
|
|
391
|
+
# Checks if object should be rejected.
|
|
392
|
+
#
|
|
393
|
+
# @param object [Object] object to check
|
|
394
|
+
# @param history [Array] object history
|
|
395
|
+
# @param parameters [Hash] additional parameters
|
|
396
|
+
#
|
|
397
|
+
# @return [Array<String>, nil] rejection reasons or nil if not rejected
|
|
355
398
|
def rejects?(object, history, **parameters)
|
|
356
399
|
# TODO: optimize context using only one instance for all rejects? checks
|
|
357
400
|
context = CutRuleEvalContext.new
|
|
@@ -367,10 +410,16 @@ module Musa
|
|
|
367
410
|
|
|
368
411
|
attr_reader :_secondary_reasons
|
|
369
412
|
|
|
413
|
+
# @return [void]
|
|
370
414
|
def initialize
|
|
371
415
|
@_secondary_reasons = []
|
|
372
416
|
end
|
|
373
417
|
|
|
418
|
+
# Marks object for pruning.
|
|
419
|
+
#
|
|
420
|
+
# @param secondary_reason [String, nil] additional reason detail
|
|
421
|
+
#
|
|
422
|
+
# @return [self]
|
|
374
423
|
def prune(secondary_reason = nil)
|
|
375
424
|
@_secondary_reasons << secondary_reason
|
|
376
425
|
self
|
|
@@ -399,6 +448,11 @@ module Musa
|
|
|
399
448
|
class Node
|
|
400
449
|
attr_reader :parent, :children, :object, :rejected
|
|
401
450
|
|
|
451
|
+
# @param object [Object, nil] node object
|
|
452
|
+
# @param parent [Node, nil] parent node
|
|
453
|
+
#
|
|
454
|
+
# @return [void]
|
|
455
|
+
#
|
|
402
456
|
# @api private
|
|
403
457
|
def initialize(object = nil, parent = nil)
|
|
404
458
|
@parent = parent
|
|
@@ -143,6 +143,8 @@ module Musa
|
|
|
143
143
|
# field :y, [:a, :b]
|
|
144
144
|
# constructor { |x:, y:| { x: x, y: y } }
|
|
145
145
|
# end
|
|
146
|
+
#
|
|
147
|
+
# @return [void]
|
|
146
148
|
def initialize(instance_name, &block)
|
|
147
149
|
raise ArgumentError, 'instance_name should be a symbol' unless instance_name.is_a?(Symbol)
|
|
148
150
|
raise ArgumentError, 'block is needed' unless block
|
|
@@ -259,6 +261,11 @@ module Musa
|
|
|
259
261
|
|
|
260
262
|
private
|
|
261
263
|
|
|
264
|
+
# Generates evaluation tree for parameter calculation.
|
|
265
|
+
#
|
|
266
|
+
# @param fieldset [Fieldset] fieldset to process
|
|
267
|
+
#
|
|
268
|
+
# @return [A, nil] root node of evaluation tree
|
|
262
269
|
def generate_eval_tree_A(fieldset)
|
|
263
270
|
root = nil
|
|
264
271
|
current = nil
|
|
@@ -279,6 +286,11 @@ module Musa
|
|
|
279
286
|
root
|
|
280
287
|
end
|
|
281
288
|
|
|
289
|
+
# Generates evaluation tree for attribute application.
|
|
290
|
+
#
|
|
291
|
+
# @param fieldset [Fieldset] fieldset to process
|
|
292
|
+
#
|
|
293
|
+
# @return [B] root node of attribute tree
|
|
282
294
|
def generate_eval_tree_B(fieldset)
|
|
283
295
|
affected_field_names = []
|
|
284
296
|
inner = []
|
|
@@ -298,12 +310,19 @@ module Musa
|
|
|
298
310
|
attr_reader :parameter_name, :options
|
|
299
311
|
attr_accessor :inner
|
|
300
312
|
|
|
313
|
+
# @param parameter_name [Symbol] parameter name
|
|
314
|
+
# @param options [Array] option values
|
|
315
|
+
#
|
|
316
|
+
# @return [void]
|
|
301
317
|
def initialize(parameter_name, options)
|
|
302
318
|
@parameter_name = parameter_name
|
|
303
319
|
@options = options
|
|
304
320
|
@inner = nil
|
|
305
321
|
end
|
|
306
322
|
|
|
323
|
+
# Calculates all parameter combinations.
|
|
324
|
+
#
|
|
325
|
+
# @return [Array<Hash>] parameter combination hashes
|
|
307
326
|
def calc_parameters
|
|
308
327
|
unless @calc_parameters
|
|
309
328
|
if inner
|
|
@@ -320,16 +339,19 @@ module Musa
|
|
|
320
339
|
private_constant :A
|
|
321
340
|
|
|
322
341
|
class A1 < A
|
|
342
|
+
# @return [void]
|
|
323
343
|
def initialize(parameter_name, options)
|
|
324
344
|
super parameter_name, options
|
|
325
345
|
|
|
326
346
|
@own_parameters = @options.collect { |option| { @parameter_name => option } }
|
|
327
347
|
end
|
|
328
348
|
|
|
349
|
+
# @return [Array<Hash>] own parameter combinations
|
|
329
350
|
def calc_own_parameters
|
|
330
351
|
@own_parameters
|
|
331
352
|
end
|
|
332
353
|
|
|
354
|
+
# @return [String] string representation
|
|
333
355
|
def inspect
|
|
334
356
|
"A1 name: #{@parameter_name}, options: #{@options}, inner: #{@inner || 'nil'}"
|
|
335
357
|
end
|
|
@@ -340,6 +362,11 @@ module Musa
|
|
|
340
362
|
private_constant :A1
|
|
341
363
|
|
|
342
364
|
class A2 < A
|
|
365
|
+
# @param parameter_name [Symbol] parameter name
|
|
366
|
+
# @param options [Array] option values
|
|
367
|
+
# @param subcomponent [A] nested tree
|
|
368
|
+
#
|
|
369
|
+
# @return [void]
|
|
343
370
|
def initialize(parameter_name, options, subcomponent)
|
|
344
371
|
super parameter_name, options
|
|
345
372
|
|
|
@@ -361,10 +388,12 @@ module Musa
|
|
|
361
388
|
@own_parameters = result
|
|
362
389
|
end
|
|
363
390
|
|
|
391
|
+
# @return [Array<Hash>] own parameter combinations
|
|
364
392
|
def calc_own_parameters
|
|
365
393
|
@own_parameters
|
|
366
394
|
end
|
|
367
395
|
|
|
396
|
+
# @return [String] string representation
|
|
368
397
|
def inspect
|
|
369
398
|
"A2 name: #{@parameter_name}, options: #{@options}, subcomponent: #{@subcomponent}, inner: #{@inner || 'nil'}"
|
|
370
399
|
end
|
|
@@ -383,6 +412,13 @@ module Musa
|
|
|
383
412
|
class B
|
|
384
413
|
attr_reader :parameter_name, :options, :affected_field_names, :blocks, :inner
|
|
385
414
|
|
|
415
|
+
# @param parameter_name [Symbol] parameter name
|
|
416
|
+
# @param options [Array] option values
|
|
417
|
+
# @param affected_field_names [Array<Symbol>] field names affected
|
|
418
|
+
# @param inner [Array<B>] nested B nodes
|
|
419
|
+
# @param blocks [Array<Proc>] with_attributes blocks
|
|
420
|
+
#
|
|
421
|
+
# @return [void]
|
|
386
422
|
def initialize(parameter_name, options, affected_field_names, inner, blocks)
|
|
387
423
|
@parameter_name = parameter_name
|
|
388
424
|
@options = options
|
|
@@ -392,6 +428,12 @@ module Musa
|
|
|
392
428
|
@procedures = blocks.collect { |proc| Musa::Extension::SmartProcBinder::SmartProcBinder.new proc }
|
|
393
429
|
end
|
|
394
430
|
|
|
431
|
+
# Runs attribute application for this node.
|
|
432
|
+
#
|
|
433
|
+
# @param parameters_with_depth [Hash] parameters with nesting depth
|
|
434
|
+
# @param parent_parameters [Hash, nil] parent context parameters
|
|
435
|
+
#
|
|
436
|
+
# @return [void]
|
|
395
437
|
def run(parameters_with_depth, parent_parameters = nil)
|
|
396
438
|
parent_parameters ||= {}
|
|
397
439
|
|
|
@@ -417,6 +459,7 @@ module Musa
|
|
|
417
459
|
end
|
|
418
460
|
end
|
|
419
461
|
|
|
462
|
+
# @return [String] string representation
|
|
420
463
|
def inspect
|
|
421
464
|
"B name: #{@parameter_name}, options: #{@options}, affected_field_names: #{@affected_field_names}, blocks_size: #{@blocks.size}, inner: #{@inner}"
|
|
422
465
|
end
|
|
@@ -435,6 +478,10 @@ module Musa
|
|
|
435
478
|
# @return [Fieldset] defined fieldset
|
|
436
479
|
attr_reader :_fieldset
|
|
437
480
|
|
|
481
|
+
# @param name [Symbol] fieldset name
|
|
482
|
+
# @param options [Array, Range, nil] fieldset options
|
|
483
|
+
#
|
|
484
|
+
# @return [void]
|
|
438
485
|
# @api private
|
|
439
486
|
def initialize(name, options = nil, &block)
|
|
440
487
|
@_fieldset = Fieldset.new name, options.arrayfy.explode_ranges
|
|
@@ -446,6 +493,8 @@ module Musa
|
|
|
446
493
|
#
|
|
447
494
|
# @param name [Symbol] field name
|
|
448
495
|
# @param options [Array, Range, nil] field option values
|
|
496
|
+
#
|
|
497
|
+
# @return [void]
|
|
449
498
|
# @api private
|
|
450
499
|
def field(name, options = nil)
|
|
451
500
|
@_fieldset.components << Field.new(name, options.arrayfy.explode_ranges)
|
|
@@ -455,7 +504,10 @@ module Musa
|
|
|
455
504
|
#
|
|
456
505
|
# @param name [Symbol] fieldset name
|
|
457
506
|
# @param options [Array, Range, nil] fieldset option values
|
|
507
|
+
#
|
|
458
508
|
# @yield fieldset DSL block
|
|
509
|
+
#
|
|
510
|
+
# @return [void]
|
|
459
511
|
# @api private
|
|
460
512
|
def fieldset(name, options = nil, &block)
|
|
461
513
|
fieldset_context = FieldsetContext.new name, options, &block
|
|
@@ -465,6 +517,8 @@ module Musa
|
|
|
465
517
|
# Adds attribute modification block.
|
|
466
518
|
#
|
|
467
519
|
# @yield attribute modification block
|
|
520
|
+
#
|
|
521
|
+
# @return [void]
|
|
468
522
|
# @api private
|
|
469
523
|
def with_attributes(&block)
|
|
470
524
|
@_fieldset.with_attributes << block
|
|
@@ -481,6 +535,7 @@ module Musa
|
|
|
481
535
|
# @return [Proc, nil] finalize block
|
|
482
536
|
attr_reader :_constructor, :_finalize
|
|
483
537
|
|
|
538
|
+
# @return [void]
|
|
484
539
|
# @api private
|
|
485
540
|
def initialize(&block)
|
|
486
541
|
@_constructor = nil
|
|
@@ -492,6 +547,8 @@ module Musa
|
|
|
492
547
|
# Defines object constructor.
|
|
493
548
|
#
|
|
494
549
|
# @yield constructor block receiving field values
|
|
550
|
+
#
|
|
551
|
+
# @return [void]
|
|
495
552
|
# @api private
|
|
496
553
|
def constructor(&block)
|
|
497
554
|
@_constructor = block
|
|
@@ -500,6 +557,8 @@ module Musa
|
|
|
500
557
|
# Defines finalize block.
|
|
501
558
|
#
|
|
502
559
|
# @yield finalize block receiving completed object
|
|
560
|
+
#
|
|
561
|
+
# @return [void]
|
|
503
562
|
# @api private
|
|
504
563
|
def finalize(&block)
|
|
505
564
|
@_finalize = block
|
|
@@ -512,6 +571,7 @@ module Musa
|
|
|
512
571
|
attr_reader :name
|
|
513
572
|
attr_accessor :options
|
|
514
573
|
|
|
574
|
+
# @return [String] string representation
|
|
515
575
|
def inspect
|
|
516
576
|
"Field #{@name} options: #{@options}"
|
|
517
577
|
end
|
|
@@ -520,6 +580,10 @@ module Musa
|
|
|
520
580
|
|
|
521
581
|
private
|
|
522
582
|
|
|
583
|
+
# @param name [Symbol] field name
|
|
584
|
+
# @param options [Array] option values
|
|
585
|
+
#
|
|
586
|
+
# @return [void]
|
|
523
587
|
def initialize(name, options)
|
|
524
588
|
@name = name
|
|
525
589
|
@options = options
|
|
@@ -532,6 +596,7 @@ module Musa
|
|
|
532
596
|
attr_reader :name, :with_attributes, :components
|
|
533
597
|
attr_accessor :options
|
|
534
598
|
|
|
599
|
+
# @return [String] string representation
|
|
535
600
|
def inspect
|
|
536
601
|
"Fieldset #{@name} options: #{@options} components: #{@components}"
|
|
537
602
|
end
|
|
@@ -540,6 +605,10 @@ module Musa
|
|
|
540
605
|
|
|
541
606
|
private
|
|
542
607
|
|
|
608
|
+
# @param name [Symbol] fieldset name
|
|
609
|
+
# @param options [Array, nil] option values
|
|
610
|
+
#
|
|
611
|
+
# @return [void]
|
|
543
612
|
def initialize(name, options)
|
|
544
613
|
@name = name
|
|
545
614
|
@options = options || [nil]
|
|
@@ -79,6 +79,8 @@ module Musa
|
|
|
79
79
|
# @see https://github.com/javier-sy/midi-events MIDIEvents documentation
|
|
80
80
|
class MIDIRecorder
|
|
81
81
|
# @param sequencer [Musa::Sequencer::Sequencer] provides the musical position for each recorded message.
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
82
84
|
def initialize(sequencer)
|
|
83
85
|
@sequencer = sequencer
|
|
84
86
|
@midi_parser = MIDIParser.new
|
|
@@ -179,6 +181,8 @@ module Musa
|
|
|
179
181
|
#
|
|
180
182
|
# @param position [Rational] sequencer position when captured.
|
|
181
183
|
# @param message [MIDIEvents::Event] parsed MIDI event.
|
|
184
|
+
#
|
|
185
|
+
# @return [void]
|
|
182
186
|
def initialize(position, message)
|
|
183
187
|
@position = position
|
|
184
188
|
@message = message
|
|
@@ -97,6 +97,8 @@ module Musa
|
|
|
97
97
|
# @param output [#puts, nil] anything responding to `puts` that accepts `MIDIEvents::Event`s (typically a MIDICommunications output).
|
|
98
98
|
# @param channels [Array<Numeric>, Range, Numeric] list of MIDI channels to control. Ranges are expanded automatically.
|
|
99
99
|
# @param do_log [Boolean] enables info level logs per emitted message.
|
|
100
|
+
#
|
|
101
|
+
# @return [void]
|
|
100
102
|
def initialize(sequencer:, output:, channels:, do_log: nil)
|
|
101
103
|
do_log ||= false
|
|
102
104
|
|
|
@@ -200,6 +202,8 @@ module Musa
|
|
|
200
202
|
# @param output [#puts, nil]
|
|
201
203
|
# @param channel [Integer] MIDI channel number (0-15).
|
|
202
204
|
# @param name [String, nil] human friendly identifier.
|
|
205
|
+
#
|
|
206
|
+
# @return [void]
|
|
203
207
|
def initialize(sequencer:, output:, channel:, name: nil, do_log: nil)
|
|
204
208
|
do_log ||= false
|
|
205
209
|
|
|
@@ -337,6 +341,8 @@ module Musa
|
|
|
337
341
|
class ControllersControl
|
|
338
342
|
# @param output [#puts] MIDI output.
|
|
339
343
|
# @param channel [Integer] MIDI channel number.
|
|
344
|
+
#
|
|
345
|
+
# @return [void]
|
|
340
346
|
def initialize(output, channel)
|
|
341
347
|
@output = output
|
|
342
348
|
@channel = channel
|
|
@@ -368,6 +374,8 @@ module Musa
|
|
|
368
374
|
#
|
|
369
375
|
# @param controller_number_or_symbol [Integer, Symbol] CC number or well-known alias (see +@controller_map+).
|
|
370
376
|
# @param value [Integer] byte value that will be clamped to 0-127.
|
|
377
|
+
#
|
|
378
|
+
# @return [Integer] clamped value
|
|
371
379
|
def []=(controller_number_or_symbol, value)
|
|
372
380
|
number = number_of(controller_number_or_symbol)
|
|
373
381
|
value ||= 0
|
|
@@ -429,6 +437,8 @@ module Musa
|
|
|
429
437
|
# @param velocity [Numeric, Array<Numeric>] on velocity (can be per-note).
|
|
430
438
|
# @param duration [Numeric, nil] duration in bars or nil for infinite.
|
|
431
439
|
# @param velocity_off [Numeric, Array<Numeric>] release velocity.
|
|
440
|
+
#
|
|
441
|
+
# @return [void]
|
|
432
442
|
def initialize(voice, pitch:, velocity: nil, duration: nil, velocity_off: nil)
|
|
433
443
|
raise ArgumentError, "MIDIVoice: note duration should be nil or Numeric: #{duration} (#{duration.class})" unless duration.nil? || duration.is_a?(Numeric)
|
|
434
444
|
|
|
@@ -429,32 +429,41 @@ module Musa
|
|
|
429
429
|
Chord.new(@root.octave(octave), @scale, chord_definition, @move, @duplicate, source_notes_map)
|
|
430
430
|
end
|
|
431
431
|
|
|
432
|
-
# Creates new chord with positions moved to different octaves.
|
|
432
|
+
# Creates new chord with positions moved to different octaves, or returns current move settings.
|
|
433
433
|
#
|
|
434
|
-
#
|
|
435
|
-
# other positions unchanged. Multiple positions can be
|
|
436
|
-
# Merges with existing moves.
|
|
434
|
+
# When called with arguments, relocates specific chord positions to different
|
|
435
|
+
# octaves while keeping other positions unchanged. Multiple positions can be
|
|
436
|
+
# moved at once. Merges with existing moves.
|
|
437
|
+
#
|
|
438
|
+
# When called without arguments, returns the current move settings hash.
|
|
437
439
|
#
|
|
438
440
|
# @param octaves [Hash{Symbol => Integer}] position to octave offset mapping
|
|
439
|
-
# @return [Chord] new chord with moved positions
|
|
441
|
+
# @return [Chord, Hash] new chord with moved positions, or current move settings if no arguments
|
|
440
442
|
#
|
|
441
443
|
# @example Move root down, seventh up
|
|
442
444
|
# chord.move(root: -1, seventh: 1)
|
|
443
445
|
#
|
|
444
446
|
# @example Drop voicing (move third and seventh down)
|
|
445
447
|
# chord.move(third: -1, seventh: -1)
|
|
448
|
+
#
|
|
449
|
+
# @example Get current move settings
|
|
450
|
+
# chord.move # => { root: -1 }
|
|
446
451
|
def move(**octaves)
|
|
452
|
+
return @move if octaves.empty?
|
|
453
|
+
|
|
447
454
|
Chord.new(@root, @scale, @chord_definition, @move.merge(octaves), @duplicate, @source_notes_map)
|
|
448
455
|
end
|
|
449
456
|
|
|
450
|
-
# Creates new chord with positions duplicated in other octaves.
|
|
457
|
+
# Creates new chord with positions duplicated in other octaves, or returns current duplicate settings.
|
|
451
458
|
#
|
|
452
|
-
#
|
|
453
|
-
# Original positions remain at their current octave.
|
|
459
|
+
# When called with arguments, adds copies of specific chord positions in
|
|
460
|
+
# different octaves. Original positions remain at their current octave.
|
|
454
461
|
# Merges with existing duplications.
|
|
455
462
|
#
|
|
463
|
+
# When called without arguments, returns the current duplicate settings hash.
|
|
464
|
+
#
|
|
456
465
|
# @param octaves [Hash{Symbol => Integer, Array<Integer>}] position to octave(s)
|
|
457
|
-
# @return [Chord] new chord with duplicated positions
|
|
466
|
+
# @return [Chord, Hash] new chord with duplicated positions, or current duplicate settings if no arguments
|
|
458
467
|
#
|
|
459
468
|
# @example Duplicate root two octaves down
|
|
460
469
|
# chord.duplicate(root: -2)
|
|
@@ -464,10 +473,46 @@ module Musa
|
|
|
464
473
|
#
|
|
465
474
|
# @example Duplicate multiple positions
|
|
466
475
|
# chord.duplicate(root: -1, fifth: 1)
|
|
476
|
+
#
|
|
477
|
+
# @example Get current duplicate settings
|
|
478
|
+
# chord.duplicate # => { root: -1 }
|
|
467
479
|
def duplicate(**octaves)
|
|
480
|
+
return @duplicate if octaves.empty?
|
|
481
|
+
|
|
468
482
|
Chord.new(@root, @scale, @chord_definition, @move, @duplicate.merge(octaves), @source_notes_map)
|
|
469
483
|
end
|
|
470
484
|
|
|
485
|
+
# Finds this chord in other scales.
|
|
486
|
+
#
|
|
487
|
+
# Searches through scale kinds matching the given metadata criteria to find
|
|
488
|
+
# all scales that contain this chord. Returns new chord instances, each with
|
|
489
|
+
# its containing scale as context.
|
|
490
|
+
#
|
|
491
|
+
# @param roots [Range, Array, nil] pitch offsets to search (default: full octave)
|
|
492
|
+
# @param metadata [Hash] metadata filters for scale kinds (family:, brightness:, etc.)
|
|
493
|
+
# @return [Array<Chord>] this chord in different scale contexts
|
|
494
|
+
#
|
|
495
|
+
# @example Find G major triad in diatonic scales
|
|
496
|
+
# g_triad = c_major.dominant.chord
|
|
497
|
+
# g_triad.in_scales(family: :diatonic)
|
|
498
|
+
#
|
|
499
|
+
# @example Find chord in scales with specific brightness
|
|
500
|
+
# g7.in_scales(brightness: -1..1)
|
|
501
|
+
#
|
|
502
|
+
# @example Iterate over results
|
|
503
|
+
# g7.in_scales(family: :greek_modes).each do |chord|
|
|
504
|
+
# scale = chord.scale
|
|
505
|
+
# degree = scale.degree_of_chord(chord)
|
|
506
|
+
# puts "#{scale.kind.class.id} on #{scale.root_pitch}: degree #{degree}"
|
|
507
|
+
# end
|
|
508
|
+
#
|
|
509
|
+
# @see Musa::Scales::Scale#chord_on
|
|
510
|
+
# @see Musa::Scales::ScaleSystemTuning#chords_of
|
|
511
|
+
def in_scales(roots: nil, **metadata)
|
|
512
|
+
tuning = @scale&.kind&.tuning || @root.scale.kind.tuning
|
|
513
|
+
tuning.chords_of(self, roots: roots, **metadata)
|
|
514
|
+
end
|
|
515
|
+
|
|
471
516
|
# Checks chord equality.
|
|
472
517
|
#
|
|
473
518
|
# Chords are equal if they have the same notes and chord definition.
|