xi-lang 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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