stave 0.1.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 +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +71 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/lib/stave/core/degree_collection.rb +51 -0
- data/lib/stave/core/lookup.rb +110 -0
- data/lib/stave/core/note_collection.rb +47 -0
- data/lib/stave/core/option_merger.rb +25 -0
- data/lib/stave/core/scale_harmoniser.rb +34 -0
- data/lib/stave/theory/accidental.rb +23 -0
- data/lib/stave/theory/chord.rb +15 -0
- data/lib/stave/theory/chord_inversion.rb +15 -0
- data/lib/stave/theory/chord_inversion_type.rb +32 -0
- data/lib/stave/theory/chord_type.rb +70 -0
- data/lib/stave/theory/circle.rb +22 -0
- data/lib/stave/theory/circle_type.rb +8 -0
- data/lib/stave/theory/degree.rb +117 -0
- data/lib/stave/theory/interval.rb +185 -0
- data/lib/stave/theory/key_signature.rb +41 -0
- data/lib/stave/theory/mode.rb +6 -0
- data/lib/stave/theory/mode_type.rb +19 -0
- data/lib/stave/theory/note.rb +140 -0
- data/lib/stave/theory/scale.rb +38 -0
- data/lib/stave/theory/scale_type.rb +77 -0
- data/lib/stave/version.rb +3 -0
- data/lib/stave.rb +13 -0
- data/sig/stave.rbs +3 -0
- data/stave.gemspec +29 -0
- metadata +88 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class Degree < Core::Lookup
|
4
|
+
with_options position: 1 do
|
5
|
+
variant :root,
|
6
|
+
interval: Interval.perfect_unison,
|
7
|
+
accidental: Accidental.natural
|
8
|
+
|
9
|
+
variant :octave,
|
10
|
+
interval: Interval.perfect_octave,
|
11
|
+
accidental: Accidental.natural
|
12
|
+
end
|
13
|
+
|
14
|
+
with_options position: 2 do
|
15
|
+
variant :flat_two,
|
16
|
+
interval: Interval.minor_second,
|
17
|
+
accidental: Accidental.flat
|
18
|
+
|
19
|
+
variant :two,
|
20
|
+
interval: Interval.major_second,
|
21
|
+
accidental: Accidental.natural
|
22
|
+
|
23
|
+
variant :sharp_two,
|
24
|
+
interval: Interval.augmented_second,
|
25
|
+
accidental: Accidental.sharp
|
26
|
+
end
|
27
|
+
|
28
|
+
with_options position: 3 do
|
29
|
+
variant :flat_three,
|
30
|
+
interval: Interval.minor_third,
|
31
|
+
accidental: Accidental.flat
|
32
|
+
|
33
|
+
variant :three,
|
34
|
+
interval: Interval.major_third,
|
35
|
+
accidental: Accidental.natural
|
36
|
+
|
37
|
+
variant :sharp_three,
|
38
|
+
interval: Interval.augmented_third,
|
39
|
+
accidental: Accidental.sharp
|
40
|
+
end
|
41
|
+
|
42
|
+
with_options position: 4 do
|
43
|
+
variant :flat_four,
|
44
|
+
interval: Interval.diminished_fourth,
|
45
|
+
accidental: Accidental.flat
|
46
|
+
|
47
|
+
variant :four,
|
48
|
+
interval: Interval.perfect_fourth,
|
49
|
+
accidental: Accidental.natural
|
50
|
+
|
51
|
+
variant :sharp_four,
|
52
|
+
interval: Interval.augmented_fourth,
|
53
|
+
accidental: Accidental.sharp
|
54
|
+
end
|
55
|
+
|
56
|
+
with_options position: 5 do
|
57
|
+
variant :flat_five,
|
58
|
+
interval: Interval.diminished_fifth,
|
59
|
+
accidental: Accidental.flat
|
60
|
+
|
61
|
+
variant :five,
|
62
|
+
interval: Interval.perfect_fifth,
|
63
|
+
accidental: Accidental.natural
|
64
|
+
|
65
|
+
variant :sharp_five,
|
66
|
+
interval: Interval.augmented_fifth,
|
67
|
+
accidental: Accidental.sharp
|
68
|
+
end
|
69
|
+
|
70
|
+
with_options position: 6 do
|
71
|
+
variant :flat_six,
|
72
|
+
interval: Interval.minor_sixth,
|
73
|
+
accidental: Accidental.flat
|
74
|
+
|
75
|
+
variant :six,
|
76
|
+
interval: Interval.major_sixth,
|
77
|
+
accidental: Accidental.natural
|
78
|
+
|
79
|
+
variant :sharp_six,
|
80
|
+
interval: Interval.augmented_sixth,
|
81
|
+
accidental: Accidental.sharp
|
82
|
+
end
|
83
|
+
|
84
|
+
with_options position: 7 do
|
85
|
+
variant :flat_seven,
|
86
|
+
interval: Interval.minor_seventh,
|
87
|
+
accidental: Accidental.flat
|
88
|
+
|
89
|
+
variant :seven,
|
90
|
+
interval: Interval.major_seventh,
|
91
|
+
accidental: Accidental.natural
|
92
|
+
end
|
93
|
+
|
94
|
+
def symbol
|
95
|
+
return "R" if (to_i % 12).zero?
|
96
|
+
|
97
|
+
"#{accidental.symbol}#{interval.number.symbol}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_i
|
101
|
+
interval.to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
def +(other)
|
105
|
+
new_interval = interval + other.interval
|
106
|
+
|
107
|
+
Degree.find_by(interval: new_interval)
|
108
|
+
end
|
109
|
+
|
110
|
+
def -(other)
|
111
|
+
new_interval = interval - other.interval
|
112
|
+
|
113
|
+
Degree.find_by(interval: new_interval)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class Interval < Core::Lookup
|
4
|
+
class Quality < Core::Lookup
|
5
|
+
variant :diminished,
|
6
|
+
transform: { major: -2, perfect: -1 },
|
7
|
+
inversion: :augmented,
|
8
|
+
symbol: "d"
|
9
|
+
|
10
|
+
variant :minor,
|
11
|
+
transform: { major: -1, perfect: nil },
|
12
|
+
inversion: :major,
|
13
|
+
symbol: "m"
|
14
|
+
|
15
|
+
variant :major,
|
16
|
+
transform: { major: 0, perfect: nil },
|
17
|
+
inversion: :minor,
|
18
|
+
symbol: "M"
|
19
|
+
|
20
|
+
variant :perfect,
|
21
|
+
transform: { major: nil, perfect: 0 },
|
22
|
+
inversion: :perfect,
|
23
|
+
symbol: "P"
|
24
|
+
|
25
|
+
variant :augmented,
|
26
|
+
transform: { major: 1, perfect: 1 },
|
27
|
+
inversion: :diminished,
|
28
|
+
symbol: "A"
|
29
|
+
|
30
|
+
def invert!
|
31
|
+
Quality.new(inversion)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Number < Core::Lookup
|
36
|
+
variant :one, to_i: 1, size: 0, perfect?: true
|
37
|
+
variant :two, to_i: 2, size: 2, perfect?: false
|
38
|
+
variant :three, to_i: 3, size: 4, perfect?: false
|
39
|
+
variant :four, to_i: 4, size: 5, perfect?: true
|
40
|
+
variant :five, to_i: 5, size: 7, perfect?: true
|
41
|
+
variant :six, to_i: 6, size: 9, perfect?: false
|
42
|
+
variant :seven, to_i: 7, size: 11, perfect?: false
|
43
|
+
variant :eight, to_i: 8, size: 12, perfect?: true
|
44
|
+
variant :nine, to_i: 9, size: 14, perfect?: false
|
45
|
+
variant :eleven, to_i: 11, size: 17, perfect?: true
|
46
|
+
variant :thirteen, to_i: 13, size: 21, perfect?: false
|
47
|
+
|
48
|
+
def symbol = to_i.to_s
|
49
|
+
|
50
|
+
def compound? = size >= 12
|
51
|
+
|
52
|
+
def octave? = (size % 12).zero?
|
53
|
+
|
54
|
+
def degree = compound? ? to_i - 7 : to_i
|
55
|
+
|
56
|
+
def offset = to_i - 1
|
57
|
+
|
58
|
+
def +(other)
|
59
|
+
target_i = to_i + other.relative.offset
|
60
|
+
target_i -= 7 while target_i >= 8
|
61
|
+
|
62
|
+
Number.find_by(to_i: target_i)
|
63
|
+
end
|
64
|
+
|
65
|
+
def relative
|
66
|
+
target_i = to_i
|
67
|
+
target_i -= 7 while target_i >= 8
|
68
|
+
|
69
|
+
Number.find_by(to_i: target_i)
|
70
|
+
end
|
71
|
+
|
72
|
+
def invert!
|
73
|
+
return self if octave?
|
74
|
+
|
75
|
+
Number.find_by(degree: 9 - degree)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.between(note, other_note)
|
79
|
+
target = other_note.pitch_class.index - note.pitch_class.index + 1
|
80
|
+
target += 7 unless target.positive?
|
81
|
+
|
82
|
+
find_by(to_i: target)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
with_options number: Number.one, scope: :unisons do
|
87
|
+
variant :perfect_unison, quality: Quality.perfect
|
88
|
+
end
|
89
|
+
|
90
|
+
with_options number: Number.two, scope: :seconds do
|
91
|
+
variant :minor_second, quality: Quality.minor
|
92
|
+
variant :major_second, quality: Quality.major
|
93
|
+
variant :augmented_second, quality: Quality.augmented
|
94
|
+
end
|
95
|
+
|
96
|
+
with_options number: Number.three, scope: :thirds do
|
97
|
+
variant :diminished_third, quality: Quality.diminished
|
98
|
+
variant :minor_third, quality: Quality.minor
|
99
|
+
variant :major_third, quality: Quality.major
|
100
|
+
variant :augmented_third, quality: Quality.augmented
|
101
|
+
end
|
102
|
+
|
103
|
+
with_options number: Number.four, scope: :fourths do
|
104
|
+
variant :diminished_fourth, quality: Quality.diminished
|
105
|
+
variant :perfect_fourth, quality: Quality.perfect
|
106
|
+
variant :augmented_fourth, quality: Quality.augmented
|
107
|
+
end
|
108
|
+
|
109
|
+
with_options number: Number.five, scope: :fifths do
|
110
|
+
variant :diminished_fifth, quality: Quality.diminished
|
111
|
+
variant :perfect_fifth, quality: Quality.perfect
|
112
|
+
variant :augmented_fifth, quality: Quality.augmented
|
113
|
+
end
|
114
|
+
|
115
|
+
with_options number: Number.six, scope: :sixths do
|
116
|
+
variant :diminished_sixth, quality: Quality.diminished
|
117
|
+
variant :minor_sixth, quality: Quality.minor
|
118
|
+
variant :major_sixth, quality: Quality.major
|
119
|
+
variant :augmented_sixth, quality: Quality.augmented
|
120
|
+
end
|
121
|
+
|
122
|
+
with_options number: Number.seven, scope: :sevenths do
|
123
|
+
variant :diminished_seventh, quality: Quality.diminished
|
124
|
+
variant :minor_seventh, quality: Quality.minor
|
125
|
+
variant :major_seventh, quality: Quality.major
|
126
|
+
end
|
127
|
+
|
128
|
+
with_options number: Number.eight, scope: :octaves do
|
129
|
+
variant :perfect_octave, quality: Quality.perfect
|
130
|
+
end
|
131
|
+
|
132
|
+
with_options number: Number.nine, scope: :ninths do
|
133
|
+
variant :minor_ninth, quality: Quality.minor
|
134
|
+
variant :major_ninth, quality: Quality.major
|
135
|
+
variant :augmented_ninth, quality: Quality.augmented
|
136
|
+
end
|
137
|
+
|
138
|
+
with_options number: Number.eleven, scope: :elevenths do
|
139
|
+
variant :diminished_eleventh, quality: Quality.diminished
|
140
|
+
variant :perfect_eleventh, quality: Quality.perfect
|
141
|
+
variant :augmented_eleventh, quality: Quality.augmented
|
142
|
+
end
|
143
|
+
|
144
|
+
with_options number: Number.thirteen, scope: :thirteenths do
|
145
|
+
variant :diminished_thirteenth, quality: Quality.diminished
|
146
|
+
variant :minor_thirteenth, quality: Quality.minor
|
147
|
+
variant :major_thirteenth, quality: Quality.major
|
148
|
+
variant :augmented_thirteenth, quality: Quality.augmented
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_i
|
152
|
+
transform_key = number.perfect? ? :perfect : :major
|
153
|
+
|
154
|
+
number.size + quality.transform[transform_key]
|
155
|
+
end
|
156
|
+
|
157
|
+
def symbol
|
158
|
+
"#{quality.symbol}#{number.symbol}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def +(other)
|
162
|
+
target_number = number + other.number
|
163
|
+
target_i = (to_i + other.to_i) % 12
|
164
|
+
|
165
|
+
Interval.find_by(number: target_number, to_i: target_i)
|
166
|
+
end
|
167
|
+
|
168
|
+
def -(other)
|
169
|
+
self + other.invert!
|
170
|
+
end
|
171
|
+
|
172
|
+
def invert!
|
173
|
+
Interval.find_by(number: number.invert!, quality: quality.invert!)
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.between(note, other_note)
|
177
|
+
number = Number.between(note, other_note)
|
178
|
+
to_i = other_note.to_i - note.to_i
|
179
|
+
to_i += 12 if to_i.negative?
|
180
|
+
|
181
|
+
Interval.find_by(number:, to_i:)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class KeySignature < Core::Lookup
|
4
|
+
class Group < Core::Lookup
|
5
|
+
variant :flats, accidental: Accidental.flat, name: "flats"
|
6
|
+
variant :natural, accidental: Accidental.natural, name: ""
|
7
|
+
variant :sharps, accidental: Accidental.sharp, name: "sharps"
|
8
|
+
end
|
9
|
+
|
10
|
+
variant :natural, group: Group.natural, flat_count: 0, sharp_count: 0
|
11
|
+
|
12
|
+
with_options group: Group.flats, sharp_count: 0 do
|
13
|
+
variant :one_flat, flat_count: 1
|
14
|
+
variant :two_flats, flat_count: 2
|
15
|
+
variant :three_flats, flat_count: 3
|
16
|
+
variant :four_flats, flat_count: 4
|
17
|
+
variant :five_flats, flat_count: 5
|
18
|
+
variant :six_flats, flat_count: 6
|
19
|
+
variant :seven_flats, flat_count: 7
|
20
|
+
end
|
21
|
+
|
22
|
+
with_options group: Group.sharps, flat_count: 0 do
|
23
|
+
variant :one_sharp, sharp_count: 1
|
24
|
+
variant :two_sharps, sharp_count: 2
|
25
|
+
variant :three_sharps, sharp_count: 3
|
26
|
+
variant :four_sharps, sharp_count: 4
|
27
|
+
variant :five_sharps, sharp_count: 5
|
28
|
+
variant :six_sharps, sharp_count: 6
|
29
|
+
variant :seven_sharps, sharp_count: 7
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.parse(scale)
|
33
|
+
accidentals = scale.uniq.map(&:accidental)
|
34
|
+
flat_count = accidentals.count(&:flat?)
|
35
|
+
sharp_count = accidentals.count(&:sharp?)
|
36
|
+
|
37
|
+
find_by(flat_count:, sharp_count:)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class ModeType < Core::DegreeCollection
|
4
|
+
with_options scale_type: ScaleType.major do
|
5
|
+
variant :ionian, position: 1
|
6
|
+
variant :dorian, position: 2
|
7
|
+
variant :phrygian, position: 3
|
8
|
+
variant :lydian, position: 4
|
9
|
+
variant :mixolydian, position: 5
|
10
|
+
variant :aeolian, position: 6
|
11
|
+
variant :locrian, position: 7
|
12
|
+
end
|
13
|
+
|
14
|
+
def degrees
|
15
|
+
scale_type.relative_rotate(position)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class Note < Core::Lookup
|
4
|
+
class PitchClass < Core::Lookup
|
5
|
+
variant :c, index: 0, symbol: "C", to_i: 0
|
6
|
+
variant :d, index: 1, symbol: "D", to_i: 2
|
7
|
+
variant :e, index: 2, symbol: "E", to_i: 4
|
8
|
+
variant :f, index: 3, symbol: "F", to_i: 5
|
9
|
+
variant :g, index: 4, symbol: "G", to_i: 7
|
10
|
+
variant :a, index: 5, symbol: "A", to_i: 9
|
11
|
+
variant :b, index: 6, symbol: "B", to_i: 11
|
12
|
+
|
13
|
+
def +(other)
|
14
|
+
case other
|
15
|
+
when Integer then PitchClass.find_by(index: (index + other) % 7)
|
16
|
+
else raise TypeError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def -(other)
|
21
|
+
case other
|
22
|
+
when Integer then PitchClass.find_by(index: (index - other) % 7)
|
23
|
+
else raise TypeError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
with_options pitch_class: PitchClass.a do
|
29
|
+
variant :a_double_flat, accidental: Accidental.double_flat
|
30
|
+
variant :a_flat, accidental: Accidental.flat
|
31
|
+
variant :a_natural, accidental: Accidental.natural
|
32
|
+
variant :a_sharp, accidental: Accidental.sharp
|
33
|
+
variant :a_double_sharp, accidental: Accidental.double_sharp
|
34
|
+
end
|
35
|
+
|
36
|
+
with_options pitch_class: PitchClass.b do
|
37
|
+
variant :b_double_flat, accidental: Accidental.double_flat
|
38
|
+
variant :b_flat, accidental: Accidental.flat
|
39
|
+
variant :b_natural, accidental: Accidental.natural
|
40
|
+
variant :b_sharp, accidental: Accidental.sharp
|
41
|
+
variant :b_double_sharp, accidental: Accidental.double_sharp
|
42
|
+
end
|
43
|
+
|
44
|
+
with_options pitch_class: PitchClass.c do
|
45
|
+
variant :c_double_flat, accidental: Accidental.double_flat
|
46
|
+
variant :c_flat, accidental: Accidental.flat
|
47
|
+
variant :c_natural, accidental: Accidental.natural
|
48
|
+
variant :c_sharp, accidental: Accidental.sharp
|
49
|
+
variant :c_double_sharp, accidental: Accidental.double_sharp
|
50
|
+
end
|
51
|
+
|
52
|
+
with_options pitch_class: PitchClass.d do
|
53
|
+
variant :d_double_flat, accidental: Accidental.double_flat
|
54
|
+
variant :d_flat, accidental: Accidental.flat
|
55
|
+
variant :d_natural, accidental: Accidental.natural
|
56
|
+
variant :d_sharp, accidental: Accidental.sharp
|
57
|
+
variant :d_double_sharp, accidental: Accidental.double_sharp
|
58
|
+
end
|
59
|
+
|
60
|
+
with_options pitch_class: PitchClass.e do
|
61
|
+
variant :e_double_flat, accidental: Accidental.double_flat
|
62
|
+
variant :e_flat, accidental: Accidental.flat
|
63
|
+
variant :e_natural, accidental: Accidental.natural
|
64
|
+
variant :e_sharp, accidental: Accidental.sharp
|
65
|
+
variant :e_double_sharp, accidental: Accidental.double_sharp
|
66
|
+
end
|
67
|
+
|
68
|
+
with_options pitch_class: PitchClass.f do
|
69
|
+
variant :f_double_flat, accidental: Accidental.double_flat
|
70
|
+
variant :f_flat, accidental: Accidental.flat
|
71
|
+
variant :f_natural, accidental: Accidental.natural
|
72
|
+
variant :f_sharp, accidental: Accidental.sharp
|
73
|
+
variant :f_double_sharp, accidental: Accidental.double_sharp
|
74
|
+
end
|
75
|
+
|
76
|
+
with_options pitch_class: PitchClass.g do
|
77
|
+
variant :g_double_flat, accidental: Accidental.double_flat
|
78
|
+
variant :g_flat, accidental: Accidental.flat
|
79
|
+
variant :g_natural, accidental: Accidental.natural
|
80
|
+
variant :g_sharp, accidental: Accidental.sharp
|
81
|
+
variant :g_double_sharp, accidental: Accidental.double_sharp
|
82
|
+
end
|
83
|
+
|
84
|
+
def +(other)
|
85
|
+
case other
|
86
|
+
when Interval then note_above(other)
|
87
|
+
when Integer then Note.find_by(to_i: (to_i + other) % 12)
|
88
|
+
else raise
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def -(other)
|
93
|
+
case other
|
94
|
+
when Interval then note_below(other)
|
95
|
+
when Integer then Note.find_by(to_i: (to_i - other) % 12)
|
96
|
+
else raise
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def symbol
|
101
|
+
"#{pitch_class.symbol}#{accidental.symbol}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_i
|
105
|
+
(pitch_class.to_i + accidental.transform) % 12
|
106
|
+
end
|
107
|
+
|
108
|
+
def note_above(interval)
|
109
|
+
target_pitch_class = pitch_class + interval.number.offset
|
110
|
+
target_integer = (to_i + interval.to_i) % 12
|
111
|
+
|
112
|
+
Note.find_by(pitch_class: target_pitch_class, to_i: target_integer)
|
113
|
+
end
|
114
|
+
|
115
|
+
def note_below(interval)
|
116
|
+
note_above(interval.invert!)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.flats
|
120
|
+
where(accidental: Accidental.flat)
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.naturals
|
124
|
+
where(accidental: Accidental.natural)
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.sharps
|
128
|
+
where(accidental: Accidental.sharp)
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.single_accidental
|
132
|
+
flats + naturals + sharps
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.circle_of_fifths
|
136
|
+
Circle.new(type: CircleType.fifths, root: Note.c_natural).notes
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class Scale < Core::NoteCollection
|
4
|
+
def key_signature
|
5
|
+
KeySignature.parse(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def triads
|
9
|
+
return unless type.hexatonic?
|
10
|
+
|
11
|
+
harmonise!(type.triad_types)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sevenths
|
15
|
+
return unless type.hexatonic?
|
16
|
+
|
17
|
+
harmonise!(type.seventh_types)
|
18
|
+
end
|
19
|
+
|
20
|
+
def mode(position)
|
21
|
+
return if type.mode_types.empty?
|
22
|
+
|
23
|
+
Mode.new(
|
24
|
+
type: type.mode_type_at(position),
|
25
|
+
root: note_at(position)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def harmonise!(chord_types)
|
32
|
+
chord_types.zip(notes).map do |chord_type, note|
|
33
|
+
Chord.new(type: chord_type, root: note)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Stave
|
2
|
+
module Theory
|
3
|
+
class ScaleType < Core::DegreeCollection
|
4
|
+
variant :major, degrees: [
|
5
|
+
Degree.root,
|
6
|
+
Degree.two,
|
7
|
+
Degree.three,
|
8
|
+
Degree.four,
|
9
|
+
Degree.five,
|
10
|
+
Degree.six,
|
11
|
+
Degree.seven,
|
12
|
+
Degree.octave
|
13
|
+
]
|
14
|
+
|
15
|
+
variant :minor, degrees: [
|
16
|
+
Degree.root,
|
17
|
+
Degree.two,
|
18
|
+
Degree.flat_three,
|
19
|
+
Degree.four,
|
20
|
+
Degree.five,
|
21
|
+
Degree.flat_six,
|
22
|
+
Degree.flat_seven,
|
23
|
+
Degree.octave
|
24
|
+
]
|
25
|
+
|
26
|
+
with_options suffix: :pentatonic do
|
27
|
+
variant :major, degrees: [
|
28
|
+
Degree.root,
|
29
|
+
Degree.two,
|
30
|
+
Degree.three,
|
31
|
+
Degree.five,
|
32
|
+
Degree.six,
|
33
|
+
Degree.octave
|
34
|
+
]
|
35
|
+
|
36
|
+
variant :minor, degrees: [
|
37
|
+
Degree.root,
|
38
|
+
Degree.flat_three,
|
39
|
+
Degree.four,
|
40
|
+
Degree.five,
|
41
|
+
Degree.flat_seven,
|
42
|
+
Degree.octave
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
def pentatonic?
|
47
|
+
count == 5
|
48
|
+
end
|
49
|
+
|
50
|
+
def hexatonic?
|
51
|
+
count == 7
|
52
|
+
end
|
53
|
+
|
54
|
+
def triad_types
|
55
|
+
Core::ScaleHarmoniser
|
56
|
+
.new(scale_type: self, chord_set: ChordType::Set.triad)
|
57
|
+
.harmonise!
|
58
|
+
end
|
59
|
+
|
60
|
+
def seventh_types
|
61
|
+
Core::ScaleHarmoniser
|
62
|
+
.new(scale_type: self, chord_set: ChordType::Set.seventh)
|
63
|
+
.harmonise!
|
64
|
+
end
|
65
|
+
|
66
|
+
def mode_types
|
67
|
+
ModeType.where(scale_type: self).sort_by(&:position)
|
68
|
+
end
|
69
|
+
|
70
|
+
def mode_type_at(position)
|
71
|
+
position -= 7 while position >= 8
|
72
|
+
|
73
|
+
mode_types[position - 1]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/stave.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "zeitwerk"
|
3
|
+
|
4
|
+
module Stave
|
5
|
+
INFLECTIONS = {}.freeze
|
6
|
+
|
7
|
+
COLLAPSED_DIRS = [].freeze
|
8
|
+
|
9
|
+
Zeitwerk::Loader.for_gem(warn_on_extra_files: false).tap do |loader|
|
10
|
+
loader.inflector.inflect(INFLECTIONS)
|
11
|
+
loader.collapse(COLLAPSED_DIRS)
|
12
|
+
end.setup
|
13
|
+
end
|
data/sig/stave.rbs
ADDED
data/stave.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "lib/stave/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "stave"
|
5
|
+
spec.version = Stave::VERSION
|
6
|
+
spec.authors = ["Chris Welham"]
|
7
|
+
spec.email = ["71787007+apexatoll@users.noreply.github.com"]
|
8
|
+
|
9
|
+
spec.summary = "Music theory object library"
|
10
|
+
spec.description = "A Ruby gem that abstracts and models music theory"
|
11
|
+
spec.homepage = "https://github.com/apexatoll/stave"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = ">= 3.3.0"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
17
|
+
|
18
|
+
spec.files = Dir.chdir(__dir__) do
|
19
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
20
|
+
next false if File.expand_path(f) == __FILE__
|
21
|
+
|
22
|
+
f.start_with?(*%w[bin/ spec/ .git Gemfile])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "zeitwerk"
|
29
|
+
end
|