head_music 0.23.2 → 0.24.2

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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb1e01b3119838d06ece395586b5ba1a83d8b432f01d6bc63dfbea324f48be1c
4
- data.tar.gz: 3f6ac1484a66455949d27fe1af7b53e7becdfddaf66093e151d89ad31b31ffa5
3
+ metadata.gz: 204a1613a88b8c14b7f9441a2a593637dc3a496076abda2621d71f85ffbdf604
4
+ data.tar.gz: c9819f9c1983934caad0d58d625fbda15a787148d6e15f7cc92473de8200c2f7
5
5
  SHA512:
6
- metadata.gz: fa9f404bd1a24082020cf72d0daef7ac68f3b954ab692fec5b3c1f23ecb3a913786550127a9de543b8f88aa0a3370e3c321867cf453ab26cca3cf98ee5b81bfb
7
- data.tar.gz: '08bae5514fb6dc84f35cbaf44b16e8ab001688e379a640c462769e950193fb9b1361a018ef29d8a048e256de535a71fe3796336511f65b6cc6fbad40ab1d9209'
6
+ metadata.gz: e7e0fa5267eaa482b16abf497fb7122d6f3ade4c55f9a6ac40c43043dbfbe68a03dc9ae3a8c83262ade571965c0bf997121a9a774aee950e5598097a3fba33dd
7
+ data.tar.gz: 6617eabf6992c8bf5ab1a7533d605ca2a54f58873c13fa91e79cc65acbfe925906bd727a100d969fd057a560a1bb64d470eaa18f37a751e5b41f584909b3b007
@@ -1,6 +1,13 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 2.5
4
+
1
5
  Layout/DotPosition:
2
6
  EnforcedStyle: trailing
3
7
 
8
+ Layout/LineLength:
9
+ Max: 120
10
+
4
11
  Metrics/BlockLength:
5
12
  Exclude:
6
13
  - 'Gemfile'
@@ -10,9 +17,6 @@ Metrics/BlockLength:
10
17
  Metrics/ClassLength:
11
18
  Max: 155
12
19
 
13
- Metrics/LineLength:
14
- Max: 120
15
-
16
20
  Style/ClassAndModuleChildren:
17
21
  EnforcedStyle: compact
18
22
 
@@ -34,8 +38,5 @@ Style/TrailingCommaInArrayLiteral:
34
38
  Style/TrailingCommaInHashLiteral:
35
39
  EnforcedStyleForMultiline: consistent_comma
36
40
 
37
- AllCops:
38
- TargetRubyVersion: 2.4
39
-
40
41
  Layout/EmptyLineAfterGuardClause:
41
42
  Enabled: false
data/Rakefile CHANGED
@@ -9,5 +9,5 @@ task default: :spec
9
9
 
10
10
  desc 'Open an irb session preloaded with this library'
11
11
  task :console do
12
- sh 'irb -rubygems -I lib -r head_music.rb'
12
+ sh 'irb -I lib -r head_music.rb'
13
13
  end
@@ -22,10 +22,11 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.required_ruby_version = '>= 2.4.0'
25
+ spec.required_ruby_version = '>= 2.5'
26
26
 
27
- spec.add_runtime_dependency 'activesupport', '~> 5.0'
27
+ spec.add_runtime_dependency 'activesupport', '> 5.0'
28
28
  spec.add_runtime_dependency 'humanize', '~> 1.3'
29
+ spec.add_runtime_dependency 'i18n', '~> 1.8'
29
30
 
30
31
  spec.add_development_dependency 'bundler', '~> 1.13'
31
32
  spec.add_development_dependency 'rake', '>= 12.3.3'
@@ -12,12 +12,25 @@ require 'head_music/version'
12
12
  require 'active_support/core_ext/module/delegation'
13
13
  require 'active_support/core_ext/string/access'
14
14
  require 'humanize'
15
+ require 'i18n'
16
+ require 'i18n/backend/fallbacks'
17
+
18
+ I18n::Backend::Simple.include I18n::Backend::Fallbacks
19
+ I18n.load_path << Dir[File.join(File.dirname(__dir__), 'lib', 'head_music', 'locales', '*.yml')]
20
+ I18n.config.available_locales = %i[en de fr it es en_US en_GB]
21
+ I18n.default_locale = :en
22
+ I18n.fallbacks[:de] = %i[de en_GB en]
23
+ I18n.fallbacks[:en_US] = %i[en_US en en_GB]
24
+ I18n.fallbacks[:en_GB] = %i[en_GB en en_US]
25
+ I18n.fallbacks[:es] = %i[es en]
26
+ I18n.fallbacks[:fr] = %i[fr en_GB en]
27
+ I18n.fallbacks[:it] = %i[it en_GB en]
15
28
 
16
29
  # utilities
17
30
  require 'head_music/utilities/hash_key'
18
31
 
19
32
  # modules
20
- require 'head_music/named_rudiment'
33
+ require 'head_music/named'
21
34
 
22
35
  # rudiments
23
36
  require 'head_music/chromatic_interval'
@@ -50,6 +63,7 @@ require 'head_music/scale'
50
63
  require 'head_music/scale_degree'
51
64
  require 'head_music/scale_type'
52
65
  require 'head_music/sign'
66
+ require 'head_music/solmization'
53
67
  require 'head_music/sonority'
54
68
  require 'head_music/spelling'
55
69
  require 'head_music/staff'
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # An interval is the distance between two pitches.
3
+ # A chromatic interval is the distance between two pitches measured in half-steps.
4
4
  class HeadMusic::ChromaticInterval
5
5
  include Comparable
6
6
 
7
7
  private_class_method :new
8
8
 
9
+ # TODO: include the Named module
9
10
  NAMES = %w[
10
11
  perfect_unison minor_second major_second minor_third major_third perfect_fourth tritone perfect_fifth
11
12
  minor_sixth major_sixth minor_seventh major_seventh perfect_octave
@@ -40,6 +41,10 @@ class HeadMusic::ChromaticInterval
40
41
  semitones
41
42
  end
42
43
 
44
+ def diatonic_name
45
+ NAMES[simple.semitones].gsub(/_/, ' ')
46
+ end
47
+
43
48
  # diatonic set theory
44
49
  alias specific_interval semitones
45
50
 
@@ -5,39 +5,50 @@ require 'head_music/interval_cycle'
5
5
  # A Circle of Fifths or Fourths shows relationships between pitch classes
6
6
  class HeadMusic::Circle < HeadMusic::IntervalCycle
7
7
  def self.of_fifths
8
- get(7)
8
+ get(:perfect_fifth)
9
9
  end
10
10
 
11
11
  def self.of_fourths
12
- get(5)
12
+ get(:perfect_fourth)
13
13
  end
14
14
 
15
- def self.get(interval = 7)
15
+ def self.get(interval = :perfect_fifth)
16
16
  @circles ||= {}
17
- @circles[interval.to_i] ||= new(interval)
18
- end
19
-
20
- attr_reader :interval, :pitch_classes
21
-
22
- # Accepts an interval (as an integer number of semitones)
23
- def initialize(interval)
24
- @interval = interval.to_i
25
- @pitch_classes = pitch_classes_by_interval
17
+ diatonic_interval = HeadMusic::DiatonicInterval.get(interval)
18
+ @circles[interval] ||= new(interval: diatonic_interval, starting_pitch: 'C4')
26
19
  end
27
20
 
28
21
  def index(pitch_class)
29
- @pitch_classes.index(HeadMusic::Spelling.get(pitch_class).pitch_class)
22
+ pitch_classes.index(HeadMusic::Spelling.get(pitch_class).pitch_class)
30
23
  end
31
24
 
32
- private_class_method :new
25
+ alias spellings_up spellings
33
26
 
34
- private
27
+ def key_signatures_up
28
+ spellings_up.map { |spelling| HeadMusic::KeySignature.new(spelling) }
29
+ end
35
30
 
36
- def interval_cycle
37
- @interval_cycle ||= HeadMusic::IntervalCycle.get(interval)
31
+ def key_signatures_down
32
+ spellings_down.map { |spelling| HeadMusic::KeySignature.new(spelling) }
38
33
  end
39
34
 
40
- def pitch_classes_by_interval
41
- interval_cycle.send(:pitch_classes_by_interval)
35
+ def spellings_down
36
+ pitches_down.map(&:spelling)
42
37
  end
38
+
39
+ def pitches_down
40
+ @pitches_down ||= begin
41
+ [starting_pitch].tap do |list|
42
+ loop do
43
+ next_pitch = list.last - interval
44
+ next_pitch += octave while starting_pitch - next_pitch > 12
45
+ break if next_pitch.pitch_class == list.first.pitch_class
46
+
47
+ list << next_pitch
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ private_class_method :new
43
54
  end
@@ -1,39 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
4
+
3
5
  # A clef assigns pitches to the lines and spaces of a staff.
4
6
  class HeadMusic::Clef
5
- include HeadMusic::NamedRudiment
6
-
7
- CLEFS = [
8
- { pitch: 'G4', line: 2, names: %w[treble G-clef], modern: true },
9
- { pitch: 'G4', line: 1, names: ['French', 'French violin'] },
10
- { pitch: 'G3', line: 2, names: ['choral tenor', 'tenor', 'tenor G-clef'], modern: true },
11
-
12
- { pitch: 'F3', line: 3, names: ['baritone'] },
13
- { pitch: 'F3', line: 4, names: %w[bass F-clef], modern: true },
14
- { pitch: 'F3', line: 5, names: ['sub-bass'] },
15
-
16
- { pitch: 'C4', line: 1, names: ['soprano'] },
17
- { pitch: 'C4', line: 2, names: ['mezzo-soprano'] },
18
- { pitch: 'C4', line: 3, names: %w[alto viola counter-tenor countertenor C-clef], modern: true },
19
- { pitch: 'C4', line: 4, names: ['tenor', 'tenor C-clef'], modern: true },
20
- { pitch: 'C4', line: 5, names: ['baritone', 'baritone C-clef'] },
7
+ include HeadMusic::Named
21
8
 
22
- { pitch: nil, line: 3, names: %w[neutral percussion] },
23
- ].freeze
9
+ RECORDS = YAML.load_file(File.expand_path('data/clefs.yml', __dir__)).freeze
24
10
 
25
11
  def self.get(name)
26
12
  get_by_name(name)
27
13
  end
28
14
 
29
- attr_reader :pitch, :line
15
+ attr_reader :pitch, :line, :musical_symbols
30
16
 
31
- def initialize(name)
32
- @name = name.to_s
33
- clef_data = CLEFS.detect { |clef| clef[:names].map(&:downcase).include?(name.downcase) }
34
- @pitch = HeadMusic::Pitch.get(clef_data[:pitch])
35
- @line = clef_data[:line]
36
- @modern = clef_data[:modern]
17
+ delegate :ascii, :html_entity, :unicode, to: :musical_symbol
18
+
19
+ def musical_symbol
20
+ musical_symbols.first
37
21
  end
38
22
 
39
23
  def clef_type
@@ -61,6 +45,59 @@ class HeadMusic::Clef
61
45
  end
62
46
 
63
47
  def ==(other)
64
- to_s == other.to_s
48
+ HeadMusic::Utilities::HashKey.for(self) == HeadMusic::Utilities::HashKey.for(other)
49
+ end
50
+
51
+ def name(locale_code: Locale::DEFAULT_CODE)
52
+ I18n.translate(name_key, scope: :clefs, locale: locale_code)
53
+ end
54
+
55
+ private_class_method :new
56
+
57
+ private
58
+
59
+ def initialize(name)
60
+ record = record_for_name(name)
61
+ initialize_data_from_record(record)
62
+ end
63
+
64
+ def record_for_name(name)
65
+ name = name.to_s.strip
66
+ key = HeadMusic::Utilities::HashKey.for(name)
67
+ RECORDS.detect do |record|
68
+ name_keys = name_keys_from_record(record)
69
+ name_keys.include?(key) || name_key_translations(name_keys).include?(name)
70
+ end
71
+ end
72
+
73
+ def name_keys_from_record(record)
74
+ ([record[:name_key]] + [record[:alias_name_keys]]).flatten.compact.uniq.map(&:to_sym)
75
+ end
76
+
77
+ def name_key_translations(name_keys)
78
+ name_keys.map do |name_key|
79
+ I18n.config.available_locales.map do |locale_code|
80
+ I18n.translate(name_key, scope: :clefs, locale: locale_code)
81
+ end.flatten.uniq.compact
82
+ end.flatten.uniq.compact
83
+ end
84
+
85
+ def initialize_data_from_record(record)
86
+ initialize_keys_from_record(record)
87
+ @pitch = HeadMusic::Pitch.get(record[:pitch])
88
+ @line = record[:line]
89
+ @modern = record[:modern]
90
+ initialize_musical_symbols(record[:symbols])
91
+ end
92
+
93
+ def initialize_keys_from_record(record)
94
+ @name_key = record[:name_key]
95
+ @alias_name_keys = [record[:alias_name_keys]].flatten.compact
96
+ end
97
+
98
+ def initialize_musical_symbols(list)
99
+ @musical_symbols = (list || []).map do |symbol_data|
100
+ HeadMusic::MusicalSymbol.new(symbol_data.slice(:ascii, :html_entity, :unicode))
101
+ end
65
102
  end
66
103
  end
@@ -5,6 +5,7 @@ class HeadMusic::Placement
5
5
  include Comparable
6
6
 
7
7
  attr_reader :voice, :position, :rhythmic_value, :pitch
8
+
8
9
  delegate :composition, to: :voice
9
10
  delegate :spelling, to: :pitch, allow_nil: true
10
11
 
@@ -5,6 +5,7 @@ class HeadMusic::Position
5
5
  include Comparable
6
6
 
7
7
  attr_reader :composition, :bar_number, :count, :tick
8
+
8
9
  delegate :to_s, to: :code
9
10
 
10
11
  def initialize(composition, code_or_bar, count = nil, tick = nil)
@@ -6,6 +6,7 @@ class HeadMusic::Voice
6
6
  include Comparable
7
7
 
8
8
  attr_reader :composition, :placements, :role
9
+
9
10
  delegate :key_signature, to: :composition
10
11
 
11
12
  def initialize(composition: nil, role: nil)
@@ -0,0 +1,126 @@
1
+ ---
2
+ - :pitch: G4
3
+ :line: 2
4
+ :modern: true
5
+ :name_key: treble_clef
6
+ :alias_name_keys:
7
+ - g_clef
8
+ - second_line_g_clef
9
+ - violin_clef
10
+ :symbols:
11
+ - :unicode: "\U0001D11E"
12
+ :html_entity: "&#119070;"
13
+ - :pitch: G4
14
+ :line: 1
15
+ :name_key: french_violin_clef
16
+ :alias_name_keys:
17
+ - french_clef
18
+ - first_line_g_clef
19
+ :symbols:
20
+ - :unicode: "\U0001D11E"
21
+ :html_entity: "&#119070;"
22
+ - :pitch: G3
23
+ :line: 2
24
+ :modern: true
25
+ :name_key: choral_tenor_clef
26
+ :alias_name_keys:
27
+ - tenor_clef
28
+ - tenor_g_clef
29
+ :symbols:
30
+ - :unicode: "\U0001D120"
31
+ :html_entity: "&#119072;"
32
+ - :pitch: G3
33
+ :line: 2
34
+ :name_key: double_treble_clef
35
+ :symbols:
36
+ - :unicode: "\U0001D11E\U0001D11E"
37
+ :html_entity: "&#119070;&#119070;"
38
+ - :pitch: F3
39
+ :line: 3
40
+ :name_key: baritone_clef
41
+ :alias_name_keys:
42
+ - baritone_f_clef
43
+ - third_line_f_clef
44
+ :symbols:
45
+ - :unicode: "\U0001D122"
46
+ :html_entity: "&#119074;"
47
+ - :pitch: F3
48
+ :line: 4
49
+ :modern: true
50
+ :name_key: bass_clef
51
+ :alias_name_keys:
52
+ - f_clef
53
+ - fourth_line_f_clef
54
+ :symbols:
55
+ - :unicode: "\U0001D122"
56
+ :html_entity: "&#119074;"
57
+ - :pitch: F3
58
+ :line: 5
59
+ :name_key: sub_bass_clef
60
+ :alias_name_keys:
61
+ - contrabass_clef
62
+ - fifth_line_f_clef
63
+ :symbols:
64
+ - :unicode: "\U0001D122"
65
+ :html_entity: "&#119074;"
66
+ - :pitch: C4
67
+ :line: 1
68
+ :name_key: soprano_clef
69
+ :alias_name_keys:
70
+ - first_line_c_clef
71
+ :symbols:
72
+ - :unicode: "\U0001D121"
73
+ :html_entity: "&#119073;"
74
+ - :pitch: C4
75
+ :line: 2
76
+ :name_key: mezzo_soprano_clef
77
+ :alias_name_keys:
78
+ - second_line_c_clef
79
+ :symbols:
80
+ - :unicode: "\U0001D121"
81
+ :html_entity: "&#119073;"
82
+ - :pitch: C4
83
+ :line: 3
84
+ :modern: true
85
+ :name_key: alto_clef
86
+ :alias_name_keys:
87
+ - c_clef
88
+ - third_line_c_clef
89
+ - viola_clef
90
+ - countertenor_clef
91
+ :symbols:
92
+ - :unicode: "\U0001D121"
93
+ :html_entity: "&#119073;"
94
+ - :pitch: C4
95
+ :line: 4
96
+ :modern: true
97
+ :name_key: tenor_clef
98
+ :alias_name_keys:
99
+ - tenor_c_clef
100
+ - fourth_line_c_clef
101
+ :symbols:
102
+ - :unicode: "\U0001D121"
103
+ :html_entity: "&#119073;"
104
+ - :pitch: C4
105
+ :line: 5
106
+ :name_key: baritone_c_clef
107
+ :alias_name_keys:
108
+ - baritone_clef
109
+ - fifth_line_c_clef
110
+ :symbols:
111
+ - :unicode: "\U0001D121"
112
+ :html_entity: "&#119073;"
113
+ - :pitch:
114
+ :line: 3
115
+ :modern: true
116
+ :name_key: neutral_clef
117
+ :alias_name_keys:
118
+ - percussion_clef
119
+ - indefinite_pitch_clef
120
+ - rhythm_clef
121
+ - drum_clef
122
+ :symbols:
123
+ - :unicode: "\U0001D125"
124
+ :html_entity: "&#119077;"
125
+ - :unicode: "\U0001D126"
126
+ :html_entity: "&#119078;"