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.
- checksums.yaml +4 -4
- data/README.md +24 -11
- data/bin/xi +0 -1
- data/lib/xi/bjorklund.rb +60 -0
- data/lib/xi/clock.rb +9 -1
- data/lib/xi/core_ext/array.rb +13 -0
- data/lib/xi/core_ext/enumerable.rb +6 -14
- data/lib/xi/core_ext/enumerator.rb +14 -0
- data/lib/xi/core_ext/fixnum.rb +3 -3
- data/lib/xi/core_ext/numeric.rb +4 -4
- data/lib/xi/core_ext/scalar.rb +16 -0
- data/lib/xi/core_ext/string.rb +46 -44
- data/lib/xi/core_ext.rb +3 -1
- data/lib/xi/pattern/generators.rb +157 -162
- data/lib/xi/pattern/transforms.rb +96 -79
- data/lib/xi/pattern.rb +424 -95
- data/lib/xi/step_sequencer.rb +40 -0
- data/lib/xi/stream.rb +56 -64
- data/lib/xi/tidal_clock.rb +2 -1
- data/lib/xi/version.rb +1 -1
- data/lib/xi.rb +12 -3
- data/xi.gemspec +0 -1
- metadata +7 -18
- data/lib/xi/core_ext/simple.rb +0 -15
- data/lib/xi/event.rb +0 -82
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
|
-
|
21
|
+
extend Generators
|
9
22
|
include Transforms
|
10
|
-
include Generators
|
11
|
-
extend Forwardable
|
12
23
|
|
13
|
-
|
24
|
+
# Array or Proc that produces values or events
|
25
|
+
attr_reader :source
|
14
26
|
|
15
|
-
|
27
|
+
# Event delta in terms of cycles (default: 1)
|
28
|
+
attr_reader :delta
|
16
29
|
|
17
|
-
|
30
|
+
# Hash that contains metadata related to pattern usage
|
31
|
+
attr_reader :metadata
|
18
32
|
|
19
|
-
#
|
20
|
-
|
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 [
|
23
|
-
# @param size [Fixnum] number of
|
24
|
-
# @param
|
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? &&
|
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
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
91
|
+
@delta = 1
|
92
|
+
@size = (source.respond_to?(:size) ? source.size : nil) ||
|
93
|
+
Float::INFINITY
|
94
|
+
@metadata = {}
|
37
95
|
end
|
38
96
|
|
39
|
-
|
40
|
-
@
|
41
|
-
@event_duration ||= 1
|
97
|
+
# Flatten source if it is a pattern
|
98
|
+
@source = @source.source if @source.is_a?(Pattern)
|
42
99
|
|
43
|
-
|
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
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
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
|
-
@
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
85
|
-
return enum_for(__method__) unless block_given?
|
212
|
+
delta = @delta
|
86
213
|
|
87
|
-
|
88
|
-
|
214
|
+
if delta.is_a?(Array)
|
215
|
+
size = delta.size
|
216
|
+
return if size == 0
|
89
217
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
243
|
+
|
244
|
+
each_event { |v, _, _, i|
|
245
|
+
break if i > 0
|
246
|
+
yield v
|
247
|
+
}
|
111
248
|
end
|
112
249
|
|
113
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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?(
|
126
|
-
"?
|
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!(
|
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
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
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
|
-
|
157
|
-
|
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
|
161
|
-
|
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
|
167
|
-
|
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
|