Class: HeadMusic::Content::Placement

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/head_music/content/placement.rb

Overview

A placement is a note, chord, or rest at a position within a voice in a composition

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(voice, position, rhythmic_value, sound_or_sounds = nil) ⇒ Placement

Returns a new instance of Placement.



20
21
22
# File 'lib/head_music/content/placement.rb', line 20

def initialize(voice, position, rhythmic_value, sound_or_sounds = nil)
  ensure_attributes(voice, position, rhythmic_value, sound_or_sounds)
end

Instance Attribute Details

#beam_break_beforeObject

Authored beam grouping relative to the previous placement, set after construction (the Bar-style side-metadata pattern). Tri-state: nil = use the meter-derived default, true = force a beam break before this placement, false = force a beam join to the previous placement. Consumed by the MusicXML writer, which prefers it over the default grouping.



15
16
17
# File 'lib/head_music/content/placement.rb', line 15

def beam_break_before
  @beam_break_before
end

#positionObject (readonly)

Returns the value of attribute position.



8
9
10
# File 'lib/head_music/content/placement.rb', line 8

def position
  @position
end

#rhythmic_valueObject (readonly)

Returns the value of attribute rhythmic_value.



8
9
10
# File 'lib/head_music/content/placement.rb', line 8

def rhythmic_value
  @rhythmic_value
end

#soundsObject (readonly)

Returns the value of attribute sounds.



8
9
10
# File 'lib/head_music/content/placement.rb', line 8

def sounds
  @sounds
end

#voiceObject (readonly)

Returns the value of attribute voice.



8
9
10
# File 'lib/head_music/content/placement.rb', line 8

def voice
  @voice
end

Instance Method Details

#<=>(other) ⇒ Object



82
83
84
# File 'lib/head_music/content/placement.rb', line 82

def <=>(other)
  position <=> other.position
end

#chord?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/head_music/content/placement.rb', line 57

def chord?
  pitches.length > 1
end

#during?(other_placement) ⇒ Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/head_music/content/placement.rb', line 86

def during?(other_placement)
  starts_during?(other_placement) || ends_during?(other_placement) || wraps?(other_placement)
end

#ends_during?(other_placement) ⇒ Boolean (private)

Returns:

  • (Boolean)


120
121
122
# File 'lib/head_music/content/placement.rb', line 120

def ends_during?(other_placement)
  next_position > other_placement.position && next_position <= other_placement.next_position
end

#ensure_attributes(voice, position, rhythmic_value, sound_or_sounds) ⇒ Object (private)



128
129
130
131
132
133
# File 'lib/head_music/content/placement.rb', line 128

def ensure_attributes(voice, position, rhythmic_value, sound_or_sounds)
  @voice = voice
  ensure_position(position)
  @rhythmic_value = HeadMusic::Rudiment::RhythmicValue.get(rhythmic_value)
  @sounds = HeadMusic::Content::SoundResolver.resolve(sound_or_sounds)
end

#ensure_position(position) ⇒ Object (private)



135
136
137
138
139
140
141
# File 'lib/head_music/content/placement.rb', line 135

def ensure_position(position)
  @position = if position.is_a?(HeadMusic::Content::Position)
    position
  else
    HeadMusic::Content::Position.new(composition, position)
  end
end

#merge(other) ⇒ Object

Voice#place merges a same-position placement into the existing one, so a position holds at most one placement. The sound union keeps the chord free of duplicates, making repeated placement of a sound idempotent.



68
69
70
71
72
73
74
75
76
# File 'lib/head_music/content/placement.rb', line 68

def merge(other)
  unless rhythmic_value == other.rhythmic_value
    raise ArgumentError,
      "cannot place a #{other.rhythmic_value} at #{position}: position occupied by a #{rhythmic_value}"
  end

  @sounds = (sounds + other.sounds).uniq.freeze
  self
end

#next_positionObject



78
79
80
# File 'lib/head_music/content/placement.rb', line 78

def next_position
  @next_position ||= position + rhythmic_value
end

#note?Boolean

Returns:

  • (Boolean)


45
46
47
# File 'lib/head_music/content/placement.rb', line 45

def note?
  sounds.length == 1
end

#pitchObject

The top pitch of a chord (or the only pitch of a note), which melodic analysis treats as the melody note. Returns nil for rests and unpitched-only placements; pitched? is the guard. Enharmonic ties resolve to the first-listed pitch (MRI’s max keeps the earliest of equals; a spec pins the behavior).



33
34
35
# File 'lib/head_music/content/placement.rb', line 33

def pitch
  pitches.max
end

#pitched?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/head_music/content/placement.rb', line 61

def pitched?
  sounds.any?(&:pitched?)
end

#pitched_note?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/head_music/content/placement.rb', line 49

def pitched_note?
  note? && pitched?
end

#pitchesObject



24
25
26
# File 'lib/head_music/content/placement.rb', line 24

def pitches
  sounds.select(&:pitched?)
end

#rest?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/head_music/content/placement.rb', line 37

def rest?
  sounds.empty?
end

#sound_datum(sound) ⇒ Object (private)



112
113
114
# File 'lib/head_music/content/placement.rb', line 112

def sound_datum(sound)
  sound.pitched? ? sound.to_s : {"unpitched" => sound.name_key&.to_s}
end

#sound_label(sound) ⇒ Object (private)

Unpitched names may be multi-word, so they are bracketed to keep the space-delimited sound list unambiguous.



108
109
110
# File 'lib/head_music/content/placement.rb', line 108

def sound_label(sound)
  sound.pitched? ? sound.to_s : "[#{sound}]"
end

#sounded?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/head_music/content/placement.rb', line 41

def sounded?
  sounds.any?
end

#starts_during?(other_placement) ⇒ Boolean (private)

Returns:

  • (Boolean)


116
117
118
# File 'lib/head_music/content/placement.rb', line 116

def starts_during?(other_placement)
  position >= other_placement.position && position < other_placement.next_position
end

#to_hObject



94
95
96
97
98
99
100
101
102
# File 'lib/head_music/content/placement.rb', line 94

def to_h
  hash = {
    "position" => position.to_s,
    "rhythmic_value" => rhythmic_value.to_s,
    "sounds" => sounds.map { |sound| sound_datum(sound) }
  }
  hash["beam_break_before"] = beam_break_before unless beam_break_before.nil?
  hash
end

#to_sObject



90
91
92
# File 'lib/head_music/content/placement.rb', line 90

def to_s
  "#{rhythmic_value} #{sounds.any? ? sounds.map { |sound| sound_label(sound) }.join(" ") : "rest"} at #{position}"
end

#unpitched_note?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/head_music/content/placement.rb', line 53

def unpitched_note?
  note? && !pitched?
end

#wraps?(other_placement) ⇒ Boolean (private)

Returns:

  • (Boolean)


124
125
126
# File 'lib/head_music/content/placement.rb', line 124

def wraps?(other_placement)
  position <= other_placement.position && next_position >= other_placement.next_position
end