post_tonal 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/CHANGELOG.md +14 -0
- data/README.md +8 -0
- data/Rakefile +8 -0
- data/lib/post_tonal/note_parser.rb +30 -0
- data/lib/post_tonal/pitch_class.rb +37 -0
- data/lib/post_tonal/pitch_class_interval.rb +32 -0
- data/lib/post_tonal/pitch_class_set.rb +145 -0
- data/lib/post_tonal/pitch_interval.rb +19 -0
- data/lib/post_tonal/version.rb +3 -0
- data/lib/post_tonal.rb +4 -0
- data/post_tonal.gemspec +21 -0
- data/test/test_helper.rb +3 -0
- data/test/unit/note_parser_test.rb +56 -0
- data/test/unit/pitch_class_interval_test.rb +38 -0
- data/test/unit/pitch_class_set_test.rb +159 -0
- data/test/unit/pitch_class_test.rb +49 -0
- metadata +113 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#CHANGELOG
|
2
|
+
|
3
|
+
####October 21, 2012 [0.1.0.pre]
|
4
|
+
-Implement transposition in PitchClassSet
|
5
|
+
-Change normal_form, inversion, and transpose so that they each return a PitchClassSet
|
6
|
+
|
7
|
+
####October 20, 2012 [0.0.3.pre]
|
8
|
+
-Implement inversion in PitchClassSet
|
9
|
+
|
10
|
+
####October 16, 2012 [0.0.2.pre]
|
11
|
+
-Implement normal form in PitchClassSet
|
12
|
+
|
13
|
+
####October 15, 2012 [0.0.1.pre]
|
14
|
+
- Create PitchClass, PitchClassSet, PitchClassInterval, PitchInterval, and NoteParser
|
data/README.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
PostTonal
|
2
|
+
==========
|
3
|
+
|
4
|
+
PostTonal is a Ruby library for analyzing sets of musical pitches. It's based on [pitch class set theory](http://en.wikipedia.org/wiki/Set_theory_\(music\) "Pitch class set theory") pioneered by Allen Forte.
|
5
|
+
|
6
|
+
Work In Progress: Detailed usage documentation to come soon.
|
7
|
+
|
8
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module PostTonal
|
2
|
+
class NoteParser
|
3
|
+
|
4
|
+
NAME_TO_INT = { :'b#' => 0, :'c' => 0,
|
5
|
+
:'c#' => 1, :'db' => 1,
|
6
|
+
:'d' => 2,
|
7
|
+
:'d#' => 3, :'eb' => 3,
|
8
|
+
:'e' => 4, :'fb' => 4,
|
9
|
+
:'e#' => 5, :'f' => 5,
|
10
|
+
:'f#' => 6, :'gb' => 6,
|
11
|
+
:'g' => 7,
|
12
|
+
:'g#' => 8, :'ab' => 8,
|
13
|
+
:'a' => 9,
|
14
|
+
:'a#' => 10, :'bb' => 10,
|
15
|
+
:'b' => 11, :'cb' => 11}
|
16
|
+
|
17
|
+
INT_TO_NAME = ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b']
|
18
|
+
INT_TO_NAME_FLAT = ['c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'bb', 'b']
|
19
|
+
|
20
|
+
def self.integer_value(name_or_integer)
|
21
|
+
return name_or_integer.abs % 12 if name_or_integer.class == Fixnum
|
22
|
+
return NAME_TO_INT[name_or_integer.downcase.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.note_name(name_or_integer)
|
26
|
+
return INT_TO_NAME[(name_or_integer.abs % 12)] if name_or_integer.class == Fixnum
|
27
|
+
return name_or_integer.downcase if NAME_TO_INT.has_key?(name_or_integer.downcase.to_sym)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module PostTonal
|
2
|
+
class PitchClass
|
3
|
+
|
4
|
+
require 'post_tonal/note_parser'
|
5
|
+
|
6
|
+
# value - The integer value of a pitch class, with C as 0.
|
7
|
+
# octave - A relative marker denoting the octave of the pitch class. Defaults to 0.
|
8
|
+
attr_reader :value, :octave
|
9
|
+
|
10
|
+
def initialize(note_name_or_integer, octave = 0)
|
11
|
+
@value = NoteParser.integer_value(note_name_or_integer)
|
12
|
+
@octave = octave
|
13
|
+
end
|
14
|
+
|
15
|
+
def value=(val)
|
16
|
+
v = val
|
17
|
+
v -= 12 while v > 11
|
18
|
+
v += 12 while v < 0
|
19
|
+
|
20
|
+
@value = v
|
21
|
+
|
22
|
+
@value
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"[#{@value}, oct.#{@octave}]"
|
27
|
+
end
|
28
|
+
|
29
|
+
def eql?(pitch_class)
|
30
|
+
return pitch_class.value == @value && pitch_class.octave == @octave
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(pitch_class)
|
34
|
+
eql? pitch_class
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PostTonal
|
2
|
+
class PitchClassInterval
|
3
|
+
|
4
|
+
attr_reader :pitch1, :pitch2, :ordered, :unordered
|
5
|
+
|
6
|
+
def initialize(pitch1, pitch2)
|
7
|
+
@pitch1 = pitch1
|
8
|
+
@pitch2 = pitch2
|
9
|
+
|
10
|
+
@ordered = calculate_ordered(pitch1, pitch2)
|
11
|
+
@unordered = calculate_unordered(pitch1, pitch2)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def calculate_ordered(p1, p2)
|
17
|
+
pitches = [p1, p2]
|
18
|
+
|
19
|
+
if pitches[0].octave != pitches[1].octave
|
20
|
+
pitches = pitches.sort { |x, y| y.octave <=> x.octave }
|
21
|
+
else
|
22
|
+
pitches = pitches.sort { |x, y| y.value <=> x.value }
|
23
|
+
end
|
24
|
+
|
25
|
+
(pitches[0].value + (12 * pitches[0].octave)) - (pitches[1].value + (12 * pitches[1].octave))
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate_unordered(p1, p2)
|
29
|
+
(p1.value - p2.value).abs > 6 ? 12 - (p1.value - p2.value).abs : (p1.value - p2.value).abs
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module PostTonal
|
2
|
+
class PitchClassSet
|
3
|
+
|
4
|
+
require 'post_tonal/pitch_class'
|
5
|
+
|
6
|
+
attr_reader :pitch_classes
|
7
|
+
|
8
|
+
def initialize(pitch_classes = nil)
|
9
|
+
@pitch_classes = pitch_classes || []
|
10
|
+
@normalized_pitch_classes = pitch_classes ? self.class.to_normal_form(@pitch_classes) : []
|
11
|
+
@inverted_pitch_classes = pitch_classes ? invert(@pitch_classes) : []
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add a pitch to the pitch class set
|
15
|
+
#
|
16
|
+
# The reason you are adding pitches as opposed to pitch classes
|
17
|
+
# is that you may want to get the ordered intervals of the set.
|
18
|
+
# Therefore, you may pass an optional octave value, which is an
|
19
|
+
# arbitrary marker starting at 0 representing the default octave.
|
20
|
+
# Non-zero values are calculated relative to octave 0.
|
21
|
+
#
|
22
|
+
# note_name_or_integer may be the name of the note (e.g. A-G, with #
|
23
|
+
# and lowercase b representing sharps and flats, respectively), or the
|
24
|
+
# integer value of the pitch class (e.g. 0-11, or T and E for eleven
|
25
|
+
# and twelve, respectively)
|
26
|
+
def add_pitch(note_name_or_integer, octave = 0)
|
27
|
+
|
28
|
+
pc = PitchClass.new(note_name_or_integer, octave)
|
29
|
+
|
30
|
+
if !@pitch_classes.any? { |p| p.value == pc.value && p.octave == pc.octave }
|
31
|
+
@pitch_classes << pc
|
32
|
+
@normalized_pitch_classes = self.class.to_normal_form(@pitch_classes)
|
33
|
+
@inverted_pitch_classes = invert(@pitch_classes)
|
34
|
+
end
|
35
|
+
|
36
|
+
pc
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql?(pitch_class_set)
|
40
|
+
return false if @pitch_classes.size != pitch_class_set.pitch_classes.size
|
41
|
+
|
42
|
+
@normalized_pitch_classes.each_with_index do |pitch_class, i|
|
43
|
+
return false if !pitch_class_set.normal_form.pitch_classes[i].eql?(pitch_class)
|
44
|
+
end
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(pitch_class_set)
|
50
|
+
eql? pitch_class_set
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
"PitchClassSet: #{@pitch_classes}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the normal form of an array of pitch classes
|
58
|
+
def self.to_normal_form(pitch_classes)
|
59
|
+
normal = pitch_classes.sort { |x, y| x.value <=> y.value }
|
60
|
+
|
61
|
+
newLen = normal.last.value - normal.first.value
|
62
|
+
newLen += 12 if newLen < 0
|
63
|
+
newLen += 12 if newLen == 1 && normal.size > 2
|
64
|
+
|
65
|
+
shortest = {:array => normal.dup, :length => newLen}
|
66
|
+
|
67
|
+
0.upto(normal.size - 1) do |q|
|
68
|
+
# Rotate
|
69
|
+
normal.push normal.shift
|
70
|
+
|
71
|
+
newLen = normal.last.value - normal.first.value
|
72
|
+
newLen += 12 if newLen < 0
|
73
|
+
newLen += 12 if newLen == 1 && normal.size > 2
|
74
|
+
|
75
|
+
if newLen < shortest[:length]
|
76
|
+
shortest = {:array => normal.dup, :length => newLen}
|
77
|
+
elsif newLen == shortest[:length]
|
78
|
+
(normal.size - 1).downto(q) do |r|
|
79
|
+
|
80
|
+
newLen = normal[r].value - normal.first.value
|
81
|
+
newLen += 12 if newLen < 0
|
82
|
+
newLen += 12 if newLen == 1 && normal.size > 2
|
83
|
+
|
84
|
+
sNewLen = shortest[:array][r].value - shortest[:array].first.value
|
85
|
+
sNewLen += 12 if sNewLen < 0
|
86
|
+
sNewLen += 12 if sNewLen == 1 && normal.size > 2
|
87
|
+
|
88
|
+
if newLen < sNewLen
|
89
|
+
shortest = {:array => normal.dup, :length => newLen}
|
90
|
+
break
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
shortest[:array]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns a PitchClassSet of the inversino of the current PitchClassSet
|
100
|
+
def inversion
|
101
|
+
self.class.new(@inverted_pitch_classes)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a PitchClassSet of the current PitchClassSet in normal form
|
105
|
+
def normal_form
|
106
|
+
self.class.new(@normalized_pitch_classes)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Transposes the set by a degree (integer)
|
110
|
+
# Returns PitchClassSet of the transposed set
|
111
|
+
def transpose(degree)
|
112
|
+
transposed = self.class.new
|
113
|
+
|
114
|
+
@pitch_classes.each do |pitch_class|
|
115
|
+
oct = pitch_class.octave
|
116
|
+
val = pitch_class.value
|
117
|
+
|
118
|
+
val += degree
|
119
|
+
|
120
|
+
oct += val / 12
|
121
|
+
oct -= 1 if val < 0 && val % 12 == 0
|
122
|
+
|
123
|
+
transposed.add_pitch(val, oct)
|
124
|
+
end
|
125
|
+
|
126
|
+
transposed
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Inverts an array of pitch classes. The PitchClass attribute octave may become invalid after inversion.
|
132
|
+
# Returns a PitchClassSet of the inverted pitch classes
|
133
|
+
def invert(pitch_classes)
|
134
|
+
inverted = []
|
135
|
+
|
136
|
+
pitch_classes.each do |pitch_class|
|
137
|
+
pc = PitchClass.new(12 - pitch_class.value, pitch_class.octave)
|
138
|
+
inverted << pc
|
139
|
+
end
|
140
|
+
|
141
|
+
inverted
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PostTonal
|
2
|
+
class PitchInterval
|
3
|
+
|
4
|
+
attr_reader :pitch1, :pitch2
|
5
|
+
|
6
|
+
def initialize(pitch1, pitch2)
|
7
|
+
@pitch1 = pitch1
|
8
|
+
@pitch2 = pitch2
|
9
|
+
end
|
10
|
+
|
11
|
+
def ordered
|
12
|
+
pitch1.int_value - pitch2.int_value
|
13
|
+
end
|
14
|
+
|
15
|
+
def unordered
|
16
|
+
(pitch1.int_value - pitch2.int_value).abs
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/post_tonal.rb
ADDED
data/post_tonal.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "post_tonal/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'post_tonal'
|
7
|
+
s.version = PostTonal::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.author = 'Eric Rubio'
|
10
|
+
s.email = 'penmanglewood@fastmail.fm'
|
11
|
+
s.summary = %q{Pitch-class set analysis library}
|
12
|
+
s.description = %q{Ruby library for analyzing pitch-class sets according to post-tonal theory}
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- test/{functional,unit}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency "shoulda", ">= 2.1.1"
|
19
|
+
s.add_development_dependency "mocha", ">= 0.9.5"
|
20
|
+
s.add_development_dependency "rake"
|
21
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class NoteParserTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_valid_int_values
|
6
|
+
(0..11).each do |i|
|
7
|
+
@p = PostTonal::NoteParser.integer_value(i)
|
8
|
+
assert_equal(i, @p, "0-11 should be valid")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_mod12_int_values
|
13
|
+
(12..23).each do |i|
|
14
|
+
@p = PostTonal::NoteParser.integer_value(i)
|
15
|
+
assert_equal(i-12, @p, "12-23 should be valid")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_negative_int_values
|
20
|
+
(-11..0).each do |i|
|
21
|
+
@p = PostTonal::NoteParser.integer_value(i)
|
22
|
+
assert_equal(i.abs, @p, "(-11)-0 should count as 11-0")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_valid_str_values
|
27
|
+
PostTonal::NoteParser::NAME_TO_INT.each do |k, v|
|
28
|
+
@p = PostTonal::NoteParser.integer_value(k)
|
29
|
+
@q = PostTonal::NoteParser.integer_value(k.upcase)
|
30
|
+
assert_equal(v, @p, "Note names should be valid 1")
|
31
|
+
assert_equal(v, @q, "Note names should be valid 2")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_invalid_str_values
|
36
|
+
('h'..'z').each do |z|
|
37
|
+
@p = PostTonal::NoteParser.integer_value(z)
|
38
|
+
assert_equal(nil, @p, "h-z should be invalid")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_note_names
|
43
|
+
assert_equal('c', PostTonal::NoteParser.note_name(0), '0 should be c')
|
44
|
+
assert_equal('c#', PostTonal::NoteParser.note_name(1), '1 should be c#')
|
45
|
+
assert_equal('d', PostTonal::NoteParser.note_name(2), '2 should be d')
|
46
|
+
assert_equal('d#', PostTonal::NoteParser.note_name(3), '3 should be d#')
|
47
|
+
assert_equal('e', PostTonal::NoteParser.note_name(4), '4 should be e')
|
48
|
+
assert_equal('f', PostTonal::NoteParser.note_name(5), '5 should be f')
|
49
|
+
assert_equal('f#', PostTonal::NoteParser.note_name(6), '6 should be f#')
|
50
|
+
assert_equal('g', PostTonal::NoteParser.note_name(7), '7 should be g')
|
51
|
+
assert_equal('g#', PostTonal::NoteParser.note_name(8), '8 should be g#')
|
52
|
+
assert_equal('a', PostTonal::NoteParser.note_name(9), '9 should be a')
|
53
|
+
assert_equal('a#', PostTonal::NoteParser.note_name(10), '10 should be a#')
|
54
|
+
assert_equal('b', PostTonal::NoteParser.note_name(11), '11 should be b')
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class PitchClassIntervalTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_unordered_intervals
|
6
|
+
(0..11).each do |i|
|
7
|
+
@p1 = PostTonal::PitchClass.new(i)
|
8
|
+
(0..11).each do |j|
|
9
|
+
@p2 = PostTonal::PitchClass.new(j)
|
10
|
+
@interval = PostTonal::PitchClassInterval.new(@p1, @p2)
|
11
|
+
expected = (@p1.value - @p2.value).abs > 6 ? 12 - (@p1.value - @p2.value).abs : (@p1.value - @p2.value).abs
|
12
|
+
assert_equal(expected, @interval.unordered, "p1: #{i}, p2: #{j}") if i != j
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_ordered_intervals
|
18
|
+
|
19
|
+
#Same octave ordered intervals
|
20
|
+
(0..11).each do |i|
|
21
|
+
@p1 = PostTonal::PitchClass.new(i)
|
22
|
+
(0..11).each do |j|
|
23
|
+
@p2 = PostTonal::PitchClass.new(j)
|
24
|
+
@interval = PostTonal::PitchClassInterval.new(@p1, @p2)
|
25
|
+
expected = j > i ? @p2.value - @p1.value : @p1.value - @p2.value
|
26
|
+
assert_equal(expected, @interval.ordered, "p1: #{i}, p2: #{j}") if i != j
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#Cross-octave ordered intervals
|
31
|
+
@p3 = PostTonal::PitchClass.new(11)
|
32
|
+
(0...11).each do |j|
|
33
|
+
@p4 = PostTonal::PitchClass.new(j, 1)
|
34
|
+
@interval = PostTonal::PitchClassInterval.new(@p3, @p4)
|
35
|
+
assert_equal(j+1, @interval.ordered, "Cross-Octave p1: 11, p2: #{j}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class PitchClassSetTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@pitch_class_set = PostTonal::PitchClassSet.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
@pitch_class_set = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_duplicate_assignment
|
14
|
+
@pitch_class_set.add_pitch(0)
|
15
|
+
@pitch_class_set.add_pitch(0)
|
16
|
+
@pitch_class_set.add_pitch(0)
|
17
|
+
|
18
|
+
assert_equal(1, @pitch_class_set.pitch_classes.size)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_nonduplicate_assignment
|
22
|
+
@pitch_class_set.add_pitch(0)
|
23
|
+
@pitch_class_set.add_pitch(1)
|
24
|
+
@pitch_class_set.add_pitch(0, 1)
|
25
|
+
|
26
|
+
assert_equal(3, @pitch_class_set.pitch_classes.size)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_equality
|
30
|
+
@pitch_class_set.add_pitch(0)
|
31
|
+
@pitch_class_set.add_pitch(1)
|
32
|
+
@pitch_class_set.add_pitch(2)
|
33
|
+
|
34
|
+
@p1 = PostTonal::PitchClassSet.new
|
35
|
+
@p1.add_pitch(0)
|
36
|
+
@p1.add_pitch(1)
|
37
|
+
@p1.add_pitch(2)
|
38
|
+
|
39
|
+
assert(@pitch_class_set.eql?(@p1), "eql? should be true")
|
40
|
+
assert(@pitch_class_set == @p1, "== should be true")
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_normal_form
|
44
|
+
|
45
|
+
@p1 = PostTonal::PitchClassSet.new
|
46
|
+
@p1.add_pitch(0)
|
47
|
+
@p1.add_pitch(1)
|
48
|
+
@p1.add_pitch(2)
|
49
|
+
|
50
|
+
[0,1,2].each do |n|
|
51
|
+
assert_equal(@p1.pitch_classes[n], @p1.normal_form.pitch_classes[n])
|
52
|
+
end
|
53
|
+
|
54
|
+
@p2 = PostTonal::PitchClassSet.new
|
55
|
+
@p2.add_pitch(0)
|
56
|
+
@p2.add_pitch(10)
|
57
|
+
@p2.add_pitch(11)
|
58
|
+
|
59
|
+
[10,11,0].each_with_index do |n, i|
|
60
|
+
assert_equal(n, @p2.normal_form.pitch_classes[i].value)
|
61
|
+
end
|
62
|
+
|
63
|
+
@p3 = PostTonal::PitchClassSet.new
|
64
|
+
@p3.add_pitch(0)
|
65
|
+
@p3.add_pitch(3)
|
66
|
+
@p3.add_pitch(4)
|
67
|
+
@p3.add_pitch(9)
|
68
|
+
|
69
|
+
[9,0,3,4].each_with_index do |n, i|
|
70
|
+
assert_equal(n, @p3.normal_form.pitch_classes[i].value)
|
71
|
+
end
|
72
|
+
|
73
|
+
@p4 = PostTonal::PitchClassSet.new
|
74
|
+
@p4.add_pitch(0)
|
75
|
+
@p4.add_pitch(3)
|
76
|
+
@p4.add_pitch(4)
|
77
|
+
@p4.add_pitch(8)
|
78
|
+
|
79
|
+
[0,3,4,8].each_with_index do |n, i|
|
80
|
+
assert_equal(n, @p4.normal_form.pitch_classes[i].value)
|
81
|
+
end
|
82
|
+
|
83
|
+
@p5 = PostTonal::PitchClassSet.new
|
84
|
+
@p5.add_pitch(0)
|
85
|
+
@p5.add_pitch(1)
|
86
|
+
@p5.add_pitch(10)
|
87
|
+
@p5.add_pitch(11)
|
88
|
+
|
89
|
+
[10,11,0,1].each_with_index do |n, i|
|
90
|
+
assert_equal(n, @p5.normal_form.pitch_classes[i].value)
|
91
|
+
end
|
92
|
+
|
93
|
+
@p6 = PostTonal::PitchClassSet.new
|
94
|
+
@p6.add_pitch(0)
|
95
|
+
@p6.add_pitch(1)
|
96
|
+
@p6.add_pitch(8)
|
97
|
+
@p6.add_pitch(4)
|
98
|
+
|
99
|
+
[0,1,4,8].each_with_index do |n, i|
|
100
|
+
assert_equal(n, @p6.normal_form.pitch_classes[i].value)
|
101
|
+
end
|
102
|
+
|
103
|
+
@p7 = PostTonal::PitchClassSet.new
|
104
|
+
@p7.add_pitch(0)
|
105
|
+
@p7.add_pitch(3)
|
106
|
+
@p7.add_pitch(6)
|
107
|
+
@p7.add_pitch(9)
|
108
|
+
|
109
|
+
[0,3,6,9].each_with_index do |n, i|
|
110
|
+
assert_equal(n, @p7.normal_form.pitch_classes[i].value)
|
111
|
+
end
|
112
|
+
|
113
|
+
@p7 = PostTonal::PitchClassSet.new
|
114
|
+
@p7.add_pitch(0)
|
115
|
+
@p7.add_pitch(6)
|
116
|
+
@p7.add_pitch(7)
|
117
|
+
|
118
|
+
[6,7,0].each_with_index do |n, i|
|
119
|
+
assert_equal(n, @p7.normal_form.pitch_classes[i].value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_inversion
|
124
|
+
@p1 = PostTonal::PitchClassSet.new
|
125
|
+
0.upto(11) do |i|
|
126
|
+
@p1.add_pitch(i)
|
127
|
+
end
|
128
|
+
|
129
|
+
@p1i = PostTonal::PitchClassSet.new
|
130
|
+
@p1i.add_pitch(0)
|
131
|
+
11.downto(1) do |i|
|
132
|
+
@p1i.add_pitch(i)
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_equal(@p1i, @p1.inversion, "Should be inverted")
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_transposition
|
139
|
+
@p1 = PostTonal::PitchClassSet.new
|
140
|
+
0.upto(11) do |i|
|
141
|
+
@p1.add_pitch(i)
|
142
|
+
end
|
143
|
+
|
144
|
+
#transposition degree
|
145
|
+
0.upto(200) do |t|
|
146
|
+
@p1t = PostTonal::PitchClassSet.new
|
147
|
+
t.upto(t+11) do |i|
|
148
|
+
oct = 0
|
149
|
+
oct = i / 12
|
150
|
+
oct -= 1 if i < 0 && i % 12 == 0
|
151
|
+
|
152
|
+
@p1t.add_pitch(i, oct)
|
153
|
+
end
|
154
|
+
|
155
|
+
assert_equal(@p1t, @p1.transpose(t), "Should transpose #{t} degrees")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class PitchClassTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_valid_int_values
|
6
|
+
(0..11).each do |i|
|
7
|
+
@p = PostTonal::PitchClass.new(i)
|
8
|
+
assert_equal(i, @p.value, "0-11 should be valid")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_mod12_int_values
|
13
|
+
(12..23).each do |i|
|
14
|
+
@p = PostTonal::PitchClass.new(i)
|
15
|
+
assert_equal(i-12, @p.value, "12-23 should be valid")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_negative_int_values
|
20
|
+
(-11..0).each do |i|
|
21
|
+
@p = PostTonal::PitchClass.new(i)
|
22
|
+
assert_equal(i.abs, @p.value, "(-11)-0 should count as 11-0")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_valid_str_values
|
27
|
+
PostTonal::NoteParser::NAME_TO_INT.each do |k, v|
|
28
|
+
@p = PostTonal::PitchClass.new(k)
|
29
|
+
@q = PostTonal::PitchClass.new(k.upcase)
|
30
|
+
assert_equal(v, @p.value, "Note names should be valid 1")
|
31
|
+
assert_equal(v, @q.value, "Note names should be valid 2")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_invalid_str_values
|
36
|
+
('h'..'z').each do |z|
|
37
|
+
@p = PostTonal::PitchClass.new(z)
|
38
|
+
assert_equal(nil, @p.value, "h-z should be invalid")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_octave
|
43
|
+
(0..11).each do |i|
|
44
|
+
@p = PostTonal::PitchClass.new(i, i)
|
45
|
+
assert_equal(i, @p.octave, "0-11 should be valid octave values")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: post_tonal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Eric Rubio
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: shoulda
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.1.1
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.1.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mocha
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.5
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.5
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Ruby library for analyzing pitch-class sets according to post-tonal theory
|
63
|
+
email: penmanglewood@fastmail.fm
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- .gitignore
|
69
|
+
- CHANGELOG.md
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- lib/post_tonal.rb
|
73
|
+
- lib/post_tonal/note_parser.rb
|
74
|
+
- lib/post_tonal/pitch_class.rb
|
75
|
+
- lib/post_tonal/pitch_class_interval.rb
|
76
|
+
- lib/post_tonal/pitch_class_set.rb
|
77
|
+
- lib/post_tonal/pitch_interval.rb
|
78
|
+
- lib/post_tonal/version.rb
|
79
|
+
- post_tonal.gemspec
|
80
|
+
- test/test_helper.rb
|
81
|
+
- test/unit/note_parser_test.rb
|
82
|
+
- test/unit/pitch_class_interval_test.rb
|
83
|
+
- test/unit/pitch_class_set_test.rb
|
84
|
+
- test/unit/pitch_class_test.rb
|
85
|
+
homepage:
|
86
|
+
licenses: []
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>'
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.3.1
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.8.24
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Pitch-class set analysis library
|
109
|
+
test_files:
|
110
|
+
- test/unit/note_parser_test.rb
|
111
|
+
- test/unit/pitch_class_interval_test.rb
|
112
|
+
- test/unit/pitch_class_set_test.rb
|
113
|
+
- test/unit/pitch_class_test.rb
|