class Timed::Sequence

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.

Public Instance Methods

&(other)
Alias for: intersect
begin() click to toggle source

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.

# File lib/timed/sequence.rb, line 33
def begin
  empty? ? 0 : first.begin
end
each_edge() { |begin| ... } click to toggle source

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.

# 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_edge() { |begin| ... } click to toggle source

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.

# 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_edge() { |end| ... } click to toggle source

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.

# 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
end() click to toggle source

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.

# File lib/timed/sequence.rb, line 41
def end
  empty? ? 0 : last.end
end
first(n = 1, after: nil, &block) click to toggle source

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.

Calls superclass method
# 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) click to toggle source

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

# 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
Also aliased as: &
intersect_time(other, from: nil, to: nil) click to toggle source

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.

# 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) { |begin, end| ... } click to toggle source

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.

# 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) click to toggle source

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.

Calls superclass method
# 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) click to toggle source

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.

# File lib/timed/sequence.rb, line 132
def offset(time)
  time
end
offset=(*c)
Alias for: offset_by
offset_by(*c) click to toggle source

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.
# 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
Also aliased as: offset=
time() click to toggle source

Returns the total time made up by the items

# File lib/timed/sequence.rb, line 47
def time
  each_item.reduce(0) { |a, e| a + e.duration }
end

Protected Instance Methods

create_item(*args) click to toggle source

Protected factory method for creating items compatible with the sequence. This method is called whenever an arbitrary object is pushed or unshifted onto the list and need to be wraped inside an Item.

args - any arguments will be passed on to Item.new.

Returns a new Item.

# File lib/timed/sequence.rb, line 289
          def create_item(*args)
  Item.new(*args)
end

Private Instance Methods

redefine_method(name, body) click to toggle source

Private helper method for (re)defining method on the singleton class.

name - symbol name of the method. body - proc that will be used as method body.

# File lib/timed/sequence.rb, line 298
        def redefine_method name, body
  singleton_class.send :remove_method, name
rescue NameError
ensure
  singleton_class.send :define_method, name, body
end