Class: HeadMusic::Style::Guidelines::Contoured
- Inherits:
-
Annotation
- Object
- Annotation
- HeadMusic::Style::Guidelines::Contoured
show all
- Defined in:
- lib/head_music/style/guidelines/contoured.rb
Overview
Flags a melody without the configured contour
Configure the contour with the factory, e.g. Contoured.with(:arch).
Constant Summary
collapse
- CONTOURS =
%i[ascending descending arch valley wave static].freeze
- TREND_REVERSAL_SEMITONES =
a trend reversal must exceed a whole step
3
Class Method Summary
collapse
Instance Method Summary
collapse
Class Method Details
.with(contour_key) ⇒ Object
11
12
13
14
15
16
17
18
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 11
def self.with(contour_key)
contour = contour_key.to_s.downcase.to_sym
unless CONTOURS.include?(contour)
raise ArgumentError, "Contour must be one of: #{CONTOURS.join(", ")} (got #{contour_key.inspect})"
end
super(contour: contour)
end
|
Instance Method Details
#arch? ⇒ Boolean
The climax is by definition the maximum, so “net rise before, net fall after”
is equivalent to both endpoints sitting below the climax pitch.
Climax uniqueness and consonance remain ConsonantClimax’s job.
60
61
62
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 60
def arch?
notes.length >= 3 && first_note.pitch < highest_pitch && last_note.pitch < highest_pitch
end
|
#ascending? ⇒ Boolean
49
50
51
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 49
def ascending?
first_note.pitch == lowest_pitch && last_note.pitch == highest_pitch
end
|
#contour ⇒ Object
Validated again here because Contoured.new(voice, contour: :bogus) bypasses .with.
33
34
35
36
37
38
39
40
41
42
43
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 33
def contour
@contour ||= begin
key = options.fetch(:contour)
contour = key.to_s.downcase.to_sym
unless CONTOURS.include?(contour)
raise ArgumentError, "Contour must be one of: #{CONTOURS.join(", ")} (got #{key.inspect})"
end
contour
end
end
|
#descending? ⇒ Boolean
53
54
55
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 53
def descending?
first_note.pitch == highest_pitch && last_note.pitch == lowest_pitch
end
|
#directional_endpoints? ⇒ Boolean
The highest_pitch > lowest_pitch guard is load-bearing: without it, an
all-same-pitch melody (first == lowest and last == highest simultaneously)
would absurdly fail static.
79
80
81
82
83
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 79
def directional_endpoints?
highest_pitch > lowest_pitch &&
((first_note.pitch == lowest_pitch && last_note.pitch == highest_pitch) ||
(first_note.pitch == highest_pitch && last_note.pitch == lowest_pitch))
end
|
#marks ⇒ Object
20
21
22
23
24
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 20
def marks
return if notes.empty? || matches_contour?
HeadMusic::Style::Mark.for_all(notes)
end
|
#matches_contour? ⇒ Boolean
45
46
47
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 45
def matches_contour?
send("#{contour}?")
end
|
#message ⇒ Object
26
27
28
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 26
def message
"Write a melody with the #{contour} contour."
end
|
#pitch_numbers ⇒ Object
85
86
87
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 85
def pitch_numbers
@pitch_numbers ||= notes.map { |note| note.pitch.midi_note_number }
end
|
#trend_directions ⇒ Object
Zigzag walk: a trend reversal is confirmed only when the melody retraces at
least TREND_REVERSAL_SEMITONES from the running extreme of the current trend,
so stepwise neighbor-note undulation never registers as a trend change.
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 92
def trend_directions
@trend_directions ||= begin
directions = []
direction = nil
high = low = pitch_numbers.first
extreme = nil
pitch_numbers.drop(1).each do |number|
case direction
when nil if number - low >= TREND_REVERSAL_SEMITONES
direction = :ascending
extreme = number
directions << direction
elsif high - number >= TREND_REVERSAL_SEMITONES
direction = :descending
extreme = number
directions << direction
else
high = [high, number].max
low = [low, number].min
end
when :ascending
if number > extreme
extreme = number
elsif extreme - number >= TREND_REVERSAL_SEMITONES
direction = :descending
extreme = number
directions << direction
end
when :descending
if number < extreme
extreme = number
elsif number - extreme >= TREND_REVERSAL_SEMITONES
direction = :ascending
extreme = number
directions << direction
end
end
end
directions
end
end
|
#valley? ⇒ Boolean
64
65
66
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 64
def valley?
notes.length >= 3 && first_note.pitch > lowest_pitch && last_note.pitch > lowest_pitch
end
|
#wave? ⇒ Boolean
68
69
70
|
# File 'lib/head_music/style/guidelines/contoured.rb', line 68
def wave?
trend_directions.length >= 3
end
|