Class: HeadMusic::Content::Voice

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/head_music/content/voice.rb,
lib/head_music/content/voice/melodic_line.rb,
lib/head_music/content/voice/melodic_note_pair.rb

Overview

A Voice is a stream of music with some indepedence that is conceptually one part or for one performer. The melodic lines in counterpoint are each a voice.

Defined Under Namespace

Classes: MelodicLine, MelodicNotePair

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(composition: nil, role: nil) ⇒ Voice

Returns a new instance of Voice.



13
14
15
16
17
# File 'lib/head_music/content/voice.rb', line 13

def initialize(composition: nil, role: nil)
  @composition = composition || HeadMusic::Content::Composition.new
  @role = role
  @placements = []
end

Instance Attribute Details

#compositionObject (readonly)

Returns the value of attribute composition.



9
10
11
# File 'lib/head_music/content/voice.rb', line 9

def composition
  @composition
end

#placementsObject (readonly)

Returns the value of attribute placements.



9
10
11
# File 'lib/head_music/content/voice.rb', line 9

def placements
  @placements
end

#roleObject (readonly)

Returns the value of attribute role.



9
10
11
# File 'lib/head_music/content/voice.rb', line 9

def role
  @role
end

Instance Method Details

#bar_start_position(placement) ⇒ Object (private)



144
145
146
# File 'lib/head_music/content/voice.rb', line 144

def bar_start_position(placement)
  HeadMusic::Content::Position.new(composition, placement.position.bar_number, 1, 0)
end

#cantus_firmus?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/head_music/content/voice.rb', line 71

def cantus_firmus?
  role.to_s =~ /cantus.?firmus/i
end

#earliest_bar_numberObject



91
92
93
94
95
# File 'lib/head_music/content/voice.rb', line 91

def earliest_bar_number
  return 1 if notes.empty?

  placements.first.position.bar_number
end

#first_gapObject

Returns nil if placements are contiguous, or [expected_position, found_placement] for the first gap: either the first placement not starting its bar, or the first pair of consecutive placements where the second doesn’t begin where the first one ends.



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/head_music/content/voice.rb', line 115

def first_gap
  first = placements.first
  return unless first

  first_position = first.position
  return [bar_start_position(first), first] unless first_position.count == 1 && first_position.tick.zero?

  placements.each_cons(2) do |previous, current|
    expected_position = previous.next_position
    return [expected_position, current] unless current.position == expected_position
  end
  nil
end

#highest_notesObject



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

def highest_notes
  notes.select { |note| note.pitch == highest_pitch }
end

#highest_pitchObject



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

def highest_pitch
  pitches.max
end

#insert_into_placements(placement) ⇒ Object (private)

Positions are unique within a voice (place merges same-position placements), so insertion order is simply position order.



154
155
156
157
# File 'lib/head_music/content/voice.rb', line 154

def insert_into_placements(placement)
  index = @placements.index { |existing| existing > placement } || @placements.length
  @placements.insert(index, placement)
end

#last_placementObject



103
104
105
# File 'lib/head_music/content/voice.rb', line 103

def last_placement
  placements.last
end

#latest_bar_numberObject



97
98
99
100
101
# File 'lib/head_music/content/voice.rb', line 97

def latest_bar_number
  return 1 if notes.empty?

  placements.last.position.bar_number
end

#lowest_notesObject



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

def lowest_notes
  notes.select { |note| note.pitch == lowest_pitch }
end

#lowest_pitchObject



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

def lowest_pitch
  pitches.min
end

#melodic_lineObject



65
66
67
# File 'lib/head_music/content/voice.rb', line 65

def melodic_line
  @melodic_line ||= MelodicLine.new(notes)
end

#next_positionObject



107
108
109
# File 'lib/head_music/content/voice.rb', line 107

def next_position
  last_placement ? last_placement.next_position : HeadMusic::Content::Position.new(composition, 1, 1, 0)
end

#note_at(position) ⇒ Object



75
76
77
# File 'lib/head_music/content/voice.rb', line 75

def note_at(position)
  notes.detect { |note| position.within_placement?(note) }
end

#note_following(position) ⇒ Object



87
88
89
# File 'lib/head_music/content/voice.rb', line 87

def note_following(position)
  notes.detect { |note| note.position > position }
end

#note_preceding(position) ⇒ Object



83
84
85
# File 'lib/head_music/content/voice.rb', line 83

def note_preceding(position)
  notes.reverse.find { |note| note.position < position }
end

#notesObject



28
29
30
# File 'lib/head_music/content/voice.rb', line 28

def notes
  @placements.select(&:pitched?).sort_by(&:position)
end

#notes_during(placement) ⇒ Object



79
80
81
# File 'lib/head_music/content/voice.rb', line 79

def notes_during(placement)
  notes.select { |note| note.during?(placement) }
end

#notes_not_in_keyObject



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

def notes_not_in_key
  key_spellings = key_signature.pitches.map(&:spelling).uniq
  notes.reject { |note| key_spellings.include? note.pitch.spelling }
end

#pitchesObject



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

def pitches
  notes.map(&:pitch)
end

#pitches_stringObject (private)



159
160
161
# File 'lib/head_music/content/voice.rb', line 159

def pitches_string
  pitches.first(10).map(&:to_s).join(" ")
end

#place(position, rhythmic_value, sound_or_sounds = nil) ⇒ Object



19
20
21
22
23
24
25
26
# File 'lib/head_music/content/voice.rb', line 19

def place(position, rhythmic_value, sound_or_sounds = nil)
  placement = HeadMusic::Content::Placement.new(self, position, rhythmic_value, sound_or_sounds)
  existing = placement_at(placement.position)
  return existing.merge(placement) if existing

  insert_into_placements(placement)
  placement
end

#placement_at(position) ⇒ Object (private)



148
149
150
# File 'lib/head_music/content/voice.rb', line 148

def placement_at(position)
  @placements.find { |placement| placement.position == position }
end

#rangeObject



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

def range
  HeadMusic::Analysis::DiatonicInterval.new(lowest_pitch, highest_pitch)
end

#restsObject



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

def rests
  @placements.select(&:rest?)
end

#to_hObject



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

def to_h
  {
    "role" => role&.to_s,
    "placements" => placements.map(&:to_h)
  }
end

#to_sObject



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

def to_s
  return pitches_string if role.to_s.strip == ""

  [role, pitches_string].join(": ")
end