head_music 0.20.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +22 -0
  3. data/.circleci/setup-rubygems.sh +3 -0
  4. data/.gitignore +1 -0
  5. data/.pairs +8 -0
  6. data/.rubocop.yml +11 -2
  7. data/.ruby-version +1 -0
  8. data/CODE_OF_CONDUCT.md +3 -6
  9. data/Gemfile +2 -2
  10. data/README.md +0 -1
  11. data/TODO.md +18 -0
  12. data/head_music.gemspec +1 -2
  13. data/lib/head_music/chord.rb +9 -2
  14. data/lib/head_music/circle.rb +1 -0
  15. data/lib/head_music/clef.rb +10 -5
  16. data/lib/head_music/consonance.rb +1 -1
  17. data/lib/head_music/content/composition.rb +1 -2
  18. data/lib/head_music/content/note.rb +2 -0
  19. data/lib/head_music/content/placement.rb +1 -1
  20. data/lib/head_music/content/rhythmic_value.rb +1 -0
  21. data/lib/head_music/content/voice.rb +4 -2
  22. data/lib/head_music/functional_interval.rb +43 -17
  23. data/lib/head_music/grand_staff.rb +2 -1
  24. data/lib/head_music/harmonic_interval.rb +1 -0
  25. data/lib/head_music/key_signature.rb +1 -0
  26. data/lib/head_music/letter_name.rb +20 -7
  27. data/lib/head_music/melodic_interval.rb +4 -2
  28. data/lib/head_music/meter.rb +1 -0
  29. data/lib/head_music/octave.rb +2 -0
  30. data/lib/head_music/pitch/enharmonic_equivalence.rb +28 -0
  31. data/lib/head_music/pitch/octave_equivalence.rb +24 -0
  32. data/lib/head_music/pitch.rb +41 -57
  33. data/lib/head_music/pitch_class.rb +5 -0
  34. data/lib/head_music/rhythmic_unit.rb +1 -0
  35. data/lib/head_music/scale.rb +7 -3
  36. data/lib/head_music/scale_degree.rb +2 -1
  37. data/lib/head_music/sign.rb +2 -1
  38. data/lib/head_music/spelling.rb +6 -2
  39. data/lib/head_music/style/analysis.rb +1 -0
  40. data/lib/head_music/style/annotation.rb +2 -2
  41. data/lib/head_music/style/guidelines/at_least_eight_notes.rb +1 -0
  42. data/lib/head_music/style/guidelines/direction_changes.rb +2 -0
  43. data/lib/head_music/style/guidelines/limit_octave_leaps.rb +1 -0
  44. data/lib/head_music/style/guidelines/mostly_conjunct.rb +1 -0
  45. data/lib/head_music/style/guidelines/notes_same_length.rb +2 -1
  46. data/lib/head_music/style/guidelines/one_to_one.rb +1 -0
  47. data/lib/head_music/style/guidelines/prefer_contrary_motion.rb +2 -0
  48. data/lib/head_music/style/guidelines/prefer_imperfect.rb +1 -0
  49. data/lib/head_music/style/guidelines/single_large_leaps.rb +1 -0
  50. data/lib/head_music/style/guidelines/start_on_perfect_consonance.rb +1 -0
  51. data/lib/head_music/style/guidelines/step_down_to_final_note.rb +1 -0
  52. data/lib/head_music/style/guidelines/step_out_of_unison.rb +1 -1
  53. data/lib/head_music/style/guidelines/step_up_to_final_note.rb +1 -0
  54. data/lib/head_music/style/mark.rb +3 -2
  55. data/lib/head_music/tuning.rb +6 -1
  56. data/lib/head_music/version.rb +1 -1
  57. data/lib/head_music.rb +2 -0
  58. metadata +10 -4
  59. data/circle.yml +0 -9
@@ -7,10 +7,14 @@ class HeadMusic::Pitch
7
7
  attr_reader :spelling
8
8
  attr_reader :octave
9
9
 
10
- delegate :letter_name, :letter_name_cycle, to: :spelling
10
+ delegate :letter_name, to: :spelling
11
+ delegate :series_ascending, :series_descending, to: :letter_name, prefix: true
11
12
  delegate :sign, :sharp?, :flat?, to: :spelling
12
13
  delegate :pitch_class, to: :spelling
14
+ delegate :number, to: :pitch_class, prefix: true
15
+ delegate :pitch_class_number, to: :natural, prefix: true
13
16
  delegate :semitones, to: :sign, prefix: true, allow_nil: true
17
+ delegate :steps_to, to: :letter_name, prefix: true
14
18
 
15
19
  delegate :smallest_interval_to, to: :pitch_class
16
20
 
@@ -31,16 +35,19 @@ class HeadMusic::Pitch
31
35
 
32
36
  def self.from_pitch_class(pitch_class)
33
37
  return nil unless pitch_class.is_a?(HeadMusic::PitchClass)
38
+
34
39
  fetch_or_create(pitch_class.sharp_spelling)
35
40
  end
36
41
 
37
42
  def self.from_name(name)
38
43
  return nil unless name == name.to_s
44
+
39
45
  fetch_or_create(HeadMusic::Spelling.get(name), HeadMusic::Octave.get(name).to_i)
40
46
  end
41
47
 
42
48
  def self.from_number(number)
43
49
  return nil unless number == number.to_i
50
+
44
51
  spelling = HeadMusic::Spelling.from_number(number)
45
52
  octave = (number.to_i / 12) - 1
46
53
  fetch_or_create(spelling, octave)
@@ -64,6 +71,7 @@ class HeadMusic::Pitch
64
71
  def self.fetch_or_create(spelling, octave = nil)
65
72
  octave ||= HeadMusic::Octave::DEFAULT
66
73
  return unless spelling && (-1..9).cover?(octave)
74
+
67
75
  @pitches ||= {}
68
76
  hash_key = [spelling, octave].join
69
77
  @pitches[hash_key] ||= new(spelling, octave)
@@ -94,15 +102,24 @@ class HeadMusic::Pitch
94
102
  end
95
103
 
96
104
  def natural
97
- HeadMusic::Pitch.get(to_s.gsub(/[#b]/, ''))
105
+ HeadMusic::Pitch.get(to_s.gsub(HeadMusic::Sign.matcher, ''))
98
106
  end
99
107
 
100
108
  def +(other)
101
- HeadMusic::Pitch.get(to_i + other.to_i)
109
+ if other.is_a?(HeadMusic::FunctionalInterval)
110
+ # return a pitch
111
+ other.above(self)
112
+ else
113
+ # assume value represents an interval in semitones and return another pitch
114
+ HeadMusic::Pitch.get(to_i + other.to_i)
115
+ end
102
116
  end
103
117
 
104
118
  def -(other)
105
- if other.is_a?(HeadMusic::Pitch)
119
+ if other.is_a?(HeadMusic::FunctionalInterval)
120
+ # return a pitch
121
+ other.below(self)
122
+ elsif other.is_a?(HeadMusic::Pitch)
106
123
  # return an interval
107
124
  HeadMusic::Interval.get(to_i - other.to_i)
108
125
  else
@@ -132,16 +149,33 @@ class HeadMusic::Pitch
132
149
  tuning.frequency_for(self)
133
150
  end
134
151
 
152
+ def steps_to(other)
153
+ other = HeadMusic::Pitch.get(other)
154
+ letter_name_steps_to(other) + 7 * octave_changes_to(other)
155
+ end
156
+
135
157
  private_class_method :new
136
158
 
137
159
  private
138
160
 
161
+ def octave_changes_to(other)
162
+ other.octave - octave - octave_adjustment_to(other)
163
+ end
164
+
165
+ def octave_adjustment_to(other)
166
+ (pitch_class_above?(other) ? 1 : 0)
167
+ end
168
+
169
+ def pitch_class_above?(other)
170
+ natural_pitch_class_number > other.natural_pitch_class_number
171
+ end
172
+
139
173
  def enharmonic_equivalence
140
- @enharmonic_equivalence ||= EnharmonicEquivalence.get(self)
174
+ @enharmonic_equivalence ||= HeadMusic::Pitch::EnharmonicEquivalence.get(self)
141
175
  end
142
176
 
143
177
  def octave_equivalence
144
- @octave_equivalence ||= OctaveEquivalence.get(self)
178
+ @octave_equivalence ||= HeadMusic::Pitch::OctaveEquivalence.get(self)
145
179
  end
146
180
 
147
181
  def tuning
@@ -168,56 +202,6 @@ class HeadMusic::Pitch
168
202
 
169
203
  def target_letter_name(num_steps)
170
204
  @target_letter_name ||= {}
171
- @target_letter_name[num_steps] ||= letter_name.steps(num_steps)
172
- end
173
-
174
- # An enharmonic equivalent pitch is the same frequency spelled differently, such as D# and Eb.
175
- class EnharmonicEquivalence
176
- def self.get(pitch)
177
- pitch = HeadMusic::Pitch.get(pitch)
178
- @enharmonic_equivalences ||= {}
179
- @enharmonic_equivalences[pitch.to_s] ||= new(pitch)
180
- end
181
-
182
- attr_reader :pitch
183
-
184
- delegate :pitch_class, to: :pitch
185
-
186
- def initialize(pitch)
187
- @pitch = HeadMusic::Pitch.get(pitch)
188
- end
189
-
190
- def enharmonic_equivalent?(other)
191
- other = HeadMusic::Pitch.get(other)
192
- pitch.midi_note_number == other.midi_note_number && pitch.spelling != other.spelling
193
- end
194
-
195
- alias enharmonic? enharmonic_equivalent?
196
- alias equivalent? enharmonic_equivalent?
197
-
198
- private_class_method :new
199
- end
200
-
201
- # Octave equivalence is the functional equivalence of pitches with the same spelling separated by one or more octaves.
202
- class OctaveEquivalence
203
- def self.get(pitch)
204
- @octave_equivalences ||= {}
205
- @octave_equivalences[pitch.to_s] ||= new(pitch)
206
- end
207
-
208
- attr_reader :pitch
209
-
210
- def initialize(pitch)
211
- @pitch = pitch
212
- end
213
-
214
- def octave_equivalent?(other)
215
- other = HeadMusic::Pitch.get(other)
216
- pitch.spelling == other.spelling && pitch.octave != other.octave
217
- end
218
-
219
- alias equivalent? octave_equivalent?
220
-
221
- private_class_method :new
205
+ @target_letter_name[num_steps] ||= letter_name.steps_up(num_steps)
222
206
  end
223
207
  end
@@ -7,6 +7,7 @@ class HeadMusic::PitchClass
7
7
 
8
8
  SHARP_SPELLINGS = %w[C C# D D# E F F# G G# A A# B].freeze
9
9
  FLAT_SPELLINGS = %w[C Db D Eb E F Gb G Ab A Bb B].freeze
10
+ INTEGER_NOTATION = %w[0 1 2 3 4 5 6 7 8 9 t e].freeze
10
11
 
11
12
  def self.get(identifier)
12
13
  @pitch_classes ||= {}
@@ -30,6 +31,10 @@ class HeadMusic::PitchClass
30
31
  number
31
32
  end
32
33
 
34
+ def to_integer_notation
35
+ INTEGER_NOTATION[number]
36
+ end
37
+
33
38
  def sharp_spelling
34
39
  SHARP_SPELLINGS[number]
35
40
  end
@@ -45,6 +45,7 @@ class HeadMusic::RhythmicUnit
45
45
  return :longa if relative_value == 4
46
46
  return :breve if relative_value == 2
47
47
  return :open if [0.5, 1].include? relative_value
48
+
48
49
  :closed
49
50
  end
50
51
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # A scale contains ordered pitches starting at a tonal center.
4
4
  class HeadMusic::Scale
5
- SCALE_REGEX = /^[A-G][#b]?\s+\w+$/
5
+ SCALE_REGEX = /^[A-G][#b]?\s+\w+$/.freeze
6
6
 
7
7
  def self.get(root_pitch, scale_type = nil)
8
8
  root_pitch, scale_type = root_pitch.split(/\s+/) if root_pitch.is_a?(String) && scale_type =~ SCALE_REGEX
@@ -15,7 +15,7 @@ class HeadMusic::Scale
15
15
  @scales[hash_key] ||= new(root_pitch, scale_type)
16
16
  end
17
17
 
18
- delegate :letter_name_cycle, to: :root_pitch
18
+ delegate :letter_name_series_ascending, :letter_name_series_descending, to: :root_pitch
19
19
 
20
20
  attr_reader :root_pitch, :scale_type
21
21
 
@@ -53,6 +53,7 @@ class HeadMusic::Scale
53
53
  pitches = [root_pitch]
54
54
  %i[ascending descending].each do |single_direction|
55
55
  next unless [single_direction, :both].include?(direction)
56
+
56
57
  (1..octaves).each do
57
58
  pitches += octave_scale_pitches(single_direction, semitones_from_root)
58
59
  semitones_from_root += 12 * direction_sign(single_direction)
@@ -101,16 +102,19 @@ class HeadMusic::Scale
101
102
 
102
103
  def diatonic_letter_for_step(direction, step)
103
104
  return unless scale_type.diatonic?
104
- direction == :ascending ? letter_name_cycle[step % 7] : letter_name_cycle[-step % 7]
105
+
106
+ direction == :ascending ? letter_name_series_ascending[step % 7] : letter_name_series_descending[step % 7]
105
107
  end
106
108
 
107
109
  def child_scale_letter_for_step(semitones_from_root)
108
110
  return unless scale_type.parent
111
+
109
112
  parent_scale_pitch_for(semitones_from_root).letter_name
110
113
  end
111
114
 
112
115
  def flat_letter_for_step(semitones_from_root)
113
116
  return unless root_pitch.flat?
117
+
114
118
  HeadMusic::PitchClass::FLAT_SPELLINGS[pitch_class_number(semitones_from_root)]
115
119
  end
116
120
 
@@ -17,7 +17,7 @@ class HeadMusic::ScaleDegree
17
17
  end
18
18
 
19
19
  def degree
20
- scale.letter_name_cycle.index(spelling.letter_name.to_s) + 1
20
+ scale.letter_name_series_ascending.index(spelling.letter_name.to_s) + 1
21
21
  end
22
22
 
23
23
  def sign
@@ -41,6 +41,7 @@ class HeadMusic::ScaleDegree
41
41
 
42
42
  def name_for_degree
43
43
  return unless scale_type.diatonic?
44
+
44
45
  NAME_FOR_DIATONIC_DEGREE[degree] ||
45
46
  (scale_type.intervals.last == 1 || sign == '#' ? 'leading tone' : 'subtonic')
46
47
  end
@@ -17,7 +17,7 @@ class HeadMusic::Sign
17
17
  end
18
18
 
19
19
  def self.symbols
20
- @sign_symbols ||= all.map { |sign| [sign.ascii, sign.unicode] }.flatten.reject { |s| s.nil? || s.empty? }
20
+ @symbols ||= all.map { |sign| [sign.ascii, sign.unicode] }.flatten.reject { |s| s.nil? || s.empty? }
21
21
  end
22
22
 
23
23
  def self.matcher
@@ -30,6 +30,7 @@ class HeadMusic::Sign
30
30
 
31
31
  def self.get(identifier)
32
32
  return identifier if identifier.is_a?(HeadMusic::Sign)
33
+
33
34
  all.detect do |sign|
34
35
  sign.representions.include?(identifier)
35
36
  end
@@ -4,7 +4,7 @@
4
4
  # Composite of a LetterName and an optional Sign.
5
5
  # Does not include the octave. See Pitch for that.
6
6
  class HeadMusic::Spelling
7
- MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(\-?\d+)?\s*$/i
7
+ MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(\-?\d+)?\s*$/i.freeze
8
8
 
9
9
  attr_reader :pitch_class
10
10
  attr_reader :letter_name
@@ -12,11 +12,12 @@ class HeadMusic::Spelling
12
12
 
13
13
  delegate :number, to: :pitch_class, prefix: true
14
14
  delegate :to_i, to: :pitch_class_number
15
- delegate :cycle, to: :letter_name, prefix: true
15
+ delegate :series_ascending, :series_descending, to: :letter_name, prefix: true
16
16
  delegate :enharmonic?, to: :enharmonic_equivalence
17
17
 
18
18
  def self.get(identifier)
19
19
  return identifier if identifier.is_a?(HeadMusic::Spelling)
20
+
20
21
  from_name(identifier) || from_number(identifier)
21
22
  end
22
23
 
@@ -26,15 +27,18 @@ class HeadMusic::Spelling
26
27
 
27
28
  def self.from_name(name)
28
29
  return nil unless matching_string(name)
30
+
29
31
  letter_name, sign_string, _octave = matching_string(name).captures
30
32
  letter_name = HeadMusic::LetterName.get(letter_name)
31
33
  return nil unless letter_name
34
+
32
35
  sign = HeadMusic::Sign.get(sign_string)
33
36
  fetch_or_create(letter_name, sign)
34
37
  end
35
38
 
36
39
  def self.from_number(number)
37
40
  return nil unless number == number.to_i
41
+
38
42
  pitch_class_number = number.to_i % 12
39
43
  letter_name = HeadMusic::LetterName.from_pitch_class(pitch_class_number)
40
44
  from_number_and_letter(number, letter_name)
@@ -23,6 +23,7 @@ class HeadMusic::Style::Analysis
23
23
 
24
24
  def fitness
25
25
  return 1.0 if annotations.empty?
26
+
26
27
  @fitness ||= fitness_scores.inject(:+).to_f / fitness_scores.length
27
28
  end
28
29
 
@@ -41,11 +41,11 @@ class HeadMusic::Style::Annotation
41
41
  end
42
42
 
43
43
  def start_position
44
- [marks].flatten.compact.map(&:start_position).sort.first
44
+ [marks].flatten.compact.map(&:start_position).min
45
45
  end
46
46
 
47
47
  def end_position
48
- [marks].flatten.compact.map(&:end_position).sort.last
48
+ [marks].flatten.compact.map(&:end_position).max
49
49
  end
50
50
 
51
51
  def marks
@@ -25,6 +25,7 @@ class HeadMusic::Style::Guidelines::AtLeastEightNotes < HeadMusic::Style::Annota
25
25
 
26
26
  def deficiency_mark
27
27
  return unless notes.length < MINIMUM_NOTES
28
+
28
29
  HeadMusic::Style::Mark.for_all(placements, fitness: notes.length.to_f / MINIMUM_NOTES)
29
30
  end
30
31
  end
@@ -7,6 +7,7 @@ module HeadMusic::Style::Guidelines; end
7
7
  class HeadMusic::Style::Guidelines::DirectionChanges < HeadMusic::Style::Annotation
8
8
  def marks
9
9
  return unless overage.positive?
10
+
10
11
  penalty_exponent = overage**0.5
11
12
  HeadMusic::Style::Mark.for_all(notes, fitness: HeadMusic::PENALTY_FACTOR**penalty_exponent)
12
13
  end
@@ -15,6 +16,7 @@ class HeadMusic::Style::Guidelines::DirectionChanges < HeadMusic::Style::Annotat
15
16
 
16
17
  def overage
17
18
  return 0 if notes.length < 2
19
+
18
20
  [notes_per_direction - self.class.maximum_notes_per_direction, 0].max
19
21
  end
20
22
 
@@ -9,6 +9,7 @@ class HeadMusic::Style::Guidelines::LimitOctaveLeaps < HeadMusic::Style::Annotat
9
9
 
10
10
  def marks
11
11
  return if octave_leaps.length <= 1
12
+
12
13
  octave_leaps.map do |leap|
13
14
  HeadMusic::Style::Mark.for_all(leap.notes)
14
15
  end
@@ -25,6 +25,7 @@ class HeadMusic::Style::Guidelines::MostlyConjunct < HeadMusic::Style::Annotatio
25
25
 
26
26
  def conjunct_ratio
27
27
  return 1 if melodic_intervals.empty?
28
+
28
29
  melodic_intervals.count(&:step?).to_f / melodic_intervals.length
29
30
  end
30
31
  end
@@ -53,8 +53,9 @@ class HeadMusic::Style::Guidelines::NotesSameLength < HeadMusic::Style::Annotati
53
53
 
54
54
  def most_common_rhythmic_values
55
55
  return [] if notes.empty?
56
+
56
57
  occurrences = occurrences_by_rhythmic_value
57
- highest_count = occurrences.values.sort.last
58
+ highest_count = occurrences.values.max
58
59
  occurrences.select { |_rhythmic_value, count| count == highest_count }.keys
59
60
  end
60
61
 
@@ -10,6 +10,7 @@ class HeadMusic::Style::Guidelines::OneToOne < HeadMusic::Style::Annotation
10
10
  def marks
11
11
  return unless cantus_firmus&.notes
12
12
  return if cantus_firmus.notes.empty?
13
+
13
14
  HeadMusic::Style::Mark.for_each(
14
15
  notes_without_match(voice, cantus_firmus) + notes_without_match(cantus_firmus, voice)
15
16
  )
@@ -10,6 +10,7 @@ class HeadMusic::Style::Guidelines::PreferContraryMotion < HeadMusic::Style::Ann
10
10
  def marks
11
11
  return nil if notes.length < 2
12
12
  return nil if direct_motion_ratio <= 0.5
13
+
13
14
  direct_motions.map { |motion| HeadMusic::Style::Mark.for_all(motion.notes) }
14
15
  end
15
16
 
@@ -21,6 +22,7 @@ class HeadMusic::Style::Guidelines::PreferContraryMotion < HeadMusic::Style::Ann
21
22
 
22
23
  def direct_motion_ratio
23
24
  return 0 if motions.empty?
25
+
24
26
  direct_motions.count / motions.count.to_f
25
27
  end
26
28
  end
@@ -15,6 +15,7 @@ class HeadMusic::Style::Guidelines::PreferImperfect < HeadMusic::Style::Annotati
15
15
 
16
16
  def ratio_of_perfect_intervals
17
17
  return 0 if downbeat_harmonic_intervals_in_middle.nil?
18
+
18
19
  perfect_intervals.length.to_f / downbeat_harmonic_intervals.length
19
20
  end
20
21
 
@@ -14,6 +14,7 @@ class HeadMusic::Style::Guidelines::SingleLargeLeaps < HeadMusic::Style::Guideli
14
14
  return false if spelling_consonant_triad?(first_interval, second_interval, third_interval)
15
15
  return false if second_interval.step?
16
16
  return false if second_interval.repetition?
17
+
17
18
  !direction_changed?(first_interval, second_interval) && second_interval.leap?
18
19
  end
19
20
  end
@@ -9,6 +9,7 @@ class HeadMusic::Style::Guidelines::StartOnPerfectConsonance < HeadMusic::Style:
9
9
 
10
10
  def marks
11
11
  return unless first_note && ((bass_voice? && !starts_on_tonic?) || !starts_on_perfect_consonance?)
12
+
12
13
  HeadMusic::Style::Mark.for(first_note)
13
14
  end
14
15
 
@@ -9,6 +9,7 @@ class HeadMusic::Style::Guidelines::StepDownToFinalNote < HeadMusic::Style::Anno
9
9
 
10
10
  def marks
11
11
  return if last_melodic_interval.nil?
12
+
12
13
  fitness = 1
13
14
  fitness *= HeadMusic::PENALTY_FACTOR unless step?
14
15
  fitness *= HeadMusic::PENALTY_FACTOR unless descending?
@@ -29,6 +29,6 @@ class HeadMusic::Style::Guidelines::StepOutOfUnison < HeadMusic::Style::Annotati
29
29
  end
30
30
 
31
31
  def perfect_unisons
32
- @unisons ||= harmonic_intervals.select(&:perfect_consonance?).select(&:unison?)
32
+ @perfect_unisons ||= harmonic_intervals.select(&:perfect_consonance?).select(&:unison?)
33
33
  end
34
34
  end
@@ -9,6 +9,7 @@ class HeadMusic::Style::Guidelines::StepUpToFinalNote < HeadMusic::Style::Annota
9
9
 
10
10
  def marks
11
11
  return if last_melodic_interval.nil?
12
+
12
13
  fitness = 1
13
14
  fitness *= HeadMusic::PENALTY_FACTOR unless step?
14
15
  fitness *= HeadMusic::PENALTY_FACTOR unless ascending?
@@ -12,8 +12,9 @@ class HeadMusic::Style::Mark
12
12
  def self.for_all(placements, fitness: nil)
13
13
  placements = [placements].flatten.compact
14
14
  return [] if placements.empty?
15
- start_position = placements.map(&:position).sort.first
16
- end_position = placements.map(&:next_position).sort.last
15
+
16
+ start_position = placements.map(&:position).min
17
+ end_position = placements.map(&:next_position).max
17
18
  new(start_position, end_position, placements: placements, fitness: fitness)
18
19
  end
19
20
 
@@ -3,8 +3,8 @@
3
3
  # A tuning has a reference pitch and frequency and provides frequencies for all pitches
4
4
  # The base class assumes equal temperament tuning. By default, A4 = 440.0 Hz
5
5
  class HeadMusic::Tuning
6
- REFERENCE_FREQUENCY = 440.0
7
6
  REFERENCE_PITCH_NAME = 'A4'
7
+ REFERENCE_FREQUENCY = 440.0
8
8
 
9
9
  attr_reader :reference_pitch, :reference_frequency
10
10
 
@@ -18,3 +18,8 @@ class HeadMusic::Tuning
18
18
  reference_frequency * (2**(1.0 / 12))**(pitch - reference_pitch).semitones
19
19
  end
20
20
  end
21
+
22
+ # TODO: other tunings
23
+ # Create website that hosts videos on theory and history, handy charts, etc.
24
+ # one of those charts can be a frequency table in various tunings
25
+ # maybe show pythagorean commas and such. or cents sharp or flat relative to either equal temperment or just intonation
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HeadMusic
4
- VERSION = '0.20.0'
4
+ VERSION = '0.22.0'
5
5
  end
data/lib/head_music.rb CHANGED
@@ -36,6 +36,8 @@ require 'head_music/meter'
36
36
  require 'head_music/motion'
37
37
  require 'head_music/octave'
38
38
  require 'head_music/pitch'
39
+ require 'head_music/pitch/enharmonic_equivalence'
40
+ require 'head_music/pitch/octave_equivalence'
39
41
  require 'head_music/pitch_class'
40
42
  require 'head_music/quality'
41
43
  require 'head_music/rhythm'
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.20.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-16 00:00:00.000000000 Z
11
+ date: 2018-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -102,18 +102,22 @@ executables: []
102
102
  extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
+ - ".circleci/config.yml"
106
+ - ".circleci/setup-rubygems.sh"
105
107
  - ".gitignore"
108
+ - ".pairs"
106
109
  - ".rspec"
107
110
  - ".rubocop.yml"
111
+ - ".ruby-version"
108
112
  - ".travis.yml"
109
113
  - CODE_OF_CONDUCT.md
110
114
  - Gemfile
111
115
  - LICENSE.txt
112
116
  - README.md
113
117
  - Rakefile
118
+ - TODO.md
114
119
  - bin/console
115
120
  - bin/setup
116
- - circle.yml
117
121
  - head_music.gemspec
118
122
  - lib/head_music.rb
119
123
  - lib/head_music/chord.rb
@@ -140,6 +144,8 @@ files:
140
144
  - lib/head_music/named_rudiment.rb
141
145
  - lib/head_music/octave.rb
142
146
  - lib/head_music/pitch.rb
147
+ - lib/head_music/pitch/enharmonic_equivalence.rb
148
+ - lib/head_music/pitch/octave_equivalence.rb
143
149
  - lib/head_music/pitch_class.rb
144
150
  - lib/head_music/quality.rb
145
151
  - lib/head_music/rhythm.rb
@@ -213,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
219
  version: '0'
214
220
  requirements: []
215
221
  rubyforge_project:
216
- rubygems_version: 2.6.8
222
+ rubygems_version: 2.7.6
217
223
  signing_key:
218
224
  specification_version: 4
219
225
  summary: The rudiments of western music theory.
data/circle.yml DELETED
@@ -1,9 +0,0 @@
1
- ## Customize the test machine
2
- machine:
3
- timezone:
4
- America/Los_Angeles # Set the timezone
5
-
6
- # Version of ruby to use
7
- ruby:
8
- version:
9
- 2.4.0