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). 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)


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

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)


54
55
56
57
58
59
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 54

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)


40
41
42
43
44
45
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 40

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)


61
62
63
64
65
66
67
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 61

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)



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

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)


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

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

#dissonant_with_cantus?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


9
10
11
12
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 9

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)


78
79
80
81
82
83
84
85
86
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 78

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)


47
48
49
50
51
52
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 47

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)


88
89
90
91
92
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 88

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)



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

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

#neighbor_tone?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


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

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

#passing_tone?(note) ⇒ Boolean (private)

Returns:

  • (Boolean)


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

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

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

Returns:

  • (Boolean)


69
70
71
72
73
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 69

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)


26
27
28
29
30
31
32
33
34
# File 'lib/head_music/style/guidelines/dissonance_figure_detection.rb', line 26

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