head_music 0.23.2 → 0.24.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) 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 +63 -26
  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 +36 -0
  14. data/lib/head_music/diatonic_interval.rb +3 -0
  15. data/lib/head_music/grand_staff.rb +11 -6
  16. data/lib/head_music/instrument.rb +40 -27
  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/musical_symbol.rb +1 -1
  26. data/lib/head_music/named.rb +112 -0
  27. data/lib/head_music/octave.rb +1 -0
  28. data/lib/head_music/pitch.rb +11 -4
  29. data/lib/head_music/pitch_class.rb +2 -2
  30. data/lib/head_music/quality.rb +1 -0
  31. data/lib/head_music/reference_pitch.rb +105 -47
  32. data/lib/head_music/rhythmic_unit.rb +2 -2
  33. data/lib/head_music/scale_degree.rb +1 -0
  34. data/lib/head_music/scale_type.rb +1 -1
  35. data/lib/head_music/sign.rb +46 -14
  36. data/lib/head_music/solmization.rb +52 -0
  37. data/lib/head_music/solmizations.yml +20 -0
  38. data/lib/head_music/spelling.rb +6 -9
  39. data/lib/head_music/tuning.rb +1 -4
  40. data/lib/head_music/utilities/hash_key.rb +1 -1
  41. data/lib/head_music/version.rb +1 -1
  42. metadata +30 -6
  43. data/lib/head_music/named_rudiment.rb +0 -29
@@ -2,7 +2,7 @@
2
2
 
3
3
  # A rhythmic unit is a rudiment of duration consisting of doublings and divisions of a whole note.
4
4
  class HeadMusic::RhythmicUnit
5
- include HeadMusic::NamedRudiment
5
+ include HeadMusic::Named
6
6
 
7
7
  MULTIPLES = ['whole', 'double whole', 'longa', 'maxima'].freeze
8
8
  FRACTIONS = [
@@ -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
@@ -8,6 +8,7 @@ class HeadMusic::ScaleDegree
8
8
  NAME_FOR_DIATONIC_DEGREE = [nil, 'tonic', 'supertonic', 'mediant', 'subdominant', 'dominant', 'submediant'].freeze
9
9
 
10
10
  attr_reader :key_signature, :spelling
11
+
11
12
  delegate :scale, to: :key_signature
12
13
  delegate :scale_type, to: :scale
13
14
 
@@ -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,16 +6,37 @@ require 'head_music/musical_symbol'
6
6
  class HeadMusic::Sign
7
7
  include Comparable
8
8
 
9
- attr_reader :identifier, :cents, :symbol
9
+ attr_reader :identifier, :cents, :musical_symbols
10
+
11
+ delegate :ascii, :unicode, :html_entity, to: :musical_symbol
12
+
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
+ },
34
+ ].freeze
35
+
36
+ SIGN_IDENTIFIERS = SIGN_RECORDS.map { |attributes| attributes[:identifier] }.freeze
10
37
 
11
38
  def self.all
12
- @all ||= [
13
- new(identifier: :sharp, ascii: '#', unicode: '♯', html_entity: '♯', cents: 100),
14
- new(identifier: :flat, ascii: 'b', unicode: '♭', html_entity: '♭', cents: -100),
15
- new(identifier: :natural, ascii: '', unicode: '♮', html_entity: '♮', cents: 0),
16
- new(identifier: :double_sharp, ascii: '##', unicode: '𝄪', html_entity: '𝄪', cents: 200),
17
- new(identifier: :double_flat, ascii: 'bb', unicode: '𝄫', html_entity: '𝄫', cents: -200),
18
- ]
39
+ SIGN_RECORDS.map { |attributes| new(attributes) }
19
40
  end
20
41
 
21
42
  def self.symbols
@@ -57,6 +78,10 @@ class HeadMusic::Sign
57
78
  cents / 100.0
58
79
  end
59
80
 
81
+ SIGN_IDENTIFIERS.each do |key|
82
+ define_method(:"#{key}?") { identifier == key }
83
+ end
84
+
60
85
  def to_s
61
86
  unicode
62
87
  end
@@ -66,19 +91,26 @@ class HeadMusic::Sign
66
91
  cents <=> other.cents
67
92
  end
68
93
 
69
- delegate :ascii, :html_entity, :unicode, to: :symbol
94
+ def musical_symbol
95
+ musical_symbols.first
96
+ end
70
97
 
71
98
  private
72
99
 
73
100
  def initialize(attributes)
74
101
  @identifier = attributes[:identifier]
75
102
  @cents = attributes[:cents]
103
+ initialize_musical_symbols(attributes[:symbols])
104
+ end
76
105
 
77
- @symbol = HeadMusic::Symbol.new(
78
- unicode: attributes[:unicode],
79
- ascii: attributes[:ascii],
80
- html_entity: attributes[:html_entity]
81
- )
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
82
114
  end
83
115
 
84
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
@@ -4,7 +4,7 @@
4
4
  # Composite of a LetterName and an optional Sign.
5
5
  # Does not include the octave. See Pitch for that.
6
6
  class HeadMusic::Spelling
7
- MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(\-?\d+)?\s*$/i.freeze
7
+ MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(-?\d+)?\s*$/i.freeze
8
8
 
9
9
  attr_reader :pitch_class
10
10
  attr_reader :letter_name
@@ -14,6 +14,7 @@ class HeadMusic::Spelling
14
14
  delegate :to_i, to: :pitch_class_number
15
15
  delegate :series_ascending, :series_descending, to: :letter_name, prefix: true
16
16
  delegate :enharmonic?, to: :enharmonic_equivalence
17
+ delegate :sharp?, :flat?, :double_sharp?, :double_flat?, to: :sign, allow_nil: true
17
18
 
18
19
  def self.get(identifier)
19
20
  return identifier if identifier.is_a?(HeadMusic::Spelling)
@@ -73,14 +74,6 @@ class HeadMusic::Spelling
73
74
  name
74
75
  end
75
76
 
76
- def sharp?
77
- sign && sign == '#'
78
- end
79
-
80
- def flat?
81
- sign && sign == 'b'
82
- end
83
-
84
77
  def ==(other)
85
78
  other = HeadMusic::Spelling.get(other)
86
79
  to_s == other.to_s
@@ -90,6 +83,10 @@ class HeadMusic::Spelling
90
83
  HeadMusic::Scale.get(self, scale_type_name)
91
84
  end
92
85
 
86
+ def natural?
87
+ !sign || sign.natural?
88
+ end
89
+
93
90
  private_class_method :new
94
91
 
95
92
  private
@@ -12,12 +12,9 @@ class HeadMusic::Tuning
12
12
  end
13
13
 
14
14
  def frequency_for(pitch)
15
- pitch = HeadMusic::Pitch.get(pitch) unless pitch.is_a?(HeadMusic::Pitch)
15
+ pitch = HeadMusic::Pitch.get(pitch)
16
16
  reference_pitch_frequency * (2**(1.0 / 12))**(pitch - reference_pitch.pitch).semitones
17
17
  end
18
18
  end
19
19
 
20
20
  # TODO: other tunings
21
- # Create website that hosts videos on theory and history, handy charts, etc.
22
- # one of those charts can be a frequency table in various tunings
23
- # maybe show pythagorean commas and such. or cents sharp or flat relative to either equal temperment or just intonation
@@ -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.23.2'
4
+ VERSION = '0.24.2'
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.23.2
4
+ version: 0.24.2
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-25 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,11 +154,17 @@ 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
144
166
  - lib/head_music/musical_symbol.rb
145
- - lib/head_music/named_rudiment.rb
167
+ - lib/head_music/named.rb
146
168
  - lib/head_music/octave.rb
147
169
  - lib/head_music/pitch.rb
148
170
  - lib/head_music/pitch/enharmonic_equivalence.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
@@ -216,7 +240,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
216
240
  requirements:
217
241
  - - ">="
218
242
  - !ruby/object:Gem::Version
219
- version: 2.4.0
243
+ version: '2.5'
220
244
  required_rubygems_version: !ruby/object:Gem::Requirement
221
245
  requirements:
222
246
  - - ">="
@@ -1,29 +0,0 @@
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::NamedRudiment
5
- attr_reader :name
6
- delegate :to_s, to: :name
7
-
8
- def initialize(name)
9
- @name = name.to_s
10
- end
11
-
12
- def hash_key
13
- HeadMusic::Utilities::HashKey.for(name)
14
- end
15
-
16
- def self.included(base)
17
- base.extend(ClassMethods)
18
- end
19
-
20
- # Adds .get_by_name to the including class.
21
- module ClassMethods
22
- def get_by_name(name)
23
- name = name.to_s
24
- @instances_by_name ||= {}
25
- key = HeadMusic::Utilities::HashKey.for(name)
26
- @instances_by_name[key] ||= new(name)
27
- end
28
- end
29
- end