head_music 0.19.1 → 0.19.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.
- checksums.yaml +4 -4
- data/lib/head_music/content/note.rb +2 -2
- data/lib/head_music/key_signature.rb +3 -0
- data/lib/head_music/pitch.rb +81 -6
- data/lib/head_music/pitch_class.rb +22 -2
- data/lib/head_music/spelling.rb +25 -0
- data/lib/head_music/tuning.rb +20 -0
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2261f261fb02f64ef7142d521b9ac01c1cb1e2c8
|
|
4
|
+
data.tar.gz: de8ced9611c0b4c4db2e7e4449341095eaa82986
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e8b3efc74e53e4a3ab83c56c16879c974111a5da9de8abe9c2b362ddea98c96ff43df38d06e6ec110ea00133fd5a208a4b41897b10a76ab01d3799173c1d508
|
|
7
|
+
data.tar.gz: a9765c3240a4cca996d25532d9d513fa3f4a0d0984209cbba2599fd1b7f221a6159e0ac6729e503f65861fdd0f259b9ad8bedc4f66379bbc2cbb895f2a29a97e
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Note
|
|
3
|
+
# Note quacks like a placement, but requires a different set of construction arguments
|
|
4
4
|
# - always has a pitch
|
|
5
|
-
# -
|
|
5
|
+
# - receives a voice and position if unspecified
|
|
6
6
|
class HeadMusic::Note
|
|
7
7
|
attr_accessor :pitch, :rhythmic_value, :voice, :position
|
|
8
8
|
|
data/lib/head_music/pitch.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
data/lib/head_music/spelling.rb
CHANGED
|
@@ -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
|
data/lib/head_music/version.rb
CHANGED
data/lib/head_music.rb
CHANGED
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.
|
|
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-
|
|
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
|