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