motion-music 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +8 -0
- data/Guardfile +6 -0
- data/LICENSE +23 -0
- data/README.md +53 -0
- data/doc/Gemfile.html +111 -0
- data/doc/Gemfile_lock.html +168 -0
- data/doc/Guardfile.html +111 -0
- data/doc/LICENSE.html +124 -0
- data/doc/Object.html +116 -0
- data/doc/RBMusic.html +164 -0
- data/doc/RBMusic/Interval.html +440 -0
- data/doc/RBMusic/Note.html +620 -0
- data/doc/RBMusic/NoteSet.html +277 -0
- data/doc/RBMusic/Scale.html +274 -0
- data/doc/README_md.html +163 -0
- data/doc/created.rid +8 -0
- data/doc/fonts.css +167 -0
- data/doc/fonts/Lato-Light.ttf +0 -0
- data/doc/fonts/Lato-LightItalic.ttf +0 -0
- data/doc/fonts/Lato-Regular.ttf +0 -0
- data/doc/fonts/Lato-RegularItalic.ttf +0 -0
- data/doc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/doc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/doc/images/add.png +0 -0
- data/doc/images/arrow_up.png +0 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/delete.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_blue.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/transparent.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +93 -0
- data/doc/js/darkfish.js +140 -0
- data/doc/js/jquery.js +18 -0
- data/doc/js/navigation.js +142 -0
- data/doc/js/search.js +109 -0
- data/doc/js/search_index.js +1 -0
- data/doc/js/searcher.js +228 -0
- data/doc/projections_json.html +115 -0
- data/doc/rb-music_gemspec.html +132 -0
- data/doc/rdoc.css +580 -0
- data/doc/table_of_contents.html +192 -0
- data/lib/motion-music.rb +9 -0
- data/lib/motion-music/version.rb +3 -0
- data/lib/rb-music.rb +8 -0
- 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
- data/motion-music.gemspec +20 -0
- data/projections.json +12 -0
- data/rb-music.gemspec +29 -0
- data/spec/rb-music/constants_spec.rb +27 -0
- data/spec/rb-music/interval_spec.rb +90 -0
- data/spec/rb-music/note_set_spec.rb +191 -0
- data/spec/rb-music/note_spec.rb +318 -0
- data/spec/rb-music/scale_spec.rb +88 -0
- data/spec/spec_helper.rb +14 -0
- metadata +124 -0
@@ -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
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "motion-music/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "motion-music"
|
6
|
+
s.version = MotionMusic::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.license = "MIT"
|
9
|
+
s.summary = "Music theory library for RubyMotion"
|
10
|
+
s.description = "This gem wraps the rb-music gem to provide RubyMotion classes for working with musical notes, scales and intervals."
|
11
|
+
s.author = "Mark Wise"
|
12
|
+
s.authors = ["Mark Wise"]
|
13
|
+
s.email = ["markmediadude@mgail.comm"]
|
14
|
+
s.files = `git ls-files`.split($\)
|
15
|
+
s.homepage = "https://rubygems.org/mwise/motion-music"
|
16
|
+
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.extra_rdoc_files = ['README.md']
|
20
|
+
end
|
data/projections.json
ADDED
data/rb-music.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "rb-music/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "rb-music"
|
6
|
+
s.version = RBMusic::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.license = "MIT"
|
9
|
+
s.summary = "Music theory library for Ruby"
|
10
|
+
s.description = "This gem provides Ruby classes for working with musical notes, scales and intervals."
|
11
|
+
s.author = "Mark Wise"
|
12
|
+
s.authors = ["Mark Wise"]
|
13
|
+
s.email = ["markmediadude@mgail.comm"]
|
14
|
+
s.files = ["lib/rb-music.rb"]
|
15
|
+
s.homepage = "https://rubygems.org/mwise/rb-music"
|
16
|
+
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.required_ruby_version = ">= 1.8.6"
|
19
|
+
s.files = `git ls-files -- lib/*`.split("\n")
|
20
|
+
s.files += %w[README.md LICENSE]
|
21
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
22
|
+
|
23
|
+
s.extra_rdoc_files = ['README.md']
|
24
|
+
|
25
|
+
s.add_development_dependency("guard-rspec")
|
26
|
+
s.add_development_dependency("rake")
|
27
|
+
s.add_development_dependency("rspec")
|
28
|
+
s.add_development_dependency("simplecov")
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe "RBMusic Constants" do
|
4
|
+
|
5
|
+
describe RBMusic::NOTE_NAMES do
|
6
|
+
it "is correct" do
|
7
|
+
subject.should == ["F", "C", "G", "D", "A", "E", "B"]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe RBMusic::ACCIDENTALS do
|
12
|
+
it "is correct" do
|
13
|
+
subject.should == ["bb", "b", "", "#", "x"]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe RBMusic::NOTES do
|
18
|
+
it "contains a key for each note/accidental combination" do
|
19
|
+
RBMusic::NOTE_NAMES.each do |note_name|
|
20
|
+
ACCIDENTALS.each do |accidental|
|
21
|
+
NOTES.should have_key("#{note_name}#{accidental}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RBMusic::Interval do
|
4
|
+
|
5
|
+
context "after initialization" do
|
6
|
+
let(:subject) { described_class.new("some coord") }
|
7
|
+
|
8
|
+
it "assigns the coord argument" do
|
9
|
+
subject.coord.should == "some coord"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "class methods" do
|
14
|
+
describe "#from_name" do
|
15
|
+
it "looks up the correct coordinates" do
|
16
|
+
subject = described_class.from_name("major_second")
|
17
|
+
|
18
|
+
subject.coord.should == described_class::INTERVALS[:major_second]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#from_semitones" do
|
23
|
+
it "looks up the correct coordinates" do
|
24
|
+
subject = described_class.from_semitones(3)
|
25
|
+
|
26
|
+
subject.coord.should == described_class::INTERVALS_SEMITONES[3]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#from_tones_semitones" do
|
31
|
+
it "looks up the correct coordinates" do
|
32
|
+
subject = described_class.from_tones_semitones([0, 3])
|
33
|
+
|
34
|
+
subject.coord.should == [9, -15]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "instance methods" do
|
40
|
+
let(:subject) { described_class.from_name("major_second") }
|
41
|
+
|
42
|
+
describe "#tone_semitone" do
|
43
|
+
it "returns the tone / semitone vector" do
|
44
|
+
subject.tone_semitone.should == [1, 0]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#semitone" do
|
49
|
+
it "returns number of semitones" do
|
50
|
+
subject.semitone.should == 2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#add" do
|
55
|
+
context "when given a string" do
|
56
|
+
it "adds the string as an Interval" do
|
57
|
+
result = subject.add("major_second")
|
58
|
+
result.coord.should == Interval.from_name("major_third").coord
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when given an Interval" do
|
63
|
+
it "adds the given Interval's coordinates and returns a new Interval" do
|
64
|
+
result = subject.add(described_class.from_name("minor_second"))
|
65
|
+
result.coord.should == Interval.from_name("minor_third").coord
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#subtract" do
|
71
|
+
context "when given a string" do
|
72
|
+
it "returns a Interval with the given string as Interval subtracted" do
|
73
|
+
result = subject.subtract("major_second")
|
74
|
+
result.coord.should == Interval.from_name("unison").coord
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when given an Interval" do
|
79
|
+
it "subtracts the coordinates of the given Interval and returns a new Interval" do
|
80
|
+
subject = described_class.from_name("major_third")
|
81
|
+
result = subject.subtract(described_class.from_name("major_second"))
|
82
|
+
|
83
|
+
result.coord.should == Interval.from_name("major_second").coord
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe RBMusic::NoteSet do
|
4
|
+
|
5
|
+
describe "class methods" do
|
6
|
+
describe "#from_scale" do
|
7
|
+
let(:scale) { RBMusic::Scale.new("C", "major") }
|
8
|
+
|
9
|
+
context "without any arguments" do
|
10
|
+
it "raises an ArgumentError" do
|
11
|
+
lambda {
|
12
|
+
described_class.from_scale
|
13
|
+
}.should raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "without a valid RBMusic::Scale" do
|
18
|
+
it "raises an ArgumentError" do
|
19
|
+
lambda {
|
20
|
+
described_class.from_scale("foo")
|
21
|
+
}.should raise_error(RBMusic::ArgumentError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with a RBMusic::Scale only" do
|
26
|
+
let(:subject) { described_class.from_scale(scale) }
|
27
|
+
|
28
|
+
it "returns a #{described_class}" do
|
29
|
+
subject.should be_a(described_class)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "builds a note for each scale degree in the default octave ranage (1)" do
|
33
|
+
subject.notes.length.should == scale.degree_count
|
34
|
+
end
|
35
|
+
|
36
|
+
it "builds notes from the default octave (0)" do
|
37
|
+
subject.notes[0].should == Note.from_latin("#{scale.key}0")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "with a RBMusic::Scale and an octave" do
|
42
|
+
let(:octave) { 3 }
|
43
|
+
let(:subject) { described_class.from_scale(scale, octave) }
|
44
|
+
|
45
|
+
it "builds notes for the scale from the given octave" do
|
46
|
+
subject.notes[0].should == Note.from_latin("#{scale.key}#{octave}")
|
47
|
+
subject.notes.length.should == scale.degree_count
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with a RBMusic::Scale, octave and octave range" do
|
52
|
+
let(:octave) { 3 }
|
53
|
+
let(:octaves) { 2 }
|
54
|
+
let(:subject) { described_class.from_scale(scale, octave, octaves) }
|
55
|
+
|
56
|
+
it "builds notes for the given octave range" do
|
57
|
+
degrees = scale.degree_count
|
58
|
+
|
59
|
+
subject.notes[0].should == Note.from_latin("#{scale.key}#{octave}")
|
60
|
+
subject.notes[degrees].should == Note.from_latin("#{scale.key}#{octave + 1}")
|
61
|
+
subject.notes.length.should == scale.degree_count * octaves
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with an invalid octave range" do
|
66
|
+
it "raises an ArgumentError" do
|
67
|
+
lambda {
|
68
|
+
described_class.from_scale(scale, 3, -1)
|
69
|
+
}.should raise_exception(RBMusic::ArgumentError)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "instance methods" do
|
76
|
+
|
77
|
+
describe "#initialize" do
|
78
|
+
let(:notes_array) { ["foo", "bar"] }
|
79
|
+
|
80
|
+
it "assigns the notes array" do
|
81
|
+
subject = described_class.new(notes_array)
|
82
|
+
|
83
|
+
subject.notes.should == notes_array
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#==" do
|
88
|
+
let(:notes) { ["foo", "bar"] }
|
89
|
+
|
90
|
+
context "when all the notes are equal" do
|
91
|
+
it "is true" do
|
92
|
+
described_class.new(notes).should == described_class.new(notes)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when note all the notes are equal" do
|
97
|
+
it "is false" do
|
98
|
+
described_class.new(notes).should_not == described_class.new([1, 2])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#add" do
|
104
|
+
let(:f4) { Note.from_latin("F4") }
|
105
|
+
let(:g4) { Note.from_latin("G4") }
|
106
|
+
let(:subject) { NoteSet.new([f4, g4]) }
|
107
|
+
|
108
|
+
context "when adding an interval string" do
|
109
|
+
let(:operand) { "major_second" }
|
110
|
+
|
111
|
+
it "retuns a new NoteSet with the operand added to each element of the original" do
|
112
|
+
result = subject.add(operand)
|
113
|
+
|
114
|
+
result.should be_a(NoteSet)
|
115
|
+
result[0].frequency.should == subject[0].add(operand).frequency
|
116
|
+
result[1].frequency.should == subject[1].add(operand).frequency
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when adding an interval symbol" do
|
121
|
+
let(:operand) { :major_second }
|
122
|
+
|
123
|
+
it "retuns a new NoteSet with the operand added to each element of the original" do
|
124
|
+
result = subject.add(operand)
|
125
|
+
|
126
|
+
result.should be_a(NoteSet)
|
127
|
+
result[0].frequency.should == subject[0].add(operand).frequency
|
128
|
+
result[1].frequency.should == subject[1].add(operand).frequency
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "when adding an note" do
|
133
|
+
let(:operand) { Note.from_latin("C4") }
|
134
|
+
|
135
|
+
it "retuns a new NoteSet with the operand added to each element of the original" do
|
136
|
+
result = subject.add(operand)
|
137
|
+
|
138
|
+
result.should be_a(NoteSet)
|
139
|
+
result[0].frequency.should == subject[0].add(operand).frequency
|
140
|
+
result[1].frequency.should == subject[1].add(operand).frequency
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "#subtract" do
|
147
|
+
let(:f4) { Note.from_latin("F4") }
|
148
|
+
let(:g4) { Note.from_latin("G4") }
|
149
|
+
let(:subject) { NoteSet.new([f4, g4]) }
|
150
|
+
|
151
|
+
context "when subtracting an interval string" do
|
152
|
+
let(:operand) { "major_second" }
|
153
|
+
|
154
|
+
it "retuns a new NoteSet with the operand subtracted to each element of the original" do
|
155
|
+
result = subject.subtract(operand)
|
156
|
+
|
157
|
+
result.should be_a(NoteSet)
|
158
|
+
result[0].frequency.should == subject[0].subtract(operand).frequency
|
159
|
+
result[1].frequency.should == subject[1].subtract(operand).frequency
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when subtracting an interval symbol" do
|
164
|
+
let(:operand) { :major_second }
|
165
|
+
|
166
|
+
it "retuns a new NoteSet with the operand subtracted to each element of the original" do
|
167
|
+
result = subject.subtract(operand)
|
168
|
+
|
169
|
+
result.should be_a(NoteSet)
|
170
|
+
result[0].frequency.should == subject[0].subtract(operand).frequency
|
171
|
+
result[1].frequency.should == subject[1].subtract(operand).frequency
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "when subtracting an note" do
|
176
|
+
let(:operand) { Note.from_latin("C4") }
|
177
|
+
|
178
|
+
it "retuns a new NoteSet with the operand subtracted to each element of the original" do
|
179
|
+
result = subject.subtract(operand)
|
180
|
+
|
181
|
+
result.should be_a(NoteSet)
|
182
|
+
result[0].coord.should == subject[0].subtract(operand).coord
|
183
|
+
result[1].coord.should == subject[1].subtract(operand).coord
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|