head_music 0.24.0 → 0.24.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,60 +7,85 @@ class HeadMusic::ReferencePitch
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 },
25
- ].freeze
26
-
27
- ALIAS_DATA = [
28
- { key: :baroque, name: 'Kammerton' },
29
- { key: :classical, name: 'Haydn' },
30
- { key: :classical, name: 'Mozart' },
31
- { key: :scientific, name: 'philosophical' },
32
- { key: :scientific, name: 'Sauveur' },
33
- { key: :scientific, name: 'Schiller' },
34
- { key: :french, name: 'continental' },
35
- { key: :french, name: 'international' },
36
- { key: :new_philharmonic, name: 'low' },
37
- { key: :old_philharmonic, name: 'high' },
38
- { key: :a440, name: 'concert' },
39
- { key: :a440, name: 'Stuttgart' },
40
- { key: :a440, name: 'Scheibler' },
41
- { key: :a440, name: 'ISO 16' },
42
- { key: :chorton, name: 'choir' },
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
+ },
43
75
  ].freeze
44
76
 
45
77
  attr_reader :pitch, :frequency
46
78
 
47
- def self.aliases
48
- @aliases ||= ALIAS_DATA.map { |attributes| HeadMusic::Named::Alias.new(attributes) }
49
- end
50
-
51
79
  def self.get(name)
52
80
  return name if name.is_a?(self)
53
81
  get_by_name(name)
54
82
  end
55
83
 
56
- def initialize(name = 'A440')
57
- @name = name.to_s
58
- reference_pitch_data = NAMED_REFERENCE_PITCHES.detect do |candidate|
59
- candidate_name_key = HeadMusic::Utilities::HashKey.for(candidate[:name])
60
- [candidate_name_key, candidate_name_key.to_s.delete('_').to_sym].include?(normalized_key)
61
- end || {}
62
- @pitch = HeadMusic::Pitch.get(reference_pitch_data.fetch(:pitch, DEFAULT_PITCH_NAME))
63
- @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)
64
89
  end
65
90
 
66
91
  def description
@@ -79,12 +104,42 @@ class HeadMusic::ReferencePitch
79
104
 
80
105
  private
81
106
 
82
- def normalized_key
83
- @normalized_key ||= begin
84
- key = HeadMusic::Utilities::HashKey.for(name.to_s.gsub(/\W?(pitch|tuning|tone)/, ''))
85
- HeadMusic::ReferencePitch.aliases.detect do |alias_data|
86
- HeadMusic::Utilities::HashKey.for(alias_data.name) == key
87
- end&.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)
88
135
  end
89
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
90
145
  end
@@ -27,7 +27,7 @@ class HeadMusic::RhythmicUnit
27
27
  end
28
28
 
29
29
  def initialize(canonical_name)
30
- @name = canonical_name
30
+ self.name = canonical_name
31
31
  @numerator = 2**numerator_exponent
32
32
  @denominator = 2**denominator_exponent
33
33
  end
@@ -28,7 +28,7 @@ class HeadMusic::ScaleType
28
28
  vii: [:locrian],
29
29
  }.freeze
30
30
 
31
- CHROMATIC = [H, H, H, H, H, H, H, H, H, H, H, H].freeze
31
+ CHROMATIC = ([H] * 12)
32
32
 
33
33
  MINOR_PENTATONIC = [3, 2, 2, 3, 2].freeze
34
34
 
@@ -6,22 +6,37 @@ require 'head_music/musical_symbol'
6
6
  class HeadMusic::Sign
7
7
  include Comparable
8
8
 
9
- attr_reader :identifier, :cents, :musical_symbol
9
+ attr_reader :identifier, :cents, :musical_symbols
10
10
 
11
11
  delegate :ascii, :unicode, :html_entity, to: :musical_symbol
12
12
 
13
- SIGN_DATA = [
14
- { identifier: :sharp, ascii: '#', unicode: '♯', html_entity: '♯', cents: 100 },
15
- { identifier: :flat, ascii: 'b', unicode: '♭', html_entity: '♭', cents: -100 },
16
- { identifier: :natural, ascii: '', unicode: '', html_entity: '♮', cents: 0 },
17
- { identifier: :double_sharp, ascii: 'x', unicode: '𝄪', html_entity: '𝄪', cents: 200 },
18
- { identifier: :double_flat, ascii: 'bb', unicode: '𝄫', html_entity: '𝄫', cents: -200 },
13
+ SIGN_RECORDS = [
14
+ {
15
+ identifier: :sharp, cents: 100,
16
+ symbols: [{ ascii: '#', unicode: '', html_entity: '♯' }],
17
+ },
18
+ {
19
+ identifier: :flat, cents: -100,
20
+ symbols: [{ ascii: 'b', unicode: '♭', html_entity: '♭' }],
21
+ },
22
+ {
23
+ identifier: :natural, cents: 0,
24
+ symbols: [{ ascii: '', unicode: '♮', html_entity: '♮' }],
25
+ },
26
+ {
27
+ identifier: :double_sharp, cents: 200,
28
+ symbols: [{ ascii: 'x', unicode: '𝄪', html_entity: '𝄪' }],
29
+ },
30
+ {
31
+ identifier: :double_flat, cents: -200,
32
+ symbols: [{ ascii: 'bb', unicode: '𝄫', html_entity: '𝄫' }],
33
+ },
19
34
  ].freeze
20
35
 
21
- SIGN_IDENTIFIERS = SIGN_DATA.map { |attributes| attributes[:identifier] }.freeze
36
+ SIGN_IDENTIFIERS = SIGN_RECORDS.map { |attributes| attributes[:identifier] }.freeze
22
37
 
23
38
  def self.all
24
- SIGN_DATA.map { |attributes| new(attributes) }
39
+ SIGN_RECORDS.map { |attributes| new(attributes) }
25
40
  end
26
41
 
27
42
  def self.symbols
@@ -76,16 +91,26 @@ class HeadMusic::Sign
76
91
  cents <=> other.cents
77
92
  end
78
93
 
94
+ def musical_symbol
95
+ musical_symbols.first
96
+ end
97
+
79
98
  private
80
99
 
81
100
  def initialize(attributes)
82
101
  @identifier = attributes[:identifier]
83
102
  @cents = attributes[:cents]
84
- @musical_symbol = HeadMusic::MusicalSymbol.new(
85
- unicode: attributes[:unicode],
86
- ascii: attributes[:ascii],
87
- html_entity: attributes[:html_entity]
88
- )
103
+ initialize_musical_symbols(attributes[:symbols])
104
+ end
105
+
106
+ def initialize_musical_symbols(list)
107
+ @musical_symbols = (list || []).map do |record|
108
+ HeadMusic::MusicalSymbol.new(
109
+ unicode: record[:unicode],
110
+ ascii: record[:ascii],
111
+ html_entity: record[:html_entity]
112
+ )
113
+ end
89
114
  end
90
115
 
91
116
  private_class_method :new
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A scale degree is a number indicating the ordinality of the spelling in the key signature.
4
+ # TODO: Rewrite to accept a tonal_center and a scale type.
5
+ class HeadMusic::Solmization
6
+ include HeadMusic::Named
7
+
8
+ DEFAULT_SOLMIZATION = 'solfège'
9
+
10
+ RECORDS = YAML.load_file(File.expand_path('solmizations.yml', __dir__)).freeze
11
+
12
+ attr_reader :syllables
13
+
14
+ def self.get(identifier = nil)
15
+ get_by_name(identifier)
16
+ end
17
+
18
+ private_class_method :new
19
+
20
+ private
21
+
22
+ def initialize(name = nil)
23
+ name = nil if name.empty?
24
+ name ||= DEFAULT_SOLMIZATION
25
+ record = record_for_name(name)
26
+ if record
27
+ initialize_data_from_record(record)
28
+ else
29
+ self.name = name
30
+ end
31
+ end
32
+
33
+ def record_for_name(name)
34
+ key = HeadMusic::Utilities::HashKey.for(name)
35
+ RECORDS.detect do |record|
36
+ name_strings = record[:localized_names].map { |localized_name| localized_name[:name] }
37
+ name_keys = name_strings.map { |name_string| HeadMusic::Utilities::HashKey.for(name_string) }
38
+ name_keys.include?(key)
39
+ end
40
+ end
41
+
42
+ def initialize_data_from_record(record)
43
+ @syllables = record[:syllables]
44
+ initialize_localized_names(record[:localized_names])
45
+ end
46
+
47
+ def initialize_localized_names(list)
48
+ @localized_names = (list || []).map do |name_attributes|
49
+ HeadMusic::Named::LocalizedName.new(name_attributes.slice(:name, :locale_code, :abbreviation))
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ ---
2
+ - :syllables:
3
+ - do
4
+ - re
5
+ - mi
6
+ - fa
7
+ - sol
8
+ - la
9
+ - ti
10
+ :localized_names:
11
+ - :name: solfège
12
+ - :name: solfège
13
+ :locale_code: en
14
+ - :name: solfège
15
+ :locale_code: fr
16
+ - :name: solfeggio
17
+ :locale_code: it
18
+ - :name: sol-fa
19
+ - :name: solfa
20
+ - :name: solfeo
@@ -6,6 +6,6 @@ module HeadMusic::Utilities; end
6
6
  # Util for converting an object to a consistent hash key
7
7
  module HeadMusic::Utilities::HashKey
8
8
  def self.for(identifier)
9
- identifier.to_s.downcase.gsub(/\W+/, '_').to_sym
9
+ I18n.transliterate(identifier.to_s).downcase.gsub(/\W+/, '_').to_sym
10
10
  end
11
11
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HeadMusic
4
- VERSION = '0.24.0'
4
+ VERSION = '0.24.1'
5
5
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-28 00:00:00.000000000 Z
11
+ date: 2020-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: i18n
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -131,6 +145,8 @@ files:
131
145
  - lib/head_music/content/position.rb
132
146
  - lib/head_music/content/rhythmic_value.rb
133
147
  - lib/head_music/content/voice.rb
148
+ - lib/head_music/data/clefs.yml
149
+ - lib/head_music/data/instruments.yml
134
150
  - lib/head_music/diatonic_interval.rb
135
151
  - lib/head_music/grand_staff.rb
136
152
  - lib/head_music/harmonic_interval.rb
@@ -138,6 +154,12 @@ files:
138
154
  - lib/head_music/interval_cycle.rb
139
155
  - lib/head_music/key_signature.rb
140
156
  - lib/head_music/letter_name.rb
157
+ - lib/head_music/locales/de.yml
158
+ - lib/head_music/locales/en.yml
159
+ - lib/head_music/locales/en_GB.yml
160
+ - lib/head_music/locales/es.yml
161
+ - lib/head_music/locales/fr.yml
162
+ - lib/head_music/locales/it.yml
141
163
  - lib/head_music/melodic_interval.rb
142
164
  - lib/head_music/meter.rb
143
165
  - lib/head_music/motion.rb
@@ -158,6 +180,8 @@ files:
158
180
  - lib/head_music/scale_degree.rb
159
181
  - lib/head_music/scale_type.rb
160
182
  - lib/head_music/sign.rb
183
+ - lib/head_music/solmization.rb
184
+ - lib/head_music/solmizations.yml
161
185
  - lib/head_music/sonority.rb
162
186
  - lib/head_music/spelling.rb
163
187
  - lib/head_music/staff.rb