head_music 0.5.3 → 0.5.4
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/clef.rb +54 -0
- data/lib/head_music/functional_interval.rb +2 -2
- data/lib/head_music/letter_name.rb +6 -8
- data/lib/head_music/note.rb +14 -0
- data/lib/head_music/pitch.rb +22 -6
- data/lib/head_music/rhythmic_unit.rb +59 -0
- data/lib/head_music/rhythmic_value.rb +38 -41
- data/lib/head_music/scale.rb +7 -7
- data/lib/head_music/spelling.rb +19 -20
- 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: 26fa77af760e9919b585bfb9db2e53d30b382ecf
|
|
4
|
+
data.tar.gz: 244d2c9e9d85800cfc0f897773e17d4a1410c885
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89befc698de10095285b790246b6102bcd6d1ca94c3cf50ea19ee39c9f60e00de277c954172a652e7af1209d3660d33e8d002111aa1c4a426e3be82a574d683e
|
|
7
|
+
data.tar.gz: 71e3d3c4ed45c6b7407d077fb500227d1a372518190bd3cc3400d8bcdb49aea803c2d11b8815c4568b44ef2e5b5a2cc0022e1bef9eabee0eedb7c6ab5d23ec22
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class HeadMusic::Clef
|
|
2
|
+
CLEFS = [
|
|
3
|
+
{ pitch: 'G4', line: 2, names: ['treble', 'G-clef'], modern: true },
|
|
4
|
+
{ pitch: 'G4', line: 1, names: ['French', 'French violin'] },
|
|
5
|
+
{ pitch: 'G3', line: 2, names: ['tenor'], modern: true },
|
|
6
|
+
|
|
7
|
+
{ pitch: 'F3', line: 3, names: ['baritone'] },
|
|
8
|
+
{ pitch: 'F3', line: 4, names: ['bass', 'F-clef'], modern: true },
|
|
9
|
+
{ pitch: 'F3', line: 5, names: ['sub-bass'] },
|
|
10
|
+
|
|
11
|
+
{ pitch: 'C4', line: 1, names: ['soprano'] },
|
|
12
|
+
{ pitch: 'C4', line: 2, names: ['mezzo-soprano'] },
|
|
13
|
+
{ pitch: 'C4', line: 3, names: ['alto', 'viola', 'counter-tenor', 'countertenor'], modern: true },
|
|
14
|
+
{ pitch: 'C4', line: 4, names: ['tenor'], modern: true },
|
|
15
|
+
{ pitch: 'C4', line: 5, names: ['baritone'] },
|
|
16
|
+
|
|
17
|
+
{ pitch: nil, line: 3, names: ['neutral', 'percussion'] }
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def self.get(name)
|
|
21
|
+
name = name.to_s
|
|
22
|
+
@clefs ||= {}
|
|
23
|
+
@clefs[name.to_s.to_sym] = new(name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :name, :pitch, :line
|
|
27
|
+
|
|
28
|
+
def initialize(name)
|
|
29
|
+
@name = name.to_s
|
|
30
|
+
clef_data = CLEFS.detect { |clef| clef[:names].include?(name) }
|
|
31
|
+
@pitch = HeadMusic::Pitch.get(clef_data[:pitch])
|
|
32
|
+
@line = clef_data[:line]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def clef_type
|
|
36
|
+
"#{pitch.letter_name}-clef"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def line_pitch(line_number)
|
|
40
|
+
@line_pitches ||= {}
|
|
41
|
+
@line_pitches[line_number] ||= begin
|
|
42
|
+
steps = (line_number - line) * 2
|
|
43
|
+
pitch.natural_steps(steps)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def space_pitch(space_number)
|
|
48
|
+
@space_pitches ||= {}
|
|
49
|
+
@space_pitches[space_number] ||= begin
|
|
50
|
+
steps = (space_number - line) * 2 + 1
|
|
51
|
+
pitch.natural_steps(steps)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -33,7 +33,7 @@ class HeadMusic::FunctionalInterval
|
|
|
33
33
|
quality_name, degree_name = words[0..-2].join(' '), words.last
|
|
34
34
|
lower_pitch = HeadMusic::Pitch.get('C4')
|
|
35
35
|
steps = NUMBER_NAMES.index(degree_name)
|
|
36
|
-
higher_letter = lower_pitch.
|
|
36
|
+
higher_letter = lower_pitch.letter_name.steps(steps)
|
|
37
37
|
semitones = degree_quality_semitones.dig(degree_name.to_sym, quality_name.to_sym)
|
|
38
38
|
higher_pitch = HeadMusic::Pitch.from_number_and_letter(lower_pitch + semitones, higher_letter)
|
|
39
39
|
new(lower_pitch, higher_pitch)
|
|
@@ -70,7 +70,7 @@ class HeadMusic::FunctionalInterval
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def simple_number
|
|
73
|
-
@simple_number ||= @lower_pitch.
|
|
73
|
+
@simple_number ||= @lower_pitch.letter_name.steps_to(@higher_pitch.letter_name) + 1
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def simple_semitones
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
class HeadMusic::LetterName
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
NAMES = ('A'..'G').to_a
|
|
2
|
+
NAMES = %w[C D E F G A B]
|
|
5
3
|
|
|
6
4
|
NATURAL_PITCH_CLASS_NUMBERS = {
|
|
7
5
|
'C' => 0,
|
|
@@ -23,18 +21,18 @@ class HeadMusic::LetterName
|
|
|
23
21
|
singleton_class.send(:alias_method, :[], :get)
|
|
24
22
|
|
|
25
23
|
def self.from_name(name)
|
|
26
|
-
@
|
|
24
|
+
@letter_names ||= {}
|
|
27
25
|
name = name.to_s.first.upcase
|
|
28
|
-
@
|
|
26
|
+
@letter_names[name] ||= new(name) if NAMES.include?(name)
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
def self.from_pitch_class(pitch_class)
|
|
32
|
-
@
|
|
30
|
+
@letter_names ||= {}
|
|
33
31
|
return nil if pitch_class.to_s == pitch_class
|
|
34
32
|
pitch_class = pitch_class.to_i % 12
|
|
35
33
|
name = NAMES.detect { |name| pitch_class == NATURAL_PITCH_CLASS_NUMBERS[name] }
|
|
36
34
|
name ||= HeadMusic::PitchClass::PREFERRED_SPELLINGS[pitch_class].first
|
|
37
|
-
@
|
|
35
|
+
@letter_names[name] ||= new(name) if NAMES.include?(name)
|
|
38
36
|
end
|
|
39
37
|
|
|
40
38
|
attr_reader :name
|
|
@@ -60,7 +58,7 @@ class HeadMusic::LetterName
|
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
def steps(num)
|
|
63
|
-
cycle[num]
|
|
61
|
+
HeadMusic::LetterName.get(cycle[num % NAMES.length])
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
def steps_to(other, direction = :ascending)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class HeadMusic::Note
|
|
2
|
+
attr_reader :pitch, :rhythmic_value
|
|
3
|
+
|
|
4
|
+
delegate :ticks, to: :rhythmic_value
|
|
5
|
+
|
|
6
|
+
def initialize(pitch, rhythmic_unit, rhythmic_value_modifiers = {})
|
|
7
|
+
@pitch = HeadMusic::Pitch.get(pitch)
|
|
8
|
+
@rhythmic_value = HeadMusic::RhythmicValue.new(rhythmic_unit, rhythmic_value_modifiers)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def duration
|
|
12
|
+
rhythmic_value.total_value
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/head_music/pitch.rb
CHANGED
|
@@ -4,7 +4,7 @@ class HeadMusic::Pitch
|
|
|
4
4
|
attr_reader :spelling
|
|
5
5
|
attr_reader :octave
|
|
6
6
|
|
|
7
|
-
delegate :
|
|
7
|
+
delegate :letter_name, :letter_name_cycle, to: :spelling
|
|
8
8
|
delegate :accidental, :sharp?, :flat?, to: :spelling
|
|
9
9
|
delegate :pitch_class, to: :spelling
|
|
10
10
|
delegate :semitones, to: :accidental, prefix: true, allow_nil: true
|
|
@@ -28,14 +28,14 @@ class HeadMusic::Pitch
|
|
|
28
28
|
fetch_or_create(spelling, octave)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def self.from_number_and_letter(number,
|
|
32
|
-
|
|
33
|
-
natural_letter_pitch = get(HeadMusic::LetterName.get(
|
|
31
|
+
def self.from_number_and_letter(number, letter_name)
|
|
32
|
+
letter_name = HeadMusic::LetterName.get(letter_name)
|
|
33
|
+
natural_letter_pitch = get(HeadMusic::LetterName.get(letter_name).pitch_class)
|
|
34
34
|
natural_letter_pitch += 12 while (number - natural_letter_pitch.to_i).to_i >= 11
|
|
35
35
|
natural_letter_pitch = get(natural_letter_pitch)
|
|
36
36
|
accidental_interval = natural_letter_pitch.smallest_interval_to(HeadMusic::PitchClass.get(number))
|
|
37
37
|
accidental = HeadMusic::Accidental.for_interval(accidental_interval)
|
|
38
|
-
spelling = HeadMusic::Spelling.fetch_or_create(
|
|
38
|
+
spelling = HeadMusic::Spelling.fetch_or_create(letter_name, accidental)
|
|
39
39
|
fetch_or_create(spelling, natural_letter_pitch.octave)
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -57,7 +57,7 @@ class HeadMusic::Pitch
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def midi_note_number
|
|
60
|
-
(octave + 1) * 12 +
|
|
60
|
+
(octave + 1) * 12 + letter_name.pitch_class.to_i + accidental_semitones.to_i
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
alias_method :midi, :midi_note_number
|
|
@@ -71,6 +71,10 @@ class HeadMusic::Pitch
|
|
|
71
71
|
midi_note_number
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
def natural
|
|
75
|
+
HeadMusic::Pitch.get(self.to_s.gsub(/[#b]/, ''))
|
|
76
|
+
end
|
|
77
|
+
|
|
74
78
|
def enharmonic?(other)
|
|
75
79
|
self.midi_note_number == other.midi_note_number
|
|
76
80
|
end
|
|
@@ -101,5 +105,17 @@ class HeadMusic::Pitch
|
|
|
101
105
|
HeadMusic::Scale.get(self, scale_type_name)
|
|
102
106
|
end
|
|
103
107
|
|
|
108
|
+
def natural_steps(num_steps)
|
|
109
|
+
target_letter_name = self.letter_name.steps(num_steps)
|
|
110
|
+
direction = num_steps >= 0 ? 1 : -1
|
|
111
|
+
octaves_delta = (num_steps.abs / 7) * direction
|
|
112
|
+
if num_steps < 0 && target_letter_name.position > letter_name.position
|
|
113
|
+
octaves_delta -= 1
|
|
114
|
+
elsif num_steps > 0 && target_letter_name.position < letter_name.position
|
|
115
|
+
octaves_delta += 1
|
|
116
|
+
end
|
|
117
|
+
HeadMusic::Pitch.get([target_letter_name, octave + octaves_delta].join)
|
|
118
|
+
end
|
|
119
|
+
|
|
104
120
|
private_class_method :new
|
|
105
121
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class HeadMusic::RhythmicUnit
|
|
2
|
+
MULTIPLES = ['whole', 'double whole', 'longa', 'maxima']
|
|
3
|
+
DIVISIONS = ['whole', 'half', 'quarter', 'eighth', 'sixteenth', 'thirty-second', 'sixty-fourth', 'hundred twenty-eighth', 'two hundred fifty-sixth']
|
|
4
|
+
|
|
5
|
+
BRITISH_MULTIPLE_NAMES = %w[semibreve breve longa maxima]
|
|
6
|
+
BRITISH_DIVISION_NAMES = %w[semibreve minim crotchet quaver semiquaver demisemiquaver hemidemisemiquaver semihemidemisemiquaver demisemihemidemisemiquaver]
|
|
7
|
+
|
|
8
|
+
def self.get(name)
|
|
9
|
+
@rhythmic_units ||= {}
|
|
10
|
+
@rhythmic_units[name.to_s] ||= new(name.to_s)
|
|
11
|
+
end
|
|
12
|
+
singleton_class.send(:alias_method, :[], :get)
|
|
13
|
+
|
|
14
|
+
attr_reader :name, :numerator, :denominator
|
|
15
|
+
delegate :to_s, to: :name
|
|
16
|
+
|
|
17
|
+
def initialize(canonical_name)
|
|
18
|
+
@name ||= canonical_name
|
|
19
|
+
@numerator ||= MULTIPLES.include?(name) ? 2**MULTIPLES.index(name) : 1
|
|
20
|
+
@denominator ||= DIVISIONS.include?(name) ? 2**DIVISIONS.index(name) : 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def relative_value
|
|
24
|
+
@numerator.to_f / @denominator
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def notehead
|
|
28
|
+
case relative_value
|
|
29
|
+
when 8
|
|
30
|
+
:maxima
|
|
31
|
+
when 4
|
|
32
|
+
:longa
|
|
33
|
+
when 2
|
|
34
|
+
:breve
|
|
35
|
+
when 0.5, 1
|
|
36
|
+
:open
|
|
37
|
+
else
|
|
38
|
+
:closed
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def flags
|
|
43
|
+
DIVISIONS.include?(name) ? [DIVISIONS.index(name) - 2, 0].max : 0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def has_stem?
|
|
47
|
+
relative_value < 1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def british_name
|
|
51
|
+
if MULTIPLES.include?(name)
|
|
52
|
+
BRITISH_MULTIPLE_NAMES[MULTIPLES.index(name)]
|
|
53
|
+
elsif DIVISIONS.include?(name)
|
|
54
|
+
BRITISH_DIVISION_NAMES[DIVISIONS.index(name)]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private_class_method :new
|
|
59
|
+
end
|
|
@@ -1,63 +1,60 @@
|
|
|
1
1
|
class HeadMusic::RhythmicValue
|
|
2
|
-
|
|
3
|
-
DIVISIONS = ['whole', 'half', 'quarter', 'eighth', 'sixteenth', 'thirty-second', 'sixty-fourth', 'hundred twenty-eighth note', 'two hundred fifty-sixth note']
|
|
2
|
+
PPQN = PULSES_PER_QUARTER_NOTE = 960
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
BRITISH_DIVISION_NAMES = %w[semibreve minim crotchet quaver semiquaver demisemiquaver hemidemisemiquaver semihemidemisemiquaver demisemihemidemisemiquaver]
|
|
4
|
+
attr_reader :unit, :dots, :tied_value
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
@rhythmic_values ||= {}
|
|
10
|
-
@rhythmic_values[name.to_s] ||= new(name.to_s)
|
|
11
|
-
end
|
|
12
|
-
singleton_class.send(:alias_method, :[], :get)
|
|
6
|
+
delegate :name, to: :unit, prefix: true
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
def initialize(unit, dots: nil, tied_value: nil)
|
|
9
|
+
@unit = HeadMusic::RhythmicUnit.get(unit)
|
|
10
|
+
@dots = [0, 1, 2, 3].include?(dots) ? dots : 0
|
|
11
|
+
@tied_value = tied_value
|
|
12
|
+
end
|
|
17
13
|
|
|
18
|
-
def
|
|
19
|
-
|
|
14
|
+
def unit_value
|
|
15
|
+
unit.relative_value
|
|
20
16
|
end
|
|
21
17
|
|
|
22
18
|
def relative_value
|
|
23
|
-
|
|
24
|
-
if MULTIPLES.include?(name)
|
|
25
|
-
1.0 * 2**MULTIPLES.index(name)
|
|
26
|
-
elsif DIVISIONS.include?(name)
|
|
27
|
-
1.0 / 2**DIVISIONS.index(name)
|
|
28
|
-
end
|
|
19
|
+
unit_value * multiplier
|
|
29
20
|
end
|
|
30
21
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
if MULTIPLES.include?(name)
|
|
34
|
-
1.0 / 2**MULTIPLES.index(name)
|
|
35
|
-
elsif DIVISIONS.include?(name)
|
|
36
|
-
1.0 * 2**DIVISIONS.index(name)
|
|
37
|
-
end
|
|
22
|
+
def total_value
|
|
23
|
+
relative_value + (tied_value ? tied_value.total_value : 0)
|
|
38
24
|
end
|
|
39
25
|
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
return :open if relative_value >= 0.5
|
|
43
|
-
:closed
|
|
26
|
+
def multiplier
|
|
27
|
+
(0..dots).reduce(0) { |sum, i| sum += (1.0/2)**i }
|
|
44
28
|
end
|
|
45
29
|
|
|
46
|
-
def
|
|
47
|
-
|
|
30
|
+
def ticks
|
|
31
|
+
PPQN * 4 * total_value
|
|
48
32
|
end
|
|
49
33
|
|
|
50
|
-
def
|
|
51
|
-
|
|
34
|
+
def per_whole
|
|
35
|
+
1.0 / relative_value
|
|
52
36
|
end
|
|
53
37
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
def name_modifier_prefix
|
|
39
|
+
case dots
|
|
40
|
+
when 1
|
|
41
|
+
'dotted'
|
|
42
|
+
when 2
|
|
43
|
+
'double-dotted'
|
|
44
|
+
when 3
|
|
45
|
+
'triple-dotted'
|
|
59
46
|
end
|
|
60
47
|
end
|
|
61
48
|
|
|
62
|
-
|
|
49
|
+
def single_value_name
|
|
50
|
+
[name_modifier_prefix, unit_name].reject(&:nil?).join(' ')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def name
|
|
54
|
+
if tied_value
|
|
55
|
+
[single_value_name, tied_value.name].reject(&:nil?).join(' tied to ')
|
|
56
|
+
else
|
|
57
|
+
single_value_name
|
|
58
|
+
end
|
|
59
|
+
end
|
|
63
60
|
end
|
data/lib/head_music/scale.rb
CHANGED
|
@@ -19,7 +19,7 @@ class HeadMusic::Scale
|
|
|
19
19
|
@pitches ||= {}
|
|
20
20
|
@pitches[direction] ||= {}
|
|
21
21
|
@pitches[direction][octaves] ||= begin
|
|
22
|
-
|
|
22
|
+
letter_name_cycle = root_pitch.letter_name_cycle
|
|
23
23
|
semitones_from_root = 0
|
|
24
24
|
[root_pitch].tap do |pitches|
|
|
25
25
|
if [:ascending, :both].include?(direction)
|
|
@@ -50,8 +50,8 @@ class HeadMusic::Scale
|
|
|
50
50
|
pitches(direction: direction, octaves: octaves).map(&:name)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
@
|
|
53
|
+
def letter_name_cycle
|
|
54
|
+
@letter_name_cycle ||= root_pitch.letter_name_cycle
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def root_pitch_number
|
|
@@ -77,9 +77,9 @@ class HeadMusic::Scale
|
|
|
77
77
|
def letter_for_step(step, semitones_from_root, direction)
|
|
78
78
|
pitch_class_number = (root_pitch.pitch_class.to_i + semitones_from_root) % 12
|
|
79
79
|
if scale_type.intervals.length == 7
|
|
80
|
-
direction == :ascending ?
|
|
80
|
+
direction == :ascending ? letter_name_cycle[step % 7] : letter_name_cycle[-step % 7]
|
|
81
81
|
elsif scale_type.intervals.length < 7 && parent_scale_pitches
|
|
82
|
-
parent_scale_pitch_for(semitones_from_root).
|
|
82
|
+
parent_scale_pitch_for(semitones_from_root).letter_name
|
|
83
83
|
elsif root_pitch.flat?
|
|
84
84
|
HeadMusic::PitchClass::FLAT_SPELLINGS[pitch_class_number]
|
|
85
85
|
else
|
|
@@ -89,7 +89,7 @@ class HeadMusic::Scale
|
|
|
89
89
|
|
|
90
90
|
def pitch_for_step(step, semitones_from_root, direction)
|
|
91
91
|
pitch_number = root_pitch_number + semitones_from_root
|
|
92
|
-
|
|
93
|
-
HeadMusic::Pitch.from_number_and_letter(pitch_number,
|
|
92
|
+
letter_name = letter_for_step(step, semitones_from_root, direction)
|
|
93
|
+
HeadMusic::Pitch.from_number_and_letter(pitch_number, letter_name)
|
|
94
94
|
end
|
|
95
95
|
end
|
data/lib/head_music/spelling.rb
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
# A Spelling is a pitch class with a letter name and possibly an accidental
|
|
2
1
|
class HeadMusic::Spelling
|
|
3
2
|
MATCHER = /^\s*([A-G])([b#]*)(\-?\d+)?\s*$/
|
|
4
3
|
|
|
5
4
|
attr_reader :pitch_class
|
|
6
|
-
attr_reader :
|
|
5
|
+
attr_reader :letter_name
|
|
7
6
|
attr_reader :accidental
|
|
8
7
|
|
|
9
8
|
delegate :number, to: :pitch_class, prefix: true
|
|
10
9
|
delegate :to_i, to: :pitch_class_number
|
|
11
|
-
delegate :cycle, to: :
|
|
10
|
+
delegate :cycle, to: :letter_name, prefix: true
|
|
12
11
|
delegate :enharmonic?, to: :pitch_class
|
|
13
12
|
|
|
14
13
|
def self.get(identifier)
|
|
@@ -23,43 +22,43 @@ class HeadMusic::Spelling
|
|
|
23
22
|
def self.from_name(name)
|
|
24
23
|
if match(name)
|
|
25
24
|
letter_name, accidental_string, _octave = match(name).captures
|
|
26
|
-
|
|
27
|
-
return nil unless
|
|
25
|
+
letter_name = HeadMusic::LetterName.get(letter_name)
|
|
26
|
+
return nil unless letter_name
|
|
28
27
|
accidental = HeadMusic::Accidental.get(accidental_string)
|
|
29
|
-
fetch_or_create(
|
|
28
|
+
fetch_or_create(letter_name, accidental)
|
|
30
29
|
end
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def self.from_number(number)
|
|
34
33
|
return nil unless number == number.to_i
|
|
35
34
|
pitch_class_number = number.to_i % 12
|
|
36
|
-
|
|
37
|
-
from_number_and_letter(number,
|
|
35
|
+
letter_name = HeadMusic::LetterName.from_pitch_class(pitch_class_number)
|
|
36
|
+
from_number_and_letter(number, letter_name)
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
def self.from_number_and_letter(number,
|
|
41
|
-
|
|
42
|
-
natural_letter_pitch_class = HeadMusic::LetterName.get(
|
|
43
|
-
accidental_interval =
|
|
39
|
+
def self.from_number_and_letter(number, letter_name)
|
|
40
|
+
letter_name = HeadMusic::LetterName.get(letter_name)
|
|
41
|
+
natural_letter_pitch_class = HeadMusic::LetterName.get(letter_name).pitch_class
|
|
42
|
+
accidental_interval = letter_name.pitch_class.smallest_interval_to(HeadMusic::PitchClass.get(number))
|
|
44
43
|
accidental = HeadMusic::Accidental.for_interval(accidental_interval)
|
|
45
|
-
fetch_or_create(
|
|
44
|
+
fetch_or_create(letter_name, accidental)
|
|
46
45
|
end
|
|
47
46
|
|
|
48
|
-
def self.fetch_or_create(
|
|
47
|
+
def self.fetch_or_create(letter_name, accidental)
|
|
49
48
|
@spellings ||= {}
|
|
50
|
-
key = [
|
|
51
|
-
@spellings[key] ||= new(
|
|
49
|
+
key = [letter_name, accidental].join
|
|
50
|
+
@spellings[key] ||= new(letter_name, accidental)
|
|
52
51
|
end
|
|
53
52
|
|
|
54
|
-
def initialize(
|
|
55
|
-
@
|
|
53
|
+
def initialize(letter_name, accidental = nil)
|
|
54
|
+
@letter_name = HeadMusic::LetterName.get(letter_name.to_s)
|
|
56
55
|
@accidental = HeadMusic::Accidental.get(accidental.to_s)
|
|
57
56
|
accidental_semitones = @accidental ? @accidental.semitones : 0
|
|
58
|
-
@pitch_class = HeadMusic::PitchClass.get(
|
|
57
|
+
@pitch_class = HeadMusic::PitchClass.get(letter_name.pitch_class + accidental_semitones)
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
def name
|
|
62
|
-
[
|
|
61
|
+
[letter_name, accidental].join
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
def to_s
|
data/lib/head_music/version.rb
CHANGED
data/lib/head_music.rb
CHANGED
|
@@ -6,16 +6,19 @@ require 'humanize'
|
|
|
6
6
|
|
|
7
7
|
require 'head_music/accidental'
|
|
8
8
|
require 'head_music/circle'
|
|
9
|
+
require 'head_music/clef'
|
|
9
10
|
require 'head_music/consonance'
|
|
10
11
|
require 'head_music/functional_interval'
|
|
11
12
|
require 'head_music/interval'
|
|
12
13
|
require 'head_music/key_signature'
|
|
13
14
|
require 'head_music/letter_name'
|
|
14
15
|
require 'head_music/meter'
|
|
16
|
+
require 'head_music/note'
|
|
15
17
|
require 'head_music/octave'
|
|
16
18
|
require 'head_music/pitch_class'
|
|
17
19
|
require 'head_music/pitch'
|
|
18
20
|
require 'head_music/quality'
|
|
21
|
+
require 'head_music/rhythmic_unit'
|
|
19
22
|
require 'head_music/rhythmic_value'
|
|
20
23
|
require 'head_music/scale'
|
|
21
24
|
require 'head_music/scale_type'
|
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.5.
|
|
4
|
+
version: 0.5.4
|
|
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-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -116,16 +116,19 @@ files:
|
|
|
116
116
|
- lib/head_music.rb
|
|
117
117
|
- lib/head_music/accidental.rb
|
|
118
118
|
- lib/head_music/circle.rb
|
|
119
|
+
- lib/head_music/clef.rb
|
|
119
120
|
- lib/head_music/consonance.rb
|
|
120
121
|
- lib/head_music/functional_interval.rb
|
|
121
122
|
- lib/head_music/interval.rb
|
|
122
123
|
- lib/head_music/key_signature.rb
|
|
123
124
|
- lib/head_music/letter_name.rb
|
|
124
125
|
- lib/head_music/meter.rb
|
|
126
|
+
- lib/head_music/note.rb
|
|
125
127
|
- lib/head_music/octave.rb
|
|
126
128
|
- lib/head_music/pitch.rb
|
|
127
129
|
- lib/head_music/pitch_class.rb
|
|
128
130
|
- lib/head_music/quality.rb
|
|
131
|
+
- lib/head_music/rhythmic_unit.rb
|
|
129
132
|
- lib/head_music/rhythmic_value.rb
|
|
130
133
|
- lib/head_music/scale.rb
|
|
131
134
|
- lib/head_music/scale_type.rb
|