xi-lang 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/xi/pattern.rb CHANGED
@@ -1,172 +1,501 @@
1
- require 'forwardable'
2
- require 'xi/event'
3
1
  require 'xi/pattern/transforms'
4
2
  require 'xi/pattern/generators'
5
3
 
6
4
  module Xi
5
+ # A Pattern is a lazy, infinite enumeration of values in time.
6
+ #
7
+ # An event represents a value that occurs in a specific moment in time. It
8
+ # is a value together with its onset (start position) in terms of cycles, and
9
+ # its duration. It is usually represented by a tuple of (value, start,
10
+ # duration, iteration). This tuple indicates when a value occurs in time
11
+ # (start), its duration, and on which iteration of the pattern happens.
12
+ #
13
+ # P is an alias of Pattern, so you can build them using P instead. Note that
14
+ # if the pattern was built from an array, the string representation can be
15
+ # used to build the same pattern again (almost the same ignoring whitespace
16
+ # between constructor arguments).
17
+ #
18
+ # P[1,2,3] #=> P[1, 2, 3]
19
+ #
7
20
  class Pattern
8
- include Enumerable
21
+ extend Generators
9
22
  include Transforms
10
- include Generators
11
- extend Forwardable
12
23
 
13
- attr_reader :source, :event_duration, :metadata, :total_duration
24
+ # Array or Proc that produces values or events
25
+ attr_reader :source
14
26
 
15
- alias_method :dur, :event_duration
27
+ # Event delta in terms of cycles (default: 1)
28
+ attr_reader :delta
16
29
 
17
- def_delegators :@source, :size
30
+ # Hash that contains metadata related to pattern usage
31
+ attr_reader :metadata
18
32
 
19
- # Creates a new Pattern given either a +source+ or a block
20
- # that yields values
33
+ # Size of pattern
34
+ attr_reader :size
35
+
36
+ # Duration of pattern
37
+ attr_reader :duration
38
+
39
+ # Creates a new Pattern given either a +source+ or a +block+ that yields
40
+ # events.
41
+ #
42
+ # If a block is given, +yielder+ parameter must yield +value+ and +start+
43
+ # (optional) for each event.
44
+ #
45
+ # @example Pattern from an Array
46
+ # Pattern.new(['a', 'b', 'c']).take(5)
47
+ # # => [['a', 0, 1, 0],
48
+ # # ['b', 1, 1, 0],
49
+ # # ['c', 2, 1, 0],
50
+ # # ['a', 3, 1, 1], # starts cycling...
51
+ # # ['b', 4, 1, 1]]
52
+ #
53
+ # @example Pattern from a block that yields only values.
54
+ # Pattern.new { |y| y << rand(100) }.take(5)
55
+ # # => [[52, 0, 1, 0],
56
+ # # [8, 1, 1, 0],
57
+ # # [83, 2, 1, 0],
58
+ # # [25, 3, 1, 0],
59
+ # # [3, 4, 1, 0]]
21
60
  #
22
- # @param source [#each]
23
- # @param size [Fixnum] number of elements (default: nil)
24
- # @param dur [Hash]
61
+ # @param source [Array]
62
+ # @param size [Fixnum] number of events per iteration
63
+ # @param delta [Numeric, Array<Numeric>, Pattern<Numeric>] event delta
64
+ # @param metadata [Hash]
65
+ # @yield [yielder, delta] yielder and event delta
66
+ # @yieldreturn [value, start, duration]
67
+ # @return [Pattern]
25
68
  #
26
- def initialize(source=nil, size: nil, **metadata)
27
- if source.nil? && !block_given?
69
+ def initialize(source=nil, size: nil, delta: nil, **metadata, &block)
70
+ if source.nil? && block.nil?
28
71
  fail ArgumentError, 'must provide source or block'
29
72
  end
30
73
 
31
- size ||= source.size if source.respond_to?(:size)
74
+ if delta && delta.respond_to?(:size) && !(delta.size < Float::INFINITY)
75
+ fail ArgumentError, 'delta cannot be infinite'
76
+ end
77
+
78
+ # If delta is an array of 1 or 0 values, flatten array
79
+ delta = delta.first if delta.is_a?(Array) && delta.size <= 1
32
80
 
33
- @source = if block_given?
34
- Enumerator.new(size) { |y| yield y }
81
+ # Block takes precedence as source, even though +source+ can be used to
82
+ # infer attributes
83
+ @source = block || source
84
+
85
+ # Infer attributes from +source+ if it is a pattern
86
+ if source.is_a?(Pattern)
87
+ @delta = source.delta
88
+ @size = source.size
89
+ @metadata = source.metadata
35
90
  else
36
- source
91
+ @delta = 1
92
+ @size = (source.respond_to?(:size) ? source.size : nil) ||
93
+ Float::INFINITY
94
+ @metadata = {}
37
95
  end
38
96
 
39
- @event_duration = metadata.delete(:dur) || metadata.delete(:event_duration)
40
- @event_duration ||= source.event_duration if source.respond_to?(:event_duration)
41
- @event_duration ||= 1
97
+ # Flatten source if it is a pattern
98
+ @source = @source.source if @source.is_a?(Pattern)
42
99
 
43
- @metadata = source.respond_to?(:metadata) ? source.metadata : {}
100
+ # Override or merge custom attributes if they were specified
101
+ @size = size if size
102
+ @delta = delta if delta
44
103
  @metadata.merge!(metadata)
45
104
 
46
- @is_infinite = @source.size.nil? || @source.size == Float::INFINITY
105
+ # Flatten delta values to an array, if it is an enumerable or pattern
106
+ @delta = @delta.to_a if @delta.respond_to?(:to_a)
47
107
 
48
- if @is_infinite
49
- @total_duration = if @event_duration.respond_to?(:each)
50
- @event_duration.each.first
51
- else
52
- @event_duration
53
- end
54
- else
55
- last_ev = each_event.take(@source.size).last
56
- @total_duration = last_ev ? last_ev.start + last_ev.duration : 0
57
- end
108
+ # Set duration based on delta values
109
+ @duration = delta_values.reduce(:+) || 0
110
+ end
111
+
112
+ # Create a new Pattern given an array of +args+
113
+ #
114
+ # @see Pattern#initialize
115
+ #
116
+ # @param args [Array]
117
+ # @param kwargs [Hash]
118
+ # @return [Pattern]
119
+ #
120
+ def self.[](*args, **kwargs)
121
+ new(args, **kwargs)
58
122
  end
59
123
 
60
- def self.[](*args, **metadata)
61
- new(args, **metadata)
124
+ # Returns a new Pattern with the same +source+, but with +delta+ overriden
125
+ # and +metadata+ merged.
126
+ #
127
+ # @param delta [Array<Numeric>, Pattern<Numeric>, Numeric]
128
+ # @param metadata [Hash]
129
+ # @return [Pattern]
130
+ #
131
+ def p(*delta, **metadata)
132
+ delta = delta.compact.empty? ? @delta : delta
133
+ Pattern.new(@source, delta: delta, size: @size, **@metadata.merge(metadata))
62
134
  end
63
135
 
136
+ # Returns true if pattern is infinite
137
+ #
138
+ # A Pattern is infinite if it was created from a Proc or another infinite
139
+ # pattern, and size was not specified.
140
+ #
141
+ # @return [Boolean]
142
+ # @see #finite?
143
+ #
64
144
  def infinite?
65
- @is_infinite
145
+ @size == Float::INFINITY
66
146
  end
67
147
 
148
+ # Returns true if pattern is finite
149
+ #
150
+ # A pattern is finite if it has a finite size.
151
+ #
152
+ # @return [Boolean]
153
+ # @see #infinite?
154
+ #
68
155
  def finite?
69
156
  !infinite?
70
157
  end
71
158
 
72
- def ==(o)
73
- self.class == o.class &&
74
- source == o.source &&
75
- event_duration == o.event_duration &&
76
- metadata == o.metadata
159
+ # Calls the given block once for each event, passing its value, start
160
+ # position, duration and iteration as parameters.
161
+ #
162
+ # +cycle+ can be any number, even if there is no event that starts exactly
163
+ # at that moment. It will start from the next event.
164
+ #
165
+ # If no block is given, an enumerator is returned instead.
166
+ #
167
+ # Enumeration loops forever, and starts yielding events based on pattern's
168
+ # delta and from the +cycle+ position, which is by default 0.
169
+ #
170
+ # @example block yields value, start, duration and iteration
171
+ # Pattern.new([1, 2], delta: 0.25).each_event.take(4)
172
+ # # => [[1, 0.0, 0.25, 0],
173
+ # # [2, 0.25, 0.25, 0],
174
+ # # [1, 0.5, 0.25, 1],
175
+ # # [2, 0.75, 0.25, 1]]
176
+ #
177
+ # @example +cycle+ is used to start iterating from that moment in time
178
+ # Pattern.new([:a, :b, :c], delta: 1/2).each_event(42).take(4)
179
+ # # => [[:a, (42/1), (1/2), 28],
180
+ # # [:b, (85/2), (1/2), 28],
181
+ # # [:c, (43/1), (1/2), 28],
182
+ # # [:a, (87/2), (1/2), 29]]
183
+ #
184
+ # @example +cycle+ can also be a fractional number
185
+ # Pattern.new([:a, :b, :c]).each_event(0.97).take(3)
186
+ # # => [[:b, 1, 1, 0],
187
+ # # [:c, 2, 1, 0],
188
+ # # [:a, 3, 1, 1]]
189
+ #
190
+ # @param cycle [Numeric]
191
+ # @yield [v, s, d, i] value, start, duration and iteration
192
+ # @return [Enumerator]
193
+ #
194
+ def each_event(cycle=0)
195
+ return enum_for(__method__, cycle) unless block_given?
196
+ EventEnumerator.new(self, cycle).each { |v, s, d, i| yield v, s, d, i }
77
197
  end
78
198
 
79
- def p(dur=nil, **metadata)
80
- Pattern.new(@source, dur: dur || @event_duration,
81
- size: size, **@metadata.merge(metadata))
82
- end
199
+ # Calls the given block passing the delta of each value in pattern
200
+ #
201
+ # This method is used internally by {#each_event} to calculate when each
202
+ # event in pattern occurs in time. If no block is given, an Enumerator is
203
+ # returned instead.
204
+ #
205
+ # @param index [Numeric]
206
+ # @yield [d] duration
207
+ # @return [Enumerator]
208
+ #
209
+ def each_delta(index=0)
210
+ return enum_for(__method__, index) unless block_given?
83
211
 
84
- def each_event
85
- return enum_for(__method__) unless block_given?
212
+ delta = @delta
86
213
 
87
- dur_enum = each_event_duration
88
- pos = 0
214
+ if delta.is_a?(Array)
215
+ size = delta.size
216
+ return if size == 0
89
217
 
90
- @source.each do |value|
91
- if value.is_a?(Pattern)
92
- value.each do |v|
93
- dur = dur_enum.next
94
- yield Event.new(v, pos, dur)
95
- pos += dur
96
- end
97
- elsif value.is_a?(Event)
98
- yield value
99
- pos += value.duration
100
- else
101
- dur = dur_enum.next
102
- yield Event.new(value, pos, dur)
103
- pos += dur
218
+ start = index.floor
219
+ i = start % size
220
+ loop do
221
+ yield delta[i]
222
+ i = (i + 1) % size
223
+ start += 1
104
224
  end
225
+ elsif delta.is_a?(Pattern)
226
+ delta.each_event(index) { |v, _| yield v }
227
+ else
228
+ loop { yield delta }
105
229
  end
106
230
  end
107
231
 
232
+ # Calls the given block once for each value in source
233
+ #
234
+ # @example
235
+ # Pattern.new([1, 2, 3]).each.to_a
236
+ # # => [1, 2, 3]
237
+ #
238
+ # @return [Enumerator]
239
+ # @yield [Object] value
240
+ #
108
241
  def each
109
242
  return enum_for(__method__) unless block_given?
110
- each_event { |e| yield e.value }
243
+
244
+ each_event { |v, _, _, i|
245
+ break if i > 0
246
+ yield v
247
+ }
111
248
  end
112
249
 
113
- def each_event_duration
250
+ # Same as {#each} but in reverse order
251
+ #
252
+ # @example
253
+ # Pattern.new([1, 2, 3]).reverse_each.to_a
254
+ # # => [3, 2, 1]
255
+ #
256
+ # @return [Enumerator]
257
+ # @yield [Object] value
258
+ #
259
+ def reverse_each
114
260
  return enum_for(__method__) unless block_given?
115
- if @event_duration.respond_to?(:each)
116
- loop { @event_duration.each { |v| yield v } }
117
- else
118
- loop { yield @event_duration }
261
+ each.to_a.reverse.each { |v| yield v }
262
+ end
263
+
264
+ # Returns an array of values from a single iteration of pattern
265
+ #
266
+ # @return [Array] values
267
+ # @see #to_events
268
+ #
269
+ def to_a
270
+ fail StandardError, 'pattern is infinite' if infinite?
271
+ each.to_a
272
+ end
273
+
274
+ # Returns an array of events (i.e. a tuple [value, start, duration,
275
+ # iteration]) from the first iteration.
276
+ #
277
+ # Only applies to finite patterns.
278
+ #
279
+ # @return [Array] events
280
+ # @see #to_a
281
+ #
282
+ def to_events
283
+ fail StandardError, 'pattern is infinite' if infinite?
284
+ each_event.take(size)
285
+ end
286
+
287
+ # Returns a new Pattern with the results of running +block+ once for every
288
+ # value in +self+
289
+ #
290
+ # If no block is given, an Enumerator is returned.
291
+ #
292
+ # @yield [v, s, d, i] value, start, duration and iteration
293
+ # @yieldreturn [v, s, d] value, start (optional) and duration (optional)
294
+ # @return [Pattern]
295
+ #
296
+ def map
297
+ return enum_for(__method__) unless block_given?
298
+
299
+ Pattern.new(self) do |y, d|
300
+ each_event do |v, s, ed, i|
301
+ y << yield(v, s, ed, i)
302
+ end
119
303
  end
120
304
  end
305
+ alias_method :collect, :map
306
+
307
+ # Returns a Pattern containing all events of +self+ for which +block+ is
308
+ # true.
309
+ #
310
+ # If no block is given, an Enumerator is returned.
311
+ #
312
+ # @see Pattern#reject
313
+ #
314
+ # @yield [v, s, d, i] value, start, duration and iteration
315
+ # @yieldreturn [Boolean] whether value is selected
316
+ # @return [Pattern]
317
+ #
318
+ def select
319
+ return enum_for(__method__) unless block_given?
320
+
321
+ Pattern.new(self) do |y, d|
322
+ each_event do |v, s, ed, i|
323
+ y << v if yield(v, s, ed, i)
324
+ end
325
+ end
326
+ end
327
+ alias_method :find_all, :select
328
+
329
+ # Returns a Pattern containing all events of +self+ for which +block+
330
+ # is false.
331
+ #
332
+ # If no block is given, an Enumerator is returned.
333
+ #
334
+ # @see Pattern#select
335
+ #
336
+ # @yield [v, s, d, i] value, start, duration and iteration
337
+ # @yieldreturn [Boolean] whether event is rejected
338
+ # @return [Pattern]
339
+ #
340
+ def reject
341
+ return enum_for(__method__) unless block_given?
342
+
343
+ select { |v, s, d, i| !yield(v, s, d, i) }
344
+ end
345
+
346
+ # Returns the first +n+ events from the pattern, starting from +cycle+
347
+ #
348
+ # @param n [Fixnum]
349
+ # @param cycle [Numeric]
350
+ # @return [Array] values
351
+ #
352
+ def take(n, cycle=0)
353
+ each_event(cycle).take(n)
354
+ end
355
+
356
+ # Returns the first +n+ values from +self+, starting from +cycle+.
357
+ #
358
+ # Only values are returned, start position and duration are ignored.
359
+ #
360
+ # @see #take
361
+ #
362
+ def take_values(*args)
363
+ take(*args).map(&:first)
364
+ end
365
+
366
+ # Returns the first element, or the first +n+ elements, of the pattern.
367
+ #
368
+ # If the pattern is empty, the first form returns nil, and the second form
369
+ # returns an empty array.
370
+ #
371
+ # @see #take
372
+ #
373
+ # @param n [Fixnum]
374
+ # @param args same arguments as {#take}
375
+ # @return [Object, Array]
376
+ #
377
+ def first(n=nil, *args)
378
+ res = take(n || 1, *args)
379
+ n.nil? ? res.first : res
380
+ end
121
381
 
382
+ # Returns a string containing a human-readable representation
383
+ #
384
+ # When source is not a Proc, this string can be evaluated to construct the
385
+ # same instance.
386
+ #
387
+ # @return [String]
388
+ #
122
389
  def inspect
123
390
  ss = if @source.respond_to?(:join)
124
391
  @source.map(&:inspect).join(', ')
125
- elsif @source.is_a?(Enumerator)
126
- "?enum"
392
+ elsif @source.is_a?(Proc)
393
+ "?proc"
127
394
  else
128
395
  @source.inspect
129
396
  end
130
397
 
131
398
  ms = @metadata.reject { |_, v| v.nil? }
132
- ms.merge!(dur: dur) if dur != 1
399
+ ms.merge!(delta: delta) if delta != 1
133
400
  ms = ms.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
134
401
 
135
402
  "P[#{ss}#{", #{ms}" unless ms.empty?}]"
136
403
  end
137
404
  alias_method :to_s, :inspect
138
405
 
139
- def map_events
140
- return enum_for(__method__) unless block_given?
141
- Pattern.new(self) { |y| each_event { |e| y << yield(e) } }
406
+ # Returns pattern interation size or length
407
+ #
408
+ # This is usually calculated from the least-common multiple between the sum
409
+ # of delta values and the size of the pattern. If pattern is infinite,
410
+ # pattern size is assumed to be 1, so iteration size depends on delta
411
+ # values.
412
+ #
413
+ # @return [Fixnum]
414
+ #
415
+ def iteration_size
416
+ finite? ? delta_size.lcm(@size) : delta_size
142
417
  end
143
- alias_method :collect_events, :map_events
144
418
 
145
- def select_events
146
- return enum_for(__method__) unless block_given?
147
- Pattern.new(self) { |y| each_event { |e| y << e if yield(e) } }
419
+ # @private
420
+ def ==(o)
421
+ self.class == o.class &&
422
+ delta == o.delta &&
423
+ size == o.size &&
424
+ duration == o.duration &&
425
+ metadata == o.metadata &&
426
+ (finite? && to_a == o.to_a)
148
427
  end
149
- alias_method :find_all_events, :select_events
150
428
 
151
- def reject_events
152
- return enum_for(__method__) unless block_given?
153
- Pattern.new(self) { |y| each_event { |e| y << e unless yield(e) } }
154
- end
429
+ private
155
430
 
156
- def to_events
157
- each_event.to_a
431
+ class EventEnumerator
432
+ def initialize(pattern, cycle)
433
+ @cycle = cycle
434
+
435
+ @source = pattern.source
436
+ @size = pattern.size
437
+ @iter_size = pattern.iteration_size
438
+
439
+ @iter = pattern.duration > 0 ? (cycle / pattern.duration).floor : 0
440
+ @delta_enum = pattern.each_delta(@iter * @iter_size)
441
+ @start = @iter * pattern.duration
442
+ @prev_ev = nil
443
+ @i = 0
444
+ end
445
+
446
+ def each(&block)
447
+ return enum_for(__method__, @cycle) unless block_given?
448
+
449
+ return if @size == 0
450
+
451
+ if @source.respond_to?(:call)
452
+ loop do
453
+ yielder = ::Enumerator::Yielder.new do |value|
454
+ each_block(value, &block)
455
+ end
456
+ @source.call(yielder, @delta_enum.peek)
457
+ end
458
+ elsif @source.respond_to?(:each_event)
459
+ @source.each_event(@start) do |value, _|
460
+ each_block(value, &block)
461
+ end
462
+ elsif @source.respond_to?(:[])
463
+ loop do
464
+ each_block(@source[@i % @size], &block)
465
+ end
466
+ else
467
+ fail StandardError, 'invalid source'
468
+ end
469
+ end
470
+
471
+ private
472
+
473
+ def each_block(value)
474
+ delta = @delta_enum.peek
475
+
476
+ if @start >= @cycle
477
+ if @prev_ev
478
+ yield @prev_ev if @start > @cycle
479
+ @prev_ev = nil
480
+ end
481
+ yield value, @start, delta, @iter
482
+ else
483
+ @prev_ev = [value, @start, delta, @iter]
484
+ end
485
+
486
+ @iter += 1 if @i + 1 == @iter_size
487
+ @i = (@i + 1) % @iter_size
488
+ @start += delta
489
+ @delta_enum.next
490
+ end
158
491
  end
159
492
 
160
- def peek(limit=10)
161
- values = take(limit + 1)
162
- puts "There are more than #{limit} values..." if values.size > limit
163
- values.take(limit)
493
+ def delta_values
494
+ each_delta.take(iteration_size)
164
495
  end
165
496
 
166
- def peek_events(limit=10)
167
- events = each_event.take(limit + 1)
168
- puts "There are more than #{limit} events..." if events.size > limit
169
- events.take(limit)
497
+ def delta_size
498
+ @delta.respond_to?(:each) && @delta.respond_to?(:size) ? @delta.size : 1
170
499
  end
171
500
  end
172
501
  end
@@ -0,0 +1,40 @@
1
+ class Xi::StepSequencer
2
+ attr_reader :string, :values
3
+
4
+ def initialize(string, *values)
5
+ @string = string
6
+ @values = values
7
+ end
8
+
9
+ def p(*args, **metadata)
10
+ build_pattern(**metadata)
11
+ end
12
+
13
+ def inspect
14
+ "s(#{@string.inspect}" \
15
+ "#{", #{@values.map(&:inspect).join(', ')}" unless @values.empty?})"
16
+ end
17
+
18
+ private
19
+
20
+ def build_pattern(**metadata)
21
+ val_keys = values_per_key
22
+
23
+ values_per_bar = @string.split('|').map { |bar|
24
+ vs = bar.split(/\s*/).reject(&:empty?)
25
+ vs.map { |k| val_keys[k] }
26
+ }.reject(&:empty?)
27
+
28
+ delta = values_per_bar.map { |vs| [1 / vs.size] * vs.size }.flatten
29
+
30
+ Pattern.new(values_per_bar.flatten, delta: delta, **metadata)
31
+ end
32
+
33
+ def values_per_key
34
+ keys.map.with_index { |k, i| [k, k == '.' ? nil : @values[i]] }.to_h
35
+ end
36
+
37
+ def keys
38
+ @string.scan(/\w/).uniq
39
+ end
40
+ end