post_tonal 0.1.0.pre
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.
- 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
|