musique 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49ee2d5eebc0c30d67c830b2e8fe5abaf97e91b9
4
+ data.tar.gz: 5850e2bdb290e8eddf82336f6aa897849455feaa
5
+ SHA512:
6
+ metadata.gz: 467fbe00501716a9b449ae782d1ea925884449e879d9508107fa0c820954a76a2a5397e24bb5035aba7b7f9d77df3136c20bb3a64020a6e6f8495d0f1dbae859
7
+ data.tar.gz: fe2d69a49b2c4c31189dc5a84024f5b3388a8dd13a25648e6347dcbefa54ff993f1baeac03a0cb02757475d7e2358919593fa51491d5895450a61a36f89b141e
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014-ω Janko Marohnić
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Musique
2
+
3
+ Musique is a gem for manipulating with musical constructs, such as notes,
4
+ chords and intervals.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ ```sh
10
+ $ gem install musique
11
+ ```
12
+
13
+ Usage
14
+ -----
15
+
16
+ ### `Music::Note`
17
+
18
+ ```rb
19
+ require "musique"
20
+
21
+ include Music
22
+
23
+ note = Note.new("C#1")
24
+ note.letter #=> "C"
25
+ note.accidental #=> "#"
26
+ note.octave #=> 1
27
+
28
+ # Comparison
29
+ Note.new("C1") < Note.new("E1") #=> true
30
+ Note.new("C#1") == Note.new("D♭1") #=> true
31
+
32
+ # Transposing
33
+ major_third = Interval.new(3, :major)
34
+ Note.new("C1").transpose_up(major_third).name #=> "E1"
35
+ Note.new("E1").transpose_down(major_third).name #=> "C1"
36
+
37
+ # Difference
38
+ Note.new("C2") - Note.new("C1") #=> #<Music::Interval @number=8, @quality=:perfect>
39
+ ```
40
+
41
+ ### `Music::Chord`
42
+
43
+ ```rb
44
+ require "musique"
45
+
46
+ include Music
47
+
48
+ chord = Chord.new("C#7")
49
+ chord.root.name #=> "C#"
50
+ chord.kind #=> "7"
51
+
52
+ # Notes
53
+ Chord.new("C").notes.map(&:name) #=> ["C", "E", "G"]
54
+ Chord.new("Cm7").notes.map(&:name) #=> ["C", "E♭", "G", "B♭"]
55
+
56
+ # Transposing
57
+ major_third = Interval.new(3, :major)
58
+ Chord.new("C").transpose_up(major_third).notes.map(&:name) #=> ["E", "G#", "B"]
59
+ ```
60
+
61
+ Into `Chord.new(...)` you should be able to pass in any chord name. For example,
62
+ `"Cm"` and `"Cmin"` both represent the "C minor" chord, and both can be passed in.
63
+
64
+ If you come across a chord name that doesn't work, please open an issue,
65
+ I would like to know about it.
66
+
67
+ ### `Music::Interval`
68
+
69
+ ```rb
70
+ require "musique"
71
+
72
+ include Music
73
+
74
+ interval = Interval.new(3, :minor)
75
+ interval.number #=> 3
76
+ interval.quality #=> :minor
77
+
78
+ # Comparison
79
+ Interval.new(3, :minor) > Interval.new(2, :major) #=> true
80
+ Interval.new(3, :minor) == Interval.new(2, :augmented) #=> true
81
+
82
+ # Kinds
83
+ Interval.new(6, :major).consonance? #=> true
84
+ Interval.new(6, :major).perfect_consonance? #=> false (perfect consonances are 1, 4, and 5)
85
+ Interval.new(6, :major).imperfect_consonance? #=> true
86
+ Interval.new(6, :major).dissonance? #=> false
87
+
88
+ # Size
89
+ Interval.new(3, :major).size #=> 4 (semitones)
90
+ ```
91
+
92
+ Social
93
+ ------
94
+
95
+ You can follow me on Twitter, I'm [@m_janko](http://twitter.com/m_janko).
96
+
97
+ License
98
+ -------
99
+
100
+ This project is released under the [MIT license](/LICENSE).
@@ -0,0 +1,108 @@
1
+ # encoding: utf-8
2
+
3
+ module Music
4
+
5
+ class Chord
6
+
7
+ RULES = {
8
+ "5" => ["1", "major 3", "5"],
9
+ "m5" => ["1", "minor 3", "5"],
10
+
11
+ "7" => ["1", "major 3", "5", "major 7"],
12
+ "m7" => ["1", "minor 3", "5", "minor 7"],
13
+ }
14
+
15
+ REGEXP = /
16
+ (?<root> [CDEFGAB][#♭b]? )
17
+ (?<kind> m?\d* )
18
+ /x
19
+
20
+ ##
21
+ # @return [Music::Note]
22
+ # @example
23
+ # Chord.new("C").root #=> #<Music::Note @letter="C">
24
+ #
25
+ attr_reader :root
26
+ ##
27
+ #
28
+ # @return [String]
29
+ # @example
30
+ # Chord.new("C7").kind #=> "7"
31
+ #
32
+ attr_reader :kind
33
+
34
+ ##
35
+ # @param name [String] Name of the chord, in any form.
36
+ # @example
37
+ # Chord.new("Cm") == Chord.new("Cmin") #=> true
38
+ #
39
+ def initialize(name)
40
+ unless match = name.match(/^#{REGEXP}$/)
41
+ raise ArgumentError, "invalid chord format: #{name}"
42
+ end
43
+
44
+ @root = Note.new(match[:root])
45
+
46
+ @kind = match[:kind]
47
+ @kind << "5" if @kind !~ /\d+/
48
+ end
49
+
50
+ def name
51
+ [root, kind].join
52
+ end
53
+
54
+ ##
55
+ # Compares the names.
56
+ #
57
+ def ==(other)
58
+ self.name == other.name
59
+ end
60
+
61
+ ##
62
+ # @return [Array<Music::Note>]
63
+ #
64
+ def notes
65
+ RULES[kind].map do |interval_name|
66
+ quality = interval_name[/^[a-z]+/] || "perfect"
67
+ number = interval_name[/\d+$/]
68
+ interval = Interval.new(number.to_i, quality.to_sym)
69
+
70
+ root.transpose_by(interval)
71
+ end
72
+ end
73
+
74
+ ##
75
+ # @param interval [Music::Interval]
76
+ # @return [Music::Note]
77
+ # @example
78
+ # major_third = Interval.new(3, :major)
79
+ # Chord.new("C7").transpose_by(major_third) == Note.new("E7") #=> true
80
+ # Note.new("E7").transpose_by(-major_third) == Note.new("C7") #=> true
81
+ #
82
+ def transpose_by(interval)
83
+ Chord.new(root.transpose_by(interval).name + kind)
84
+ end
85
+
86
+ ##
87
+ # @param (see #transpose_by)
88
+ # @return (see #transpose_by)
89
+ #
90
+ # @see #transpose_by
91
+ #
92
+ def transpose_up(interval)
93
+ transpose_by(interval)
94
+ end
95
+
96
+ ##
97
+ # @param (see #transpose_by)
98
+ # @return (see #transpose_by)
99
+ #
100
+ # @see #transpose_by
101
+ #
102
+ def transpose_down(interval)
103
+ transpose_by(-interval)
104
+ end
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,173 @@
1
+ module Music
2
+
3
+ class Interval
4
+
5
+ include Comparable
6
+
7
+ ##
8
+ # @private
9
+ #
10
+ SIZES = {
11
+ 1 => [0],
12
+ 2 => [1, 2],
13
+ 3 => [3, 4],
14
+ 4 => [5],
15
+ 5 => [7],
16
+ 6 => [8, 9],
17
+ 7 => [10, 11],
18
+ 8 => [12],
19
+ }
20
+
21
+ ##
22
+ # @private
23
+ #
24
+ OCTAVE = 8
25
+
26
+ QUALITIES = [:minor, :major, :perfect, :augmented, :diminished]
27
+
28
+ ##
29
+ # @return [Integer]
30
+ #
31
+ attr_reader :number
32
+ ##
33
+ # @return [Symbol]
34
+ #
35
+ attr_reader :quality
36
+ ##
37
+ # Indicates if the interval is positive or negative.
38
+ #
39
+ # @return [1, -1]
40
+ #
41
+ attr_reader :direction
42
+
43
+ ##
44
+ # @param number [Integer]
45
+ # @param quality [Symbol] See the {QUALITIES} constant for the list of
46
+ # possible values.
47
+ #
48
+ # @example
49
+ # Interval.new(3, :major) # a major third
50
+ # Interval.new(5, :perfect) # a perfect fifth
51
+ #
52
+ def initialize(number, quality)
53
+ if not QUALITIES.include?(quality)
54
+ raise ArgumentError, "invalid interval quality: #{quality}"
55
+ elsif [1, 4, 5].include?((number.abs - 1) % 7 + 1) and [:major, :minor].include?(quality)
56
+ raise ArgumentError, "interval #{number} doesn't have quality \"#{quality}\""
57
+ end
58
+
59
+ @number, @quality = number.abs, quality
60
+ @direction = number >= 0 ? +1 : -1
61
+ end
62
+
63
+ ##
64
+ # Perfect consonants are perfect unisons, fourths and fifths (and their
65
+ # compound intervals).
66
+ #
67
+ # @return [Boolean]
68
+ #
69
+ def perfect_consonance?
70
+ [1, 4, 5].include?(normalized_number) and [:perfect].include?(quality)
71
+ end
72
+
73
+ ##
74
+ # Imperfect consonants are minor/major thirds and sixths (and their compound
75
+ # intervals).
76
+ #
77
+ # @return [Boolean]
78
+ #
79
+ def imperfect_consonance?
80
+ [3, 6].include?(normalized_number) and [:minor, :major].include?(quality)
81
+ end
82
+
83
+ ##
84
+ # Consonances are perfect and imperfect consonances together.
85
+ #
86
+ # @return [Boolean]
87
+ #
88
+ # @see #perfect_consonance?
89
+ # @see #imperfect_consonance?
90
+ #
91
+ def consonance?
92
+ perfect_consonance? or imperfect_consonance?
93
+ end
94
+
95
+ ##
96
+ # Disonances are all intervals that are not consonances.
97
+ #
98
+ # @return [Boolean]
99
+ #
100
+ # @see #consonance?
101
+ #
102
+ def dissonance?
103
+ not consonance?
104
+ end
105
+
106
+ ##
107
+ # Returns the same inteval with changed direction.
108
+ #
109
+ # @return [Music::Interval]
110
+ #
111
+ def -@
112
+ Interval.new((-1 * direction) * number, quality)
113
+ end
114
+
115
+ ##
116
+ # Returns the number of semitones that interval covers.
117
+ #
118
+ # @return [Integer]
119
+ #
120
+ def size
121
+ result = 0
122
+ current_number = number
123
+
124
+ while current_number >= OCTAVE
125
+ result += SIZES[OCTAVE][0]
126
+ current_number -= OCTAVE - 1
127
+ end
128
+
129
+ sizes = SIZES.fetch(current_number)
130
+ result +=
131
+ case quality
132
+ when :diminished then sizes.first - 1
133
+ when :minor then sizes.first
134
+ when :perfect then sizes[0]
135
+ when :major then sizes.last
136
+ when :augmented then sizes.last + 1
137
+ end
138
+
139
+ result * direction
140
+ end
141
+
142
+ ##
143
+ # Compares intervals by their size.
144
+ #
145
+ # @param other [Music::Interval]
146
+ # @example
147
+ # Interval.new(3, :major) > Interval.new(4, :perfect) #=> true
148
+ # Interval.new(3, :major) == Interval.new(4, :diminished) #=> true
149
+ #
150
+ def <=>(other)
151
+ self.size.abs <=> other.size.abs
152
+ end
153
+
154
+ ##
155
+ # The actual diatonic difference. For example, seconds are numbered with
156
+ # `2`, but the actual diatonic difference between two notes that
157
+ # are in a second is `1`.
158
+ #
159
+ # @return [Integer]
160
+ #
161
+ def diff
162
+ (number - 1) * direction
163
+ end
164
+
165
+ private
166
+
167
+ def normalized_number
168
+ (number - 1) % 7 + 1
169
+ end
170
+
171
+ end
172
+
173
+ end
data/lib/music/note.rb ADDED
@@ -0,0 +1,173 @@
1
+ # encoding: utf-8
2
+
3
+ module Music
4
+
5
+ class Note
6
+
7
+ include Comparable
8
+
9
+ CHROMATIC_SCALE = %w[C C/D D D/E E F F/G G G/A A A/B B]
10
+ DIATONIC_SCALE = %w[C D E F G A B]
11
+
12
+ ACCIDENTALS = %w[# ♭]
13
+
14
+ REGEXP = /
15
+ (?<letter> [CDEFGAB] )
16
+ (?<accidental> [#♭b]? )
17
+ (?<octave> (-?\d+)? )
18
+ /x
19
+
20
+ ##
21
+ # C, D, E, F, G, A or B.
22
+ #
23
+ # @return [String]
24
+ #
25
+ attr_reader :letter
26
+ ##
27
+ # \# or ♭.
28
+ #
29
+ # @return [String]
30
+ #
31
+ attr_reader :accidental
32
+ ##
33
+ # An integer.
34
+ #
35
+ # @return [Integer]
36
+ #
37
+ attr_reader :octave
38
+
39
+ ##
40
+ # @param name [String] Consists of a letter (C, D, E, F, G, A, B),
41
+ # accidental (# or ♭) and octave (any integer).
42
+ #
43
+ # @example
44
+ # Note.new("C")
45
+ # Note.new("C#")
46
+ # Note.new("C♭") #=> or Note.new("Cb")
47
+ #
48
+ # Note.new("C1")
49
+ # Note.new("B-1")
50
+ #
51
+ def initialize(name)
52
+ unless match = name.match(/^#{REGEXP}$/)
53
+ raise ArgumentError, "invalid note name: #{name} (example: C#1)"
54
+ end
55
+
56
+ @letter = match[:letter]
57
+ @accidental = match[:accidental].sub("b", "♭") unless match[:accidental].empty?
58
+ @octave = match[:octave].to_i unless match[:octave].empty?
59
+ end
60
+
61
+ def name
62
+ [letter, accidental, octave].join
63
+ end
64
+ alias to_s name
65
+
66
+ ##
67
+ # Compares notes by their pitch.
68
+ #
69
+ # @param other [Music::Note]
70
+ # @example
71
+ # Note.new("C#") == Note.new("D♭") #=> true
72
+ # Note.new("C") < Note.new("D") #=> true
73
+ # Note.new("C2") > Note.new("C1") #=> true
74
+ #
75
+ def <=>(other)
76
+ self.pitch <=> other.pitch
77
+ end
78
+
79
+ ##
80
+ # @param interval [Music::Interval]
81
+ # @return [Music::Note]
82
+ # @example
83
+ # major_third = Interval.new(3, :major)
84
+ # Note.new("C").transpose_by(major_third) == Note.new("E") #=> true
85
+ # Note.new("E").transpose_by(-major_third) == Note.new("C") #=> true
86
+ #
87
+ def transpose_by(interval)
88
+ transposed_pitch = pitch + interval.size
89
+ transposed_pitch %= CHROMATIC_SCALE.size if octave.nil?
90
+
91
+ transposed_diatonic_idx = (diatonic_idx + interval.diff) % DIATONIC_SCALE.size
92
+ transposed_letter = DIATONIC_SCALE.fetch(transposed_diatonic_idx)
93
+
94
+ transposed_octave = octave + (diatonic_idx + interval.diff) / DIATONIC_SCALE.size if octave
95
+
96
+ transposed_accidental = ACCIDENTALS.find do |accidental|
97
+ note = Note.new [transposed_letter, accidental, transposed_octave].join
98
+ note.pitch == transposed_pitch
99
+ end
100
+
101
+ Note.new [transposed_letter, transposed_accidental, transposed_octave].join
102
+ end
103
+
104
+ ##
105
+ # @param (see #transpose_by)
106
+ # @return (see #transpose_by)
107
+ #
108
+ # @see #transpose_by
109
+ #
110
+ def transpose_up(interval)
111
+ transpose_by(interval)
112
+ end
113
+
114
+ ##
115
+ # @param (see #transpose_by)
116
+ # @return (see #transpose_by)
117
+ #
118
+ # @see #transpose_by
119
+ #
120
+ def transpose_down(interval)
121
+ transpose_by(-interval)
122
+ end
123
+
124
+ ##
125
+ # @param other [Music::Note]
126
+ # @return [Music::Interval]
127
+ # @example
128
+ # Note.new("E") - Note.new("C") #=> #<Music::Interval @number=3, @quality=:major>
129
+ #
130
+ def -(other)
131
+ number = (self.diatonic_idx - other.diatonic_idx).abs + 1
132
+ number += (self.octave - other.octave).abs * DIATONIC_SCALE.size unless octave.nil?
133
+
134
+ distance = (self.pitch - other.pitch).abs
135
+ quality = Interval::QUALITIES.find do |quality|
136
+ begin; Interval.new(number, quality).size == distance; rescue ArgumentError; end
137
+ end
138
+
139
+ interval = Interval.new(number, quality)
140
+ self >= other ? interval : -interval
141
+ end
142
+
143
+ protected
144
+
145
+ ##
146
+ # Internal integer representation on the note.
147
+ #
148
+ # @return [Integer]
149
+ #
150
+ def pitch
151
+ result = chromatic_idx + (octave.to_i * CHROMATIC_SCALE.size)
152
+ result %= CHROMATIC_SCALE.size if octave.nil?
153
+ result
154
+ end
155
+
156
+ ##
157
+ # @return [Integer] An integer from 0 to 6
158
+ #
159
+ def diatonic_idx
160
+ DIATONIC_SCALE.index(letter)
161
+ end
162
+
163
+ ##
164
+ # @return [Integer] An integer from 0 to 11
165
+ #
166
+ def chromatic_idx
167
+ accidental_effect = {"#" => +1, "♭" => -1}.fetch(accidental, 0)
168
+ CHROMATIC_SCALE.index(letter) + accidental_effect
169
+ end
170
+
171
+ end
172
+
173
+ end
data/lib/music.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Music
2
+ autoload :Note, "music/note"
3
+ autoload :Interval, "music/interval"
4
+ autoload :Chord, "music/chord"
5
+ autoload :Scale, "music/scale"
6
+ end
data/lib/musique.rb ADDED
@@ -0,0 +1 @@
1
+ require "music"
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: musique
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Janko Marohnić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: This gem is an object-oriented wrapper for the Flickr API.
14
+ email:
15
+ - janko.marohnic@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - lib/music.rb
23
+ - lib/music/chord.rb
24
+ - lib/music/interval.rb
25
+ - lib/music/note.rb
26
+ - lib/musique.rb
27
+ homepage: http://janko-m.github.com/flickr-objects
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.9.2
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.2.0
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: This gem is an object-oriented wrapper for the Flickr API.
51
+ test_files: []
52
+ has_rdoc: