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
-
.A(*series) ⇒ FromArrayOfSeries
Creates array-mode serie from array of series.
-
.AC(*series) ⇒ Object
Combines series of different lengths, cycling the short ones.
-
.E(*value_args, **key_args) {|value_args, last_value, caller, key_args| ... } ⇒ FromEvalBlockWithParameters
Creates serie from evaluation block.
-
.FIBO(first = 1, second = 1) ⇒ Fibonacci
Creates a Fibonacci serie: every value is the sum of the two before it.
-
.FOR(from: nil, to: nil, step: nil) ⇒ ForLoop
Creates for-loop style numeric sequence.
-
.H(**series_hash) ⇒ FromHashOfSeries
Creates hash-mode serie from hash of series.
-
.HARMO(error: nil, extended: nil) ⇒ HarmonicNotes
Creates a serie of the harmonic series, in semitones over the fundamental.
-
.HC(**series_hash) ⇒ FromHashOfSeries
Creates hash-mode combined serie from hash of series.
-
.MERGE(*series) ⇒ Sequence
Merges multiple series sequentially.
-
.NIL ⇒ NilSerie
Creates serie that always returns nil.
-
.PROXY(serie = nil, cyclic: nil) ⇒ ProxySerie
Creates a proxy serie with optional initial source.
-
.QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ RawQuantizer, PredictiveQuantizer
Turns a continuous ramp into a staircase.
-
.QUEUE(*series) ⇒ QueueSerie
Creates queue serie from initial series.
-
.RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValuesFromArray, RandomNumbersFromRange
Creates random value serie from array or range.
-
.RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValueFromArray, RandomNumberFromRange
Creates single random value serie from array or range.
-
.S(*values) ⇒ FromArray
Creates serie from array of values.
-
.SIN(start_value: nil, steps: nil, amplitude: nil, center: nil) ⇒ SinFunction
Creates sine wave function serie.
-
.TIMED_UNION(*array_of_timed_series, **hash_of_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries
Merges multiple timed series by synchronizing events at each time point.
-
.UNDEFINED ⇒ UndefinedSerie
Creates undefined serie.
Instance Method Summary collapse
-
#A(*series) ⇒ FromArrayOfSeries
Creates array-mode serie from array of series.
-
#AC(*series) ⇒ Object
Combines series of different lengths, cycling the short ones.
-
#E(*value_args, **key_args) {|value_args, last_value, caller, key_args| ... } ⇒ FromEvalBlockWithParameters
Creates serie from evaluation block.
-
#FIBO(first = 1, second = 1) ⇒ Fibonacci
Creates a Fibonacci serie: every value is the sum of the two before it.
-
#FOR(from: nil, to: nil, step: nil) ⇒ ForLoop
Creates for-loop style numeric sequence.
-
#H(**series_hash) ⇒ FromHashOfSeries
Creates hash-mode serie from hash of series.
-
#HARMO(error: nil, extended: nil) ⇒ HarmonicNotes
Creates a serie of the harmonic series, in semitones over the fundamental.
-
#HC(**series_hash) ⇒ FromHashOfSeries
Creates hash-mode combined serie from hash of series.
-
#MERGE(*series) ⇒ Sequence
Merges multiple series sequentially.
-
#NIL ⇒ NilSerie
Creates serie that always returns nil.
-
#PROXY(serie = nil, cyclic: nil) ⇒ ProxySerie
Creates a proxy serie with optional initial source.
-
#QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ RawQuantizer, PredictiveQuantizer
Turns a continuous ramp into a staircase.
-
#QUEUE(*series) ⇒ QueueSerie
Creates queue serie from initial series.
-
#RND(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValuesFromArray, RandomNumbersFromRange
Creates random value serie from array or range.
-
#RND1(*_values, values: nil, from: nil, to: nil, step: nil, random: nil) ⇒ RandomValueFromArray, RandomNumberFromRange
Creates single random value serie from array or range.
-
#S(*values) ⇒ FromArray
Creates serie from array of values.
-
#SIN(start_value: nil, steps: nil, amplitude: nil, center: nil) ⇒ SinFunction
Creates sine wave function serie.
-
#TIMED_UNION(*array_of_timed_series, **hash_of_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries
Merges multiple timed series by synchronizing events at each time point.
-
#UNDEFINED ⇒ UndefinedSerie
Creates undefined serie.
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.
193 194 195 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 193 def A(*series) FromArrayOfSeries.new series, false end |
.AC(*series) ⇒ Object
Combines series of different lengths, cycling the short ones.
When this is the answer
Two materials of different lengths sounding together: a four-note ostinato against a three-note one, a rhythm of five against a melody of seven. They line up again only after the least common multiple of their lengths, and what happens in between -- the same notes meeting different partners -- is the point.
#A stops with the shortest, which is what you want when the series are
meant to end together. AC keeps going until every one of them has
completed a whole number of cycles, so the result is exactly one full turn
of the pattern.
223 224 225 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 223 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
When this is the answer
Every other constructor decides its values when it is built. E decides
them when it is asked, which is the only way to write a serie whose values
depend on something the serie does not own: a variable the piece is
changing, an input that has arrived, a decision made elsewhere while the
music was already sounding.
If the values are known in advance, S, FOR, FIBO or a transformation
of one of them says it better. E is for what is not.
285 286 287 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 285 def E(*value_args, **key_args, &block) FromEvalBlockWithParameters.new *value_args, **key_args, &block end |
.FIBO(first = 1, second = 1) ⇒ Fibonacci
Creates a Fibonacci serie: every value is the sum of the two before it.
The two seeds ARE the first two values, so FIBO() yields 1, 1, 2, 3, 5...
and any other pair gives a different sequence out of the same machine —
not a delayed echo of Fibonacci, a relative of it. Infinite serie.
533 534 535 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 533 def FIBO(first = 1, second = 1) Fibonacci.new first, second 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.
313 314 315 316 317 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 313 def FOR(from: nil, to: nil, step: nil) from ||= 0 step ||= 1 ForLoop.new from, to, step end |
.H(**series_hash) ⇒ FromHashOfSeries
h.i builds a NEW instance every time it is called, each starting
from the beginning. Keep the instance to advance through the serie.
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.
154 155 156 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 154 def H(**series_hash) FromHashOfSeries.new series_hash, false end |
.HARMO(error: nil, extended: nil) ⇒ HarmonicNotes
Creates a serie of the harmonic series, in semitones over the fundamental.
Yields the interval of each harmonic from a fundamental of 0, approximated to the nearest semitone, and skips any harmonic whose approximation error exceeds the tolerance. Infinite serie; add the fundamental's own pitch to place it. The values do not depend on any input: it starts producing at once.
Parameters
- error: maximum approximation error, IN SEMITONES, for a harmonic to be accepted (default: 0.5, i.e. accept every harmonic, since no approximation to the nearest semitone can be off by more than half of one)
- extended: yield
{ pitch:, error: }instead of the bare pitch, so the approximation error of each harmonic is available. It does NOT add harmonics: the pitches are the same ones.
586 587 588 589 590 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 586 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.
173 174 175 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 173 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.
399 400 401 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 399 def MERGE(*series) Sequence.new(series) end |
.NIL ⇒ NilSerie
Creates serie that always returns nil.
Returns nil on every next_value call. Useful for padding or as placeholder in composite structures.
108 109 110 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 108 def NIL NilSerie.new end |
.PROXY(serie = nil, cyclic: 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.
Cycles
A proxy that points back into the serie it is part of closes a cycle: the
material loops instead of ending. This has to be declared with
cyclic: true, because a cycle changes what every walk of the graph has to
do and should not appear by accident -- a proxy that closes one without
having been declared raises ArgumentError. The reverse is fine: declaring a
proxy cyclic and pointing it somewhere that does not loop back is exactly
the forward reference the declaration exists for.
What a cycle is for is material whose repetition is not decided in advance:
a QUEUE fed while the loop is already sounding, an E() reading state that
changes between turns. For a serie that is fully known beforehand, .repeat
says the same thing without any of this.
103 104 105 |
# File 'lib/musa-dsl/series/proxy-serie.rb', line 103 def PROXY(serie = nil, cyclic: nil) ProxySerie.new(serie, cyclic: cyclic) end |
.QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ RawQuantizer, PredictiveQuantizer
Turns a continuous ramp into a staircase.
When this is the answer
Something computed as a curve -- a glissando, an envelope, a trajectory out of a matrix -- has to become discrete before it can be played: pitches are semitones, a controller takes integers, a rhythm lands on divisions. Quantizing is that step, and what comes back is not a set of samples taken at the input times but a staircase: one step per boundary crossed, each carrying the time it holds.
The source has to be a serie of timed values -- hashes extended with Datasets::AbsTimed. A bare hash of the right shape is not one and raises "Don't know how to process".
The two modes, and they sound different
Normal changes the step when the ramp reaches it. Predictive changes when the ramp is nearer the next step than the last -- it rounds instead of waiting to arrive, which is what a listener hears as the pitch.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/musa-dsl/series/quantizer-serie.rb', line 118 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
to_a RESTARTS the instance, so it returns everything queued and
not what is left after the next_value above.
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
59 60 61 |
# File 'lib/musa-dsl/series/queue-serie.rb', line 59 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)
A SHUFFLE, NOT A DIE. Each value is drawn once and removed, so the serie is
a random permutation and then ends: six values from RND(1..6) and nil on
the seventh. .repeat is what gives sampling with replacement, reshuffling
on each pass, and that one is infinite.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 361 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.
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 435 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.
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, PEAK TO PEAK (default: 1.0). The wave
spans
center ± amplitude / 2, socenter: 70, amplitude: 50runs from 45 to 95 and not from 20 to 120. - center: Center/offset value (default: 0.0)
Wave equation: center + (amplitude / 2) * sin(progress)
486 487 488 489 490 491 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 486 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] }
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 |
.UNDEFINED ⇒ UndefinedSerie
Creates undefined serie.
Returns serie in undefined state. Useful as placeholder that will be resolved later (e.g., in PROXY).
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.
193 194 195 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 193 def A(*series) FromArrayOfSeries.new series, false end |
#AC(*series) ⇒ Object
Combines series of different lengths, cycling the short ones.
When this is the answer
Two materials of different lengths sounding together: a four-note ostinato against a three-note one, a rhythm of five against a melody of seven. They line up again only after the least common multiple of their lengths, and what happens in between -- the same notes meeting different partners -- is the point.
#A stops with the shortest, which is what you want when the series are
meant to end together. AC keeps going until every one of them has
completed a whole number of cycles, so the result is exactly one full turn
of the pattern.
223 224 225 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 223 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
When this is the answer
Every other constructor decides its values when it is built. E decides
them when it is asked, which is the only way to write a serie whose values
depend on something the serie does not own: a variable the piece is
changing, an input that has arrived, a decision made elsewhere while the
music was already sounding.
If the values are known in advance, S, FOR, FIBO or a transformation
of one of them says it better. E is for what is not.
285 286 287 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 285 def E(*value_args, **key_args, &block) FromEvalBlockWithParameters.new *value_args, **key_args, &block end |
#FIBO(first = 1, second = 1) ⇒ Fibonacci
Creates a Fibonacci serie: every value is the sum of the two before it.
The two seeds ARE the first two values, so FIBO() yields 1, 1, 2, 3, 5...
and any other pair gives a different sequence out of the same machine —
not a delayed echo of Fibonacci, a relative of it. Infinite serie.
533 534 535 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 533 def FIBO(first = 1, second = 1) Fibonacci.new first, second 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.
313 314 315 316 317 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 313 def FOR(from: nil, to: nil, step: nil) from ||= 0 step ||= 1 ForLoop.new from, to, step end |
#H(**series_hash) ⇒ FromHashOfSeries
h.i builds a NEW instance every time it is called, each starting
from the beginning. Keep the instance to advance through the serie.
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.
154 155 156 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 154 def H(**series_hash) FromHashOfSeries.new series_hash, false end |
#HARMO(error: nil, extended: nil) ⇒ HarmonicNotes
Creates a serie of the harmonic series, in semitones over the fundamental.
Yields the interval of each harmonic from a fundamental of 0, approximated to the nearest semitone, and skips any harmonic whose approximation error exceeds the tolerance. Infinite serie; add the fundamental's own pitch to place it. The values do not depend on any input: it starts producing at once.
Parameters
- error: maximum approximation error, IN SEMITONES, for a harmonic to be accepted (default: 0.5, i.e. accept every harmonic, since no approximation to the nearest semitone can be off by more than half of one)
- extended: yield
{ pitch:, error: }instead of the bare pitch, so the approximation error of each harmonic is available. It does NOT add harmonics: the pitches are the same ones.
586 587 588 589 590 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 586 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.
173 174 175 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 173 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.
399 400 401 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 399 def MERGE(*series) Sequence.new(series) end |
#NIL ⇒ NilSerie
Creates serie that always returns nil.
Returns nil on every next_value call. Useful for padding or as placeholder in composite structures.
108 109 110 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 108 def NIL NilSerie.new end |
#PROXY(serie = nil, cyclic: 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.
Cycles
A proxy that points back into the serie it is part of closes a cycle: the
material loops instead of ending. This has to be declared with
cyclic: true, because a cycle changes what every walk of the graph has to
do and should not appear by accident -- a proxy that closes one without
having been declared raises ArgumentError. The reverse is fine: declaring a
proxy cyclic and pointing it somewhere that does not loop back is exactly
the forward reference the declaration exists for.
What a cycle is for is material whose repetition is not decided in advance:
a QUEUE fed while the loop is already sounding, an E() reading state that
changes between turns. For a serie that is fully known beforehand, .repeat
says the same thing without any of this.
103 104 105 |
# File 'lib/musa-dsl/series/proxy-serie.rb', line 103 def PROXY(serie = nil, cyclic: nil) ProxySerie.new(serie, cyclic: cyclic) end |
#QUANTIZE(time_value_serie, reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ RawQuantizer, PredictiveQuantizer
Turns a continuous ramp into a staircase.
When this is the answer
Something computed as a curve -- a glissando, an envelope, a trajectory out of a matrix -- has to become discrete before it can be played: pitches are semitones, a controller takes integers, a rhythm lands on divisions. Quantizing is that step, and what comes back is not a set of samples taken at the input times but a staircase: one step per boundary crossed, each carrying the time it holds.
The source has to be a serie of timed values -- hashes extended with Datasets::AbsTimed. A bare hash of the right shape is not one and raises "Don't know how to process".
The two modes, and they sound different
Normal changes the step when the ramp reaches it. Predictive changes when the ramp is nearer the next step than the last -- it rounds instead of waiting to arrive, which is what a listener hears as the pitch.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/musa-dsl/series/quantizer-serie.rb', line 118 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
to_a RESTARTS the instance, so it returns everything queued and
not what is left after the next_value above.
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
59 60 61 |
# File 'lib/musa-dsl/series/queue-serie.rb', line 59 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)
A SHUFFLE, NOT A DIE. Each value is drawn once and removed, so the serie is
a random permutation and then ends: six values from RND(1..6) and nil on
the seventh. .repeat is what gives sampling with replacement, reshuffling
on each pass, and that one is infinite.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 361 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.
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 435 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.
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, PEAK TO PEAK (default: 1.0). The wave
spans
center ± amplitude / 2, socenter: 70, amplitude: 50runs from 45 to 95 and not from 20 to 120. - center: Center/offset value (default: 0.0)
Wave equation: center + (amplitude / 2) * sin(progress)
486 487 488 489 490 491 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 486 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] }
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 |
#UNDEFINED ⇒ UndefinedSerie
Creates undefined serie.
Returns serie in undefined state. Useful as placeholder that will be resolved later (e.g., in PROXY).
91 92 93 |
# File 'lib/musa-dsl/series/main-serie-constructors.rb', line 91 def UNDEFINED UndefinedSerie.new end |