Class: HeadMusic::Notation::MusicXML::BeamGrouper

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/music_xml/beam_grouper.rb

Overview

Computes MusicXML annotations for the ordered noteheads of one measure. Pure and side-effect-free: data in, data out. The Writer builds the Event list and turns the returned Beams into XML.

Defined Under Namespace

Classes: Beam, Event

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(events, group_unit_divisions) ⇒ BeamGrouper

Returns a new instance of BeamGrouper.



22
23
24
25
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 22

def initialize(events, group_unit_divisions)
  @events = events
  @group_unit_divisions = group_unit_divisions
end

Instance Attribute Details

#eventsObject (readonly, private)

Returns the value of attribute events.



33
34
35
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 33

def events
  @events
end

#group_unit_divisionsObject (readonly, private)

Returns the value of attribute group_unit_divisions.



33
34
35
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 33

def group_unit_divisions
  @group_unit_divisions
end

Class Method Details

.annotate(events, group_unit_divisions) ⇒ Object



18
19
20
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 18

def self.annotate(events, group_unit_divisions)
  new(events, group_unit_divisions).annotate
end

Instance Method Details

#add_level_beams(group, level, member_beams) ⇒ Object (private)



68
69
70
71
72
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 68

def add_level_beams(group, level, member_beams)
  participating(group, level).each do |run|
    emit_run(run, level, member_beams)
  end
end

#annotateObject



27
28
29
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 27

def annotate
  groups.flat_map { |group| beams_for_group(group) }
end

#beams_for_group(group) ⇒ Object (private)

Phase B: emit one Array per member, parallel to the group's events.



59
60
61
62
63
64
65
66
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 59

def beams_for_group(group)
  return [[]] if group.size == 1

  member_beams = Array.new(group.size) { [] }
  max_level = group.map { |i| events[i].levels }.max
  (1..max_level).each { |level| add_level_beams(group, level, member_beams) }
  member_beams
end

#break_before?(index) ⇒ Boolean (private)

Returns:

  • (Boolean)


48
49
50
51
52
53
54
55
56
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 48

def break_before?(index)
  event = events[index]
  return true if event.levels.zero?
  return true if events[index - 1].levels.zero?
  return true if event.beam_break_before == true
  return false if event.beam_break_before == false

  (event.onset % group_unit_divisions).zero?
end

#emit_run(run, level, member_beams) ⇒ Object (private)



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 86

def emit_run(run, level, member_beams)
  if run.size == 1
    position = run.first
    # A lone member at this level gets a partial beam (hook). It points
    # backward unless it is the group's first member, which has nothing
    # to hook back to.
    member_beams[position] << Beam.new(number: level, type: position.zero? ? "forward hook" : "backward hook")
    return
  end

  run.each_with_index do |position, offset|
    member_beams[position] << Beam.new(number: level, type: run_type(offset, run.size))
  end
end

#groupsObject (private)

Phase A: segment event indices into groups of consecutive indices.



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 36

def groups
  result = []
  events.each_index do |i|
    if i.zero? || break_before?(i)
      result << [i]
    else
      result.last << i
    end
  end
  result
end

#participating(group, level) ⇒ Object (private)

Maximal runs of consecutive-within-the-group positions whose event has levels >= level. Positions are indices into group (and member_beams).



76
77
78
79
80
81
82
83
84
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 76

def participating(group, level)
  runs = []
  group.each_with_index do |event_index, position|
    if events[event_index].levels >= level
      (runs.last && runs.last.last == position - 1) ? runs.last << position : runs << [position]
    end
  end
  runs
end

#run_type(offset, run_size) ⇒ Object (private)



101
102
103
104
105
106
# File 'lib/head_music/notation/music_xml/beam_grouper.rb', line 101

def run_type(offset, run_size)
  return "begin" if offset.zero?
  return "end" if offset == run_size - 1

  "continue"
end