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 ⇒ Object
-
#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 ⇒ Object
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).
432 433 434 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 432 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.
417 418 419 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 417 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.
586 587 588 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 586 def anticipate(&block) Anticipate.new self, &block end |
#autorestart ⇒ Autorestart
Auto-restarts serie when exhausted.
Creates an infinite serie from an original serie that automatically restarts from beginning when it reaches the end.
111 112 113 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 111 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)
578 579 580 |
# File 'lib/musa-dsl/series/timed-serie.rb', line 578 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
composer do
input_name >> operation1 >> operation2 >> :output_name
:other_input >> transform >> :other_output
end
Musical Applications
- Complex multi-voice processing
- Effect chains and routing
- Algorithmic composition pipelines
- Multi-stage transformations
- Modular synthesis-style routing
63 64 65 |
# File 'lib/musa-dsl/series/series-composer.rb', line 63 def composer(&block) ComposerAsOperationSerie.new(self, &block) end |
#cut(length) ⇒ Cutter
Cuts serie into chunks of specified length.
Returns serie of arrays, each containing length values.
449 450 451 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 449 def cut(length) Cutter.new self, length end |
#flatten ⇒ Flattener
Flattens nested series into single level.
Recursively consumes series elements that are themselves series.
196 197 198 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 196 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
527 528 529 |
# File 'lib/musa-dsl/series/timed-serie.rb', line 527 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.
229 230 231 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 229 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.
605 606 607 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 605 def lazy(&block) LazySerieEval.new self, &block end |
#lock ⇒ Locker
Locks serie preventing further modifications.
Returns locked copy that cannot be transformed further.
274 275 276 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 274 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.
556 557 558 559 560 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 556 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.
164 165 166 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 164 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.
466 467 468 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 466 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.
386 387 388 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 386 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.
212 213 214 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 212 def process_with(**parameters, &processor) Processor.new self, parameters, &processor end |
#proxy ⇒ Object
TODO add test case
112 113 114 |
# File 'lib/musa-dsl/series/proxy-serie.rb', line 112 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
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/musa-dsl/series/quantizer-serie.rb', line 45 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 ⇒ Object
151 152 153 |
# File 'lib/musa-dsl/series/queue-serie.rb', line 151 def queued Series::Constructors.QUEUE(self) end |
#randomize(random: nil) ⇒ Randomizer
Randomizes order of values.
Shuffles values randomly. Requires finite serie.
307 308 309 310 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 307 def randomize(random: nil) random ||= Random.new Randomizer.new self, random end |
#remove(block = nil) {|value| ... } ⇒ Remover
Removes values matching condition.
Filters out values where block returns true.
328 329 330 331 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 328 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
141 142 143 144 145 146 147 148 149 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 141 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.
290 291 292 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 290 def reverse Reverser.new self end |
#select(block = nil) {|value| ... } ⇒ Selector
Selects values matching condition.
Keeps only values where block returns true.
349 350 351 352 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 349 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.
263 264 265 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 263 def shift(shift) Shifter.new self, shift end |
#skip(length) ⇒ Skipper
Skips first N values.
Discards first length values, returns rest.
181 182 183 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 181 def skip(length) Skipper.new self, length end |
#split ⇒ Object
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
44 45 46 |
# File 'lib/musa-dsl/series/hash-or-array-serie-splitter.rb', line 44 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).
372 373 374 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 372 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.
400 401 402 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 400 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)
616 617 618 619 620 621 622 623 624 625 626 |
# File 'lib/musa-dsl/series/timed-serie.rb', line 616 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)
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/musa-dsl/series/main-serie-operations.rb', line 515 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 |