motion-music 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.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
|