class Musicality::ScoreCollator

Combine multiple program segments to one, using tempo/note/dynamic replication and truncation where necessary.

Public Class Methods

new(score) click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 8
def initialize score
  unless score.valid?
    raise ScoreNotValidError, "errors found in score: #{score.errors}"
  end
  @score = score
  @program = score.program.any? ? score.program : [0...score.duration]
end

Public Instance Methods

collate_key_changes() click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 39
def collate_key_changes
  collate_changes(@score.start_key, @score.key_changes, @program)
end
collate_meter_changes() click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 35
def collate_meter_changes
  collate_changes(@score.start_meter, @score.meter_changes, @program)
end
collate_parts() click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 16
def collate_parts
  Hash[
    @score.parts.map do |name, part|
      dyn_comp = ValueComputer.new(part.start_dynamic,part.dynamic_changes)

      new_part = part.clone
      new_part.notes = collate_notes(part.notes, @program)
      new_part.start_dynamic, new_part.dynamic_changes = collate_changes(
        part.start_dynamic, part.dynamic_changes, @program
      )
      [ name, new_part ]
    end
  ]
end
collate_tempo_changes() click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 31
def collate_tempo_changes
  collate_changes(@score.start_tempo, @score.tempo_changes, @program)
end

Private Instance Methods

collate_changes(start_value, changes, program_segments) click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 45
def collate_changes start_value, changes, program_segments
  new_changes = {}
  comp = ValueComputer.new(start_value, changes)
  segment_start_offset = 0.to_r
  
  new_start_val = comp.at(program_segments.first.first)
  
  program_segments.each_with_index do |seg, i|
    seg = seg.first...seg.last
    
    # add segment start value, but only if it's different than the value at
    # the end of the prev segment
    value = comp.at seg.first
    if i != 0 && comp.at(program_segments[i-1].last - 1e-5) != value
      new_changes[segment_start_offset] = Change::Immediate.new(value)
    end
    
    changes.each do |off,change|
             adj_start_off = (off - seg.first) + segment_start_offset

      new_change = case change
      when Change::Immediate
        change.clone if seg.include?(off)
      when Change::Gradual::Trimmed
        end_off = off + change.remaining
        if off < seg.last && end_off > seg.first
          add_preceding = seg.first > off ? seg.first - off : 0
          add_trailing = end_off > seg.last ? end_off - seg.last : 0

          if add_preceding == 0 && add_trailing == 0
            change.clone
          else
            adj_start_off += add_preceding
            change.untrim.trim(change.preceding + add_preceding,
                               change.trailing + add_trailing)
          end
        end
      when Change::Gradual
        end_off = off + change.duration
        if off < seg.last && end_off > seg.first
          preceding = seg.first > off ? seg.first - off : 0
          trailing = end_off > seg.last ? end_off - seg.last : 0
          if preceding == 0 && trailing == 0
            change.clone
          else
            adj_start_off += preceding
            change.trim(preceding, trailing)
          end
        end
      end
      
      unless new_change.nil?
        new_changes[adj_start_off] = new_change
      end
    end
  end
  
  return new_start_val, new_changes
end
collate_notes(notes, program_segments) click to toggle source
# File lib/musicality/performance/conversion/score_collator.rb, line 105
def collate_notes notes, program_segments
  new_notes = []
  program_segments.each do |seg|
    cur_offset = 0
    cur_notes = []
    
    i = 0
    while cur_offset < seg.first && i < notes.size
      cur_offset += notes[i].duration
      i += 1
    end
    
    pre_remainder = cur_offset - seg.first
    if pre_remainder > 0
      cur_notes << Note.new(pre_remainder)
    end
    
    # found some notes to add...
    if i < notes.size
      while cur_offset < seg.last && i < notes.size
        cur_notes << notes[i].clone
        cur_offset += notes[i].duration
        i += 1
      end
      overshoot = cur_offset - seg.last
      if overshoot > 0
        cur_notes.last.duration = cur_notes.last.duration - overshoot
        cur_offset = seg.last
      end
    end
    
    post_remainder = seg.last - cur_offset
    if post_remainder > 0
      cur_notes << Note.new(post_remainder)
    end
      
    new_notes.concat cur_notes
  end
  return new_notes
end