Module: Musa::Variatio

Included in:
All
Defined in:
lib/musa-dsl/generative/variatio.rb

Overview

Combinatorial variation generator with Cartesian product.

Variatio generates all possible combinations of parameter values across defined fields, creating comprehensive variation sets. Uses Cartesian product to produce exhaustive parameter combinations, then constructs objects and applies attribute modifications.

Core Concepts

  • Fields: Named parameters with option sets
  • Fieldsets: Nested field groups with their own options
  • Constructor: Creates base objects from field values
  • with_attributes: Modifies objects with field/fieldset values
  • Finalize: Post-processes completed objects
  • Variations: All Cartesian product combinations

Generation Process

  1. Define: Specify fields, fieldsets, constructor, attributes, finalize
  2. Combine: Calculate Cartesian product of all field options
  3. Construct: Create objects using constructor with each combination
  4. Attribute: Apply with_attributes blocks for each combination
  5. Finalize: Run finalize block on completed objects
  6. Return: Array of all generated variations

Musical Applications

  • Generate all variations of a musical motif
  • Create comprehensive parameter sweeps for synthesis
  • Produce complete harmonic permutations
  • Build exhaustive rhythm pattern combinations

Examples:

Basic field variations

variatio = Musa::Variatio::Variatio.new :chord do
  field :root, [60, 64, 67]     # C, E, G
  field :type, [:major, :minor]

  constructor do |root:, type:|
    { root: root, type: type }
  end
end

variations = variatio.run
# => [
#   { root: 60, type: :major },
#   { root: 60, type: :minor },
#   { root: 64, type: :major },
#   { root: 64, type: :minor },
#   { root: 67, type: :major },
#   { root: 67, type: :minor }
# ]
# 3 roots × 2 types = 6 variations

Override field options at runtime

variatio = Musa::Variatio::Variatio.new :object do
  field :a, 1..10
  field :b, [:alfa, :beta, :gamma]

  constructor { |a:, b:| { a: a, b: b } }
end

# Override :a to limit variations
variatio.on(a: 1..3).size
# => 9  (3 values of :a × 3 of :b, instead of the 10 × 3 of a bare `run`)

Nested fieldsets with attributes

variatio = Musa::Variatio::Variatio.new :synth do
  field :wave, [:saw, :square]
  field :cutoff, [500, 1000, 2000]

  constructor do |wave:, cutoff:|
    { wave: wave, cutoff: cutoff, lfo: {} }
  end

  # Nested fieldset for LFO parameters
  fieldset :lfo, [:vibrato, :tremolo] do
    field :rate, [4, 8]
    field :depth, [0.1, 0.5]

    with_attributes do |synth:, lfo:, rate:, depth:|
      synth[:lfo][lfo] = { rate: rate, depth: depth }
    end
  end
end

variatio.run.size  # => 96

variatio.run.first
# => { wave: :saw, cutoff: 500,
#      lfo: { vibrato: { rate: 4, depth: 0.1 },
#             tremolo: { rate: 4, depth: 0.1 } } }

# 96 and not 48. A fieldset is not a CHOICE among its values -- every
# variation carries both :vibrato and :tremolo -- so its inner fields are
# combined once for EACH of them: (2 rates × 2 depths) squared is 16 per
# (wave, cutoff), and 2 × 3 × 16 = 96. Read as "× 2 lfo types × 2 rates ×
# 2 depths" it comes out at 48, which is the arithmetic of a choice.

With finalize block

variatio = Musa::Variatio::Variatio.new :note do
  field :pitch, [60, 62, 64]
  field :velocity, [64, 96, 127]

  constructor { |pitch:, velocity:| { pitch: pitch, velocity: velocity } }

  finalize do |note:|
    note[:loudness] = note[:velocity] / 127.0
    note[:dynamics] = case note[:velocity]
      when 0..48 then :pp
      when 49..80 then :mf
      else :ff
    end
  end
end

variations = variatio.run

variations.size                    # => 9
variations.first[:dynamics]        # => :mf
variations.first[:loudness].round(2)  # => 0.5

# `finalize` runs once per finished variation, after the constructor and
# after every fieldset has been applied -- which is why it can compute
# something from values the constructor never saw together.

See Also:

Defined Under Namespace

Classes: Variatio