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,139 @@
|
|
|
1
|
+
module HeadMusic::Instruments; end
|
|
2
|
+
|
|
3
|
+
class HeadMusic::Instruments::ScoreOrder
|
|
4
|
+
include HeadMusic::Named
|
|
5
|
+
|
|
6
|
+
SCORE_ORDERS = YAML.load_file(File.expand_path("score_orders.yml", __dir__)).freeze
|
|
7
|
+
|
|
8
|
+
DEFAULT_ENSEMBLE_TYPE_KEY = :orchestral
|
|
9
|
+
|
|
10
|
+
attr_reader :ensemble_type_key, :sections
|
|
11
|
+
|
|
12
|
+
# Factory method to get a ScoreOrder instance for a specific ensemble type
|
|
13
|
+
def self.get(ensemble_type)
|
|
14
|
+
@instances ||= {}
|
|
15
|
+
key = HeadMusic::Utilities::HashKey.for(ensemble_type)
|
|
16
|
+
return unless SCORE_ORDERS.key?(key.to_s)
|
|
17
|
+
|
|
18
|
+
@instances[key] ||= new(key)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Convenience method to order instruments in orchestral order
|
|
22
|
+
def self.in_orchestral_order(instruments)
|
|
23
|
+
get(:orchestral).order(instruments)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Convenience method to order instruments in concert band order
|
|
27
|
+
def self.in_band_order(instruments)
|
|
28
|
+
get(:band).order(instruments)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Accepts a list of instruments and orders them according to this ensemble type's conventions
|
|
32
|
+
def order(instruments)
|
|
33
|
+
valid_inputs = instruments.compact.reject { |i| i.respond_to?(:empty?) && i.empty? }
|
|
34
|
+
instrument_objects = valid_inputs.map { |i| normalize_to_instrument(i) }.compact
|
|
35
|
+
|
|
36
|
+
# Build ordering index
|
|
37
|
+
ordering_index = build_ordering_index
|
|
38
|
+
|
|
39
|
+
# Separate known and unknown instruments
|
|
40
|
+
known_instruments = []
|
|
41
|
+
unknown_instruments = []
|
|
42
|
+
|
|
43
|
+
instrument_objects.each do |instrument|
|
|
44
|
+
position_info = find_position_with_transposition(instrument, ordering_index)
|
|
45
|
+
if position_info
|
|
46
|
+
known_instruments << [instrument, position_info]
|
|
47
|
+
else
|
|
48
|
+
unknown_instruments << instrument
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Sort known instruments by position (primary) and transposition (secondary)
|
|
53
|
+
sorted_known = known_instruments.sort_by { |_, pos_info|
|
|
54
|
+
[pos_info[:position], -pos_info[:transposition]]
|
|
55
|
+
}.map(&:first)
|
|
56
|
+
sorted_known + unknown_instruments.sort_by(&:to_s)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private_class_method :new
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def initialize(ensemble_type_key = DEFAULT_ENSEMBLE_TYPE_KEY)
|
|
64
|
+
@ensemble_type_key = ensemble_type_key.to_sym
|
|
65
|
+
data = SCORE_ORDERS[ensemble_type_key.to_s]
|
|
66
|
+
|
|
67
|
+
@sections = data["sections"] || []
|
|
68
|
+
self.name = data["name"] || ensemble_type_key.to_s.tr("_", " ").capitalize
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def normalize_to_instrument(input)
|
|
72
|
+
# Return if already an Instrument instance
|
|
73
|
+
return input if input.is_a?(HeadMusic::Instruments::Instrument)
|
|
74
|
+
|
|
75
|
+
# Return InstrumentType instances as-is for backward compatibility (duck typing)
|
|
76
|
+
return input.default_instrument if input.is_a?(HeadMusic::Instruments::InstrumentType)
|
|
77
|
+
|
|
78
|
+
# Return other objects that respond to required methods (mock objects, etc.)
|
|
79
|
+
return input if input.respond_to?(:name_key) && input.respond_to?(:family_key)
|
|
80
|
+
|
|
81
|
+
# Create an Instrument instance for string inputs
|
|
82
|
+
HeadMusic::Instruments::Instrument.get(input) || HeadMusic::Instruments::InstrumentType.get(input)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Builds an index mapping instrument names to their position in the order
|
|
86
|
+
def build_ordering_index
|
|
87
|
+
index = {}
|
|
88
|
+
position = 0
|
|
89
|
+
|
|
90
|
+
sections.each do |section|
|
|
91
|
+
instruments = section["instruments"] || []
|
|
92
|
+
instruments.each do |instrument_key|
|
|
93
|
+
# Store position for this instrument key
|
|
94
|
+
index[instrument_key.to_s] = position
|
|
95
|
+
position += 1
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
index
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Finds the position of an instrument in the ordering
|
|
103
|
+
def find_position(instrument, ordering_index)
|
|
104
|
+
# Try exact match with name_key
|
|
105
|
+
return ordering_index[instrument.name_key.to_s] if instrument.name_key && ordering_index.key?(instrument.name_key.to_s)
|
|
106
|
+
|
|
107
|
+
# Try matching by family + range category (e.g., alto_saxophone -> saxophone family)
|
|
108
|
+
if instrument.family_key
|
|
109
|
+
family_base = instrument.family_key.to_s
|
|
110
|
+
instrument_key = instrument.name_key.to_s
|
|
111
|
+
|
|
112
|
+
# Check if this is a variant of a family (e.g., alto_saxophone)
|
|
113
|
+
if instrument_key.include?(family_base)
|
|
114
|
+
# Look for the specific variant first
|
|
115
|
+
return ordering_index[instrument_key] if ordering_index.key?(instrument_key)
|
|
116
|
+
|
|
117
|
+
# Fall back to generic family instrument if listed
|
|
118
|
+
return ordering_index[family_base] if ordering_index.key?(family_base)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Try normalized name (lowercase, underscored)
|
|
123
|
+
normalized = instrument.name.downcase.tr(" ", "_").tr("-", "_")
|
|
124
|
+
return ordering_index[normalized] if ordering_index.key?(normalized)
|
|
125
|
+
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Finds the position and transposition information for an instrument
|
|
130
|
+
def find_position_with_transposition(instrument, ordering_index)
|
|
131
|
+
position = find_position(instrument, ordering_index)
|
|
132
|
+
return nil unless position
|
|
133
|
+
|
|
134
|
+
# Get the sounding transposition for secondary sorting
|
|
135
|
+
transposition = instrument.default_sounding_transposition || 0
|
|
136
|
+
|
|
137
|
+
{position: position, transposition: transposition}
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
orchestral:
|
|
3
|
+
name: "Orchestral"
|
|
4
|
+
sections:
|
|
5
|
+
- section_key: woodwind
|
|
6
|
+
instruments:
|
|
7
|
+
- piccolo_flute
|
|
8
|
+
- flute
|
|
9
|
+
- alto_flute
|
|
10
|
+
- alto_recorder
|
|
11
|
+
- soprano_recorder
|
|
12
|
+
- tenor_recorder
|
|
13
|
+
- oboe
|
|
14
|
+
- english_horn
|
|
15
|
+
- clarinet
|
|
16
|
+
- alto_clarinet
|
|
17
|
+
- bass_clarinet
|
|
18
|
+
- soprano_saxophone
|
|
19
|
+
- alto_saxophone
|
|
20
|
+
- tenor_saxophone
|
|
21
|
+
- baritone_saxophone
|
|
22
|
+
- bassoon
|
|
23
|
+
- contrabassoon
|
|
24
|
+
- section_key: brass
|
|
25
|
+
instruments:
|
|
26
|
+
- french_horn
|
|
27
|
+
- trumpet
|
|
28
|
+
- cornet
|
|
29
|
+
- trombone
|
|
30
|
+
- bass_trombone
|
|
31
|
+
- tuba
|
|
32
|
+
- section_key: percussion
|
|
33
|
+
instruments:
|
|
34
|
+
- timpani
|
|
35
|
+
- snare_drum
|
|
36
|
+
- bass_drum
|
|
37
|
+
- cymbal
|
|
38
|
+
- gong
|
|
39
|
+
- xylophone
|
|
40
|
+
- glockenspiel
|
|
41
|
+
- marimba
|
|
42
|
+
- vibraphone
|
|
43
|
+
- percussion
|
|
44
|
+
- section_key: keyboard
|
|
45
|
+
instruments:
|
|
46
|
+
- harp
|
|
47
|
+
- piano
|
|
48
|
+
- celesta
|
|
49
|
+
- harpsichord
|
|
50
|
+
- organ
|
|
51
|
+
- section_key: voice
|
|
52
|
+
instruments:
|
|
53
|
+
- soprano_voice
|
|
54
|
+
- alto_voice
|
|
55
|
+
- tenor_voice
|
|
56
|
+
- bass_voice
|
|
57
|
+
- section_key: string
|
|
58
|
+
instruments:
|
|
59
|
+
- violin
|
|
60
|
+
- viola
|
|
61
|
+
- cello
|
|
62
|
+
- double_bass
|
|
63
|
+
|
|
64
|
+
band:
|
|
65
|
+
name: "Concert Band"
|
|
66
|
+
sections:
|
|
67
|
+
- section_key: woodwind
|
|
68
|
+
instruments:
|
|
69
|
+
- piccolo
|
|
70
|
+
- flute
|
|
71
|
+
- oboe
|
|
72
|
+
- english_horn
|
|
73
|
+
- bassoon
|
|
74
|
+
- contrabassoon
|
|
75
|
+
- clarinet
|
|
76
|
+
- alto_clarinet
|
|
77
|
+
- bass_clarinet
|
|
78
|
+
- soprano_saxophone
|
|
79
|
+
- alto_saxophone
|
|
80
|
+
- tenor_saxophone
|
|
81
|
+
- baritone_saxophone
|
|
82
|
+
- section_key: brass
|
|
83
|
+
instruments:
|
|
84
|
+
- cornet
|
|
85
|
+
- trumpet
|
|
86
|
+
- french_horn
|
|
87
|
+
- trombone
|
|
88
|
+
- bass_trombone
|
|
89
|
+
- euphonium
|
|
90
|
+
- baritone_horn
|
|
91
|
+
- tuba
|
|
92
|
+
- section_key: percussion
|
|
93
|
+
instruments:
|
|
94
|
+
- timpani
|
|
95
|
+
- snare_drum
|
|
96
|
+
- bass_drum
|
|
97
|
+
- cymbal
|
|
98
|
+
- percussion
|
|
99
|
+
|
|
100
|
+
brass_quintet:
|
|
101
|
+
name: "Brass Quintet"
|
|
102
|
+
sections:
|
|
103
|
+
- section_key: brass
|
|
104
|
+
instruments:
|
|
105
|
+
- trumpet # First trumpet
|
|
106
|
+
- trumpet # Second trumpet
|
|
107
|
+
- french_horn
|
|
108
|
+
- trombone
|
|
109
|
+
- tuba
|
|
110
|
+
|
|
111
|
+
woodwind_quintet:
|
|
112
|
+
name: "Woodwind Quintet"
|
|
113
|
+
sections:
|
|
114
|
+
- section_key: mixed
|
|
115
|
+
instruments:
|
|
116
|
+
- flute
|
|
117
|
+
- oboe
|
|
118
|
+
- clarinet
|
|
119
|
+
- french_horn # Horn is traditional in woodwind quintets
|
|
120
|
+
- bassoon
|
|
121
|
+
|
|
122
|
+
string_quartet:
|
|
123
|
+
name: "String Quartet"
|
|
124
|
+
sections:
|
|
125
|
+
- section_key: string
|
|
126
|
+
instruments:
|
|
127
|
+
- violin # First violin
|
|
128
|
+
- violin # Second violin
|
|
129
|
+
- viola
|
|
130
|
+
- cello
|
|
@@ -35,4 +35,10 @@ class HeadMusic::Instruments::Variant
|
|
|
35
35
|
@default_staff_scheme ||=
|
|
36
36
|
staff_schemes.find(&:default?) || staff_schemes.first
|
|
37
37
|
end
|
|
38
|
+
|
|
39
|
+
def ==(other)
|
|
40
|
+
return false unless other.is_a?(self.class)
|
|
41
|
+
|
|
42
|
+
key == other.key && attributes == other.attributes
|
|
43
|
+
end
|
|
38
44
|
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
en:
|
|
2
2
|
head_music:
|
|
3
|
+
alterations:
|
|
4
|
+
sharp: sharp
|
|
5
|
+
flat: flat
|
|
6
|
+
natural: natural
|
|
7
|
+
double_sharp: double sharp
|
|
8
|
+
double_flat: double flat
|
|
3
9
|
chromatic_intervals:
|
|
4
10
|
perfect_unison: perfect unison
|
|
5
11
|
minor_second: minor second
|
|
@@ -14,6 +20,98 @@ en:
|
|
|
14
20
|
minor_seventh: minor seventh
|
|
15
21
|
major_seventh: major seventh
|
|
16
22
|
perfect_octave: perfect octave
|
|
23
|
+
diatonic_intervals:
|
|
24
|
+
perfect_unison: perfect unison
|
|
25
|
+
minor_second: minor second
|
|
26
|
+
major_second: major second
|
|
27
|
+
diminished_second: diminished second
|
|
28
|
+
augmented_second: augmented second
|
|
29
|
+
doubly_diminished_second: doubly diminished second
|
|
30
|
+
doubly_augmented_second: doubly augmented second
|
|
31
|
+
minor_third: minor third
|
|
32
|
+
major_third: major third
|
|
33
|
+
diminished_third: diminished third
|
|
34
|
+
augmented_third: augmented third
|
|
35
|
+
doubly_diminished_third: doubly diminished third
|
|
36
|
+
doubly_augmented_third: doubly augmented third
|
|
37
|
+
perfect_fourth: perfect fourth
|
|
38
|
+
diminished_fourth: diminished fourth
|
|
39
|
+
augmented_fourth: augmented fourth
|
|
40
|
+
doubly_diminished_fourth: doubly diminished fourth
|
|
41
|
+
doubly_augmented_fourth: doubly augmented fourth
|
|
42
|
+
perfect_fifth: perfect fifth
|
|
43
|
+
diminished_fifth: diminished fifth
|
|
44
|
+
augmented_fifth: augmented fifth
|
|
45
|
+
doubly_diminished_fifth: doubly diminished fifth
|
|
46
|
+
doubly_augmented_fifth: doubly augmented fifth
|
|
47
|
+
minor_sixth: minor sixth
|
|
48
|
+
major_sixth: major sixth
|
|
49
|
+
diminished_sixth: diminished sixth
|
|
50
|
+
augmented_sixth: augmented sixth
|
|
51
|
+
doubly_diminished_sixth: doubly diminished sixth
|
|
52
|
+
doubly_augmented_sixth: doubly augmented sixth
|
|
53
|
+
minor_seventh: minor seventh
|
|
54
|
+
major_seventh: major seventh
|
|
55
|
+
diminished_seventh: diminished seventh
|
|
56
|
+
augmented_seventh: augmented seventh
|
|
57
|
+
doubly_diminished_seventh: doubly diminished seventh
|
|
58
|
+
doubly_augmented_seventh: doubly augmented seventh
|
|
59
|
+
perfect_octave: perfect octave
|
|
60
|
+
diminished_octave: diminished octave
|
|
61
|
+
augmented_octave: augmented octave
|
|
62
|
+
doubly_diminished_octave: doubly diminished octave
|
|
63
|
+
doubly_augmented_octave: doubly augmented octave
|
|
64
|
+
minor_ninth: minor ninth
|
|
65
|
+
major_ninth: major ninth
|
|
66
|
+
diminished_ninth: diminished ninth
|
|
67
|
+
augmented_ninth: augmented ninth
|
|
68
|
+
doubly_diminished_ninth: doubly diminished ninth
|
|
69
|
+
doubly_augmented_ninth: doubly augmented ninth
|
|
70
|
+
minor_tenth: minor tenth
|
|
71
|
+
major_tenth: major tenth
|
|
72
|
+
diminished_tenth: diminished tenth
|
|
73
|
+
augmented_tenth: augmented tenth
|
|
74
|
+
doubly_diminished_tenth: doubly diminished tenth
|
|
75
|
+
doubly_augmented_tenth: doubly augmented tenth
|
|
76
|
+
perfect_eleventh: perfect eleventh
|
|
77
|
+
diminished_eleventh: diminished eleventh
|
|
78
|
+
augmented_eleventh: augmented eleventh
|
|
79
|
+
doubly_diminished_eleventh: doubly diminished eleventh
|
|
80
|
+
doubly_augmented_eleventh: doubly augmented eleventh
|
|
81
|
+
perfect_twelfth: perfect twelfth
|
|
82
|
+
diminished_twelfth: diminished twelfth
|
|
83
|
+
augmented_twelfth: augmented twelfth
|
|
84
|
+
doubly_diminished_twelfth: doubly diminished twelfth
|
|
85
|
+
doubly_augmented_twelfth: doubly augmented twelfth
|
|
86
|
+
minor_thirteenth: minor thirteenth
|
|
87
|
+
major_thirteenth: major thirteenth
|
|
88
|
+
diminished_thirteenth: diminished thirteenth
|
|
89
|
+
augmented_thirteenth: augmented thirteenth
|
|
90
|
+
doubly_diminished_thirteenth: doubly diminished thirteenth
|
|
91
|
+
doubly_augmented_thirteenth: doubly augmented thirteenth
|
|
92
|
+
minor_fourteenth: minor fourteenth
|
|
93
|
+
major_fourteenth: major fourteenth
|
|
94
|
+
diminished_fourteenth: diminished fourteenth
|
|
95
|
+
augmented_fourteenth: augmented fourteenth
|
|
96
|
+
doubly_diminished_fourteenth: doubly diminished fourteenth
|
|
97
|
+
doubly_augmented_fourteenth: doubly augmented fourteenth
|
|
98
|
+
perfect_fifteenth: perfect fifteenth
|
|
99
|
+
diminished_fifteenth: diminished fifteenth
|
|
100
|
+
augmented_fifteenth: augmented fifteenth
|
|
101
|
+
doubly_diminished_fifteenth: doubly diminished fifteenth
|
|
102
|
+
doubly_augmented_fifteenth: doubly augmented fifteenth
|
|
103
|
+
minor_sixteenth: minor sixteenth
|
|
104
|
+
major_sixteenth: major sixteenth
|
|
105
|
+
diminished_sixteenth: diminished sixteenth
|
|
106
|
+
augmented_sixteenth: augmented sixteenth
|
|
107
|
+
doubly_diminished_sixteenth: doubly diminished sixteenth
|
|
108
|
+
doubly_augmented_sixteenth: doubly augmented sixteenth
|
|
109
|
+
minor_seventeenth: minor seventeenth
|
|
110
|
+
major_seventeenth: major seventeenth
|
|
111
|
+
diminished_seventeenth: diminished seventeenth
|
|
112
|
+
augmented_seventeenth: augmented seventeenth
|
|
113
|
+
doubly_diminished_seventeenth: doubly diminished seventeenth
|
|
114
|
+
doubly_augmented_seventeenth: doubly augmented seventeenth
|
|
17
115
|
clefs:
|
|
18
116
|
alto_clef: alto clef
|
|
19
117
|
baritone_c_clef: baritone C-clef
|
|
@@ -5,8 +5,9 @@ module HeadMusic::Rudiment; end
|
|
|
5
5
|
|
|
6
6
|
# An Alteration is a symbol that modifies pitch, such as a sharp, flat, or natural.
|
|
7
7
|
# In French, sharps and flats in the key signature are called "altérations".
|
|
8
|
-
class HeadMusic::Rudiment::Alteration
|
|
8
|
+
class HeadMusic::Rudiment::Alteration < HeadMusic::Rudiment::Base
|
|
9
9
|
include Comparable
|
|
10
|
+
include HeadMusic::Named
|
|
10
11
|
|
|
11
12
|
attr_reader :identifier, :cents, :musical_symbols
|
|
12
13
|
|
|
@@ -36,6 +37,9 @@ class HeadMusic::Rudiment::Alteration
|
|
|
36
37
|
].freeze
|
|
37
38
|
|
|
38
39
|
ALTERATION_IDENTIFIERS = ALTERATION_RECORDS.map { |attributes| attributes[:identifier] }.freeze
|
|
40
|
+
SYMBOLS = ALTERATION_RECORDS.map { |attributes| attributes[:symbols].map { |symbol| [symbol[:unicode], symbol[:ascii]] } }.flatten.freeze
|
|
41
|
+
PATTERN = Regexp.union(SYMBOLS.reject { |s| s.nil? || s.empty? })
|
|
42
|
+
MATCHER = PATTERN
|
|
39
43
|
|
|
40
44
|
def self.all
|
|
41
45
|
ALTERATION_RECORDS.map { |attributes| new(attributes) }
|
|
@@ -45,12 +49,8 @@ class HeadMusic::Rudiment::Alteration
|
|
|
45
49
|
@symbols ||= all.map { |alteration| [alteration.ascii, alteration.unicode] }.flatten.reject { |s| s.nil? || s.empty? }
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
def self.matcher
|
|
49
|
-
@matcher ||= Regexp.new symbols.join("|")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
52
|
def self.symbol?(candidate)
|
|
53
|
-
candidate
|
|
53
|
+
SYMBOLS.include?(candidate)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def self.get(identifier)
|
|
@@ -67,8 +67,16 @@ class HeadMusic::Rudiment::Alteration
|
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
def name
|
|
71
|
-
|
|
70
|
+
def self.get_by_name(name)
|
|
71
|
+
all.detect { |alteration| alteration.name == name.to_s }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.from_pitched_item(input)
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def name(locale_code: I18n.locale)
|
|
79
|
+
super || identifier.to_s.tr("_", " ")
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def representions
|
|
@@ -103,6 +111,13 @@ class HeadMusic::Rudiment::Alteration
|
|
|
103
111
|
@identifier = attributes[:identifier]
|
|
104
112
|
@cents = attributes[:cents]
|
|
105
113
|
initialize_musical_symbols(attributes[:symbols])
|
|
114
|
+
initialize_localized_names
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def initialize_localized_names
|
|
118
|
+
# Initialize default English names
|
|
119
|
+
ensure_localized_name(name: identifier.to_s.tr("_", " "), locale_code: :en)
|
|
120
|
+
# Additional localized names will be loaded from locale files
|
|
106
121
|
end
|
|
107
122
|
|
|
108
123
|
def initialize_musical_symbols(list)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module HeadMusic::Rudiment; end
|
|
3
3
|
|
|
4
4
|
# A chromatic interval is the distance between two pitches measured in half-steps.
|
|
5
|
-
class HeadMusic::Rudiment::ChromaticInterval
|
|
5
|
+
class HeadMusic::Rudiment::ChromaticInterval < HeadMusic::Rudiment::Base
|
|
6
6
|
include Comparable
|
|
7
7
|
include HeadMusic::Named
|
|
8
8
|
|
|
@@ -23,11 +23,8 @@ class HeadMusic::Rudiment::ChromaticInterval
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def initialize(identifier)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
semitones = NAMES.index(candidate) || identifier.to_i
|
|
29
|
-
end
|
|
30
|
-
@semitones = semitones || identifier.to_i
|
|
26
|
+
candidate = HeadMusic::Utilities::HashKey.for(identifier).to_s
|
|
27
|
+
@semitones = NAMES.index(candidate) || identifier.to_i
|
|
31
28
|
set_name
|
|
32
29
|
end
|
|
33
30
|
|
|
@@ -4,7 +4,7 @@ require "yaml"
|
|
|
4
4
|
module HeadMusic::Rudiment; end
|
|
5
5
|
|
|
6
6
|
# A clef assigns pitches to the lines and spaces of a staff.
|
|
7
|
-
class HeadMusic::Rudiment::Clef
|
|
7
|
+
class HeadMusic::Rudiment::Clef < HeadMusic::Rudiment::Base
|
|
8
8
|
include HeadMusic::Named
|
|
9
9
|
|
|
10
10
|
RECORDS = YAML.load_file(File.expand_path("clefs.yml", __dir__)).freeze
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
# A module for music rudiments
|
|
2
2
|
module HeadMusic::Rudiment; end
|
|
3
3
|
|
|
4
|
-
# Consonance describes a category or degree of harmonic pleasantness
|
|
5
|
-
class HeadMusic::Rudiment::Consonance
|
|
6
|
-
|
|
4
|
+
# Consonance describes a category or degree of harmonic pleasantness
|
|
5
|
+
class HeadMusic::Rudiment::Consonance < HeadMusic::Rudiment::Base
|
|
6
|
+
# Detailed categories aligned with music theory
|
|
7
|
+
LEVELS = %w[
|
|
8
|
+
perfect_consonance
|
|
9
|
+
imperfect_consonance
|
|
10
|
+
contextual
|
|
11
|
+
mild_dissonance
|
|
12
|
+
harsh_dissonance
|
|
13
|
+
dissonance
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
# Constants for each consonance level
|
|
17
|
+
PERFECT_CONSONANCE = :perfect_consonance
|
|
18
|
+
IMPERFECT_CONSONANCE = :imperfect_consonance
|
|
19
|
+
CONTEXTUAL = :contextual
|
|
20
|
+
MILD_DISSONANCE = :mild_dissonance
|
|
21
|
+
HARSH_DISSONANCE = :harsh_dissonance
|
|
22
|
+
DISSONANCE = :dissonance
|
|
7
23
|
|
|
8
24
|
def self.get(name)
|
|
9
25
|
@consonances ||= {}
|
|
10
|
-
|
|
26
|
+
name_sym = name.to_sym
|
|
27
|
+
@consonances[name_sym] ||= new(name) if LEVELS.include?(name.to_s)
|
|
11
28
|
end
|
|
12
29
|
|
|
13
30
|
attr_reader :name
|
|
@@ -22,6 +39,22 @@ class HeadMusic::Rudiment::Consonance
|
|
|
22
39
|
to_s == other.to_s
|
|
23
40
|
end
|
|
24
41
|
|
|
42
|
+
# Check if this represents a consonance (perfect or imperfect)
|
|
43
|
+
def consonant?
|
|
44
|
+
[PERFECT_CONSONANCE, IMPERFECT_CONSONANCE].include?(name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check if this represents any form of dissonance
|
|
48
|
+
def dissonant?
|
|
49
|
+
[MILD_DISSONANCE, HARSH_DISSONANCE, DISSONANCE].include?(name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Contextual is special - neither strictly consonant nor dissonant
|
|
53
|
+
def contextual?
|
|
54
|
+
name == CONTEXTUAL
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Predicate methods for each level
|
|
25
58
|
LEVELS.each do |method_name|
|
|
26
59
|
define_method(:"#{method_name}?") { to_s == method_name }
|
|
27
60
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# A module for music rudiments
|
|
2
|
+
module HeadMusic::Rudiment; end
|
|
3
|
+
|
|
4
|
+
# Abstract class representing a diatonic tonal context (7-note scale system)
|
|
5
|
+
class HeadMusic::Rudiment::DiatonicContext < HeadMusic::Rudiment::TonalContext
|
|
6
|
+
def scale_type
|
|
7
|
+
raise NotImplementedError, "Subclasses must implement #scale_type"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def scale
|
|
11
|
+
@scale ||= HeadMusic::Rudiment::Scale.get(tonic_spelling, scale_type)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def key_signature
|
|
15
|
+
@key_signature ||= HeadMusic::Rudiment::KeySignature.from_scale(scale)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def relative
|
|
19
|
+
raise NotImplementedError, "Subclasses must implement #relative"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def parallel
|
|
23
|
+
raise NotImplementedError, "Subclasses must implement #parallel"
|
|
24
|
+
end
|
|
25
|
+
end
|