head_music 0.23.4 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|