jmtk 0.0.3.3-java

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 (110) hide show
  1. data/.yardopts +10 -0
  2. data/DEVELOPMENT_NOTES.md +115 -0
  3. data/INTRO.md +129 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.md +50 -0
  6. data/Rakefile +102 -0
  7. data/bin/jmtk +250 -0
  8. data/bin/mtk +250 -0
  9. data/examples/crescendo.rb +20 -0
  10. data/examples/drum_pattern.rb +23 -0
  11. data/examples/dynamic_pattern.rb +36 -0
  12. data/examples/gets_and_play.rb +27 -0
  13. data/examples/notation.rb +22 -0
  14. data/examples/play_midi.rb +17 -0
  15. data/examples/print_midi.rb +13 -0
  16. data/examples/random_tone_row.rb +18 -0
  17. data/examples/syntax_to_midi.rb +28 -0
  18. data/examples/test_output.rb +7 -0
  19. data/examples/tone_row_melody.rb +23 -0
  20. data/lib/mtk.rb +76 -0
  21. data/lib/mtk/core/duration.rb +213 -0
  22. data/lib/mtk/core/intensity.rb +158 -0
  23. data/lib/mtk/core/interval.rb +157 -0
  24. data/lib/mtk/core/pitch.rb +154 -0
  25. data/lib/mtk/core/pitch_class.rb +194 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/events/timeline.rb +232 -0
  30. data/lib/mtk/groups/chord.rb +56 -0
  31. data/lib/mtk/groups/collection.rb +196 -0
  32. data/lib/mtk/groups/melody.rb +96 -0
  33. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  34. data/lib/mtk/groups/pitch_collection.rb +23 -0
  35. data/lib/mtk/io/dls_synth_device.rb +146 -0
  36. data/lib/mtk/io/dls_synth_output.rb +62 -0
  37. data/lib/mtk/io/jsound_input.rb +87 -0
  38. data/lib/mtk/io/jsound_output.rb +82 -0
  39. data/lib/mtk/io/midi_file.rb +209 -0
  40. data/lib/mtk/io/midi_input.rb +97 -0
  41. data/lib/mtk/io/midi_output.rb +195 -0
  42. data/lib/mtk/io/notation.rb +162 -0
  43. data/lib/mtk/io/unimidi_input.rb +117 -0
  44. data/lib/mtk/io/unimidi_output.rb +140 -0
  45. data/lib/mtk/lang/durations.rb +57 -0
  46. data/lib/mtk/lang/intensities.rb +61 -0
  47. data/lib/mtk/lang/intervals.rb +73 -0
  48. data/lib/mtk/lang/mtk_grammar.citrus +237 -0
  49. data/lib/mtk/lang/parser.rb +29 -0
  50. data/lib/mtk/lang/pitch_classes.rb +29 -0
  51. data/lib/mtk/lang/pitches.rb +52 -0
  52. data/lib/mtk/lang/pseudo_constants.rb +26 -0
  53. data/lib/mtk/lang/variable.rb +32 -0
  54. data/lib/mtk/numeric_extensions.rb +66 -0
  55. data/lib/mtk/patterns/chain.rb +49 -0
  56. data/lib/mtk/patterns/choice.rb +43 -0
  57. data/lib/mtk/patterns/cycle.rb +18 -0
  58. data/lib/mtk/patterns/for_each.rb +71 -0
  59. data/lib/mtk/patterns/function.rb +39 -0
  60. data/lib/mtk/patterns/lines.rb +54 -0
  61. data/lib/mtk/patterns/palindrome.rb +45 -0
  62. data/lib/mtk/patterns/pattern.rb +171 -0
  63. data/lib/mtk/patterns/sequence.rb +20 -0
  64. data/lib/mtk/sequencers/event_builder.rb +132 -0
  65. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  66. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  67. data/lib/mtk/sequencers/sequencer.rb +111 -0
  68. data/lib/mtk/sequencers/step_sequencer.rb +26 -0
  69. data/spec/mtk/core/duration_spec.rb +372 -0
  70. data/spec/mtk/core/intensity_spec.rb +289 -0
  71. data/spec/mtk/core/interval_spec.rb +265 -0
  72. data/spec/mtk/core/pitch_class_spec.rb +343 -0
  73. data/spec/mtk/core/pitch_spec.rb +297 -0
  74. data/spec/mtk/events/event_spec.rb +234 -0
  75. data/spec/mtk/events/note_spec.rb +174 -0
  76. data/spec/mtk/events/parameter_spec.rb +220 -0
  77. data/spec/mtk/events/timeline_spec.rb +430 -0
  78. data/spec/mtk/groups/chord_spec.rb +85 -0
  79. data/spec/mtk/groups/collection_spec.rb +374 -0
  80. data/spec/mtk/groups/melody_spec.rb +225 -0
  81. data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
  82. data/spec/mtk/io/midi_file_spec.rb +243 -0
  83. data/spec/mtk/io/midi_output_spec.rb +102 -0
  84. data/spec/mtk/lang/durations_spec.rb +89 -0
  85. data/spec/mtk/lang/intensities_spec.rb +101 -0
  86. data/spec/mtk/lang/intervals_spec.rb +143 -0
  87. data/spec/mtk/lang/parser_spec.rb +603 -0
  88. data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
  89. data/spec/mtk/lang/pitches_spec.rb +56 -0
  90. data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
  91. data/spec/mtk/lang/variable_spec.rb +52 -0
  92. data/spec/mtk/numeric_extensions_spec.rb +83 -0
  93. data/spec/mtk/patterns/chain_spec.rb +110 -0
  94. data/spec/mtk/patterns/choice_spec.rb +97 -0
  95. data/spec/mtk/patterns/cycle_spec.rb +123 -0
  96. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  97. data/spec/mtk/patterns/function_spec.rb +120 -0
  98. data/spec/mtk/patterns/lines_spec.rb +77 -0
  99. data/spec/mtk/patterns/palindrome_spec.rb +108 -0
  100. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  101. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  102. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  103. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  104. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  105. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  106. data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
  107. data/spec/spec_coverage.rb +2 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/test.mid +0 -0
  110. metadata +226 -0
@@ -0,0 +1,374 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Groups::Collection do
4
+
5
+ class MockCollection
6
+ include MTK::Groups::Collection
7
+ attr_reader :elements
8
+ def initialize(elements); @elements = elements end
9
+ def self.from_a(elements); new(elements) end
10
+ end
11
+
12
+ class MockCollectionWithOptions
13
+ include MTK::Groups::Collection
14
+ attr_reader :elements, :options
15
+ def initialize(elements, options={})
16
+ @elements = elements
17
+ @options = options
18
+ end
19
+ def self.from_a(elements, options={})
20
+ new(elements, options)
21
+ end
22
+ end
23
+
24
+ let(:elements) { [1,2,3,4,5] }
25
+ let(:collection) { MockCollection.new elements }
26
+
27
+ let(:options) { {:opt1 => :val1, :opt2 => :val2} }
28
+ let(:collection_with_options) { MockCollectionWithOptions.new(elements, options) }
29
+
30
+ it "is Enumerable" do
31
+ collection.should be_a Enumerable
32
+ end
33
+
34
+ describe "#to_a" do
35
+ it "is the the Array of elements in the collection" do
36
+ collection.to_a.should == elements
37
+ end
38
+ end
39
+
40
+ describe "#size" do
41
+ it "is the length of the elements" do
42
+ collection.size.should == elements.length
43
+ end
44
+ end
45
+
46
+ describe "#length" do
47
+ it "acts like #size" do
48
+ collection.length.should == collection.size
49
+ end
50
+ end
51
+
52
+ describe "#empty?" do
53
+ it "is true when elements is nil" do
54
+ MockCollection.new(nil).empty?.should be_true
55
+ end
56
+
57
+ it "is true when elements is empty" do
58
+ MockCollection.new([]).empty?.should be_true
59
+ end
60
+
61
+ it "is false when elements is not empty" do
62
+ MockCollection.new([1]).empty?.should be_false
63
+ end
64
+ end
65
+
66
+ describe "#each" do
67
+ it "yields each element" do
68
+ yielded = []
69
+ collection.each do |element|
70
+ yielded << element
71
+ end
72
+ yielded.should == elements
73
+ end
74
+ end
75
+
76
+ describe "#map" do
77
+ it "returns a Collection with each item replaced with the results of the block" do
78
+ collection.map{|item| item + 10}.should == [11, 12, 13, 14, 15]
79
+ end
80
+
81
+ it "maintains the options from the original collection" do
82
+ collection_with_options.map{|item| item + 10}.options.should == options
83
+ end
84
+ end
85
+
86
+ describe "#first" do
87
+ it "is #[0] when no argument is given" do
88
+ collection.first.should == collection[0]
89
+ end
90
+
91
+ it "is the first n elements, for an argument n" do
92
+ collection.first(3).should == elements[0..2]
93
+ end
94
+
95
+ it "is the elements when n is bigger than elements.length, for an argument n" do
96
+ collection.first(elements.length * 2).should == elements
97
+ end
98
+ end
99
+
100
+ describe "#last when no argument is given" do
101
+ it "is #[-1]" do
102
+ collection.last.should == collection[-1]
103
+ end
104
+
105
+ it "is the last n elements, for an argument n" do
106
+ collection.last(3).should == elements[-3..-1]
107
+ end
108
+
109
+ it "is the elements when n is bigger than elements.length, for an argument n" do
110
+ collection.last(elements.length * 2).should == elements
111
+ end
112
+ end
113
+
114
+ describe "#[]" do
115
+ it "accesses an element by index" do
116
+ collection[3].should == elements[3]
117
+ end
118
+
119
+ it "supports negative indexes" do
120
+ collection[-1].should == elements[-1]
121
+ end
122
+
123
+ it "accesses ranges of elements" do
124
+ collection[0..3].should == elements[0..3]
125
+ end
126
+ end
127
+
128
+ describe "#repeat" do
129
+ it "repeats the elements the number of times given by the argument" do
130
+ collection.repeat(3).should == [1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]
131
+ end
132
+
133
+ it "repeats the elements twice if no argument is given" do
134
+ collection.repeat.should == [1,2,3,4,5,1,2,3,4,5]
135
+ end
136
+
137
+ it "handles fractional repetitions" do
138
+ collection.repeat(1.6).should == [1,2,3,4,5,1,2,3]
139
+ end
140
+
141
+ it "returns an instance of the same type" do
142
+ collection.repeat.should be_a collection.class
143
+ end
144
+
145
+ it "does not modify the original collection" do
146
+ collection.repeat.should_not equal collection
147
+ end
148
+
149
+ it "maintains the options from the original collection" do
150
+ collection_with_options.repeat.options.should == options
151
+ end
152
+ end
153
+
154
+ describe "#permute" do
155
+ it "randomly rearranges the order of elements" do
156
+ elements = (0..1000).to_a
157
+ permuted = MockCollection.new(elements).permute
158
+ permuted.should_not == elements
159
+ permuted.sort.should == elements
160
+ end
161
+
162
+ it "returns an instance of the same type" do
163
+ collection.permute.should be_a collection.class
164
+ end
165
+
166
+ it "does not modify the original collection" do
167
+ collection.permute.should_not equal collection
168
+ end
169
+
170
+ it "maintains the options from the original collection" do
171
+ collection_with_options.permute.options.should == options
172
+ end
173
+ end
174
+
175
+ describe "#shuffle" do
176
+ it "behaves like #permute" do
177
+ elements = (0..1000).to_a
178
+ shuffled = MockCollection.new(elements).shuffle
179
+ shuffled.should_not == elements
180
+ shuffled.sort.should == elements
181
+ end
182
+
183
+ it "returns an instance of the same type" do
184
+ collection.shuffle.should be_a collection.class
185
+ end
186
+
187
+ it "does not modify the original collection" do
188
+ collection.shuffle.should_not equal collection
189
+ end
190
+
191
+ it "maintains the options from the original collection" do
192
+ collection_with_options.shuffle.options.should == options
193
+ end
194
+ end
195
+
196
+ describe "#rotate" do
197
+ it "produces a Collection that is rotated right by the given positive offset" do
198
+ collection.rotate(2).should == [3,4,5,1,2]
199
+ end
200
+
201
+ it "produces a Collection that is rotated left by the given negative offset" do
202
+ collection.rotate(-2).should == [4,5,1,2,3]
203
+ end
204
+
205
+ it "rotates by 1 if no argument is given" do
206
+ collection.rotate.should == [2,3,4,5,1]
207
+ end
208
+
209
+ it "returns an instance of the same type" do
210
+ collection.rotate.should be_a collection.class
211
+ end
212
+
213
+ it "does not modify the original collection" do
214
+ collection.rotate.should_not equal collection
215
+ end
216
+
217
+ it "maintains the options from the original collection" do
218
+ collection_with_options.rotate.options.should == options
219
+ end
220
+ end
221
+
222
+ describe "#concat" do
223
+ it "appends the argument to the elements" do
224
+ collection.concat([6,7]).should == [1,2,3,4,5,6,7]
225
+ end
226
+
227
+ it "returns an instance of the same type" do
228
+ collection.concat([6,7]).should be_a collection.class
229
+ end
230
+
231
+ it "does not modify the original collection" do
232
+ collection.concat([6,7]).should_not equal collection
233
+ end
234
+
235
+ it "maintains the options from the original (receiver) collection" do
236
+ collection_with_options.concat([6,7]).options.should == options
237
+ end
238
+
239
+ it "ignored any options from the argument collection" do
240
+ # I considered merging the options, but it seems potentially too confusing, so
241
+ # we'll go with this simpler behavior until a use-case appears where this is inappropriate.
242
+ arg = MockCollectionWithOptions.new(elements, :opt1 => :another_val, :opt3 => :val3)
243
+ collection_with_options.concat(arg).options.should == options
244
+ end
245
+ end
246
+
247
+ describe "#reverse" do
248
+ it "reverses the elements" do
249
+ collection.reverse.should == elements.reverse
250
+ end
251
+
252
+ it "returns an instance of the same type" do
253
+ collection.reverse.should be_a collection.class
254
+ end
255
+
256
+ it "does not modify the original collection" do
257
+ collection.reverse.should_not equal collection
258
+ end
259
+
260
+ it "maintains the options from the original collection" do
261
+ collection_with_options.reverse.options.should == options
262
+ end
263
+ end
264
+
265
+ describe "#partition" do
266
+ # TODO: better descriptions! (and document the method too!)
267
+
268
+ context "Numeric argument" do
269
+ it "partitions the elements into groups of the size, plus whatever's left over as the last element" do
270
+ collection.partition(2).should == [
271
+ MockCollection.new([1,2]),
272
+ MockCollection.new([3,4]),
273
+ MockCollection.new([5])
274
+ ]
275
+ end
276
+ end
277
+
278
+ context "Array argument" do
279
+ it "partitions the elements into groups of the size of the argument elements" do
280
+ collection.partition([1,2,2]).should == [
281
+ MockCollection.new([1]),
282
+ MockCollection.new([2,3]),
283
+ MockCollection.new([4,5])
284
+ ]
285
+ end
286
+
287
+ it "does not include leftover elements" do
288
+ collection.partition([1,3]).should == [
289
+ MockCollection.new([1]),
290
+ MockCollection.new([2,3,4])
291
+ ]
292
+ end
293
+
294
+ it "does not include extra elements" do
295
+ collection.partition([1,5]).should == [
296
+ MockCollection.new([1]),
297
+ MockCollection.new([2,3,4,5])
298
+ ]
299
+ end
300
+ end
301
+
302
+ context "no argument, block given" do
303
+ it "partitions the elements into groups with the same block return value" do
304
+ collection.partition{|item| item % 3 }.should =~ [
305
+ MockCollection.new([1,4]),
306
+ MockCollection.new([2,5]),
307
+ MockCollection.new([3])
308
+ ]
309
+ end
310
+
311
+ it "optionally passes the item index into the block" do
312
+ collection.partition{|item,index| (item*index) % 3 }.should =~ [
313
+ # 1*0, 2*1, 3*2, 4*3, 5*4 => (0, 2, 6, 12, 20) % 3 => 0, 2, 0, 0, 2
314
+ MockCollection.new([1,3,4]),
315
+ MockCollection.new([2,5]),
316
+ ]
317
+ end
318
+ end
319
+
320
+ context "incompatible / missing argument, no block given" do
321
+ it "returns self" do
322
+ collection.partition.should == collection
323
+ end
324
+ end
325
+
326
+ end
327
+
328
+ describe "#==" do
329
+ it "is true when the elements in 2 Collections are equal" do
330
+ collection.should == MockCollection.new(elements)
331
+ end
332
+
333
+ it "is true when the elements equal the argument" do
334
+ collection.should == elements
335
+ end
336
+
337
+ it "is false when the elements in 2 Collections are not equal" do
338
+ collection.should_not == MockCollection.new(elements + [1,2])
339
+ end
340
+
341
+ it "is false when the elements do not equal the argument" do
342
+ collection.should_not == (elements + [1,2])
343
+ end
344
+
345
+ it "is false when the options are not equal" do
346
+ collection.should_not == collection_with_options
347
+ end
348
+ end
349
+
350
+ describe "#clone" do
351
+ it "creates an equal collection" do
352
+ collection.clone.should == collection
353
+ end
354
+
355
+ it "creates a new collection" do
356
+ collection.clone.should_not equal collection
357
+ end
358
+
359
+ it "maintains the options from the original collection" do
360
+ collection_with_options.clone.options.should == options
361
+ end
362
+ end
363
+
364
+ end
365
+
366
+
367
+ describe "Array" do
368
+ describe "#rotate" do
369
+ # test the Ruby 1.8 backport of Array#rotate
370
+ it "should rotate the Array, as in Ruby 1.9's API" do
371
+ [1,2,3].rotate(1).should == [2,3,1]
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Groups::Melody do
4
+
5
+ MELODY = MTK::Groups::Melody
6
+
7
+ let(:pitches) { [C4, D4, E4, D4] }
8
+ let(:melody) { MTK::Groups::Melody.new(pitches) }
9
+
10
+ it "is Enumerable" do
11
+ melody.should be_a Enumerable
12
+ end
13
+
14
+ describe ".new" do
15
+ it "maintains the pitches collection exactly (preserves order and keeps duplicates)" do
16
+ MELODY.new([C4, E4, G4, E4, B3, C4]).pitches.should == [C4, E4, G4, E4, B3, C4]
17
+ end
18
+ end
19
+
20
+ describe ".from_pitch_classes" do
21
+ it "creates a pitch sequence from a list of pitch classes and starting point, selecting the nearest pitch to each pitch class" do
22
+ MELODY.from_pitch_classes([C,G,B,Eb,D,C], D3).should == [C3,G2,B2,Eb3,D3,C3]
23
+ end
24
+
25
+ it "defaults to a starting point of C4 (middle C)" do
26
+ MELODY.from_pitch_classes([C]).should == [C4]
27
+ end
28
+
29
+ it "doesn't travel within an octave above or below the starting point by default" do
30
+ MELODY.from_pitch_classes([C,F,Bb,D,A,E,B]).should == [C4,F4,Bb4,D4,A3,E3,B3]
31
+ end
32
+
33
+ it "allows max distance above or below the starting point to be set via the third argument" do
34
+ MELODY.from_pitch_classes([C,F,Bb,D,A,E,B], C4, 6).should == [C4,F4,Bb3,D4,A3,E4,B3]
35
+ end
36
+ end
37
+
38
+ describe '#pitches' do
39
+ it 'is the list of pitches used to construct the scale' do
40
+ melody.pitches.should == pitches
41
+ end
42
+
43
+ it "is immutable" do
44
+ lambda { melody.pitches << Db4 }.should raise_error
45
+ end
46
+
47
+ it "does not affect the immutabilty of the pitch list used to construct it" do
48
+ list = melody # force construction with the pitches array
49
+ expect { pitches << Db4 }.to change(pitches, :length).by(1)
50
+ end
51
+
52
+ it "is not affected by changes to the pitch list used to construct it" do
53
+ melody # force construction before we modify the pitches array
54
+ expect { pitches << Db4 }.to_not change(melody.pitches, :length)
55
+ end
56
+
57
+ end
58
+
59
+ describe "#to_a" do
60
+ it "is equal to #pitches" do
61
+ melody.to_a.should == melody.pitches
62
+ end
63
+
64
+ it "is mutable" do
65
+ (melody.to_a << Bb3).should == [C4, D4, E4, D4, Bb3]
66
+ end
67
+ end
68
+
69
+ describe "#each" do
70
+ it "yields each pitch" do
71
+ ps = []
72
+ melody.each{|p| ps << p }
73
+ ps.should == pitches
74
+ end
75
+ end
76
+
77
+ describe "#map" do
78
+ it "returns a MELODY with each Pitch replaced with the results of the block" do
79
+ melody.map{|p| p + 2}.should == [D4, E4, Gb4, E4]
80
+ end
81
+ end
82
+
83
+ describe "#to_pitch_class_set" do
84
+ it "is a PitchClassSet" do
85
+ melody.to_pitch_class_set.should be_a MTK::Groups::PitchClassSet
86
+ end
87
+
88
+ it "contains all the distinct pitch_classes in this MELODY by default" do
89
+ melody.to_pitch_class_set.pitch_classes.should == melody.pitch_classes.uniq
90
+ end
91
+
92
+ it "contains all the pitch_classes (including duplicates) when the argument is false" do
93
+ melody.to_pitch_class_set(false).pitch_classes.should == melody.pitch_classes
94
+ end
95
+ end
96
+
97
+
98
+ describe '#pitch_classes' do
99
+ it 'is the list of pitch classes for the pitches in this list' do
100
+ melody.pitch_classes.should == pitches.map { |p| p.pitch_class }
101
+ end
102
+ end
103
+
104
+ describe '#transpose' do
105
+ it 'transposes upward by the given semitones' do
106
+ melody.transpose(12).should == MELODY.new([C5, D5, E5, D5])
107
+ end
108
+ end
109
+
110
+ describe '#invert' do
111
+ it 'inverts all pitches around the given center pitch' do
112
+ (melody.invert Gb4).should == MELODY.new([C5, Bb4, Ab4, Bb4])
113
+ end
114
+
115
+ it 'inverts all pitches around the first pitch, when no center pitch is given' do
116
+ melody.invert.should == MELODY.new([C4, Bb3, Ab3, Bb3])
117
+ end
118
+ end
119
+
120
+ describe '#include?' do
121
+ it 'returns true if the Pitch is in the MELODY' do
122
+ (melody.include? C4).should be_true
123
+ end
124
+
125
+ it 'returns false if the Pitch is not in the MELODY' do
126
+ (melody.include? Db4).should be_false
127
+ end
128
+ end
129
+
130
+ describe '#==' do
131
+ it "is true when all the pitches are equal" do
132
+ MELODY.new([C4, E4, G4]).should == MELODY.new([Pitch.from_i(60), Pitch.from_i(64), Pitch.from_i(67)])
133
+ end
134
+
135
+ it "is false when not all the pitches are equal" do
136
+ MELODY.new([C4, E4, G4]).should_not == MELODY.new([Pitch.from_i(60), Pitch.from_i(65), Pitch.from_i(67)])
137
+ end
138
+
139
+ it "is false when if otherwise equal Melodies don't contain the same number of duplicates" do
140
+ MELODY.new([C4, E4, G4]).should_not == MELODY.new([C4, C4, E4, G4])
141
+ end
142
+
143
+ it "is false when if otherwise equal Melodies aren't in the same order" do
144
+ MELODY.new([C4, E4, G4]).should_not == MELODY.new([C4, G4, E4])
145
+ end
146
+
147
+ it "is false when the argument is not compatible" do
148
+ MELODY.new([C4, E4, G4]).should_not == :invalid
149
+ end
150
+
151
+ it "can be compared directly to Arrays" do
152
+ MELODY.new([C4, E4, G4]).should == [C4, E4, G4]
153
+ end
154
+ end
155
+
156
+ describe "#=~" do
157
+ it "is true when all the pitches are equal" do
158
+ MELODY.new([C4, E4, G4]).should =~ MELODY.new([C4, E4, G4])
159
+ end
160
+
161
+ it "is true when all the pitches are equal, even with different numbers of duplicates" do
162
+ MELODY.new([C4, E4, G4]).should =~ MELODY.new([C4, C4, E4, G4])
163
+ end
164
+
165
+ it "is true when all the pitches are equal, even in a different order" do
166
+ MELODY.new([C4, E4, G4]).should =~ MELODY.new([C4, G4, E4])
167
+ end
168
+
169
+ it "is false when one MELODY contains a Pitch not in the other" do
170
+ MELODY.new([C4, E4, G4]).should_not =~ MELODY.new([C4, E4])
171
+ end
172
+
173
+ it "can be compared directly to Arrays" do
174
+ MELODY.new([C4, E4, G4]).should =~ [C4, E4, G4]
175
+ end
176
+ end
177
+
178
+ describe "#to_s" do
179
+ it "looks like an array of pitches" do
180
+ melody.to_s.should == "[C4, D4, E4, D4]"
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ describe MTK do
187
+
188
+ describe '#Melody' do
189
+
190
+ it "acts like new for a single Array argument" do
191
+ Melody([C4,D4]).should == MTK::Groups::Melody.new([C4,D4])
192
+ end
193
+
194
+ it "acts like new for multiple arguments, by treating them like an Array (splat)" do
195
+ Melody(C4,D4).should == MTK::Groups::Melody.new([C4,D4])
196
+ end
197
+
198
+ it "handles an Array with elements that can be converted to Pitches" do
199
+ Melody(['C4','D4']).should == MTK::Groups::Melody.new([C4,D4])
200
+ end
201
+
202
+ it "handles multiple arguments that can be converted to a Pitch" do
203
+ Melody(:C4,:D4).should == MTK::Groups::Melody.new([C4,D4])
204
+ end
205
+
206
+ it "handles a single Pitch" do
207
+ Melody(C4).should == MTK::Groups::Melody.new([C4])
208
+ end
209
+
210
+ it "handles single elements that can be converted to a Pitch" do
211
+ Melody('C4').should == MTK::Groups::Melody.new([C4])
212
+ end
213
+
214
+ it "handles a Melody" do
215
+ melody = MTK::Groups::Melody.new([C4,D4])
216
+ Melody(melody).should == [C4,D4]
217
+ end
218
+
219
+ it "raises an error for types it doesn't understand" do
220
+ lambda{ Melody({:not => :compatible}) }.should raise_error
221
+ end
222
+
223
+ end
224
+
225
+ end