Module: HeadMusic::Style::Guidelines::DissonanceFigureDetection

Included in:
FloridDissonanceTreatment, WeakBeatDissonanceTreatment
Defined in:
lib/head_music/style/guidelines/dissonance_figure_detection.rb

Overview

Shared detection of ornamental dissonance figures (nota cambiata, double neighbor) plus the melodic helpers those figures and their surrounding guidelines rely on. Include in any dissonance treatment guideline that recognizes these figures. Expects the including class to provide: #notes, #cantus_firmus, #voice.

Instance Method Summary collapse

Instance Method Details

#all_consonant_with_cantus?(*checked_notes) ⇒ Boolean (private)

Returns:

  • (Boolean)


99
100
101
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 99

def all_consonant_with_cantus?(*checked_notes)
  checked_notes.all? { |note| consonant_with_cantus?(note) }
end

#cambiata_as_note_2?(index) ⇒ Boolean (private)

Returns:

  • (Boolean)


58
59
60
61
62
63
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 58

def cambiata_as_note_2?(index)
  return false if index < 1 || index + 3 > notes.length - 1

  n1, n2, n3, n4, n5 = notes[index - 1, 5]
  cambiata_shape?(n1, n2, n3, n4, n5) && all_consonant_with_cantus?(n1, n3, n5)
end

#cambiata_dissonance?(note) ⇒ Boolean (private)

Nota cambiata: a five-note figure where note 2 is dissonant, approached by step from note 1, leaps a third in the same direction to note 3, then notes 3-4-5 proceed stepwise in the opposite direction. Notes 1, 3, and 5 must be consonant with the CF.

Returns:

  • (Boolean)


44
45
46
47
48
49
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 44

def cambiata_dissonance?(note)
  index = notes.index(note)
  return false unless index

  cambiata_as_note_2?(index)
end

#cambiata_shape?(n1, n2, n3, n4, n5) ⇒ Boolean (private)

Returns:

  • (Boolean)


65
66
67
68
69
70
71
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 65

def cambiata_shape?(n1, n2, n3, n4, n5)
  approach = melodic_interval_between(n1, n2)
  leap = melodic_interval_between(n2, n3)
  return false unless approach.step? && leap.number == 3 && approach.direction == leap.direction

  steps_back_from?(leap, melodic_interval_between(n3, n4), melodic_interval_between(n4, n5))
end

#cantus_firmus_positionsObject (private)

Positions where the cantus firmus sounds a note (the strong beats).



18
19
20
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 18

def cantus_firmus_positions
  @cantus_firmus_positions ||= Set.new(cantus_firmus.notes.map { |note| note.position.to_s })
end

#consonant_with_cantus?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


103
104
105
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 103

def consonant_with_cantus?(note)
  !dissonant_with_cantus?(note)
end

#dissonant_with_cantus?(note) ⇒ Boolean (private)

A note whose pitch clashes with the cantus firmus in two-part harmony.

Returns:

  • (Boolean)


12
13
14
15
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 12

def dissonant_with_cantus?(note)
  interval = HeadMusic::Analysis::HarmonicInterval.new(cantus_firmus, voice, note.position)
  interval.notes.length == 2 && interval.dissonance?(:two_part_harmony)
end

#double_neighbor_figure?(index, offset:) ⇒ Boolean (private)

Double neighbor: a four-note figure within one bar. Beats 1 and 4 are the same pitch (consonant), beats 2 and 3 are upper and lower neighbors connected by a leap of a third.

Returns:

  • (Boolean)


83
84
85
86
87
88
89
90
91
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 83

def double_neighbor_figure?(index, offset:)
  start = index - offset
  return false if start < 0 || start + 3 > notes.length - 1

  n1, n2, n3, n4 = notes[start, 4]
  double_neighbor_shape?(n1, n2, n3, n4) &&
    n1.pitch == n4.pitch &&
    all_consonant_with_cantus?(n1, n4)
end

#double_neighbor_member?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


51
52
53
54
55
56
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 51

def double_neighbor_member?(note)
  index = notes.index(note)
  return false unless index

  double_neighbor_figure?(index, offset: 1) || double_neighbor_figure?(index, offset: 2)
end

#double_neighbor_shape?(n1, n2, n3, n4) ⇒ Boolean (private)

Returns:

  • (Boolean)


93
94
95
96
97
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 93

def double_neighbor_shape?(n1, n2, n3, n4)
  melodic_interval_between(n1, n2).step? &&
    melodic_interval_between(n2, n3).number == 3 &&
    melodic_interval_between(n3, n4).step?
end

#melodic_interval_between(note1, note2) ⇒ Object (private)



107
108
109
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 107

def melodic_interval_between(note1, note2)
  HeadMusic::Analysis::MelodicInterval.new(note1, note2)
end

#neighbor_tone?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


26
27
28
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 26

def neighbor_tone?(note)
  stepwise_figure?(note, same_direction: false)
end

#passing_tone?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


22
23
24
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 22

def passing_tone?(note)
  stepwise_figure?(note, same_direction: true)
end

#steps_back_from?(leap, step_back_1, step_back_2) ⇒ Boolean (private)

Notes 3-4-5 must both step in the direction opposite the leap.

Returns:

  • (Boolean)


74
75
76
77
78
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 74

def steps_back_from?(leap, step_back_1, step_back_2)
  step_back_1.step? && step_back_2.step? &&
    step_back_1.direction != leap.direction &&
    step_back_2.direction != leap.direction
end

#stepwise_figure?(note, same_direction:) ⇒ Boolean (private)

Returns:

  • (Boolean)


30
31
32
33
34
35
36
37
38
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 30

def stepwise_figure?(note, same_direction:)
  prev_note = preceding_note(note)
  next_note = following_note(note)
  return false unless prev_note && next_note

  approach = melodic_interval_between(prev_note, note)
  departure = melodic_interval_between(note, next_note)
  approach.step? && departure.step? && (approach.direction == departure.direction) == same_direction
end