head_music 0.19.1 → 0.19.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 838a7ebfc5a6873d62ac2d484b655bcc5fe37302
4
- data.tar.gz: 669f4f3f9ab721dc93208b39ef22ac7170951ba9
3
+ metadata.gz: 2261f261fb02f64ef7142d521b9ac01c1cb1e2c8
4
+ data.tar.gz: de8ced9611c0b4c4db2e7e4449341095eaa82986
5
5
  SHA512:
6
- metadata.gz: 0ca1b44616d4aed6f76e5838b99a546b28889fed30802fec1c8e5ee713af89352e1326e89bf9e5195104722a574c7f8db47755fa6b561685c1b18b190fc38525
7
- data.tar.gz: cc2a14f96a3ec7e7590c59e2b81023ed41a5e9b2f7fb8fd5d25a860c929ece23fa8e2df32adf15faa508b1ed21856a774642d3e4ba40ec4299ce3d2d640df6dd
6
+ metadata.gz: 5e8b3efc74e53e4a3ab83c56c16879c974111a5da9de8abe9c2b362ddea98c96ff43df38d06e6ec110ea00133fd5a208a4b41897b10a76ab01d3799173c1d508
7
+ data.tar.gz: a9765c3240a4cca996d25532d9d513fa3f4a0d0984209cbba2599fd1b7f221a6159e0ac6729e503f65861fdd0f259b9ad8bedc4f66379bbc2cbb895f2a29a97e
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Note is like a placement, except:
3
+ # Note quacks like a placement, but requires a different set of construction arguments
4
4
  # - always has a pitch
5
- # - doesn't require voice and position
5
+ # - receives a voice and position if unspecified
6
6
  class HeadMusic::Note
7
7
  attr_accessor :pitch, :rhythmic_value, :voice, :position
8
8
 
@@ -57,6 +57,9 @@ class HeadMusic::KeySignature
57
57
  flats.any? ? flats : sharps
58
58
  end
59
59
 
60
+ alias sharps_and_flats signs
61
+ alias accidentals signs
62
+
60
63
  def name
61
64
  [tonic_spelling, scale_type].join(' ')
62
65
  end
@@ -14,14 +14,26 @@ class HeadMusic::Pitch
14
14
 
15
15
  delegate :smallest_interval_to, to: :pitch_class
16
16
 
17
+ delegate :enharmonic_equivalent?, :enharmonic?, to: :enharmonic_equivalence
18
+ delegate :octave_equivalent?, to: :octave_equivalence
19
+
17
20
  def self.get(value)
18
- from_name(value) || from_number(value)
21
+ from_pitch_class(value) || from_name(value) || from_number(value)
19
22
  end
20
23
 
21
24
  def self.middle_c
22
25
  get('C4')
23
26
  end
24
27
 
28
+ def self.concert_a
29
+ get('A4')
30
+ end
31
+
32
+ def self.from_pitch_class(pitch_class)
33
+ return nil unless pitch_class.is_a?(HeadMusic::PitchClass)
34
+ fetch_or_create(pitch_class.sharp_spelling)
35
+ end
36
+
25
37
  def self.from_name(name)
26
38
  return nil unless name == name.to_s
27
39
  fetch_or_create(HeadMusic::Spelling.get(name), HeadMusic::Octave.get(name).to_i)
@@ -49,7 +61,8 @@ class HeadMusic::Pitch
49
61
  get(natural_letter_pitch)
50
62
  end
51
63
 
52
- def self.fetch_or_create(spelling, octave)
64
+ def self.fetch_or_create(spelling, octave = nil)
65
+ octave ||= HeadMusic::Octave::DEFAULT
53
66
  return unless spelling && (-1..9).cover?(octave)
54
67
  @pitches ||= {}
55
68
  hash_key = [spelling, octave].join
@@ -84,10 +97,6 @@ class HeadMusic::Pitch
84
97
  HeadMusic::Pitch.get(to_s.gsub(/[#b]/, ''))
85
98
  end
86
99
 
87
- def enharmonic?(other)
88
- midi_note_number == other.midi_note_number
89
- end
90
-
91
100
  def +(other)
92
101
  HeadMusic::Pitch.get(to_i + other.to_i)
93
102
  end
@@ -119,10 +128,26 @@ class HeadMusic::Pitch
119
128
  HeadMusic::Pitch.get([target_letter_name(num_steps), octave + octaves_delta(num_steps)].join)
120
129
  end
121
130
 
131
+ def frequency
132
+ tuning.frequency_for(self)
133
+ end
134
+
122
135
  private_class_method :new
123
136
 
124
137
  private
125
138
 
139
+ def enharmonic_equivalence
140
+ @enharmonic_equivalence ||= EnharmonicEquivalence.get(self)
141
+ end
142
+
143
+ def octave_equivalence
144
+ @octave_equivalence ||= OctaveEquivalence.get(self)
145
+ end
146
+
147
+ def tuning
148
+ @tuning ||= HeadMusic::Tuning.new
149
+ end
150
+
126
151
  def octaves_delta(num_steps)
127
152
  octaves_delta = (num_steps.abs / 7) * (num_steps >= 0 ? 1 : -1)
128
153
  if wrapped_down?(num_steps)
@@ -145,4 +170,54 @@ class HeadMusic::Pitch
145
170
  @target_letter_name ||= {}
146
171
  @target_letter_name[num_steps] ||= letter_name.steps(num_steps)
147
172
  end
173
+
174
+ # Enharmonic equivalence occurs when two pitches are spelled differently but refer to the same frequency, such as D♯ and E♭.
175
+ class EnharmonicEquivalence
176
+ def self.get(pitch)
177
+ pitch = HeadMusic::Pitch.get(pitch)
178
+ @enharmonic_equivalences ||= {}
179
+ @enharmonic_equivalences[pitch.to_s] ||= new(pitch)
180
+ end
181
+
182
+ attr_reader :pitch
183
+
184
+ delegate :pitch_class, to: :pitch
185
+
186
+ def initialize(pitch)
187
+ @pitch = HeadMusic::Pitch.get(pitch)
188
+ end
189
+
190
+ def enharmonic_equivalent?(other)
191
+ other = HeadMusic::Pitch.get(other)
192
+ pitch.midi_note_number == other.midi_note_number && pitch.spelling != other.spelling
193
+ end
194
+
195
+ alias enharmonic? enharmonic_equivalent?
196
+ alias equivalent? enharmonic_equivalent?
197
+
198
+ private_class_method :new
199
+ end
200
+
201
+ # Octave equivalence is the functional equivalence of pitches with the same spelling separated by one or more octaves.
202
+ class OctaveEquivalence
203
+ def self.get(pitch)
204
+ @octave_equivalences ||= {}
205
+ @octave_equivalences[pitch.to_s] ||= new(pitch)
206
+ end
207
+
208
+ attr_reader :pitch
209
+
210
+ def initialize(pitch)
211
+ @pitch = pitch
212
+ end
213
+
214
+ def octave_equivalent?(other)
215
+ other = HeadMusic::Pitch.get(other)
216
+ pitch.spelling == other.spelling && pitch.octave != other.octave
217
+ end
218
+
219
+ alias equivalent? octave_equivalent?
220
+
221
+ private_class_method :new
222
+ end
148
223
  end
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # A pitch class is a set of notes separated by octaves.
3
+ # A pitch class is a set of pitches separated by octaves.
4
4
  class HeadMusic::PitchClass
5
5
  attr_reader :number
6
+ attr_reader :spelling
6
7
 
7
8
  SHARP_SPELLINGS = %w[C C# D D# E F F# G G# A A# B].freeze
8
9
  FLAT_SPELLINGS = %w[C Db D Eb E F Gb G Ab A Bb B].freeze
9
10
 
10
11
  def self.get(identifier)
11
12
  @pitch_classes ||= {}
12
- number = HeadMusic::Spelling.get(identifier).pitch_class.to_i if HeadMusic::Spelling.matching_string(identifier)
13
+ if HeadMusic::Spelling.matching_string(identifier)
14
+ spelling = HeadMusic::Spelling.get(identifier)
15
+ number = spelling.pitch_class.to_i
16
+ end
13
17
  number ||= identifier.to_i % 12
14
18
  @pitch_classes[number] ||= new(number)
15
19
  end
@@ -26,6 +30,14 @@ class HeadMusic::PitchClass
26
30
  number
27
31
  end
28
32
 
33
+ def sharp_spelling
34
+ SHARP_SPELLINGS[number]
35
+ end
36
+
37
+ def flat_spelling
38
+ FLAT_SPELLINGS[number]
39
+ end
40
+
29
41
  # Pass in the number of semitones
30
42
  def +(other)
31
43
  HeadMusic::PitchClass.get(to_i + other.to_i)
@@ -51,5 +63,13 @@ class HeadMusic::PitchClass
51
63
  intervals_to(other).first
52
64
  end
53
65
 
66
+ def white_key?
67
+ [0,2,4,5,7,9,11].include?(number)
68
+ end
69
+
70
+ def black_key?
71
+ !white_key?
72
+ end
73
+
54
74
  private_class_method :new
55
75
  end
@@ -87,4 +87,29 @@ class HeadMusic::Spelling
87
87
  end
88
88
 
89
89
  private_class_method :new
90
+
91
+ # Enharmonic equivalence occurs when two spellings refer to the same pitch class, such as D♯ and E♭.
92
+ class EnharmonicEquivalence
93
+ def self.get(spelling)
94
+ spelling = HeadMusic::Spelling.get(spelling)
95
+ @enharmonic_equivalences ||= {}
96
+ @enharmonic_equivalences[spelling.to_s] ||= new(spelling)
97
+ end
98
+
99
+ attr_reader :spelling
100
+
101
+ def initialize(spelling)
102
+ @spelling = HeadMusic::Spelling.get(spelling)
103
+ end
104
+
105
+ def enharmonic_equivalent?(other)
106
+ other = HeadMusic::Spelling.get(other)
107
+ spelling != other && spelling.pitch_class_number == other.pitch_class_number
108
+ end
109
+
110
+ alias enharmonic? enharmonic_equivalent?
111
+ alias equivalent? enharmonic_equivalent?
112
+
113
+ private_class_method :new
114
+ end
90
115
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A tuning has a reference pitch and frequency and provides frequencies for all pitches
4
+ # The base class assumes equal temperament tuning. By default, A4 = 440.0 Hz
5
+ class HeadMusic::Tuning
6
+ REFERENCE_FREQUENCY = 440.0
7
+ REFERENCE_PITCH_NAME = 'A4'
8
+
9
+ attr_reader :reference_pitch, :reference_frequency
10
+
11
+ def initialize(reference_pitch: nil, reference_frequency: nil)
12
+ @reference_pitch = reference_pitch || HeadMusic::Pitch.get(REFERENCE_PITCH_NAME)
13
+ @reference_frequency = reference_frequency || REFERENCE_FREQUENCY
14
+ end
15
+
16
+ def frequency_for(pitch)
17
+ pitch = HeadMusic::Pitch.get(pitch) unless pitch.is_a?(HeadMusic::Pitch)
18
+ reference_frequency * (2**(1.0 / 12))**(pitch - reference_pitch).semitones
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HeadMusic
4
- VERSION = '0.19.1'
4
+ VERSION = '0.19.2'
5
5
  end
data/lib/head_music.rb CHANGED
@@ -46,6 +46,7 @@ require 'head_music/scale_type'
46
46
  require 'head_music/sign'
47
47
  require 'head_music/spelling'
48
48
  require 'head_music/staff'
49
+ require 'head_music/tuning'
49
50
 
50
51
  # content
51
52
  require 'head_music/content/bar'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.1
4
+ version: 0.19.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: 2018-04-18 00:00:00.000000000 Z
11
+ date: 2018-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -190,6 +190,7 @@ files:
190
190
  - lib/head_music/style/guides/fux_cantus_firmus.rb
191
191
  - lib/head_music/style/guides/modern_cantus_firmus.rb
192
192
  - lib/head_music/style/mark.rb
193
+ - lib/head_music/tuning.rb
193
194
  - lib/head_music/utilities/hash_key.rb
194
195
  - lib/head_music/version.rb
195
196
  homepage: https://github.com/roberthead/head_music