head_music 0.23.3 → 0.24.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -6
  3. data/Rakefile +1 -1
  4. data/head_music.gemspec +3 -2
  5. data/lib/head_music.rb +15 -1
  6. data/lib/head_music/chromatic_interval.rb +6 -1
  7. data/lib/head_music/circle.rb +30 -19
  8. data/lib/head_music/clef.rb +62 -79
  9. data/lib/head_music/content/placement.rb +1 -0
  10. data/lib/head_music/content/position.rb +1 -0
  11. data/lib/head_music/content/voice.rb +1 -0
  12. data/lib/head_music/data/clefs.yml +126 -0
  13. data/lib/head_music/data/instruments.yml +83 -0
  14. data/lib/head_music/diatonic_interval.rb +3 -0
  15. data/lib/head_music/grand_staff.rb +8 -6
  16. data/lib/head_music/instrument.rb +51 -26
  17. data/lib/head_music/interval_cycle.rb +37 -16
  18. data/lib/head_music/key_signature.rb +22 -6
  19. data/lib/head_music/locales/de.yml +63 -0
  20. data/lib/head_music/locales/en.yml +198 -0
  21. data/lib/head_music/locales/en_GB.yml +3 -0
  22. data/lib/head_music/locales/es.yml +53 -0
  23. data/lib/head_music/locales/fr.yml +57 -0
  24. data/lib/head_music/locales/it.yml +58 -0
  25. data/lib/head_music/named.rb +112 -0
  26. data/lib/head_music/octave.rb +1 -0
  27. data/lib/head_music/pitch.rb +11 -4
  28. data/lib/head_music/pitch_class.rb +2 -2
  29. data/lib/head_music/quality.rb +1 -0
  30. data/lib/head_music/reference_pitch.rb +105 -47
  31. data/lib/head_music/rhythmic_unit.rb +2 -2
  32. data/lib/head_music/scale_degree.rb +1 -0
  33. data/lib/head_music/scale_type.rb +1 -1
  34. data/lib/head_music/sign.rb +46 -14
  35. data/lib/head_music/solmization.rb +52 -0
  36. data/lib/head_music/solmizations.yml +20 -0
  37. data/lib/head_music/spelling.rb +6 -9
  38. data/lib/head_music/tuning.rb +1 -4
  39. data/lib/head_music/utilities/hash_key.rb +1 -1
  40. data/lib/head_music/version.rb +1 -1
  41. metadata +30 -6
  42. data/lib/head_music/named_rudiment.rb +0 -29
@@ -0,0 +1,3 @@
1
+ en_GB:
2
+ rudiments:
3
+ grand_staff: great staff
@@ -0,0 +1,53 @@
1
+ es:
2
+ rudiments:
3
+ clef: clave
4
+ consonance: consonancia
5
+ clefs:
6
+ alto_clef: clave contralto
7
+ baritone_c_clef: clave de barítono
8
+ baritone_clef: clave de barítono
9
+ baritone_f_clef: clave de fa en tercera
10
+ bass_clef: clave de bajo
11
+ c_clef: clave de do
12
+ choral_tenor_clef: choral tenor clef
13
+ contrabass_clef: clave de contrabajo
14
+ countertenor_clef: clave de contratenor
15
+ double_treble_clef: double clave de sol
16
+ drum_clef: clave para percusión
17
+ f_clef: clave de fa
18
+ fifth_line_c_clef: clave de do en quinta
19
+ fifth_line_f_clef: clave de fa en quinta
20
+ first_line_c_clef: clave de do en primera
21
+ first_line_g_clef: clave de sol en primera
22
+ fourth_line_c_clef: clave de do en cuarta
23
+ fourth_line_f_clef: clave de fa en cuarta
24
+ french_clef: clave de sol en primera
25
+ french_violin_clef: clave de sol en primera
26
+ g_clef: clave de sol
27
+ indefinite_pitch_clef: clave neutral
28
+ mezzo_soprano_clef: clave de mezzosoprano
29
+ neutral_clef: clave neutral
30
+ percussion_clef: clave para percusión
31
+ second_line_c_clef: clave de do en segunda
32
+ second_line_g_clef: clave de sol en segunda
33
+ soprano_clef: clave de soprano
34
+ sub_bass_clef: clave de fa en quinta
35
+ tenor_c_clef: clave de do en cuarta
36
+ tenor_clef: clave de tenor
37
+ tenor_g_clef: clave de sol en segunda
38
+ third_line_c_clef: clave de do en tercera
39
+ third_line_f_clef: clave de fa en tercera
40
+ treble_clef: clave de sol
41
+ viola_clef: clave de do
42
+ violin_clef: clave de sol
43
+ instruments:
44
+ accordian: acordeóon
45
+ alto_flute: flauta alto
46
+ alto_voice: contralto
47
+ bagpipe: gaita
48
+ baritone_voice: baritono
49
+ bass_voice: bajo
50
+ guitar: guitarra
51
+ piano: piano
52
+ viola: viola
53
+ violin: violín
@@ -0,0 +1,57 @@
1
+ fr:
2
+ rudiments:
3
+ clef: clé
4
+ clefs:
5
+ alto_clef: clé alto
6
+ baritone_c_clef: clé de baryton
7
+ baritone_clef: clé de baryton
8
+ baritone_f_clef: clé de fa 3e
9
+ bass_clef: clé de basse
10
+ c_clef: clé d'ut
11
+ choral_tenor_clef: choral tenor clef
12
+ contrabass_clef: clé de contrebasse
13
+ countertenor_clef: clé de contre-ténor
14
+ double_treble_clef: double clé de sol
15
+ drum_clef: clé de tambour
16
+ f_clef: clé de fa
17
+ fifth_line_c_clef: clé d'ut 5e
18
+ fifth_line_f_clef: clé de fa 5e
19
+ first_line_c_clef: clé d'ut 1re
20
+ first_line_g_clef: clé de sol 1re
21
+ fourth_line_c_clef: clé d'ut 4e
22
+ fourth_line_f_clef: clé de fa 4e
23
+ french_clef: clé de sol 1re
24
+ french_violin_clef: clé de sol 1re
25
+ g_clef: clé de sol
26
+ indefinite_pitch_clef: clé à pas indéfini
27
+ mezzo_soprano_clef: clé de mezzo-soprano
28
+ neutral_clef: clé neutre
29
+ percussion_clef: clé de percussion
30
+ second_line_c_clef: clé d'ut 2e
31
+ second_line_g_clef: clé de sol 2e
32
+ soprano_clef: clé de soprano
33
+ sub_bass_clef: clé de fa 5e
34
+ tenor_c_clef: clé d'ut 4e
35
+ tenor_clef: clé de ténor
36
+ tenor_g_clef: clé de sol 2e
37
+ third_line_c_clef: clé d'ut 3e
38
+ third_line_f_clef: clé de fa 3e
39
+ treble_clef: clé de sol
40
+ viola_clef: clé d'ut
41
+ violin_clef: clé de sol
42
+ instruments:
43
+ accordian: accordéon
44
+ alto_flute: flûte alto
45
+ alto_voice: alto
46
+ bagpipe: cornemuse
47
+ baritone_voice: baryton
48
+ bass_clarinet: clarinette basse
49
+ bass_voice: basse
50
+ piano: piano
51
+ viola: alto
52
+ violin: violon
53
+ instrument_voices:
54
+ alto: alto
55
+ baritone: baryton
56
+ bass: basse
57
+ guitar: guitare
@@ -0,0 +1,58 @@
1
+ it:
2
+ rudiments:
3
+ clef: chiave
4
+ consonance: consonanza
5
+ clefs:
6
+ alto_clef: chiave di contralto
7
+ baritone_c_clef: chiave di baritono
8
+ baritone_clef: chiave di baritono
9
+ baritone_f_clef: chiave di baritono
10
+ bass_clef: chiave di basso
11
+ c_clef: chiave di do
12
+ choral_tenor_clef: chiave di sol in ottava
13
+ contrabass_clef: chiave di contrabbasso
14
+ countertenor_clef: chiave di controtenore
15
+ double_treble_clef: chiave di sol in ottava
16
+ drum_clef: chiave di percussione
17
+ f_clef: chiave di fa
18
+ fifth_line_c_clef: chiave di do sulla 5ª linea
19
+ fifth_line_f_clef: chiave di fa sulla 5ª linea
20
+ first_line_c_clef: chiave di do sulla 1ª linea
21
+ first_line_g_clef: chiave di sol sulla 1ª linea
22
+ fourth_line_c_clef: chiave di do sulla 4ª linea
23
+ fourth_line_f_clef: chiave di fa sulla 4ª linea
24
+ french_clef: chiave di violino francese
25
+ french_violin_clef: chiave di violino francese
26
+ g_clef: chiave di sol
27
+ indefinite_pitch_clef: chiave neutra
28
+ mezzo_soprano_clef: chiave di mezzosoprano
29
+ neutral_clef: chiave neutra
30
+ percussion_clef: chiave di percussione
31
+ second_line_c_clef: chiave di do sulla 2ª linea
32
+ second_line_g_clef: chiave di sol sulla 2ª linea
33
+ soprano_clef: chiave di soprano
34
+ sub_bass_clef: chiave di subbasso
35
+ tenor_c_clef: chiave di tenore
36
+ tenor_clef: chiave di tenore
37
+ tenor_g_clef: chiave di sol in ottava
38
+ third_line_c_clef: chiave di do sulla 3ª linea
39
+ third_line_f_clef: chiave di fa sulla 3ª linea
40
+ treble_clef: chiave di violino
41
+ viola_clef: chiave di viola
42
+ violin_clef: chiave di violino
43
+ instruments:
44
+ accordian: fisarmonica
45
+ alto_flute: flauto contralto
46
+ alto_voice: contralto
47
+ bagpipe: cornamusa
48
+ baritone_voice: baritono
49
+ bass_voice: basso
50
+ guitar: chitarra
51
+ piccolo: ottavino
52
+ piano: piano
53
+ viola: viola
54
+ violin: violino
55
+ instrument_voices:
56
+ alto: contralto
57
+ baritone: baritono
58
+ bass: basso
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NameRudiment is a module to be included in classes whose instances may be identified by name.
4
+ module HeadMusic::Named
5
+ delegate :to_s, to: :name
6
+
7
+ # Locale encapsulates a language and optional region or country.
8
+ class Locale
9
+ DEFAULT_CODE = :en_US
10
+
11
+ attr_reader :language, :region
12
+
13
+ def self.default_locale
14
+ get(DEFAULT_CODE)
15
+ end
16
+
17
+ def self.get(code)
18
+ @locales ||= {}
19
+ parts = code.to_s.split(/[_\-]/)
20
+ language = parts[0].downcase
21
+ region = parts[1]&.upcase
22
+ key = [language, region].compact.join('_').to_sym
23
+ @locales[key] ||= new(language: language, region: region)
24
+ end
25
+
26
+ def initialize(language:, region: nil)
27
+ @language = language
28
+ @region = region
29
+ end
30
+
31
+ def code
32
+ @code ||= [@language, @region].compact.join('_')
33
+ end
34
+
35
+ private_class_method :new
36
+ end
37
+
38
+ # A LocalizedName is the name of a rudiment in a locale.
39
+ class LocalizedName
40
+ attr_reader :locale, :name, :abbreviation
41
+
42
+ delegate :code, to: :locale, prefix: true
43
+ delegate :language, :region, to: :locale
44
+
45
+ def initialize(name:, locale_code: Locale::DEFAULT_CODE, abbreviation: nil)
46
+ @name = name
47
+ @locale = Locale.get(locale_code)
48
+ @abbreviation = abbreviation
49
+ end
50
+ end
51
+
52
+ # Adds .get_by_name to the including class.
53
+ module ClassMethods
54
+ def get_by_name(name)
55
+ name = name.to_s
56
+ @instances ||= {}
57
+ key = HeadMusic::Utilities::HashKey.for(name)
58
+ @instances[key] ||= new(name)
59
+ end
60
+ end
61
+
62
+ def self.included(base)
63
+ base.extend(ClassMethods)
64
+ end
65
+
66
+ attr_reader :name_key, :alias_name_keys
67
+
68
+ def name=(name)
69
+ ensure_localized_name(name: name)
70
+ end
71
+
72
+ def name(locale_code: Locale::DEFAULT_CODE)
73
+ localized_name(locale_code: locale_code)&.name
74
+ end
75
+
76
+ def localized_name(locale_code: Locale::DEFAULT_CODE)
77
+ locale = Locale.get(locale_code || Locale::DEFAULT_CODE)
78
+ localized_name_in_matching_locale(locale) ||
79
+ localized_name_in_locale_matching_language(locale) ||
80
+ localized_name_in_default_locale ||
81
+ localized_names.first
82
+ end
83
+
84
+ def ensure_localized_name(name:, locale_code: Locale::DEFAULT_CODE, abbreviation: nil)
85
+ @localized_names ||= []
86
+ @localized_names << LocalizedName.new(name: name, locale_code: locale_code, abbreviation: abbreviation)
87
+ @localized_names.uniq!
88
+ end
89
+
90
+ # Returns an array of LocalizedName instances that are synonymous with the name.
91
+ def localized_names
92
+ @localized_names ||= []
93
+ end
94
+
95
+ private
96
+
97
+ def localized_name_in_matching_locale(locale)
98
+ localized_names.detect { |candidate| candidate.locale_code == locale.code }
99
+ end
100
+
101
+ def localized_name_in_locale_matching_language(locale)
102
+ localized_names.detect { |candidate| candidate.language == locale.language }
103
+ end
104
+
105
+ def localized_name_in_default_locale
106
+ localized_names.detect { |name| name.locale_code == Locale::DEFAULT_CODE }
107
+ end
108
+
109
+ def hash_key
110
+ HeadMusic::Utilities::HashKey.for(name)
111
+ end
112
+ end
@@ -31,6 +31,7 @@ class HeadMusic::Octave
31
31
  end
32
32
 
33
33
  attr_reader :number
34
+
34
35
  delegate :to_i, :to_s, to: :number
35
36
 
36
37
  def initialize(number)
@@ -21,8 +21,17 @@ class HeadMusic::Pitch
21
21
  delegate :enharmonic_equivalent?, :enharmonic?, to: :enharmonic_equivalence
22
22
  delegate :octave_equivalent?, to: :octave_equivalence
23
23
 
24
+ # Fetches a pitch identified by the information passed in.
25
+ #
26
+ # Accepts:
27
+ # - a Pitch instance
28
+ # - a PitchClass instance
29
+ # - a name string, such as 'Ab4'
30
+ # - a number corresponding to the midi note number
24
31
  def self.get(value)
25
- from_pitch_class(value) || from_name(value) || from_number(value)
32
+ from_pitch_class(value) ||
33
+ from_name(value) ||
34
+ from_number(value)
26
35
  end
27
36
 
28
37
  def self.middle_c
@@ -48,9 +57,7 @@ class HeadMusic::Pitch
48
57
  def self.from_number(number)
49
58
  return nil unless number == number.to_i
50
59
 
51
- spelling = HeadMusic::Spelling.from_number(number)
52
- octave = (number.to_i / 12) - 1
53
- fetch_or_create(spelling, octave)
60
+ fetch_or_create(HeadMusic::Spelling.from_number(number), (number.to_i / 12) - 1)
54
61
  end
55
62
 
56
63
  def self.from_number_and_letter(number, letter_name)
@@ -7,8 +7,8 @@ class HeadMusic::PitchClass
7
7
  attr_reader :number
8
8
  attr_reader :spelling
9
9
 
10
- SHARP_SPELLINGS = %w[C C# D D# E F F# G G# A A# B].freeze
11
- FLAT_SPELLINGS = %w[C Db D Eb E F Gb G Ab A Bb B].freeze
10
+ SHARP_SPELLINGS = %w[C C D D E F F G G A A B].freeze
11
+ FLAT_SPELLINGS = %w[C D♭ D E♭ E F G♭ G A♭ A B♭ B].freeze
12
12
  INTEGER_NOTATION = %w[0 1 2 3 4 5 6 7 8 9 t e].freeze
13
13
 
14
14
  def self.get(identifier)
@@ -44,6 +44,7 @@ class HeadMusic::Quality
44
44
  end
45
45
 
46
46
  attr_reader :name
47
+
47
48
  delegate :to_s, to: :name
48
49
 
49
50
  def initialize(name)
@@ -3,48 +3,77 @@
3
3
  # A reference pitch has a pitch and a frequency
4
4
  # With no arguments, it assumes that A4 = 440.0 Hz
5
5
  class HeadMusic::ReferencePitch
6
- include HeadMusic::NamedRudiment
6
+ include HeadMusic::Named
7
7
 
8
8
  DEFAULT_PITCH_NAME = 'A4'
9
9
  DEFAULT_FREQUENCY = 440.0
10
+ DEFAULT_REFERENCE_PITCH_NAME = 'A440'
10
11
 
11
12
  NAMED_REFERENCE_PITCHES = [
12
- { name: 'Baroque', pitch: 'A4', frequency: 415.0 },
13
- { name: 'Classical', pitch: 'A4', frequency: 430.0 },
14
- { name: 'Scientific', pitch: 'C4', frequency: 256.0 },
15
- { name: 'Verdi', pitch: 'A4', frequency: 432.0 }, # Pythagorean tuning
16
- { name: 'French', pitch: 'A4', frequency: 435.0 },
17
- { name: 'New Philharmonic', pitch: 'A4', frequency: 439.0 },
18
- { name: 'A440', pitch: 'A4', frequency: 440.0 },
19
- { name: 'Sydney Symphony Orchestra', pitch: 'A4', frequency: 441.0 },
20
- { name: 'New York Philharmonic', pitch: 'A4', frequency: 442.0 },
21
- { name: 'Berlin Philharmonic', pitch: 'A4', frequency: 443.0 },
22
- { name: 'Boston Symphony Orchestra', pitch: 'A4', frequency: 444.0 },
23
- { name: 'Old Philharmonic', pitch: 'A4', frequency: 452.4 },
24
- { name: 'Chorton', pitch: 'A4', frequency: 466.0 },
13
+ {
14
+ frequency: 415.0,
15
+ key: :baroque,
16
+ alias_keys: %i[chamber_tone],
17
+ },
18
+ {
19
+ frequency: 430.0,
20
+ key: :classical,
21
+ alias_keys: %i[haydn mozart],
22
+ },
23
+ {
24
+ pitch: 'C4',
25
+ frequency: 256.0,
26
+ key: :scientific,
27
+ alias_keys: %i[philosophical sauveur],
28
+ },
29
+ {
30
+ frequency: 432.0,
31
+ tuning: 'Pythagorean',
32
+ key: :verdi,
33
+ },
34
+ {
35
+ frequency: 435.0,
36
+ key: :french,
37
+ alias_keys: %i[continental international],
38
+ },
39
+ {
40
+ frequency: 439.0,
41
+ key: :new_philharmonic,
42
+ alias_keys: %i[low],
43
+ },
44
+ {
45
+ frequency: 440.0,
46
+ key: :a440,
47
+ alias_keys: %i[concert stuttgart scheibler iso_16],
48
+ },
49
+ {
50
+ frequency: 441.0,
51
+ key: :sydney_symphony_orchestra,
52
+ },
53
+ {
54
+ frequency: 442.0,
55
+ key: :new_york_philharmonic,
56
+ },
57
+ {
58
+ frequency: 443.0,
59
+ key: :berlin_philharmonic,
60
+ },
61
+ {
62
+ frequency: 444.0,
63
+ key: :boston_symphony_orchestra,
64
+ },
65
+ {
66
+ frequency: 452.4,
67
+ key: :old_philharmonic,
68
+ alias_keys: %i[high],
69
+ },
70
+ {
71
+ frequency: 466.0,
72
+ key: :chorton,
73
+ alias_keys: %i[choir],
74
+ },
25
75
  ].freeze
26
76
 
27
- ALIASES = {
28
- kammerton: :baroque,
29
- chamber: :baroque,
30
- haydn: :classical,
31
- mozart: :classical,
32
- philosophic: :scientific,
33
- sauveur: :scientific,
34
- schiller: :scientific,
35
- continental: :french,
36
- international: :french,
37
- low: :new_philharmonic,
38
- concert: :a440,
39
- stuttgart: :a440,
40
- scheibler: :a440,
41
- iso_16: :a440,
42
- high: :old_philharmonic,
43
- choir: :chorton,
44
- }.freeze
45
-
46
- NAMED_REFERENCE_PITCH_NAMES = NAMED_REFERENCE_PITCHES.map { |pitch_data| pitch_data[:name] }
47
-
48
77
  attr_reader :pitch, :frequency
49
78
 
50
79
  def self.get(name)
@@ -52,14 +81,11 @@ class HeadMusic::ReferencePitch
52
81
  get_by_name(name)
53
82
  end
54
83
 
55
- def initialize(name = 'A440')
56
- @name = name.to_s
57
- reference_pitch_data = NAMED_REFERENCE_PITCHES.detect do |candidate|
58
- candidate_name_key = HeadMusic::Utilities::HashKey.for(candidate[:name])
59
- [candidate_name_key, candidate_name_key.to_s.delete('_').to_sym].include?(normalized_name)
60
- end || {}
61
- @pitch = HeadMusic::Pitch.get(reference_pitch_data.fetch(:pitch, DEFAULT_PITCH_NAME))
62
- @frequency = reference_pitch_data.fetch(:frequency, DEFAULT_FREQUENCY)
84
+ def initialize(name = DEFAULT_REFERENCE_PITCH_NAME)
85
+ record = named_reference_pitch_record_for_name(name)
86
+ @pitch = HeadMusic::Pitch.get(record.fetch(:pitch, DEFAULT_PITCH_NAME))
87
+ @frequency = record.fetch(:frequency, DEFAULT_FREQUENCY)
88
+ initialize_keys_from_record(record)
63
89
  end
64
90
 
65
91
  def description
@@ -78,10 +104,42 @@ class HeadMusic::ReferencePitch
78
104
 
79
105
  private
80
106
 
81
- def normalized_name
82
- @normalized_name ||= begin
83
- key = HeadMusic::Utilities::HashKey.for(name.to_s.gsub(/\W?(pitch|tuning|tone)/, ''))
84
- ALIASES[key] || key
107
+ def named_reference_pitch_record_for_name(name)
108
+ key = HeadMusic::Utilities::HashKey.for(normalized_name_string(name))
109
+ NAMED_REFERENCE_PITCHES.detect do |record|
110
+ name_keys_from_record(record).include?(key)
111
+ end || named_reference_pitch_record_for_name(DEFAULT_REFERENCE_PITCH_NAME)
112
+ end
113
+
114
+ def name_keys_from_record(record)
115
+ names_from_record(record).map { |name| HeadMusic::Utilities::HashKey.for(name) }
116
+ end
117
+
118
+ def names_from_record(record)
119
+ name_keys = ([record[:key]] + [record[:alias_keys]]).flatten.compact.uniq
120
+ normalized_translations_for_keys(name_keys)
121
+ end
122
+
123
+ def normalized_name_string(name)
124
+ name.gsub(' pitch', '').gsub(' tone', '').gsub(' tuning', '')
125
+ end
126
+
127
+ def initialize_keys_from_record(record)
128
+ @key = record[:key]
129
+ @alias_keys = [record[:alias_keys]].flatten.compact
130
+ end
131
+
132
+ def normalized_translations_for_keys(name_keys)
133
+ name_and_alias_translations_for_keys(name_keys).map do |name|
134
+ normalized_name_string(name)
85
135
  end
86
136
  end
137
+
138
+ def name_and_alias_translations_for_keys(name_keys)
139
+ name_keys.map do |name_key|
140
+ I18n.config.available_locales.map do |locale_code|
141
+ I18n.translate(name_key, scope: :reference_pitches, locale: locale_code)
142
+ end.flatten.uniq.compact
143
+ end.flatten.uniq.compact
144
+ end
87
145
  end