mtk 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +9 -0
- data/INTRO.md +73 -0
- data/LICENSE.txt +27 -0
- data/README.md +93 -18
- data/Rakefile +13 -1
- data/examples/crescendo.rb +20 -0
- data/examples/dynamic_pattern.rb +39 -0
- data/examples/play_midi.rb +19 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/tone_row_melody.rb +21 -0
- data/lib/mtk/_constants/durations.rb +80 -0
- data/lib/mtk/_constants/intensities.rb +81 -0
- data/lib/mtk/{constants → _constants}/intervals.rb +10 -1
- data/lib/mtk/_constants/pitch_classes.rb +35 -0
- data/lib/mtk/_constants/pitches.rb +49 -0
- data/lib/mtk/{numeric_extensions.rb → _numeric_extensions.rb} +0 -0
- data/lib/mtk/event.rb +14 -5
- data/lib/mtk/helper/collection.rb +114 -0
- data/lib/mtk/helper/event_builder.rb +85 -0
- data/lib/mtk/{constants → helper}/pseudo_constants.rb +7 -6
- data/lib/mtk/lang/grammar.rb +17 -0
- data/lib/mtk/lang/mtk_grammar.citrus +60 -0
- data/lib/mtk/midi/file.rb +10 -15
- data/lib/mtk/midi/jsound_input.rb +68 -0
- data/lib/mtk/midi/jsound_output.rb +80 -0
- data/lib/mtk/note.rb +22 -3
- data/lib/mtk/pattern/abstract_pattern.rb +132 -0
- data/lib/mtk/pattern/choice.rb +25 -9
- data/lib/mtk/pattern/cycle.rb +51 -0
- data/lib/mtk/pattern/enumerator.rb +26 -0
- data/lib/mtk/pattern/function.rb +46 -0
- data/lib/mtk/pattern/lines.rb +60 -0
- data/lib/mtk/pattern/palindrome.rb +42 -0
- data/lib/mtk/pattern/sequence.rb +15 -50
- data/lib/mtk/pitch.rb +45 -6
- data/lib/mtk/pitch_class.rb +36 -35
- data/lib/mtk/pitch_class_set.rb +46 -14
- data/lib/mtk/pitch_set.rb +20 -31
- data/lib/mtk/sequencer/abstract_sequencer.rb +85 -0
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +29 -0
- data/lib/mtk/sequencer/step_sequencer.rb +26 -0
- data/lib/mtk/timeline.rb +75 -22
- data/lib/mtk/transform/invertible.rb +15 -0
- data/lib/mtk/{util → transform}/mappable.rb +6 -2
- data/lib/mtk/transform/set_theory_operations.rb +34 -0
- data/lib/mtk/transform/transposable.rb +14 -0
- data/lib/mtk.rb +56 -22
- data/spec/mtk/_constants/durations_spec.rb +118 -0
- data/spec/mtk/{constants/dynamics_spec.rb → _constants/intensities_spec.rb} +48 -17
- data/spec/mtk/{constants → _constants}/intervals_spec.rb +21 -0
- data/spec/mtk/_constants/pitch_classes_spec.rb +58 -0
- data/spec/mtk/_constants/pitches_spec.rb +52 -0
- data/spec/mtk/{numeric_extensions_spec.rb → _numeric_extensions_spec.rb} +0 -0
- data/spec/mtk/event_spec.rb +19 -0
- data/spec/mtk/helper/collection_spec.rb +291 -0
- data/spec/mtk/helper/event_builder_spec.rb +92 -0
- data/spec/mtk/helper/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/grammar_spec.rb +100 -0
- data/spec/mtk/midi/file_spec.rb +41 -6
- data/spec/mtk/note_spec.rb +53 -3
- data/spec/mtk/pattern/abstract_pattern_spec.rb +45 -0
- data/spec/mtk/pattern/choice_spec.rb +89 -3
- data/spec/mtk/pattern/cycle_spec.rb +133 -0
- data/spec/mtk/pattern/function_spec.rb +133 -0
- data/spec/mtk/pattern/lines_spec.rb +93 -0
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +116 -0
- data/spec/mtk/pattern/palindrome_spec.rb +124 -0
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +47 -0
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +37 -0
- data/spec/mtk/pattern/sequence_spec.rb +128 -31
- data/spec/mtk/pitch_class_set_spec.rb +240 -7
- data/spec/mtk/pitch_class_spec.rb +84 -18
- data/spec/mtk/pitch_set_spec.rb +45 -10
- data/spec/mtk/pitch_spec.rb +59 -0
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +159 -0
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +49 -0
- data/spec/mtk/sequencer/step_sequencer_spec.rb +71 -0
- data/spec/mtk/timeline_spec.rb +118 -15
- data/spec/spec_helper.rb +4 -3
- metadata +59 -22
- data/lib/mtk/chord.rb +0 -47
- data/lib/mtk/constants/dynamics.rb +0 -56
- data/lib/mtk/constants/pitch_classes.rb +0 -18
- data/lib/mtk/constants/pitches.rb +0 -24
- data/lib/mtk/pattern/note_sequence.rb +0 -60
- data/lib/mtk/pattern/pitch_sequence.rb +0 -22
- data/lib/mtk/patterns.rb +0 -4
- data/spec/mtk/chord_spec.rb +0 -74
- data/spec/mtk/constants/pitch_classes_spec.rb +0 -35
- data/spec/mtk/constants/pitches_spec.rb +0 -23
- data/spec/mtk/pattern/note_sequence_spec.rb +0 -121
- data/spec/mtk/pattern/pitch_sequence_spec.rb +0 -47
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Pattern::Palindrome do
|
4
|
+
|
5
|
+
PALINDROME = MTK::Pattern::Palindrome
|
6
|
+
|
7
|
+
describe "#next" do
|
8
|
+
it "reverses direction when the ends of the list are reached" do
|
9
|
+
palindrome = PALINDROME.new [1,2,3,4]
|
10
|
+
nexts = []
|
11
|
+
12.times { nexts << palindrome.next }
|
12
|
+
nexts.should == [1,2,3, 4,3,2, 1,2,3, 4,3,2]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "reverses direction when the end of the list is reached, and repeats the end (first/last) elements" do
|
16
|
+
palindrome = PALINDROME.new [1,2,3,4], :repeat_ends => true
|
17
|
+
nexts = []
|
18
|
+
12.times { nexts << palindrome.next }
|
19
|
+
nexts.should == [1,2,3,4, 4,3,2,1, 1,2,3,4]
|
20
|
+
end
|
21
|
+
|
22
|
+
it "enumerates nested sequences" do
|
23
|
+
palindrome = PALINDROME.new [1, MTK::Pattern.Sequence(2,3), 4]
|
24
|
+
nexts = []
|
25
|
+
10.times { nexts << palindrome.next }
|
26
|
+
nexts.should == [1,2,3,4, 2,3,1, 2,3,4] # note sequence goes forward in both directions!
|
27
|
+
end
|
28
|
+
|
29
|
+
it "enumerates nested sequences, and repeats the last element" do
|
30
|
+
palindrome = PALINDROME.new [1, MTK::Pattern.Sequence(2,3), 4], :repeat_ends => true
|
31
|
+
nexts = []
|
32
|
+
9.times { nexts << palindrome.next }
|
33
|
+
nexts.should == [1,2,3,4, 4,2,3,1, 1] # note sequence goes forward in both directions!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#rewind" do
|
38
|
+
it "restarts the Palindrome" do
|
39
|
+
palindrome = PALINDROME.new [1,2,3,4]
|
40
|
+
5.times { palindrome.next }
|
41
|
+
palindrome.rewind
|
42
|
+
palindrome.next.should == 1
|
43
|
+
palindrome.next.should == 2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#repeat_ends?" do
|
48
|
+
it "is true if the :repeat_ends option is true" do
|
49
|
+
PALINDROME.new([], :repeat_ends => true).repeat_ends?.should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "is false if the :repeat_ends option is true" do
|
53
|
+
PALINDROME.new([], :repeat_ends => false).repeat_ends?.should be_false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#==" do
|
58
|
+
it "is false if the :repeat_ends options are different" do
|
59
|
+
PALINDROME.new([1,2,3], :repeat_ends => true).should_not == PALINDROME.new([1,2,3], :repeat_ends => false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
describe MTK::Pattern do
|
67
|
+
|
68
|
+
describe "#Palindrome" do
|
69
|
+
it "creates a Palindrome" do
|
70
|
+
MTK::Pattern.Palindrome(1,2,3).should be_a MTK::Pattern::Palindrome
|
71
|
+
end
|
72
|
+
|
73
|
+
it "sets #elements from the varargs" do
|
74
|
+
MTK::Pattern.Palindrome(1,2,3).elements.should == [1,2,3]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not set a type" do
|
78
|
+
MTK::Pattern.Palindrome(1,2,3).type.should be_nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#PitchPalindrome" do
|
83
|
+
it "creates a Palindrome" do
|
84
|
+
MTK::Pattern.PitchPalindrome(1,2,3).should be_a MTK::Pattern::Palindrome
|
85
|
+
end
|
86
|
+
|
87
|
+
it "sets #elements from the varargs" do
|
88
|
+
MTK::Pattern.PitchPalindrome(1,2,3).elements.should == [1,2,3]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "sets #type to :pitch" do
|
92
|
+
MTK::Pattern.PitchPalindrome([]).type.should == :pitch
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#IntensityPalindrome" do
|
97
|
+
it "creates a Palindrome" do
|
98
|
+
MTK::Pattern.IntensityPalindrome(1,2,3).should be_a MTK::Pattern::Palindrome
|
99
|
+
end
|
100
|
+
|
101
|
+
it "sets #elements from the varargs" do
|
102
|
+
MTK::Pattern.IntensityPalindrome(1,2,3).elements.should == [1,2,3]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "sets #type to :pitch" do
|
106
|
+
MTK::Pattern.IntensityPalindrome([]).type.should == :intensity
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#DurationPalindrome" do
|
111
|
+
it "creates a Palindrome" do
|
112
|
+
MTK::Pattern.DurationPalindrome(1,2,3).should be_a MTK::Pattern::Palindrome
|
113
|
+
end
|
114
|
+
|
115
|
+
it "sets #elements from the varargs" do
|
116
|
+
MTK::Pattern.DurationPalindrome(1,2,3).elements.should == [1,2,3]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "sets #type to :pitch" do
|
120
|
+
MTK::Pattern.DurationPalindrome([]).type.should == :duration
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Pattern::PitchCycle do
|
4
|
+
|
5
|
+
describe "#next" do
|
6
|
+
it "enumerates Pitches" do
|
7
|
+
cycle = Pattern::PitchCycle.new [C4, D4, E4]
|
8
|
+
cycle.next.should == C4
|
9
|
+
cycle.next.should == D4
|
10
|
+
cycle.next.should == E4
|
11
|
+
end
|
12
|
+
|
13
|
+
it "adds Numeric elements (intervals) to the previous pitch" do
|
14
|
+
cycle = Pattern::PitchCycle.new [C4, 1, 2, 3]
|
15
|
+
cycle.next.should == C4
|
16
|
+
cycle.next.should == C4+1
|
17
|
+
cycle.next.should == C4+1+2
|
18
|
+
cycle.next.should == C4+1+2+3
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns a Pitch when encountering a Pitch after another type" do
|
22
|
+
cycle = Pattern::PitchCycle.new [C4, 1, C4]
|
23
|
+
cycle.next
|
24
|
+
cycle.next
|
25
|
+
cycle.next.should == C4
|
26
|
+
end
|
27
|
+
|
28
|
+
it "goes to the nearest Pitch for any PitchClasses in the pitch list" do
|
29
|
+
cycle = Pattern::PitchCycle.new [C4, F, C, G, C]
|
30
|
+
cycle.next.should == C4
|
31
|
+
cycle.next.should == F4
|
32
|
+
cycle.next.should == C4
|
33
|
+
cycle.next.should == G3
|
34
|
+
cycle.next.should == C4
|
35
|
+
end
|
36
|
+
|
37
|
+
it "does not endlessly ascend or descend when alternating between two pitch classes a tritone apart" do
|
38
|
+
cycler = Pattern::PitchCycle.new [C4, Gb, C, Gb, C]
|
39
|
+
cycler.next.should == C4
|
40
|
+
cycler.next.should == Gb4
|
41
|
+
cycler.next.should == C4
|
42
|
+
cycler.next.should == Gb4
|
43
|
+
cycler.next.should == C4
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Pattern::PitchSequence do
|
4
|
+
|
5
|
+
let(:pitches) { [C4,D4,E4] }
|
6
|
+
let(:pitch_sequence) { Pattern::PitchSequence.new(pitches) }
|
7
|
+
|
8
|
+
|
9
|
+
describe ".from_pitch_classes" do
|
10
|
+
it "creates a pitch sequence from a list of pitch classes and starting point, selecting the nearest pitch to each pitch class" do
|
11
|
+
Pattern::PitchSequence.from_pitch_classes([C,G,B,Eb,D,C], D3).should == [C3,G2,B2,Eb3,D3,C3]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "defaults to a starting point of C4 (middle C)" do
|
15
|
+
Pattern::PitchSequence.from_pitch_classes([C]).should == [C4]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't travel within an octave above or below the starting point by default" do
|
19
|
+
Pattern::PitchSequence.from_pitch_classes([C,F,Bb,D,A,E,B]).should == [C4,F4,Bb4,D4,A3,E3,B3]
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allows max distance above or below the starting point to be set via the third argument" do
|
23
|
+
Pattern::PitchSequence.from_pitch_classes([C,F,Bb,D,A,E,B], C4, 6).should == [C4,F4,Bb3,D4,A3,E4,B3]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe MTK::Pattern do
|
31
|
+
|
32
|
+
it "converts the arguments to Pitches when possible" do
|
33
|
+
Pattern.PitchSequence(:C4,:D4,:E4).elements.should == [C4,D4,E4]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -2,53 +2,150 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe MTK::Pattern::Sequence do
|
4
4
|
|
5
|
-
|
6
|
-
let(:sequence) { Pattern::Sequence.new elements }
|
5
|
+
SEQUENCE = MTK::Pattern::Sequence
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
let(:elements) { [1,2,3] }
|
8
|
+
let(:sequence) { SEQUENCE.new(elements) }
|
9
|
+
|
10
|
+
it "is a MTK::Collection" do
|
11
|
+
sequence.should be_a MTK::Helper::Collection
|
12
|
+
# and now we won't test any other collection features here... see collection_spec
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".from_a" do
|
16
|
+
it "acts like .new" do
|
17
|
+
SEQUENCE.from_a(elements).should == sequence
|
13
18
|
end
|
19
|
+
end
|
14
20
|
|
15
|
-
|
21
|
+
describe "#elements" do
|
22
|
+
it "is the array the sequence was constructed with" do
|
23
|
+
sequence.elements.should == elements
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#next" do
|
28
|
+
it "enumerates the elements" do
|
29
|
+
nexts = []
|
16
30
|
elements.length.times do
|
17
|
-
sequence.next
|
31
|
+
nexts << sequence.next
|
18
32
|
end
|
19
|
-
|
33
|
+
nexts.should == elements
|
20
34
|
end
|
21
35
|
|
22
|
-
it "
|
23
|
-
|
24
|
-
sequence.next
|
25
|
-
|
36
|
+
it "raises StopIteration when the end of the Sequence is reached" do
|
37
|
+
elements.length.times{ sequence.next }
|
38
|
+
lambda{ sequence.next }.should raise_error(StopIteration)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should automatically break out of Kernel#loop" do
|
42
|
+
nexts = []
|
43
|
+
loop do # loop rescues StopIteration and exits the loop
|
44
|
+
nexts << sequence.next
|
45
|
+
end
|
46
|
+
nexts.should == elements
|
26
47
|
end
|
27
48
|
|
28
|
-
it "
|
29
|
-
|
30
|
-
sequence.
|
49
|
+
it "enumerates the elements in sub-sequences" do
|
50
|
+
sub_sequence = SEQUENCE.new [2,3]
|
51
|
+
sequence = SEQUENCE.new [1,sub_sequence,4]
|
52
|
+
nexts = []
|
53
|
+
loop { nexts << sequence.next }
|
54
|
+
nexts.should == [1,2,3,4]
|
31
55
|
end
|
32
56
|
|
33
|
-
it "
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
sequence.next
|
38
|
-
|
39
|
-
arg1.should == 5
|
40
|
-
arg2.should == return_5
|
57
|
+
it "skips over empty sub-sequences" do
|
58
|
+
sub_sequence = SEQUENCE.new []
|
59
|
+
sequence = SEQUENCE.new [1,sub_sequence,4]
|
60
|
+
nexts = []
|
61
|
+
loop { nexts << sequence.next }
|
62
|
+
nexts.should == [1,4]
|
41
63
|
end
|
64
|
+
|
42
65
|
end
|
43
66
|
|
44
|
-
describe "#
|
45
|
-
it "restarts the sequence" do
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
sequence.reset
|
67
|
+
describe "#rewind" do
|
68
|
+
it "restarts at the beginning of the sequence" do
|
69
|
+
loop { sequence.next }
|
70
|
+
sequence.rewind
|
50
71
|
sequence.next.should == elements.first
|
51
72
|
end
|
73
|
+
|
74
|
+
it "returns self, so it can be chained to #next" do
|
75
|
+
first = sequence.next
|
76
|
+
sequence.rewind.next.should == first
|
77
|
+
end
|
78
|
+
|
79
|
+
it "causes sub-sequences to start from the beginning when encountered again after #rewind" do
|
80
|
+
sub_sequence = SEQUENCE.new [2,3]
|
81
|
+
sequence = SEQUENCE.new [1,sub_sequence,4]
|
82
|
+
loop { sequence.next }
|
83
|
+
sequence.rewind
|
84
|
+
nexts = []
|
85
|
+
loop { nexts << sequence.next }
|
86
|
+
nexts.should == [1,2,3,4]
|
87
|
+
end
|
52
88
|
end
|
53
89
|
|
54
90
|
end
|
91
|
+
|
92
|
+
|
93
|
+
describe MTK::Pattern do
|
94
|
+
|
95
|
+
describe "#Sequence" do
|
96
|
+
it "creates a Sequence" do
|
97
|
+
MTK::Pattern.Sequence(1,2,3).should be_a MTK::Pattern::Sequence
|
98
|
+
end
|
99
|
+
|
100
|
+
it "sets #elements from the varargs" do
|
101
|
+
MTK::Pattern.Sequence(1,2,3).elements.should == [1,2,3]
|
102
|
+
end
|
103
|
+
|
104
|
+
it "does not set a type" do
|
105
|
+
MTK::Pattern.Sequence(1,2,3).type.should be_nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#PitchSequence" do
|
110
|
+
it "creates a Sequence" do
|
111
|
+
MTK::Pattern.PitchSequence(1,2,3).should be_a MTK::Pattern::Sequence
|
112
|
+
end
|
113
|
+
|
114
|
+
it "sets #elements from the varargs" do
|
115
|
+
MTK::Pattern.PitchSequence(1,2,3).elements.should == [1,2,3]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "sets #type to :pitch" do
|
119
|
+
MTK::Pattern.PitchSequence([]).type.should == :pitch
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#IntensitySequence" do
|
124
|
+
it "creates a Sequence" do
|
125
|
+
MTK::Pattern.IntensitySequence(1,2,3).should be_a MTK::Pattern::Sequence
|
126
|
+
end
|
127
|
+
|
128
|
+
it "sets #elements from the varargs" do
|
129
|
+
MTK::Pattern.IntensitySequence(1,2,3).elements.should == [1,2,3]
|
130
|
+
end
|
131
|
+
|
132
|
+
it "sets #type to :pitch" do
|
133
|
+
MTK::Pattern.IntensitySequence([]).type.should == :intensity
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#DurationSequence" do
|
138
|
+
it "creates a Sequence" do
|
139
|
+
MTK::Pattern.DurationSequence(1,2,3).should be_a MTK::Pattern::Sequence
|
140
|
+
end
|
141
|
+
|
142
|
+
it "sets #elements from the varargs" do
|
143
|
+
MTK::Pattern.DurationSequence(1,2,3).elements.should == [1,2,3]
|
144
|
+
end
|
145
|
+
|
146
|
+
it "sets #type to :pitch" do
|
147
|
+
MTK::Pattern.DurationSequence([]).type.should == :duration
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -9,6 +9,23 @@ describe MTK::PitchClassSet do
|
|
9
9
|
pitch_class_set.should be_a Enumerable
|
10
10
|
end
|
11
11
|
|
12
|
+
describe ".random_row" do
|
13
|
+
it "generates a 12-tone row" do
|
14
|
+
PitchClassSet.random_row.should =~ PitchClasses::PITCH_CLASSES
|
15
|
+
end
|
16
|
+
|
17
|
+
it "generates a random 12-tone row (NOTE: very slight expected chance of test failure, if this fails run it again!)" do
|
18
|
+
# there's a 1/479_001_600 chance this will fail... whaddyagonnado??
|
19
|
+
PitchClassSet.random_row.should_not == PitchClassSet.random_row
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".all" do
|
24
|
+
it "is the set of all 12 pitch classes" do
|
25
|
+
PitchClassSet.all.should == PitchClassSet(PitchClasses::PITCH_CLASSES)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
12
29
|
describe "#pitch_classes" do
|
13
30
|
it "is the list of pitch_classes contained in this set" do
|
14
31
|
pitch_class_set.pitch_classes.should == pitch_classes
|
@@ -32,10 +49,6 @@ describe MTK::PitchClassSet do
|
|
32
49
|
it "does not include duplicates" do
|
33
50
|
PitchClassSet.new([C, E, G, C]).pitch_classes.should == [C, E, G]
|
34
51
|
end
|
35
|
-
|
36
|
-
it "sorts the pitch_classes (C to B)" do
|
37
|
-
PitchClassSet.new([B, E, C]).pitch_classes.should == [C, E, B]
|
38
|
-
end
|
39
52
|
end
|
40
53
|
|
41
54
|
describe "#to_a" do
|
@@ -48,6 +61,42 @@ describe MTK::PitchClassSet do
|
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
64
|
+
describe "#size" do
|
65
|
+
it "returns the number of pitch classes in the set" do
|
66
|
+
pitch_class_set.size.should == 3
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#length" do
|
71
|
+
it "behaves like #size" do
|
72
|
+
pitch_class_set.length.should == pitch_class_set.size
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#[]" do
|
77
|
+
it "accesses the individual pitch classes (like an Array)" do
|
78
|
+
pitch_class_set[0].should == C
|
79
|
+
pitch_class_set[1].should == E
|
80
|
+
pitch_class_set[2].should == G
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns nil for invalid indexes" do
|
84
|
+
pitch_class_set[pitch_class_set.size].should == nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#first" do
|
89
|
+
it "is #[0]" do
|
90
|
+
pitch_class_set.first.should == pitch_class_set[0]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#last" do
|
95
|
+
it "is #[-1]" do
|
96
|
+
pitch_class_set.last.should == pitch_class_set[-1]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
51
100
|
describe "#each" do
|
52
101
|
it "yields each pitch_class" do
|
53
102
|
pcs = []
|
@@ -62,6 +111,105 @@ describe MTK::PitchClassSet do
|
|
62
111
|
end
|
63
112
|
end
|
64
113
|
|
114
|
+
describe '#transpose' do
|
115
|
+
it 'transposes by the given semitones' do
|
116
|
+
(pitch_class_set.transpose 4.semitones).should == PitchClassSet(E, Ab, B)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#invert" do
|
121
|
+
it 'inverts all pitch_classes around the given center pitch' do
|
122
|
+
pitch_class_set.invert(G).should == PitchClassSet(D,Bb,G)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'inverts all pitches around the first pitch, when no center pitch is given' do
|
126
|
+
pitch_class_set.invert.should == PitchClassSet(C,Ab,F)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#reverse" do
|
131
|
+
it "produces a PitchClassSet with pitch classes in reverse order" do
|
132
|
+
pitch_class_set.reverse.should == PitchClassSet(G,E,C)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "#retrograde" do
|
137
|
+
it "acts like reverse" do
|
138
|
+
pitch_class_set.retrograde.should == pitch_class_set.reverse
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#intersection" do
|
143
|
+
it "produces a PitchClassSet containing the common pitch classes from self and the argument" do
|
144
|
+
pitch_class_set.intersection(PitchClassSet(E,G,B)).should == PitchClassSet(E,G)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#union" do
|
149
|
+
it "produces a PitchClassSet containing the all pitch classes from either self or the argument" do
|
150
|
+
pitch_class_set.union(PitchClassSet(E,G,B)).should == PitchClassSet(C,E,G,B)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#difference" do
|
155
|
+
it "produces a PitchClassSet with the pitch classes from the argument removed" do
|
156
|
+
pitch_class_set.difference(PitchClassSet(E)).should == PitchClassSet(C,G)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "#symmetric_difference" do
|
161
|
+
it "produces a PitchClassSet containing the pitch classes only in self or only in the argument" do
|
162
|
+
pitch_class_set.symmetric_difference(PitchClassSet(E,G,B)).should == PitchClassSet(C,B)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "#complement" do
|
167
|
+
it "produces the set of all PitchClasses not in the current set" do
|
168
|
+
pitch_class_set.complement.should =~ PitchClassSet(Db,D,Eb,F,Gb,Ab,A,Bb,B)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#repeat" do
|
173
|
+
it "does nothing to PitchClassSets, since duplicates are removed from Sets" do
|
174
|
+
pitch_class_set.repeat.should == pitch_class_set
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "#rotate" do
|
179
|
+
it "produces a PitchClassSet that is rotated by the given offset" do
|
180
|
+
pitch_class_set.rotate(2).should == PitchClassSet(G,C,E)
|
181
|
+
pitch_class_set.rotate(-2).should == PitchClassSet(E,G,C)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "rotates by 1 if no argument is given" do
|
185
|
+
pitch_class_set.rotate.should == pitch_class_set.rotate(1)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "#permute" do
|
190
|
+
it "randomly rearranges the PitchClassSet order (NOTE: very slight expected chance of test failure, if this fails run it again!)" do
|
191
|
+
all_pcs = PitchClassSet(PitchClasses::PITCH_CLASSES)
|
192
|
+
permuted = all_pcs.permute
|
193
|
+
permuted.should =~ all_pcs
|
194
|
+
permuted.should_not == all_pcs # there's a 1/479_001_600 chance this will fail...
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "#shuffle" do
|
199
|
+
it "behaves like permute (NOTE: very slight expected chance of test failure, if this fails run it again!)" do
|
200
|
+
all_pcs = PitchClassSet(PitchClasses::PITCH_CLASSES)
|
201
|
+
shuffled = all_pcs.shuffle
|
202
|
+
shuffled.should =~ all_pcs
|
203
|
+
shuffled.should_not == all_pcs # there's a 1/479_001_600 chance this will fail...
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "#concat" do
|
208
|
+
it "appends the non-duplicate pitch classes from the other set" do
|
209
|
+
pitch_class_set.concat(PitchClassSet(D,E,F)).should == PitchClassSet(C,E,G,D,F)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
65
213
|
describe "#normal_order" do
|
66
214
|
it "permutes the set so that the first and last pitch classes are as close together as possible" do
|
67
215
|
PitchClassSet.new([E,A,C]).normal_order.should == [A,C,E]
|
@@ -92,12 +240,97 @@ describe MTK::PitchClassSet do
|
|
92
240
|
end
|
93
241
|
end
|
94
242
|
|
95
|
-
describe "
|
96
|
-
|
243
|
+
describe "#==" do
|
244
|
+
it "is true if two pitch class sets contain the same set in the same order" do
|
245
|
+
pitch_class_set.should == PitchClassSet(C,E,G)
|
246
|
+
end
|
247
|
+
|
248
|
+
it "is false if two pitch class sets are not in the same order" do
|
249
|
+
pitch_class_set.should_not == PitchClassSet(C,G,E)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "is false if two pitch class sets do not contain the same pitch classes" do
|
253
|
+
pitch_class_set.should_not == PitchClassSet(C,E)
|
254
|
+
end
|
255
|
+
|
256
|
+
it "allows for direct comparison with Arrays" do
|
257
|
+
pitch_class_set.should == [C,E,G]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe "#=~" do
|
262
|
+
it "is true if two pitch class sets contain the same set in the same order" do
|
263
|
+
pitch_class_set.should =~ PitchClassSet(C,E,G)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "is true if two pitch class sets are not in the same order" do
|
267
|
+
pitch_class_set.should =~ PitchClassSet(C,G,E)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "is false if two pitch class sets do not contain the same pitch classes" do
|
271
|
+
pitch_class_set.should_not =~ PitchClassSet(C,E)
|
272
|
+
end
|
273
|
+
|
274
|
+
it "allows for direct comparison with Arrays" do
|
275
|
+
pitch_class_set.should =~ [C,G,E]
|
276
|
+
end
|
97
277
|
end
|
98
278
|
|
99
279
|
describe ".span_between" do
|
100
|
-
|
280
|
+
it "is the distance in semitones between 2 pitch classes" do
|
281
|
+
PitchClassSet.span_between(F, Bb).should == 5
|
282
|
+
end
|
283
|
+
|
284
|
+
it "assumes an ascending interval between the arguments (order of arguments matters)" do
|
285
|
+
PitchClassSet.span_between(Bb, F).should == 7
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe ".span_for" do
|
290
|
+
it "measure the distance between the first and last pitch classes" do
|
291
|
+
PitchClassSet.span_for([C,D,E,F,G]).should == 7
|
292
|
+
end
|
101
293
|
end
|
102
294
|
|
103
295
|
end
|
296
|
+
|
297
|
+
describe MTK do
|
298
|
+
|
299
|
+
describe '#PitchClassSet' do
|
300
|
+
|
301
|
+
it "acts like new for a single Array argument" do
|
302
|
+
PitchClassSet([C,D]).should == PitchClassSet.new([C,D])
|
303
|
+
end
|
304
|
+
|
305
|
+
it "acts like new for multiple arguments, by treating them like an Array (splat)" do
|
306
|
+
PitchClassSet(C,D).should == PitchClassSet.new([C,D])
|
307
|
+
end
|
308
|
+
|
309
|
+
it "handles an Array with elements that can be converted to Pitches" do
|
310
|
+
PitchClassSet(['C','D']).should == PitchClassSet.new([C,D])
|
311
|
+
end
|
312
|
+
|
313
|
+
it "handles multiple arguments that can be converted to a Pitch" do
|
314
|
+
PitchClassSet(:C,:D).should == PitchClassSet.new([C,D])
|
315
|
+
end
|
316
|
+
|
317
|
+
it "handles a single Pitch" do
|
318
|
+
PitchClassSet(C).should == PitchClassSet.new([C])
|
319
|
+
end
|
320
|
+
|
321
|
+
it "handles single elements that can be converted to a Pitch" do
|
322
|
+
PitchClassSet('C').should == PitchClassSet.new([C])
|
323
|
+
end
|
324
|
+
|
325
|
+
it "returns the argument if it's already a PitchClassSet" do
|
326
|
+
pitch_set = PitchClassSet.new([C,D])
|
327
|
+
PitchClassSet(pitch_set).should be_equal pitch_set
|
328
|
+
end
|
329
|
+
|
330
|
+
it "raises an error for types it doesn't understand" do
|
331
|
+
lambda{ PitchClassSet({:not => :compatible}) }.should raise_error
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|