rb-music 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rb-music/constants.rb +105 -0
- data/lib/rb-music/interval.rb +50 -0
- data/lib/rb-music/note.rb +107 -0
- data/lib/rb-music/note_set.rb +61 -0
- data/lib/rb-music/scale.rb +30 -0
- data/lib/rb-music/version.rb +3 -0
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64717c99b5fbd105422fc99c79fd9d137abd80b0
|
4
|
+
data.tar.gz: b61cfe61e4b3c562480d2f5b201d4d0636af8e69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdc92e5119be32c2a4ac88869d8a48903e50992458035265096ef4efaef1a0a6f4a8f79c89237dc2ef6458998a9d689bb4d6c7feb30768d3394c5871709b6cbf
|
7
|
+
data.tar.gz: ba46377b0db3961a66a2ae7a5c0f82da8e8c3e8fa11e389204de82d87e165888a2370ee6920ffd8d0bafea289471dff5fcdb7f314fc80271d38dc0c83e36b716
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module RBMusic
|
2
|
+
Error = Class.new(StandardError)
|
3
|
+
ArgumentError = Class.new(ArgumentError)
|
4
|
+
|
5
|
+
NOTE_NAMES = ["F", "C", "G", "D", "A", "E", "B"]
|
6
|
+
ACCIDENTALS = ["bb", "b", "", "#", "x"]
|
7
|
+
|
8
|
+
# notes - two dimensional [octave, fifth] - relative to the 'main' note
|
9
|
+
NOTES = {
|
10
|
+
"Fbb" => [10, -17],
|
11
|
+
"Cbb" => [10, -16],
|
12
|
+
"Gbb" => [9, -15],
|
13
|
+
"Dbb" => [8, -14],
|
14
|
+
"Abb" => [8, -13],
|
15
|
+
"Ebb" => [7, -12],
|
16
|
+
"Bbb" => [7, -11],
|
17
|
+
|
18
|
+
"Fb" => [6, -10],
|
19
|
+
"Cb" => [5, -9],
|
20
|
+
"Gb" => [5, -8],
|
21
|
+
"Db" => [4, -7],
|
22
|
+
"Ab" => [4, -6],
|
23
|
+
"Eb" => [3, -5],
|
24
|
+
"Bb" => [3, -4],
|
25
|
+
|
26
|
+
"F" => [2, -3],
|
27
|
+
"C" => [1, -2],
|
28
|
+
"G" => [1, -1],
|
29
|
+
"D" => [0, 0],
|
30
|
+
"A" => [0, 1],
|
31
|
+
"E" => [-1, 2],
|
32
|
+
"B" => [-1, 3],
|
33
|
+
|
34
|
+
"F#" => [-2, 4],
|
35
|
+
"C#" => [-3, 5],
|
36
|
+
"G#" => [-3, 6],
|
37
|
+
"D#" => [-4, 7],
|
38
|
+
"A#" => [-4, 8],
|
39
|
+
"E#" => [-5, 9],
|
40
|
+
"B#" => [-5, 10],
|
41
|
+
|
42
|
+
"Fx" => [-6, 11],
|
43
|
+
"Cx" => [-7, 12],
|
44
|
+
"Gx" => [-7, 13],
|
45
|
+
"Dx" => [-8, 14],
|
46
|
+
"Ax" => [-8, 15],
|
47
|
+
"Ex" => [-9, 16],
|
48
|
+
"Bx" => [-10, 17]
|
49
|
+
}
|
50
|
+
|
51
|
+
BASE_FREQ = 440 # A4 'main' note
|
52
|
+
BASE_OFFSET = [4, 1] # offset of base note from D0
|
53
|
+
|
54
|
+
# intervals - two dimensional [octave, fifth] - relative to the 'main' note
|
55
|
+
INTERVALS = {
|
56
|
+
unison: [0, 0],
|
57
|
+
minor_second: [3, -5],
|
58
|
+
major_second: [-1, 2],
|
59
|
+
minor_third: [2, -3],
|
60
|
+
major_third: [-2, 4],
|
61
|
+
fourth: [1, -1],
|
62
|
+
augmented_fourth: [-3, 6],
|
63
|
+
tritone: [-3, 6],
|
64
|
+
diminished_fifth: [4, -6],
|
65
|
+
fifth: [0, 1],
|
66
|
+
minor_sixth: [3, -4],
|
67
|
+
major_sixth: [-1, 3],
|
68
|
+
minor_seventh: [2, -2],
|
69
|
+
major_seventh: [-2, 5],
|
70
|
+
octave: [1, 0]
|
71
|
+
}
|
72
|
+
|
73
|
+
INTERVALS_SEMITONES = {
|
74
|
+
0 => [0, 0],
|
75
|
+
1 => [3, -5],
|
76
|
+
2 => [-1, 2],
|
77
|
+
3 => [2, -3],
|
78
|
+
4 => [-2, 4],
|
79
|
+
5 => [1, -1],
|
80
|
+
6 => [-3, 6],
|
81
|
+
7 => [0, 1],
|
82
|
+
8 => [3, -4],
|
83
|
+
9 => [-1, 3],
|
84
|
+
10 => [2, -2],
|
85
|
+
11 => [-2, 5],
|
86
|
+
12 => [1, 0]
|
87
|
+
}
|
88
|
+
|
89
|
+
SCALES = {
|
90
|
+
major: [:major_second, :major_third, :fourth, :fifth, :major_sixth, :major_seventh],
|
91
|
+
natural_minor: [:major_second, :minor_third, :fourth, :fifth, :minor_sixth, :minor_seventh],
|
92
|
+
harmonic_minor: [:major_second, :minor_third, :fourth, :fifth, :minor_sixth, :major_seventh],
|
93
|
+
major_pentatonic: [:major_second, :major_third, :fifth, :major_sixth],
|
94
|
+
minor_pentatonic: [:minor_third, :fourth, :fifth, :minor_seventh],
|
95
|
+
blues: [:minor_third, :fourth, :augmented_fourth, :fifth, :minor_seventh],
|
96
|
+
dorian: [:major_second, :minor_third, :fourth, :fifth, :major_sixth, :minor_seventh],
|
97
|
+
phrygian: [:minor_second, :minor_third, :fourth, :fifth, :major_sixth, :minor_seventh],
|
98
|
+
lydian: [:major_second, :major_third, :augmented_fourth, :fifth, :major_sixth, :major_seventh],
|
99
|
+
mixolydian: [:major_second, :major_third, :fourth, :fifth, :major_sixth, :minor_seventh],
|
100
|
+
locrian: [:minor_second, :minor_third, :fourth, :diminished_fifth, :minor_sixth, :minor_seventh],
|
101
|
+
}
|
102
|
+
SCALES[:ionian] = SCALES[:major]
|
103
|
+
SCALES[:aeolian] = SCALES[:natural_minor]
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RBMusic
|
2
|
+
|
3
|
+
class Interval
|
4
|
+
attr_accessor :coord
|
5
|
+
|
6
|
+
def initialize(coord)
|
7
|
+
self.coord = coord
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_name(name)
|
11
|
+
Interval.new(INTERVALS[name.to_sym])
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_semitones(num)
|
15
|
+
Interval.new(INTERVALS_SEMITONES[num])
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_tones_semitones(tone_semitone)
|
19
|
+
# multiply [tones, semitones] vector with [-1 2;3 -5] to get coordinate from tones and semitones
|
20
|
+
Interval.new([tone_semitone[0] * -1 + tone_semitone[1] * 3, tone_semitone[0] * 2 + tone_semitone[1] * -5])
|
21
|
+
end
|
22
|
+
|
23
|
+
def tone_semitone
|
24
|
+
# multiply coord vector with [5 2;3 1] to get coordinate in tones and semitones
|
25
|
+
# [5 2;3 1] is the inverse of [-1 2;3 -5], which is the coordinates of [tone; semitone]
|
26
|
+
@tone_semitone ||= [coord[0] * 5 + coord[1] * 3, coord[0] * 2 + coord[1] * 1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def semitone
|
30
|
+
# number of semitones of interval = tones * 2 + semitones
|
31
|
+
tone_semitone[0] * 2 + tone_semitone[1]
|
32
|
+
end
|
33
|
+
|
34
|
+
def add(interval)
|
35
|
+
if interval.is_a?(String)
|
36
|
+
interval = Interval.from_name(interval)
|
37
|
+
end
|
38
|
+
Interval.new([coord[0] + interval.coord[0], coord[1] + interval.coord[1]])
|
39
|
+
end
|
40
|
+
|
41
|
+
def subtract(interval)
|
42
|
+
if interval.is_a?(String)
|
43
|
+
interval = Interval.from_name(interval)
|
44
|
+
end
|
45
|
+
Interval.new([coord[0] - interval.coord[0], coord[1] - interval.coord[1]])
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module RBMusic
|
2
|
+
|
3
|
+
class Note
|
4
|
+
attr_accessor :coord
|
5
|
+
|
6
|
+
def initialize(coord)
|
7
|
+
self.coord = coord
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_latin(name)
|
11
|
+
raise ArgumentError unless name.is_a?(String)
|
12
|
+
|
13
|
+
note_parts = name.split(/(\d+)/)
|
14
|
+
note_name = note_parts.first
|
15
|
+
octave = note_parts.last.to_i
|
16
|
+
|
17
|
+
unless NOTES.has_key?(note_name) && note_parts.size < 3
|
18
|
+
raise ArgumentError
|
19
|
+
end
|
20
|
+
|
21
|
+
coordinate = [NOTES[note_name][0] + octave, NOTES[note_name][1]]
|
22
|
+
|
23
|
+
coordinate[0] -= BASE_OFFSET[0]
|
24
|
+
coordinate[1] -= BASE_OFFSET[1]
|
25
|
+
|
26
|
+
Note.new(coordinate)
|
27
|
+
end
|
28
|
+
|
29
|
+
def frequency
|
30
|
+
BASE_FREQ * (2.0 ** ((coord[0] * 1200 + coord[1] * 700.0) / 1200.0))
|
31
|
+
end
|
32
|
+
|
33
|
+
def accidental
|
34
|
+
@accidental ||= ((coord[1] + BASE_OFFSET[1]) / 7.0).round
|
35
|
+
end
|
36
|
+
|
37
|
+
def octave
|
38
|
+
# calculate octave of base note without accidentals
|
39
|
+
@octave ||= coord[0] + BASE_OFFSET[0] + 4 * accidental + ((coord[1] + BASE_OFFSET[1] - 7 * accidental) / 2).floor
|
40
|
+
end
|
41
|
+
|
42
|
+
def latin
|
43
|
+
return @latin if @latin
|
44
|
+
accidentalName = ACCIDENTALS[accidental + 2]
|
45
|
+
@latin ||= base_note_name + accidentalName
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
other.is_a?(Note) && other.latin == latin && other.octave == octave
|
50
|
+
end
|
51
|
+
|
52
|
+
def enharmonic?(other)
|
53
|
+
raise ArgumentError unless other.is_a?(Note)
|
54
|
+
|
55
|
+
other.frequency == frequency
|
56
|
+
end
|
57
|
+
alias_method :enharmonically_equivalent_to?, :enharmonic?
|
58
|
+
|
59
|
+
def midi_note_number
|
60
|
+
# see http://www.phys.unsw.edu.au/jw/notes.html
|
61
|
+
12 * Math.log2(frequency / 440) + 69
|
62
|
+
end
|
63
|
+
|
64
|
+
def scale(name, octaves = 1)
|
65
|
+
NoteSet.from_scale(Scale.new(latin, name), octave, octaves)
|
66
|
+
end
|
67
|
+
|
68
|
+
def add(that)
|
69
|
+
# if input is an array return an array
|
70
|
+
if that.is_a?(Array)
|
71
|
+
notes = that.map { |thing| add(thing) }
|
72
|
+
return NoteSet.new(notes)
|
73
|
+
end
|
74
|
+
|
75
|
+
# if input is string/symbol try to parse it as interval
|
76
|
+
if that.is_a?(String) || that.is_a?(Symbol)
|
77
|
+
that = Interval.from_name(that)
|
78
|
+
end
|
79
|
+
|
80
|
+
Note.new([coord[0] + that.coord[0], coord[1] + that.coord[1]])
|
81
|
+
end
|
82
|
+
|
83
|
+
def subtract(that)
|
84
|
+
if that.is_a?(Array)
|
85
|
+
notes = that.map { |thing| subtract(thing) }
|
86
|
+
return NoteSet.new(notes)
|
87
|
+
end
|
88
|
+
|
89
|
+
# if input is string try to parse it as interval
|
90
|
+
if that.is_a?(String) || that.is_a?(Symbol)
|
91
|
+
that = Interval.from_name(that)
|
92
|
+
end
|
93
|
+
|
94
|
+
coordinate = [coord[0] - that.coord[0], coord[1] - that.coord[1]]
|
95
|
+
|
96
|
+
# if input is another note return the difference as an Interval
|
97
|
+
that.is_a?(Note) ? Interval.new(coordinate) : Note.new(coordinate)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def base_note_name
|
103
|
+
@base_note_name ||= NOTE_NAMES[coord[1] + BASE_OFFSET[1] - accidental * 7 + 3]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RBMusic
|
2
|
+
|
3
|
+
class NoteSet
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :notes
|
7
|
+
|
8
|
+
def initialize(notes = [])
|
9
|
+
@notes = notes
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_scale(scale, octave=0, octaves=1)
|
13
|
+
raise ArgumentError unless scale.is_a?(Scale) && octaves > 0
|
14
|
+
|
15
|
+
root_note = Note.from_latin("#{scale.key}#{octave}")
|
16
|
+
notes = []
|
17
|
+
octaves.times do |i|
|
18
|
+
notes += scale.degrees.map do |interval_name|
|
19
|
+
note = root_note.add(interval_name)
|
20
|
+
i.times do |octave_offset|
|
21
|
+
note = note.add(:octave)
|
22
|
+
end
|
23
|
+
note
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
self.new(notes)
|
28
|
+
end
|
29
|
+
|
30
|
+
def each(&block)
|
31
|
+
@notes.each(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](index)
|
35
|
+
@notes[index]
|
36
|
+
end
|
37
|
+
|
38
|
+
def <<(other)
|
39
|
+
@notes << other
|
40
|
+
end
|
41
|
+
|
42
|
+
def map(&block)
|
43
|
+
@notes.map(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
@notes == other.notes
|
48
|
+
end
|
49
|
+
alias_method :eql?, :==
|
50
|
+
|
51
|
+
def add(that)
|
52
|
+
NoteSet.new(@notes.map { |note| note.add(that) })
|
53
|
+
end
|
54
|
+
|
55
|
+
def subtract(that)
|
56
|
+
NoteSet.new(@notes.map { |note| note.subtract(that) })
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RBMusic
|
2
|
+
|
3
|
+
class Scale
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :degrees
|
6
|
+
|
7
|
+
def initialize(key, name)
|
8
|
+
@scale_name = name.to_sym
|
9
|
+
raise ArgumentError unless NOTES.has_key?(key)
|
10
|
+
raise ArgumentError unless SCALES.has_key?(@scale_name)
|
11
|
+
@key = key
|
12
|
+
@degrees = [:unison] + SCALES[@scale_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def degree_count
|
16
|
+
@degree_count ||= @degrees.size
|
17
|
+
end
|
18
|
+
alias_method :size, :degree_count
|
19
|
+
|
20
|
+
def name
|
21
|
+
@name ||= "#{key} #{human_scale_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def human_scale_name
|
26
|
+
@scale_name.to_s.split("_").map { |word| word.capitalize }.join(" ")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rb-music
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Wise
|
@@ -102,6 +102,12 @@ files:
|
|
102
102
|
- LICENSE
|
103
103
|
- README.md
|
104
104
|
- lib/rb-music.rb
|
105
|
+
- lib/rb-music/constants.rb
|
106
|
+
- lib/rb-music/interval.rb
|
107
|
+
- lib/rb-music/note.rb
|
108
|
+
- lib/rb-music/note_set.rb
|
109
|
+
- lib/rb-music/scale.rb
|
110
|
+
- lib/rb-music/version.rb
|
105
111
|
- spec/rb-music/constants_spec.rb
|
106
112
|
- spec/rb-music/interval_spec.rb
|
107
113
|
- spec/rb-music/note_set_spec.rb
|