Module: Musa::Series::Constructors

Extended by:
Constructors
Included in:
Musa::Series, Constructors
Defined in:
lib/musa-dsl/series/base-series.rb,
lib/musa-dsl/series/proxy-serie.rb,
lib/musa-dsl/series/queue-serie.rb,
lib/musa-dsl/series/timed-serie.rb,
lib/musa-dsl/series/quantizer-serie.rb,
lib/musa-dsl/series/main-serie-constructors.rb

Overview

Series constructor methods for creating series from various sources.

Provides factory methods for common serie types:

Basic Constructors

  • UNDEFINED - Undefined serie (unresolved state)
  • NIL - Serie that always returns nil
  • S - Serie from array of values
  • E - Serie from evaluation block

Collection Constructors

  • H/HC - Hash of series (hash/combined mode)
  • A/AC - Array of series (array/combined mode)
  • MERGE - Sequential merge of multiple series

Numeric Generators

  • FOR - For-loop style numeric sequence
  • RND - Random values (from array or range)
  • RND1 - Single random value
  • SIN - Sine wave function
  • FIBO - Fibonacci sequence

Musical Generators

  • HARMO - Harmonic note series

Usage Patterns

Array Serie

notes = S(60, 64, 67, 72)
notes.i.next_value  # => 60

Evaluation Block

counter = E(1) { |v, last_value:| last_value + 1 unless last_value == 10 }
counter.i.to_a  # => [1, 2, 3, ..., 10]

Random Values

dice = RND(1, 2, 3, 4, 5, 6)
dice.i.next_value  # => random 1-6

Numeric Sequences

sequence = FOR(from: 0, to: 10, step: 2)
sequence.i.to_a  # => [0, 2, 4, 6, 8, 10]

Combining Series

melody = MERGE(S(60, 64), S(67, 72))
melody.i.to_a  # => [60, 64, 67, 72]

Defined Under Namespace

Classes: FromArray, ProxySerie, QueueSerie, UndefinedSerie

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.A(*series) ⇒ FromArrayOfSeries

Creates array-mode serie from array of series.

Combines multiple series into array-structured values. Returns array of values from respective series. Stops when first serie exhausts.

Examples:

Array of series

a = A(S(1, 2, 3), S(10, 20, 30))
a.i.next_value  # => [1, 10]
a.i.next_value  # => [2, 20]


188
189
190
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 188

def A(*series)
  FromArrayOfSeries.new series, false
end

.AC(*series) ⇒ FromArrayOfSeries

Creates array-mode combined serie from array of series.

Like A but cycles all series. When a serie exhausts, it restarts from the beginning, continuing until all series complete their cycles.

Examples:

Combined cycling all series

ac = AC(S(1, 2), S(10, 20, 30))
ac.max_size(6).i.to_a  # => [[1, 10], [2, 20], [1, 30],
                        #     [2, 10], [1, 20], [2, 30]]


207
208
209
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 207

def AC(*series)
  FromArrayOfSeries.new series, true
end

.E(*value_args, **key_args) {|value_args, last_value, caller, key_args| ... } ⇒ FromEvalBlockWithParameters

Creates serie from evaluation block.

Calls block repeatedly with parameters and last_value. Block returns next value or nil to stop. Enables stateful generators and algorithms.

Block Parameters

  • value_args: Initial positional parameters
  • last_value: Previous return value (nil on first call)
  • caller: Serie instance (access to parameters attribute)
  • key_args: Initial keyword parameters

Examples:

Counter

counter = E(1) { |v, last_value:| last_value + 1 unless last_value == 5 }
counter.i.to_a  # => [1, 2, 3, 4, 5]

Fibonacci

fib = E { |last_value:, caller:|
  a, b = caller.parameters
  caller.parameters = [b, a + b]
  a
}
fib.parameters = [0, 1]
fib.i.to_a(limit: 10)  # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Yields:

  • block called for each value

Yield Parameters:

  • value_args (Array)

    current positional parameters

  • last_value (Object, nil)

    previous return value

  • caller (FromEvalBlockWithParameters)

    serie instance

  • key_args (Hash)

    current keyword parameters

Yield Returns:

  • (Object, nil)

    next value or nil to stop



248
249
250
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 248

def E(*value_args, **key_args, &block)
  FromEvalBlockWithParameters.new *value_args, **key_args, &block
end

.FIBOFibonacci

Creates Fibonacci sequence serie.

Generates classic Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, ... Infinite serie.

Examples:

Fibonacci numbers

fib = FIBO()
fib.infinite?  # => true
inst = fib.i
10.times.map { inst.next_value }
# => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Rhythmic proportions

durations = FIBO().i.map { |n| Rational(n, 16) }


456
457
458
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 456

def FIBO()
  Fibonacci.new
end

.FOR(from: nil, to: nil, step: nil) ⇒ ForLoop

Creates for-loop style numeric sequence.

Generates sequence from from to to (inclusive) with step increment. Automatically adjusts step sign based on from/to relationship.

Examples:

Ascending sequence

s = FOR(from: 0, to: 10, step: 2)
s.i.to_a  # => [0, 2, 4, 6, 8, 10]

Descending sequence

s = FOR(from: 10, to: 0, step: 2)
s.i.to_a  # => [10, 8, 6, 4, 2, 0]

Infinite sequence

s = FOR(from: 0, step: 1)  # to: nil
s.infinite?  # => true


276
277
278
279
280
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 276

def FOR(from: nil, to: nil, step: nil)
  from ||= 0
  step ||= 1
  ForLoop.new from, to, step
end

.H(**series_hash) ⇒ FromHashOfSeries

Creates hash-mode serie from hash of series.

Combines multiple series into hash-structured values. Returns hash with same keys, values from respective series. Stops when first serie exhausts.

Examples:

Hash of series

h = H(pitch: S(60, 64, 67), velocity: S(96, 80, 64))
h.i.next_value  # => {pitch: 60, velocity: 96}
h.i.next_value  # => {pitch: 64, velocity: 80}


150
151
152
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 150

def H(**series_hash)
  FromHashOfSeries.new series_hash, false
end

.HARMO(error: nil, extended: nil) ⇒ HarmonicNotes

Creates harmonic notes serie from fundamental.

Generates MIDI note numbers for harmonic series based on listened fundamental. Approximates harmonics to nearest semitone within error tolerance.

Parameters

  • error: Maximum cents deviation to accept harmonic (default: 0.5)
  • extended: Include extended harmonics beyond audible range

Examples:

Harmonic series

# Listen to fundamental, serie returns harmonic notes
harmonics = HARMO(error: 0.5)
harmonics.i  # Waits for fundamental input

Extended harmonics

harm = HARMO(error: 0.3, extended: true)


485
486
487
488
489
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 485

def HARMO(error: nil, extended: nil)
  error ||= 0.5
  extended ||= false
  HarmonicNotes.new error, extended
end

.HC(**series_hash) ⇒ FromHashOfSeries

Creates hash-mode combined serie from hash of series.

Like H but cycles all series. When a serie exhausts, it restarts from the beginning, continuing until all series complete their cycles.

Examples:

Combined cycling all series

hc = HC(a: S(1, 2), b: S(10, 20, 30))
hc.max_size(6).i.to_a  # => [{a:1, b:10}, {a:2, b:20}, {a:1, b:30},
                        #     {a:2, b:10}, {a:1, b:20}, {a:2, b:30}]


169
170
171
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 169

def HC(**series_hash)
  FromHashOfSeries.new series_hash, true
end

.MERGE(*series) ⇒ Sequence

Merges multiple series sequentially.

Plays series in sequence: first serie until exhausted, then second, etc. Restarts each serie (except first) before playing.

Examples:

Merge sequences

merged = MERGE(S(1, 2, 3), S(10, 20, 30))
merged.i.to_a  # => [1, 2, 3, 10, 20, 30]

Melodic phrases

phrase1 = S(60, 64, 67)
phrase2 = S(72, 69, 65)
melody = MERGE(phrase1, phrase2)


352
353
354
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 352

def MERGE(*series)
  Sequence.new(series)
end

.NILNilSerie

Creates serie that always returns nil.

Returns nil on every next_value call. Useful for padding or as placeholder in composite structures.

Examples:

Nil serie

s = NIL().i
s.next_value  # => nil
s.next_value  # => nil


108
109
110
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 108

def NIL
  NilSerie.new
end

.PROXY(serie = nil) ⇒ ProxySerie

Creates a proxy serie with optional initial source.

Proxy series enable late binding - creating a serie placeholder that will be resolved later. Useful for:

Use Cases

  • Forward references: Reference series before definition
  • Circular structures: Self-referential or mutually referential series
  • Dependency injection: Define structure, inject source later
  • Dynamic routing: Change source serie at runtime

Method Delegation

Proxy delegates all methods to underlying source via method_missing, making it transparent proxy for most operations.

State Resolution

Proxy starts in :undefined state, becomes :prototype/:instance when source is set and resolved.

Examples:

Forward reference

proxy = PROXY()
proxy.undefined?  # => true

# Define later
proxy.proxy_source = S(1, 2, 3)
proxy.prototype?  # => true

Circular structure

loop_serie = PROXY()
sequence = S(1, 2, 3).after(loop_serie)
loop_serie.proxy_source = sequence
# Creates infinite loop: 1, 2, 3, 1, 2, 3, ...

With initial source

proxy = PROXY(S(1, 2, 3))


51
52
53
# File 'lib/musa-dsl/series/proxy-serie.rb', line 51

def PROXY(serie = nil)
  ProxySerie.new(serie)
end

.QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/musa-dsl/series/quantizer-serie.rb', line 66

def QUANTIZE(time_value_serie,
             reference: nil, step: nil,
             value_attribute: nil,
             stops: nil,
             predictive: nil,
             left_open: nil,
             right_open: nil)

  reference ||= 0r
  step ||= 1r
  value_attribute ||= :value
  stops ||= false
  predictive ||= false

  if predictive
    raise ArgumentError, "Predictive quantization doesn't allow parameters 'left_open' or 'right_open'" if left_open || right_open

    PredictiveQuantizer.new(reference, step, time_value_serie, value_attribute, stops)
  else
    # By default: left closed and right_open
    # By default 2:
    #   if right_open is true and left_open is nil, left_open will be false
    #   if left_open is true and right_open is nil, right_open will be false

    right_open = right_open.nil? ? !left_open : right_open
    left_open = left_open.nil? ? !right_open : left_open

    RawQuantizer.new(reference, step, time_value_serie, value_attribute, stops, left_open, right_open)
  end
end

.QUEUE(*series) ⇒ QueueSerie

Creates queue serie from initial series.

Queue allows adding series dynamically during playback, creating flexible sequential playback with runtime modification.

Features

  • Dynamic addition: Add series with << during playback
  • Sequential playback: Plays series in queue order
  • Method delegation: Delegates methods to current serie
  • Clear: Can clear queue and reset

Use Cases

  • Interactive sequencing with user input
  • Dynamic phrase assembly
  • Playlist-style serie management
  • Reactive composition systems
  • Live coding pattern queuing

Examples:

Basic queue

queue = QUEUE(S(1, 2, 3)).i
queue.next_value  # => 1
queue << S(4, 5, 6).i  # Add dynamically
queue.to_a  # => [2, 3, 4, 5, 6]

Dynamic playlist

queue = QUEUE().i
queue << melody1.i
queue << melody2.i
# Plays melody1 then melody2


46
47
48
# File 'lib/musa-dsl/series/queue-serie.rb', line 46

def QUEUE(*series)
  QueueSerie.new(series)
end

.RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValuesFromArray, RandomNumbersFromRange

Creates random value serie from array or range.

Two modes:

  • Array mode: Random values from provided array
  • Range mode: Random numbers from range (from, to, step)

Infinite serie - never exhausts.

Examples:

Random from array

dice = RND(1, 2, 3, 4, 5, 6)
dice.i.next_value  # => random 1-6

Random from range

rand = RND(from: 0, to: 100, step: 10)
rand.i.next_value  # => random 0, 10, 20, ..., 100

With seed

rnd = RND(1, 2, 3, random: 42)  # Reproducible

Raises:

  • (ArgumentError)

    if using both positional and named values

  • (ArgumentError)

    if mixing array and range parameters



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 314

def RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil)
  raise ArgumentError, "Can't use both direct values #{_values} and values named parameter #{values} at the same time." if values && !_values.empty?

  random = Random.new random if random.is_a?(Integer)
  random ||= Random.new

  values ||= _values

  if !values.empty? && from.nil? && to.nil? && step.nil?
    RandomValuesFromArray.new values.explode_ranges, random
  elsif values.empty? && !to.nil?
    from ||= 0
    step ||= 1
    RandomNumbersFromRange.new from, to, step, random
  else
    raise ArgumentError, 'cannot use values and from:/to:/step: together'
  end
end

.RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValueFromArray, RandomNumberFromRange

Creates single random value serie from array or range.

Like RND but returns only one random value then exhausts. Two modes: array mode and range mode.

Examples:

Single random value

rnd = RND1(1, 2, 3, 4, 5)
rnd.i.next_value  # => random 1-5
rnd.i.next_value  # => nil (exhausted)

Random seed selection

seed = RND1(10, 20, 30, random: 42)

Raises:

  • (ArgumentError)

    if using both positional and named values

  • (ArgumentError)

    if mixing array and range parameters



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 382

def RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil)
  raise ArgumentError, "Can't use both direct values #{_values} and values named parameter #{values} at the same time." if values && !_values.empty?

  random = Random.new random if random.is_a?(Integer)
  random ||= Random.new

  values ||= _values

  if !values.empty? && from.nil? && to.nil? && step.nil?
    RandomValueFromArray.new values.explode_ranges, random
  elsif values.empty? && !to.nil?
    from ||= 0
    step ||= 1
    RandomNumberFromRange.new from, to, step, random
  else
    raise ArgumentError, 'cannot use values and from:/to:/step: parameters together'
  end
end

.S(*values) ⇒ FromArray

Creates serie from array of values.

Most common constructor. Values can include ranges which will be expanded automatically via ExplodeRanges extension.

Examples:

Basic array

notes = S(60, 64, 67, 72)
notes.i.to_a  # => [60, 64, 67, 72]

With ranges

scale = S(60..67)
scale.i.to_a  # => [60, 61, 62, 63, 64, 65, 66, 67]


130
131
132
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 130

def S(*values)
  FromArray.new values.explode_ranges
end

.SIN(start_value: nil, steps: nil, amplitude: nil, center: nil) ⇒ SinFunction

Creates sine wave function serie.

Generates values following sine curve. Useful for smooth oscillations, LFO-style modulation, and periodic variations.

Wave Parameters

  • start_value: Initial value (default: center)
  • steps: Period in steps (nil for continuous)
  • amplitude: Wave amplitude (default: 1.0)
  • center: Center/offset value (default: 0.0)

Wave equation: center + amplitude * sin(progress)

Examples:

Basic sine wave

wave = SIN(steps: 8, amplitude: 10, center: 50)
wave.i.to_a  # => oscillates around 50 ± 10

LFO modulation

lfo = SIN(steps: 16, amplitude: 0.5, center: 0.5)
# Use for amplitude modulation


431
432
433
434
435
436
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 431

def SIN(start_value: nil, steps: nil, amplitude: nil, center: nil)
  amplitude ||= 1.0
  center ||= 0.0
  start_value ||= center
  SinFunction.new start_value, steps, amplitude, center
end

.TIMED_UNION(*array_of_timed_series, **hash_of_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries

Merges multiple timed series by synchronizing events at each time point.

TIMED_UNION combines series with :time attributes, emitting events at each unique time where at least one source has a value. Sources without values at a given time emit nil. Operates in two distinct modes based on input format.

Timed Series Format

Each event is a hash with :time and :value keys, extended with AbsTimed:

{ time: 0r, value: 60, duration: 1r }.extend(Musa::Datasets::AbsTimed)

Additional attributes (:duration, :velocity, etc.) are preserved and synchronized alongside values.

Operating Modes

Array Mode: TIMED_UNION(s1, s2, s3)

  • Anonymous positional sources
  • Output: { time: t, value: [val1, val2, val3] }
  • Use for: Ordered tracks without specific names

Hash Mode: TIMED_UNION(melody: s1, bass: s2)

  • Named sources with keys
  • Output: { time: t, value: { melody: val1, bass: val2 } }
  • Use for: Identified voices/tracks for routing

Value Types and Combination

Direct values (integers, strings, etc.):

s1 = S({ time: 0, value: 60 })
s2 = S({ time: 0, value: 64 })
TIMED_UNION(s1, s2)  # => { time: 0, value: [60, 64] }

Hash values (polyphonic events):

s1 = S({ time: 0, value: { a: 1, b: 2 } })
s2 = S({ time: 0, value: { c: 10 } })
TIMED_UNION(s1, s2)  # => { time: 0, value: { a: 1, b: 2, c: 10 } }

Array values (multi-element events):

s1 = S({ time: 0, value: [1, 2] })
s2 = S({ time: 0, value: [10, 20] })
TIMED_UNION(s1, s2)  # => { time: 0, value: [1, 2, 10, 20] }

Mixed Hash + Direct (advanced):

s1 = S({ time: 0, value: { a: 1, b: 2 } })
s2 = S({ time: 0, value: 100 })
TIMED_UNION(s1, s2)  # => { time: 0, value: { a: 1, b: 2, 0 => 100 } }

Synchronization Behavior

Events are emitted at each unique time point across all sources:

s1 = S({ time: 0r, value: 1 }, { time: 2r, value: 3 })
s2 = S({ time: 1r, value: 10 })
TIMED_UNION(s1, s2).i.to_a
# => [{ time: 0r, value: [1, nil] },
#     { time: 1r, value: [nil, 10] },
#     { time: 2r, value: [3, nil] }]

Extra Attributes

Non-standard attributes (beyond :time, :value) are synchronized:

s1 = S({ time: 0, value: 1, velocity: 80 })
s2 = S({ time: 0, value: 10, duration: 1r })
TIMED_UNION(s1, s2)
# => { time: 0, value: [1, 10], velocity: [80, nil], duration: [nil, 1r] }

Examples:

Array mode with direct values

s1 = S({ time: 0r, value: 1 }, { time: 1r, value: 2 })
s2 = S({ time: 0r, value: 10 }, { time: 2r, value: 20 })

union = TIMED_UNION(s1, s2).i
union.to_a
# => [{ time: 0r, value: [1, 10] },
#     { time: 1r, value: [2, nil] },
#     { time: 2r, value: [nil, 20] }]

Hash mode with named sources

melody = S({ time: 0r, value: 60 }, { time: 1r, value: 64 })
bass = S({ time: 0r, value: 36 }, { time: 2r, value: 40 })

union = TIMED_UNION(melody: melody, bass: bass).i
union.to_a
# => [{ time: 0r, value: { melody: 60, bass: 36 } },
#     { time: 1r, value: { melody: 64, bass: nil } },
#     { time: 2r, value: { melody: nil, bass: 40 } }]

Hash values with polyphonic events

s1 = S({ time: 0r, value: { a: 1, b: 2 } })
s2 = S({ time: 0r, value: { c: 10, d: 20 } })

union = TIMED_UNION(s1, s2).i
union.next_value  # => { time: 0r, value: { a: 1, b: 2, c: 10, d: 20 } }

Extra attributes synchronization

s1 = S({ time: 0r, value: 1, velocity: 80, duration: 1r })
s2 = S({ time: 0r, value: 10, velocity: 90 })

union = TIMED_UNION(s1, s2).i
union.next_value
# => { time: 0r,
#      value: [1, 10],
#      velocity: [80, 90],
#      duration: [1r, nil] }

Key conflict detection

s1 = S({ time: 0r, value: { a: 1, b: 2 } })
s2 = S({ time: 0r, value: { a: 10 } })  # 'a' already used!

union = TIMED_UNION(s1, s2).i
union.next_value  # RuntimeError: Value: key a already used

Raises:

  • (ArgumentError)

    if mixing array and hash modes

  • (RuntimeError)

    if hash values have duplicate keys across sources

  • (RuntimeError)

    if mixing incompatible value types (Hash with Array)

See Also:

  • Splits compound values into individual timed events
  • Removes events with all-nil values
  • Instance method for union


149
150
151
152
153
154
155
156
157
158
159
# File 'lib/musa-dsl/series/timed-serie.rb', line 149

def TIMED_UNION(*array_of_timed_series, **hash_of_timed_series)
  raise ArgumentError, 'Can\'t union an array of series with a hash of series' if array_of_timed_series.any? && hash_of_timed_series.any?

  if array_of_timed_series.any?
    TimedUnionOfArrayOfTimedSeries.new(array_of_timed_series)
  elsif hash_of_timed_series.any?
    TimedUnionOfHashOfTimedSeries.new(hash_of_timed_series)
  else
    raise ArgumentError, 'Missing argument series'
  end
end

.UNDEFINEDUndefinedSerie

Creates undefined serie.

Returns serie in undefined state. Useful as placeholder that will be resolved later (e.g., in PROXY).

Examples:

Undefined placeholder

proxy = PROXY()  # Uses UNDEFINED internally
proxy.undefined?  # => true


91
92
93
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 91

def UNDEFINED
  UndefinedSerie.new
end

Instance Method Details

#A(*series) ⇒ FromArrayOfSeries

Creates array-mode serie from array of series.

Combines multiple series into array-structured values. Returns array of values from respective series. Stops when first serie exhausts.

Examples:

Array of series

a = A(S(1, 2, 3), S(10, 20, 30))
a.i.next_value  # => [1, 10]
a.i.next_value  # => [2, 20]


188
189
190
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 188

def A(*series)
  FromArrayOfSeries.new series, false
end

#AC(*series) ⇒ FromArrayOfSeries

Creates array-mode combined serie from array of series.

Like A but cycles all series. When a serie exhausts, it restarts from the beginning, continuing until all series complete their cycles.

Examples:

Combined cycling all series

ac = AC(S(1, 2), S(10, 20, 30))
ac.max_size(6).i.to_a  # => [[1, 10], [2, 20], [1, 30],
                        #     [2, 10], [1, 20], [2, 30]]


207
208
209
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 207

def AC(*series)
  FromArrayOfSeries.new series, true
end

#E(*value_args, **key_args) {|value_args, last_value, caller, key_args| ... } ⇒ FromEvalBlockWithParameters

Creates serie from evaluation block.

Calls block repeatedly with parameters and last_value. Block returns next value or nil to stop. Enables stateful generators and algorithms.

Block Parameters

  • value_args: Initial positional parameters
  • last_value: Previous return value (nil on first call)
  • caller: Serie instance (access to parameters attribute)
  • key_args: Initial keyword parameters

Examples:

Counter

counter = E(1) { |v, last_value:| last_value + 1 unless last_value == 5 }
counter.i.to_a  # => [1, 2, 3, 4, 5]

Fibonacci

fib = E { |last_value:, caller:|
  a, b = caller.parameters
  caller.parameters = [b, a + b]
  a
}
fib.parameters = [0, 1]
fib.i.to_a(limit: 10)  # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Yields:

  • block called for each value

Yield Parameters:

  • value_args (Array)

    current positional parameters

  • last_value (Object, nil)

    previous return value

  • caller (FromEvalBlockWithParameters)

    serie instance

  • key_args (Hash)

    current keyword parameters

Yield Returns:

  • (Object, nil)

    next value or nil to stop



248
249
250
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 248

def E(*value_args, **key_args, &block)
  FromEvalBlockWithParameters.new *value_args, **key_args, &block
end

#FIBOFibonacci

Creates Fibonacci sequence serie.

Generates classic Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, ... Infinite serie.

Examples:

Fibonacci numbers

fib = FIBO()
fib.infinite?  # => true
inst = fib.i
10.times.map { inst.next_value }
# => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Rhythmic proportions

durations = FIBO().i.map { |n| Rational(n, 16) }


456
457
458
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 456

def FIBO()
  Fibonacci.new
end

#FOR(from: nil, to: nil, step: nil) ⇒ ForLoop

Creates for-loop style numeric sequence.

Generates sequence from from to to (inclusive) with step increment. Automatically adjusts step sign based on from/to relationship.

Examples:

Ascending sequence

s = FOR(from: 0, to: 10, step: 2)
s.i.to_a  # => [0, 2, 4, 6, 8, 10]

Descending sequence

s = FOR(from: 10, to: 0, step: 2)
s.i.to_a  # => [10, 8, 6, 4, 2, 0]

Infinite sequence

s = FOR(from: 0, step: 1)  # to: nil
s.infinite?  # => true


276
277
278
279
280
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 276

def FOR(from: nil, to: nil, step: nil)
  from ||= 0
  step ||= 1
  ForLoop.new from, to, step
end

#H(**series_hash) ⇒ FromHashOfSeries

Creates hash-mode serie from hash of series.

Combines multiple series into hash-structured values. Returns hash with same keys, values from respective series. Stops when first serie exhausts.

Examples:

Hash of series

h = H(pitch: S(60, 64, 67), velocity: S(96, 80, 64))
h.i.next_value  # => {pitch: 60, velocity: 96}
h.i.next_value  # => {pitch: 64, velocity: 80}


150
151
152
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 150

def H(**series_hash)
  FromHashOfSeries.new series_hash, false
end

#HARMO(error: nil, extended: nil) ⇒ HarmonicNotes

Creates harmonic notes serie from fundamental.

Generates MIDI note numbers for harmonic series based on listened fundamental. Approximates harmonics to nearest semitone within error tolerance.

Parameters

  • error: Maximum cents deviation to accept harmonic (default: 0.5)
  • extended: Include extended harmonics beyond audible range

Examples:

Harmonic series

# Listen to fundamental, serie returns harmonic notes
harmonics = HARMO(error: 0.5)
harmonics.i  # Waits for fundamental input

Extended harmonics

harm = HARMO(error: 0.3, extended: true)


485
486
487
488
489
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 485

def HARMO(error: nil, extended: nil)
  error ||= 0.5
  extended ||= false
  HarmonicNotes.new error, extended
end

#HC(**series_hash) ⇒ FromHashOfSeries

Creates hash-mode combined serie from hash of series.

Like H but cycles all series. When a serie exhausts, it restarts from the beginning, continuing until all series complete their cycles.

Examples:

Combined cycling all series

hc = HC(a: S(1, 2), b: S(10, 20, 30))
hc.max_size(6).i.to_a  # => [{a:1, b:10}, {a:2, b:20}, {a:1, b:30},
                        #     {a:2, b:10}, {a:1, b:20}, {a:2, b:30}]


169
170
171
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 169

def HC(**series_hash)
  FromHashOfSeries.new series_hash, true
end

#MERGE(*series) ⇒ Sequence

Merges multiple series sequentially.

Plays series in sequence: first serie until exhausted, then second, etc. Restarts each serie (except first) before playing.

Examples:

Merge sequences

merged = MERGE(S(1, 2, 3), S(10, 20, 30))
merged.i.to_a  # => [1, 2, 3, 10, 20, 30]

Melodic phrases

phrase1 = S(60, 64, 67)
phrase2 = S(72, 69, 65)
melody = MERGE(phrase1, phrase2)


352
353
354
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 352

def MERGE(*series)
  Sequence.new(series)
end

#NILNilSerie

Creates serie that always returns nil.

Returns nil on every next_value call. Useful for padding or as placeholder in composite structures.

Examples:

Nil serie

s = NIL().i
s.next_value  # => nil
s.next_value  # => nil


108
109
110
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 108

def NIL
  NilSerie.new
end

#PROXY(serie = nil) ⇒ ProxySerie

Creates a proxy serie with optional initial source.

Proxy series enable late binding - creating a serie placeholder that will be resolved later. Useful for:

Use Cases

  • Forward references: Reference series before definition
  • Circular structures: Self-referential or mutually referential series
  • Dependency injection: Define structure, inject source later
  • Dynamic routing: Change source serie at runtime

Method Delegation

Proxy delegates all methods to underlying source via method_missing, making it transparent proxy for most operations.

State Resolution

Proxy starts in :undefined state, becomes :prototype/:instance when source is set and resolved.

Examples:

Forward reference

proxy = PROXY()
proxy.undefined?  # => true

# Define later
proxy.proxy_source = S(1, 2, 3)
proxy.prototype?  # => true

Circular structure

loop_serie = PROXY()
sequence = S(1, 2, 3).after(loop_serie)
loop_serie.proxy_source = sequence
# Creates infinite loop: 1, 2, 3, 1, 2, 3, ...

With initial source

proxy = PROXY(S(1, 2, 3))


51
52
53
# File 'lib/musa-dsl/series/proxy-serie.rb', line 51

def PROXY(serie = nil)
  ProxySerie.new(serie)
end

#QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/musa-dsl/series/quantizer-serie.rb', line 66

def QUANTIZE(time_value_serie,
             reference: nil, step: nil,
             value_attribute: nil,
             stops: nil,
             predictive: nil,
             left_open: nil,
             right_open: nil)

  reference ||= 0r
  step ||= 1r
  value_attribute ||= :value
  stops ||= false
  predictive ||= false

  if predictive
    raise ArgumentError, "Predictive quantization doesn't allow parameters 'left_open' or 'right_open'" if left_open || right_open

    PredictiveQuantizer.new(reference, step, time_value_serie, value_attribute, stops)
  else
    # By default: left closed and right_open
    # By default 2:
    #   if right_open is true and left_open is nil, left_open will be false
    #   if left_open is true and right_open is nil, right_open will be false

    right_open = right_open.nil? ? !left_open : right_open
    left_open = left_open.nil? ? !right_open : left_open

    RawQuantizer.new(reference, step, time_value_serie, value_attribute, stops, left_open, right_open)
  end
end

#QUEUE(*series) ⇒ QueueSerie

Creates queue serie from initial series.

Queue allows adding series dynamically during playback, creating flexible sequential playback with runtime modification.

Features

  • Dynamic addition: Add series with << during playback
  • Sequential playback: Plays series in queue order
  • Method delegation: Delegates methods to current serie
  • Clear: Can clear queue and reset

Use Cases

  • Interactive sequencing with user input
  • Dynamic phrase assembly
  • Playlist-style serie management
  • Reactive composition systems
  • Live coding pattern queuing

Examples:

Basic queue

queue = QUEUE(S(1, 2, 3)).i
queue.next_value  # => 1
queue << S(4, 5, 6).i  # Add dynamically
queue.to_a  # => [2, 3, 4, 5, 6]

Dynamic playlist

queue = QUEUE().i
queue << melody1.i
queue << melody2.i
# Plays melody1 then melody2


46
47
48
# File 'lib/musa-dsl/series/queue-serie.rb', line 46

def QUEUE(*series)
  QueueSerie.new(series)
end

#RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValuesFromArray, RandomNumbersFromRange

Creates random value serie from array or range.

Two modes:

  • Array mode: Random values from provided array
  • Range mode: Random numbers from range (from, to, step)

Infinite serie - never exhausts.

Examples:

Random from array

dice = RND(1, 2, 3, 4, 5, 6)
dice.i.next_value  # => random 1-6

Random from range

rand = RND(from: 0, to: 100, step: 10)
rand.i.next_value  # => random 0, 10, 20, ..., 100

With seed

rnd = RND(1, 2, 3, random: 42)  # Reproducible

Raises:

  • (ArgumentError)

    if using both positional and named values

  • (ArgumentError)

    if mixing array and range parameters



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 314

def RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil)
  raise ArgumentError, "Can't use both direct values #{_values} and values named parameter #{values} at the same time." if values && !_values.empty?

  random = Random.new random if random.is_a?(Integer)
  random ||= Random.new

  values ||= _values

  if !values.empty? && from.nil? && to.nil? && step.nil?
    RandomValuesFromArray.new values.explode_ranges, random
  elsif values.empty? && !to.nil?
    from ||= 0
    step ||= 1
    RandomNumbersFromRange.new from, to, step, random
  else
    raise ArgumentError, 'cannot use values and from:/to:/step: together'
  end
end

#RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValueFromArray, RandomNumberFromRange

Creates single random value serie from array or range.

Like RND but returns only one random value then exhausts. Two modes: array mode and range mode.

Examples:

Single random value

rnd = RND1(1, 2, 3, 4, 5)
rnd.i.next_value  # => random 1-5
rnd.i.next_value  # => nil (exhausted)

Random seed selection

seed = RND1(10, 20, 30, random: 42)

Raises:

  • (ArgumentError)

    if using both positional and named values

  • (ArgumentError)

    if mixing array and range parameters



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 382

def RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil)
  raise ArgumentError, "Can't use both direct values #{_values} and values named parameter #{values} at the same time." if values && !_values.empty?

  random = Random.new random if random.is_a?(Integer)
  random ||= Random.new

  values ||= _values

  if !values.empty? && from.nil? && to.nil? && step.nil?
    RandomValueFromArray.new values.explode_ranges, random
  elsif values.empty? && !to.nil?
    from ||= 0
    step ||= 1
    RandomNumberFromRange.new from, to, step, random
  else
    raise ArgumentError, 'cannot use values and from:/to:/step: parameters together'
  end
end

#S(*values) ⇒ FromArray

Creates serie from array of values.

Most common constructor. Values can include ranges which will be expanded automatically via ExplodeRanges extension.

Examples:

Basic array

notes = S(60, 64, 67, 72)
notes.i.to_a  # => [60, 64, 67, 72]

With ranges

scale = S(60..67)
scale.i.to_a  # => [60, 61, 62, 63, 64, 65, 66, 67]


130
131
132
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 130

def S(*values)
  FromArray.new values.explode_ranges
end

#SIN(start_value: nil, steps: nil, amplitude: nil, center: nil) ⇒ SinFunction

Creates sine wave function serie.

Generates values following sine curve. Useful for smooth oscillations, LFO-style modulation, and periodic variations.

Wave Parameters

  • start_value: Initial value (default: center)
  • steps: Period in steps (nil for continuous)
  • amplitude: Wave amplitude (default: 1.0)
  • center: Center/offset value (default: 0.0)

Wave equation: center + amplitude * sin(progress)

Examples:

Basic sine wave

wave = SIN(steps: 8, amplitude: 10, center: 50)
wave.i.to_a  # => oscillates around 50 ± 10

LFO modulation

lfo = SIN(steps: 16, amplitude: 0.5, center: 0.5)
# Use for amplitude modulation


431
432
433
434
435
436
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 431

def SIN(start_value: nil, steps: nil, amplitude: nil, center: nil)
  amplitude ||= 1.0
  center ||= 0.0
  start_value ||= center
  SinFunction.new start_value, steps, amplitude, center
end

#TIMED_UNION(*array_of_timed_series, **hash_of_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries

Merges multiple timed series by synchronizing events at each time point.

TIMED_UNION combines series with :time attributes, emitting events at each unique time where at least one source has a value. Sources without values at a given time emit nil. Operates in two distinct modes based on input format.

Timed Series Format

Each event is a hash with :time and :value keys, extended with AbsTimed:

{ time: 0r, value: 60, duration: 1r }.extend(Musa::Datasets::AbsTimed)

Additional attributes (:duration, :velocity, etc.) are preserved and synchronized alongside values.

Operating Modes

Array Mode: TIMED_UNION(s1, s2, s3)

  • Anonymous positional sources
  • Output: { time: t, value: [val1, val2, val3] }
  • Use for: Ordered tracks without specific names

Hash Mode: TIMED_UNION(melody: s1, bass: s2)

  • Named sources with keys
  • Output: { time: t, value: { melody: val1, bass: val2 } }
  • Use for: Identified voices/tracks for routing

Value Types and Combination

Direct values (integers, strings, etc.):

s1 = S({ time: 0, value: 60 })
s2 = S({ time: 0, value: 64 })
TIMED_UNION(s1, s2)  # => { time: 0, value: [60, 64] }

Hash values (polyphonic events):

s1 = S({ time: 0, value: { a: 1, b: 2 } })
s2 = S({ time: 0, value: { c: 10 } })
TIMED_UNION(s1, s2)  # => { time: 0, value: { a: 1, b: 2, c: 10 } }

Array values (multi-element events):

s1 = S({ time: 0, value: [1, 2] })
s2 = S({ time: 0, value: [10, 20] })
TIMED_UNION(s1, s2)  # => { time: 0, value: [1, 2, 10, 20] }

Mixed Hash + Direct (advanced):

s1 = S({ time: 0, value: { a: 1, b: 2 } })
s2 = S({ time: 0, value: 100 })
TIMED_UNION(s1, s2)  # => { time: 0, value: { a: 1, b: 2, 0 => 100 } }

Synchronization Behavior

Events are emitted at each unique time point across all sources:

s1 = S({ time: 0r, value: 1 }, { time: 2r, value: 3 })
s2 = S({ time: 1r, value: 10 })
TIMED_UNION(s1, s2).i.to_a
# => [{ time: 0r, value: [1, nil] },
#     { time: 1r, value: [nil, 10] },
#     { time: 2r, value: [3, nil] }]

Extra Attributes

Non-standard attributes (beyond :time, :value) are synchronized:

s1 = S({ time: 0, value: 1, velocity: 80 })
s2 = S({ time: 0, value: 10, duration: 1r })
TIMED_UNION(s1, s2)
# => { time: 0, value: [1, 10], velocity: [80, nil], duration: [nil, 1r] }

Examples:

Array mode with direct values

s1 = S({ time: 0r, value: 1 }, { time: 1r, value: 2 })
s2 = S({ time: 0r, value: 10 }, { time: 2r, value: 20 })

union = TIMED_UNION(s1, s2).i
union.to_a
# => [{ time: 0r, value: [1, 10] },
#     { time: 1r, value: [2, nil] },
#     { time: 2r, value: [nil, 20] }]

Hash mode with named sources

melody = S({ time: 0r, value: 60 }, { time: 1r, value: 64 })
bass = S({ time: 0r, value: 36 }, { time: 2r, value: 40 })

union = TIMED_UNION(melody: melody, bass: bass).i
union.to_a
# => [{ time: 0r, value: { melody: 60, bass: 36 } },
#     { time: 1r, value: { melody: 64, bass: nil } },
#     { time: 2r, value: { melody: nil, bass: 40 } }]

Hash values with polyphonic events

s1 = S({ time: 0r, value: { a: 1, b: 2 } })
s2 = S({ time: 0r, value: { c: 10, d: 20 } })

union = TIMED_UNION(s1, s2).i
union.next_value  # => { time: 0r, value: { a: 1, b: 2, c: 10, d: 20 } }

Extra attributes synchronization

s1 = S({ time: 0r, value: 1, velocity: 80, duration: 1r })
s2 = S({ time: 0r, value: 10, velocity: 90 })

union = TIMED_UNION(s1, s2).i
union.next_value
# => { time: 0r,
#      value: [1, 10],
#      velocity: [80, 90],
#      duration: [1r, nil] }

Key conflict detection

s1 = S({ time: 0r, value: { a: 1, b: 2 } })
s2 = S({ time: 0r, value: { a: 10 } })  # 'a' already used!

union = TIMED_UNION(s1, s2).i
union.next_value  # RuntimeError: Value: key a already used

Raises:

  • (ArgumentError)

    if mixing array and hash modes

  • (RuntimeError)

    if hash values have duplicate keys across sources

  • (RuntimeError)

    if mixing incompatible value types (Hash with Array)

See Also:

  • Splits compound values into individual timed events
  • Removes events with all-nil values
  • Instance method for union


149
150
151
152
153
154
155
156
157
158
159
# File 'lib/musa-dsl/series/timed-serie.rb', line 149

def TIMED_UNION(*array_of_timed_series, **hash_of_timed_series)
  raise ArgumentError, 'Can\'t union an array of series with a hash of series' if array_of_timed_series.any? && hash_of_timed_series.any?

  if array_of_timed_series.any?
    TimedUnionOfArrayOfTimedSeries.new(array_of_timed_series)
  elsif hash_of_timed_series.any?
    TimedUnionOfHashOfTimedSeries.new(hash_of_timed_series)
  else
    raise ArgumentError, 'Missing argument series'
  end
end

#UNDEFINEDUndefinedSerie

Creates undefined serie.

Returns serie in undefined state. Useful as placeholder that will be resolved later (e.g., in PROXY).

Examples:

Undefined placeholder

proxy = PROXY()  # Uses UNDEFINED internally
proxy.undefined?  # => true


91
92
93
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 91

def UNDEFINED
  UndefinedSerie.new
end