Module: Musa::Series

Includes:
Constructors
Included in:
All
Defined in:
lib/musa-dsl/series/base-series.rb,
lib/musa-dsl/series/series-composer.rb,
lib/musa-dsl/series/main-serie-operations.rb

Overview

Core Serie infrastructure providing prototype/instance system and base implementation.

This module defines the fundamental architecture for all Series in Musa DSL:

Core Concepts

Prototype/Instance Pattern

Series use a prototype/instance pattern to enable reusable series definitions:

  • Prototype: Template/blueprint for series (cannot be consumed)
  • Instance: Cloned copy ready for consumption (can call next_value)
  • Undefined: Series with unresolved dependencies

Serie States

Every serie exists in one of three states:

  • :prototype - Template state, cannot consume, can create instances
  • :instance - Active state, can consume values, has independent state
  • :undefined - Unresolved state, cannot use until dependencies resolve

Why Prototype/Instance?

Enables reusable series definitions without re-evaluating constructors:

# Define once (prototype)
melody = S(60, 64, 67, 72)

# Use multiple times (instances)
voice1 = melody.instance  # Independent playback
voice2 = melody.instance  # Separate playback

Integration

Series integrate with:

  • Sequencer: Play series over time via play() method
  • Generative: Use with generative grammars
  • MIDI: Generate MIDI events from series
  • Datasets: Use AbsTimed, AbsD for timing

Module Architecture

Serie Module

Factory module providing:

  • Serie.base: Base module without source/sources
  • Serie.with: Configurable module with source/sources/block

Prototyping Module

Implements prototype/instance lifecycle:

  • State management (:prototype, :instance, :undefined)
  • Cloning (prototype -> instance)
  • State resolution from sources
  • Validation and permissions

SerieImplementation Module

Core iteration protocol:

  • init: Initialize instance state
  • restart: Reset to beginning
  • next_value: Consume next element
  • peek_next_value: Look ahead without consuming
  • current_value: Last consumed value

Serie Protocol

All series must implement:

def _next_value
  # Return next value or nil when finished
end

def _init
  # Initialize instance state (optional)
end

def _restart
  # Reset to beginning (optional)
end

Usage Patterns

Basic Prototype/Instance

# Create prototype
proto = S(1, 2, 3)
proto.prototype?  # => true

# Create instance
inst = proto.instance  # or proto.i
inst.instance?  # => true

# Consume values
inst.next_value  # => 1
inst.next_value  # => 2

Multiple Instances

proto = S(1, 2, 3)

a = proto.i
b = proto.i  # Independent instance

a.next_value  # => 1
b.next_value  # => 1 (independent)

State Resolution

proxy = PROXY()  # Undefined - no source yet
proxy.undefined?  # => true

proxy.source = S(1, 2, 3)  # Becomes prototype
proxy.prototype?  # => true

Technical Details

Peek Mechanism

peek_next_value uses internal buffering to look ahead without state change:

s = S(1, 2, 3).i
s.peek_next_value  # => 1 (buffered)
s.peek_next_value  # => 1 (same)
s.next_value       # => 1 (consumes buffered)

Source/Sources Pattern

Series can depend on:

  • source: Single upstream serie
  • sources: Multiple upstream series (Array or Hash)

State automatically resolves based on dependencies:

  • All sources :prototype → :prototype
  • All sources :instance → :instance
  • Mixed or undefined → :undefined

Deep Copy Support

Uses Musa::Extension::DeepCopy for proper cloning including nested structures.

Applications

  • Reusable melodic/harmonic patterns
  • Multiple voices from single definition
  • Lazy evaluation of algorithmic sequences
  • Composable transformations
  • Memory-efficient sequence playback

Defined Under Namespace

Modules: Composer, Constructors, Operations, Serie

Instance Method Summary collapse

Instance Method Details

#A(*series) ⇒ FromArrayOfSeries Originally defined in module Constructors

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]

Parameters:

  • series (Array)

    array of series

Returns:

  • (FromArrayOfSeries)

    combined array serie

#AC(*series) ⇒ FromArrayOfSeries Originally defined in module Constructors

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]]

Parameters:

  • series (Array)

    array of series

Returns:

  • (FromArrayOfSeries)

    combined array serie that cycles all series

#E(*value_args, **key_args) {|value_args, last_value, caller, key_args| ... } ⇒ FromEvalBlockWithParameters Originally defined in module Constructors

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]

Parameters:

  • value_args (Array)

    initial positional parameters

  • key_args (Hash)

    initial keyword parameters

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

Returns:

  • (FromEvalBlockWithParameters)

    evaluation-based serie

#FIBOFibonacci Originally defined in module Constructors

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) }

Returns:

  • (Fibonacci)

    Fibonacci sequence serie

#FOR(from: nil, to: nil, step: nil) ⇒ ForLoop Originally defined in module Constructors

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

Parameters:

  • from (Numeric, nil) (defaults to: nil)

    starting value (default: 0)

  • to (Numeric, nil) (defaults to: nil)

    ending value (nil for infinite)

  • step (Numeric, nil) (defaults to: nil)

    increment (default: 1, sign auto-adjusted)

Returns:

  • (ForLoop)

    numeric sequence serie

#H(**series_hash) ⇒ FromHashOfSeries Originally defined in module Constructors

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}

Parameters:

  • series_hash (Hash)

    hash of series (key => serie)

Returns:

  • (FromHashOfSeries)

    combined hash serie

#HARMO(error: nil, extended: nil) ⇒ HarmonicNotes Originally defined in module Constructors

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)

Parameters:

  • error (Numeric, nil) (defaults to: nil)

    maximum deviation in semitones (default: 0.5)

  • extended (Boolean, nil) (defaults to: nil)

    include extended harmonics (default: false)

Returns:

  • (HarmonicNotes)

    harmonic series serie

#HC(**series_hash) ⇒ FromHashOfSeries Originally defined in module Constructors

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}]

Parameters:

  • series_hash (Hash)

    hash of series (key => serie)

Returns:

  • (FromHashOfSeries)

    combined hash serie that cycles all series

#MERGE(*series) ⇒ Sequence Originally defined in module Constructors

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)

Parameters:

  • series (Array<Serie>)

    series to merge sequentially

Returns:

  • (Sequence)

    sequential merge serie

#NILNilSerie Originally defined in module Constructors

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

Returns:

  • (NilSerie)

    serie returning nil

#PROXY(serie = nil) ⇒ ProxySerie Originally defined in module Constructors

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))

Parameters:

  • serie (Serie, nil) (defaults to: nil)

    initial source serie (default: nil)

Returns:

#QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ Object Originally defined in module Constructors

#QUEUE(*series) ⇒ QueueSerie Originally defined in module Constructors

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

Parameters:

  • series (Array<Serie>)

    initial series in queue

Returns:

#RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValuesFromArray, RandomNumbersFromRange Originally defined in module Constructors

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

Parameters:

  • _values (Array)

    values to choose from (positional)

  • values (Array, nil) (defaults to: nil)

    values to choose from (named)

  • from (Numeric, nil) (defaults to: nil)

    range start (range mode)

  • to (Numeric, nil) (defaults to: nil)

    range end (range mode, required)

  • step (Numeric, nil) (defaults to: nil)

    range step (default: 1)

  • random (Random, Integer, nil) (defaults to: nil)

    Random instance or seed

Returns:

  • (RandomValuesFromArray, RandomNumbersFromRange)

    random serie

Raises:

  • (ArgumentError)

    if using both positional and named values

  • (ArgumentError)

    if mixing array and range parameters

#RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValueFromArray, RandomNumberFromRange Originally defined in module Constructors

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)

Parameters:

  • _values (Array)

    values to choose from (positional)

  • values (Array, nil) (defaults to: nil)

    values to choose from (named)

  • from (Numeric, nil) (defaults to: nil)

    range start (range mode)

  • to (Numeric, nil) (defaults to: nil)

    range end (range mode, required)

  • step (Numeric, nil) (defaults to: nil)

    range step (default: 1)

  • random (Random, Integer, nil) (defaults to: nil)

    Random instance or seed

Returns:

  • (RandomValueFromArray, RandomNumberFromRange)

    single random value serie

Raises:

  • (ArgumentError)

    if using both positional and named values

  • (ArgumentError)

    if mixing array and range parameters

#S(*values) ⇒ FromArray Originally defined in module Constructors

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]

Parameters:

  • values (Array)

    values to iterate (supports ranges)

Returns:

#SIN(start_value: nil, steps: nil, amplitude: nil, center: nil) ⇒ SinFunction Originally defined in module Constructors

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

Parameters:

  • start_value (Numeric, nil) (defaults to: nil)

    initial value

  • steps (Numeric, nil) (defaults to: nil)

    full period in steps

  • amplitude (Numeric, nil) (defaults to: nil)

    wave amplitude (default: 1.0)

  • center (Numeric, nil) (defaults to: nil)

    center offset (default: 0.0)

Returns:

  • (SinFunction)

    sine wave serie

#TIMED_UNION(*array_of_timed_series, **hash_of_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries Originally defined in module Constructors

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

Parameters:

  • array_of_timed_series (Array<Serie>)

    timed series (array mode)

  • hash_of_timed_series (Hash{Symbol => Serie})

    named timed series (hash mode)

Returns:

  • (TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries)

    merged serie

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

#UNDEFINEDUndefinedSerie Originally defined in module Constructors

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

Returns: