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.
Files changed (93) hide show
  1. data/.yardopts +9 -0
  2. data/INTRO.md +73 -0
  3. data/LICENSE.txt +27 -0
  4. data/README.md +93 -18
  5. data/Rakefile +13 -1
  6. data/examples/crescendo.rb +20 -0
  7. data/examples/dynamic_pattern.rb +39 -0
  8. data/examples/play_midi.rb +19 -0
  9. data/examples/print_midi.rb +13 -0
  10. data/examples/random_tone_row.rb +18 -0
  11. data/examples/tone_row_melody.rb +21 -0
  12. data/lib/mtk/_constants/durations.rb +80 -0
  13. data/lib/mtk/_constants/intensities.rb +81 -0
  14. data/lib/mtk/{constants → _constants}/intervals.rb +10 -1
  15. data/lib/mtk/_constants/pitch_classes.rb +35 -0
  16. data/lib/mtk/_constants/pitches.rb +49 -0
  17. data/lib/mtk/{numeric_extensions.rb → _numeric_extensions.rb} +0 -0
  18. data/lib/mtk/event.rb +14 -5
  19. data/lib/mtk/helper/collection.rb +114 -0
  20. data/lib/mtk/helper/event_builder.rb +85 -0
  21. data/lib/mtk/{constants → helper}/pseudo_constants.rb +7 -6
  22. data/lib/mtk/lang/grammar.rb +17 -0
  23. data/lib/mtk/lang/mtk_grammar.citrus +60 -0
  24. data/lib/mtk/midi/file.rb +10 -15
  25. data/lib/mtk/midi/jsound_input.rb +68 -0
  26. data/lib/mtk/midi/jsound_output.rb +80 -0
  27. data/lib/mtk/note.rb +22 -3
  28. data/lib/mtk/pattern/abstract_pattern.rb +132 -0
  29. data/lib/mtk/pattern/choice.rb +25 -9
  30. data/lib/mtk/pattern/cycle.rb +51 -0
  31. data/lib/mtk/pattern/enumerator.rb +26 -0
  32. data/lib/mtk/pattern/function.rb +46 -0
  33. data/lib/mtk/pattern/lines.rb +60 -0
  34. data/lib/mtk/pattern/palindrome.rb +42 -0
  35. data/lib/mtk/pattern/sequence.rb +15 -50
  36. data/lib/mtk/pitch.rb +45 -6
  37. data/lib/mtk/pitch_class.rb +36 -35
  38. data/lib/mtk/pitch_class_set.rb +46 -14
  39. data/lib/mtk/pitch_set.rb +20 -31
  40. data/lib/mtk/sequencer/abstract_sequencer.rb +85 -0
  41. data/lib/mtk/sequencer/rhythmic_sequencer.rb +29 -0
  42. data/lib/mtk/sequencer/step_sequencer.rb +26 -0
  43. data/lib/mtk/timeline.rb +75 -22
  44. data/lib/mtk/transform/invertible.rb +15 -0
  45. data/lib/mtk/{util → transform}/mappable.rb +6 -2
  46. data/lib/mtk/transform/set_theory_operations.rb +34 -0
  47. data/lib/mtk/transform/transposable.rb +14 -0
  48. data/lib/mtk.rb +56 -22
  49. data/spec/mtk/_constants/durations_spec.rb +118 -0
  50. data/spec/mtk/{constants/dynamics_spec.rb → _constants/intensities_spec.rb} +48 -17
  51. data/spec/mtk/{constants → _constants}/intervals_spec.rb +21 -0
  52. data/spec/mtk/_constants/pitch_classes_spec.rb +58 -0
  53. data/spec/mtk/_constants/pitches_spec.rb +52 -0
  54. data/spec/mtk/{numeric_extensions_spec.rb → _numeric_extensions_spec.rb} +0 -0
  55. data/spec/mtk/event_spec.rb +19 -0
  56. data/spec/mtk/helper/collection_spec.rb +291 -0
  57. data/spec/mtk/helper/event_builder_spec.rb +92 -0
  58. data/spec/mtk/helper/pseudo_constants_spec.rb +20 -0
  59. data/spec/mtk/lang/grammar_spec.rb +100 -0
  60. data/spec/mtk/midi/file_spec.rb +41 -6
  61. data/spec/mtk/note_spec.rb +53 -3
  62. data/spec/mtk/pattern/abstract_pattern_spec.rb +45 -0
  63. data/spec/mtk/pattern/choice_spec.rb +89 -3
  64. data/spec/mtk/pattern/cycle_spec.rb +133 -0
  65. data/spec/mtk/pattern/function_spec.rb +133 -0
  66. data/spec/mtk/pattern/lines_spec.rb +93 -0
  67. data/spec/mtk/pattern/note_cycle_spec.rb.bak +116 -0
  68. data/spec/mtk/pattern/palindrome_spec.rb +124 -0
  69. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +47 -0
  70. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +37 -0
  71. data/spec/mtk/pattern/sequence_spec.rb +128 -31
  72. data/spec/mtk/pitch_class_set_spec.rb +240 -7
  73. data/spec/mtk/pitch_class_spec.rb +84 -18
  74. data/spec/mtk/pitch_set_spec.rb +45 -10
  75. data/spec/mtk/pitch_spec.rb +59 -0
  76. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +159 -0
  77. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +49 -0
  78. data/spec/mtk/sequencer/step_sequencer_spec.rb +71 -0
  79. data/spec/mtk/timeline_spec.rb +118 -15
  80. data/spec/spec_helper.rb +4 -3
  81. metadata +59 -22
  82. data/lib/mtk/chord.rb +0 -47
  83. data/lib/mtk/constants/dynamics.rb +0 -56
  84. data/lib/mtk/constants/pitch_classes.rb +0 -18
  85. data/lib/mtk/constants/pitches.rb +0 -24
  86. data/lib/mtk/pattern/note_sequence.rb +0 -60
  87. data/lib/mtk/pattern/pitch_sequence.rb +0 -22
  88. data/lib/mtk/patterns.rb +0 -4
  89. data/spec/mtk/chord_spec.rb +0 -74
  90. data/spec/mtk/constants/pitch_classes_spec.rb +0 -35
  91. data/spec/mtk/constants/pitches_spec.rb +0 -23
  92. data/spec/mtk/pattern/note_sequence_spec.rb +0 -121
  93. 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
- let(:elements) { [1, 2, 3] }
6
- let(:sequence) { Pattern::Sequence.new elements }
5
+ SEQUENCE = MTK::Pattern::Sequence
7
6
 
8
- describe "#next" do
9
- it "iterates through the list of elements and emits them one at a time" do
10
- sequence.next.should == elements[0]
11
- sequence.next.should == elements[1]
12
- sequence.next.should == elements[2]
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
- it "starts at the beginning of the list of elements after the end of the list is reached" do
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
- sequence.next.should == elements.first
33
+ nexts.should == elements
20
34
  end
21
35
 
22
- it "evaluates any lambdas in the elements list, passing in the previous return value" do
23
- sequence = Pattern::Sequence.new [1, lambda { |prev_val| prev_val*10 }]
24
- sequence.next
25
- sequence.next.should == 10
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 "does not require the lambda to have any parameters" do
29
- sequence = Pattern::Sequence.new [lambda { 42 }]
30
- sequence.next.should == 42
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 "passed the previous item (which is not necessarily == the previous return value) as the second arg to lambdas that take 2 parameters" do
34
- arg1, arg2 = nil, nil
35
- return_5 = lambda { 5 }
36
- sequence = Pattern::Sequence.new [return_5, lambda { |a1, a2| arg1, arg2=a1, a2 }]
37
- sequence.next
38
- sequence.next
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 "#reset" do
45
- it "restarts the sequence" do
46
- (elements.length - 1).times do
47
- sequence.next
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 ".span_for" do
96
- pending
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
- pending
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