head_music 0.11.8 → 0.11.9

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: 6be1575228b7a6ea778c74be5e1f0422732fa7e0
4
- data.tar.gz: 5761e7ae91487307331c9f73487d6b91a1fdd8d9
3
+ metadata.gz: 3a0baef52f11f8aab0c4becbe31e70928b358499
4
+ data.tar.gz: 47ae1ef06c1e25a2abd0559eb40a20494c47eed3
5
5
  SHA512:
6
- metadata.gz: b754a036d18c02225ed9bef0e9a778f870adf265a39df3d612dbc67544631496883fe2cda40c5088bfaac58fe46df309e38e43037eb43c4b96258650ca82d49e
7
- data.tar.gz: 4e7bb9461fbf78a833773d1cabcbbe0574e48087fde520f4f181aeafdb4ade491adcc463b79c2b384e50e437bf3e31a50d3ea2885314dea845e75f130075c843
6
+ metadata.gz: 07e0d608df0d6e5901e652c8796b84ef99c2987c94a346347efff6c73d0b159352d0813eaa2a932530fe7f5e1613548c96ca9c2dbcc2b486ef4978d4e6509235
7
+ data.tar.gz: 1dd90a0197c26138b56b109de9dae548c5b9d4b6f4f382b13da80b2fa1093d006c197e26523ff4f2bacb5863bee878d36037049857e8c2655bb3fd61125ec7ae
@@ -3,6 +3,7 @@ class HeadMusic::Composition
3
3
 
4
4
  def initialize(name: nil, key_signature: nil, meter: nil)
5
5
  ensure_attributes(name, key_signature, meter)
6
+ @voices = []
6
7
  end
7
8
 
8
9
  def add_bar
@@ -17,8 +18,8 @@ class HeadMusic::Composition
17
18
  end
18
19
 
19
20
  def add_voice(role: nil)
20
- @voices ||= []
21
21
  @voices << HeadMusic::Voice.new(composition: self, role: role)
22
+ @voices.last
22
23
  end
23
24
 
24
25
  private
@@ -178,6 +178,22 @@ class HeadMusic::FunctionalInterval
178
178
  end
179
179
  end
180
180
 
181
+ def consonance?(style = :standard_practice)
182
+ consonance(style).perfect? || consonance(style).imperfect?
183
+ end
184
+
185
+ def perfect_consonance?(style = :standard_practice)
186
+ consonance(style).perfect?
187
+ end
188
+
189
+ def imperfect_consonance?(style = :standard_practice)
190
+ consonance(style).imperfect?
191
+ end
192
+
193
+ def dissonance?(style = :standard_practice)
194
+ consonance(style).dissonant?
195
+ end
196
+
181
197
  def step?
182
198
  number <= 2
183
199
  end
@@ -0,0 +1,29 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ # marks the voice if the first note is not the first or fifth scale degree of the key.
5
+ class HeadMusic::Style::Annotations::EndOnPerfectConsonance < HeadMusic::Style::Annotation
6
+ def message
7
+ 'End on the tonic or a perfect consonance above the tonic.'
8
+ end
9
+
10
+ def marks
11
+ if last_note && !ends_on_perfect_consonance?
12
+ HeadMusic::Style::Mark.for(last_note)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def ends_on_perfect_consonance?
19
+ functional_interval.perfect_consonance?(:two_part_harmony)
20
+ end
21
+
22
+ def functional_interval
23
+ HeadMusic::FunctionalInterval.new(composition.key_signature.tonic_spelling, last_note.spelling)
24
+ end
25
+
26
+ def last_note
27
+ notes.last
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ class HeadMusic::Style::Annotations::OneToOne < HeadMusic::Style::Annotation
5
+ def message
6
+ 'Place a note for each note in the other voice.'
7
+ end
8
+
9
+ def marks
10
+ if other_voice && other_voice.notes.length > 0
11
+ HeadMusic::Style::Mark.for_each(
12
+ notes_without_match(voice, other_voice) + notes_without_match(other_voice, voice)
13
+ )
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def notes_without_match(voice1, voice2)
20
+ voice1.notes.reject do |voice1_note|
21
+ voice2.notes.map(&:position).include?(voice1_note.position)
22
+ end
23
+ end
24
+
25
+ def other_voice
26
+ other_voices.detect(&:cantus_firmus?) || other_voices.first
27
+ end
28
+
29
+ def other_voices
30
+ @other_voices ||= voice.composition.voices.select { |part| part != voice }
31
+ end
32
+ end
@@ -1,7 +1,7 @@
1
1
  module HeadMusic::Style::Annotations
2
2
  end
3
3
 
4
- class HeadMusic::Style::Annotations::PermittedIntervals < HeadMusic::Style::Annotation
4
+ class HeadMusic::Style::Annotations::SingableIntervals < HeadMusic::Style::Annotation
5
5
  PERMITTED_ASCENDING = %w[m2 M2 m3 M3 P4 P5 m6 P8]
6
6
  PERMITTED_DESCENDING = %w[m2 M2 m3 M3 P4 P5 P8]
7
7
 
@@ -0,0 +1,29 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ # marks the voice if the first note is not the first or fifth scale degree of the key.
5
+ class HeadMusic::Style::Annotations::StartOnPerfectConsonance < HeadMusic::Style::Annotation
6
+ def message
7
+ 'Start on the tonic or a perfect consonance above the tonic.'
8
+ end
9
+
10
+ def marks
11
+ if first_note && !starts_on_perfect_consonance?
12
+ HeadMusic::Style::Mark.for(first_note)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def starts_on_perfect_consonance?
19
+ functional_interval.perfect_consonance?(:two_part_harmony)
20
+ end
21
+
22
+ def functional_interval
23
+ HeadMusic::FunctionalInterval.new(composition.key_signature.tonic_spelling, first_note.spelling)
24
+ end
25
+
26
+ def first_note
27
+ notes.first
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ class HeadMusic::Style::Annotations::StepUpToFinalNote < HeadMusic::Style::Annotation
5
+ def message
6
+ 'Step up to final note.'
7
+ end
8
+
9
+ def marks
10
+ if !last_melodic_interval.nil?
11
+ fitness = 1
12
+ fitness *= HeadMusic::PENALTY_FACTOR unless step?
13
+ fitness *= HeadMusic::PENALTY_FACTOR unless ascending?
14
+ if fitness < 1
15
+ HeadMusic::Style::Mark.for_all(notes[-2..-1], fitness: fitness)
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def ascending?
23
+ last_melodic_interval && last_melodic_interval.ascending?
24
+ end
25
+
26
+ def step?
27
+ last_melodic_interval && last_melodic_interval.step?
28
+ end
29
+
30
+ def last_melodic_interval
31
+ @last_melodic_interval ||= melodic_intervals.last
32
+ end
33
+ end
@@ -6,7 +6,8 @@ class HeadMusic::Style::Mark
6
6
  end
7
7
 
8
8
  def self.for_all(placements, fitness: nil)
9
- placements = [placements].flatten
9
+ placements = [placements].flatten.compact
10
+ return [] if placements.length == 0
10
11
  start_position = placements.map { |placement| placement.position }.sort.first
11
12
  end_position = placements.map { |placement| placement.next_position }.sort.last
12
13
  new(start_position, end_position, placements: placements, fitness: fitness)
@@ -13,7 +13,7 @@ class HeadMusic::Style::Rulesets::CantusFirmus
13
13
  HeadMusic::Style::Annotations::MostlyConjunct,
14
14
  HeadMusic::Style::Annotations::NoRests,
15
15
  HeadMusic::Style::Annotations::NotesSameLength,
16
- HeadMusic::Style::Annotations::PermittedIntervals,
16
+ HeadMusic::Style::Annotations::SingableIntervals,
17
17
  HeadMusic::Style::Annotations::RecoverLargeLeaps,
18
18
  HeadMusic::Style::Annotations::StartOnTonic,
19
19
  HeadMusic::Style::Annotations::StepDownToFinalNote,
@@ -0,0 +1,12 @@
1
+ module HeadMusic::Style::Rulesets
2
+ end
3
+
4
+ class HeadMusic::Style::Rulesets::FirstSpeciesHarmony
5
+ RULESET = [
6
+ # HeadMusic::Style::Annotations::ConsonantOnStrongBeats,
7
+ ]
8
+
9
+ def self.analyze(voice)
10
+ RULESET.map { |rule| rule.new(voice) }
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module HeadMusic::Style::Rulesets
2
+ end
3
+
4
+ class HeadMusic::Style::Rulesets::FirstSpeciesMelody
5
+ RULESET = [
6
+ HeadMusic::Style::Annotations::OneToOne,
7
+ HeadMusic::Style::Annotations::NotesSameLength,
8
+ HeadMusic::Style::Annotations::SingableIntervals,
9
+ HeadMusic::Style::Annotations::StartOnPerfectConsonance,
10
+ HeadMusic::Style::Annotations::EndOnPerfectConsonance,
11
+ HeadMusic::Style::Annotations::StepUpToFinalNote,
12
+ ]
13
+
14
+ def self.analyze(voice)
15
+ RULESET.map { |rule| rule.new(voice) }
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "0.11.8"
2
+ VERSION = "0.11.9"
3
3
  end
@@ -63,6 +63,10 @@ class HeadMusic::Voice
63
63
  melodic_intervals.select(&:leap?)
64
64
  end
65
65
 
66
+ def cantus_firmus?
67
+ role && role =~ /cantus.?firmus/i
68
+ end
69
+
66
70
  private
67
71
 
68
72
  def insert_into_placements(placement)
data/lib/head_music.rb CHANGED
@@ -43,18 +43,24 @@ require 'head_music/style/annotations/at_least_eight_notes'
43
43
  require 'head_music/style/annotations/consonant_climax'
44
44
  require 'head_music/style/annotations/diatonic'
45
45
  require 'head_music/style/annotations/direction_changes'
46
+ require 'head_music/style/annotations/end_on_perfect_consonance'
46
47
  require 'head_music/style/annotations/end_on_tonic'
47
48
  require 'head_music/style/annotations/limit_range'
48
49
  require 'head_music/style/annotations/mostly_conjunct'
49
50
  require 'head_music/style/annotations/no_rests'
50
51
  require 'head_music/style/annotations/notes_same_length'
51
- require 'head_music/style/annotations/permitted_intervals'
52
+ require 'head_music/style/annotations/one_to_one'
52
53
  require 'head_music/style/annotations/recover_large_leaps'
54
+ require 'head_music/style/annotations/singable_intervals'
55
+ require 'head_music/style/annotations/start_on_perfect_consonance'
53
56
  require 'head_music/style/annotations/start_on_tonic'
54
57
  require 'head_music/style/annotations/step_down_to_final_note'
58
+ require 'head_music/style/annotations/step_up_to_final_note'
55
59
  require 'head_music/style/annotations/up_to_thirteen_notes'
56
60
 
57
61
  require 'head_music/style/rulesets/cantus_firmus'
62
+ require 'head_music/style/rulesets/first_species_melody'
63
+ require 'head_music/style/rulesets/first_species_harmony'
58
64
 
59
65
  require 'head_music/utilities/hash_key'
60
66
  require 'head_music/voice'
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.11.8
4
+ version: 0.11.9
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-05-12 00:00:00.000000000 Z
11
+ date: 2017-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -152,18 +152,24 @@ files:
152
152
  - lib/head_music/style/annotations/consonant_climax.rb
153
153
  - lib/head_music/style/annotations/diatonic.rb
154
154
  - lib/head_music/style/annotations/direction_changes.rb
155
+ - lib/head_music/style/annotations/end_on_perfect_consonance.rb
155
156
  - lib/head_music/style/annotations/end_on_tonic.rb
156
157
  - lib/head_music/style/annotations/limit_range.rb
157
158
  - lib/head_music/style/annotations/mostly_conjunct.rb
158
159
  - lib/head_music/style/annotations/no_rests.rb
159
160
  - lib/head_music/style/annotations/notes_same_length.rb
160
- - lib/head_music/style/annotations/permitted_intervals.rb
161
+ - lib/head_music/style/annotations/one_to_one.rb
161
162
  - lib/head_music/style/annotations/recover_large_leaps.rb
163
+ - lib/head_music/style/annotations/singable_intervals.rb
164
+ - lib/head_music/style/annotations/start_on_perfect_consonance.rb
162
165
  - lib/head_music/style/annotations/start_on_tonic.rb
163
166
  - lib/head_music/style/annotations/step_down_to_final_note.rb
167
+ - lib/head_music/style/annotations/step_up_to_final_note.rb
164
168
  - lib/head_music/style/annotations/up_to_thirteen_notes.rb
165
169
  - lib/head_music/style/mark.rb
166
170
  - lib/head_music/style/rulesets/cantus_firmus.rb
171
+ - lib/head_music/style/rulesets/first_species_harmony.rb
172
+ - lib/head_music/style/rulesets/first_species_melody.rb
167
173
  - lib/head_music/utilities/hash_key.rb
168
174
  - lib/head_music/version.rb
169
175
  - lib/head_music/voice.rb