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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/Readme.md +49 -0
- data/lib/dodecaphony.rb +120 -0
- data/lib/pitch.rb +73 -0
- data/spec/dodecaphony_spec.rb +96 -0
- data/spec/pitch_spec.rb +54 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -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.
|
data/Readme.md
ADDED
@@ -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
|
+
|
data/lib/dodecaphony.rb
ADDED
@@ -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
|
data/lib/pitch.rb
ADDED
@@ -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
|
data/spec/pitch_spec.rb
ADDED
@@ -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
|