class Music::Performance::PiecewiseFunction

Combine functions that are each applicable for a non-overlapping domain.

@author James Tunnell

Attributes

pieces[R]

Public Class Methods

new(points = []) click to toggle source

Take an array of points (each point is a two-element array pair) and create a piecewise function to calculate values in-between.

# File lib/music-performance/util/piecewise_function.rb, line 12
def initialize points = []
  @pieces = { }
  
  points = points.sort_by {|p| p[0]}
  
  if points.count > 1
    if points.is_a?(Hash)
      points = points.to_a
    end
    
    for i in 1...points.count
      add_points points[i-1], points[i]
    end
  end
end

Public Instance Methods

add_piece(domain, func) click to toggle source

Add a function piece, which covers the given domain (includes domain start but not the end). @param [Range] domain The function domain. If this overlaps an existing domain,

the existing domain will be split with the non-
overlapping pieces kept and the overlapping old piece
discarded.
# File lib/music-performance/util/piecewise_function.rb, line 44
def add_piece domain, func
  
  raise ArgumentError, "domain is not a Range" if !domain.is_a? Range
  raise ArgumentError, "func is not a Proc" if !func.is_a? Proc
  
  contains_domain_completely = @pieces.select { |d,f| d.include?(domain.begin) && d.include?(domain.end) }
  if contains_domain_completely.any?
    contains_domain_completely.each do |d,f|
      l = d.begin...domain.begin
      if d.exclude_end?
        r = domain.end...d.end
      else
        r = domain.end..d.end
      end
      
      @pieces.delete d
      
      if domain.begin != d.begin
        @pieces[l] = f
      end
      if domain.end == d.end
        @pieces[domain.begin..domain.end] = func
      else
        @pieces[domain.begin...domain.end] = func
        @pieces[r] = f
      end
    end
  else
    delete_completely = @pieces.select { |d,f| domain.include?(d.begin) && domain.include?(d.end) }
    delete_completely.each do |d,f|
      @pieces.delete d
    end
    
    # should only be one
    move_end = @pieces.select { |d,f| domain.include?(d.end) }
    move_end.each do |d,f|
      @pieces.delete d
      @pieces[d.begin...domain.begin] = f
    end
    
    # should only be one
    move_begin = @pieces.select { |d,f| domain.include?(d.begin) }
    move_begin.each do |d,f|
      @pieces.delete d
      if d.exclude_end?
        @pieces[domain.end...d.end] = f
      else
        @pieces[domain.end..d.end] = f
      end
    end
    
    if move_begin.any?
      @pieces[domain.begin...domain.end] = func
    else
      @pieces[domain] = func
    end
  end
end
add_points(prev_point, point) click to toggle source
# File lib/music-performance/util/piecewise_function.rb, line 28
def add_points prev_point, point
  domain = prev_point[0]..point[0]
  func = lambda do |x|
    perc = (x - domain.min).to_f / (domain.max - domain.min)
    y = Interpolation.linear prev_point[1], point[1], perc
    return y
  end
  add_piece(domain, func)
end
eval(x) click to toggle source

Evaluate the piecewise function by finding a function piece whose domain includes the given independent value.

# File lib/music-performance/util/piecewise_function.rb, line 105
def eval x
  y = nil
  
  @pieces.each do |domain, func|
    if domain.include? x
      y = func.call x
      break
    end
  end
  
  if y.nil?
    raise ArgumentError, "The input #{x} is not in the domain."
  end
  
  return y
end