music 0.5.0

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.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,48 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # jeweler generated
15
+ pkg
16
+
17
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
18
+ #
19
+ # * Create a file at ~/.gitignore
20
+ # * Include files you want ignored
21
+ # * Run: git config --global core.excludesfile ~/.gitignore
22
+ #
23
+ # After doing this, these files will be ignored in all your git projects,
24
+ # saving you from having to 'pollute' every project you touch with them
25
+ #
26
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
27
+ #
28
+ # For MacOS:
29
+ #
30
+ #.DS_Store
31
+
32
+ # For TextMate
33
+ #*.tmproj
34
+ #tmtags
35
+
36
+ # For emacs:
37
+ #*~
38
+ #\#*
39
+ #.\#*
40
+
41
+ # For vim:
42
+ #*.swp
43
+
44
+ # For redcar:
45
+ #.redcar
46
+
47
+ # For rubinius:
48
+ #*.rbc
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode # JRuby in 1.8 mode
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-18mode
9
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ music-utils (0.5.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activemodel (3.2.2)
10
+ activesupport (= 3.2.2)
11
+ builder (~> 3.0.0)
12
+ activesupport (3.2.2)
13
+ i18n (~> 0.6)
14
+ multi_json (~> 1.0)
15
+ builder (3.0.0)
16
+ diff-lcs (1.1.3)
17
+ i18n (0.6.0)
18
+ multi_json (1.1.0)
19
+ rake (0.9.2.2)
20
+ rspec (2.9.0)
21
+ rspec-core (~> 2.9.0)
22
+ rspec-expectations (~> 2.9.0)
23
+ rspec-mocks (~> 2.9.0)
24
+ rspec-core (2.9.0)
25
+ rspec-expectations (2.9.1)
26
+ diff-lcs (~> 1.1.3)
27
+ rspec-mocks (2.9.0)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ activemodel (>= 3.0)
34
+ bundler (>= 1.1.3)
35
+ music-utils!
36
+ rake (>= 0.9)
37
+ rspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Brian Underwood
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.
@@ -0,0 +1,54 @@
1
+ music
2
+ =====
3
+
4
+ The *music* gem provides a means on calculating notes and chords.
5
+
6
+ Examples:
7
+ ---------
8
+
9
+ Creating notes:
10
+
11
+ note = Note.new(698.46) # Creates a note object with the frequency 698.46
12
+ note = Note.new('F#6') # Creates an F sharp note in the 6th octave
13
+
14
+ Comparing notes:
15
+
16
+ Note.new(698.46) < Note.new(1975.53) # => true
17
+ Note.new(698.46) == Note.new('F5') # => true
18
+
19
+ Converting from frequency to note string and vice versa:
20
+
21
+ Note.new(698.46).note_string # => 'F5'
22
+ Note.new('Bb3').frequency # => 233.08
23
+
24
+ Find how many semitones two notes are apart:
25
+
26
+ Note.note_distance('A4', 'A#4') # => 1
27
+ Note.note_distance('B1', 'F1') # => -6
28
+ Note.note_distance('G#1', 'Ab1') # => 0
29
+
30
+ Adjust frequencies by semitones:
31
+
32
+ Note.frequency_adjustment(440.0, 15) # => 1046.50
33
+
34
+ Create chords:
35
+
36
+ chord = Chord.new(['C4', 'E4', 'G4']) # Create a C major chord
37
+
38
+ Describe chords:
39
+
40
+ Chord.new(['C4', 'Eb4', 'Gb4']) # => ['C', :diminished]
41
+
42
+ *For further usage, see the examples in the spec files*
43
+
44
+ Contributing to music
45
+ =====================
46
+
47
+ Contributions are welcome. Check out the issues list and feel free to fork and make a pull request if you have any fixes/additions.
48
+
49
+ Copyright
50
+ =========
51
+
52
+ Copyright (c) 2012 Brian Underwood. See LICENSE.txt for
53
+ further details.
54
+
@@ -0,0 +1,25 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => [:spec]
5
+
6
+ desc "Run all test"
7
+ task :test => [:spec]
8
+
9
+ # RSpec tasks
10
+ desc "Run all examples"
11
+ RSpec::Core::RakeTask.new(:spec) do |spec|
12
+ spec.ruby_opts = '-I lib'
13
+ spec.pattern = 'spec/**/*_spec.rb'
14
+ spec.rspec_opts = ['--color --format doc']
15
+ end
16
+
17
+ # Gems tasks
18
+ require "bundler"
19
+ Bundler::GemHelper.install_tasks
20
+
21
+ desc "Clean automatically generated files"
22
+ task :clean do
23
+ FileUtils.rm_rf "pkg"
24
+ end
25
+
@@ -0,0 +1,3 @@
1
+ require 'music/chord'
2
+ require 'music/note'
3
+
@@ -0,0 +1,36 @@
1
+ module Music
2
+ class Chord
3
+ def initialize(notes)
4
+ @notes = notes.collect do |note|
5
+ if note.is_a?(Note)
6
+ note
7
+ else
8
+ Note.new(note)
9
+ end
10
+ end.sort
11
+ end
12
+
13
+ def note_strings
14
+ @notes.collect(&:note_string)
15
+ end
16
+
17
+ def describe
18
+ distances = (1...@notes.size).collect do |i|
19
+ @notes[0].distance_to(@notes[i])
20
+ end
21
+
22
+ quality = case distances
23
+ when [4, 7]
24
+ :major
25
+ when [3, 7]
26
+ :minor
27
+ when [3, 6]
28
+ :diminished
29
+ when [4, 8]
30
+ :augmented
31
+ end
32
+
33
+ [@notes.first.letter, quality]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,148 @@
1
+ require 'active_model'
2
+ require 'music/patches/hash'
3
+
4
+ module Music
5
+ class Note
6
+ include ActiveModel::Validations
7
+
8
+ NOTES = ['C', 'C#/Db', 'D', 'D#/Eb', 'E', 'F', 'F#/Gb', 'G', 'G#/Ab', 'A', 'A#/Bb', 'B']
9
+ NOTE_STRINGS = ['Ab', 'A', 'A#', 'Bb', 'B', 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'Gb', 'G', 'G#']
10
+
11
+ attr_accessor :frequency
12
+
13
+ validates_presence_of :frequency
14
+
15
+ include Comparable
16
+ def <=>(other_note)
17
+ self.frequency <=> other_note.frequency
18
+ end
19
+
20
+ def initialize(descriptor, assumed_octave = nil)
21
+ self.frequency = if descriptor.is_a? Numeric
22
+ Note.nearest_note_frequency(descriptor)
23
+ else
24
+ Note.calculate_frequency(descriptor, assumed_octave)
25
+ end
26
+ end
27
+
28
+ def note_string(give_flat = false)
29
+ Note.calculate_note(self.frequency, give_flat).join
30
+ end
31
+
32
+ def letter(give_flat = false)
33
+ Note.calculate_note(self.frequency, give_flat)[0]
34
+ end
35
+
36
+ def accidental(give_flat = false)
37
+ Note.calculate_note(self.frequency, give_flat)[1]
38
+ end
39
+
40
+ def octave
41
+ Note.calculate_note(self.frequency)[2]
42
+ end
43
+
44
+ def pred
45
+ Note.new(Note.frequency_adjustment(self.frequency, -1))
46
+ end
47
+
48
+ def succ
49
+ Note.new(Note.frequency_adjustment(self.frequency, 1))
50
+ end
51
+ alias :next :succ
52
+
53
+ def distance_to(note)
54
+ Note.note_distance(self.note_string, note.note_string)
55
+ end
56
+
57
+ class << self
58
+ extend ActiveSupport::Memoizable
59
+
60
+ def parse_note_string(note_string, assumed_octave = nil)
61
+ match = note_string.match(/^([A-Ga-g])([#b]?)([0-8]?)$/)
62
+
63
+ raise ArgumentError, "Did not recognize note string: #{note_string}" if !match
64
+ raise ArgumentError, "No octave found or specified" if match[3].blank? && assumed_octave.nil?
65
+ raise ArgumentError if match[3].to_i > 8 || (assumed_octave && !(0..8).include?(assumed_octave))
66
+
67
+ octave = match[3].present? ? match[3] : assumed_octave
68
+ [match[1].upcase, match[2] == '' ? nil : match[2], octave.to_i]
69
+ end
70
+
71
+ def note_distance(note_string1, note_string2)
72
+ letter1, accidental1, octave1 = parse_note_string(note_string1)
73
+ letter2, accidental2, octave2 = parse_note_string(note_string2)
74
+
75
+ get_index = Proc.new do |letter, accidental|
76
+ NOTES.index do |note|
77
+ regex = case accidental
78
+ when '#' then
79
+ /^#{letter}#/
80
+ when 'b' then
81
+ /#{letter}b$/
82
+ else
83
+ /^#{letter}$/
84
+ end
85
+ note.match(regex)
86
+ end
87
+ end
88
+
89
+ index1 = get_index.call(letter1, accidental1)
90
+ index2 = get_index.call(letter2, accidental2)
91
+
92
+ (index2 - index1) + ((octave2.to_i - octave1.to_i) * NOTES.size)
93
+ end
94
+
95
+ def frequency_adjustment(start_frequency, distance)
96
+ result = (start_frequency * (2.0 ** (distance.to_f / NOTES.size)))
97
+
98
+ # Would like to use #round(2), but want to support Ruby 1.8
99
+ (result * 100.0).round / 100.0
100
+ end
101
+
102
+ def calculate_frequency(*args)
103
+ case args.size
104
+ when 1
105
+ letter, accidental, octave = parse_note_string(args[0])
106
+ when 2
107
+ letter, accidental, octave = parse_note_string(args[0], args[1])
108
+ when 3
109
+ letter, accidental, octave = args
110
+ else
111
+ raise ArgumentError, "Invalid octave of arguments"
112
+ end
113
+
114
+ distance = note_distance('A4', "#{letter}#{accidental}#{octave}")
115
+
116
+ frequency_adjustment(440.0, distance)
117
+ end
118
+
119
+ def calculate_note(frequency, give_flat = false)
120
+ # Would like to use #log(frequency / 440.0, 2), but would like to support Ruby 1.8
121
+ frequency_log_base_2 = Math.log(frequency / 440.0) / Math.log(2)
122
+
123
+ distance = (NOTES.size * frequency_log_base_2).round
124
+
125
+ index = 9 + distance # 9 is index for A
126
+
127
+ octave = 4 + (index / NOTES.size) # 4 is because we're using A4
128
+ index = (index % NOTES.size)
129
+
130
+ parts = "#{NOTES[index]}".split('/')
131
+ note = if give_flat
132
+ parts.last
133
+ else
134
+ parts.first
135
+ end
136
+
137
+ "#{note}#{octave}"
138
+ note_parts = note.split('')
139
+ note_parts + (note_parts.size == 1 ? [nil] : []) + [octave.to_i]
140
+ end
141
+
142
+ def nearest_note_frequency(frequency)
143
+ Note.calculate_frequency(Note.calculate_note(frequency).join)
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def flip
3
+ Hash[*self.to_a.collect(&:reverse).flatten]
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Music
2
+ VERSION = '0.5.0'
3
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "music/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ # Basic Info
7
+ s.name = "music"
8
+ s.version = Music::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Brian Underwood"]
11
+ s.email = ["ml+musicgem@semi-sentient.com"]
12
+ s.homepage = "http://github.com/cheerfulstoic/music"
13
+ s.summary = %q{Library for performing calculations on musical elements}
14
+ s.description = %q{Library for classifying notes and chords and performing calculations on them. See README.md}
15
+ s.license = %q{MIT}
16
+
17
+ # Files
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {spec,factories}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ # Ruby Gems Version
24
+ s.required_rubygems_version = ">= 1.8"
25
+
26
+ # Dependencies
27
+ s.add_development_dependency "rake", ">= 0.9"
28
+ s.add_development_dependency "bundler", ">= 1.1.3"
29
+ s.add_development_dependency "rspec"
30
+ s.add_development_dependency "activemodel", '>= 3.2.0'
31
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Music::Chord do
4
+ before :all do
5
+ @standard_tuning_notes = [Note.new('E2'), Note.new('A2'), Note.new('D3'), Note.new('G3'), Note.new('B3'), Note.new('E4')]
6
+ end
7
+
8
+ describe '#new(notes)' do
9
+ it 'should take an array of Note objects' do
10
+ Chord.new(@standard_tuning_notes)
11
+ end
12
+
13
+ it 'should take an array of note strings' do
14
+ Chord.new(@standard_tuning_notes.collect(&:note_string))
15
+ end
16
+
17
+ it 'should sort the notes by frequency' do
18
+ Chord.new(['A2', 'E2']).note_strings.should == ['E2', 'A2']
19
+ end
20
+ end
21
+
22
+ describe '#note_strings' do
23
+ it 'should return an array of note strings' do
24
+ chord = Chord.new(@standard_tuning_notes)
25
+
26
+ chord.note_strings.should == @standard_tuning_notes.collect(&:note_string)
27
+ end
28
+ end
29
+
30
+ describe '#describe' do
31
+ describe 'major chords' do
32
+ it 'should recognize C major' do
33
+ Chord.new(['C4', 'E4', 'G4']).describe.should == ['C', :major]
34
+ end
35
+ end
36
+
37
+ describe 'minor chords' do
38
+ it 'should recognize C minor' do
39
+ Chord.new(['C4', 'Eb4', 'G4']).describe.should == ['C', :minor]
40
+ end
41
+ end
42
+
43
+ describe 'diminished chords' do
44
+ it 'should recognize C diminished' do
45
+ Chord.new(['C4', 'Eb4', 'Gb4']).describe.should == ['C', :diminished]
46
+ end
47
+ end
48
+
49
+ describe 'augmented chords' do
50
+ it 'should recognize C augmented' do
51
+ Chord.new(['C4', 'E4', 'G#4']).describe.should == ['C', :augmented]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'music/patches/hash'
3
+
4
+ describe Hash do
5
+ describe '#flip' do
6
+ it "Should reverse keys and values" do
7
+ {1 => 2, 3 => 4}.flip.should == {2 => 1, 4 => 3}
8
+
9
+ {'foo' => 'bar', 'baz' => 'bitz'}.flip.should == {'bitz' => 'baz', 'bar' => 'foo'}
10
+ end
11
+
12
+ it "Should return an empty hash from an empty hash" do
13
+ {}.flip.should == {}
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,264 @@
1
+ require 'spec_helper'
2
+ include Music
3
+
4
+ describe Music::Note do
5
+
6
+ describe "#new" do
7
+ it "should allow note strings to initalize notes" do
8
+ Note.new('E0').frequency.should == 20.60
9
+ Note.new('Bb3').frequency.should == 233.08
10
+ end
11
+
12
+ it "should allow frequencies to initalize notes" do
13
+ Note.new(698.46).note_string.should == 'F5'
14
+ Note.new(1975.53).note_string.should == 'B6'
15
+ end
16
+ end
17
+
18
+ describe "Comparing notes" do
19
+ it "should compare notes by their frequencies" do
20
+ Note.new(698.46).should < Note.new(1975.53)
21
+ Note.new('B5').should > Note.new('B2')
22
+ Note.new(698.46).should == Note.new('F5')
23
+ end
24
+ end
25
+
26
+ describe '#note_string' do
27
+ it 'Should return the letter, accidental, and octave as a string' do
28
+ Note.new(698.46).note_string.should == 'F5'
29
+ Note.new('C#6').note_string.should == 'C#6'
30
+ end
31
+
32
+ it 'Should return the flat version if asked' do
33
+ Note.new('C#6').note_string(true).should == 'Db6'
34
+ end
35
+ end
36
+
37
+ describe '#letter' do
38
+ it 'Should return just the letter' do
39
+ Note.new('E0').letter.should == 'E'
40
+ Note.new(698.46).letter.should == 'F'
41
+ Note.new(1975.53).letter.should == 'B'
42
+ end
43
+
44
+ it 'Should return the letter based on if asked for the flat' do
45
+ Note.new('Bb3').letter.should == 'A'
46
+ Note.new('Bb3').letter(true).should == 'B'
47
+ end
48
+ end
49
+
50
+ describe '#accidental' do
51
+ it 'Should return just the accidental' do
52
+ Note.new('A#4').accidental.should == '#'
53
+ end
54
+
55
+ it 'Should return the flat if asked for' do
56
+ Note.new('Bb3').accidental.should == '#'
57
+ Note.new('Bb3').accidental(true).should == 'b'
58
+ end
59
+
60
+ it 'Should return nil for no accidental' do
61
+ Note.new('B2').accidental.should == nil
62
+ Note.new('G2').accidental.should == nil
63
+ end
64
+ end
65
+
66
+ describe '#octave' do
67
+ it 'Should return the octave' do
68
+ Note.new('A#4').octave.should == 4
69
+ Note.new('B7').octave.should == 7
70
+ Note.new('Gb1').octave.should == 1
71
+ Note.new('E0').octave.should == 0
72
+ end
73
+ end
74
+
75
+ describe ".parse_note_string(note_string)" do
76
+ it "Should split note letter, accidental, and octave" do
77
+ Note.parse_note_string('A#1').should == ['A', '#', 1]
78
+ Note.parse_note_string('Cb4').should == ['C', 'b', 4]
79
+ end
80
+
81
+ it "Should allow for lower case notes" do
82
+ Note.parse_note_string('g#1').should == ['G', '#', 1]
83
+ end
84
+
85
+ it "Should allow for an assumed octave" do
86
+ Note.parse_note_string('A', 4).should == ['A', nil, 4]
87
+ Note.parse_note_string('C#', 6).should == ['C', '#', 6]
88
+ end
89
+
90
+ it "Should ignore the assumed octave if there is a octave" do
91
+ Note.parse_note_string('B3', 4).should == ['B', nil, 3]
92
+ Note.parse_note_string('Gb6', 2).should == ['G', 'b', 6]
93
+ end
94
+
95
+ it "Should not allow notes above G" do
96
+ expect { Note.parse_note_string('HB1') }.to raise_error ArgumentError
97
+ end
98
+
99
+ it "Should not allow extraneous characters" do
100
+ expect { Note.parse_note_string(' Ab1') }.to raise_error ArgumentError
101
+ expect { Note.parse_note_string('%Hb1') }.to raise_error ArgumentError
102
+ expect { Note.parse_note_string('Hb1-') }.to raise_error ArgumentError
103
+ end
104
+
105
+ it "Should not allow for upper case flats" do
106
+ expect { Note.parse_note_string('AB1') }.to raise_error ArgumentError
107
+ expect { Note.parse_note_string('aB1') }.to raise_error ArgumentError
108
+ end
109
+
110
+ it "Should return nil when there is no accidental" do
111
+ Note.parse_note_string('C4').should == ['C', nil, 4]
112
+ end
113
+
114
+ it "Should not allow note above octave 8" do
115
+ Note.parse_note_string('G#8').should == ['G', '#', 8]
116
+ expect { Note.parse_note_string('Ab9') }.to raise_error ArgumentError
117
+ expect { Note.parse_note_string('Ab', 9) }.to raise_error ArgumentError
118
+ end
119
+ end
120
+
121
+ describe ".note_distance(note_string1, note_string2)" do
122
+ {
123
+ ['A4', 'A#4'] => 1,
124
+ ['A4', 'Ab4'] => -1,
125
+ ['B0', 'B0'] => 0,
126
+ ['G#1', 'Ab1'] => 0,
127
+ ['Bb1', 'A#1'] => 0,
128
+ ['A3', 'A4'] => 12,
129
+ ['a3', 'A4'] => 12,
130
+ ['B1', 'F1'] => -6,
131
+ ['B1', 'f1'] => -6,
132
+ ['A4', 'Eb0'] => -54,
133
+ ['a4', 'eb0'] => -54,
134
+ ['A2', 'C4'] => 15
135
+ }.each do |notes, distance|
136
+ it "Should return #{distance} between #{notes[0]} and #{notes[1]}" do
137
+ Note.note_distance(*notes).should == distance
138
+ end
139
+ end
140
+
141
+ it "Should not allow invalid note strings" do
142
+ expect { Note.note_distance('H0', 'A0') }.to raise_error ArgumentError
143
+ expect { Note.note_distance('A0', 'I#0') }.to raise_error ArgumentError
144
+ expect { Note.note_distance('A%0', 'A0') }.to raise_error ArgumentError
145
+ end
146
+ end
147
+
148
+ describe '#distance_to(note)' do
149
+ it 'Should find the distance between the subject note object and the note passed in' do
150
+ Note.new('A2').distance_to(Note.new('C4')).should == 15
151
+ end
152
+ end
153
+
154
+ describe ".calculate_frequency(letter, accidental, octave)" do
155
+ {
156
+ ['C', nil, 0] => 16.35,
157
+ ['E', 'b', 0] => 19.45,
158
+ ['A', '#', 1] => 58.27,
159
+ ['B', 'b', 1] => 58.27,
160
+ ['A', 'b', 4] => 415.30,
161
+ ['A', nil, 4] => 440.00,
162
+ ['A', '#', 4] => 466.16,
163
+ ['B', nil, 6] => 1975.53,
164
+ ['C', nil, 7] => 2093.00,
165
+ ['E', 'b', 8] => 4978.03
166
+ }.each do |note_array, frequency|
167
+ it "Should return #{frequency} for #{note_array.join}" do
168
+ Note.calculate_frequency(*note_array).should == frequency
169
+ end
170
+ end
171
+
172
+ it "Should take note strings as an argument" do
173
+ Note.calculate_frequency('A#1').should == 58.27
174
+ Note.calculate_frequency('A4').should == 440.00
175
+ Note.calculate_frequency('C7').should == 2093.00
176
+ end
177
+
178
+ it "Should allow lower case notes" do
179
+ Note.calculate_frequency('a#1').should == 58.27
180
+ Note.calculate_frequency('db2').should == 69.3
181
+ Note.calculate_frequency('e', nil, 3).should == 164.81
182
+ end
183
+
184
+ it "Should not take argument lengths above 3" do
185
+ expect { Note.calculate_frequency('A', nil, 0, 0) }.to raise_error ArgumentError
186
+ end
187
+
188
+ it "Should not allow invalid notes" do
189
+ expect { Note.calculate_frequency('H', nil, 0) }.to raise_error ArgumentError
190
+ expect { Note.calculate_frequency('I', nil, 0) }.to raise_error ArgumentError
191
+ end
192
+
193
+ it "Should not allow invalid accidentals" do
194
+ expect { Note.calculate_frequency('A', 5, 0) }.to raise_error ArgumentError
195
+ expect { Note.calculate_frequency('A', '&', 0) }.to raise_error ArgumentError
196
+ end
197
+
198
+ it "Should not allow invalid octaves" do
199
+ expect { Note.calculate_frequency('A', nil, -1) }.to raise_error ArgumentError
200
+ expect { Note.calculate_frequency('A', nil, 9) }.to raise_error ArgumentError
201
+ end
202
+ end
203
+
204
+ describe ".calculate_note(frequency)" do
205
+ test_frequencies = {
206
+ [16.35] => ['C', nil, 0],
207
+ [19.45, true] => ['E', 'b', 0],
208
+ [58.27] => ['A', '#', 1],
209
+ [58.27, true] => ['B', 'b', 1],
210
+ [415.30, true] => ['A', 'b', 4],
211
+ [440.00] => ['A', nil, 4],
212
+ [466.16] => ['A', '#', 4],
213
+ [1975.53] => ['B', nil, 6],
214
+ [2093.00] => ['C', nil, 7],
215
+ [4978.03, true] => ['E', 'b', 8]
216
+ }
217
+
218
+ test_frequencies.each do |args, note_string|
219
+ it "Should return #{note_string} for #{args[0]}#{args[1] && ' (giving flat)'}" do
220
+ Note.calculate_note(*args).should == note_string
221
+ end
222
+ end
223
+
224
+ it "Should calculate the closest note near to a frequency" do
225
+ Note.calculate_note(420, true).should == ['A', 'b', 4]
226
+ Note.calculate_note(430).should == ['A', nil, 4]
227
+ end
228
+ end
229
+
230
+ describe ".nearest_note_frequency(frequency)" do
231
+ it 'should return the frequency if it is the frequency of a note' do
232
+ Note.nearest_note_frequency(2093.00).should == 2093.00
233
+ end
234
+
235
+ it 'should return the nearest frequency which matches a note' do
236
+ Note.nearest_note_frequency(41.00).should == 41.20
237
+ Note.nearest_note_frequency(40.50).should == 41.20
238
+ Note.nearest_note_frequency(40.00).should == 38.89
239
+ Note.nearest_note_frequency(39.00).should == 38.89
240
+ end
241
+ end
242
+
243
+ describe ".frequency_adjustment(start_frequency, distance)" do
244
+ it "Should find frequencies based on start frequency and distance" do
245
+ Note.frequency_adjustment(440.0, 0).should == 440.0
246
+ Note.frequency_adjustment(440.0, 15).should == 1046.50
247
+ Note.frequency_adjustment(440.0, -10).should == 246.94
248
+
249
+ Note.frequency_adjustment(1479.98, -6).should == 1046.50
250
+ Note.frequency_adjustment(1479.98, 7).should == 2217.46
251
+ end
252
+ end
253
+
254
+ describe "Getting adjacent notes" do
255
+ it "should allow getting of next note" do
256
+ Note.new(698.46).next.should == Note.new(739.99)
257
+ Note.new(698.46).succ.should == Note.new(739.99)
258
+ end
259
+
260
+ it "should allow getting of previous note" do
261
+ Note.new(739.99).pred.should == Note.new(698.46)
262
+ end
263
+ end
264
+ end
@@ -0,0 +1 @@
1
+ require 'music'
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'music'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestMusic < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: music
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
+ platform: ruby
12
+ authors:
13
+ - Brian Underwood
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-15 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rake
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 25
29
+ segments:
30
+ - 0
31
+ - 9
32
+ version: "0.9"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: bundler
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 21
44
+ segments:
45
+ - 1
46
+ - 1
47
+ - 3
48
+ version: 1.1.3
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rspec
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: activemodel
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 15
74
+ segments:
75
+ - 3
76
+ - 2
77
+ - 0
78
+ version: 3.2.0
79
+ type: :development
80
+ version_requirements: *id004
81
+ description: Library for classifying notes and chords and performing calculations on them. See README.md
82
+ email:
83
+ - ml+musicgem@semi-sentient.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files: []
89
+
90
+ files:
91
+ - .document
92
+ - .gitignore
93
+ - .travis.yml
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - lib/music.rb
100
+ - lib/music/chord.rb
101
+ - lib/music/note.rb
102
+ - lib/music/patches/hash.rb
103
+ - lib/music/version.rb
104
+ - music.gemspec
105
+ - spec/classes/chord_spec.rb
106
+ - spec/classes/hash_spec.rb
107
+ - spec/classes/note_spec.rb
108
+ - spec/spec_helper.rb
109
+ - test/helper.rb
110
+ - test/test_music.rb
111
+ homepage: http://github.com/cheerfulstoic/music
112
+ licenses:
113
+ - MIT
114
+ post_install_message:
115
+ rdoc_options: []
116
+
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 31
134
+ segments:
135
+ - 1
136
+ - 8
137
+ version: "1.8"
138
+ requirements: []
139
+
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.17
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Library for performing calculations on musical elements
145
+ test_files:
146
+ - spec/classes/chord_spec.rb
147
+ - spec/classes/hash_spec.rb
148
+ - spec/classes/note_spec.rb
149
+ - spec/spec_helper.rb
150
+ has_rdoc: