head_music 0.23.4 → 0.24.0
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/.rubocop.yml +3 -3
- data/Rakefile +1 -1
- data/lib/head_music/chromatic_interval.rb +5 -1
- data/lib/head_music/circle.rb +30 -19
- data/lib/head_music/diatonic_interval.rb +2 -0
- data/lib/head_music/interval_cycle.rb +37 -16
- data/lib/head_music/key_signature.rb +22 -6
- data/lib/head_music/named.rb +16 -0
- data/lib/head_music/pitch.rb +11 -4
- data/lib/head_music/pitch_class.rb +2 -2
- data/lib/head_music/reference_pitch.rb +27 -24
- data/lib/head_music/sign.rb +17 -10
- data/lib/head_music/spelling.rb +5 -8
- data/lib/head_music/tuning.rb +1 -4
- data/lib/head_music/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46aae73acea5e3331b19a08a02ded3c00fea5731e920207fc9d2b61dd9ebc359
|
|
4
|
+
data.tar.gz: c2cfdbb52641ad27bd96308fe99da5f59b2e66efdf2f40a4932e8bc6552510c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23a9cc4013e6058892effeeaf940947a90ce2e5950eb4c3c622b7036fecbaa14dd4cd4aa5d5dbe5b6ffdefe1ce8e37681b1d8d040e135b1a4c737fca23c84fa6
|
|
7
|
+
data.tar.gz: 15d30c56ea0b08db8bde6b0d69b38a9605751b370086e6a66db5a74029b1f4c87ab408b0e567e092f0e2418d4f65e1dfe5644358c0fd0f6f676548466b5544ec
|
data/.rubocop.yml
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
Layout/DotPosition:
|
|
2
2
|
EnforcedStyle: trailing
|
|
3
3
|
|
|
4
|
+
Layout/LineLength:
|
|
5
|
+
Max: 120
|
|
6
|
+
|
|
4
7
|
Metrics/BlockLength:
|
|
5
8
|
Exclude:
|
|
6
9
|
- 'Gemfile'
|
|
@@ -10,9 +13,6 @@ Metrics/BlockLength:
|
|
|
10
13
|
Metrics/ClassLength:
|
|
11
14
|
Max: 155
|
|
12
15
|
|
|
13
|
-
Metrics/LineLength:
|
|
14
|
-
Max: 120
|
|
15
|
-
|
|
16
16
|
Style/ClassAndModuleChildren:
|
|
17
17
|
EnforcedStyle: compact
|
|
18
18
|
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
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
|
|
|
@@ -40,6 +40,10 @@ class HeadMusic::ChromaticInterval
|
|
|
40
40
|
semitones
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def diatonic_name
|
|
44
|
+
NAMES[simple.semitones].gsub(/_/, ' ')
|
|
45
|
+
end
|
|
46
|
+
|
|
43
47
|
# diatonic set theory
|
|
44
48
|
alias specific_interval semitones
|
|
45
49
|
|
data/lib/head_music/circle.rb
CHANGED
|
@@ -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(
|
|
8
|
+
get(:perfect_fifth)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def self.of_fourths
|
|
12
|
-
get(
|
|
12
|
+
get(:perfect_fourth)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def self.get(interval =
|
|
15
|
+
def self.get(interval = :perfect_fifth)
|
|
16
16
|
@circles ||= {}
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
+
pitch_classes.index(HeadMusic::Spelling.get(pitch_class).pitch_class)
|
|
30
23
|
end
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
alias spellings_up spellings
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
def key_signatures_up
|
|
28
|
+
spellings_up.map { |spelling| HeadMusic::KeySignature.new(spelling) }
|
|
29
|
+
end
|
|
35
30
|
|
|
36
|
-
def
|
|
37
|
-
|
|
31
|
+
def key_signatures_down
|
|
32
|
+
spellings_down.map { |spelling| HeadMusic::KeySignature.new(spelling) }
|
|
38
33
|
end
|
|
39
34
|
|
|
40
|
-
def
|
|
41
|
-
|
|
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
|
|
@@ -2,35 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# An Interval Cycle is a collection of pitch classes created from a sequence of the same interval class.
|
|
4
4
|
class HeadMusic::IntervalCycle
|
|
5
|
+
attr_reader :interval, :starting_pitch
|
|
6
|
+
|
|
5
7
|
def self.get(interval = 7)
|
|
6
|
-
@
|
|
8
|
+
@interval_cycles ||= {}
|
|
7
9
|
interval = interval.to_s.gsub(/^C/i, '').to_i
|
|
8
|
-
|
|
10
|
+
interval = HeadMusic::ChromaticInterval.get(interval)
|
|
11
|
+
@interval_cycles[interval.to_i] ||= new(interval: interval, starting_pitch: 'C4')
|
|
9
12
|
end
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
def initialize(interval:, starting_pitch: 'C4')
|
|
15
|
+
@interval = interval if interval.is_a?(HeadMusic::DiatonicInterval)
|
|
16
|
+
@interval ||= interval if interval.is_a?(HeadMusic::ChromaticInterval)
|
|
17
|
+
@interval ||= HeadMusic::ChromaticInterval.get(interval) if interval.to_s.match?(/\d/)
|
|
18
|
+
@interval ||= HeadMusic::DiatonicInterval.get(interval)
|
|
19
|
+
@starting_pitch = HeadMusic::Pitch.get(starting_pitch)
|
|
20
|
+
end
|
|
12
21
|
|
|
13
|
-
def
|
|
14
|
-
@
|
|
15
|
-
@pitch_classes = pitch_classes_by_interval
|
|
22
|
+
def pitches
|
|
23
|
+
@pitches ||= pitches_up
|
|
16
24
|
end
|
|
17
25
|
|
|
18
|
-
def
|
|
19
|
-
@pitch_classes.
|
|
26
|
+
def pitch_classes
|
|
27
|
+
@pitch_classes ||= pitches.map(&:pitch_class)
|
|
20
28
|
end
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
def pitch_class_set
|
|
31
|
+
@pitch_class_set ||= HeadMusic::PitchClassSet.new(pitches)
|
|
32
|
+
end
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
def spellings
|
|
35
|
+
@spellings ||= pitches.map(&:spelling)
|
|
36
|
+
end
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
[HeadMusic::PitchClass.get(0)].tap do |list|
|
|
28
|
-
loop do
|
|
29
|
-
next_pitch_class = list.last + interval
|
|
30
|
-
break if next_pitch_class == list.first
|
|
38
|
+
protected
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
def pitches_up
|
|
41
|
+
@pitches_up ||= begin
|
|
42
|
+
[starting_pitch].tap do |list|
|
|
43
|
+
loop do
|
|
44
|
+
next_pitch = list.last + interval
|
|
45
|
+
next_pitch -= octave while next_pitch - starting_pitch > 12
|
|
46
|
+
break if next_pitch.pitch_class == list.first.pitch_class
|
|
47
|
+
|
|
48
|
+
list << next_pitch
|
|
49
|
+
end
|
|
33
50
|
end
|
|
34
51
|
end
|
|
35
52
|
end
|
|
53
|
+
|
|
54
|
+
def octave
|
|
55
|
+
@octave ||= HeadMusic::DiatonicInterval.get(:perfect_octave)
|
|
56
|
+
end
|
|
36
57
|
end
|
|
@@ -6,8 +6,8 @@ class HeadMusic::KeySignature
|
|
|
6
6
|
attr_reader :scale_type
|
|
7
7
|
attr_reader :scale
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
ORDERED_LETTER_NAMES_OF_SHARPS = %w[F C G D A E B].freeze
|
|
10
|
+
ORDERED_LETTER_NAMES_OF_FLATS = ORDERED_LETTER_NAMES_OF_SHARPS.reverse.freeze
|
|
11
11
|
|
|
12
12
|
def self.default
|
|
13
13
|
@default ||= new('C', :major)
|
|
@@ -39,19 +39,35 @@ class HeadMusic::KeySignature
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def sharps
|
|
42
|
-
spellings.select(&:sharp?).sort_by
|
|
42
|
+
spellings.select(&:sharp?).sort_by do |spelling|
|
|
43
|
+
ORDERED_LETTER_NAMES_OF_SHARPS.index(spelling.letter_name.to_s)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def double_sharps
|
|
48
|
+
spellings.select(&:double_sharp?).sort_by do |spelling|
|
|
49
|
+
ORDERED_LETTER_NAMES_OF_SHARPS.index(spelling.letter_name.to_s)
|
|
50
|
+
end
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
def flats
|
|
46
|
-
spellings.select(&:flat?).sort_by
|
|
54
|
+
spellings.select(&:flat?).sort_by do |spelling|
|
|
55
|
+
ORDERED_LETTER_NAMES_OF_FLATS.index(spelling.letter_name.to_s)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def double_flats
|
|
60
|
+
spellings.select(&:double_flat?).sort_by do |spelling|
|
|
61
|
+
ORDERED_LETTER_NAMES_OF_FLATS.index(spelling.letter_name.to_s)
|
|
62
|
+
end
|
|
47
63
|
end
|
|
48
64
|
|
|
49
65
|
def num_sharps
|
|
50
|
-
sharps.length
|
|
66
|
+
sharps.length + double_sharps.length * 2
|
|
51
67
|
end
|
|
52
68
|
|
|
53
69
|
def num_flats
|
|
54
|
-
flats.length
|
|
70
|
+
flats.length + double_flats.length * 2
|
|
55
71
|
end
|
|
56
72
|
|
|
57
73
|
def signs
|
data/lib/head_music/named.rb
CHANGED
|
@@ -25,5 +25,21 @@ module HeadMusic::Named
|
|
|
25
25
|
key = HeadMusic::Utilities::HashKey.for(name)
|
|
26
26
|
@instances_by_name[key] ||= new(name)
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
def aliases
|
|
30
|
+
{}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# An Alias encapsulates an alternative name for a rudiment.
|
|
35
|
+
class Alias
|
|
36
|
+
attr_reader :key, :name, :abbreviation, :locale
|
|
37
|
+
|
|
38
|
+
def initialize(key:, name:, abbreviation: nil, locale: nil)
|
|
39
|
+
@key = key
|
|
40
|
+
@name = name
|
|
41
|
+
@abbreviation = abbreviation
|
|
42
|
+
@locale = locale
|
|
43
|
+
end
|
|
28
44
|
end
|
|
29
45
|
end
|
data/lib/head_music/pitch.rb
CHANGED
|
@@ -21,8 +21,17 @@ class HeadMusic::Pitch
|
|
|
21
21
|
delegate :enharmonic_equivalent?, :enharmonic?, to: :enharmonic_equivalence
|
|
22
22
|
delegate :octave_equivalent?, to: :octave_equivalence
|
|
23
23
|
|
|
24
|
+
# Fetches a pitch identified by the information passed in.
|
|
25
|
+
#
|
|
26
|
+
# Accepts:
|
|
27
|
+
# - a Pitch instance
|
|
28
|
+
# - a PitchClass instance
|
|
29
|
+
# - a name string, such as 'Ab4'
|
|
30
|
+
# - a number corresponding to the midi note number
|
|
24
31
|
def self.get(value)
|
|
25
|
-
from_pitch_class(value) ||
|
|
32
|
+
from_pitch_class(value) ||
|
|
33
|
+
from_name(value) ||
|
|
34
|
+
from_number(value)
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
def self.middle_c
|
|
@@ -48,9 +57,7 @@ class HeadMusic::Pitch
|
|
|
48
57
|
def self.from_number(number)
|
|
49
58
|
return nil unless number == number.to_i
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
octave = (number.to_i / 12) - 1
|
|
53
|
-
fetch_or_create(spelling, octave)
|
|
60
|
+
fetch_or_create(HeadMusic::Spelling.from_number(number), (number.to_i / 12) - 1)
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
def self.from_number_and_letter(number, letter_name)
|
|
@@ -7,8 +7,8 @@ class HeadMusic::PitchClass
|
|
|
7
7
|
attr_reader :number
|
|
8
8
|
attr_reader :spelling
|
|
9
9
|
|
|
10
|
-
SHARP_SPELLINGS = %w[C C
|
|
11
|
-
FLAT_SPELLINGS = %w[C
|
|
10
|
+
SHARP_SPELLINGS = %w[C C♯ D D♯ E F F♯ G G♯ A A♯ B].freeze
|
|
11
|
+
FLAT_SPELLINGS = %w[C D♭ D E♭ E F G♭ G A♭ A B♭ B].freeze
|
|
12
12
|
INTEGER_NOTATION = %w[0 1 2 3 4 5 6 7 8 9 t e].freeze
|
|
13
13
|
|
|
14
14
|
def self.get(identifier)
|
|
@@ -24,29 +24,30 @@ class HeadMusic::ReferencePitch
|
|
|
24
24
|
{ name: 'Chorton', pitch: 'A4', frequency: 466.0 },
|
|
25
25
|
].freeze
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}.freeze
|
|
45
|
-
|
|
46
|
-
NAMED_REFERENCE_PITCH_NAMES = NAMED_REFERENCE_PITCHES.map { |pitch_data| pitch_data[:name] }
|
|
27
|
+
ALIAS_DATA = [
|
|
28
|
+
{ key: :baroque, name: 'Kammerton' },
|
|
29
|
+
{ key: :classical, name: 'Haydn' },
|
|
30
|
+
{ key: :classical, name: 'Mozart' },
|
|
31
|
+
{ key: :scientific, name: 'philosophical' },
|
|
32
|
+
{ key: :scientific, name: 'Sauveur' },
|
|
33
|
+
{ key: :scientific, name: 'Schiller' },
|
|
34
|
+
{ key: :french, name: 'continental' },
|
|
35
|
+
{ key: :french, name: 'international' },
|
|
36
|
+
{ key: :new_philharmonic, name: 'low' },
|
|
37
|
+
{ key: :old_philharmonic, name: 'high' },
|
|
38
|
+
{ key: :a440, name: 'concert' },
|
|
39
|
+
{ key: :a440, name: 'Stuttgart' },
|
|
40
|
+
{ key: :a440, name: 'Scheibler' },
|
|
41
|
+
{ key: :a440, name: 'ISO 16' },
|
|
42
|
+
{ key: :chorton, name: 'choir' },
|
|
43
|
+
].freeze
|
|
47
44
|
|
|
48
45
|
attr_reader :pitch, :frequency
|
|
49
46
|
|
|
47
|
+
def self.aliases
|
|
48
|
+
@aliases ||= ALIAS_DATA.map { |attributes| HeadMusic::Named::Alias.new(attributes) }
|
|
49
|
+
end
|
|
50
|
+
|
|
50
51
|
def self.get(name)
|
|
51
52
|
return name if name.is_a?(self)
|
|
52
53
|
get_by_name(name)
|
|
@@ -56,7 +57,7 @@ class HeadMusic::ReferencePitch
|
|
|
56
57
|
@name = name.to_s
|
|
57
58
|
reference_pitch_data = NAMED_REFERENCE_PITCHES.detect do |candidate|
|
|
58
59
|
candidate_name_key = HeadMusic::Utilities::HashKey.for(candidate[:name])
|
|
59
|
-
[candidate_name_key, candidate_name_key.to_s.delete('_').to_sym].include?(
|
|
60
|
+
[candidate_name_key, candidate_name_key.to_s.delete('_').to_sym].include?(normalized_key)
|
|
60
61
|
end || {}
|
|
61
62
|
@pitch = HeadMusic::Pitch.get(reference_pitch_data.fetch(:pitch, DEFAULT_PITCH_NAME))
|
|
62
63
|
@frequency = reference_pitch_data.fetch(:frequency, DEFAULT_FREQUENCY)
|
|
@@ -78,10 +79,12 @@ class HeadMusic::ReferencePitch
|
|
|
78
79
|
|
|
79
80
|
private
|
|
80
81
|
|
|
81
|
-
def
|
|
82
|
-
@
|
|
82
|
+
def normalized_key
|
|
83
|
+
@normalized_key ||= begin
|
|
83
84
|
key = HeadMusic::Utilities::HashKey.for(name.to_s.gsub(/\W?(pitch|tuning|tone)/, ''))
|
|
84
|
-
|
|
85
|
+
HeadMusic::ReferencePitch.aliases.detect do |alias_data|
|
|
86
|
+
HeadMusic::Utilities::HashKey.for(alias_data.name) == key
|
|
87
|
+
end&.key || key
|
|
85
88
|
end
|
|
86
89
|
end
|
|
87
90
|
end
|
data/lib/head_music/sign.rb
CHANGED
|
@@ -8,14 +8,20 @@ class HeadMusic::Sign
|
|
|
8
8
|
|
|
9
9
|
attr_reader :identifier, :cents, :musical_symbol
|
|
10
10
|
|
|
11
|
+
delegate :ascii, :unicode, :html_entity, to: :musical_symbol
|
|
12
|
+
|
|
13
|
+
SIGN_DATA = [
|
|
14
|
+
{ identifier: :sharp, ascii: '#', unicode: '♯', html_entity: '♯', cents: 100 },
|
|
15
|
+
{ identifier: :flat, ascii: 'b', unicode: '♭', html_entity: '♭', cents: -100 },
|
|
16
|
+
{ identifier: :natural, ascii: '', unicode: '♮', html_entity: '♮', cents: 0 },
|
|
17
|
+
{ identifier: :double_sharp, ascii: 'x', unicode: '𝄪', html_entity: '𝄪', cents: 200 },
|
|
18
|
+
{ identifier: :double_flat, ascii: 'bb', unicode: '𝄫', html_entity: '𝄫', cents: -200 },
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
SIGN_IDENTIFIERS = SIGN_DATA.map { |attributes| attributes[:identifier] }.freeze
|
|
22
|
+
|
|
11
23
|
def self.all
|
|
12
|
-
|
|
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
|
-
]
|
|
24
|
+
SIGN_DATA.map { |attributes| new(attributes) }
|
|
19
25
|
end
|
|
20
26
|
|
|
21
27
|
def self.symbols
|
|
@@ -57,6 +63,10 @@ class HeadMusic::Sign
|
|
|
57
63
|
cents / 100.0
|
|
58
64
|
end
|
|
59
65
|
|
|
66
|
+
SIGN_IDENTIFIERS.each do |key|
|
|
67
|
+
define_method(:"#{key}?") { identifier == key }
|
|
68
|
+
end
|
|
69
|
+
|
|
60
70
|
def to_s
|
|
61
71
|
unicode
|
|
62
72
|
end
|
|
@@ -66,14 +76,11 @@ class HeadMusic::Sign
|
|
|
66
76
|
cents <=> other.cents
|
|
67
77
|
end
|
|
68
78
|
|
|
69
|
-
delegate :ascii, :html_entity, :unicode, to: :musical_symbol
|
|
70
|
-
|
|
71
79
|
private
|
|
72
80
|
|
|
73
81
|
def initialize(attributes)
|
|
74
82
|
@identifier = attributes[:identifier]
|
|
75
83
|
@cents = attributes[:cents]
|
|
76
|
-
|
|
77
84
|
@musical_symbol = HeadMusic::MusicalSymbol.new(
|
|
78
85
|
unicode: attributes[:unicode],
|
|
79
86
|
ascii: attributes[:ascii],
|
data/lib/head_music/spelling.rb
CHANGED
|
@@ -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
|
data/lib/head_music/tuning.rb
CHANGED
|
@@ -12,12 +12,9 @@ class HeadMusic::Tuning
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def frequency_for(pitch)
|
|
15
|
-
pitch = HeadMusic::Pitch.get(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
|
data/lib/head_music/version.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.
|
|
4
|
+
version: 0.24.0
|
|
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-
|
|
11
|
+
date: 2020-03-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|