Class: Timed::Sequence

Inherits:
Object
  • Object
show all
Includes:
Linked::List, Moment
Defined in:
lib/timed/sequence.rb

Overview

Sequence

This class implements a sequence of Timed Items. Any object that implements the methods #begin and #end can be added to the sequence. Note that the items must be inserted in chronological order, or the sequence will raise an exception.

Example

sequence = Timed::Sequence.new
sequence << 2..3
sequence.prepend Timed::Item.new 1..2 # Same result as above

A sequence can also be treated like a Moment and be compared, in time, with other compatible objects.

Sequences also provide a mechanism to offset the items in it, in time by providing the #offset method. Items can use it to offset their begin and end times on the fly.

Instance Method Summary collapse

Methods included from Moment

#==, #after?, #before?, #duration, #during?, #inspect

Instance Method Details

#beginObject

Returns the time at which the first item in the sequence, and therefore the sequence as a whole, begins. If the sequence is empty, by convention, it both begins and ends at time 0, giving it a 0 length.



33
34
35
# File 'lib/timed/sequence.rb', line 33

def begin
  empty? ? 0 : first.begin
end

#each_edgeObject

Iterate over all of the edges in the sequence. An edge is the point in time when an item either begins or ends. That time, a numeric value, will be yielded to the block. If a block is not given and enumerator is returned.

Returns an enumerator if a block was not given.



143
144
145
146
147
148
149
150
# File 'lib/timed/sequence.rb', line 143

def each_edge
  return to_enum(__method__) { 2 * count } unless block_given?

  each_item do |item|
    yield item.begin
    yield item.end
  end
end

#each_leading_edgeObject

Iterates over all the leading edges in the sequence. A leading edge is the point in time where an item begins. That time, a numeric value, will be yielded to the block. If a block is not given and enumerator is returned.

Returns an enumerator if a block was not given.



158
159
160
161
162
# File 'lib/timed/sequence.rb', line 158

def each_leading_edge
  return to_enum(__method__) { count } unless block_given?

  each_item { |item| yield item.begin }
end

#each_trailing_edgeObject

Iterates over all the trailing edges in the sequence. A trailing edge is the point in time where an item begins. That time, a numeric value, will be yielded to the block. If a block is not given and enumerator is returned.

Returns an enumerator if a block was not given.



171
172
173
174
175
# File 'lib/timed/sequence.rb', line 171

def each_trailing_edge
  return to_enum(__method__) { count } unless block_given?

  each_item { |item| yield item.end }
end

#endObject

Returns the time at which the last item in the sequence, and therefore the sequence as a whole, ends. If the sequence is empty, by convention, it both begins and ends at time 0, giving it a 0 length.



41
42
43
# File 'lib/timed/sequence.rb', line 41

def end
  empty? ? 0 : last.end
end

#first(n = 1, after: nil, &block) ⇒ Object

Extends the standard behaviour of Linked::List#first with the option of only returning items that begin after a specified time.

after - a time after which the returned item(s) must occur.

Returns one or more items, or nil if there are no items after the given time.



59
60
61
62
63
64
65
66
67
# File 'lib/timed/sequence.rb', line 59

def first(n = 1, after: nil, &block)
  return super(n, &block) unless after

  if include? after
    first_item_after after, n
  else
    super(n) { |item| item.after? after }
  end
end

#intersect(other) ⇒ Object Also known as: &

Returns a new sequence with items that make up the intersection between the two sequences.



232
233
234
235
# File 'lib/timed/sequence.rb', line 232

def intersect(other)
  intersections(other)
    .with_object(self.class.new) { |(b, e), a| a << create_item(b, e) }
end

#intersect_time(other, from: nil, to: nil) ⇒ Object

More efficient than first calling #intersect and then #time on the result.

from - a point in time to start from. to - a point in time to stop at.

Returns the total time of the intersection between this sequence and the other one.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/timed/sequence.rb', line 247

def intersect_time(other, from: nil, to: nil)
  enum = intersections(other)
  total = 0

  if from
    # Reuse the variable total. It's perhaps a bit messy
    # and confusing but it works.
    _, total = enum.next until total > from
    total -= from
  end

  if to
    loop do
      b, e = enum.next

      if e > to
        total += to - b unless b >= to
        break
      end

      total += e - b
    end
  else
    loop do
      b, e = enum.next
      total += e - b
    end
  end

  total
rescue StopIteration
  return 0
end

#intersections(other, &block) ⇒ Object

This method takes a second sequence and iterates over each intersection between the two. If a block is given, the beginning and end of each intersecting period will be yielded to it. Otherwise an enumerator is returned.

other - a sequence, or object that responds to #begin and #end and returns

a Timed::Item in response to #first

Returns an enumerator unless a block is given.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/timed/sequence.rb', line 187

def intersections(other, &block)
  return to_enum __callee__, other unless block_given?

  return unless during? other

  # Sort the first items from each sequence into leading
  # and trailing by whichever begins first
  if self.begin <= other.begin
    item_l, item_t = self.item, other.item
  else
    item_l, item_t = other.item, self.item
  end

  loop do
    # Now there are three posibilities:

    # 1: The leading item ends before the trailing one
    #    begins. In this case the items do not intersect
    #    at all and we do nothing.
    if item_l.end <= item_t.begin

    # 2: The leading item ends before the trailing one
    #    ends
    elsif item_l.end <= item_t.end
      yield item_t.begin, item_l.end

    # 3: The leading item ends after the trailing one
    else
      yield item_t.begin, item_t.end

      # Swap leading and trailing
      item_l, item_t = item_t, item_l
    end

    # Advance the leading item
    item_l = item_l.next

    # Swap leading and trailing if needed
    item_l, item_t = item_t, item_l if item_l.begin > item_t.begin
  end
end

#last(n = 1, before: nil, &block) ⇒ Object

Extends the standard behaviour of Linked::List#last with the option of only returning items that end before a specified time.

after - a time after which the returned item(s) must occur.

Returns one or more items, or nil if there are no items before the given time.



77
78
79
80
81
82
83
84
85
# File 'lib/timed/sequence.rb', line 77

def last(n = 1, before: nil, &block)
  return super(n, &block) unless before

  if include? before
    last_item_before before, n
  else
    super(n) { |item| item.before? before }
  end
end

#offset(time) ⇒ Object

Offset any time using the current offset settings of the sequence. Note that this method is overridden everytime #offset_by is called.

time - the time to be offset.

Returns the offset time.



132
133
134
# File 'lib/timed/sequence.rb', line 132

def offset(time)
  time
end

#offset_by(*c) ⇒ Object Also known as: offset=

Offset the entire sequence by specifying the coefficients of a polynomial of up to degree 2. This is then used to recalculate the begin and end times of each item in the set. The operation does not change the items but is instead performed on the fly every time either #begin or #end is called.

Example

sequence.offset_by 10, 1.1, 0.01
                   ^   ^    ^ Quadratic term
                   |   + Linear term
                   + Constant term
sequence.offset 42.0 # => 73.84

c - list of coefficients, starting with the constant term and ending with,

at most, the quadratic.


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/timed/sequence.rb', line 103

def offset_by(*c)
  body =
    case c.length
    when 0 then proc { |t| t }
    when 1
      if c[0] == 0 || !c[0]
        proc { |t| t }
      else
        proc { |t| c[0] + t }
      end
    when 2 then proc { |t| c[0] + c[1] * t }
    when 3 then proc { |t| c[0] + c[1] * t + c[2] * t**2 }
    else
      raise ArgumentError,
            'Only polynomilas of order 2 or lower are supported'
    end

  redefine_method :offset, body
end

#timeObject

Returns the total time made up by the items



47
48
49
# File 'lib/timed/sequence.rb', line 47

def time
  each_item.reduce(0) { |a, e| a + e.duration }
end