Class: HeadMusic::Notation::RenderPlan

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

Overview

The measure-level facts every Writer needs, whatever format it emits; a format's plan subclasses this and adds what only that format needs. Keys are per part, because a mixed ensemble has as many written keys as it has transpositions. Construction eagerly computes everything that can raise, so a plan that builds successfully cannot fail assembly on those grounds.

Direct Known Subclasses

LilyPond::RenderPlan, MusicXML::RenderPlan

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(flow, transposed: false) ⇒ RenderPlan

Returns a new instance of RenderPlan.



12
13
14
15
16
# File 'lib/head_music/notation/render_plan.rb', line 12

def initialize(flow, transposed: false)
  @flow = flow
  @transposed = transposed
  precompute_eager_data
end

Instance Attribute Details

#flow ⇒ Object (readonly)

Returns the value of attribute flow.



10
11
12
# File 'lib/head_music/notation/render_plan.rb', line 10

def flow
  @flow
end

Instance Method Details

#bar_numbers ⇒ Object



22
23
24
# File 'lib/head_music/notation/render_plan.rb', line 22

def bar_numbers
  flow.earliest_bar_number..flow.latest_bar_number
end

#build_first_measure_key(part) ⇒ Object (private)



81
82
83
84
# File 'lib/head_music/notation/render_plan.rb', line 81

def build_first_measure_key(part)
  measure_key_changes(part)[bar_numbers.first] ||
    written_key_value(part, bar_numbers.first, flow.timeline.opening_key_signature_event)
end

#build_measure_key_changes(part) ⇒ Object (private)



86
87
88
89
90
91
# File 'lib/head_music/notation/render_plan.rb', line 86

def build_measure_key_changes(part)
  changes = flow.key_signature_changes
    .select { |bar_number, _| bar_numbers.cover?(bar_number) }
    .to_h { |bar_number, event| [bar_number, written_key_value(part, bar_number, event)] }
  changes.merge(instrument_change_keys(part))
end

#effective_key_event(bar_number) ⇒ Object (private)



105
106
107
108
# File 'lib/head_music/notation/render_plan.rb', line 105

def effective_key_event(bar_number)
  change_bar = flow.key_signature_changes.keys.select { |number| number <= bar_number }.max
  change_bar ? flow.key_signature_changes[change_bar] : flow.timeline.opening_key_signature_event
end

#effective_meter(bar_number) ⇒ Object



44
45
46
47
# File 'lib/head_music/notation/render_plan.rb', line 44

def effective_meter(bar_number)
  change_bar = measure_time_changes.keys.select { |number| number <= bar_number }.max
  change_bar ? measure_time_changes[change_bar] : flow.meter
end

#first_measure_key(part = nil) ⇒ Object



36
37
38
# File 'lib/head_music/notation/render_plan.rb', line 36

def first_measure_key(part = nil)
  keys_by_part[[:first, part]] ||= build_first_measure_key(part)
end

#first_measure_meter ⇒ Object



40
41
42
# File 'lib/head_music/notation/render_plan.rb', line 40

def first_measure_meter
  @first_measure_meter ||= effective_meter(bar_numbers.first)
end

#instrument_change_keys(part) ⇒ Object (private)

A part that changes instrument mid-flow changes written key with it, even though the timeline holds no change there.



95
96
97
98
99
100
101
102
103
# File 'lib/head_music/notation/render_plan.rb', line 95

def instrument_change_keys(part)
  return {} unless transposed? && part

  bars = part.instrument_changes.keys.select { |bar| bar_numbers.cover?(bar) && bar > bar_numbers.first }
  bars.filter_map { |bar|
    value = written_key_value(part, bar, effective_key_event(bar))
    [bar, value] unless value == written_key_value(part, bar - 1, effective_key_event(bar - 1))
  }.to_h
end

#key_value(event) ⇒ Object (private)

How the format renders a key signature event. The event rather than a key signature, because MusicXML wants the signature and the interpretation as separate elements and LilyPond can only say one of them.

Raises:

  • (NotImplementedError)


125
126
127
# File 'lib/head_music/notation/render_plan.rb', line 125

def key_value(event)
  raise NotImplementedError, "#{self.class} must map a key signature event to its rendering"
end

#keys_by_part ⇒ Object (private)



77
78
79
# File 'lib/head_music/notation/render_plan.rb', line 77

def keys_by_part
  @keys_by_part ||= {}
end

#measure_key_changes(part = nil) ⇒ Object

The key changes to print in this part, by bar. A nil part answers the sounding key, which is every part's key in a concert-pitch plan.



28
29
30
# File 'lib/head_music/notation/render_plan.rb', line 28

def measure_key_changes(part = nil)
  keys_by_part[[:changes, part]] ||= build_measure_key_changes(part)
end

#measure_time_changes ⇒ Object



32
33
34
# File 'lib/head_music/notation/render_plan.rb', line 32

def measure_time_changes
  @measure_time_changes ||= flow.meter_changes.select { |bar_number, _| bar_numbers.cover?(bar_number) }
end

#placements_by_bar(voice) ⇒ Object



49
50
51
52
# File 'lib/head_music/notation/render_plan.rb', line 49

def placements_by_bar(voice)
  @placements_by_bar ||= {}
  @placements_by_bar[voice] ||= voice.placements.group_by { |placement| placement.position.bar_number }
end

#precompute_eager_data ⇒ Object (private)

A subclass computes here whatever else must raise at construction, and calls super for the signatures every format needs.



58
59
60
61
62
63
64
# File 'lib/head_music/notation/render_plan.rb', line 58

def precompute_eager_data
  first_measure_key
  first_measure_meter
  measure_key_changes
  measure_time_changes
  precompute_part_keys
end

#precompute_part_keys ⇒ Object (private)

A written key that no signature can print must raise here rather than midway through assembly, so every part's keys are computed up front.



68
69
70
71
72
73
74
75
# File 'lib/head_music/notation/render_plan.rb', line 68

def precompute_part_keys
  return unless transposed?

  flow.parts.each do |part|
    first_measure_key(part)
    measure_key_changes(part)
  end
end

#transposed? ⇒ Boolean

Returns:

  • (Boolean)


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

def transposed?
  !!@transposed
end

#transposition_at(part, bar_number) ⇒ Object (private)



116
117
118
119
120
# File 'lib/head_music/notation/render_plan.rb', line 116

def transposition_at(part, bar_number)
  return nil unless transposed? && part

  HeadMusic::Content::Layout::Transposition.for(part.instrument_at(bar_number))
end

#written_key_value(part, bar_number, event) ⇒ Object (private)

In a concert-pitch plan the event is mapped exactly as it always was.



111
112
113
114
# File 'lib/head_music/notation/render_plan.rb', line 111

def written_key_value(part, bar_number, event)
  transposition = transposition_at(part, bar_number)
  key_value(transposition ? transposition.key_signature_event(event) : event)
end