Class: Xi::Pattern

Inherits:
Object show all
Extended by:
Generators
Includes:
Transforms
Defined in:
lib/xi/pattern.rb,
lib/xi/pattern/generators.rb,
lib/xi/pattern/transforms.rb

Overview

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]

Defined Under Namespace

Modules: Generators, Transforms Classes: EventEnumerator

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Generators

geom, isaw, rand, saw, series, shuf, sin, tri, xrand

Methods included from Transforms

#%, #*, #**, #+, #-, #-@, #/, #bounce, #denormalize, #every, #every_iter, #fast, #normalize, #rand, #repeat_each, #scale, #seq, #shuf, #slow, #sometimes, #when, #xrand

Constructor Details

#initialize(source = nil, size: nil, delta: nil, **metadata) {|yielder, delta| ... } ⇒ Pattern

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.

Examples:

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

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

Parameters:

  • source (Array) (defaults to: nil)
  • size (Integer) (defaults to: nil)

    number of events per iteration

  • delta (Numeric, Array<Numeric>, Pattern<Numeric>) (defaults to: nil)

    event delta

  • metadata (Hash)

Yields:

  • (yielder, delta)

    yielder and event delta

Yield Returns:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/xi/pattern.rb', line 69

def initialize(source=nil, size: nil, delta: nil, **, &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.
  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!()

  # 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

Instance Attribute Details

#deltaObject (readonly)

Event delta in terms of cycles (default: 1)



28
29
30
# File 'lib/xi/pattern.rb', line 28

def delta
  @delta
end

#durationObject (readonly)

Duration of pattern



37
38
39
# File 'lib/xi/pattern.rb', line 37

def duration
  @duration
end

#metadataObject (readonly)

Hash that contains metadata related to pattern usage



31
32
33
# File 'lib/xi/pattern.rb', line 31

def 
  @metadata
end

#sizeObject (readonly)

Size of pattern



34
35
36
# File 'lib/xi/pattern.rb', line 34

def size
  @size
end

#sourceObject (readonly)

Array or Proc that produces values or events



25
26
27
# File 'lib/xi/pattern.rb', line 25

def source
  @source
end

Class Method Details

.[](*args, **kwargs) ⇒ Pattern

Create a new Pattern given an array of args

Parameters:

Returns:

See Also:



120
121
122
# File 'lib/xi/pattern.rb', line 120

def self.[](*args, **kwargs)
  new(args, **kwargs)
end

Instance Method Details

#==(o) ⇒ Object



430
431
432
433
434
435
436
437
# File 'lib/xi/pattern.rb', line 430

def ==(o)
  self.class == o.class &&
    delta == o.delta &&
    size == o.size &&
    duration == o.duration &&
     == o. &&
    (finite? && to_a == o.to_a)
end

#each {|Object| ... } ⇒ Enumerator

Calls the given block once for each value in source

Examples:

Pattern.new([1, 2, 3]).each.to_a
# => [1, 2, 3]

Yields:

Returns:



241
242
243
244
245
246
247
248
# 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

#each_delta(index = 0) {|d| ... } ⇒ Enumerator

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.

Parameters:

  • index (Numeric) (defaults to: 0)

Yields:

  • (d)

    duration

Returns:



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 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

#each_event(cycle = 0) {|v, s, d, i| ... } ⇒ Enumerator

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.

Examples:

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

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

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

Parameters:

  • cycle (Numeric) (defaults to: 0)

Yields:

  • (v, s, d, i)

    value, start, duration and iteration

Returns:



194
195
196
197
# 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

#finite?Boolean

Returns true if pattern is finite

A pattern is finite if it has a finite size.

Returns:

  • (Boolean)

See Also:



155
156
157
# File 'lib/xi/pattern.rb', line 155

def finite?
  !infinite?
end

#first(n = nil, *args) ⇒ Object, Array

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.

Parameters:

  • n (Integer) (defaults to: nil)
  • args

    same arguments as #take

Returns:

See Also:



387
388
389
390
# File 'lib/xi/pattern.rb', line 387

def first(n=nil, *args)
  res = take(n || 1, *args)
  n.nil? ? res.first : res
end

#infinite?Boolean

Returns true if pattern is infinite

A Pattern is infinite if it was created from a Proc or another infinite pattern, and size was not specified.

Returns:

  • (Boolean)

See Also:



144
145
146
# File 'lib/xi/pattern.rb', line 144

def infinite?
  @size == Float::INFINITY
end

#inspectString Also known as: to_s

Returns a string containing a human-readable representation

When source is not a Proc, this string can be evaluated to construct the same instance.

Returns:



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# 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

#iteration_sizeInteger

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.

Returns:



425
426
427
# File 'lib/xi/pattern.rb', line 425

def iteration_size
  finite? ? delta_size.lcm(@size) : delta_size
end

#map {|v, s, d, i| ... } ⇒ Pattern Also known as: collect

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.

Yields:

  • (v, s, d, i)

    value, start, duration and iteration

Yield Returns:

  • (v, s, d)

    value, start (optional) and duration (optional)

Returns:



296
297
298
299
300
301
302
303
304
# 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

#p(*delta, **metadata) ⇒ Pattern

Returns a new Pattern with the same source, but with delta overriden and metadata merged.

Parameters:

Returns:



131
132
133
134
# File 'lib/xi/pattern.rb', line 131

def p(*delta, **)
  delta = delta.compact.empty? ? @delta : delta
  Pattern.new(@source, delta: delta, size: @size, **@metadata.merge())
end

#peek(n = 10, *args) ⇒ Object

See Also:



367
368
369
# File 'lib/xi/pattern.rb', line 367

def peek(n=10, *args)
  take_values(n, *args)
end

#peek_events(n = 10, cycle = 0) ⇒ Object

See Also:



372
373
374
# File 'lib/xi/pattern.rb', line 372

def peek_events(n=10, cycle=0)
  take(n, cycle)
end

#reject {|v, s, d, i| ... } ⇒ Pattern

Returns a Pattern containing all events of self for which block is false.

If no block is given, an Enumerator is returned.

Yields:

  • (v, s, d, i)

    value, start, duration and iteration

Yield Returns:

  • (Boolean)

    whether event is rejected

Returns:

See Also:



340
341
342
343
344
# 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

#reverse_each {|Object| ... } ⇒ Enumerator

Same as #each but in reverse order

Examples:

Pattern.new([1, 2, 3]).reverse_each.to_a
# => [3, 2, 1]

Yields:

Returns:



259
260
261
262
# 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

#select {|v, s, d, i| ... } ⇒ Pattern Also known as: find_all

Returns a Pattern containing all events of self for which block is true.

If no block is given, an Enumerator is returned.

Yields:

  • (v, s, d, i)

    value, start, duration and iteration

Yield Returns:

  • (Boolean)

    whether value is selected

Returns:

See Also:



318
319
320
321
322
323
324
325
326
# 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

#take(n, cycle = 0) ⇒ Array

Returns the first n events from the pattern, starting from cycle

Parameters:

  • n (Integer)
  • cycle (Numeric) (defaults to: 0)

Returns:



352
353
354
# File 'lib/xi/pattern.rb', line 352

def take(n, cycle=0)
  each_event(cycle).take(n)
end

#take_values(*args) ⇒ Object

Returns the first n values from self, starting from cycle.

Only values are returned, start position and duration are ignored.

See Also:



362
363
364
# File 'lib/xi/pattern.rb', line 362

def take_values(*args)
  take(*args).map(&:first)
end

#to_aArray

Returns an array of values from a single iteration of pattern

Returns:

See Also:



269
270
271
272
# File 'lib/xi/pattern.rb', line 269

def to_a
  fail StandardError, 'pattern is infinite' if infinite?
  each.to_a
end

#to_eventsArray

Returns an array of events (i.e. a tuple [value, start, duration, iteration]) from the first iteration.

Only applies to finite patterns.

Returns:

See Also:



282
283
284
285
# File 'lib/xi/pattern.rb', line 282

def to_events
  fail StandardError, 'pattern is infinite' if infinite?
  each_event.take(size)
end