head_music 11.8.0 → 12.0.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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/head_music/analysis/dyad.rb +55 -106
- data/lib/head_music/analysis/pitch_collection.rb +15 -4
- data/lib/head_music/content/voice.rb +3 -2
- data/lib/head_music/instruments/instrument.rb +18 -29
- data/lib/head_music/rudiment/pitch.rb +9 -6
- data/lib/head_music/rudiment/tuning/just_intonation.rb +1 -20
- data/lib/head_music/rudiment/tuning/meantone.rb +1 -20
- data/lib/head_music/rudiment/tuning/pythagorean.rb +1 -20
- data/lib/head_music/rudiment/tuning.rb +13 -2
- data/lib/head_music/style/guidelines/directional_step_to_final_note.rb +28 -0
- data/lib/head_music/style/guidelines/four_to_one.rb +4 -49
- data/lib/head_music/style/guidelines/note_count_per_bar.rb +54 -0
- data/lib/head_music/style/guidelines/singable_intervals.rb +2 -2
- data/lib/head_music/style/guidelines/step_down_to_final_note.rb +2 -19
- data/lib/head_music/style/guidelines/step_up_to_final_note.rb +2 -19
- data/lib/head_music/style/guidelines/third_species_dissonance_treatment.rb +10 -83
- data/lib/head_music/style/guidelines/three_to_one.rb +57 -0
- data/lib/head_music/style/guidelines/triple_meter_dissonance_treatment.rb +26 -0
- data/lib/head_music/style/guidelines/two_to_one.rb +5 -49
- data/lib/head_music/style/guidelines/weak_beat_dissonance_treatment.rb +11 -3
- data/lib/head_music/style/guides/first_species_harmony.rb +1 -8
- data/lib/head_music/style/guides/first_species_melody.rb +1 -5
- data/lib/head_music/style/guides/fux_cantus_firmus.rb +1 -5
- data/lib/head_music/style/guides/modern_cantus_firmus.rb +1 -5
- data/lib/head_music/style/guides/second_species_harmony.rb +1 -8
- data/lib/head_music/style/guides/second_species_melody.rb +1 -5
- data/lib/head_music/style/guides/species_harmony.rb +9 -0
- data/lib/head_music/style/guides/species_melody.rb +9 -0
- data/lib/head_music/style/guides/third_species_harmony.rb +1 -8
- data/lib/head_music/style/guides/third_species_melody.rb +1 -5
- data/lib/head_music/style/guides/triple_meter_harmony.rb +15 -0
- data/lib/head_music/style/guides/triple_meter_melody.rb +22 -0
- data/lib/head_music/time/event_map_support.rb +29 -0
- data/lib/head_music/time/meter_map.rb +2 -92
- data/lib/head_music/time/tempo_map.rb +14 -96
- data/lib/head_music/time.rb +1 -0
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +9 -1
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2be29f6b7e43ea3463fa98fba14bbdeea632fe94472df74b5c286b0952b322e
|
|
4
|
+
data.tar.gz: 5ec5d730b43357a8aa2283e3ea73d23596b21a983d5b9bc93e80bfd910eb8a92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17d1a2575d8b32a6c5ce22c4e522ca7c84bbcc6ffc66d22c3cd76d7af49d2c82628b096eeeee40e4d38def2eaeed5123a4281131c35b5eb432da680a08631eab
|
|
7
|
+
data.tar.gz: ccc33122bdadc8deccfd4258abebf7723fd12ec15dddeacc164529c4477dd69c0d5a575fe9c0a7251199c588e374896c1288a6991e883eb60893aa18443598f6
|
data/Gemfile.lock
CHANGED
|
@@ -68,95 +68,59 @@ class HeadMusic::Analysis::Dyad
|
|
|
68
68
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
intervals.each do |interval_name|
|
|
93
|
-
interval = HeadMusic::Analysis::DiatonicInterval.get(interval_name)
|
|
94
|
-
next_pitch = interval.above(root_pitch)
|
|
95
|
-
trichord_pitches << next_pitch
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
pitch_collection = HeadMusic::Analysis::PitchCollection.new(trichord_pitches)
|
|
99
|
-
trichord_pitch_classes = pitch_collection.pitch_classes
|
|
71
|
+
TRICHORD_INTERVALS = [
|
|
72
|
+
%w[M3 P5], # major triad
|
|
73
|
+
%w[m3 P5], # minor triad
|
|
74
|
+
%w[m3 d5], # diminished triad
|
|
75
|
+
%w[M3 A5], # augmented triad
|
|
76
|
+
%w[P4 P5], # sus4 (not a triad)
|
|
77
|
+
%w[M2 P5] # sus2 (not a triad)
|
|
78
|
+
].freeze
|
|
79
|
+
|
|
80
|
+
SEVENTH_CHORD_INTERVALS = [
|
|
81
|
+
%w[M3 P5 M7], # major seventh
|
|
82
|
+
%w[M3 P5 m7], # dominant seventh (major-minor)
|
|
83
|
+
%w[m3 P5 m7], # minor seventh
|
|
84
|
+
%w[m3 P5 M7], # minor-major seventh
|
|
85
|
+
%w[m3 d5 m7], # half-diminished seventh
|
|
86
|
+
%w[m3 d5 d7], # diminished seventh
|
|
87
|
+
%w[M2 M3 P5 m7], # dominant ninth
|
|
88
|
+
%w[m2 M3 P5 m7], # dominant minor ninth
|
|
89
|
+
%w[M2 m3 P5 m7], # minor ninth
|
|
90
|
+
%w[M2 M3 P5 M7] # major ninth
|
|
91
|
+
].freeze
|
|
100
92
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
trichords << pitch_collection
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
trichords.uniq { |t| t.pitch_classes.sort.map(&:to_i) }
|
|
93
|
+
def generate_possible_trichords
|
|
94
|
+
generate_possible_chords(TRICHORD_INTERVALS)
|
|
109
95
|
end
|
|
110
96
|
|
|
111
97
|
def generate_possible_seventh_chords
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
generate_possible_chords(SEVENTH_CHORD_INTERVALS)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def generate_possible_chords(interval_sets)
|
|
102
|
+
dyad_pitch_classes = [lower_pitch.pitch_class, upper_pitch.pitch_class]
|
|
103
|
+
chords = []
|
|
114
104
|
|
|
115
105
|
HeadMusic::Rudiment::Spelling::CHROMATIC_SPELLINGS.each do |root_spelling|
|
|
116
106
|
root_pitch = HeadMusic::Rudiment::Pitch.get("#{root_spelling}4")
|
|
117
107
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
%w[M3 P5 M7], # major seventh
|
|
121
|
-
%w[M3 P5 m7], # dominant seventh (major-minor)
|
|
122
|
-
%w[m3 P5 m7], # minor seventh
|
|
123
|
-
%w[m3 P5 M7], # minor-major seventh
|
|
124
|
-
%w[m3 d5 m7], # half-diminished seventh
|
|
125
|
-
%w[m3 d5 d7], # diminished seventh
|
|
126
|
-
%w[M2 M3 P5 m7], # dominant ninth
|
|
127
|
-
%w[m2 M3 P5 m7], # dominant minor ninth
|
|
128
|
-
%w[M2 m3 P5 m7], # minor ninth
|
|
129
|
-
%w[M2 M3 P5 M7] # major ninth
|
|
130
|
-
]
|
|
131
|
-
|
|
132
|
-
seventh_chord_intervals.each do |intervals|
|
|
133
|
-
chord_pitches = [root_pitch]
|
|
134
|
-
|
|
135
|
-
# Each interval is FROM THE ROOT, not consecutive
|
|
136
|
-
intervals.each do |interval_name|
|
|
137
|
-
interval = HeadMusic::Analysis::DiatonicInterval.get(interval_name)
|
|
138
|
-
next_pitch = interval.above(root_pitch)
|
|
139
|
-
chord_pitches << next_pitch
|
|
140
|
-
end
|
|
141
|
-
|
|
108
|
+
interval_sets.each do |intervals|
|
|
109
|
+
chord_pitches = [root_pitch] + intervals.map { |name| HeadMusic::Analysis::DiatonicInterval.get(name).above(root_pitch) }
|
|
142
110
|
pitch_collection = HeadMusic::Analysis::PitchCollection.new(chord_pitches)
|
|
143
|
-
chord_pitch_classes = pitch_collection.pitch_classes
|
|
144
111
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
seventh_chords << pitch_collection
|
|
112
|
+
if dyad_pitch_classes.all? { |pc| pitch_collection.pitch_classes.include?(pc) }
|
|
113
|
+
chords << pitch_collection
|
|
148
114
|
end
|
|
149
115
|
end
|
|
150
116
|
end
|
|
151
117
|
|
|
152
|
-
|
|
118
|
+
chords.uniq { |chord| chord.pitch_classes.sort.map(&:to_i) }
|
|
153
119
|
end
|
|
154
120
|
|
|
155
121
|
def filter_by_key(pitch_collections)
|
|
156
122
|
return pitch_collections unless key
|
|
157
123
|
|
|
158
|
-
diatonic_spellings = key.scale.spellings
|
|
159
|
-
|
|
160
124
|
pitch_collections.select do |pitch_collection|
|
|
161
125
|
pitch_collection.pitches.all? { |pitch| diatonic_spellings.include?(pitch.spelling) }
|
|
162
126
|
end
|
|
@@ -165,65 +129,50 @@ class HeadMusic::Analysis::Dyad
|
|
|
165
129
|
def sort_by_diatonic_agreement(pitch_collections)
|
|
166
130
|
return pitch_collections unless key
|
|
167
131
|
|
|
168
|
-
diatonic_spellings = key.scale.spellings
|
|
169
|
-
|
|
170
132
|
pitch_collections.sort_by do |pitch_collection|
|
|
171
|
-
|
|
172
|
-
diatonic_count = pitch_collection.pitches.count { |pitch| diatonic_spellings.include?(pitch.spelling) }
|
|
173
|
-
-diatonic_count # Negative so higher counts come first
|
|
133
|
+
-pitch_collection.pitches.count { |pitch| diatonic_spellings.include?(pitch.spelling) }
|
|
174
134
|
end
|
|
175
135
|
end
|
|
176
136
|
|
|
137
|
+
def diatonic_spellings
|
|
138
|
+
@diatonic_spellings ||= key.scale.spellings
|
|
139
|
+
end
|
|
140
|
+
|
|
177
141
|
def generate_enharmonic_respellings
|
|
178
142
|
respellings = []
|
|
179
143
|
|
|
180
144
|
# Get enharmonic equivalents for each pitch
|
|
181
|
-
pitch1_equivalents =
|
|
182
|
-
pitch2_equivalents =
|
|
145
|
+
pitch1_equivalents = enharmonic_equivalents_for(pitch1)
|
|
146
|
+
pitch2_equivalents = enharmonic_equivalents_for(pitch2)
|
|
183
147
|
|
|
184
148
|
# Generate all combinations
|
|
185
|
-
pitch1_equivalents.each do |
|
|
186
|
-
pitch2_equivalents.each do |
|
|
187
|
-
|
|
188
|
-
next if p1.spelling == pitch1.spelling && p2.spelling == pitch2.spelling
|
|
149
|
+
pitch1_equivalents.each do |lower|
|
|
150
|
+
pitch2_equivalents.each do |upper|
|
|
151
|
+
next if lower.spelling == pitch1.spelling && upper.spelling == pitch2.spelling
|
|
189
152
|
|
|
190
|
-
|
|
191
|
-
respellings << self.class.new(p1, p2, key: key)
|
|
153
|
+
respellings << self.class.new(lower, upper, key: key)
|
|
192
154
|
end
|
|
193
155
|
end
|
|
194
156
|
|
|
195
157
|
respellings
|
|
196
158
|
end
|
|
197
159
|
|
|
198
|
-
|
|
199
|
-
equivalents = [pitch]
|
|
160
|
+
ALTERATION_SIGNS = {-2 => "bb", -1 => "b", 0 => "", 1 => "#", 2 => "##"}.freeze
|
|
200
161
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
162
|
+
def enharmonic_equivalents_for(pitch)
|
|
163
|
+
target_pitch_class = pitch.pitch_class
|
|
164
|
+
equivalents = [pitch]
|
|
204
165
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
spelling = HeadMusic::Rudiment::Spelling.get("#{letter_name}#{
|
|
208
|
-
next unless spelling
|
|
166
|
+
HeadMusic::Rudiment::LetterName.all.each do |letter_name|
|
|
167
|
+
ALTERATION_SIGNS.each_value do |sign|
|
|
168
|
+
spelling = HeadMusic::Rudiment::Spelling.get("#{letter_name}#{sign}")
|
|
169
|
+
next unless spelling && spelling.pitch_class == target_pitch_class
|
|
170
|
+
next if equivalents.any? { |equiv| equiv.spelling == spelling }
|
|
209
171
|
|
|
210
|
-
|
|
211
|
-
equivalent_pitch = HeadMusic::Rudiment::Pitch.fetch_or_create(spelling, pitch.register)
|
|
212
|
-
equivalents << equivalent_pitch unless equivalents.any? { |p| p.spelling == spelling }
|
|
213
|
-
end
|
|
172
|
+
equivalents << HeadMusic::Rudiment::Pitch.fetch_or_create(spelling, pitch.register)
|
|
214
173
|
end
|
|
215
174
|
end
|
|
216
175
|
|
|
217
176
|
equivalents
|
|
218
177
|
end
|
|
219
|
-
|
|
220
|
-
def alteration_sign(semitones)
|
|
221
|
-
case semitones
|
|
222
|
-
when -2 then "bb"
|
|
223
|
-
when -1 then "b"
|
|
224
|
-
when 0 then ""
|
|
225
|
-
when 1 then "#"
|
|
226
|
-
when 2 then "##"
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
178
|
end
|
|
@@ -110,20 +110,27 @@ class HeadMusic::Analysis::PitchCollection
|
|
|
110
110
|
major_triad? || minor_triad?
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
TRIAD_PATTERNS = {
|
|
114
|
+
major: [%w[M3 m3], %w[m3 P4], %w[P4 M3]],
|
|
115
|
+
minor: [%w[m3 M3], %w[M3 P4], %w[P4 m3]],
|
|
116
|
+
diminished: [%w[m3 m3], %w[m3 A4], %w[A4 m3]],
|
|
117
|
+
augmented: [%w[M3 M3], %w[M3 d4], %w[d4 M3]]
|
|
118
|
+
}.freeze
|
|
119
|
+
|
|
113
120
|
def major_triad?
|
|
114
|
-
|
|
121
|
+
triad_type?(:major)
|
|
115
122
|
end
|
|
116
123
|
|
|
117
124
|
def minor_triad?
|
|
118
|
-
|
|
125
|
+
triad_type?(:minor)
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
def diminished_triad?
|
|
122
|
-
|
|
129
|
+
triad_type?(:diminished)
|
|
123
130
|
end
|
|
124
131
|
|
|
125
132
|
def augmented_triad?
|
|
126
|
-
|
|
133
|
+
triad_type?(:augmented)
|
|
127
134
|
end
|
|
128
135
|
|
|
129
136
|
def root_position_triad?
|
|
@@ -195,6 +202,10 @@ class HeadMusic::Analysis::PitchCollection
|
|
|
195
202
|
|
|
196
203
|
private
|
|
197
204
|
|
|
205
|
+
def triad_type?(type)
|
|
206
|
+
TRIAD_PATTERNS[type].include?(reduction_diatonic_intervals.map(&:shorthand))
|
|
207
|
+
end
|
|
208
|
+
|
|
198
209
|
def reduction_pitches
|
|
199
210
|
pitches.map do |pitch|
|
|
200
211
|
pitch = HeadMusic::Rudiment::Pitch.fetch_or_create(pitch.spelling, pitch.register - 1) while pitch > bass_pitch + 12
|
|
@@ -60,8 +60,8 @@ class HeadMusic::Content::Voice
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def melodic_note_pairs
|
|
63
|
-
@melodic_note_pairs ||= notes.each_cons(2).map do |
|
|
64
|
-
HeadMusic::Content::Voice::MelodicNotePair.new(
|
|
63
|
+
@melodic_note_pairs ||= notes.each_cons(2).map do |first_note, second_note|
|
|
64
|
+
HeadMusic::Content::Voice::MelodicNotePair.new(first_note, second_note)
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -135,6 +135,7 @@ class HeadMusic::Content::Voice
|
|
|
135
135
|
pitches.first(10).map(&:to_s).join(" ")
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
+
# A pair of consecutive notes in a melodic line, used to analyze intervals and leaps.
|
|
138
139
|
class MelodicNotePair
|
|
139
140
|
attr_reader :first_note, :second_note
|
|
140
141
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# A module for musical instruments and their properties
|
|
1
2
|
module HeadMusic::Instruments; end
|
|
2
3
|
|
|
3
4
|
# A musical instrument with parent-based inheritance.
|
|
@@ -36,15 +37,12 @@ class HeadMusic::Instruments::Instrument
|
|
|
36
37
|
def get(name, variant_key = nil)
|
|
37
38
|
return name if name.is_a?(self)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
name_str = name.to_s
|
|
40
41
|
if variant_key
|
|
41
|
-
|
|
42
|
-
result = find_valid_instrument(combined_name) || find_valid_instrument(name.to_s)
|
|
42
|
+
find_valid_instrument("#{name_str}_#{variant_key}") || find_valid_instrument(name_str)
|
|
43
43
|
else
|
|
44
|
-
|
|
44
|
+
find_valid_instrument(name_str) || find_valid_instrument(normalize_variant_name(name_str))
|
|
45
45
|
end
|
|
46
|
-
|
|
47
|
-
result
|
|
48
46
|
end
|
|
49
47
|
|
|
50
48
|
def find_valid_instrument(name)
|
|
@@ -63,24 +61,14 @@ class HeadMusic::Instruments::Instrument
|
|
|
63
61
|
# Convert shorthand variant names to full form
|
|
64
62
|
# e.g., "trumpet_in_eb" -> "trumpet_in_e_flat"
|
|
65
63
|
# e.g., "clarinet_in_bb" -> "clarinet_in_b_flat"
|
|
66
|
-
|
|
67
|
-
name_str = name.to_s
|
|
64
|
+
VARIANT_PATTERN = /^(.+)_in_([a-g])([b#])$/i
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
note = Regexp.last_match(2).downcase
|
|
76
|
-
"#{instrument}_in_#{note}_flat"
|
|
77
|
-
elsif name_str =~ sharp_pattern
|
|
78
|
-
instrument = Regexp.last_match(1)
|
|
79
|
-
note = Regexp.last_match(2).downcase
|
|
80
|
-
"#{instrument}_in_#{note}_sharp"
|
|
81
|
-
else
|
|
82
|
-
name_str
|
|
83
|
-
end
|
|
66
|
+
def normalize_variant_name(name_str)
|
|
67
|
+
match = VARIANT_PATTERN.match(name_str.to_s)
|
|
68
|
+
return name_str.to_s unless match
|
|
69
|
+
|
|
70
|
+
suffix = (match[3] == "b") ? "flat" : "sharp"
|
|
71
|
+
"#{match[1]}_in_#{match[2].downcase}_#{suffix}"
|
|
84
72
|
end
|
|
85
73
|
end
|
|
86
74
|
|
|
@@ -306,13 +294,14 @@ class HeadMusic::Instruments::Instrument
|
|
|
306
294
|
def pitch_key_to_designation
|
|
307
295
|
return nil unless pitch_key
|
|
308
296
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
297
|
+
pitch_key_str = pitch_key.to_s
|
|
298
|
+
first_letter = pitch_key_str[0].upcase
|
|
299
|
+
if pitch_key_str.end_with?("_flat")
|
|
300
|
+
"#{first_letter}b"
|
|
301
|
+
elsif pitch_key_str.end_with?("_sharp")
|
|
302
|
+
"#{first_letter}#"
|
|
314
303
|
else
|
|
315
|
-
|
|
304
|
+
pitch_key_str.upcase
|
|
316
305
|
end
|
|
317
306
|
end
|
|
318
307
|
|
|
@@ -56,9 +56,10 @@ class HeadMusic::Rudiment::Pitch < HeadMusic::Rudiment::Base
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def self.from_number(number)
|
|
59
|
-
|
|
59
|
+
number_int = number.to_i
|
|
60
|
+
return nil unless number == number_int
|
|
60
61
|
|
|
61
|
-
fetch_or_create(HeadMusic::Rudiment::Spelling.from_number(number), (
|
|
62
|
+
fetch_or_create(HeadMusic::Rudiment::Spelling.from_number(number), (number_int / 12) - 1)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
def self.from_number_and_letter(number, letter_name)
|
|
@@ -71,9 +72,10 @@ class HeadMusic::Rudiment::Pitch < HeadMusic::Rudiment::Base
|
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
def self.natural_letter_pitch(number, letter_name)
|
|
75
|
+
number_int = number.to_i
|
|
74
76
|
natural_letter_pitch = get(HeadMusic::Rudiment::LetterName.get(letter_name).pitch_class)
|
|
75
|
-
natural_letter_pitch += 12 while (
|
|
76
|
-
natural_letter_pitch -= 12 while (
|
|
77
|
+
natural_letter_pitch += 12 while (number_int - natural_letter_pitch.to_i) >= 6
|
|
78
|
+
natural_letter_pitch -= 12 while (number_int - natural_letter_pitch.to_i) <= -6
|
|
77
79
|
get(natural_letter_pitch)
|
|
78
80
|
end
|
|
79
81
|
|
|
@@ -218,9 +220,10 @@ class HeadMusic::Rudiment::Pitch < HeadMusic::Rudiment::Base
|
|
|
218
220
|
end
|
|
219
221
|
|
|
220
222
|
def helmholtz_letter_name
|
|
221
|
-
|
|
223
|
+
spelling_str = spelling.to_s
|
|
224
|
+
return spelling_str.downcase if HeadMusic::Rudiment::Register.get(register).helmholtz_case == :lower
|
|
222
225
|
|
|
223
|
-
|
|
226
|
+
spelling_str
|
|
224
227
|
end
|
|
225
228
|
|
|
226
229
|
def helmholtz_marks
|
|
@@ -21,26 +21,7 @@ class HeadMusic::Rudiment::Tuning::JustIntonation < HeadMusic::Rudiment::Tuning
|
|
|
21
21
|
octave: Rational(2, 1)
|
|
22
22
|
}.freeze
|
|
23
23
|
|
|
24
|
-
attr_reader :tonal_center
|
|
25
|
-
|
|
26
24
|
def initialize(reference_pitch: :a440, tonal_center: nil)
|
|
27
|
-
super
|
|
28
|
-
@tonal_center = HeadMusic::Rudiment::Pitch.get(tonal_center || "C4")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def frequency_for(pitch)
|
|
32
|
-
pitch = HeadMusic::Rudiment::Pitch.get(pitch)
|
|
33
|
-
|
|
34
|
-
# Calculate the frequency of the tonal center using equal temperament from reference pitch
|
|
35
|
-
tonal_center_frequency = calculate_tonal_center_frequency
|
|
36
|
-
|
|
37
|
-
# Calculate the interval from the tonal center to the requested pitch
|
|
38
|
-
interval_from_tonal_center = (pitch - tonal_center).semitones
|
|
39
|
-
|
|
40
|
-
# Get the just intonation ratio for this interval
|
|
41
|
-
ratio = ratio_for_interval(interval_from_tonal_center)
|
|
42
|
-
|
|
43
|
-
# Calculate the frequency
|
|
44
|
-
tonal_center_frequency * ratio
|
|
25
|
+
super(reference_pitch: reference_pitch, tonal_center: tonal_center || "C4")
|
|
45
26
|
end
|
|
46
27
|
end
|
|
@@ -23,26 +23,7 @@ class HeadMusic::Rudiment::Tuning::Meantone < HeadMusic::Rudiment::Tuning
|
|
|
23
23
|
octave: Rational(2, 1) # Octave (2.0)
|
|
24
24
|
}.freeze
|
|
25
25
|
|
|
26
|
-
attr_reader :tonal_center
|
|
27
|
-
|
|
28
26
|
def initialize(reference_pitch: :a440, tonal_center: nil)
|
|
29
|
-
super
|
|
30
|
-
@tonal_center = HeadMusic::Rudiment::Pitch.get(tonal_center || "C4")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def frequency_for(pitch)
|
|
34
|
-
pitch = HeadMusic::Rudiment::Pitch.get(pitch)
|
|
35
|
-
|
|
36
|
-
# Calculate the frequency of the tonal center using equal temperament from reference pitch
|
|
37
|
-
tonal_center_frequency = calculate_tonal_center_frequency
|
|
38
|
-
|
|
39
|
-
# Calculate the interval from the tonal center to the requested pitch
|
|
40
|
-
interval_from_tonal_center = (pitch - tonal_center).semitones
|
|
41
|
-
|
|
42
|
-
# Get the meantone ratio for this interval
|
|
43
|
-
ratio = ratio_for_interval(interval_from_tonal_center)
|
|
44
|
-
|
|
45
|
-
# Calculate the frequency
|
|
46
|
-
tonal_center_frequency * ratio
|
|
27
|
+
super(reference_pitch: reference_pitch, tonal_center: tonal_center || "C4")
|
|
47
28
|
end
|
|
48
29
|
end
|
|
@@ -27,26 +27,7 @@ class HeadMusic::Rudiment::Tuning::Pythagorean < HeadMusic::Rudiment::Tuning
|
|
|
27
27
|
diminished_second: Rational(256, 243) # Same as minor second in Pythagorean
|
|
28
28
|
}.freeze
|
|
29
29
|
|
|
30
|
-
attr_reader :tonal_center
|
|
31
|
-
|
|
32
30
|
def initialize(reference_pitch: :a440, tonal_center: nil)
|
|
33
|
-
super
|
|
34
|
-
@tonal_center = HeadMusic::Rudiment::Pitch.get(tonal_center || "C4")
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def frequency_for(pitch)
|
|
38
|
-
pitch = HeadMusic::Rudiment::Pitch.get(pitch)
|
|
39
|
-
|
|
40
|
-
# Calculate the frequency of the tonal center using equal temperament from reference pitch
|
|
41
|
-
tonal_center_frequency = calculate_tonal_center_frequency
|
|
42
|
-
|
|
43
|
-
# Calculate the interval from the tonal center to the requested pitch
|
|
44
|
-
interval_from_tonal_center = (pitch - tonal_center).semitones
|
|
45
|
-
|
|
46
|
-
# Get the Pythagorean ratio for this interval
|
|
47
|
-
ratio = ratio_for_interval(interval_from_tonal_center)
|
|
48
|
-
|
|
49
|
-
# Calculate the frequency
|
|
50
|
-
tonal_center_frequency * ratio
|
|
31
|
+
super(reference_pitch: reference_pitch, tonal_center: tonal_center || "C4")
|
|
51
32
|
end
|
|
52
33
|
end
|
|
@@ -23,18 +23,29 @@ class HeadMusic::Rudiment::Tuning < HeadMusic::Rudiment::Base
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
attr_reader :tonal_center
|
|
27
|
+
|
|
26
28
|
def initialize(reference_pitch: :a440, tonal_center: nil)
|
|
27
29
|
@reference_pitch = HeadMusic::Rudiment::ReferencePitch.get(reference_pitch)
|
|
28
|
-
@tonal_center = tonal_center
|
|
30
|
+
@tonal_center = tonal_center ? HeadMusic::Rudiment::Pitch.get(tonal_center) : nil
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def frequency_for(pitch)
|
|
32
34
|
pitch = HeadMusic::Rudiment::Pitch.get(pitch)
|
|
33
|
-
|
|
35
|
+
return equal_temperament_frequency(pitch) unless tonal_center
|
|
36
|
+
|
|
37
|
+
tonal_center_frequency = calculate_tonal_center_frequency
|
|
38
|
+
interval_from_tonal_center = (pitch - tonal_center).semitones
|
|
39
|
+
ratio = ratio_for_interval(interval_from_tonal_center)
|
|
40
|
+
tonal_center_frequency * ratio
|
|
34
41
|
end
|
|
35
42
|
|
|
36
43
|
private
|
|
37
44
|
|
|
45
|
+
def equal_temperament_frequency(pitch)
|
|
46
|
+
reference_pitch_frequency * (2**(1.0 / 12))**(pitch - reference_pitch.pitch).semitones
|
|
47
|
+
end
|
|
48
|
+
|
|
38
49
|
def calculate_tonal_center_frequency
|
|
39
50
|
# Use equal temperament to get the tonal center frequency from the reference pitch
|
|
40
51
|
interval_to_tonal_center = (tonal_center - reference_pitch.pitch).semitones
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Module for style guidelines.
|
|
2
|
+
module HeadMusic::Style::Guidelines; end
|
|
3
|
+
|
|
4
|
+
# Base class for guidelines requiring a step in a specific direction to the final note.
|
|
5
|
+
class HeadMusic::Style::Guidelines::DirectionalStepToFinalNote < HeadMusic::Style::Annotation
|
|
6
|
+
def marks
|
|
7
|
+
return if last_melodic_interval.nil?
|
|
8
|
+
|
|
9
|
+
fitness = 1
|
|
10
|
+
fitness *= HeadMusic::PENALTY_FACTOR unless step?
|
|
11
|
+
fitness *= HeadMusic::PENALTY_FACTOR unless expected_direction?
|
|
12
|
+
HeadMusic::Style::Mark.for_all(notes[-2..], fitness: fitness) if fitness < 1
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def expected_direction?
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def step?
|
|
22
|
+
last_melodic_interval&.step?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def last_melodic_interval
|
|
26
|
+
@last_melodic_interval ||= melodic_intervals.last
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -2,26 +2,10 @@
|
|
|
2
2
|
module HeadMusic::Style::Guidelines; end
|
|
3
3
|
|
|
4
4
|
# A counterpoint guideline
|
|
5
|
-
class HeadMusic::Style::Guidelines::FourToOne < HeadMusic::Style::
|
|
5
|
+
class HeadMusic::Style::Guidelines::FourToOne < HeadMusic::Style::Guidelines::NoteCountPerBar
|
|
6
6
|
MESSAGE = "Use four quarter notes against each whole note in the cantus firmus."
|
|
7
7
|
|
|
8
8
|
QUARTER = HeadMusic::Rudiment::RhythmicValue.get(:quarter)
|
|
9
|
-
WHOLE = HeadMusic::Rudiment::RhythmicValue.get(:whole)
|
|
10
|
-
|
|
11
|
-
def marks
|
|
12
|
-
return [] unless cantus_firmus&.notes&.any?
|
|
13
|
-
|
|
14
|
-
cantus_firmus.notes.each_with_index.filter_map do |cf_note, index|
|
|
15
|
-
bar_number = cf_note.position.bar_number
|
|
16
|
-
if index == cantus_firmus.notes.length - 1
|
|
17
|
-
check_final_bar(bar_number)
|
|
18
|
-
elsif index == 0
|
|
19
|
-
check_first_bar(bar_number)
|
|
20
|
-
else
|
|
21
|
-
check_middle_bar(bar_number)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
9
|
|
|
26
10
|
private
|
|
27
11
|
|
|
@@ -42,49 +26,20 @@ class HeadMusic::Style::Guidelines::FourToOne < HeadMusic::Style::Annotation
|
|
|
42
26
|
mark_bar(bar_number)
|
|
43
27
|
end
|
|
44
28
|
|
|
45
|
-
def check_final_bar(bar_number)
|
|
46
|
-
bar_notes = notes_in_bar(bar_number)
|
|
47
|
-
return if one_whole_note?(bar_notes)
|
|
48
|
-
|
|
49
|
-
mark_bar(bar_number)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
29
|
def four_quarter_notes?(bar_notes)
|
|
53
|
-
bar_notes.length == 4 && bar_notes.all? { |
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def one_whole_note?(bar_notes)
|
|
57
|
-
bar_notes.length == 1 && bar_notes.first.rhythmic_value == WHOLE
|
|
30
|
+
bar_notes.length == 4 && bar_notes.all? { |note| note.rhythmic_value == QUARTER }
|
|
58
31
|
end
|
|
59
32
|
|
|
60
33
|
def rest_then_three_quarter_notes?(bar_notes, bar_rests)
|
|
61
34
|
bar_notes.length == 3 &&
|
|
62
|
-
bar_notes.all? { |
|
|
35
|
+
bar_notes.all? { |note| note.rhythmic_value == QUARTER } &&
|
|
63
36
|
bar_rests.length == 1 &&
|
|
64
37
|
bar_rests.first.rhythmic_value == QUARTER
|
|
65
38
|
end
|
|
66
39
|
|
|
67
40
|
def three_quarter_notes_after_downbeat?(bar_notes)
|
|
68
41
|
bar_notes.length == 3 &&
|
|
69
|
-
bar_notes.all? { |
|
|
42
|
+
bar_notes.all? { |note| note.rhythmic_value == QUARTER } &&
|
|
70
43
|
bar_notes.first.position.count > 1
|
|
71
44
|
end
|
|
72
|
-
|
|
73
|
-
def notes_in_bar(bar_number)
|
|
74
|
-
notes.select { |n| n.position.bar_number == bar_number }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def rests_in_bar(bar_number)
|
|
78
|
-
rests.select { |r| r.position.bar_number == bar_number }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def mark_bar(bar_number)
|
|
82
|
-
bar_placements = notes_in_bar(bar_number)
|
|
83
|
-
if bar_placements.any?
|
|
84
|
-
HeadMusic::Style::Mark.for_all(bar_placements)
|
|
85
|
-
else
|
|
86
|
-
cf_note = cantus_firmus.notes.detect { |n| n.position.bar_number == bar_number }
|
|
87
|
-
HeadMusic::Style::Mark.for(cf_note) if cf_note
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
45
|
end
|