class Xi::P
A Pattern
is a lazy, infinite enumeration of values in time.
An event represents a value that occurs in a specific moment in time. It is a value together with its onset (start position) in terms of cycles, and its duration. It is usually represented by a tuple of (value, start, duration, iteration). This tuple indicates when a value occurs in time (start), its duration, and on which iteration of the pattern happens.
P
is an alias of Pattern
, so you can build them using P
instead. Note that if the pattern was built from an array, the string representation can be used to build the same pattern again (almost the same ignoring whitespace between constructor arguments).
P[1,2,3] #=> P[1, 2, 3]
Attributes
Event delta in terms of cycles (default: 1)
Duration of pattern
Hash
that contains metadata related to pattern usage
Size of pattern
Array
or Proc that produces values or events
Public Class Methods
Create a new Pattern
given an array of args
@see Pattern#initialize
@param args [Array] @param kwargs [Hash] @return [Pattern]
# File lib/xi/pattern.rb, line 120 def self.[](*args, **kwargs) new(args, **kwargs) end
Creates a new Pattern
given either a source
or a block
that yields events.
If a block is given, yielder
parameter must yield value
and start
(optional) for each event.
@example Pattern
from an Array
Pattern.new(['a', 'b', 'c']).take(5) # => [['a', 0, 1, 0], # ['b', 1, 1, 0], # ['c', 2, 1, 0], # ['a', 3, 1, 1], # starts cycling... # ['b', 4, 1, 1]]
@example Pattern
from a block that yields only values.
Pattern.new { |y| y << rand(100) }.take(5) # => [[52, 0, 1, 0], # [8, 1, 1, 0], # [83, 2, 1, 0], # [25, 3, 1, 0], # [3, 4, 1, 0]]
@param source [Array] @param size [Integer] number of events per iteration @param delta [Numeric, Array
<Numeric>, Pattern
<Numeric>] event delta @param metadata [Hash] @yield [yielder, delta] yielder and event delta @yieldreturn [value, start, duration] @return [Pattern]
# File lib/xi/pattern.rb, line 69 def initialize(source=nil, size: nil, delta: nil, **metadata, &block) if source.nil? && block.nil? fail ArgumentError, 'must provide source or block' end if delta && delta.respond_to?(:size) && !(delta.size < Float::INFINITY) fail ArgumentError, 'delta cannot be infinite' end # If delta is an array of 1 or 0 values, flatten array delta = delta.first if delta.is_a?(Array) && delta.size <= 1 # Block takes precedence as source, even though +source+ can be used to # infer attributes @source = block || source # Infer attributes from +source+ if it is a pattern if source.is_a?(Pattern) @delta = source.delta @size = source.size @metadata = source.metadata else @delta = 1 @size = (source.respond_to?(:size) ? source.size : nil) || Float::INFINITY @metadata = {} end # Flatten source if it is a pattern @source = @source.source if @source.is_a?(Pattern) # Override or merge custom attributes if they were specified @size = size if size @delta = delta if delta @metadata.merge!(metadata) # Flatten delta values to an array, if it is an enumerable or pattern @delta = @delta.to_a if @delta.respond_to?(:to_a) # Set duration based on delta values @duration = delta_values.reduce(:+) || 0 end
Public Instance Methods
@private
# File lib/xi/pattern.rb, line 430 def ==(o) self.class == o.class && delta == o.delta && size == o.size && duration == o.duration && metadata == o.metadata && (finite? && to_a == o.to_a) end
Calls the given block once for each value in source
@example
Pattern.new([1, 2, 3]).each.to_a # => [1, 2, 3]
@return [Enumerator] @yield [Object] value
# File lib/xi/pattern.rb, line 241 def each return enum_for(__method__) unless block_given? each_event { |v, _, _, i| break if i > 0 yield v } end
Calls the given block passing the delta of each value in pattern
This method is used internally by {#each_event} to calculate when each event in pattern occurs in time. If no block is given, an Enumerator
is returned instead.
@param index [Numeric] @yield [d] duration @return [Enumerator]
# File lib/xi/pattern.rb, line 209 def each_delta(index=0) return enum_for(__method__, index) unless block_given? delta = @delta if delta.is_a?(Array) size = delta.size return if size == 0 start = index.floor i = start % size loop do yield delta[i] i = (i + 1) % size start += 1 end elsif delta.is_a?(Pattern) delta.each_event(index) { |v, _| yield v } else loop { yield delta } end end
Calls the given block once for each event, passing its value, start position, duration and iteration as parameters.
cycle
can be any number, even if there is no event that starts exactly at that moment. It will start from the next event.
If no block is given, an enumerator is returned instead.
Enumeration loops forever, and starts yielding events based on pattern's delta and from the cycle
position, which is by default 0.
@example block yields value, start, duration and iteration
Pattern.new([1, 2], delta: 0.25).each_event.take(4) # => [[1, 0.0, 0.25, 0], # [2, 0.25, 0.25, 0], # [1, 0.5, 0.25, 1], # [2, 0.75, 0.25, 1]]
@example cycle
is used to start iterating from that moment in time
Pattern.new([:a, :b, :c], delta: 1/2).each_event(42).take(4) # => [[:a, (42/1), (1/2), 28], # [:b, (85/2), (1/2), 28], # [:c, (43/1), (1/2), 28], # [:a, (87/2), (1/2), 29]]
@example cycle
can also be a fractional number
Pattern.new([:a, :b, :c]).each_event(0.97).take(3) # => [[:b, 1, 1, 0], # [:c, 2, 1, 0], # [:a, 3, 1, 1]]
@param cycle [Numeric] @yield [v, s, d, i] value, start, duration and iteration @return [Enumerator]
# File lib/xi/pattern.rb, line 194 def each_event(cycle=0) return enum_for(__method__, cycle) unless block_given? EventEnumerator.new(self, cycle).each { |v, s, d, i| yield v, s, d, i } end
Returns true if pattern is finite
A pattern is finite if it has a finite size.
@return [Boolean] @see infinite?
# File lib/xi/pattern.rb, line 155 def finite? !infinite? end
Returns the first element, or the first n
elements, of the pattern.
If the pattern is empty, the first form returns nil, and the second form returns an empty array.
@see take
@param n [Integer] @param args same arguments as {#take} @return [Object, Array]
# File lib/xi/pattern.rb, line 387 def first(n=nil, *args) res = take(n || 1, *args) n.nil? ? res.first : res end
Returns a string containing a human-readable representation
When source is not a Proc, this string can be evaluated to construct the same instance.
@return [String]
# File lib/xi/pattern.rb, line 399 def inspect ss = if @source.respond_to?(:join) @source.map(&:inspect).join(', ') elsif @source.is_a?(Proc) "?proc" else @source.inspect end ms = @metadata.reject { |_, v| v.nil? } ms.merge!(delta: delta) if delta != 1 ms = ms.map { |k, v| "#{k}: #{v.inspect}" }.join(', ') "P[#{ss}#{", #{ms}" unless ms.empty?}]" end
Returns pattern interation size or length
This is usually calculated from the least-common multiple between the sum of delta values and the size of the pattern. If pattern is infinite, pattern size is assumed to be 1, so iteration size depends on delta values.
@return [Integer]
# File lib/xi/pattern.rb, line 425 def iteration_size finite? ? delta_size.lcm(@size) : delta_size end
Returns a new Pattern
with the results of running block
once for every value in self
If no block is given, an Enumerator
is returned.
@yield [v, s, d, i] value, start, duration and iteration @yieldreturn [v, s, d] value, start (optional) and duration (optional) @return [Pattern]
# File lib/xi/pattern.rb, line 296 def map return enum_for(__method__) unless block_given? Pattern.new(self) do |y, d| each_event do |v, s, ed, i| y << yield(v, s, ed, i) end end end
Returns a new Pattern
with the same source
, but with delta
overriden and metadata
merged.
@param delta [Array<Numeric>, Pattern
<Numeric>, Numeric] @param metadata [Hash] @return [Pattern]
# File lib/xi/pattern.rb, line 131 def p(*delta, **metadata) delta = delta.compact.empty? ? @delta : delta Pattern.new(@source, delta: delta, size: @size, **@metadata.merge(metadata)) end
@see take_values
# File lib/xi/pattern.rb, line 367 def peek(n=10, *args) take_values(n, *args) end
@see take
# File lib/xi/pattern.rb, line 372 def peek_events(n=10, cycle=0) take(n, cycle) end
Returns a Pattern
containing all events of self
for which block
is false.
If no block is given, an Enumerator
is returned.
@see Pattern#select
@yield [v, s, d, i] value, start, duration and iteration @yieldreturn [Boolean] whether event is rejected @return [Pattern]
# File lib/xi/pattern.rb, line 340 def reject return enum_for(__method__) unless block_given? select { |v, s, d, i| !yield(v, s, d, i) } end
Same as {#each} but in reverse order
@example
Pattern.new([1, 2, 3]).reverse_each.to_a # => [3, 2, 1]
@return [Enumerator] @yield [Object] value
# File lib/xi/pattern.rb, line 259 def reverse_each return enum_for(__method__) unless block_given? each.to_a.reverse.each { |v| yield v } end
Returns a Pattern
containing all events of self
for which block
is true.
If no block is given, an Enumerator
is returned.
@see Pattern#reject
@yield [v, s, d, i] value, start, duration and iteration @yieldreturn [Boolean] whether value is selected @return [Pattern]
# File lib/xi/pattern.rb, line 318 def select return enum_for(__method__) unless block_given? Pattern.new(self) do |y, d| each_event do |v, s, ed, i| y << v if yield(v, s, ed, i) end end end
Returns the first n
events from the pattern, starting from cycle
@param n [Integer] @param cycle [Numeric] @return [Array] values
# File lib/xi/pattern.rb, line 352 def take(n, cycle=0) each_event(cycle).take(n) end
Returns the first n
values from self
, starting from cycle
.
Only values are returned, start position and duration are ignored.
@see take
# File lib/xi/pattern.rb, line 362 def take_values(*args) take(*args).map(&:first) end
Returns an array of values from a single iteration of pattern
@return [Array] values @see to_events
# File lib/xi/pattern.rb, line 269 def to_a fail StandardError, 'pattern is infinite' if infinite? each.to_a end
Returns an array of events (i.e. a tuple [value, start, duration, iteration]) from the first iteration.
Only applies to finite patterns.
@return [Array] events @see to_a
# File lib/xi/pattern.rb, line 282 def to_events fail StandardError, 'pattern is infinite' if infinite? each_event.take(size) end
Private Instance Methods
# File lib/xi/pattern.rb, line 507 def delta_size @delta.respond_to?(:each) && @delta.respond_to?(:size) ? @delta.size : 1 end
# File lib/xi/pattern.rb, line 503 def delta_values each_delta.take(iteration_size) end