interval 0.0.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/LICENSE +20 -0
- data/README.mkd +17 -0
- data/Rakefile +53 -0
- data/lib/interval.rb +318 -0
- data/spec/interval_spec.rb +402 -0
- data/spec/spec_helper.rb +2 -0
- metadata +61 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 YOUR NAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mkd
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Interval
|
2
|
+
|
3
|
+
Interval is a tiny library that provides simple musicial note pitch and interval arithmetic.
|
4
|
+
|
5
|
+
Observe:
|
6
|
+
|
7
|
+
>> p = Interval::Pitch.from_string("c")
|
8
|
+
>> i = Interval::Interval.from_string("M3")
|
9
|
+
>> p2 = p + i
|
10
|
+
>> p2.to_short_name
|
11
|
+
=> "e"
|
12
|
+
|
13
|
+
# Authors
|
14
|
+
|
15
|
+
Nate Murray 2009
|
16
|
+
|
17
|
+
based on code from GNU Solfege
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
GEM = "interval"
|
8
|
+
GEM_VERSION = "0.0.1"
|
9
|
+
|
10
|
+
spec = Gem::Specification.new do |s|
|
11
|
+
s.name = GEM
|
12
|
+
s.version = GEM_VERSION
|
13
|
+
s.author = "Nate Murray"
|
14
|
+
s.email = "nate@natemurray.com"
|
15
|
+
s.homepage = "http://www.xcombinator.com"
|
16
|
+
s.description = s.summary = "interval is a tiny library that provides simple musicial note pitch and interval arithmetic."
|
17
|
+
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.has_rdoc = true
|
20
|
+
s.extra_rdoc_files = ["README.mkd", "LICENSE"]
|
21
|
+
|
22
|
+
# Uncomment this to add a dependency
|
23
|
+
# s.add_dependency "foo"
|
24
|
+
|
25
|
+
s.require_path = 'lib'
|
26
|
+
s.autorequire = GEM
|
27
|
+
s.files = %w(LICENSE README.mkd Rakefile) + Dir.glob("{lib,spec}/**/*")
|
28
|
+
end
|
29
|
+
|
30
|
+
task :default => :spec
|
31
|
+
|
32
|
+
desc "Run specs"
|
33
|
+
Spec::Rake::SpecTask.new do |t|
|
34
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
35
|
+
t.spec_opts = %w(-fs --color)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
40
|
+
pkg.gem_spec = spec
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "install the gem locally"
|
44
|
+
task :install => [:package] do
|
45
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "create a gemspec file"
|
49
|
+
task :make_spec do
|
50
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
51
|
+
file.puts spec.to_ruby
|
52
|
+
end
|
53
|
+
end
|
data/lib/interval.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
class Array
|
2
|
+
def rand
|
3
|
+
self[super(self.length)]
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Interval
|
8
|
+
class Pitch
|
9
|
+
NOTE_NAMES = %w{c d e f g a b}
|
10
|
+
NOTE_NAMES_TO_I = {'c' => 0, 'd' => 1, 'e' => 2, 'f' => 3, 'g' => 4, 'a' => 5, 'b' => 6}
|
11
|
+
|
12
|
+
attr_accessor :octave
|
13
|
+
attr_accessor :notename_i
|
14
|
+
attr_accessor :accidental
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def from_string(str)
|
18
|
+
p = new
|
19
|
+
ary = str.split(//)
|
20
|
+
possible_notename = ary.shift.downcase
|
21
|
+
raise "invalid notename: #{possible_notename}" unless Pitch::NOTE_NAMES_TO_I.has_key?(possible_notename)
|
22
|
+
p.notename_i = note_s_to_i(possible_notename)
|
23
|
+
|
24
|
+
octave = 0
|
25
|
+
accidental = 0
|
26
|
+
while(char = ary.shift)
|
27
|
+
octave = octave + 1 if char == "'"
|
28
|
+
octave = octave - 1 if char == ","
|
29
|
+
accidental = accidental + 1 if char == "#"
|
30
|
+
accidental = accidental - 1 if char == "b"
|
31
|
+
end
|
32
|
+
p.octave = octave
|
33
|
+
p.accidental = accidental
|
34
|
+
p
|
35
|
+
end
|
36
|
+
|
37
|
+
def from_int(midiint)
|
38
|
+
p = new
|
39
|
+
p.octave = (midiint - 48) / 12
|
40
|
+
p.notename_i = {0 => 0, 1 => 0,
|
41
|
+
2 => 1, 3 => 1,
|
42
|
+
4 => 2,
|
43
|
+
5 => 3, 6 => 3,
|
44
|
+
7 => 4, 8 => 4,
|
45
|
+
9 => 5, 10 => 5,
|
46
|
+
11 => 6}[midiint % 12]
|
47
|
+
p.accidental = midiint - (p.octave + 4) * 12 - ([0, 2, 4, 5, 7, 9, 11][p.notename_i])
|
48
|
+
p
|
49
|
+
end
|
50
|
+
|
51
|
+
def notename_s(s)
|
52
|
+
NOTE_NAMES[s]
|
53
|
+
end
|
54
|
+
|
55
|
+
# if normal=true then only return notes you would "normally" see.
|
56
|
+
# what is the word for that, natural?
|
57
|
+
def random(normal=false)
|
58
|
+
p = new
|
59
|
+
p.notename_i = NOTE_NAMES_TO_I.values.rand
|
60
|
+
p.octave = 0
|
61
|
+
|
62
|
+
accidentals = []
|
63
|
+
if normal
|
64
|
+
accidentals = case p.notename
|
65
|
+
when 'c', 'f'
|
66
|
+
[0, 1]
|
67
|
+
when 'e', 'b'
|
68
|
+
[-1, 0]
|
69
|
+
else [-1, 0, 1]
|
70
|
+
end
|
71
|
+
else
|
72
|
+
accidentals = [-1, 0, 1]
|
73
|
+
end
|
74
|
+
p.accidental = accidentals.rand
|
75
|
+
p
|
76
|
+
end
|
77
|
+
|
78
|
+
# def notename_s(s)
|
79
|
+
# {0 => 'c', 1 => 'c',
|
80
|
+
# 2 => 'd', 3 => 'd',
|
81
|
+
# 4 => 'e',
|
82
|
+
# 5 => 'f', 6 => 'f',
|
83
|
+
# 7 => 'g', 8 => 'g',
|
84
|
+
# 9 => 'a', 10 => 'a',
|
85
|
+
# 11 => 'c'}[s]
|
86
|
+
# end
|
87
|
+
|
88
|
+
# i dont think this is right. also, just start storing notename as an integer. make it a lot easier
|
89
|
+
# this isn't notename_i!!! this is some strange mididint accidental array. todo, redo this
|
90
|
+
# def pitchy(i)
|
91
|
+
# notes =
|
92
|
+
# {'c' => 0,
|
93
|
+
# 'd' => 2,
|
94
|
+
# 'e' => 4,
|
95
|
+
# 'f' => 5,
|
96
|
+
# 'g' => 6,
|
97
|
+
# 'a' => 9,
|
98
|
+
# 'b' => 11}
|
99
|
+
# notes[i]
|
100
|
+
# end
|
101
|
+
|
102
|
+
# def note_i_to_s(i)
|
103
|
+
# NOTENAMES[i]
|
104
|
+
# end
|
105
|
+
|
106
|
+
def note_s_to_i(s)
|
107
|
+
NOTE_NAMES_TO_I[s]
|
108
|
+
end
|
109
|
+
|
110
|
+
def accidental_name(i)
|
111
|
+
names =
|
112
|
+
{ -2 => " double flat",
|
113
|
+
-1 => " flat",
|
114
|
+
0 => "",
|
115
|
+
1 => " sharp",
|
116
|
+
2 => " double sharp" }
|
117
|
+
names[i]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def semitone_pitch
|
122
|
+
[0, 2, 4, 5, 7, 9, 11][notename_i] + accidental + (octave * 12) + 48
|
123
|
+
end
|
124
|
+
|
125
|
+
# def notename_i
|
126
|
+
# self.class.notename_i(self.notename)
|
127
|
+
# end
|
128
|
+
|
129
|
+
# def notename_i=(i)
|
130
|
+
# end
|
131
|
+
|
132
|
+
def notename
|
133
|
+
self.class.notename_s(notename_i)
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_long_name
|
137
|
+
"%s%s" % [notename.upcase, self.class.accidental_name(accidental)]
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_short_name
|
141
|
+
"%s%s" % [notename, accidental > 0 ? ('#' * accidental.abs) : ('b' * accidental.abs)]
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
to_short_name
|
146
|
+
end
|
147
|
+
|
148
|
+
def +(other)
|
149
|
+
if other.kind_of?(Interval)
|
150
|
+
plus_interval(other)
|
151
|
+
else
|
152
|
+
raise "todo"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def -(other)
|
157
|
+
if other.kind_of?(Interval)
|
158
|
+
minus_interval(other)
|
159
|
+
else
|
160
|
+
raise "todo"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
def plus_interval(other)
|
166
|
+
r = self.dup
|
167
|
+
_p = r.semitone_pitch
|
168
|
+
r.notename_i = r.notename_i + (other.interval - 1) * other.direction
|
169
|
+
r.octave = r.octave + r.notename_i / 7 + other.octave * other.direction
|
170
|
+
r.notename_i = r.notename_i % 7
|
171
|
+
_diff = r.semitone_pitch - _p
|
172
|
+
r.accidental = r.accidental + (other.to_i - _diff)
|
173
|
+
r
|
174
|
+
end
|
175
|
+
def minus_interval(other)
|
176
|
+
other2 = other.dup
|
177
|
+
other2.direction = other2.direction * -1
|
178
|
+
plus_interval(other2)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class Interval
|
183
|
+
attr_accessor :direction # value to be 1 or -1
|
184
|
+
attr_accessor :octave # 0, 1, 2, 3 etc
|
185
|
+
attr_accessor :interval # 1:unison, 2:second, ... 7: seventh
|
186
|
+
# unison: dim perfect aug
|
187
|
+
# second: -2:dim -1:minor 0:major 1:aug
|
188
|
+
# third: dim minor major aug
|
189
|
+
# fourth: dim perfect aug
|
190
|
+
# fifth: dim perfect aug
|
191
|
+
# sixth: dim minor major aug
|
192
|
+
# seventh: dim minor major aug
|
193
|
+
attr_accessor :mod # -2:dim -1:minor 0:major 1:aug
|
194
|
+
|
195
|
+
QUALITY_LONG_NAMES = {
|
196
|
+
'p' => 'Perfect',
|
197
|
+
'dd' => 'Doubly-diminished',
|
198
|
+
'd' => 'Diminished',
|
199
|
+
'm' => 'Minor',
|
200
|
+
'M' => 'Major',
|
201
|
+
'a' => 'Augmented',
|
202
|
+
'aa' => 'Doubly-augmented',
|
203
|
+
}
|
204
|
+
|
205
|
+
SIZE_LONG_NAMES = %w{x
|
206
|
+
Unison
|
207
|
+
Second
|
208
|
+
Third
|
209
|
+
Fourth
|
210
|
+
Fifth
|
211
|
+
Sixth
|
212
|
+
Seventh
|
213
|
+
Octave
|
214
|
+
}
|
215
|
+
|
216
|
+
class << self
|
217
|
+
# Number of
|
218
|
+
# semitones name enharmonic notes
|
219
|
+
# 0 Perfect Unison (P1) Diminished second (dim2)
|
220
|
+
# 1 Minor second (m2) Augmented unison (aug1)
|
221
|
+
# 2 Major second (M2) Diminished third (dim3)
|
222
|
+
# 3 Minor third (m3) Augmented second (aug2)
|
223
|
+
# 4 Major third (M3) Diminished fourth (dim4)
|
224
|
+
# 5 Perfect fourth (P4) Augmented third (aug3)
|
225
|
+
# 6 Tritone Augmented fourth (aug4) Diminished fifth (dim5)
|
226
|
+
# 7 Perfect fifth (P5) Diminished sixth (dim6)
|
227
|
+
# 8 Minor sixth (m6) Augmented fifth (aug5)
|
228
|
+
# 9 Major sixth (M6) Diminished seventh (dim7)
|
229
|
+
# 10 Minor seventh (m7) Augmented sixth (aug6)
|
230
|
+
# 11 Major seventh (M7) Diminished octave (dim8)
|
231
|
+
# 12 Perfect octave (P8) Augmented seventh (aug7)
|
232
|
+
|
233
|
+
# unison p1
|
234
|
+
# second m2 M2
|
235
|
+
# third m3 M3
|
236
|
+
# fourth p4
|
237
|
+
# fifth p5
|
238
|
+
# sixth m6 M6
|
239
|
+
# seventh m7 M7
|
240
|
+
# octave p8
|
241
|
+
def from_string(str)
|
242
|
+
i = new
|
243
|
+
i.direction = str[0,1] == "-" ? -1 : 1
|
244
|
+
str =~ /([mMdap])(\d+)/
|
245
|
+
modifier, size = $1,$2
|
246
|
+
raise "#{str} is not a valid interval" unless modifier && size
|
247
|
+
size = size.to_i
|
248
|
+
if size <= 7
|
249
|
+
i.octave = 0
|
250
|
+
else
|
251
|
+
i.octave = size / 8 # ?
|
252
|
+
end
|
253
|
+
i.interval = size - (i.octave * 7)
|
254
|
+
|
255
|
+
i.mod = case i.interval
|
256
|
+
when 2, 3, 6, 7
|
257
|
+
{"d" => -2, "m" => -1, "M" => 0, "a" => 1}[modifier]
|
258
|
+
when 1, 4, 5, 8
|
259
|
+
{"d" => -1, "p" => 0, "a" => 1}[modifier]
|
260
|
+
else
|
261
|
+
raise "unknown interval"
|
262
|
+
end
|
263
|
+
|
264
|
+
i
|
265
|
+
end
|
266
|
+
|
267
|
+
def from_int(number)
|
268
|
+
interval = new
|
269
|
+
interval.direction = number >= 0 ? 1 : -1
|
270
|
+
interval.octave = number.abs / 12
|
271
|
+
mod, intervali = *([
|
272
|
+
[0, 1], # unison
|
273
|
+
[-1, 2], [0, 2], # second
|
274
|
+
[-1, 3], [0, 3], # third
|
275
|
+
[0, 4], # fourth
|
276
|
+
[-1, 5], # dim 5, tritone
|
277
|
+
[0, 5], # fifth
|
278
|
+
[-1, 6], [0, 6], # sixth
|
279
|
+
[-1, 7], [0, 7]])[number.abs % 12] # seventh
|
280
|
+
interval.mod = mod
|
281
|
+
interval.interval = intervali
|
282
|
+
interval
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
def to_long_name
|
289
|
+
short_modifier = case self.interval
|
290
|
+
when 2, 3, 6, 7
|
291
|
+
{-2 => "d", -1 => "m", 0 => "M", 1 => "a"}[self.mod]
|
292
|
+
when 1, 4, 5, 8
|
293
|
+
{-1 => "d", 0 => "p", 1 => "a"}[self.mod]
|
294
|
+
else
|
295
|
+
raise "unknown interval"
|
296
|
+
end
|
297
|
+
|
298
|
+
long_modifier = QUALITY_LONG_NAMES[short_modifier]
|
299
|
+
size = SIZE_LONG_NAMES[self.interval]
|
300
|
+
size = "Octave" if interval == 1 && octave > 0
|
301
|
+
|
302
|
+
# tritone (diminished fifth) has the following: todo
|
303
|
+
#<Interval::Interval:0x2efa90 @mod=0, @interval=5, @octave=0, @direction=1>
|
304
|
+
|
305
|
+
return "%s %s" % [long_modifier, size]
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
def to_s
|
310
|
+
to_long_name
|
311
|
+
end
|
312
|
+
|
313
|
+
def to_i
|
314
|
+
return ([0, 0, 2, 4, 5, 7, 9, 11][interval] + octave * 12 + mod) * direction
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
2
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
3
|
+
require "interval"
|
4
|
+
require "pp"
|
5
|
+
|
6
|
+
include Interval
|
7
|
+
|
8
|
+
describe "interval" do
|
9
|
+
it "should create from int" do
|
10
|
+
test_set = {
|
11
|
+
0 => "Perfect Unison",
|
12
|
+
1 => "Minor second",
|
13
|
+
2 => "Major second",
|
14
|
+
3 => "Minor third",
|
15
|
+
4 => "Major third",
|
16
|
+
5 => "Perfect fourth",
|
17
|
+
6 => "Diminished fifth",
|
18
|
+
7 => "Perfect fifth",
|
19
|
+
8 => "Minor sixth",
|
20
|
+
9 => "Major sixth",
|
21
|
+
10 => "Minor seventh",
|
22
|
+
11 => "Major seventh",
|
23
|
+
12 => "Perfect octave"}
|
24
|
+
|
25
|
+
test_set.keys.sort.each do |k|
|
26
|
+
value = test_set[k]
|
27
|
+
v = Interval::Interval.from_int(k)
|
28
|
+
v.to_long_name.downcase.should == value.downcase
|
29
|
+
v.to_i.should == k
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should create from string" do
|
34
|
+
test_set = [
|
35
|
+
["p1", "Perfect Unison"],
|
36
|
+
["m2", "Minor second"],
|
37
|
+
["M2", "Major second"],
|
38
|
+
["m3", "Minor third"],
|
39
|
+
["M3", "Major third"],
|
40
|
+
["p4", "Perfect fourth"],
|
41
|
+
["d5", "Diminished fifth"],
|
42
|
+
["p5", "Perfect fifth"],
|
43
|
+
["m6", "Minor sixth"],
|
44
|
+
["M6", "Major sixth"],
|
45
|
+
["m7", "Minor seventh"],
|
46
|
+
["M7", "Major seventh"],
|
47
|
+
["p8", "Perfect octave"]]
|
48
|
+
test_set.each do |key,value|
|
49
|
+
v = Interval::Interval.from_string(key)
|
50
|
+
v.to_long_name.downcase.should == value.downcase
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "pitch" do
|
56
|
+
it "should create from string and integer" do
|
57
|
+
|
58
|
+
p = Interval::Pitch.from_string("c")
|
59
|
+
p.notename_i.should == 0
|
60
|
+
p.notename.should == "c"
|
61
|
+
p.accidental.should == 0
|
62
|
+
p.octave.should == 0
|
63
|
+
|
64
|
+
p2 = Interval::Pitch.from_int(p.semitone_pitch)
|
65
|
+
p2.notename.should == "c"
|
66
|
+
p2.accidental.should == 0
|
67
|
+
p2.octave.should == 0
|
68
|
+
|
69
|
+
p2.to_long_name.should == "C"
|
70
|
+
p2.to_short_name.should == "c"
|
71
|
+
|
72
|
+
p.semitone_pitch.should == p2.semitone_pitch
|
73
|
+
|
74
|
+
p = Interval::Pitch.from_string("c#'")
|
75
|
+
p.notename.should == "c"
|
76
|
+
p.accidental.should == 1
|
77
|
+
p.octave.should == 1
|
78
|
+
|
79
|
+
p2 = Interval::Pitch.from_int(p.semitone_pitch)
|
80
|
+
p2.notename.should == "c"
|
81
|
+
p2.accidental.should == 1
|
82
|
+
p2.octave.should == 1
|
83
|
+
|
84
|
+
p2.to_long_name.should == "C sharp"
|
85
|
+
p2.to_short_name.should == "c#"
|
86
|
+
|
87
|
+
p.semitone_pitch.should == p2.semitone_pitch
|
88
|
+
|
89
|
+
p = Interval::Pitch.from_string("eb,,")
|
90
|
+
p.notename.should == "e"
|
91
|
+
p.accidental.should == -1
|
92
|
+
p.octave.should == -2
|
93
|
+
|
94
|
+
p2 = Interval::Pitch.from_int(p.semitone_pitch)
|
95
|
+
|
96
|
+
p2.notename.should == "d"
|
97
|
+
p2.accidental.should == 1
|
98
|
+
p2.octave.should == -2
|
99
|
+
|
100
|
+
p2.to_long_name.should == "D sharp"
|
101
|
+
p2.to_short_name.should == "d#"
|
102
|
+
|
103
|
+
p.semitone_pitch.should == p2.semitone_pitch
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "adding intervals" do
|
107
|
+
|
108
|
+
it "should be able to be added to an interval" do
|
109
|
+
p = Pitch.from_string("c")
|
110
|
+
i = Interval::Interval.from_string("M3")
|
111
|
+
p2 = p + i
|
112
|
+
p2.to_short_name.should == "e"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should add minor seconds" do
|
116
|
+
i = Interval::Interval.from_string("m2")
|
117
|
+
[["c", "db"],
|
118
|
+
["f", "gb"],
|
119
|
+
["Bb", "cb"],
|
120
|
+
["eb", "fb"],
|
121
|
+
["ab", "bbb"],
|
122
|
+
["db", "ebb"],
|
123
|
+
["gb", "abb"],
|
124
|
+
["f#", "g"],
|
125
|
+
["b", "c"],
|
126
|
+
["e", "f"],
|
127
|
+
["a", "bb"],
|
128
|
+
["d", "eb"],
|
129
|
+
["g", "ab"]
|
130
|
+
].each do |pitch, new_pitch|
|
131
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should add major seconds" do
|
136
|
+
i = Interval::Interval.from_string("M2")
|
137
|
+
[["c", "d"],
|
138
|
+
["f", "g"],
|
139
|
+
["Bb", "c"],
|
140
|
+
["eb", "f"],
|
141
|
+
["ab", "bb"],
|
142
|
+
["db", "eb"],
|
143
|
+
["gb", "ab"],
|
144
|
+
["f#", "g#"],
|
145
|
+
["b", "c#"],
|
146
|
+
["e", "f#"],
|
147
|
+
["a", "b"],
|
148
|
+
["d", "e"],
|
149
|
+
["g", "a"]
|
150
|
+
].each do |pitch, new_pitch|
|
151
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should add minor thirds" do
|
156
|
+
i = Interval::Interval.from_string("m3")
|
157
|
+
[["c", "eb"],
|
158
|
+
["f", "ab"],
|
159
|
+
["Bb", "db"],
|
160
|
+
["eb", "gb"],
|
161
|
+
["ab", "cb"],
|
162
|
+
["db", "fb"],
|
163
|
+
["gb", "bbb"],
|
164
|
+
["f#", "a"],
|
165
|
+
["b", "d"],
|
166
|
+
["e", "g"],
|
167
|
+
["a", "c"],
|
168
|
+
["d", "f"],
|
169
|
+
["g", "bb"]
|
170
|
+
].each do |pitch, new_pitch|
|
171
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should add major thirds" do
|
176
|
+
i = Interval::Interval.from_string("M3")
|
177
|
+
[["c", "e"],
|
178
|
+
["f", "a"],
|
179
|
+
["Bb", "d"],
|
180
|
+
["eb", "g"],
|
181
|
+
["ab", "c"],
|
182
|
+
["db", "f"],
|
183
|
+
["gb", "bb"],
|
184
|
+
["f#", "a#"],
|
185
|
+
["b", "d#"],
|
186
|
+
["e", "g#"],
|
187
|
+
["a", "c#"],
|
188
|
+
["d", "f#"],
|
189
|
+
["g", "b"]
|
190
|
+
].each do |pitch, new_pitch|
|
191
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should add perfect fourths" do
|
196
|
+
i = Interval::Interval.from_string("p4")
|
197
|
+
[["c", "f"],
|
198
|
+
["f", "bb"],
|
199
|
+
["Bb", "eb"],
|
200
|
+
["eb", "ab"],
|
201
|
+
["ab", "db"],
|
202
|
+
["db", "gb"],
|
203
|
+
["gb", "cb"],
|
204
|
+
["f#", "b"],
|
205
|
+
["b", "e"],
|
206
|
+
["e", "a"],
|
207
|
+
["a", "d"],
|
208
|
+
["d", "g"],
|
209
|
+
["g", "c"]
|
210
|
+
].each do |pitch, new_pitch|
|
211
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should add diminished fifths (tritone)" do
|
216
|
+
i = Interval::Interval.from_string("d5")
|
217
|
+
[["c", "gb"],
|
218
|
+
["f", "cb"],
|
219
|
+
["Bb", "fb"],
|
220
|
+
["eb", "bbb"],
|
221
|
+
["ab", "ebb"],
|
222
|
+
["db", "abb"],
|
223
|
+
["gb", "dbb"],
|
224
|
+
["f#", "c"],
|
225
|
+
["b", "f"],
|
226
|
+
["e", "bb"],
|
227
|
+
["a", "eb"],
|
228
|
+
["d", "ab"],
|
229
|
+
["g", "db"]
|
230
|
+
].each do |pitch, new_pitch|
|
231
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should add perfect fifths" do
|
236
|
+
i = Interval::Interval.from_string("p5")
|
237
|
+
[["c", "g"],
|
238
|
+
["f", "c"],
|
239
|
+
["Bb", "f"],
|
240
|
+
["eb", "bb"],
|
241
|
+
["ab", "eb"],
|
242
|
+
["db", "ab"],
|
243
|
+
["gb", "db"],
|
244
|
+
["f#", "c#"],
|
245
|
+
["b", "f#"],
|
246
|
+
["e", "b"],
|
247
|
+
["a", "e"],
|
248
|
+
["d", "a"],
|
249
|
+
["g", "d"]
|
250
|
+
].each do |pitch, new_pitch|
|
251
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should add minor sixths" do
|
256
|
+
i = Interval::Interval.from_string("m6")
|
257
|
+
[["c", "ab"],
|
258
|
+
["f", "db"],
|
259
|
+
["Bb", "gb"],
|
260
|
+
["eb", "cb"],
|
261
|
+
["ab", "fb"],
|
262
|
+
["db", "bbb"],
|
263
|
+
["gb", "ebb"],
|
264
|
+
["f#", "d"],
|
265
|
+
["b", "g"],
|
266
|
+
["e", "c"],
|
267
|
+
["a", "f"],
|
268
|
+
["d", "bb"],
|
269
|
+
["g", "eb"]
|
270
|
+
].each do |pitch, new_pitch|
|
271
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should add major sixths" do
|
276
|
+
i = Interval::Interval.from_string("M6")
|
277
|
+
[["c", "a"],
|
278
|
+
["f", "d"],
|
279
|
+
["Bb", "g"],
|
280
|
+
["eb", "c"],
|
281
|
+
["ab", "f"],
|
282
|
+
["db", "bb"],
|
283
|
+
["gb", "eb"],
|
284
|
+
["f#", "d#"],
|
285
|
+
["b", "g#"],
|
286
|
+
["e", "c#"],
|
287
|
+
["a", "f#"],
|
288
|
+
["d", "b"],
|
289
|
+
["g", "e"]
|
290
|
+
].each do |pitch, new_pitch|
|
291
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should add minor sevenths" do
|
296
|
+
i = Interval::Interval.from_string("m7")
|
297
|
+
[["c", "bb"],
|
298
|
+
["f", "eb"],
|
299
|
+
["Bb", "ab"],
|
300
|
+
["eb", "db"],
|
301
|
+
["ab", "gb"],
|
302
|
+
["db", "cb"],
|
303
|
+
["gb", "fb"],
|
304
|
+
["f#", "e"],
|
305
|
+
["b", "a"],
|
306
|
+
["e", "d"],
|
307
|
+
["a", "g"],
|
308
|
+
["d", "c"],
|
309
|
+
["g", "f"]
|
310
|
+
].each do |pitch, new_pitch|
|
311
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
it "should add major sevenths" do
|
316
|
+
i = Interval::Interval.from_string("M7")
|
317
|
+
[["c", "b"],
|
318
|
+
["f", "e"],
|
319
|
+
["Bb", "a"],
|
320
|
+
["eb", "d"],
|
321
|
+
["ab", "g"],
|
322
|
+
["db", "c"],
|
323
|
+
["gb", "f"],
|
324
|
+
["f#", "e#"],
|
325
|
+
["b", "a#"],
|
326
|
+
["e", "d#"],
|
327
|
+
["a", "g#"],
|
328
|
+
["d", "c#"],
|
329
|
+
["g", "f#"]
|
330
|
+
].each do |pitch, new_pitch|
|
331
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
it "should add perfect octaves" do
|
336
|
+
i = Interval::Interval.from_string("p8")
|
337
|
+
[["c", "c"],
|
338
|
+
["f", "f"],
|
339
|
+
["Bb", "bb"],
|
340
|
+
["eb", "eb"],
|
341
|
+
["ab", "ab"],
|
342
|
+
["db", "db"],
|
343
|
+
["gb", "gb"],
|
344
|
+
["f#", "f#"],
|
345
|
+
["b", "b"],
|
346
|
+
["e", "e"],
|
347
|
+
["a", "a"],
|
348
|
+
["d", "d"],
|
349
|
+
["g", "g"]
|
350
|
+
].each do |pitch, new_pitch|
|
351
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe "adding enharmonic intervals" do
|
356
|
+
it "should add diminished seconds" do
|
357
|
+
i = Interval::Interval.from_string("d2")
|
358
|
+
[["c", "dbb"],
|
359
|
+
["f", "gbb"],
|
360
|
+
["Bb", "cbb"],
|
361
|
+
["eb", "fbb"],
|
362
|
+
["ab", "bbbb"],
|
363
|
+
["db", "ebbb"],
|
364
|
+
["gb", "abbb"],
|
365
|
+
["f#", "gb"],
|
366
|
+
["b", "cb"],
|
367
|
+
["e", "fb"],
|
368
|
+
["a", "bbb"],
|
369
|
+
["d", "ebb"],
|
370
|
+
["g", "abb"]
|
371
|
+
].each do |pitch, new_pitch|
|
372
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe "finding intervals below" do
|
378
|
+
it "should subtract major thirds" do
|
379
|
+
i = Interval::Interval.from_string("-M3")
|
380
|
+
[["c", "ab"],
|
381
|
+
["f", "db"],
|
382
|
+
["Bb", "gb"],
|
383
|
+
["eb", "cb"],
|
384
|
+
["ab", "fb"],
|
385
|
+
["db", "bbb"],
|
386
|
+
["gb", "ebb"],
|
387
|
+
["f#", "d"],
|
388
|
+
["b", "g"],
|
389
|
+
["e", "c"],
|
390
|
+
["a", "f"],
|
391
|
+
["d", "bb"],
|
392
|
+
["g", "eb"]
|
393
|
+
].each do |pitch, new_pitch|
|
394
|
+
(Pitch.from_string(pitch) + i).to_short_name.should == new_pitch
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
|
400
|
+
|
401
|
+
end
|
402
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: interval
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nate Murray
|
8
|
+
autorequire: interval
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-17 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: interval is a tiny library that provides simple musicial note pitch and interval arithmetic.
|
17
|
+
email: nate@natemurray.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.mkd
|
24
|
+
- LICENSE
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README.mkd
|
28
|
+
- Rakefile
|
29
|
+
- lib/interval.rb
|
30
|
+
- spec/interval_spec.rb
|
31
|
+
- spec/spec_helper.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://www.xcombinator.com
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.3.2
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: interval is a tiny library that provides simple musicial note pitch and interval arithmetic.
|
60
|
+
test_files: []
|
61
|
+
|