Module: Musa::Series::Operations
- 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/buffer-serie.rb,
lib/musa-dsl/series/quantizer-serie.rb,
lib/musa-dsl/series/series-composer.rb,
lib/musa-dsl/series/main-serie-operations.rb,
lib/musa-dsl/series/hash-or-array-serie-splitter.rb
Overview
Series transformation operations for composing and modifying series.
Provides methods for transforming, combining, and controlling series flow. All operations return new series (functional/immutable style).
Categories
Mapping & Transformation
- map - Transform values via block
- with - Combine multiple series for mapping
- process_with - Generic processor with parameters
- hashify - Convert array values to hash
- shift - Shift values by offset
Filtering & Selection
- select - Keep values matching condition
- remove - Remove values matching condition
- skip - Skip first N values
- max_size - Limit to N values
- cut - Cut into chunks
Flow Control
- repeat - Repeat series N times or conditionally
- autorestart - Auto-restart when exhausted
- flatten - Flatten nested series
- merge - Merge serie of series
- after / + - Append series sequentially
Switching & Multiplexing
- switch - Switch between series based on selector
- multiplex - Multiplex series based on selector
- switch_serie - Switch to different series entirely
Structural Operations
- reverse - Reverse values
- randomize - Shuffle values randomly
- lock - Lock serie (prevent changes)
- flatten - Flatten nested series
Timing Operations
- anticipate - Evaluate block one step ahead
- lazy - Delay evaluation to next step
Usage Patterns
Mapping
notes = S(60, 64, 67).map { |n| n + 12 } # Transpose octave
notes.i.to_a # => [72, 76, 79]
Filtering
evens = S(1, 2, 3, 4, 5, 6).select { |n| n.even? }
evens.i.to_a # => [2, 4, 6]
Combining
pitches = S(60, 64, 67)
velocities = S(96, 80, 64)
notes = pitches.with(velocities) { |p, v| {pitch: p, velocity: v} }
Repeating
pattern = S(1, 2, 3).repeat(3)
pattern.i.to_a # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
Chaining Operations
result = S(1, 2, 3, 4, 5)
.select { |n| n.even? }
.map { |n| n * 10 }
.repeat(2)
result.i.to_a # => [20, 40, 20, 40]
Defined Under Namespace
Classes: ComposerAsOperationSerie, Processor
Instance Method Summary collapse
-
#+(other) ⇒ Sequence
Appends another serie (operator alias for after).
-
#after(*series) ⇒ Sequence
Appends series sequentially.
-
#anticipate {|current, next_value| ... } ⇒ Anticipate
Evaluates block one step ahead (anticipate).
-
#autorestart ⇒ Autorestart
Auto-restarts serie when exhausted.
-
#buffered(sync: false) ⇒ BufferSerie, SyncBufferSerie
Creates a buffered serie allowing multiple independent iterations over same source.
-
#compact_timed ⇒ TimedCompacter
Removes timed events where all values are nil.
-
#composer { ... } ⇒ ComposerAsOperationSerie
Creates a composer transformation pipeline for complex multi-stage transformations.
-
#cut(length) ⇒ Cutter
Cuts serie into chunks of specified length.
-
#flatten ⇒ Flattener
Flattens nested series into single level.
-
#flatten_timed ⇒ TimedFlattener
Splits compound timed values into individual timed events.
-
#hashify(*keys) ⇒ HashFromSeriesArray
Converts array values to hash with specified keys.
-
#lazy {|previous| ... } ⇒ LazySerieEval
Delays evaluation to next step (lazy evaluation).
-
#lock ⇒ Locker
Locks serie preventing further modifications.
-
#map(isolate_values: nil) {|value| ... } ⇒ ProcessWith
Maps values via transformation block.
-
#max_size(length) ⇒ LengthLimiter
Limits serie to maximum number of values.
-
#merge ⇒ MergeSerieOfSeries
Merges serie of series into single serie.
-
#multiplex(*indexed_series, **hash_series) ⇒ MultiplexSelector
Multiplexes values from multiple series based on selector.
-
#process_with(**parameters) {|value, parameters| ... } ⇒ Processor
Processes values with parameterized block.
-
#proxy ⇒ Object
TODO add test case.
-
#quantize(reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ RawQuantizer, PredictiveQuantizer
Quantizes time-value serie to discrete steps.
-
#queued ⇒ QueueSerie
Wraps this serie in a queue.
-
#randomize(random: nil) ⇒ Randomizer
Randomizes order of values.
-
#remove(block = nil) {|value| ... } ⇒ Remover
Removes values matching condition.
-
#repeat(times = nil, condition: nil) { ... } ⇒ Repeater, InfiniteRepeater
Repeats serie multiple times or conditionally.
-
#reverse ⇒ Reverser
Reverses order of values.
-
#select(block = nil) {|value| ... } ⇒ Selector
Selects values matching condition.
-
#shift(shift) ⇒ Shifter
Rotates serie elements circularly.
-
#skip(length) ⇒ Skipper
Skips first N values.
-
#split ⇒ Splitter
Serie splitter for decomposing hash/array values into component series.
-
#switch(*indexed_series, **hash_series) ⇒ Switcher
Switches between multiple series based on selector values.
-
#switch_serie(*indexed_series, **hash_series) ⇒ SwitchFullSerie
Switches to entirely different series based on selector.
-
#union_timed(*other_timed_series, key: nil, **other_key_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries
Combines this timed serie with others via TIMED_UNION.
-
#with(*with_series, on_restart: nil, isolate_values: nil, **with_key_series) {|main_value, with_values, with_key_values| ... } ⇒ With
(also: #eval)
Combines multiple series for mapping.
Instance Method Details
#+(other) ⇒ Sequence
Appends another serie (operator alias for after).
448 449 450 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 448 def +(other) Musa::Series::Constructors.MERGE self, other end |
#after(*series) ⇒ Sequence
Appends series sequentially.
Alias for MERGE - plays this serie, then others in sequence.
433 434 435 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 433 def after(*series) Musa::Series::Constructors.MERGE self, *series end |
#anticipate {|current, next_value| ... } ⇒ Anticipate
Evaluates block one step ahead (anticipate).
Block receives current value and NEXT value (peeked). Enables look-ahead transformations and transitions.
608 609 610 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 608 def anticipate(&block) Anticipate.new self, &block end |
#autorestart ⇒ Autorestart
The serie ENDS at each pass -- yielding nil -- and restarts on the
next call. That nil is the signal that a pass finished, which is what
makes the repetition countable; it also means infinite? is false and
that to_a stops at the end of the first pass. For a serie with no
seam in it, use repeat.
Auto-restarts serie when exhausted.
Creates an infinite serie from an original serie that automatically restarts from beginning when it reaches the end.
116 117 118 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 116 def autorestart Autorestart.new self end |
#buffered(sync: false) ⇒ BufferSerie, SyncBufferSerie
Creates a buffered serie allowing multiple independent iterations over same source.
Provides buffering mechanism enabling multiple "readers" to independently iterate over the same serie source without interfering with each other.
Buffering Modes
- Async (default): Buffers fill independently, each progresses at own pace
- Sync: All buffers synchronized, restart affects all
Use Cases
- Multiple voices reading same melodic sequence at different speeds
- Polyphonic playback from single source
- Canonic structures (rounds, fugues)
- Independent transformations of same base material
Memory Management
History is automatically cleaned when all buffers have progressed past old values, preventing unbounded memory growth.
51 52 53 54 55 56 57 |
# File 'lib/musa-dsl/series/buffer-serie.rb', line 51 def buffered(sync: false) if sync SyncBufferSerie.new(self) else BufferSerie.new(self) end end |
#compact_timed ⇒ TimedCompacter
Removes timed events where all values are nil.
Filters out temporal "gaps" where no sources have active values. Useful after union operations that create nil placeholders, or for cleaning sparse sequences.
Removal logic:
- Direct nil:
{ time: t, value: nil }→ removed - All-nil Hash:
{ time: t, value: { a: nil, b: nil } }→ removed - Partial Hash:
{ time: t, value: { a: 1, b: nil } }→ kept (has non-nil) - All-nil Array:
{ time: t, value: [nil, nil] }→ removed - Partial Array:
{ time: t, value: [1, nil] }→ kept (has non-nil)
580 581 582 |
# File 'lib/musa-dsl/series/timed-serie.rb', line 580 def compact_timed TimedCompacter.new(self) end |
#composer { ... } ⇒ ComposerAsOperationSerie
Creates a composer transformation pipeline for complex multi-stage transformations.
Composer provides declarative DSL for building transformation pipelines with multiple inputs, outputs, and intermediate processing stages.
Composer Concepts
- Pipelines: Named transformation chains
- Inputs: Named input series (proxied and buffered)
- Outputs: Named output series
- Operations: Transformation steps in pipeline
- Auto-commit: Automatic finalization of pipelines
DSL Structure
A pipeline is declared by NAMING it and listing its operations; the names
are then wired together with route. Operations are symbols (reverse)
or hashes ({ map: ... }, { skip: 1 }, { S: [...] }). input and
output are pipelines like any other — they are simply the ones the
Composer creates for you, and can be renamed or multiplied through the
inputs: and outputs: keywords.
composer do
stage1 operation1, operation2
stage2 operation3
route input, to: stage1
route stage1, to: stage2
route stage2, to: output
end
Musical Applications
- Complex multi-voice processing
- Effect chains and routing
- Algorithmic composition pipelines
- Multi-stage transformations
- Modular synthesis-style routing
89 90 91 |
# File 'lib/musa-dsl/series/series-composer.rb', line 89 def composer(&block) ComposerAsOperationSerie.new(self, &block) end |
#cut(length) ⇒ Cutter
Cuts serie into chunks of specified length.
Returns a serie of SERIES, each yielding length values.
The chunks are series and not arrays, which is what keeps the whole thing
lazy -- nothing is materialised until each chunk is consumed. Ask for the
arrays explicitly with to_a(recursive: true).
469 470 471 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 469 def cut(length) Cutter.new self, length end |
#flatten ⇒ Flattener
Flattens nested series into single level.
Recursively consumes series elements that are themselves series.
201 202 203 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 201 def flatten Flattener.new self end |
#flatten_timed ⇒ TimedFlattener
Splits compound timed values into individual timed events.
Converts events with Hash or Array values into separate timed events per element, preserving time and extra attributes. Direct values pass through unchanged.
Hash values → Hash of timed events (keyed by original keys):
{ time: 0, value: { a: 1, b: 2 }, velocity: { a: 80, b: 90 } }
# becomes:
{ a: { time: 0, value: 1, velocity: 80 },
b: { time: 0, value: 2, velocity: 90 } }
Array values → Array of timed events (indexed):
{ time: 0, value: [1, 2], velocity: [80, 90] }
# becomes:
[{ time: 0, value: 1, velocity: 80 },
{ time: 0, value: 2, velocity: 90 }]
Direct values → Pass through unchanged (already flat)
Use Cases
- Separate polyphonic events into individual voices
- Split multi-track sequences for independent processing
- Prepare for voice-specific routing via
split - Enable per-voice filtering with
compact_timed
529 530 531 |
# File 'lib/musa-dsl/series/timed-serie.rb', line 529 def flatten_timed TimedFlattener.new(self) end |
#hashify(*keys) ⇒ HashFromSeriesArray
Converts array values to hash with specified keys.
Takes array-valued serie and converts to hash using provided keys.
234 235 236 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 234 def hashify(*keys) HashFromSeriesArray.new self, keys end |
#lazy {|previous| ... } ⇒ LazySerieEval
Delays evaluation to next step (lazy evaluation).
Block receives previous value and evaluates for current step. Enables state-dependent transformations.
627 628 629 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 627 def lazy(&block) LazySerieEval.new self, &block end |
#lock ⇒ Locker
Locks serie preventing further modifications.
Returns locked copy that cannot be transformed further.
279 280 281 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 279 def lock Locker.new self end |
#map(isolate_values: nil) {|value| ... } ⇒ ProcessWith
Maps values via transformation block.
Simplest and most common transformation. Applies block to each value.
Shorthand for with without additional series.
578 579 580 581 582 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 578 def map(isolate_values: nil, &block) isolate_values ||= isolate_values.nil? ? false : isolate_values ProcessWith.new self, isolate_values: isolate_values, &block end |
#max_size(length) ⇒ LengthLimiter
Limits serie to maximum number of values.
Stops after N values regardless of source length.
169 170 171 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 169 def max_size(length) LengthLimiter.new self, length end |
#merge ⇒ MergeSerieOfSeries
Merges serie of series into single serie.
Flattens one level: consumes serie where each element is itself a serie, merging them sequentially.
486 487 488 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 486 def merge MergeSerieOfSeries.new self end |
#multiplex(*indexed_series, **hash_series) ⇒ MultiplexSelector
Multiplexes values from multiple series based on selector.
Like switch but returns composite values instead of switching.
402 403 404 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 402 def multiplex(*indexed_series, **hash_series) MultiplexSelector.new self, indexed_series, hash_series end |
#process_with(**parameters) {|value, parameters| ... } ⇒ Processor
Processes values with parameterized block.
Generic processor passing values and parameters to block.
217 218 219 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 217 def process_with(**parameters, &processor) Processor.new self, parameters, &processor end |
#proxy ⇒ Object
TODO add test case
260 261 262 |
# File 'lib/musa-dsl/series/proxy-serie.rb', line 260 def proxy Series::ProxySerie.new(self) end |
#quantize(reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) ⇒ RawQuantizer, PredictiveQuantizer
Quantizes time-value serie to discrete steps.
Quantization Modes
- Raw: Rounds values to nearest step, interpolates between points
- Predictive: Predicts crossings of quantization boundaries
Applications
- Quantize MIDI controller data to discrete values
- Convert continuous pitch bends to semitones
- Snap timing to grid
- Generate stepped automation curves
- Convert analog input to digital steps
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/musa-dsl/series/quantizer-serie.rb', line 48 def quantize(reference: nil, step: nil, value_attribute: nil, stops: nil, predictive: nil, left_open: nil, right_open: nil) # TODO remove debugging puts, intermediate hash comments on :info and InspectNice Series::Constructors.QUANTIZE(self, reference: reference, step: step, value_attribute: value_attribute, stops: stops, predictive: predictive, left_open: left_open, right_open: right_open) end |
#queued ⇒ QueueSerie
Wraps this serie in a queue.
190 191 192 |
# File 'lib/musa-dsl/series/queue-serie.rb', line 190 def queued Series::Constructors.QUEUE(self) end |
#randomize(random: nil) ⇒ Randomizer
Randomizes order of values.
Shuffles values randomly. Requires finite serie.
318 319 320 321 322 323 324 325 326 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 318 def randomize(random: nil) # Without this the Integer reached Randomizer intact and only failed # when the serie was consumed, with a traceback pointing far away from # the call that caused it (issue #87). random = Random.new(random) if random.is_a?(Integer) random ||= Random.new Randomizer.new self, random end |
#remove(block = nil) {|value| ... } ⇒ Remover
Removes values matching condition.
Filters out values where block returns true.
344 345 346 347 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 344 def remove(block = nil, &yield_block) block ||= yield_block Remover.new self, &block end |
#repeat(times = nil, condition: nil) { ... } ⇒ Repeater, InfiniteRepeater
Repeats serie multiple times or conditionally.
Three modes:
- times: Repeat exact number of times
- condition: Repeat while condition true
- neither: Infinite repetition
146 147 148 149 150 151 152 153 154 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 146 def repeat(times = nil, condition: nil, &condition_block) condition ||= condition_block if times || condition Repeater.new self, times, &condition else InfiniteRepeater.new self end end |
#reverse ⇒ Reverser
Reverses order of values.
Consumes entire serie and returns values in reverse order. Requires finite serie.
295 296 297 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 295 def reverse Reverser.new self end |
#select(block = nil) {|value| ... } ⇒ Selector
Selects values matching condition.
Keeps only values where block returns true.
365 366 367 368 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 365 def select(block = nil, &yield_block) block ||= yield_block Selector.new self, &block end |
#shift(shift) ⇒ Shifter
Rotates serie elements circularly.
Performs circular rotation of elements:
- Negative values rotate left (first elements move to end)
- Positive values rotate right (last elements move to beginning)
- Zero performs no rotation
Note: Right rotation (positive values) requires finite series as the entire serie must be loaded into memory for rotation.
268 269 270 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 268 def shift(shift) Shifter.new self, shift end |
#skip(length) ⇒ Skipper
Skips first N values.
Discards first length values, returns rest.
186 187 188 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 186 def skip(length) Skipper.new self, length end |
#split ⇒ Splitter
Serie splitter for decomposing hash/array values into component series.
Splits series of hash or array values into individual component series, enabling independent access to each component.
Splitting Modes
- Hash mode: Split
{pitch: 60, velocity: 96}into separate series for:pitchand:velocity - Array mode: Split
[60, 96]into separate series for indices 0, 1
Component Access
- Hash:
splitter[:pitch],splitter[:velocity] - Array:
splitter[0],splitter[1] - Enumerable:
splitter.each { |component| ... }
Use Cases
- Separate polyphonic voices from single source
- Independent processing of musical parameters
- Extract specific components (pitch, duration, velocity, etc.)
- Multi-track decomposition
45 46 47 |
# File 'lib/musa-dsl/series/hash-or-array-serie-splitter.rb', line 45 def split Splitter.new(self) end |
#switch(*indexed_series, **hash_series) ⇒ Switcher
Switches between multiple series based on selector values.
Uses selector serie to choose which source serie to read from. Selector values can be indices (Integer) or keys (Symbol).
388 389 390 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 388 def switch(*indexed_series, **hash_series) Switcher.new self, indexed_series, hash_series end |
#switch_serie(*indexed_series, **hash_series) ⇒ SwitchFullSerie
Switches to entirely different series based on selector.
Changes which serie is being consumed entirely.
416 417 418 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 416 def switch_serie(*indexed_series, **hash_series) SwitchFullSerie.new self, indexed_series, hash_series end |
#union_timed(*other_timed_series, key: nil, **other_key_timed_series) ⇒ TimedUnionOfArrayOfTimedSeries, TimedUnionOfHashOfTimedSeries
Combines this timed serie with others via TIMED_UNION.
Convenience method for unioning series, supporting both array and hash modes. Calls Constructors#TIMED_UNION constructor with appropriate parameters.
Array mode: s1.union_timed(s2, s3)
Hash mode: s1.union_timed(key: :melody, bass: s2, drums: s3)
618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/musa-dsl/series/timed-serie.rb', line 618 def union_timed(*other_timed_series, key: nil, **other_key_timed_series) if key && other_key_timed_series.any? Series::Constructors.TIMED_UNION(key => self, **other_key_timed_series) elsif other_timed_series.any? && other_key_timed_series.empty? Series::Constructors.TIMED_UNION(self, *other_timed_series) else raise ArgumentError, 'Can\'t union an array of series with a hash of series' end end |
#with(*with_series, on_restart: nil, isolate_values: nil, **with_key_series) {|main_value, with_values, with_key_values| ... } ⇒ With Also known as: eval
Combines multiple series for mapping.
Synchronously iterates multiple series, passing all values to block. Enables multi-voice transformations and combinations.
Parameters
- with_series: Positional series (passed as array to block)
- with_key_series: Named series (passed as keywords to block)
- on_restart: Block called on restart
- isolate_values: Clone values to prevent mutation
Block Parameters
Block receives:
- Main serie value (first argument)
- Positional with_series values (array)
- Keyword with_key_series values (keywords)
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 537 def with(*with_series, on_restart: nil, isolate_values: nil, **with_key_series, &block) if with_series.any? && with_key_series.any? raise ArgumentError, 'Can\'t use extra parameters series and key named parameters series' end extra_series = if with_series.any? with_series elsif with_key_series.any? with_key_series end isolate_values ||= isolate_values.nil? ? true : isolate_values ProcessWith.new self, extra_series, on_restart, isolate_values: isolate_values, &block end |