music 0.5.1 → 0.6.1
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.
- data/.gitignore +3 -1
- data/Gemfile +6 -0
- data/Gemfile.lock +25 -1
- data/Guardfile +29 -0
- data/README.md +13 -1
- data/lib/music/chord.rb +67 -6
- data/lib/music/note.rb +195 -1
- data/lib/music/version.rb +1 -1
- data/spec/classes/chord_spec.rb +122 -5
- data/spec/classes/note_spec.rb +327 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/support/matchers/have_an_interval.rb +13 -0
- metadata +77 -89
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
music (0.5.
|
|
4
|
+
music (0.5.1)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: http://rubygems.org/
|
|
@@ -13,10 +13,29 @@ GEM
|
|
|
13
13
|
i18n (~> 0.6)
|
|
14
14
|
multi_json (~> 1.0)
|
|
15
15
|
builder (3.0.0)
|
|
16
|
+
coderay (1.0.8)
|
|
16
17
|
diff-lcs (1.1.3)
|
|
18
|
+
guard (1.5.3)
|
|
19
|
+
listen (>= 0.4.2)
|
|
20
|
+
lumberjack (>= 1.0.2)
|
|
21
|
+
pry (>= 0.9.10)
|
|
22
|
+
thor (>= 0.14.6)
|
|
23
|
+
guard-bundler (1.0.0)
|
|
24
|
+
bundler (~> 1.0)
|
|
25
|
+
guard (~> 1.1)
|
|
26
|
+
guard-rspec (1.2.1)
|
|
27
|
+
guard (>= 1.1)
|
|
17
28
|
i18n (0.6.0)
|
|
29
|
+
listen (0.5.3)
|
|
30
|
+
lumberjack (1.0.2)
|
|
31
|
+
method_source (0.8.1)
|
|
18
32
|
multi_json (1.3.5)
|
|
33
|
+
pry (0.9.10)
|
|
34
|
+
coderay (~> 1.0.5)
|
|
35
|
+
method_source (~> 0.8)
|
|
36
|
+
slop (~> 3.3.1)
|
|
19
37
|
rake (0.9.2.2)
|
|
38
|
+
rb-fsevent (0.9.2)
|
|
20
39
|
rspec (2.10.0)
|
|
21
40
|
rspec-core (~> 2.10.0)
|
|
22
41
|
rspec-expectations (~> 2.10.0)
|
|
@@ -25,6 +44,8 @@ GEM
|
|
|
25
44
|
rspec-expectations (2.10.0)
|
|
26
45
|
diff-lcs (~> 1.1.3)
|
|
27
46
|
rspec-mocks (2.10.1)
|
|
47
|
+
slop (3.3.3)
|
|
48
|
+
thor (0.16.0)
|
|
28
49
|
|
|
29
50
|
PLATFORMS
|
|
30
51
|
ruby
|
|
@@ -32,6 +53,9 @@ PLATFORMS
|
|
|
32
53
|
DEPENDENCIES
|
|
33
54
|
activemodel (>= 3.2.0)
|
|
34
55
|
bundler (>= 1.1.3)
|
|
56
|
+
guard-bundler
|
|
57
|
+
guard-rspec
|
|
35
58
|
music!
|
|
36
59
|
rake (>= 0.9)
|
|
60
|
+
rb-fsevent (~> 0.9.1)
|
|
37
61
|
rspec
|
data/Guardfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# A sample Guardfile
|
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
|
3
|
+
|
|
4
|
+
guard 'bundler' do
|
|
5
|
+
watch('Gemfile')
|
|
6
|
+
# Uncomment next line if Gemfile contain `gemspec' command
|
|
7
|
+
# watch(/^.+\.gemspec/)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
guard 'rspec', :version => 2, :rvm => ['1.9.3', '1.8.7', 'rbx'] do
|
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
|
14
|
+
|
|
15
|
+
# Rails example
|
|
16
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
|
17
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
|
18
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
|
19
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
|
20
|
+
watch('config/routes.rb') { "spec/routing" }
|
|
21
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
|
22
|
+
|
|
23
|
+
# Capybara request specs
|
|
24
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
|
25
|
+
|
|
26
|
+
# Turnip features and steps
|
|
27
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
|
28
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
|
29
|
+
end
|
data/README.md
CHANGED
|
@@ -3,6 +3,8 @@ music
|
|
|
3
3
|
|
|
4
4
|
The *music* gem provides a means on calculating notes and chords.
|
|
5
5
|
|
|
6
|
+
[](https://travis-ci.org/cheerfulstoic/music)
|
|
7
|
+
|
|
6
8
|
Examples:
|
|
7
9
|
---------
|
|
8
10
|
|
|
@@ -39,7 +41,17 @@ Describe chords:
|
|
|
39
41
|
|
|
40
42
|
Chord.new(['C4', 'Eb4', 'Gb4']) # => ['C', :diminished]
|
|
41
43
|
|
|
42
|
-
*For further usage
|
|
44
|
+
*For further usage:*
|
|
45
|
+
* Docs: http://rubydoc.info/github/cheerfulstoic/music/frames
|
|
46
|
+
* See the examples in the spec files
|
|
47
|
+
|
|
48
|
+
TODOs:
|
|
49
|
+
======
|
|
50
|
+
|
|
51
|
+
* Use the term musical term 'interval' or 'semitones' instead of 'distance' in code
|
|
52
|
+
* 'C-' can be used to represent C minor
|
|
53
|
+
* There can be many versions of chords. See: http://giventowail.com/lessons/evan/the-basics-major-minor-and-power-chords?phpMyAdmin=97715a053dab6cd797cf01c69d7492a4
|
|
54
|
+
* Implement calculation of 'cents'
|
|
43
55
|
|
|
44
56
|
Contributing to music
|
|
45
57
|
=====================
|
data/lib/music/chord.rb
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
module Music
|
|
2
2
|
class Chord
|
|
3
|
+
|
|
4
|
+
def eql?(other_chord)
|
|
5
|
+
@notes == other_chord.notes
|
|
6
|
+
end
|
|
7
|
+
def hash
|
|
8
|
+
@notes.hash
|
|
9
|
+
end
|
|
10
|
+
def ==(other_chord)
|
|
11
|
+
self.eql?(other_chord)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :notes
|
|
15
|
+
|
|
3
16
|
def initialize(notes)
|
|
4
|
-
|
|
17
|
+
raise ArgumentError, 'Chords must have at least two notes' if notes.size < 2
|
|
18
|
+
@notes = Set.new(notes) do |note|
|
|
5
19
|
if note.is_a?(Note)
|
|
6
20
|
note
|
|
7
21
|
else
|
|
8
22
|
Note.new(note)
|
|
9
23
|
end
|
|
10
|
-
end
|
|
24
|
+
end
|
|
11
25
|
end
|
|
12
26
|
|
|
27
|
+
# Spec and implement
|
|
28
|
+
# def to_i
|
|
29
|
+
|
|
13
30
|
def note_strings
|
|
14
|
-
@notes.collect(&:note_string)
|
|
31
|
+
Set.new(@notes.collect(&:note_string))
|
|
15
32
|
end
|
|
16
33
|
|
|
17
34
|
def describe
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
note_array = @notes.to_a.sort
|
|
36
|
+
distances = (1...note_array.size).collect do |i|
|
|
37
|
+
note_array[0].distance_to(note_array[i])
|
|
20
38
|
end
|
|
21
39
|
|
|
22
40
|
quality = case distances
|
|
@@ -40,7 +58,50 @@ module Music
|
|
|
40
58
|
:augmented_7
|
|
41
59
|
end
|
|
42
60
|
|
|
43
|
-
[
|
|
61
|
+
[note_array.first.letter, quality]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_s
|
|
65
|
+
@notes.to_a.sort.collect(&:to_s).join(' / ')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Give the Nth inversion of the chord which simply adjusts the lowest N notes up by one octive
|
|
69
|
+
#
|
|
70
|
+
# @returns [Chord] The specified inversion of chord
|
|
71
|
+
def inversion(amount)
|
|
72
|
+
raise ArgumentError, "Inversion amount must be greater than or equal to 1" if amount < 1
|
|
73
|
+
raise ArgumentError, "Not enough notes in chord for inversion" if amount >= @notes.size
|
|
74
|
+
|
|
75
|
+
note_array = @notes.to_a.sort
|
|
76
|
+
notes = (0...amount).collect { note_array.shift.adjust_by_semitones(12) }
|
|
77
|
+
Chord.new(notes + note_array)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Calls inversion(1)
|
|
81
|
+
def first_inversion
|
|
82
|
+
self.inversion(1)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Calls inversion(2)
|
|
86
|
+
def second_inversion
|
|
87
|
+
self.inversion(2)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Calls inversion(3)
|
|
91
|
+
def third_inversion
|
|
92
|
+
self.inversion(3)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class << self
|
|
96
|
+
def parse_chord_string(chord_string, assumed_octave = nil)
|
|
97
|
+
if note_string_match = chord_string.match(/^([A-Ga-g])([#b]?)([^\d]*)(\d*)$/)
|
|
98
|
+
full_string, note, accidental, interval, octave = note_string_match.to_a
|
|
99
|
+
|
|
100
|
+
raise ArgumentError, 'No octave found and no octave assumed' if note.blank? && assumed_octave.nil?
|
|
101
|
+
|
|
102
|
+
Note.new(note + accidental + octave, assumed_octave).chord(interval)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
44
105
|
end
|
|
45
106
|
end
|
|
46
107
|
end
|
data/lib/music/note.rb
CHANGED
|
@@ -16,7 +16,22 @@ module Music
|
|
|
16
16
|
def <=>(other_note)
|
|
17
17
|
self.frequency <=> other_note.frequency
|
|
18
18
|
end
|
|
19
|
+
def hash
|
|
20
|
+
self.frequency.hash
|
|
21
|
+
end
|
|
22
|
+
def eql?(other_note)
|
|
23
|
+
self.frequency == other_note.frequency
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
self.note_string
|
|
28
|
+
end
|
|
19
29
|
|
|
30
|
+
# Creates a new note
|
|
31
|
+
#
|
|
32
|
+
# @param [String, Numeric] descriptor Either a string describing the note (e.g. 'C#4') or a number giving the note's frequency (e.g. 440)
|
|
33
|
+
# @param [Numeric, nil] assumed_octave If no octive is given in the descriptor, use this
|
|
34
|
+
# @returns [Note] Note specified
|
|
20
35
|
def initialize(descriptor, assumed_octave = nil)
|
|
21
36
|
self.frequency = if descriptor.is_a? Numeric
|
|
22
37
|
Note.nearest_note_frequency(descriptor)
|
|
@@ -25,35 +40,214 @@ module Music
|
|
|
25
40
|
end
|
|
26
41
|
end
|
|
27
42
|
|
|
43
|
+
# Returns string representing note with letter, accidental, and octave number
|
|
44
|
+
# e.g. 'C#5'
|
|
45
|
+
#
|
|
46
|
+
# @param [boolean] give_flat Should the result give a flat? (defults to giving a sharp)
|
|
47
|
+
# @return [String] The resulting note string
|
|
28
48
|
def note_string(give_flat = false)
|
|
29
49
|
Note.calculate_note(self.frequency, give_flat).join
|
|
30
50
|
end
|
|
31
51
|
|
|
52
|
+
# Returns the letter portion of the note
|
|
53
|
+
# e.g. 'C'
|
|
54
|
+
#
|
|
55
|
+
# @param [boolean] give_flat Should the result be based on giving a flat? (defaults to giving a sharp)
|
|
56
|
+
# @return [String] The resulting note letter
|
|
32
57
|
def letter(give_flat = false)
|
|
33
58
|
Note.calculate_note(self.frequency, give_flat)[0]
|
|
34
59
|
end
|
|
35
60
|
|
|
61
|
+
# Returns the accidental portion of the note
|
|
62
|
+
# e.g. '#' or 'b'
|
|
63
|
+
#
|
|
64
|
+
# @param [boolean] give_flat Should the result give a flat? (defaults to giving a sharp)
|
|
65
|
+
# @return [String] The resulting accidental
|
|
36
66
|
def accidental(give_flat = false)
|
|
37
67
|
Note.calculate_note(self.frequency, give_flat)[1]
|
|
38
68
|
end
|
|
39
69
|
|
|
70
|
+
# Returns the octive number of the note
|
|
71
|
+
# e.g. 4
|
|
72
|
+
#
|
|
73
|
+
# @return [Fixnum] The resulting octive number
|
|
40
74
|
def octave
|
|
41
75
|
Note.calculate_note(self.frequency)[2]
|
|
42
76
|
end
|
|
43
77
|
|
|
44
|
-
|
|
78
|
+
# Return the previous note (adjusted by one semitone down)
|
|
79
|
+
#
|
|
80
|
+
# @return [Note] The previous note
|
|
81
|
+
def prev
|
|
45
82
|
Note.new(Note.frequency_adjustment(self.frequency, -1))
|
|
46
83
|
end
|
|
47
84
|
|
|
85
|
+
# Return the next note (adjusted by one semitone up)
|
|
86
|
+
#
|
|
87
|
+
# @return [Note] The next note
|
|
48
88
|
def succ
|
|
49
89
|
Note.new(Note.frequency_adjustment(self.frequency, 1))
|
|
50
90
|
end
|
|
51
91
|
alias :next :succ
|
|
52
92
|
|
|
93
|
+
# Return the distance (in semitones) to a note
|
|
94
|
+
#
|
|
95
|
+
# @return [Fixnum] Number of semitones
|
|
53
96
|
def distance_to(note)
|
|
54
97
|
Note.note_distance(self.note_string, note.note_string)
|
|
55
98
|
end
|
|
56
99
|
|
|
100
|
+
# Return another note adjusted by a given interval
|
|
101
|
+
#
|
|
102
|
+
# @param [Fixnum] interval Number of semitones to adjust by
|
|
103
|
+
# @return [Note] Resulting note after adjustment
|
|
104
|
+
def adjust_by_semitones(interval)
|
|
105
|
+
Note.new(Note.frequency_adjustment(self.frequency, interval))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
:minor_second => 1,
|
|
110
|
+
:major_second => 2,
|
|
111
|
+
|
|
112
|
+
:minor_third => 3,
|
|
113
|
+
:major_third => 4,
|
|
114
|
+
|
|
115
|
+
:perfect_fourth => 5,
|
|
116
|
+
|
|
117
|
+
:tritone => 6, :diminished_fifth => 6, :flat_fifth => 6, :augmented_fourth => 6,
|
|
118
|
+
:perfect_fifth => 7,
|
|
119
|
+
:augmented_fifth => 8, :minor_sixth => 8,
|
|
120
|
+
|
|
121
|
+
:major_sixth => 9, :diminished_seventh => 9,
|
|
122
|
+
:minor_seventh => 10,
|
|
123
|
+
:major_seventh => 11
|
|
124
|
+
}.each do |interval, semitones_count|
|
|
125
|
+
define_method interval do
|
|
126
|
+
adjust_by_semitones(semitones_count)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Uses note as key to give major scale
|
|
131
|
+
#
|
|
132
|
+
# @returns [Array<Note>] Notes in major scale
|
|
133
|
+
def major_scale
|
|
134
|
+
[self,
|
|
135
|
+
self.major_second,
|
|
136
|
+
self.major_third,
|
|
137
|
+
self.perfect_fourth,
|
|
138
|
+
self.perfect_fifth,
|
|
139
|
+
self.major_sixth,
|
|
140
|
+
self.major_seventh,
|
|
141
|
+
]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Uses note as key to give minor scale
|
|
145
|
+
#
|
|
146
|
+
# @returns [Array<Note>] Notes in minor scale
|
|
147
|
+
def minor_scale
|
|
148
|
+
[self,
|
|
149
|
+
self.major_second,
|
|
150
|
+
self.minor_third,
|
|
151
|
+
self.perfect_fourth,
|
|
152
|
+
self.perfect_fifth,
|
|
153
|
+
self.minor_sixth,
|
|
154
|
+
self.minor_seventh,
|
|
155
|
+
]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
CHORD_INTERVALS = {
|
|
159
|
+
:minor => [:minor_third, :perfect_fifth],
|
|
160
|
+
:major => [:major_third, :perfect_fifth],
|
|
161
|
+
:fifth => [:perfect_fifth],
|
|
162
|
+
:diminished => [:minor_third, :diminished_fifth],
|
|
163
|
+
:augmented => [:major_third, :augmented_fifth],
|
|
164
|
+
:major_seventh => [:major_third, :perfect_fifth, :major_seventh],
|
|
165
|
+
:minor_seventh => [:minor_third, :perfect_fifth, :minor_seventh],
|
|
166
|
+
:diminished_seventh => [:minor_third, :diminished_fifth, :diminished_seventh],
|
|
167
|
+
:augmented_seventh => [:major_third, :augmented_fifth, :minor_seventh],
|
|
168
|
+
:half_diminished_seventh => [:minor_third, :diminished_fifth, :minor_seventh]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
CHORD_ALIASES = {
|
|
172
|
+
:min => :minor,
|
|
173
|
+
:m => :minor,
|
|
174
|
+
:maj => :major,
|
|
175
|
+
:M => :major,
|
|
176
|
+
:p => :fifth,
|
|
177
|
+
:pow => :fifth,
|
|
178
|
+
:power => :fifth,
|
|
179
|
+
:'5' => :fifth,
|
|
180
|
+
:'5th' => :fifth,
|
|
181
|
+
:dim => :diminished,
|
|
182
|
+
:aug => :augmented,
|
|
183
|
+
:'+' => :augmented,
|
|
184
|
+
|
|
185
|
+
:maj_seventh => :major_seventh,
|
|
186
|
+
:major_7 => :major_seventh,
|
|
187
|
+
:major_7th => :major_seventh,
|
|
188
|
+
:maj_7 => :major_seventh,
|
|
189
|
+
:maj_7th => :major_seventh,
|
|
190
|
+
:maj7 => :major_seventh,
|
|
191
|
+
:maj7th => :major_seventh,
|
|
192
|
+
:M7 => :major_seventh,
|
|
193
|
+
|
|
194
|
+
:min_seventh => :minor_seventh,
|
|
195
|
+
:minor_7 => :minor_seventh,
|
|
196
|
+
:minor_7th => :minor_seventh,
|
|
197
|
+
:min_7 => :minor_seventh,
|
|
198
|
+
:min_7th => :minor_seventh,
|
|
199
|
+
:min7 => :minor_seventh,
|
|
200
|
+
:min7th => :minor_seventh,
|
|
201
|
+
:m7 => :minor_seventh,
|
|
202
|
+
|
|
203
|
+
:dim_seventh => :diminished_seventh,
|
|
204
|
+
:diminished_7 => :diminished_seventh,
|
|
205
|
+
:diminished_7th => :diminished_seventh,
|
|
206
|
+
:dim_7 => :diminished_seventh,
|
|
207
|
+
:dim_7th => :diminished_seventh,
|
|
208
|
+
:dim7 => :diminished_seventh,
|
|
209
|
+
:dim7th => :diminished_seventh,
|
|
210
|
+
:d7 => :diminished_seventh,
|
|
211
|
+
|
|
212
|
+
:aug_seventh => :augmented_seventh,
|
|
213
|
+
:augmented_7 => :augmented_seventh,
|
|
214
|
+
:augmented_7th => :augmented_seventh,
|
|
215
|
+
:aug_7 => :augmented_seventh,
|
|
216
|
+
:aug_7th => :augmented_seventh,
|
|
217
|
+
:aug7 => :augmented_seventh,
|
|
218
|
+
:aug7th => :augmented_seventh,
|
|
219
|
+
:'+7' => :augmented_seventh,
|
|
220
|
+
|
|
221
|
+
:half_dim_seventh => :half_diminished_seventh,
|
|
222
|
+
:half_diminished_7 => :half_diminished_seventh,
|
|
223
|
+
:half_diminished_7th => :half_diminished_seventh,
|
|
224
|
+
:half_dim_7 => :half_diminished_seventh,
|
|
225
|
+
:half_dim_7th => :half_diminished_seventh,
|
|
226
|
+
:half_dim7 => :half_diminished_seventh,
|
|
227
|
+
:half_dim7th => :half_diminished_seventh,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
def chord(description)
|
|
231
|
+
description = :major if description.blank?
|
|
232
|
+
|
|
233
|
+
description = description.to_s
|
|
234
|
+
description.downcase! unless ['M', 'M7'].include?(description)
|
|
235
|
+
description.gsub!(/[\s\-]+/, '_')
|
|
236
|
+
description = description.to_sym
|
|
237
|
+
|
|
238
|
+
intervals = CHORD_INTERVALS[description] || CHORD_INTERVALS[CHORD_ALIASES[description]]
|
|
239
|
+
|
|
240
|
+
if intervals
|
|
241
|
+
Chord.new([self] + intervals.collect {|interval| self.send(interval) })
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
(CHORD_INTERVALS.keys + CHORD_ALIASES.keys).each do |chord_description|
|
|
246
|
+
define_method "#{chord_description}_chord" do
|
|
247
|
+
self.chord(chord_description)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
57
251
|
class << self
|
|
58
252
|
extend ActiveSupport::Memoizable
|
|
59
253
|
|
data/lib/music/version.rb
CHANGED
data/spec/classes/chord_spec.rb
CHANGED
|
@@ -3,6 +3,16 @@ require 'spec_helper'
|
|
|
3
3
|
describe Music::Chord do
|
|
4
4
|
before :all do
|
|
5
5
|
@standard_tuning_notes = [Note.new('E2'), Note.new('A2'), Note.new('D3'), Note.new('G3'), Note.new('B3'), Note.new('E4')]
|
|
6
|
+
|
|
7
|
+
@c_minor = Chord.new(['C4', 'Eb4', 'G4'])
|
|
8
|
+
@c_major = Chord.new(['C4', 'E4', 'G4'])
|
|
9
|
+
@c_diminished = Chord.new(['C4', 'Eb4', 'Gb4'])
|
|
10
|
+
@c_augmented = Chord.new(['C4', 'E4', 'G#4'])
|
|
11
|
+
@c_major_seventh = Chord.new(['C4', 'E4', 'G4', 'B4'])
|
|
12
|
+
@c_minor_seventh = Chord.new(['C4', 'Eb4', 'G4', 'Bb4'])
|
|
13
|
+
@c_diminished_seventh = Chord.new(['C4', 'Eb4', 'Gb4', 'A4'])
|
|
14
|
+
@c_augmented_seventh = Chord.new(['C4', 'E4', 'G#4', 'Bb4'])
|
|
15
|
+
@c_half_diminished_seventh = Chord.new(['C4', 'Eb4', 'Gb4', 'Bb4'])
|
|
6
16
|
end
|
|
7
17
|
|
|
8
18
|
describe '#new(notes)' do
|
|
@@ -14,23 +24,36 @@ describe Music::Chord do
|
|
|
14
24
|
Chord.new(@standard_tuning_notes.collect(&:note_string))
|
|
15
25
|
end
|
|
16
26
|
|
|
17
|
-
it 'should
|
|
18
|
-
Chord.new(['
|
|
27
|
+
it 'should validate that chords must have at least two notes' do
|
|
28
|
+
lambda { Chord.new(['C4']) }.should raise_error(ArgumentError, 'Chords must have at least two notes')
|
|
29
|
+
lambda { Chord.new([]) }.should raise_error(ArgumentError, 'Chords must have at least two notes')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#==' do
|
|
34
|
+
it 'should recognize that the order of notes in the chord does not matter' do
|
|
35
|
+
Chord.new(['C4', 'Eb4', 'G4']).should == Chord.new(['G4', 'Eb4', 'C4'])
|
|
19
36
|
end
|
|
20
37
|
end
|
|
21
38
|
|
|
22
39
|
describe '#note_strings' do
|
|
23
|
-
it 'should return
|
|
40
|
+
it 'should return a set of note strings' do
|
|
24
41
|
chord = Chord.new(@standard_tuning_notes)
|
|
25
42
|
|
|
26
|
-
chord.note_strings.should == @standard_tuning_notes.collect(&:note_string)
|
|
43
|
+
chord.note_strings.should == Set.new(@standard_tuning_notes.collect(&:note_string))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#to_s' do
|
|
48
|
+
it 'should output just the sorted note descriptions separated by slashes' do
|
|
49
|
+
@c_minor.to_s.should == 'C4 / D#4 / G4'
|
|
27
50
|
end
|
|
28
51
|
end
|
|
29
52
|
|
|
30
53
|
describe '#describe' do
|
|
31
54
|
describe "triads" do
|
|
32
55
|
it 'should recognize C major' do
|
|
33
|
-
|
|
56
|
+
@c_major.describe.should == ['C', :major]
|
|
34
57
|
end
|
|
35
58
|
|
|
36
59
|
it 'should recognize C minor' do
|
|
@@ -62,4 +85,98 @@ describe Music::Chord do
|
|
|
62
85
|
end
|
|
63
86
|
end
|
|
64
87
|
end
|
|
88
|
+
|
|
89
|
+
describe '.parse_chord_string' do
|
|
90
|
+
it 'should recognize C major' do
|
|
91
|
+
Chord.parse_chord_string('Cmajor4').should == @c_major
|
|
92
|
+
Chord.parse_chord_string('CMajor4').should == @c_major
|
|
93
|
+
Chord.parse_chord_string('Cmaj4').should == @c_major
|
|
94
|
+
Chord.parse_chord_string('CMaj4').should == @c_major
|
|
95
|
+
Chord.parse_chord_string('CM4').should == @c_major
|
|
96
|
+
Chord.parse_chord_string('C4').should == @c_major
|
|
97
|
+
|
|
98
|
+
expect { Chord.parse_chord_string('C') }.to raise_error ArgumentError
|
|
99
|
+
Chord.parse_chord_string('C', 4).should == @c_major
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'should recognize C minor' do
|
|
103
|
+
Chord.parse_chord_string('Cminor4').should == @c_minor
|
|
104
|
+
Chord.parse_chord_string('CMinor4').should == @c_minor
|
|
105
|
+
Chord.parse_chord_string('Cmin4').should == @c_minor
|
|
106
|
+
Chord.parse_chord_string('CMin4').should == @c_minor
|
|
107
|
+
Chord.parse_chord_string('Cm4').should == @c_minor
|
|
108
|
+
|
|
109
|
+
expect { Chord.parse_chord_string('Cm') }.to raise_error ArgumentError
|
|
110
|
+
Chord.parse_chord_string('Cm', 4).should == @c_minor
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'should recognize C diminished' do
|
|
115
|
+
Chord.parse_chord_string('Cdiminished4').should == @c_diminished
|
|
116
|
+
Chord.parse_chord_string('CDiminished4').should == @c_diminished
|
|
117
|
+
Chord.parse_chord_string('Cdim4').should == @c_diminished
|
|
118
|
+
Chord.parse_chord_string('CDim4').should == @c_diminished
|
|
119
|
+
Chord.parse_chord_string('CDIM4').should == @c_diminished
|
|
120
|
+
|
|
121
|
+
expect { Chord.parse_chord_string('Cdim').should == @c_diminished }.to raise_error ArgumentError
|
|
122
|
+
Chord.parse_chord_string('Cdim', 4).should == @c_diminished
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'should recognize C augmented' do
|
|
126
|
+
Chord.parse_chord_string('Caugmented4').should == @c_augmented
|
|
127
|
+
Chord.parse_chord_string('Caugmented4').should == @c_augmented
|
|
128
|
+
Chord.parse_chord_string('Caug4').should == @c_augmented
|
|
129
|
+
Chord.parse_chord_string('CAug4').should == @c_augmented
|
|
130
|
+
Chord.parse_chord_string('CAUG4').should == @c_augmented
|
|
131
|
+
Chord.parse_chord_string('C+4').should == @c_augmented
|
|
132
|
+
|
|
133
|
+
expect { Chord.parse_chord_string('Caug').should == @c_augmented }.to raise_error ArgumentError
|
|
134
|
+
Chord.parse_chord_string('Caug', 4).should == @c_augmented
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# TODO: Fill out other chords
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe '#inversion' do
|
|
141
|
+
it 'should adjust the lowest n notes up by an octive' do
|
|
142
|
+
@c_major.inversion(1).should == Chord.new(['E4', 'G4', 'C5'])
|
|
143
|
+
Chord.new(['Eb4', 'C4', 'Gb4']).inversion(1).should == Chord.new(['Eb4', 'C5', 'Gb4'])
|
|
144
|
+
|
|
145
|
+
@c_major.inversion(2).should == Chord.new(['E5', 'G4', 'C5'])
|
|
146
|
+
Chord.new(['Eb4', 'C4', 'Gb4']).inversion(2).should == Chord.new(['Eb5', 'C5', 'Gb4'])
|
|
147
|
+
|
|
148
|
+
@c_augmented_seventh.inversion(3).should == Chord.new(['E5', 'G#5', 'C5', 'Bb4'])
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'should raise an error when the inversion amount is too great' do
|
|
152
|
+
lambda { @c_major.inversion(3) }.should raise_error(ArgumentError, 'Not enough notes in chord for inversion')
|
|
153
|
+
lambda { @c_major.inversion(4) }.should raise_error(ArgumentError, 'Not enough notes in chord for inversion')
|
|
154
|
+
|
|
155
|
+
lambda { @c_augmented_seventh.inversion(4) }.should raise_error(ArgumentError, 'Not enough notes in chord for inversion')
|
|
156
|
+
lambda { @c_augmented_seventh.inversion(5) }.should raise_error(ArgumentError, 'Not enough notes in chord for inversion')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'should raise an error when the inversion amount is too small' do
|
|
160
|
+
lambda { @c_major.inversion(0) }.should raise_error(ArgumentError, 'Inversion amount must be greater than or equal to 1')
|
|
161
|
+
lambda { @c_major.inversion(-1) }.should raise_error(ArgumentError, 'Inversion amount must be greater than or equal to 1')
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe '#first_inversion' do
|
|
166
|
+
it 'should adjust the lowest note up by an octive' do
|
|
167
|
+
@c_major.first_inversion.should == Chord.new(['E4', 'G4', 'C5'])
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
describe '#second_inversion' do
|
|
172
|
+
it 'should adjust the lowest two notes up by an octive' do
|
|
173
|
+
@c_major.second_inversion.should == Chord.new(['E5', 'G4', 'C5'])
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe '#third_inversion' do
|
|
178
|
+
it 'should adjust the lowest three notes up by an octive' do
|
|
179
|
+
@c_augmented_seventh.third_inversion.should == Chord.new(['E5', 'G#5', 'C5', 'Bb4'])
|
|
180
|
+
end
|
|
181
|
+
end
|
|
65
182
|
end
|
data/spec/classes/note_spec.rb
CHANGED
|
@@ -151,6 +151,330 @@ describe Music::Note do
|
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
+
describe 'interval calculations' do
|
|
155
|
+
let(:c4) { Note.new('C4') }
|
|
156
|
+
let(:b4) { Note.new('B4') }
|
|
157
|
+
|
|
158
|
+
it { c4.should have_an_interval :minor_second, 'C#4' }
|
|
159
|
+
it { b4.should have_an_interval :minor_second, 'C5' }
|
|
160
|
+
|
|
161
|
+
it { c4.should have_an_interval :major_second, 'D4' }
|
|
162
|
+
it { b4.should have_an_interval :major_second, 'C#5' }
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
it { c4.should have_an_interval :minor_third, 'D#4' }
|
|
166
|
+
it { b4.should have_an_interval :minor_third, 'D5' }
|
|
167
|
+
|
|
168
|
+
it { c4.should have_an_interval :major_third, 'E4' }
|
|
169
|
+
it { b4.should have_an_interval :major_third, 'D#5' }
|
|
170
|
+
|
|
171
|
+
it { c4.should have_an_interval :perfect_fourth, 'F4' }
|
|
172
|
+
it { b4.should have_an_interval :perfect_fourth, 'E5' }
|
|
173
|
+
|
|
174
|
+
# Enharmonic equivalents
|
|
175
|
+
it { c4.should have_an_interval :tritone, 'F#4' }
|
|
176
|
+
it { b4.should have_an_interval :tritone, 'F5' }
|
|
177
|
+
it { c4.should have_an_interval :diminished_fifth, 'F#4' }
|
|
178
|
+
it { b4.should have_an_interval :diminished_fifth, 'F5' }
|
|
179
|
+
it { c4.should have_an_interval :flat_fifth, 'F#4' }
|
|
180
|
+
it { b4.should have_an_interval :flat_fifth, 'F5' }
|
|
181
|
+
it { c4.should have_an_interval :augmented_fourth, 'F#4' }
|
|
182
|
+
it { b4.should have_an_interval :augmented_fourth, 'F5' }
|
|
183
|
+
|
|
184
|
+
it { c4.should have_an_interval :perfect_fifth, 'G4' }
|
|
185
|
+
it { b4.should have_an_interval :perfect_fifth, 'F#5' }
|
|
186
|
+
|
|
187
|
+
# Enharmonic equivalents
|
|
188
|
+
it { c4.should have_an_interval :augmented_fifth, 'G#4' }
|
|
189
|
+
it { b4.should have_an_interval :augmented_fifth, 'G5' }
|
|
190
|
+
it { c4.should have_an_interval :minor_sixth, 'G#4' }
|
|
191
|
+
it { b4.should have_an_interval :minor_sixth, 'G5' }
|
|
192
|
+
|
|
193
|
+
it { c4.should have_an_interval :major_sixth, 'A4' }
|
|
194
|
+
it { b4.should have_an_interval :major_sixth, 'G#5' }
|
|
195
|
+
|
|
196
|
+
it { c4.should have_an_interval :diminished_seventh, 'A4' }
|
|
197
|
+
it { b4.should have_an_interval :diminished_seventh, 'G#5' }
|
|
198
|
+
|
|
199
|
+
it { c4.should have_an_interval :minor_seventh, 'A#4' }
|
|
200
|
+
it { b4.should have_an_interval :minor_seventh, 'A5' }
|
|
201
|
+
|
|
202
|
+
it { c4.should have_an_interval :major_seventh, 'B4' }
|
|
203
|
+
it { b4.should have_an_interval :major_seventh, 'A#5' }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
describe 'scales from notes (as scale key)' do
|
|
208
|
+
describe '#major_scale' do
|
|
209
|
+
Note.new('C4').major_scale.should == [
|
|
210
|
+
Note.new('C4'),
|
|
211
|
+
Note.new('D4'),
|
|
212
|
+
Note.new('E4'),
|
|
213
|
+
Note.new('F4'),
|
|
214
|
+
Note.new('G4'),
|
|
215
|
+
Note.new('A4'),
|
|
216
|
+
Note.new('B4')
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
Note.new('G4').major_scale.should == [
|
|
220
|
+
Note.new('G4'),
|
|
221
|
+
Note.new('A4'),
|
|
222
|
+
Note.new('B4'),
|
|
223
|
+
Note.new('C5'),
|
|
224
|
+
Note.new('D5'),
|
|
225
|
+
Note.new('E5'),
|
|
226
|
+
Note.new('F#5')
|
|
227
|
+
]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe '#minor_scale' do
|
|
231
|
+
Note.new('C4').minor_scale.should == [
|
|
232
|
+
Note.new('C4'),
|
|
233
|
+
Note.new('D4'),
|
|
234
|
+
Note.new('D#4'),
|
|
235
|
+
Note.new('F4'),
|
|
236
|
+
Note.new('G4'),
|
|
237
|
+
Note.new('G#4'),
|
|
238
|
+
Note.new('A#4')
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
Note.new('G4').minor_scale.should == [
|
|
242
|
+
Note.new('G4'),
|
|
243
|
+
Note.new('A4'),
|
|
244
|
+
Note.new('A#4'),
|
|
245
|
+
Note.new('C5'),
|
|
246
|
+
Note.new('D5'),
|
|
247
|
+
Note.new('D#5'),
|
|
248
|
+
Note.new('F5')
|
|
249
|
+
]
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
describe '#to_s' do
|
|
254
|
+
it 'should output the note_string' do
|
|
255
|
+
Note.new('E4').to_s.should == 'E4'
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it 'should use the sharp version' do
|
|
259
|
+
Note.new('D#5').to_s.should == 'D#5'
|
|
260
|
+
Note.new('Eb5').to_s.should == 'D#5'
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
describe 'chords from notes' do
|
|
265
|
+
c4 = Note.new('C4')
|
|
266
|
+
|
|
267
|
+
c_minor = Chord.new(['C4', 'Eb4', 'G4'])
|
|
268
|
+
c_major = Chord.new(['C4', 'E4', 'G4'])
|
|
269
|
+
c_fifth = Chord.new(['C4', 'G4'])
|
|
270
|
+
c_diminished = Chord.new(['C4', 'Eb4', 'Gb4'])
|
|
271
|
+
c_augmented = Chord.new(['C4', 'E4', 'G#4'])
|
|
272
|
+
c_major_seventh = Chord.new(['C4', 'E4', 'G4', 'B4'])
|
|
273
|
+
c_minor_seventh = Chord.new(['C4', 'Eb4', 'G4', 'Bb4'])
|
|
274
|
+
c_diminished_seventh = Chord.new(['C4', 'Eb4', 'Gb4', 'A4'])
|
|
275
|
+
c_augmented_seventh = Chord.new(['C4', 'E4', 'G#4', 'Bb4'])
|
|
276
|
+
c_half_diminished_seventh = Chord.new(['C4', 'Eb4', 'Gb4', 'Bb4'])
|
|
277
|
+
|
|
278
|
+
describe 'chords from notes' do
|
|
279
|
+
describe '#chord' do
|
|
280
|
+
it 'should recognize minor chords' do
|
|
281
|
+
c4.chord(:minor).should == c_minor
|
|
282
|
+
c4.chord('Minor').should == c_minor
|
|
283
|
+
c4.chord('minor').should == c_minor
|
|
284
|
+
c4.chord('min').should == c_minor
|
|
285
|
+
c4.chord('MIN').should == c_minor
|
|
286
|
+
c4.chord('m').should == c_minor
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it 'should recognize major chords' do
|
|
290
|
+
c4.chord(:major).should == c_major
|
|
291
|
+
c4.chord('Major').should == c_major
|
|
292
|
+
c4.chord('major').should == c_major
|
|
293
|
+
c4.chord('maj').should == c_major
|
|
294
|
+
c4.chord('MAJ').should == c_major
|
|
295
|
+
c4.chord('M').should == c_major
|
|
296
|
+
c4.chord('').should == c_major
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it 'should recognize power chords' do
|
|
300
|
+
c4.chord(:power).should == c_fifth
|
|
301
|
+
c4.chord(:fifth).should == c_fifth
|
|
302
|
+
c4.chord('Power').should == c_fifth
|
|
303
|
+
c4.chord('power').should == c_fifth
|
|
304
|
+
c4.chord('Fifth').should == c_fifth
|
|
305
|
+
c4.chord('fifth').should == c_fifth
|
|
306
|
+
c4.chord('pow').should == c_fifth
|
|
307
|
+
c4.chord('POW').should == c_fifth
|
|
308
|
+
c4.chord('5').should == c_fifth
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'should recognize diminished chords' do
|
|
312
|
+
c4.chord(:diminished).should == c_diminished
|
|
313
|
+
c4.chord('Diminished').should == c_diminished
|
|
314
|
+
c4.chord('diminished').should == c_diminished
|
|
315
|
+
c4.chord('dim').should == c_diminished
|
|
316
|
+
c4.chord('DIM').should == c_diminished
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it 'should recognize augmented chords' do
|
|
320
|
+
c4.chord(:augmented).should == c_augmented
|
|
321
|
+
c4.chord('Augmented').should == c_augmented
|
|
322
|
+
c4.chord('augmented').should == c_augmented
|
|
323
|
+
c4.chord('aug').should == c_augmented
|
|
324
|
+
c4.chord('AUG').should == c_augmented
|
|
325
|
+
c4.chord('+').should == c_augmented
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it 'should recognize major seventh chords' do
|
|
329
|
+
c4.chord(:major_seventh).should == c_major_seventh
|
|
330
|
+
c4.chord('major_seventh').should == c_major_seventh
|
|
331
|
+
c4.chord('major seventh').should == c_major_seventh
|
|
332
|
+
c4.chord('Major seventh').should == c_major_seventh
|
|
333
|
+
c4.chord('maj seventh').should == c_major_seventh
|
|
334
|
+
c4.chord('maj 7').should == c_major_seventh
|
|
335
|
+
c4.chord('maj 7th').should == c_major_seventh
|
|
336
|
+
c4.chord('maj7').should == c_major_seventh
|
|
337
|
+
c4.chord('maj7th').should == c_major_seventh
|
|
338
|
+
c4.chord('MAJ7').should == c_major_seventh
|
|
339
|
+
c4.chord('M7').should == c_major_seventh
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it 'should recognize minor seventh chords' do
|
|
343
|
+
c4.chord(:minor_seventh).should == c_minor_seventh
|
|
344
|
+
c4.chord('minor_seventh').should == c_minor_seventh
|
|
345
|
+
c4.chord('minor seventh').should == c_minor_seventh
|
|
346
|
+
c4.chord('minor seventh').should == c_minor_seventh
|
|
347
|
+
c4.chord('min seventh').should == c_minor_seventh
|
|
348
|
+
c4.chord('min 7').should == c_minor_seventh
|
|
349
|
+
c4.chord('min 7th').should == c_minor_seventh
|
|
350
|
+
c4.chord('min7').should == c_minor_seventh
|
|
351
|
+
c4.chord('min7th').should == c_minor_seventh
|
|
352
|
+
c4.chord('min7').should == c_minor_seventh
|
|
353
|
+
c4.chord('m7').should == c_minor_seventh
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it 'should recognize diminished seventh chords' do
|
|
357
|
+
c4.chord(:diminished_seventh).should == c_diminished_seventh
|
|
358
|
+
c4.chord('diminished_seventh').should == c_diminished_seventh
|
|
359
|
+
c4.chord('diminished seventh').should == c_diminished_seventh
|
|
360
|
+
c4.chord('diminished seventh').should == c_diminished_seventh
|
|
361
|
+
c4.chord('dim seventh').should == c_diminished_seventh
|
|
362
|
+
c4.chord('dim 7').should == c_diminished_seventh
|
|
363
|
+
c4.chord('dim 7th').should == c_diminished_seventh
|
|
364
|
+
c4.chord('dim7').should == c_diminished_seventh
|
|
365
|
+
c4.chord('dim7th').should == c_diminished_seventh
|
|
366
|
+
c4.chord('dim7').should == c_diminished_seventh
|
|
367
|
+
c4.chord('d7').should == c_diminished_seventh
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
it 'should recognize augmented seventh chords' do
|
|
371
|
+
c4.chord(:augmented_seventh).should == c_augmented_seventh
|
|
372
|
+
c4.chord('augmented_seventh').should == c_augmented_seventh
|
|
373
|
+
c4.chord('augmented seventh').should == c_augmented_seventh
|
|
374
|
+
c4.chord('augmented seventh').should == c_augmented_seventh
|
|
375
|
+
c4.chord('aug seventh').should == c_augmented_seventh
|
|
376
|
+
c4.chord('aug 7').should == c_augmented_seventh
|
|
377
|
+
c4.chord('aug 7th').should == c_augmented_seventh
|
|
378
|
+
c4.chord('aug7').should == c_augmented_seventh
|
|
379
|
+
c4.chord('aug7th').should == c_augmented_seventh
|
|
380
|
+
c4.chord('aug7').should == c_augmented_seventh
|
|
381
|
+
c4.chord('+7').should == c_augmented_seventh
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it 'should recognize half diminished seventh chords' do
|
|
385
|
+
c4.chord(:half_diminished_seventh).should == c_half_diminished_seventh
|
|
386
|
+
c4.chord('half_diminished_7').should == c_half_diminished_seventh
|
|
387
|
+
c4.chord('half_diminished_7th').should == c_half_diminished_seventh
|
|
388
|
+
c4.chord('half-diminished seventh').should == c_half_diminished_seventh
|
|
389
|
+
c4.chord('half-diminished 7').should == c_half_diminished_seventh
|
|
390
|
+
c4.chord('half-diminished 7th').should == c_half_diminished_seventh
|
|
391
|
+
c4.chord('half_dim seventh').should == c_half_diminished_seventh
|
|
392
|
+
c4.chord('half_dim 7').should == c_half_diminished_seventh
|
|
393
|
+
c4.chord('half_dim 7th').should == c_half_diminished_seventh
|
|
394
|
+
c4.chord('half_dim7').should == c_half_diminished_seventh
|
|
395
|
+
c4.chord('half_dim7th').should == c_half_diminished_seventh
|
|
396
|
+
c4.chord('half_dim7').should == c_half_diminished_seventh
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
describe 'chord methods' do
|
|
401
|
+
it 'should have minor methods' do
|
|
402
|
+
c4.minor_chord.should == c_minor
|
|
403
|
+
c4.min_chord.should == c_minor
|
|
404
|
+
c4.m_chord.should == c_minor
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
it 'should have major methods' do
|
|
408
|
+
c4.major_chord.should == c_major
|
|
409
|
+
c4.maj_chord.should == c_major
|
|
410
|
+
c4.M_chord.should == c_major
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
it 'should have diminished methods' do
|
|
414
|
+
c4.diminished_chord.should == c_diminished
|
|
415
|
+
c4.dim_chord.should == c_diminished
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it 'should have augmented methods' do
|
|
419
|
+
c4.augmented_chord.should == c_augmented
|
|
420
|
+
c4.aug_chord.should == c_augmented
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it 'should have major seventh methods' do
|
|
424
|
+
c4.major_seventh_chord.should == c_major_seventh
|
|
425
|
+
c4.maj_seventh_chord.should == c_major_seventh
|
|
426
|
+
c4.maj_7_chord.should == c_major_seventh
|
|
427
|
+
c4.maj_7th_chord.should == c_major_seventh
|
|
428
|
+
c4.maj7_chord.should == c_major_seventh
|
|
429
|
+
c4.maj7th_chord.should == c_major_seventh
|
|
430
|
+
c4.M7_chord.should == c_major_seventh
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it 'should have minor seventh methods' do
|
|
434
|
+
c4.minor_seventh_chord.should == c_minor_seventh
|
|
435
|
+
c4.min_seventh_chord.should == c_minor_seventh
|
|
436
|
+
c4.min_7_chord.should == c_minor_seventh
|
|
437
|
+
c4.min_7th_chord.should == c_minor_seventh
|
|
438
|
+
c4.min7_chord.should == c_minor_seventh
|
|
439
|
+
c4.min7th_chord.should == c_minor_seventh
|
|
440
|
+
c4.m7_chord.should == c_minor_seventh
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
it 'should have diminished seventh methods' do
|
|
444
|
+
c4.diminished_seventh_chord.should == c_diminished_seventh
|
|
445
|
+
c4.dim_seventh_chord.should == c_diminished_seventh
|
|
446
|
+
c4.dim_7_chord.should == c_diminished_seventh
|
|
447
|
+
c4.dim_7th_chord.should == c_diminished_seventh
|
|
448
|
+
c4.dim7_chord.should == c_diminished_seventh
|
|
449
|
+
c4.dim7th_chord.should == c_diminished_seventh
|
|
450
|
+
c4.d7_chord.should == c_diminished_seventh
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
it 'should have augmented seventh methods' do
|
|
454
|
+
c4.augmented_seventh_chord.should == c_augmented_seventh
|
|
455
|
+
c4.aug_seventh_chord.should == c_augmented_seventh
|
|
456
|
+
c4.aug_7_chord.should == c_augmented_seventh
|
|
457
|
+
c4.aug_7th_chord.should == c_augmented_seventh
|
|
458
|
+
c4.aug7_chord.should == c_augmented_seventh
|
|
459
|
+
c4.aug7th_chord.should == c_augmented_seventh
|
|
460
|
+
c4.send('+7_chord').should == c_augmented_seventh
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
it 'should have half diminished seventh methods' do
|
|
464
|
+
c4.half_diminished_seventh_chord.should == c_half_diminished_seventh
|
|
465
|
+
c4.half_diminished_7_chord.should == c_half_diminished_seventh
|
|
466
|
+
c4.half_diminished_7th_chord.should == c_half_diminished_seventh
|
|
467
|
+
c4.half_dim_seventh_chord.should == c_half_diminished_seventh
|
|
468
|
+
c4.half_dim_7_chord.should == c_half_diminished_seventh
|
|
469
|
+
c4.half_dim_7th_chord.should == c_half_diminished_seventh
|
|
470
|
+
c4.half_dim7_chord.should == c_half_diminished_seventh
|
|
471
|
+
c4.half_dim7th_chord.should == c_half_diminished_seventh
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
end
|
|
477
|
+
|
|
154
478
|
describe ".calculate_frequency(letter, accidental, octave)" do
|
|
155
479
|
{
|
|
156
480
|
['C', nil, 0] => 16.35,
|
|
@@ -201,6 +525,8 @@ describe Music::Note do
|
|
|
201
525
|
end
|
|
202
526
|
end
|
|
203
527
|
|
|
528
|
+
# TODO: Should return accurracy
|
|
529
|
+
# Thought: ((frequency off) / (distance to next note's frequency)) * 2.0?
|
|
204
530
|
describe ".calculate_note(frequency)" do
|
|
205
531
|
test_frequencies = {
|
|
206
532
|
[16.35] => ['C', nil, 0],
|
|
@@ -258,7 +584,7 @@ describe Music::Note do
|
|
|
258
584
|
end
|
|
259
585
|
|
|
260
586
|
it "should allow getting of previous note" do
|
|
261
|
-
Note.new(739.99).
|
|
587
|
+
Note.new(739.99).prev.should == Note.new(698.46)
|
|
262
588
|
end
|
|
263
589
|
end
|
|
264
590
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
RSpec::Matchers.define :have_an_interval do |interval_description, expected_note_string|
|
|
2
|
+
match do |note|
|
|
3
|
+
note.send(interval_description).note_string == expected_note_string
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
failure_message_for_should do |note|
|
|
7
|
+
"Expected #{note.note_string}'s #{interval_description} interval to be #{expected_note_string}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
description do |note|
|
|
11
|
+
"have an #{interval_description} interval of #{expected_note_string} for #{note.note_string}"
|
|
12
|
+
end
|
|
13
|
+
end
|
metadata
CHANGED
|
@@ -1,98 +1,94 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: music
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
prerelease:
|
|
6
|
-
segments:
|
|
7
|
-
- 0
|
|
8
|
-
- 5
|
|
9
|
-
- 1
|
|
10
|
-
version: 0.5.1
|
|
11
6
|
platform: ruby
|
|
12
|
-
authors:
|
|
7
|
+
authors:
|
|
13
8
|
- Brian Underwood
|
|
14
9
|
autorequire:
|
|
15
10
|
bindir: bin
|
|
16
11
|
cert_chain: []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- !ruby/object:Gem::Dependency
|
|
12
|
+
date: 2012-11-18 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
21
15
|
name: rake
|
|
22
|
-
|
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
24
17
|
none: false
|
|
25
|
-
requirements:
|
|
26
|
-
- -
|
|
27
|
-
- !ruby/object:Gem::Version
|
|
28
|
-
|
|
29
|
-
segments:
|
|
30
|
-
- 0
|
|
31
|
-
- 9
|
|
32
|
-
version: "0.9"
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0.9'
|
|
33
22
|
type: :development
|
|
34
|
-
version_requirements: *id001
|
|
35
|
-
- !ruby/object:Gem::Dependency
|
|
36
|
-
name: bundler
|
|
37
23
|
prerelease: false
|
|
38
|
-
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ! '>='
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0.9'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: bundler
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
39
33
|
none: false
|
|
40
|
-
requirements:
|
|
41
|
-
- -
|
|
42
|
-
- !ruby/object:Gem::Version
|
|
43
|
-
hash: 21
|
|
44
|
-
segments:
|
|
45
|
-
- 1
|
|
46
|
-
- 1
|
|
47
|
-
- 3
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
48
37
|
version: 1.1.3
|
|
49
38
|
type: :development
|
|
50
|
-
version_requirements: *id002
|
|
51
|
-
- !ruby/object:Gem::Dependency
|
|
52
|
-
name: rspec
|
|
53
39
|
prerelease: false
|
|
54
|
-
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 1.1.3
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rspec
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
49
|
none: false
|
|
56
|
-
requirements:
|
|
57
|
-
- -
|
|
58
|
-
- !ruby/object:Gem::Version
|
|
59
|
-
|
|
60
|
-
segments:
|
|
61
|
-
- 0
|
|
62
|
-
version: "0"
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
63
54
|
type: :development
|
|
64
|
-
version_requirements: *id003
|
|
65
|
-
- !ruby/object:Gem::Dependency
|
|
66
|
-
name: activemodel
|
|
67
55
|
prerelease: false
|
|
68
|
-
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ! '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: activemodel
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
65
|
none: false
|
|
70
|
-
requirements:
|
|
71
|
-
- -
|
|
72
|
-
- !ruby/object:Gem::Version
|
|
73
|
-
hash: 15
|
|
74
|
-
segments:
|
|
75
|
-
- 3
|
|
76
|
-
- 2
|
|
77
|
-
- 0
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
78
69
|
version: 3.2.0
|
|
79
70
|
type: :development
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ! '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 3.2.0
|
|
78
|
+
description: Library for classifying notes and chords and performing calculations
|
|
79
|
+
on them. See README.md
|
|
80
|
+
email:
|
|
83
81
|
- ml+musicgem@semi-sentient.com
|
|
84
82
|
executables: []
|
|
85
|
-
|
|
86
83
|
extensions: []
|
|
87
|
-
|
|
88
84
|
extra_rdoc_files: []
|
|
89
|
-
|
|
90
|
-
files:
|
|
85
|
+
files:
|
|
91
86
|
- .document
|
|
92
87
|
- .gitignore
|
|
93
88
|
- .travis.yml
|
|
94
89
|
- Gemfile
|
|
95
90
|
- Gemfile.lock
|
|
91
|
+
- Guardfile
|
|
96
92
|
- LICENSE.txt
|
|
97
93
|
- README.md
|
|
98
94
|
- Rakefile
|
|
@@ -106,43 +102,35 @@ files:
|
|
|
106
102
|
- spec/classes/hash_spec.rb
|
|
107
103
|
- spec/classes/note_spec.rb
|
|
108
104
|
- spec/spec_helper.rb
|
|
105
|
+
- spec/support/matchers/have_an_interval.rb
|
|
109
106
|
homepage: http://github.com/cheerfulstoic/music
|
|
110
|
-
licenses:
|
|
107
|
+
licenses:
|
|
111
108
|
- MIT
|
|
112
109
|
post_install_message:
|
|
113
110
|
rdoc_options: []
|
|
114
|
-
|
|
115
|
-
require_paths:
|
|
111
|
+
require_paths:
|
|
116
112
|
- lib
|
|
117
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
114
|
none: false
|
|
119
|
-
requirements:
|
|
120
|
-
- -
|
|
121
|
-
- !ruby/object:Gem::Version
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
- 0
|
|
125
|
-
version: "0"
|
|
126
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ! '>='
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
120
|
none: false
|
|
128
|
-
requirements:
|
|
129
|
-
- -
|
|
130
|
-
- !ruby/object:Gem::Version
|
|
131
|
-
|
|
132
|
-
segments:
|
|
133
|
-
- 1
|
|
134
|
-
- 8
|
|
135
|
-
version: "1.8"
|
|
121
|
+
requirements:
|
|
122
|
+
- - ! '>='
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '1.8'
|
|
136
125
|
requirements: []
|
|
137
|
-
|
|
138
126
|
rubyforge_project:
|
|
139
|
-
rubygems_version: 1.8.
|
|
127
|
+
rubygems_version: 1.8.23
|
|
140
128
|
signing_key:
|
|
141
129
|
specification_version: 3
|
|
142
130
|
summary: Library for performing calculations on musical elements
|
|
143
|
-
test_files:
|
|
131
|
+
test_files:
|
|
144
132
|
- spec/classes/chord_spec.rb
|
|
145
133
|
- spec/classes/hash_spec.rb
|
|
146
134
|
- spec/classes/note_spec.rb
|
|
147
135
|
- spec/spec_helper.rb
|
|
148
|
-
|
|
136
|
+
- spec/support/matchers/have_an_interval.rb
|