head_music 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -1145
  3. data/Gemfile +8 -2
  4. data/Rakefile +7 -5
  5. data/bin/console +4 -3
  6. data/circle.yml +1 -1
  7. data/head_music.gemspec +19 -17
  8. data/lib/head_music/bar.rb +2 -0
  9. data/lib/head_music/chord.rb +24 -6
  10. data/lib/head_music/circle.rb +5 -0
  11. data/lib/head_music/clef.rb +5 -2
  12. data/lib/head_music/composition.rb +3 -0
  13. data/lib/head_music/consonance.rb +5 -2
  14. data/lib/head_music/functional_interval.rb +84 -47
  15. data/lib/head_music/grand_staff.rb +11 -11
  16. data/lib/head_music/harmonic_interval.rb +13 -7
  17. data/lib/head_music/instrument.rb +9 -6
  18. data/lib/head_music/interval.rb +13 -7
  19. data/lib/head_music/key_signature.rb +8 -5
  20. data/lib/head_music/language.rb +21 -14
  21. data/lib/head_music/letter_name.rb +13 -10
  22. data/lib/head_music/melodic_interval.rb +10 -4
  23. data/lib/head_music/meter.rb +12 -12
  24. data/lib/head_music/motion.rb +12 -9
  25. data/lib/head_music/named_rudiment.rb +22 -20
  26. data/lib/head_music/note.rb +7 -1
  27. data/lib/head_music/octave.rb +9 -7
  28. data/lib/head_music/pitch.rb +54 -27
  29. data/lib/head_music/pitch_class.rb +17 -12
  30. data/lib/head_music/placement.rb +22 -9
  31. data/lib/head_music/position.rb +8 -9
  32. data/lib/head_music/quality.rb +9 -6
  33. data/lib/head_music/rhythm.rb +2 -0
  34. data/lib/head_music/rhythmic_unit.rb +29 -19
  35. data/lib/head_music/rhythmic_value.rb +5 -2
  36. data/lib/head_music/scale.rb +65 -45
  37. data/lib/head_music/scale_degree.rb +9 -6
  38. data/lib/head_music/scale_type.rb +70 -30
  39. data/lib/head_music/sign.rb +18 -13
  40. data/lib/head_music/spelling.rb +14 -10
  41. data/lib/head_music/staff.rb +4 -1
  42. data/lib/head_music/style/analysis.rb +36 -34
  43. data/lib/head_music/style/annotation.rb +14 -13
  44. data/lib/head_music/style/annotations/always_move.rb +7 -6
  45. data/lib/head_music/style/annotations/approach_perfection_contrarily.rb +5 -2
  46. data/lib/head_music/style/annotations/at_least_eight_notes.rb +10 -8
  47. data/lib/head_music/style/annotations/avoid_crossing_voices.rb +11 -8
  48. data/lib/head_music/style/annotations/avoid_overlapping_voices.rb +17 -10
  49. data/lib/head_music/style/annotations/consonant_climax.rb +18 -15
  50. data/lib/head_music/style/annotations/consonant_downbeats.rb +6 -3
  51. data/lib/head_music/style/annotations/diatonic.rb +8 -5
  52. data/lib/head_music/style/annotations/direction_changes.rb +8 -6
  53. data/lib/head_music/style/annotations/end_on_perfect_consonance.rb +5 -5
  54. data/lib/head_music/style/annotations/end_on_tonic.rb +7 -6
  55. data/lib/head_music/style/annotations/frequent_direction_changes.rb +6 -3
  56. data/lib/head_music/style/annotations/limit_octave_leaps.rb +9 -7
  57. data/lib/head_music/style/annotations/moderate_direction_changes.rb +6 -3
  58. data/lib/head_music/style/annotations/mostly_conjunct.rb +8 -5
  59. data/lib/head_music/style/annotations/no_rests.rb +6 -3
  60. data/lib/head_music/style/annotations/no_unisons_in_middle.rb +6 -3
  61. data/lib/head_music/style/annotations/notes_same_length.rb +9 -6
  62. data/lib/head_music/style/annotations/one_to_one.rb +10 -7
  63. data/lib/head_music/style/annotations/prefer_contrary_motion.rb +6 -3
  64. data/lib/head_music/style/annotations/prefer_imperfect.rb +7 -6
  65. data/lib/head_music/style/annotations/prepare_octave_leaps.rb +21 -12
  66. data/lib/head_music/style/annotations/recover_large_leaps.rb +17 -12
  67. data/lib/head_music/style/annotations/singable_intervals.rb +8 -5
  68. data/lib/head_music/style/annotations/singable_range.rb +7 -6
  69. data/lib/head_music/style/annotations/single_large_leaps.rb +6 -3
  70. data/lib/head_music/style/annotations/start_on_perfect_consonance.rb +6 -5
  71. data/lib/head_music/style/annotations/start_on_tonic.rb +6 -5
  72. data/lib/head_music/style/annotations/step_down_to_final_note.rb +12 -12
  73. data/lib/head_music/style/annotations/step_out_of_unison.rb +10 -7
  74. data/lib/head_music/style/annotations/step_to_final_note.rb +6 -3
  75. data/lib/head_music/style/annotations/step_up_to_final_note.rb +12 -12
  76. data/lib/head_music/style/annotations/up_to_fourteen_notes.rb +6 -5
  77. data/lib/head_music/style/mark.rb +10 -4
  78. data/lib/head_music/style/rulesets/first_species_harmony.rb +6 -3
  79. data/lib/head_music/style/rulesets/first_species_melody.rb +6 -3
  80. data/lib/head_music/style/rulesets/fux_cantus_firmus.rb +6 -3
  81. data/lib/head_music/style/rulesets/modern_cantus_firmus.rb +6 -3
  82. data/lib/head_music/utilities/hash_key.rb +9 -7
  83. data/lib/head_music/version.rb +3 -1
  84. data/lib/head_music/voice.rb +7 -3
  85. data/lib/head_music.rb +2 -0
  86. metadata +4 -4
data/Gemfile CHANGED
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
5
+ ruby '2.4.0'
6
+
3
7
  # Specify your gem's dependencies in head_music.gemspec
4
8
  gemspec
5
9
 
6
10
  group :test do
7
- gem "simplecov"
8
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
+ gem 'codeclimate-test-reporter', '~> 1.0.0'
12
+ gem 'rubocop', require: false
13
+ gem 'rubocop-rspec', require: false
14
+ gem 'simplecov'
9
15
  end
data/Rakefile CHANGED
@@ -1,11 +1,13 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
7
9
 
8
- desc "Open an irb session preloaded with this library"
10
+ desc 'Open an irb session preloaded with this library'
9
11
  task :console do
10
- sh "irb -rubygems -I lib -r head_music.rb"
12
+ sh 'irb -rubygems -I lib -r head_music.rb'
11
13
  end
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "head_music"
4
+ require 'bundler/setup'
5
+ require 'head_music'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "head_music"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
data/circle.yml CHANGED
@@ -6,4 +6,4 @@ machine:
6
6
  # Version of ruby to use
7
7
  ruby:
8
8
  version:
9
- 2.3.1
9
+ 2.4.0
data/head_music.gemspec CHANGED
@@ -1,33 +1,35 @@
1
- # coding: utf-8
1
+
2
+ # frozen_string_literal: true
3
+
2
4
  lib = File.expand_path('../lib', __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'head_music/version'
5
7
 
6
8
  Gem::Specification.new do |spec|
7
- spec.name = "head_music"
9
+ spec.name = 'head_music'
8
10
  spec.version = HeadMusic::VERSION
9
- spec.authors = ["Rob Head"]
10
- spec.email = ["robert.head@gmail.com"]
11
+ spec.authors = ['Rob Head']
12
+ spec.email = ['robert.head@gmail.com']
11
13
 
12
- spec.summary = %q{The rudiments of western music theory.}
13
- spec.description = %q{Work with the elements of western music theory, such as pitches, scales, intervals, and chords.}
14
- spec.homepage = "https://github.com/roberthead/head_music"
15
- spec.license = "MIT"
14
+ spec.summary = 'The rudiments of western music theory.'
15
+ spec.description = 'Work with the elements of western music theory, such as pitches, scales, intervals, and chords.'
16
+ spec.homepage = 'https://github.com/roberthead/head_music'
17
+ spec.license = 'MIT'
16
18
 
17
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
20
  f.match(%r{^(test|spec|features)/})
19
21
  end
20
- spec.bindir = "exe"
22
+ spec.bindir = 'exe'
21
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
- spec.require_paths = ["lib"]
24
+ spec.require_paths = ['lib']
23
25
 
24
- spec.required_ruby_version = '> 2.3.0'
26
+ spec.required_ruby_version = '>= 2.4.0'
25
27
 
26
- spec.add_runtime_dependency "activesupport", "~> 5.0"
27
- spec.add_runtime_dependency "humanize", "~> 1.3"
28
+ spec.add_runtime_dependency 'activesupport', '~> 5.0'
29
+ spec.add_runtime_dependency 'humanize', '~> 1.3'
28
30
 
29
- spec.add_development_dependency "bundler", "~> 1.13"
30
- spec.add_development_dependency "rake", "~> 10.0"
31
- spec.add_development_dependency "rspec", "~> 3.0"
32
- spec.add_development_dependency "rspec-its", "~> 1.2"
31
+ spec.add_development_dependency 'bundler', '~> 1.13'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.0'
34
+ spec.add_development_dependency 'rspec-its', '~> 1.2'
33
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Representation of a bar in a composition
2
4
  # Encapsulates meter and key signature changes
3
5
  class HeadMusic::Bar
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A Chord is a collection of three or more pitches
1
4
  class HeadMusic::Chord
2
5
  attr_reader :pitches
3
6
 
@@ -7,12 +10,27 @@ class HeadMusic::Chord
7
10
  end
8
11
 
9
12
  def consonant_triad?
10
- pitches.length == 3 &&
11
- (
12
- intervals.map(&:shorthand).sort == %w[M3 m3] ||
13
- invert.intervals.map(&:shorthand).sort == %w[M3 m3] ||
14
- invert.invert.intervals.map(&:shorthand).sort == %w[M3 m3]
15
- )
13
+ return false unless three_pitches?
14
+ root_triad? || first_inversion_triad? || second_inversion_triad?
15
+ end
16
+
17
+ def root_triad?
18
+ intervals.map(&:shorthand).sort == %w[M3 m3]
19
+ end
20
+
21
+ def first_inversion_triad?
22
+ invert.invert.intervals.map(&:shorthand).sort == %w[M3 m3]
23
+ end
24
+
25
+ def second_inversion_triad?
26
+ invert.intervals.map(&:shorthand).sort == %w[M3 m3]
27
+ end
28
+
29
+ # TODO
30
+ def reduction; end
31
+
32
+ def three_pitches?
33
+ pitches.length == 3
16
34
  end
17
35
 
18
36
  def intervals
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A Circle of Fifths or Fourths shows relationships between pitch classes
4
+ # TODO: Replace or empower with IntervalCycle (?)
5
+ # https://en.wikipedia.org/wiki/Interval_cycle
1
6
  class HeadMusic::Circle
2
7
  def self.of_fifths
3
8
  get(7)
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A clef assigns pitches to the lines and spaces of a staff.
1
4
  class HeadMusic::Clef
2
5
  include HeadMusic::NamedRudiment
3
6
 
@@ -16,8 +19,8 @@ class HeadMusic::Clef
16
19
  { pitch: 'C4', line: 4, names: ['tenor'], modern: true },
17
20
  { pitch: 'C4', line: 5, names: ['baritone'] },
18
21
 
19
- { pitch: nil, line: 3, names: ['neutral', 'percussion'] }
20
- ]
22
+ { pitch: nil, line: 3, names: %w[neutral percussion] },
23
+ ].freeze
21
24
 
22
25
  def self.get(name)
23
26
  get_by_name(name)
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A composition is a portion of music.
1
4
  class HeadMusic::Composition
2
5
  attr_reader :name, :key_signature, :meter, :voices
3
6
 
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Consonance is a description of intervals that sound more pleasing.
1
4
  class HeadMusic::Consonance
2
- LEVELS = %w[perfect imperfect dissonant]
5
+ LEVELS = %w[perfect imperfect dissonant].freeze
3
6
 
4
7
  def self.get(name)
5
8
  @consonances ||= {}
@@ -15,7 +18,7 @@ class HeadMusic::Consonance
15
18
  end
16
19
 
17
20
  def ==(other)
18
- self.to_s == other.to_s
21
+ to_s == other.to_s
19
22
  end
20
23
 
21
24
  LEVELS.each do |method_name|
@@ -1,8 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents a functional interval.
1
4
  class HeadMusic::FunctionalInterval
2
5
  include Comparable
3
6
 
4
- NUMBER_NAMES = %w[unison second third fourth fifth sixth seventh octave ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth]
5
- NAME_SUFFIXES = Hash.new('th').merge({ 1 => 'st', 2 => 'nd', 3 => 'rd' })
7
+ NUMBER_NAMES = %w[
8
+ unison second third fourth fifth sixth seventh octave
9
+ ninth tenth eleventh twelfth thirteenth fourteenth fifteenth
10
+ sixteenth seventeenth
11
+ ].freeze
12
+ NAME_SUFFIXES = Hash.new('th').merge(1 => 'st', 2 => 'nd', 3 => 'rd').freeze
6
13
 
7
14
  QUALITY_SEMITONES = {
8
15
  unison: { perfect: 0 },
@@ -21,43 +28,75 @@ class HeadMusic::FunctionalInterval
21
28
  fourteenth: { major: 23 },
22
29
  fifteenth: { perfect: 24 },
23
30
  sixteenth: { major: 26 },
24
- seventeenth: { major: 28 }
25
- }
31
+ seventeenth: { major: 28 },
32
+ }.freeze
26
33
 
27
34
  attr_reader :lower_pitch, :higher_pitch
28
35
 
29
36
  delegate :to_s, to: :name
30
37
  delegate :perfect?, :major?, :minor?, :diminished?, :augmented?, :doubly_diminished?, :doubly_augmented?, to: :quality
31
38
 
32
- def self.get(name)
33
- words = name.to_s.split(/[_ ]+/)
34
- quality_name, degree_name = words[0..-2].join(' '), words.last
35
- lower_pitch = HeadMusic::Pitch.get('C4')
36
- steps = NUMBER_NAMES.index(degree_name)
37
- higher_letter = lower_pitch.letter_name.steps(steps)
38
- semitones = degree_quality_semitones.dig(degree_name.to_sym, quality_name.to_sym)
39
- higher_pitch = HeadMusic::Pitch.from_number_and_letter(lower_pitch + semitones, higher_letter)
40
- new(lower_pitch, higher_pitch)
41
- end
42
-
43
- def self.degree_quality_semitones
44
- @degree_quality_semitones ||= begin
45
- degree_quality_semitones = QUALITY_SEMITONES
46
- QUALITY_SEMITONES.each do |degree_name, qualities|
47
- default_quality = qualities.keys.first
48
- if default_quality == :perfect
49
- modification_hash = HeadMusic::Quality::PERFECT_INTERVAL_MODIFICATION.invert
50
- else
51
- modification_hash = HeadMusic::Quality::MAJOR_INTERVAL_MODIFICATION.invert
52
- end
53
- default_semitones = qualities[default_quality]
54
- modification_hash.each do |quality_name, delta|
55
- if quality_name != default_quality
56
- degree_quality_semitones[degree_name][quality_name] = default_semitones + delta
57
- end
39
+ # Representation of the name of the functional interval
40
+ class Name
41
+ attr_reader :identifier
42
+
43
+ def initialize(identifier)
44
+ @identifier = identifier
45
+ end
46
+
47
+ def words
48
+ identifier.to_s.split(/[_ ]+/)
49
+ end
50
+
51
+ def quality_name
52
+ words[0..-2].join(' ').to_sym
53
+ end
54
+
55
+ def degree_name
56
+ words.last
57
+ end
58
+
59
+ def steps
60
+ NUMBER_NAMES.index(degree_name)
61
+ end
62
+
63
+ def higher_letter
64
+ HeadMusic::Pitch.middle_c.letter_name.steps(steps)
65
+ end
66
+ end
67
+
68
+ def self.get(identifier)
69
+ name = Name.new(identifier)
70
+ semitones = _degree_quality_semitones.dig(name.degree_name.to_sym, name.quality_name)
71
+ higher_pitch = HeadMusic::Pitch.from_number_and_letter(HeadMusic::Pitch.middle_c + semitones, name.higher_letter)
72
+ new(HeadMusic::Pitch.middle_c, higher_pitch)
73
+ end
74
+
75
+ def self._degree_quality_semitones
76
+ @_degree_quality_semitones ||= begin
77
+ {}.tap do |degree_quality_semitones|
78
+ QUALITY_SEMITONES.each do |degree_name, qualities|
79
+ default_quality = qualities.keys.first
80
+ default_semitones = qualities[default_quality]
81
+ degree_quality_semitones[degree_name] = _semitones_for_degree(default_quality, default_semitones)
58
82
  end
59
83
  end
60
- degree_quality_semitones
84
+ end
85
+ end
86
+
87
+ def self._semitones_for_degree(quality, default_semitones)
88
+ {}.tap do |semitones|
89
+ _degree_quality_modifications(quality).each do |current_quality, delta|
90
+ semitones[current_quality] = default_semitones + delta
91
+ end
92
+ end
93
+ end
94
+
95
+ def self._degree_quality_modifications(quality)
96
+ if quality == :perfect
97
+ HeadMusic::Quality::PERFECT_INTERVAL_MODIFICATION.invert
98
+ else
99
+ HeadMusic::Quality::MAJOR_INTERVAL_MODIFICATION.invert
61
100
  end
62
101
  end
63
102
 
@@ -96,7 +135,7 @@ class HeadMusic::FunctionalInterval
96
135
  end
97
136
 
98
137
  def simple?
99
- octaves == 0
138
+ octaves.zero?
100
139
  end
101
140
 
102
141
  def simple_name
@@ -104,7 +143,7 @@ class HeadMusic::FunctionalInterval
104
143
  end
105
144
 
106
145
  def name
107
- if number < NUMBER_NAMES.length
146
+ if named_number?
108
147
  [quality_name, number_name].join(' ')
109
148
  elsif simple_name == 'perfect unison'
110
149
  "#{octaves.humanize} octaves"
@@ -125,7 +164,7 @@ class HeadMusic::FunctionalInterval
125
164
  def quality_name
126
165
  starting_quality = QUALITY_SEMITONES[simple_number_name.to_sym].keys.first
127
166
  delta = simple_semitones - QUALITY_SEMITONES[simple_number_name.to_sym][starting_quality]
128
- HeadMusic::Quality::from(starting_quality, delta)
167
+ HeadMusic::Quality.from(starting_quality, delta)
129
168
  end
130
169
 
131
170
  def simple_number_name
@@ -133,16 +172,12 @@ class HeadMusic::FunctionalInterval
133
172
  end
134
173
 
135
174
  def number_name
136
- NUMBER_NAMES[number - 1] || begin
137
- number.to_s + NAME_SUFFIXES[number % 10]
138
- end
175
+ NUMBER_NAMES[number - 1] || (number.to_s + NAME_SUFFIXES[number % 10])
139
176
  end
140
177
 
141
178
  def inversion
142
179
  inverted_low_pitch = lower_pitch
143
- while inverted_low_pitch < higher_pitch
144
- inverted_low_pitch += 12
145
- end
180
+ inverted_low_pitch += 12 while inverted_low_pitch < higher_pitch
146
181
  HeadMusic::FunctionalInterval.new(higher_pitch, inverted_low_pitch)
147
182
  end
148
183
 
@@ -169,11 +204,11 @@ class HeadMusic::FunctionalInterval
169
204
  end
170
205
 
171
206
  def step?
172
- number <= 2
207
+ number == 2
173
208
  end
174
209
 
175
210
  def skip?
176
- number >= 3
211
+ number == 3
177
212
  end
178
213
 
179
214
  def leap?
@@ -185,10 +220,8 @@ class HeadMusic::FunctionalInterval
185
220
  end
186
221
 
187
222
  def <=>(other)
188
- if !other.is_a?(HeadMusic::FunctionalInterval)
189
- other = self.class.get(other)
190
- end
191
- self.semitones <=> other.semitones
223
+ other = self.class.get(other) unless other.is_a?(HeadMusic::FunctionalInterval)
224
+ semitones <=> other.semitones
192
225
  end
193
226
 
194
227
  NUMBER_NAMES.each do |interval_name|
@@ -201,12 +234,16 @@ class HeadMusic::FunctionalInterval
201
234
 
202
235
  private
203
236
 
237
+ def named_number?
238
+ number < NUMBER_NAMES.length
239
+ end
240
+
204
241
  def consonance_for_perfect(style = :standard_practice)
205
242
  HeadMusic::Consonance.get(dissonant_fourth?(style) ? :dissonant : :perfect) if perfect?
206
243
  end
207
244
 
208
245
  def consonance_for_major_and_minor
209
- HeadMusic::Consonance.get((third_or_compound? || sixth_or_compound?) ? :imperfect : :dissonant) if (major? || minor?)
246
+ HeadMusic::Consonance.get(third_or_compound? || sixth_or_compound? ? :imperfect : :dissonant) if major? || minor?
210
247
  end
211
248
 
212
249
  def dissonant_fourth?(style = :standard_practice)
@@ -1,21 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A grand staff is a group of staves for a single instrument, such as a piano.
1
4
  class HeadMusic::GrandStaff
2
5
  GRAND_STAVES = {
3
6
  piano: {
4
7
  instrument: :piano,
5
8
  staves: [
6
9
  { clef: :treble, instrument: :piano },
7
- { clef: :bass, instrument: :piano }
8
- ]
10
+ { clef: :bass, instrument: :piano },
11
+ ],
9
12
  },
10
13
  organ: {
11
14
  instrument: :organ,
12
15
  staves: [
13
16
  { clef: :treble, instrument: :organ },
14
17
  { clef: :bass, instrument: :organ },
15
- { clef: :bass, instrument: :pedals }
16
- ]
17
- }
18
- }
18
+ { clef: :bass, instrument: :pedals },
19
+ ],
20
+ },
21
+ }.freeze
19
22
 
20
23
  def self.get(name)
21
24
  @grand_staves ||= {}
@@ -36,11 +39,8 @@ class HeadMusic::GrandStaff
36
39
  end
37
40
 
38
41
  def staves
39
- @staves ||= begin
40
- data[:staves].map { |staff|
41
- HeadMusic::Staff.new(staff[:clef], instrument: staff[:instrument] || instrument)
42
- }
43
- end
42
+ @staves ||=
43
+ data[:staves].map { |staff| HeadMusic::Staff.new(staff[:clef], instrument: staff[:instrument] || instrument) }
44
44
  end
45
45
 
46
46
  def brace_staves_index_first
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A harmonic interval is the functional interval between two notes sounding together.
1
4
  class HeadMusic::HarmonicInterval
2
5
  attr_reader :voice1, :voice2, :position
3
6
 
@@ -40,12 +43,11 @@ class HeadMusic::HarmonicInterval
40
43
  end
41
44
 
42
45
  def pitch_orientation
43
- if lower_pitch < upper_pitch
44
- if lower_note.voice == voice1
45
- :up
46
- elsif lower_note.voice == voice2
47
- :down
48
- end
46
+ return if lower_pitch == upper_pitch
47
+ if lower_note.voice == voice1
48
+ :up
49
+ elsif lower_note.voice == voice2
50
+ :down
49
51
  end
50
52
  end
51
53
 
@@ -54,6 +56,10 @@ class HeadMusic::HarmonicInterval
54
56
  end
55
57
 
56
58
  def method_missing(method_name, *args, &block)
57
- functional_interval.send(method_name, *args, &block)
59
+ respond_to_missing?(method_name) ? functional_interval.send(method_name, *args, &block) : super
60
+ end
61
+
62
+ def respond_to_missing?(method_name, *_args)
63
+ functional_interval.respond_to?(method_name)
58
64
  end
59
65
  end
@@ -1,18 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # An instrument can be assigned to a staff.
1
4
  class HeadMusic::Instrument
2
5
  include HeadMusic::NamedRudiment
3
6
 
4
7
  INSTRUMENTS = {
5
8
  violin: {
6
- name: "violin",
9
+ name: 'violin',
7
10
  family: :string,
8
- default_clef: :treble
11
+ default_clef: :treble,
9
12
  },
10
13
  piano: {
11
- name: "piano",
14
+ name: 'piano',
12
15
  family: :string,
13
- default_system: [:treble, :bass]
14
- }
15
- }
16
+ default_system: %i[treble bass],
17
+ },
18
+ }.freeze
16
19
 
17
20
  def self.get(name)
18
21
  get_by_name(name)
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # An interval is the distance between two pitches.
1
4
  class HeadMusic::Interval
2
5
  include Comparable
3
6
 
4
7
  private_class_method :new
5
8
 
6
- NAMES = %w{perfect_unison minor_second major_second minor_third major_third perfect_fourth tritone perfect_fifth minor_sixth major_sixth minor_seventh major_seventh perfect_octave}
9
+ NAMES = %w[
10
+ perfect_unison minor_second major_second minor_third major_third perfect_fourth tritone perfect_fifth
11
+ minor_sixth major_sixth minor_seventh major_seventh perfect_octave
12
+ ].freeze
7
13
 
8
14
  attr_reader :semitones
9
15
 
@@ -23,7 +29,7 @@ class HeadMusic::Interval
23
29
  end
24
30
 
25
31
  def simple?
26
- (0..12).include?(semitones)
32
+ (0..12).cover?(semitones)
27
33
  end
28
34
 
29
35
  def compound?
@@ -34,15 +40,15 @@ class HeadMusic::Interval
34
40
  semitones
35
41
  end
36
42
 
37
- def +(value)
38
- HeadMusic::Interval.get(to_i + value.to_i)
43
+ def +(other)
44
+ HeadMusic::Interval.get(to_i + other.to_i)
39
45
  end
40
46
 
41
- def -(value)
42
- HeadMusic::Interval.get((to_i - value.to_i).abs)
47
+ def -(other)
48
+ HeadMusic::Interval.get((to_i - other.to_i).abs)
43
49
  end
44
50
 
45
51
  def <=>(other)
46
- self.to_i <=> other.to_i
52
+ to_i <=> other.to_i
47
53
  end
48
54
  end
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents a key signature.
1
4
  class HeadMusic::KeySignature
2
5
  attr_reader :tonic_spelling
3
6
  attr_reader :scale_type
4
7
  attr_reader :scale
5
8
 
6
- SHARPS = %w{F♯ C♯ G♯ D♯ A♯ E♯ B♯}
7
- FLATS = %w{B♭ E♭ A♭ D♭ G♭ C♭ F♭}
9
+ SHARPS = %w[F♯ C♯ G♯ D♯ A♯ E♯ B♯].freeze
10
+ FLATS = %w[B♭ E♭ A♭ D♭ G♭ C♭ F♭].freeze
8
11
 
9
12
  def self.default
10
13
  @default ||= new('C', :major)
@@ -50,8 +53,8 @@ class HeadMusic::KeySignature
50
53
  flats.length
51
54
  end
52
55
 
53
- def sharps_or_flats
54
- flats.length > 0 ? flats : sharps
56
+ def signs
57
+ !flats.empty? ? flats : sharps
55
58
  end
56
59
 
57
60
  def name
@@ -59,6 +62,6 @@ class HeadMusic::KeySignature
59
62
  end
60
63
 
61
64
  def ==(other)
62
- self.sharps_or_flats == self.class.get(other).sharps_or_flats
65
+ signs == self.class.get(other).signs
63
66
  end
64
67
  end