Class: HeadMusic::Content::Voice

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/head_music/content/voice.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: 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)



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

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

#cantus_firmus?Boolean

Returns:

  • (Boolean)


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

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

#earliest_bar_numberObject



104
105
106
107
108
# File 'lib/head_music/content/voice.rb', line 104

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.



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/head_music/content/voice.rb', line 128

def first_gap
  first = placements.first
  return unless first

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

  placements.each_cons(2) do |previous, current|
    return [previous.next_position, current] unless current.position == previous.next_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.



165
166
167
168
# File 'lib/head_music/content/voice.rb', line 165

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

#large_leapsObject



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

def large_leaps
  melodic_note_pairs.select(&:large_leap?)
end

#last_placementObject



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

def last_placement
  placements.last
end

#latest_bar_numberObject



110
111
112
113
114
# File 'lib/head_music/content/voice.rb', line 110

def latest_bar_number
  return 1 if notes.empty?

  placements.last.position.bar_number
end

#leapsObject



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

def leaps
  melodic_note_pairs.select(&:leap?)
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_intervalsObject



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

def melodic_intervals
  @melodic_intervals ||=
    melodic_note_pairs.map { |note_pair| HeadMusic::Analysis::MelodicInterval.new(*note_pair.notes) }
end

#melodic_note_pairsObject



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

def melodic_note_pairs
  @melodic_note_pairs ||= notes.each_cons(2).map do |first_note, second_note|
    HeadMusic::Content::Voice::MelodicNotePair.new(first_note, second_note)
  end
end

#next_positionObject



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

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

#note_at(position) ⇒ Object



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

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

#note_following(position) ⇒ Object



100
101
102
# File 'lib/head_music/content/voice.rb', line 100

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

#note_preceding(position) ⇒ Object



96
97
98
# File 'lib/head_music/content/voice.rb', line 96

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



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

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)



170
171
172
# File 'lib/head_music/content/voice.rb', line 170

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)



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

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



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

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

#to_sObject



140
141
142
143
144
# File 'lib/head_music/content/voice.rb', line 140

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

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