head_music 0.14.2 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 94643e9fad9404dd51c148dd74a9deba4f0100b5
4
- data.tar.gz: 5509506687c478db25e7c38d64ea69994d46f1b2
3
+ metadata.gz: ff284893d21767573fa4d994b8500e229e067aa6
4
+ data.tar.gz: 5dc30f493cfd2b0774c6fb87399b7d237f164166
5
5
  SHA512:
6
- metadata.gz: 5e704efcbffd9738263488cebaaf3478dc8bdf7cae22f55875d681cdb7017d6bea510620f9db77a2e012947caab0677077853e0af818ea5d760302706a4c31d6
7
- data.tar.gz: 6d16156270a86e137083ba7376e4bbc486e9635dc11fd628cef10d453f84ea1280c8b3a2251e1d4efe6c484781295bc39c2db29f1b0e3d976d994a1e67ce6eaa
6
+ metadata.gz: 9aca4f3ab9e889f03a33074f110c1fafc48dc4606e1b8c89ff2d4e8be54b9879cac792f3825c0c3edd0e7c805ae55866d10988e47afbb9ed9b8a1b91b43659cb
7
+ data.tar.gz: e0303cd214d229ad6d20988e2d98e988471f0494c0a56acd2c183b2709b509516b428e839c6580b43d2e699e89f13e3a0a3870f277562da1d3643390103b09f9
@@ -0,0 +1,51 @@
1
+ class HeadMusic::ScaleDegree
2
+ include Comparable
3
+
4
+ NAME_FOR_DIATONIC_DEGREE = [nil, 'tonic', 'supertonic', 'mediant', 'subdominant', 'dominant', 'submediant']
5
+
6
+ attr_reader :key_signature, :spelling
7
+ delegate :scale, to: :key_signature
8
+ delegate :scale_type, to: :scale
9
+
10
+ def initialize(key_signature, spelling)
11
+ @key_signature = key_signature
12
+ @spelling = Spelling.get(spelling)
13
+ end
14
+
15
+ def degree
16
+ scale.letter_name_cycle.index(spelling.letter_name.to_s) + 1
17
+ end
18
+
19
+ def accidental
20
+ spelling.accidental
21
+ scale_degree_usual_spelling.accidental
22
+ accidental_semitones = spelling.accidental && spelling.accidental.semitones || 0
23
+ usual_accidental_semitones = scale_degree_usual_spelling.accidental && scale_degree_usual_spelling.accidental.semitones || 0
24
+ Accidental.for_interval(accidental_semitones - usual_accidental_semitones)
25
+ end
26
+
27
+ def to_s
28
+ "#{accidental}#{degree}"
29
+ end
30
+
31
+ def <=>(other)
32
+ if other.is_a?(HeadMusic::ScaleDegree)
33
+ [degree, accidental.semitones] <=> [other.degree, other.accidental.semitones]
34
+ else
35
+ to_s <=> other.to_s
36
+ end
37
+ end
38
+
39
+ def name_for_degree
40
+ if scale_type.diatonic?
41
+ NAME_FOR_DIATONIC_DEGREE[degree] ||
42
+ (scale_type.intervals.last == 1 || accidental == '#' ? 'leading tone' : 'subtonic')
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def scale_degree_usual_spelling
49
+ Spelling.get(scale.spellings[degree - 1])
50
+ end
51
+ end
@@ -92,4 +92,8 @@ class HeadMusic::ScaleType
92
92
  def parent
93
93
  @parent ||= self.class.get(parent_name) if parent_name
94
94
  end
95
+
96
+ def diatonic?
97
+ intervals.length == 7
98
+ end
95
99
  end
@@ -33,6 +33,10 @@ class HeadMusic::Style::Annotation
33
33
  fitness == 1
34
34
  end
35
35
 
36
+ def notes?
37
+ first_note
38
+ end
39
+
36
40
  def start_position
37
41
  [marks].flatten.compact.map(&:start_position).sort.first
38
42
  end
@@ -113,7 +117,8 @@ class HeadMusic::Style::Annotation
113
117
  end
114
118
 
115
119
  def positions
116
- @positions ||= voices.map(&:notes).flatten.map(&:position).sort.uniq(&:to_s)
120
+ @positions ||=
121
+ voices.map(&:notes).flatten.map(&:position).sort.uniq(&:to_s)
117
122
  end
118
123
 
119
124
  def unsorted_higher_voices
@@ -2,16 +2,56 @@ module HeadMusic::Style::Annotations
2
2
  end
3
3
 
4
4
  class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotation
5
- MESSAGE = "Peak on a consonant high note one time."
5
+ MESSAGE = "Peak on a consonant high note one time or twice with a step between."
6
6
 
7
7
  def marks
8
- if notes
9
- improper_climaxes = highest_notes.select.with_index do |note, i|
10
- tonic_pitch = HeadMusic::Pitch.get(tonic_spelling)
11
- interval = HeadMusic::FunctionalInterval.new(tonic_pitch, note.pitch)
12
- interval.consonance(:melodic).dissonant? || i > 0
13
- end
14
- HeadMusic::Style::Mark.for_each(improper_climaxes)
15
- end
8
+ HeadMusic::Style::Mark.for_each(highest_notes) if !adherent_climax?
9
+ end
10
+
11
+ private
12
+
13
+ def adherent_climax?
14
+ notes? && highest_pitch_consonant_with_tonic? &&
15
+ ( highest_pitch_appears_once? || highest_pitch_appears_twice_with_step_between? )
16
+ end
17
+
18
+ def highest_pitch_consonant_with_tonic?
19
+ functional_interval_to_highest_pitch.consonance?(:melodic)
20
+ end
21
+
22
+ def functional_interval_to_highest_pitch
23
+ @functional_interval_to_highest_pitch ||=
24
+ HeadMusic::FunctionalInterval.new(tonic_pitch, highest_pitch)
25
+ end
26
+
27
+ def tonic_pitch
28
+ @tonic_pitch ||= HeadMusic::Pitch.get(tonic_spelling)
29
+ end
30
+
31
+ def highest_pitch_appears_once?
32
+ highest_notes.length == 1
33
+ end
34
+
35
+ def highest_pitch_appears_twice_with_step_between?
36
+ highest_pitch_appears_twice? &&
37
+ single_note_between_highest_notes? &&
38
+ step_between_highest_notes?
39
+ end
40
+
41
+ def highest_pitch_appears_twice?
42
+ highest_notes.length == 2
43
+ end
44
+
45
+ def step_between_highest_notes?
46
+ MelodicInterval.new(voice, highest_notes.first, notes_between_highest_notes.first).step?
47
+ end
48
+
49
+ def single_note_between_highest_notes?
50
+ notes_between_highest_notes.length == 1
51
+ end
52
+
53
+ def notes_between_highest_notes
54
+ indexes = highest_notes.map { |note| notes.index(note) }
55
+ notes[(indexes.first + 1)..(indexes.last - 1)] || []
16
56
  end
17
57
  end
@@ -5,6 +5,20 @@ class HeadMusic::Style::Annotations::Diatonic < HeadMusic::Style::Annotation
5
5
  MESSAGE = "Use only notes in the key signature."
6
6
 
7
7
  def marks
8
- HeadMusic::Style::Mark.for_each(notes_not_in_key)
8
+ HeadMusic::Style::Mark.for_each(notes_not_in_key_excluding_penultimate_leading_tone)
9
+ end
10
+
11
+ private
12
+
13
+ def notes_not_in_key_excluding_penultimate_leading_tone
14
+ notes_not_in_key.reject do |note|
15
+ penultimate_note &&
16
+ note == penultimate_note &&
17
+ HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).accidental == '#'
18
+ end
19
+ end
20
+
21
+ def penultimate_note
22
+ voice.note_preceding(positions.last) if positions.last
9
23
  end
10
24
  end
@@ -2,10 +2,10 @@ module HeadMusic::Style::Annotations
2
2
  end
3
3
 
4
4
  class HeadMusic::Style::Annotations::SingableIntervals < HeadMusic::Style::Annotation
5
- PERMITTED_ASCENDING = %w[m2 M2 m3 M3 P4 P5 m6 P8]
6
- PERMITTED_DESCENDING = %w[m2 M2 m3 M3 P4 P5 P8]
5
+ PERMITTED_ASCENDING = %w[PU m2 M2 m3 M3 P4 P5 m6 P8]
6
+ PERMITTED_DESCENDING = %w[PU m2 M2 m3 M3 P4 P5 P8]
7
7
 
8
- MESSAGE = "Use only m2, M2, m3, M3, P4, P5, m6 (ascending), P8."
8
+ MESSAGE = "Use only PU, m2, M2, m3, M3, P4, P5, m6 (ascending), P8 in the melodic line."
9
9
 
10
10
  def marks
11
11
  melodic_intervals.reject { |interval| permitted?(interval) }.map do |unpermitted_interval|
@@ -9,6 +9,7 @@ class HeadMusic::Style::Rulesets::CantusFirmus
9
9
  HeadMusic::Style::Annotations::Diatonic,
10
10
  HeadMusic::Style::Annotations::DirectionChanges,
11
11
  HeadMusic::Style::Annotations::EndOnTonic,
12
+ HeadMusic::Style::Annotations::LimitOctaveLeaps,
12
13
  HeadMusic::Style::Annotations::MostlyConjunct,
13
14
  HeadMusic::Style::Annotations::NoRests,
14
15
  HeadMusic::Style::Annotations::NotesSameLength,
@@ -3,7 +3,7 @@ end
3
3
 
4
4
  class HeadMusic::Style::Rulesets::FirstSpeciesMelody
5
5
  RULESET = [
6
- HeadMusic::Style::Annotations::AlwaysMove,
6
+ HeadMusic::Style::Annotations::ConsonantClimax,
7
7
  HeadMusic::Style::Annotations::Diatonic,
8
8
  HeadMusic::Style::Annotations::DirectionChanges,
9
9
  HeadMusic::Style::Annotations::EndOnTonic,
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "0.14.2"
2
+ VERSION = "0.14.4"
3
3
  end
data/lib/head_music.rb CHANGED
@@ -34,6 +34,7 @@ require 'head_music/rhythm'
34
34
  require 'head_music/rhythmic_unit'
35
35
  require 'head_music/rhythmic_value'
36
36
  require 'head_music/scale'
37
+ require 'head_music/scale_degree'
37
38
  require 'head_music/scale_type'
38
39
  require 'head_music/spelling'
39
40
  require 'head_music/staff'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.2
4
+ version: 0.14.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-11 00:00:00.000000000 Z
11
+ date: 2017-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -145,6 +145,7 @@ files:
145
145
  - lib/head_music/rhythmic_unit.rb
146
146
  - lib/head_music/rhythmic_value.rb
147
147
  - lib/head_music/scale.rb
148
+ - lib/head_music/scale_degree.rb
148
149
  - lib/head_music/scale_type.rb
149
150
  - lib/head_music/spelling.rb
150
151
  - lib/head_music/staff.rb