dodecaphony 0.1.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2cdb493c58e0f214ef7cc89dac4bafd38d7826b9
4
+ data.tar.gz: 807fe2e936382444b1c9612969105a7215bf9307
5
+ SHA512:
6
+ metadata.gz: 9662162bdee955579875ba1e37a41b591176ea4cd9e24c92c9c829ef3f211e49fd3c38ce629ca8d570cdaecdc4234746c95ba7cc0b0dbacd088ef2ca17ca6273
7
+ data.tar.gz: 06baece067f2ad549ce6808a10c5dba842fef4e924698f56c01f7c1bc69a1d6099b7c31cd5a79cec703847714bfd92ed64b7c85565e285c950b948006187e992
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Kevin McGladdery
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ Dodecaphony
2
+ -----------
3
+
4
+ Dodecaphony calculates twelve tone rows for
5
+ you. To learn more about what this means, [read
6
+ this](http://www.tufts.edu/~mdevoto/12TonePrimer.pdf).
7
+
8
+ Examples
9
+ ---
10
+
11
+ Dodecaphony takes an array of twelve strings, one for each pitch in your
12
+ twelve tone row.
13
+
14
+ ```ruby
15
+ Dodecaphony::Row.new %w[a a# b c c# d d# e f f# g g#]
16
+ ```
17
+
18
+ Each pitch must begin with a valid letter name (a through g) and can be
19
+ followed by one or more accidentals. `#`, `+`, `b` and `-` are the four valid
20
+ accidentals.
21
+
22
+ Once you have your row, you can ask it for any prime, inversion, retrograde, or
23
+ retrograde inversion like so:
24
+
25
+ ```ruby
26
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
27
+
28
+ # "p" followed by 0 through 11 returns the corresponding prime row
29
+ tone_row.p0 # ["a", "f#", "g", "ab", "e", "f", "b", "bb", "d", "c#", "c", "eb"]
30
+
31
+ # "r" followed by 0 through 11 returns the corresponding retrograde
32
+ tone_row.r11 # ["d", "b", "c", "c#", "a", "bb", "e", "eb", "g", "f#", "f", "ab"]
33
+
34
+ # "i" followed by 0 through 11 returns the corresponding inversion
35
+ tone_row.i2 # ["b", "d", "c#", "c", "e", "eb", "a", "bb", "f#", "g", "ab", "f"]
36
+
37
+ # "ri" followed by 0 through 11 returns the corresponding retrograde inversion
38
+ tone_row.ri6 # ["a", "c", "b", "bb", "d", "c#", "g", "ab", "e", "f", "f#", "eb"]
39
+ ```
40
+
41
+ You can also ask a row to respell itself, favoring flats or sharps:
42
+ ```ruby
43
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
44
+
45
+ tone_row.spell_with_sharps # ["A", "F#", "G", "G#", "E", "F", "B", "A#", "D", "C#", "C", "D#"]
46
+
47
+ tone_row.spell_with_flats # ["A", "Gb", "G", "Ab", "E", "F", "B", "Bb", "D", "Db", "C", "Eb"]
48
+ ```
49
+
@@ -0,0 +1,120 @@
1
+ require_relative 'pitch'
2
+ require 'set'
3
+
4
+ module Dodecaphony
5
+ class Row
6
+
7
+ attr_reader :original_row
8
+
9
+ def initialize tone_row
10
+ self.original_row = create_row_with_pitches(tone_row)
11
+ validate_size_of original_row
12
+ self.row_with_intervals = create_list_with_intervals(original_row,
13
+ starting_pitch)
14
+ end
15
+
16
+ def to_s
17
+ p0.join(" ")
18
+ end
19
+
20
+ def spell_with_sharps
21
+ normalize_row(:spell_as_sharp)
22
+ end
23
+
24
+ def spell_with_flats
25
+ normalize_row(:spell_as_flat)
26
+ end
27
+
28
+ def i0
29
+ original_row.each_with_object([]) do |pitch, row|
30
+ row << row_with_intervals[((row_with_intervals.key(pitch) - 12).abs)].name
31
+ end
32
+ end
33
+
34
+ (0..11).each do |i|
35
+ define_method "p#{i}".to_sym do
36
+ original_row.each_with_object([]) do |pitch, row|
37
+ row << row_with_intervals[(transpose i, pitch)].name
38
+ end
39
+ end
40
+ end
41
+
42
+ (1..11).each do |i|
43
+ define_method "i#{i}".to_sym do
44
+ corresponding_p = self.send("p#{i}")
45
+ new_row = self.class.new corresponding_p
46
+ new_row.i0
47
+ end
48
+ end
49
+
50
+ (0..11).each do |i|
51
+ define_method "r#{i}".to_sym do
52
+ self.send("p#{i}".to_sym).reverse.each_with_object([]) do |pitch, row|
53
+ row << pitch
54
+ end
55
+ end
56
+ end
57
+
58
+ (0..11).each do |i|
59
+ define_method "ri#{i}".to_sym do
60
+ self.send("i#{i}").reverse.each_with_object([]) do |pitch, row|
61
+ row << pitch
62
+ end
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ attr_writer :original_row
69
+
70
+ attr_accessor :row_with_intervals
71
+
72
+ def create_list_with_intervals(row, first_pitch)
73
+ row_list = row.each_with_object({}) do |pitch, hash|
74
+ distance = first_pitch.distance_from pitch
75
+ validate_uniqueness_of distance, hash
76
+ hash[distance] = pitch
77
+ end
78
+ finalize_pitches(row_list)
79
+ end
80
+
81
+ def finalize_pitches(row_list)
82
+ row_list[12] = starting_pitch
83
+ row_list
84
+ end
85
+
86
+ def starting_pitch
87
+ original_row[0]
88
+ end
89
+
90
+ def create_row_with_pitches tone_row
91
+ tone_row.each_with_object([]) do |pitch, row|
92
+ row << Pitch.new(pitch)
93
+ end
94
+ end
95
+
96
+ def validate_size_of row
97
+ row_size = row.to_set.length
98
+ unless row_size == 12
99
+ raise ArgumentError, "incorrect number of pitches (#{row_size}) in row"
100
+ end
101
+ end
102
+
103
+ def validate_uniqueness_of distance, hash
104
+ if hash.has_key? distance
105
+ raise ArgumentError, "duplicate pitch (#{hash[distance].name})"
106
+ end
107
+ end
108
+
109
+ def normalize_row message
110
+ original_row.each_with_object([]) do |pitch, row|
111
+ row << pitch.send(message)
112
+ end
113
+ end
114
+
115
+ def transpose interval, pitch
116
+ (starting_pitch.distance_from(pitch) + interval) % 12
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,73 @@
1
+ module Dodecaphony
2
+ class Pitch
3
+
4
+ attr_reader :name
5
+
6
+ VALID_PITCHES = {'A' => 0, 'B' => 2, 'C' => 3, 'D' => 5,
7
+ 'E' => 7, 'F' => 8, 'G' => 10}
8
+
9
+ private_constant :VALID_PITCHES
10
+
11
+ def initialize pitch_name
12
+ self.name = pitch_name
13
+ validate_name
14
+ self.pitch_number = adjust_for_accidentals(starting_number)
15
+ end
16
+
17
+ def distance_from second_pitch
18
+ ensure_number_scale(second_pitch.pitch_number - pitch_number)
19
+ end
20
+
21
+ def spell_as_sharp
22
+ respell :-, "#"
23
+ end
24
+
25
+ def spell_as_flat
26
+ respell :+, "b"
27
+ end
28
+
29
+ protected
30
+
31
+ attr_reader :pitch_number
32
+
33
+ private
34
+
35
+ attr_writer :name, :pitch_number
36
+
37
+ def validate_name
38
+ unless VALID_PITCHES.has_key?(starting_letter)
39
+ raise ArgumentError, 'invalid pitch name'
40
+ end
41
+ end
42
+
43
+ def respell method, accidental
44
+ num = self.pitch_number
45
+ VALID_PITCHES.key(num) ||
46
+ VALID_PITCHES.key(ensure_number_scale(num.send(method, 1))) + accidental
47
+ end
48
+
49
+ def accidentals
50
+ name.downcase.split(//)[1..-1]
51
+ end
52
+
53
+ def adjust_for_accidentals number
54
+ accidentals.each do |a|
55
+ number += 1 if a == '+' or a == '#'
56
+ number -= 1 if a == '-' or a == 'b'
57
+ end
58
+ ensure_number_scale number
59
+ end
60
+
61
+ def ensure_number_scale number
62
+ number % 12
63
+ end
64
+
65
+ def starting_number
66
+ VALID_PITCHES[starting_letter]
67
+ end
68
+
69
+ def starting_letter
70
+ name.upcase.split(//)[0]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,96 @@
1
+ require 'dodecaphony'
2
+
3
+ describe Dodecaphony::Row do
4
+
5
+ it "initializes with an array of strings and returns the original row" do
6
+ tone_row = %w[ a a# b c db d eb e f f# g g# ]
7
+ new_dod = Dodecaphony::Row.new tone_row
8
+
9
+ expect(new_dod.p0).to eq tone_row
10
+ end
11
+
12
+ it "raises an error for a row with more than 12 pitches" do
13
+ expect{ Dodecaphony::Row.new %w[ a a# b c c# d eb e f f# g ab a] }.
14
+ to raise_error(ArgumentError, 'incorrect number of pitches (13) in row')
15
+ end
16
+
17
+ it "raises an error for a row with less than 12 pitches" do
18
+ expect{ Dodecaphony::Row.new %w[ a a# b eb e f f# g ab a] }.
19
+ to raise_error(ArgumentError, 'incorrect number of pitches (10) in row')
20
+ end
21
+
22
+ it "raises an error for duplicate pitches" do
23
+ expect{ Dodecaphony::Row.new %w[a a# b c c# d eb e f f g ab]}.
24
+ to raise_error(ArgumentError, 'duplicate pitch (f)')
25
+ end
26
+
27
+ it "can respell the row, favoring sharps" do
28
+ tone_row = %w[ c c+ d d# fb f gb g a- b-- bb b ]
29
+ new_dod = Dodecaphony::Row.new tone_row
30
+
31
+ expect(new_dod.spell_with_sharps).to eq %w[ C C# D D# E F F# G G# A A# B ]
32
+ end
33
+
34
+ it "can respell the row, favoring flats" do
35
+ tone_row = %w[ c c+ d d# fb f gb g a- b-- bb b ]
36
+ new_dod = Dodecaphony::Row.new tone_row
37
+
38
+ expect(new_dod.spell_with_flats).to eq %w[ C Db D Eb E F Gb G Ab A Bb B ]
39
+ end
40
+
41
+ it "can give p1" do
42
+ tone_row = %w[ bb b c c# d f e eb g gb a ab ]
43
+ new_dod = Dodecaphony::Row.new tone_row
44
+
45
+ expect(new_dod.p1).to eq %w[ b c c# d eb gb f e ab g bb a ]
46
+ end
47
+
48
+ it "can give p7" do
49
+ tone_row = Dodecaphony::Row.new %w[ d c# a b- f eb e c ab g f# b ]
50
+
51
+ expect(tone_row.p7).to eq %w[ a ab e f c b- b g eb d c# f# ]
52
+ end
53
+
54
+ it "can give i0" do
55
+ tone_row = Dodecaphony::Row.new %w[ A ab a# b c c# d eb e f gb g ]
56
+
57
+ expect(tone_row.i0).to eq %w[ A a# ab g gb f e eb d c# c b ]
58
+ end
59
+
60
+ it "can give i8" do
61
+ tone_row = Dodecaphony::Row.new %w[ A ab a# b c c# d eb e f gb g ]
62
+
63
+ expect(tone_row.i8).to eq %w[ f gb e eb d c# c b a# A ab g ]
64
+ end
65
+
66
+ it "can give r0" do
67
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
68
+
69
+ expect(tone_row.r0).to eq %w[ eb c c# d bb b f e ab g f# a ]
70
+ end
71
+
72
+ it "can give r9" do
73
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
74
+
75
+ expect(tone_row.r9).to eq %w[ c a bb b g ab d c# f e eb f# ]
76
+ end
77
+
78
+ it "can give ri0" do
79
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
80
+
81
+ expect(tone_row.ri0).to eq %w[eb f# f e ab g c# d bb b c a ]
82
+ end
83
+
84
+ it "can give ri11" do
85
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
86
+
87
+ expect(tone_row.ri10).to eq %w[ c# e eb d f# f b c ab a bb g ]
88
+ end
89
+
90
+ it "converts to a reasonable string" do
91
+ tone_row = Dodecaphony::Row.new %w[ a f# g ab e f b bb d c# c eb ]
92
+
93
+ expect(tone_row.to_s).to eq "a f# g ab e f b bb d c# c eb"
94
+ end
95
+
96
+ end
@@ -0,0 +1,54 @@
1
+ require 'pitch'
2
+
3
+ describe Dodecaphony::Pitch do
4
+
5
+ it "can tell you its pitch name" do
6
+ pitch = Dodecaphony::Pitch.new "A"
7
+
8
+ expect(pitch.name).to eq "A"
9
+ end
10
+
11
+ it "can tell you its distance from another pitch" do
12
+ first_pitch = Dodecaphony::Pitch.new "d"
13
+ second_pitch = Dodecaphony::Pitch.new "f"
14
+
15
+ expect(first_pitch.distance_from second_pitch).to eq 3
16
+ end
17
+
18
+ it "responds with the ascending distance, not an inversion for length" do
19
+ first_pitch = Dodecaphony::Pitch.new "f"
20
+ second_pitch = Dodecaphony::Pitch.new "e"
21
+
22
+ expect(first_pitch.distance_from second_pitch).to eq 11
23
+ end
24
+
25
+ it "understands unusual pitch names" do
26
+ first_pitch = Dodecaphony::Pitch.new "fb"
27
+ second_pitch = Dodecaphony::Pitch.new "A+++"
28
+
29
+ expect(first_pitch.distance_from second_pitch).to eq 8
30
+ end
31
+
32
+ it "understands more unusual pitch names" do
33
+ first_pitch = Dodecaphony::Pitch.new "g#####"
34
+ second_pitch = Dodecaphony::Pitch.new "C"
35
+
36
+ expect(first_pitch.distance_from second_pitch).to eq 0
37
+ end
38
+
39
+ it "can respell pitch as a normalized sharp" do
40
+ pitch = Dodecaphony::Pitch.new "G++++++"
41
+
42
+ expect(pitch.spell_as_sharp).to eq "C#"
43
+ end
44
+
45
+ it "can respell pitch as a normalized flat" do
46
+ pitch = Dodecaphony::Pitch.new "c###"
47
+
48
+ expect(pitch.spell_as_flat).to eq "Eb"
49
+ end
50
+
51
+ it "raises an error with an invalid pitch name" do
52
+ expect {Dodecaphony::Pitch.new "H"}.to raise_error(ArgumentError, 'invalid pitch name')
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dodecaphony
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kevin McGladdery
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.1
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.1
33
+ description: A gem for calculating twelve-tone rows
34
+ email:
35
+ - kevin.mcgladdery@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - LICENSE
41
+ - Readme.md
42
+ - lib/dodecaphony.rb
43
+ - lib/pitch.rb
44
+ - spec/dodecaphony_spec.rb
45
+ - spec/pitch_spec.rb
46
+ homepage: http://github.com/runkmc/dodecaphony
47
+ licenses:
48
+ - MIT
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 2.3.0
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Tone Row Calculator
70
+ test_files:
71
+ - spec/dodecaphony_spec.rb
72
+ - spec/pitch_spec.rb