head_music 8.2.1 → 9.0.1
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/.github/workflows/ci.yml +1 -1
- data/.github/workflows/release.yml +1 -1
- data/CHANGELOG.md +53 -0
- data/CLAUDE.md +151 -0
- data/Gemfile.lock +25 -25
- data/MUSIC_THEORY.md +120 -0
- data/Rakefile +2 -2
- data/bin/check_instrument_consistency.rb +86 -0
- data/check_instrument_consistency.rb +0 -0
- data/head_music.gemspec +1 -1
- data/lib/head_music/analysis/diatonic_interval/naming.rb +1 -1
- data/lib/head_music/analysis/diatonic_interval.rb +50 -27
- data/lib/head_music/analysis/interval_consonance.rb +51 -0
- data/lib/head_music/content/note.rb +1 -1
- data/lib/head_music/content/placement.rb +1 -1
- data/lib/head_music/content/position.rb +1 -1
- data/lib/head_music/content/staff.rb +1 -1
- data/lib/head_music/instruments/instrument.rb +103 -113
- data/lib/head_music/instruments/instrument_families.yml +10 -9
- data/lib/head_music/instruments/instrument_family.rb +13 -2
- data/lib/head_music/instruments/instrument_type.rb +188 -0
- data/lib/head_music/instruments/instruments.yml +350 -368
- data/lib/head_music/instruments/score_order.rb +139 -0
- data/lib/head_music/instruments/score_orders.yml +130 -0
- data/lib/head_music/instruments/variant.rb +6 -0
- data/lib/head_music/locales/de.yml +6 -0
- data/lib/head_music/locales/en.yml +98 -0
- data/lib/head_music/locales/es.yml +6 -0
- data/lib/head_music/locales/fr.yml +6 -0
- data/lib/head_music/locales/it.yml +6 -0
- data/lib/head_music/locales/ru.yml +6 -0
- data/lib/head_music/rudiment/alteration.rb +23 -8
- data/lib/head_music/rudiment/base.rb +9 -0
- data/lib/head_music/rudiment/chromatic_interval.rb +3 -6
- data/lib/head_music/rudiment/clef.rb +1 -1
- data/lib/head_music/rudiment/consonance.rb +37 -4
- data/lib/head_music/rudiment/diatonic_context.rb +25 -0
- data/lib/head_music/rudiment/key.rb +77 -0
- data/lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb +1 -1
- data/lib/head_music/rudiment/key_signature.rb +46 -7
- data/lib/head_music/rudiment/letter_name.rb +3 -3
- data/lib/head_music/rudiment/meter.rb +19 -9
- data/lib/head_music/rudiment/mode.rb +92 -0
- data/lib/head_music/rudiment/musical_symbol.rb +1 -1
- data/lib/head_music/rudiment/note.rb +112 -0
- data/lib/head_music/rudiment/pitch/parser.rb +52 -0
- data/lib/head_music/rudiment/pitch.rb +5 -6
- data/lib/head_music/rudiment/pitch_class.rb +1 -1
- data/lib/head_music/rudiment/quality.rb +1 -1
- data/lib/head_music/rudiment/reference_pitch.rb +1 -1
- data/lib/head_music/rudiment/register.rb +4 -1
- data/lib/head_music/rudiment/rest.rb +36 -0
- data/lib/head_music/rudiment/rhythmic_element.rb +53 -0
- data/lib/head_music/rudiment/rhythmic_unit/parser.rb +86 -0
- data/lib/head_music/rudiment/rhythmic_unit.rb +104 -29
- data/lib/head_music/rudiment/rhythmic_units.yml +80 -0
- data/lib/head_music/rudiment/rhythmic_value/parser.rb +77 -0
- data/lib/head_music/{content → rudiment}/rhythmic_value.rb +23 -5
- data/lib/head_music/rudiment/scale.rb +4 -5
- data/lib/head_music/rudiment/scale_degree.rb +9 -4
- data/lib/head_music/rudiment/scale_type.rb +9 -3
- data/lib/head_music/rudiment/solmization.rb +1 -1
- data/lib/head_music/rudiment/spelling.rb +5 -4
- data/lib/head_music/rudiment/tempo.rb +85 -0
- data/lib/head_music/rudiment/tonal_context.rb +35 -0
- data/lib/head_music/rudiment/tuning/just_intonation.rb +85 -0
- data/lib/head_music/rudiment/tuning/meantone.rb +87 -0
- data/lib/head_music/rudiment/tuning/pythagorean.rb +91 -0
- data/lib/head_music/rudiment/tuning.rb +18 -4
- data/lib/head_music/rudiment/unpitched_note.rb +62 -0
- data/lib/head_music/style/annotation.rb +4 -4
- data/lib/head_music/style/guidelines/notes_same_length.rb +16 -16
- data/lib/head_music/style/medieval_tradition.rb +26 -0
- data/lib/head_music/style/modern_tradition.rb +34 -0
- data/lib/head_music/style/renaissance_tradition.rb +26 -0
- data/lib/head_music/style/tradition.rb +21 -0
- data/lib/head_music/utilities/hash_key.rb +34 -2
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +33 -9
- data/user_stories/active/handle-time.md +7 -0
- data/user_stories/active/handle-time.rb +177 -0
- data/user_stories/done/epic--score-order/PLAN.md +244 -0
- data/user_stories/done/epic--score-order/band-score-order.md +38 -0
- data/user_stories/done/epic--score-order/chamber-ensemble-score-order.md +33 -0
- data/user_stories/done/epic--score-order/orchestral-score-order.md +43 -0
- data/user_stories/done/instrument-variant.md +65 -0
- data/user_stories/done/superclass-for-note.md +30 -0
- data/user_stories/todo/agentic-daw.md +3 -0
- data/user_stories/todo/consonance-dissonance-classification.md +57 -0
- data/user_stories/todo/dyad-analysis.md +57 -0
- data/user_stories/todo/material-and-scores.md +10 -0
- data/user_stories/todo/organizing-content.md +72 -0
- data/user_stories/todo/percussion_set.md +1 -0
- data/user_stories/todo/pitch-class-set-analysis.md +79 -0
- data/user_stories/todo/pitch-set-classification.md +72 -0
- data/user_stories/todo/sonority-identification.md +67 -0
- metadata +51 -6
- data/TODO.md +0 -218
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Namespace for instrument definitions, categorization, and configuration
|
|
2
|
+
module HeadMusic::Instruments; end
|
|
3
|
+
|
|
4
|
+
# A musical instrument type representing a catalog entry.
|
|
5
|
+
# An instrument type defines the base characteristics and available variants for an instrument.
|
|
6
|
+
# Attributes:
|
|
7
|
+
# name_key: the name of the instrument type
|
|
8
|
+
# alias_name_keys: an array of alternative names for the instrument type
|
|
9
|
+
# orchestra_section_key: the section of the orchestra (e.g. "strings")
|
|
10
|
+
# family_key: the key for the family of the instrument (e.g. "saxophone")
|
|
11
|
+
# classification_keys: an array of classification_keys
|
|
12
|
+
# default_clefs: the default clef or system of clefs for the instrument type
|
|
13
|
+
# - [treble] for instruments that use the treble clef
|
|
14
|
+
# - [treble, bass] for instruments that use the grand staff
|
|
15
|
+
# variants:
|
|
16
|
+
# a hash of default and alternative pitch designations
|
|
17
|
+
# Associations:
|
|
18
|
+
# family: the family of the instrument (e.g. "saxophone")
|
|
19
|
+
# orchestra_section: the section of the orchestra (e.g. "strings")
|
|
20
|
+
class HeadMusic::Instruments::InstrumentType
|
|
21
|
+
include HeadMusic::Named
|
|
22
|
+
|
|
23
|
+
INSTRUMENTS = YAML.load_file(File.expand_path("instruments.yml", __dir__)).freeze
|
|
24
|
+
|
|
25
|
+
def self.get(name)
|
|
26
|
+
get_by_name(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.all
|
|
30
|
+
HeadMusic::Instruments::InstrumentFamily.all
|
|
31
|
+
@all ||=
|
|
32
|
+
INSTRUMENTS.map { |key, _data| get(key) }.sort_by { |instrument| instrument.name.downcase }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader(
|
|
36
|
+
:name_key, :alias_name_keys,
|
|
37
|
+
:family_key, :orchestra_section_key,
|
|
38
|
+
:variants, :classification_keys
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def ==(other)
|
|
42
|
+
to_s == other.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def translation(locale = :en)
|
|
46
|
+
return name unless name_key
|
|
47
|
+
|
|
48
|
+
I18n.translate(name_key, scope: %i[head_music instruments], locale: locale, default: name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def family
|
|
52
|
+
return unless family_key
|
|
53
|
+
|
|
54
|
+
HeadMusic::Instruments::InstrumentFamily.get(family_key)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns true if the instrument sounds at a different pitch than written.
|
|
58
|
+
def transposing?
|
|
59
|
+
default_sounding_transposition != 0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns true if the instrument sounds at a different register than written.
|
|
63
|
+
def transposing_at_the_octave?
|
|
64
|
+
transposing? && default_sounding_transposition % 12 == 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def single_staff?
|
|
68
|
+
default_staves.length == 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def multiple_staves?
|
|
72
|
+
default_staves.length > 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def pitched?
|
|
76
|
+
return false if default_clefs.compact.uniq == [HeadMusic::Rudiment::Clef.get("neutral_clef")]
|
|
77
|
+
|
|
78
|
+
default_clefs.any?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def default_variant
|
|
82
|
+
variants&.find(&:default?) || variants&.first
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def default_instrument
|
|
86
|
+
@default_instrument ||= HeadMusic::Instruments::Instrument.new(self, default_variant)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def default_staff_scheme
|
|
90
|
+
default_variant&.default_staff_scheme
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def default_staves
|
|
94
|
+
default_staff_scheme&.staves || []
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def default_clefs
|
|
98
|
+
default_staves&.map(&:clef) || []
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def default_sounding_transposition
|
|
102
|
+
default_staves&.first&.sounding_transposition || 0
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private_class_method :new
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def initialize(name)
|
|
110
|
+
record = record_for_name(name)
|
|
111
|
+
if record
|
|
112
|
+
initialize_data_from_record(record)
|
|
113
|
+
else
|
|
114
|
+
self.name = name.to_s
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def record_for_name(name)
|
|
119
|
+
record_for_key(HeadMusic::Utilities::HashKey.for(name)) ||
|
|
120
|
+
record_for_key(key_for_name(name)) ||
|
|
121
|
+
record_for_alias(name)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def key_for_name(name)
|
|
125
|
+
INSTRUMENTS.each do |key, _data|
|
|
126
|
+
I18n.config.available_locales.each do |locale|
|
|
127
|
+
translation = I18n.t("head_music.instruments.#{key}", locale: locale)
|
|
128
|
+
return key if translation.downcase == name.downcase
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def record_for_key(key)
|
|
135
|
+
INSTRUMENTS.each do |name_key, data|
|
|
136
|
+
return data.merge!("name_key" => name_key) if name_key.to_s == key.to_s
|
|
137
|
+
end
|
|
138
|
+
nil
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def record_for_alias(name)
|
|
142
|
+
normalized_name = HeadMusic::Utilities::HashKey.for(name).to_s
|
|
143
|
+
INSTRUMENTS.each do |name_key, data|
|
|
144
|
+
data["alias_name_keys"]&.each do |alias_key|
|
|
145
|
+
return data.merge!("name_key" => name_key) if HeadMusic::Utilities::HashKey.for(alias_key).to_s == normalized_name
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def initialize_data_from_record(record)
|
|
152
|
+
initialize_family(record)
|
|
153
|
+
inherit_family_attributes(record)
|
|
154
|
+
initialize_names(record)
|
|
155
|
+
initialize_attributes(record)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def initialize_family(record)
|
|
159
|
+
@family_key = record["family_key"]
|
|
160
|
+
@family = HeadMusic::Instruments::InstrumentFamily.get(family_key)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def inherit_family_attributes(record)
|
|
164
|
+
return unless family
|
|
165
|
+
|
|
166
|
+
@orchestra_section_key = family.orchestra_section_key
|
|
167
|
+
@classification_keys = family.classification_keys || []
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def initialize_names(record)
|
|
171
|
+
@name_key = record["name_key"].to_sym
|
|
172
|
+
self.name = I18n.translate(name_key, scope: "head_music.instruments", locale: "en", default: inferred_name)
|
|
173
|
+
@alias_name_keys = record["alias_name_keys"] || []
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def initialize_attributes(record)
|
|
177
|
+
@orchestra_section_key ||= record["orchestra_section_key"]
|
|
178
|
+
@classification_keys = [@classification_keys, record["classification_keys"]].flatten.compact.uniq
|
|
179
|
+
@variants =
|
|
180
|
+
(record["variants"] || {}).map do |key, attributes|
|
|
181
|
+
HeadMusic::Instruments::Variant.new(key, attributes)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def inferred_name
|
|
186
|
+
name_key.to_s.tr("_", " ")
|
|
187
|
+
end
|
|
188
|
+
end
|