head_music 0.20.0 → 0.22.0
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 +5 -5
- data/.circleci/config.yml +22 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.gitignore +1 -0
- data/.pairs +8 -0
- data/.rubocop.yml +11 -2
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +3 -6
- data/Gemfile +2 -2
- data/README.md +0 -1
- data/TODO.md +18 -0
- data/head_music.gemspec +1 -2
- data/lib/head_music/chord.rb +9 -2
- data/lib/head_music/circle.rb +1 -0
- data/lib/head_music/clef.rb +10 -5
- data/lib/head_music/consonance.rb +1 -1
- data/lib/head_music/content/composition.rb +1 -2
- data/lib/head_music/content/note.rb +2 -0
- data/lib/head_music/content/placement.rb +1 -1
- data/lib/head_music/content/rhythmic_value.rb +1 -0
- data/lib/head_music/content/voice.rb +4 -2
- data/lib/head_music/functional_interval.rb +43 -17
- data/lib/head_music/grand_staff.rb +2 -1
- data/lib/head_music/harmonic_interval.rb +1 -0
- data/lib/head_music/key_signature.rb +1 -0
- data/lib/head_music/letter_name.rb +20 -7
- data/lib/head_music/melodic_interval.rb +4 -2
- data/lib/head_music/meter.rb +1 -0
- data/lib/head_music/octave.rb +2 -0
- data/lib/head_music/pitch/enharmonic_equivalence.rb +28 -0
- data/lib/head_music/pitch/octave_equivalence.rb +24 -0
- data/lib/head_music/pitch.rb +41 -57
- data/lib/head_music/pitch_class.rb +5 -0
- data/lib/head_music/rhythmic_unit.rb +1 -0
- data/lib/head_music/scale.rb +7 -3
- data/lib/head_music/scale_degree.rb +2 -1
- data/lib/head_music/sign.rb +2 -1
- data/lib/head_music/spelling.rb +6 -2
- data/lib/head_music/style/analysis.rb +1 -0
- data/lib/head_music/style/annotation.rb +2 -2
- data/lib/head_music/style/guidelines/at_least_eight_notes.rb +1 -0
- data/lib/head_music/style/guidelines/direction_changes.rb +2 -0
- data/lib/head_music/style/guidelines/limit_octave_leaps.rb +1 -0
- data/lib/head_music/style/guidelines/mostly_conjunct.rb +1 -0
- data/lib/head_music/style/guidelines/notes_same_length.rb +2 -1
- data/lib/head_music/style/guidelines/one_to_one.rb +1 -0
- data/lib/head_music/style/guidelines/prefer_contrary_motion.rb +2 -0
- data/lib/head_music/style/guidelines/prefer_imperfect.rb +1 -0
- data/lib/head_music/style/guidelines/single_large_leaps.rb +1 -0
- data/lib/head_music/style/guidelines/start_on_perfect_consonance.rb +1 -0
- data/lib/head_music/style/guidelines/step_down_to_final_note.rb +1 -0
- data/lib/head_music/style/guidelines/step_out_of_unison.rb +1 -1
- data/lib/head_music/style/guidelines/step_up_to_final_note.rb +1 -0
- data/lib/head_music/style/mark.rb +3 -2
- data/lib/head_music/tuning.rb +6 -1
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +2 -0
- metadata +10 -4
- data/circle.yml +0 -9
data/lib/head_music/pitch.rb
CHANGED
@@ -7,10 +7,14 @@ class HeadMusic::Pitch
|
|
7
7
|
attr_reader :spelling
|
8
8
|
attr_reader :octave
|
9
9
|
|
10
|
-
delegate :letter_name,
|
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(
|
105
|
+
HeadMusic::Pitch.get(to_s.gsub(HeadMusic::Sign.matcher, ''))
|
98
106
|
end
|
99
107
|
|
100
108
|
def +(other)
|
101
|
-
|
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::
|
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.
|
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
|
data/lib/head_music/scale.rb
CHANGED
@@ -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 :
|
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
|
-
|
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.
|
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
|
data/lib/head_music/sign.rb
CHANGED
@@ -17,7 +17,7 @@ class HeadMusic::Sign
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.symbols
|
20
|
-
@
|
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
|
data/lib/head_music/spelling.rb
CHANGED
@@ -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 :
|
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)
|
@@ -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).
|
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).
|
48
|
+
[marks].flatten.compact.map(&:end_position).max
|
49
49
|
end
|
50
50
|
|
51
51
|
def marks
|
@@ -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
|
|
@@ -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.
|
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
|
@@ -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::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
|
-
@
|
32
|
+
@perfect_unisons ||= harmonic_intervals.select(&:perfect_consonance?).select(&:unison?)
|
33
33
|
end
|
34
34
|
end
|
@@ -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
|
-
|
16
|
-
|
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
|
|
data/lib/head_music/tuning.rb
CHANGED
@@ -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
|
data/lib/head_music/version.rb
CHANGED
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.
|
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-
|
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
|
222
|
+
rubygems_version: 2.7.6
|
217
223
|
signing_key:
|
218
224
|
specification_version: 4
|
219
225
|
summary: The rudiments of western music theory.
|