head_music 0.1.1 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/head_music/accidental.rb +3 -3
- data/lib/head_music/interval.rb +1 -1
- data/lib/head_music/key_signature.rb +2 -2
- data/lib/head_music/letter.rb +3 -3
- data/lib/head_music/octave.rb +42 -0
- data/lib/head_music/pitch.rb +11 -4
- data/lib/head_music/pitch_class.rb +4 -3
- data/lib/head_music/quality.rb +22 -0
- data/lib/head_music/scale_type.rb +83 -0
- data/lib/head_music/spelling.rb +22 -13
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8379f9a907ea8dd73018b1cd9a8da6a055510735
|
4
|
+
data.tar.gz: f3de473d170a910e26a5b4318232a5a9d7eb4258
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05c161185607fd4f4ac48b5ad9389ee6d15fda24deaed1bf1a8f4e88b7140615ef60cdf695266c6e6a714cf66dc9db3a5f47e056c7c6096a9c0fde6e0d5fb0fa
|
7
|
+
data.tar.gz: f7142b9a29669c3a885c6ee9edf68330c9912ff8e71622efb1d95f7c3f314dea05dc2443c5f1c092988ceb236ba7a7768ac28a0ba43651c5954085595e57b295
|
@@ -10,15 +10,15 @@ class HeadMusic::Accidental
|
|
10
10
|
|
11
11
|
def self.get(identifier)
|
12
12
|
@accidentals ||= {}
|
13
|
-
|
13
|
+
for_symbol(identifier) || for_interval(identifier)
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.for_symbol(identifier)
|
17
|
-
new(identifier) if ACCIDENTAL_SEMITONES.keys.include?(identifier)
|
17
|
+
@accidentals[identifier.to_s] ||= new(identifier.to_s) if ACCIDENTAL_SEMITONES.keys.include?(identifier.to_s)
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.for_interval(semitones)
|
21
|
-
ACCIDENTAL_SEMITONES.key(semitones.to_i)
|
21
|
+
@accidentals[semitones.to_i] ||= ACCIDENTAL_SEMITONES.key(semitones.to_i)
|
22
22
|
end
|
23
23
|
|
24
24
|
def initialize(string)
|
data/lib/head_music/interval.rb
CHANGED
@@ -7,9 +7,9 @@ class HeadMusic::KeySignature
|
|
7
7
|
|
8
8
|
delegate :pitch_class, to: :tonic_spelling, prefix: :tonic
|
9
9
|
|
10
|
-
def initialize(tonic_spelling, scale_type =
|
10
|
+
def initialize(tonic_spelling, scale_type = nil)
|
11
11
|
@tonic_spelling = tonic_spelling
|
12
|
-
@scale_type = scale_type
|
12
|
+
@scale_type = scale_type || :major
|
13
13
|
end
|
14
14
|
|
15
15
|
def sharps
|
data/lib/head_music/letter.rb
CHANGED
@@ -19,12 +19,12 @@ class HeadMusic::Letter
|
|
19
19
|
|
20
20
|
def self.get(identifier)
|
21
21
|
@letters ||= {}
|
22
|
-
|
22
|
+
from_name(identifier) || from_pitch_class(identifier)
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.from_name(name)
|
26
26
|
name = name.to_s.first.upcase
|
27
|
-
new(name) if NAMES.include?(name)
|
27
|
+
@letters[name] ||= new(name) if NAMES.include?(name)
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.from_pitch_class(pitch_class)
|
@@ -33,7 +33,7 @@ class HeadMusic::Letter
|
|
33
33
|
pitch_class = pitch_class.to_i % 12
|
34
34
|
name = NAMES.detect { |name| pitch_class == NATURAL_PITCH_CLASS_NUMBERS[name] }
|
35
35
|
name ||= HeadMusic::PitchClass::PREFERRED_SPELLINGS[pitch_class].first
|
36
|
-
|
36
|
+
@letters[name] ||= new(name) if NAMES.include?(name)
|
37
37
|
end
|
38
38
|
|
39
39
|
attr_reader :name
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class HeadMusic::Octave
|
2
|
+
include Comparable
|
3
|
+
|
4
|
+
DEFAULT = 4
|
5
|
+
|
6
|
+
MATCHER = /(-?\d+)$/
|
7
|
+
|
8
|
+
def self.get(identifier)
|
9
|
+
@octaves ||= {}
|
10
|
+
from_number(identifier) || from_name(identifier) || default
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_number(identifier)
|
14
|
+
return nil unless identifier.to_s == identifier.to_i.to_s
|
15
|
+
return nil unless (-2..12).include?(identifier.to_i)
|
16
|
+
@octaves[identifier.to_i] ||= new(identifier.to_i)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_name(string)
|
20
|
+
if string.to_s.match(HeadMusic::Spelling::MATCHER)
|
21
|
+
_letter, _accidental, octave_string = string.to_s.match(HeadMusic::Spelling::MATCHER).captures
|
22
|
+
@octaves[octave_string.to_i] ||= new(octave_string.to_i) if octave_string
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.default
|
27
|
+
@octaves[DEFAULT] ||= new(DEFAULT)
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :number
|
31
|
+
delegate :to_i, :to_s, to: :number
|
32
|
+
|
33
|
+
def initialize(number)
|
34
|
+
@number ||= number
|
35
|
+
end
|
36
|
+
|
37
|
+
def <=>(other)
|
38
|
+
self.to_i <=> other.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
private_class_method :new
|
42
|
+
end
|
data/lib/head_music/pitch.rb
CHANGED
@@ -13,16 +13,21 @@ class HeadMusic::Pitch
|
|
13
13
|
|
14
14
|
def self.from_name(name)
|
15
15
|
return nil unless name == name.to_s
|
16
|
-
|
17
|
-
octave = name.scan(/-?\d+$/).first.to_i
|
18
|
-
new(spelling, octave) if spelling && (-1..9).include?(octave)
|
16
|
+
fetch_or_create(HeadMusic::Spelling.get(name), HeadMusic::Octave.get(name).to_i)
|
19
17
|
end
|
20
18
|
|
21
19
|
def self.from_number(number)
|
22
20
|
return nil unless number == number.to_i
|
23
21
|
spelling = HeadMusic::Spelling.from_number(number)
|
24
22
|
octave = (number.to_i / 12) - 1
|
25
|
-
|
23
|
+
fetch_or_create(spelling, octave)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.fetch_or_create(spelling, octave)
|
27
|
+
if spelling && (-1..9).include?(octave)
|
28
|
+
key = [spelling, octave].join
|
29
|
+
@pitches[key] ||= new(spelling, octave)
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
33
|
def initialize(spelling, octave)
|
@@ -38,6 +43,8 @@ class HeadMusic::Pitch
|
|
38
43
|
(octave + 1) * 12 + pitch_class.to_i
|
39
44
|
end
|
40
45
|
|
46
|
+
alias_method :midi, :midi_note_number
|
47
|
+
|
41
48
|
def to_s
|
42
49
|
name
|
43
50
|
end
|
@@ -3,9 +3,10 @@ class HeadMusic::PitchClass
|
|
3
3
|
|
4
4
|
PREFERRED_SPELLINGS = %w[C C# D Eb E F F# G Ab A Bb B]
|
5
5
|
|
6
|
-
def self.get(
|
6
|
+
def self.get(identifier)
|
7
7
|
@pitch_classes ||= {}
|
8
|
-
number =
|
8
|
+
number = Spelling.get(identifier).pitch_class.to_i if Spelling.match(identifier)
|
9
|
+
number ||= identifier.to_i % 12
|
9
10
|
@pitch_classes[number] ||= new(number)
|
10
11
|
end
|
11
12
|
|
@@ -14,7 +15,7 @@ class HeadMusic::PitchClass
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def initialize(pitch_class_or_midi_number)
|
17
|
-
@number = pitch_class_or_midi_number % 12
|
18
|
+
@number = pitch_class_or_midi_number.to_i % 12
|
18
19
|
end
|
19
20
|
|
20
21
|
def to_i
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class HeadMusic::Quality
|
2
|
+
QUALITY_NAMES = %w[perfect major minor diminished augmented].map(&:to_sym)
|
3
|
+
|
4
|
+
def self.get(identifier)
|
5
|
+
@qualities ||= {}
|
6
|
+
identifier = identifier.to_s.to_sym
|
7
|
+
@qualities[identifier] ||= new(identifier) if QUALITY_NAMES.include?(identifier)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
delegate :to_s, to: :name
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
self.to_s == other.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
private_class_method :new
|
22
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class HeadMusic::ScaleType
|
2
|
+
H = 1 # whole step
|
3
|
+
W = 2 # half step
|
4
|
+
WH = W + H # augmented second
|
5
|
+
|
6
|
+
# Modal
|
7
|
+
I = [W, W, H, W, W, W, H]
|
8
|
+
II = I.rotate
|
9
|
+
III = I.rotate(2)
|
10
|
+
IV = I.rotate(3)
|
11
|
+
V = I.rotate(4)
|
12
|
+
VI = I.rotate(5)
|
13
|
+
VII = I.rotate(6)
|
14
|
+
|
15
|
+
# Tonal
|
16
|
+
HARMONIC_MINOR = [W, H, W, W, H, WH, H]
|
17
|
+
MELODIC_MINOR_ASCENDING = [W, H, W, W, W, W, H]
|
18
|
+
|
19
|
+
CHROMATIC = [H, H, H, H, H, H, H, H, H, H, H, H]
|
20
|
+
MINOR_PENTATONIC = [3, 2, 2, 3, 2]
|
21
|
+
MAJOR_PENTATONIC = MINOR_PENTATONIC.rotate
|
22
|
+
|
23
|
+
MODE_NAMES = {
|
24
|
+
i: [:ionian, :major, :maj],
|
25
|
+
ii: [:dorian],
|
26
|
+
iii: [:phrygian],
|
27
|
+
iv: [:lydian],
|
28
|
+
v: [:mixolydian],
|
29
|
+
vi: [:aeolian, :minor, :natural_minor, :min],
|
30
|
+
vii: [:locrian],
|
31
|
+
}
|
32
|
+
SCALE_TYPES = {}
|
33
|
+
MODE_NAMES.each do |roman_numeral, aliases|
|
34
|
+
intervals = { ascending: const_get(roman_numeral.upcase) }
|
35
|
+
SCALE_TYPES[roman_numeral] = intervals
|
36
|
+
aliases.each do |name|
|
37
|
+
SCALE_TYPES[name.to_sym] = intervals
|
38
|
+
end
|
39
|
+
end
|
40
|
+
SCALE_TYPES[:harmonic_minor] = { ascending: HARMONIC_MINOR }
|
41
|
+
SCALE_TYPES[:melodic_minor] = { ascending: MELODIC_MINOR_ASCENDING, descending: VI.reverse }
|
42
|
+
|
43
|
+
SCALE_TYPES[:chromatic] = { ascending: CHROMATIC }
|
44
|
+
|
45
|
+
SCALE_TYPES[:minor_pentatonic] = { ascending: MINOR_PENTATONIC }
|
46
|
+
SCALE_TYPES[:major_pentatonic] = { ascending: MAJOR_PENTATONIC }
|
47
|
+
|
48
|
+
SCALE_TYPES[:octatonic] = { ascending: [W, H, W, H, W, H, W, H] }
|
49
|
+
SCALE_TYPES[:whole_tone] = { ascending: [W, W, W, W, W, W] }
|
50
|
+
SCALE_TYPES[:monotonic] = { ascending: [12] }
|
51
|
+
|
52
|
+
class << self
|
53
|
+
SCALE_TYPES.keys.each do |name|
|
54
|
+
define_method(name) do
|
55
|
+
self.get(name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.get(name)
|
61
|
+
@scale_types ||= {}
|
62
|
+
name = name.to_s.to_sym
|
63
|
+
intervals = SCALE_TYPES[name]
|
64
|
+
@scale_types[name] ||= new(name, intervals[:ascending], intervals[:descending])
|
65
|
+
end
|
66
|
+
|
67
|
+
attr_reader :name, :ascending_intervals, :descending_intervals
|
68
|
+
delegate :to_s, to: :name
|
69
|
+
|
70
|
+
def initialize(name, ascending_intervals, descending_intervals = nil)
|
71
|
+
@name = name
|
72
|
+
@ascending_intervals = ascending_intervals
|
73
|
+
@descending_intervals = descending_intervals || ascending_intervals.reverse
|
74
|
+
end
|
75
|
+
|
76
|
+
def ==(other)
|
77
|
+
self.state == other.state
|
78
|
+
end
|
79
|
+
|
80
|
+
def state
|
81
|
+
[ascending_intervals, descending_intervals]
|
82
|
+
end
|
83
|
+
end
|
data/lib/head_music/spelling.rb
CHANGED
@@ -1,24 +1,30 @@
|
|
1
|
-
# A Spelling is a pitch class with a letter and an accidental
|
1
|
+
# A Spelling is a pitch class with a letter and possibly an accidental
|
2
2
|
|
3
3
|
class HeadMusic::Spelling
|
4
|
+
MATCHER = /^\s*([A-G])([b#]*)(\-?\d+)?\s*$/
|
5
|
+
|
6
|
+
attr_reader :pitch_class
|
4
7
|
attr_reader :letter
|
5
8
|
attr_reader :accidental
|
6
|
-
attr_reader :pitch_class
|
7
9
|
|
8
|
-
|
10
|
+
delegate :number, to: :pitch_class, prefix: true
|
9
11
|
|
10
12
|
def self.get(identifier)
|
11
13
|
@spellings ||= {}
|
12
|
-
|
14
|
+
from_name(identifier) || from_number(identifier)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.match(string)
|
18
|
+
string.to_s.match(MATCHER)
|
13
19
|
end
|
14
20
|
|
15
21
|
def self.from_name(name)
|
16
|
-
|
17
|
-
|
18
|
-
if match
|
19
|
-
letter_name, accidental_string, _octave = match.captures
|
22
|
+
if match(name)
|
23
|
+
letter_name, accidental_string, _octave = match(name).captures
|
20
24
|
letter = HeadMusic::Letter.get(letter_name)
|
21
|
-
|
25
|
+
return nil unless letter
|
26
|
+
accidental = HeadMusic::Accidental.get(accidental_string)
|
27
|
+
fetch_or_create(letter, accidental)
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -26,10 +32,13 @@ class HeadMusic::Spelling
|
|
26
32
|
return nil unless number == number.to_i
|
27
33
|
pitch_class_number = number.to_i % 12
|
28
34
|
letter = HeadMusic::Letter.from_pitch_class(pitch_class_number)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
accidental = HeadMusic::Accidental.for_interval(pitch_class_number - letter.pitch_class.to_i)
|
36
|
+
fetch_or_create(letter, accidental)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.fetch_or_create(letter, accidental)
|
40
|
+
key = [letter, accidental].join
|
41
|
+
@spellings[key] ||= new(letter, accidental)
|
33
42
|
end
|
34
43
|
|
35
44
|
def initialize(letter, accidental = nil)
|
data/lib/head_music/version.rb
CHANGED
data/lib/head_music.rb
CHANGED
@@ -8,9 +8,12 @@ require "head_music/circle"
|
|
8
8
|
require "head_music/interval"
|
9
9
|
require "head_music/key_signature"
|
10
10
|
require "head_music/letter"
|
11
|
+
require "head_music/octave"
|
11
12
|
require "head_music/pitch_class"
|
12
13
|
require "head_music/pitch"
|
14
|
+
require "head_music/quality"
|
13
15
|
require "head_music/scale"
|
16
|
+
require "head_music/scale_type"
|
14
17
|
require "head_music/spelling"
|
15
18
|
|
16
19
|
module HeadMusic
|
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.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Head
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -105,9 +105,12 @@ files:
|
|
105
105
|
- lib/head_music/interval.rb
|
106
106
|
- lib/head_music/key_signature.rb
|
107
107
|
- lib/head_music/letter.rb
|
108
|
+
- lib/head_music/octave.rb
|
108
109
|
- lib/head_music/pitch.rb
|
109
110
|
- lib/head_music/pitch_class.rb
|
111
|
+
- lib/head_music/quality.rb
|
110
112
|
- lib/head_music/scale.rb
|
113
|
+
- lib/head_music/scale_type.rb
|
111
114
|
- lib/head_music/spelling.rb
|
112
115
|
- lib/head_music/version.rb
|
113
116
|
homepage: https://github.com/roberthead/head_music
|