mtk 0.0.1 → 0.0.2
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/.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
|