Class: HeadMusic::Content::Voice
- Inherits:
-
Object
- Object
- HeadMusic::Content::Voice
- 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
-
#composition ⇒ Object
readonly
Returns the value of attribute composition.
-
#placements ⇒ Object
readonly
Returns the value of attribute placements.
-
#role ⇒ Object
readonly
Returns the value of attribute role.
Instance Method Summary collapse
-
#bar_start_position(placement) ⇒ Object
private
-
#cantus_firmus? ⇒ Boolean
-
#earliest_bar_number ⇒ Object
-
#first_gap ⇒ Object
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.
-
#highest_notes ⇒ Object
-
#highest_pitch ⇒ Object
-
#initialize(composition: nil, role: nil) ⇒ Voice
constructor
A new instance of Voice.
-
#insert_into_placements(placement) ⇒ Object
private
Positions are unique within a voice (place merges same-position placements), so insertion order is simply position order.
-
#large_leaps ⇒ Object
-
#last_placement ⇒ Object
-
#latest_bar_number ⇒ Object
-
#leaps ⇒ Object
-
#lowest_notes ⇒ Object
-
#lowest_pitch ⇒ Object
-
#melodic_intervals ⇒ Object
-
#melodic_note_pairs ⇒ Object
-
#next_position ⇒ Object
-
#note_at(position) ⇒ Object
-
#note_following(position) ⇒ Object
-
#note_preceding(position) ⇒ Object
-
#notes ⇒ Object
-
#notes_during(placement) ⇒ Object
-
#notes_not_in_key ⇒ Object
-
#pitches ⇒ Object
-
#pitches_string ⇒ Object
private
-
#place(position, rhythmic_value, sound_or_sounds = nil) ⇒ Object
-
#placement_at(position) ⇒ Object
private
-
#range ⇒ Object
-
#rests ⇒ Object
-
#to_h ⇒ Object
-
#to_s ⇒ Object
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
#composition ⇒ Object (readonly)
Returns the value of attribute composition.
9 10 11 |
# File 'lib/head_music/content/voice.rb', line 9 def composition @composition end |
#placements ⇒ Object (readonly)
Returns the value of attribute placements.
9 10 11 |
# File 'lib/head_music/content/voice.rb', line 9 def placements @placements end |
#role ⇒ Object (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 (placement) HeadMusic::Content::Position.new(composition, placement.position., 1, 0) end |
#cantus_firmus? ⇒ 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_number ⇒ Object
104 105 106 107 108 |
# File 'lib/head_music/content/voice.rb', line 104 def return 1 if notes.empty? placements.first.position. end |
#first_gap ⇒ Object
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 [(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_notes ⇒ Object
53 54 55 |
# File 'lib/head_music/content/voice.rb', line 53 def highest_notes notes.select { |note| note.pitch == highest_pitch } end |
#highest_pitch ⇒ Object
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_leaps ⇒ Object
80 81 82 |
# File 'lib/head_music/content/voice.rb', line 80 def large_leaps melodic_note_pairs.select(&:large_leap?) end |
#last_placement ⇒ Object
116 117 118 |
# File 'lib/head_music/content/voice.rb', line 116 def last_placement placements.last end |
#latest_bar_number ⇒ Object
110 111 112 113 114 |
# File 'lib/head_music/content/voice.rb', line 110 def return 1 if notes.empty? placements.last.position. end |
#leaps ⇒ Object
76 77 78 |
# File 'lib/head_music/content/voice.rb', line 76 def leaps melodic_note_pairs.select(&:leap?) end |
#lowest_notes ⇒ Object
57 58 59 |
# File 'lib/head_music/content/voice.rb', line 57 def lowest_notes notes.select { |note| note.pitch == lowest_pitch } end |
#lowest_pitch ⇒ Object
49 50 51 |
# File 'lib/head_music/content/voice.rb', line 49 def lowest_pitch pitches.min end |
#melodic_intervals ⇒ Object
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_pairs ⇒ Object
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_position ⇒ Object
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 |
#notes ⇒ Object
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_key ⇒ Object
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 |
#pitches ⇒ Object
37 38 39 |
# File 'lib/head_music/content/voice.rb', line 37 def pitches notes.map(&:pitch) end |
#pitches_string ⇒ Object (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 |
#range ⇒ Object
61 62 63 |
# File 'lib/head_music/content/voice.rb', line 61 def range HeadMusic::Analysis::DiatonicInterval.new(lowest_pitch, highest_pitch) end |
#rests ⇒ Object
41 42 43 |
# File 'lib/head_music/content/voice.rb', line 41 def rests @placements.select(&:rest?) end |
#to_h ⇒ Object
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_s ⇒ Object
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 |