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.
- data/.yardopts +10 -0
- data/DEVELOPMENT_NOTES.md +115 -0
- data/INTRO.md +129 -0
- data/LICENSE.txt +27 -0
- data/README.md +50 -0
- data/Rakefile +102 -0
- data/bin/jmtk +250 -0
- data/bin/mtk +250 -0
- data/examples/crescendo.rb +20 -0
- data/examples/drum_pattern.rb +23 -0
- data/examples/dynamic_pattern.rb +36 -0
- data/examples/gets_and_play.rb +27 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +17 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +7 -0
- data/examples/tone_row_melody.rb +23 -0
- data/lib/mtk.rb +76 -0
- data/lib/mtk/core/duration.rb +213 -0
- data/lib/mtk/core/intensity.rb +158 -0
- data/lib/mtk/core/interval.rb +157 -0
- data/lib/mtk/core/pitch.rb +154 -0
- data/lib/mtk/core/pitch_class.rb +194 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/events/timeline.rb +232 -0
- data/lib/mtk/groups/chord.rb +56 -0
- data/lib/mtk/groups/collection.rb +196 -0
- data/lib/mtk/groups/melody.rb +96 -0
- data/lib/mtk/groups/pitch_class_set.rb +163 -0
- data/lib/mtk/groups/pitch_collection.rb +23 -0
- data/lib/mtk/io/dls_synth_device.rb +146 -0
- data/lib/mtk/io/dls_synth_output.rb +62 -0
- data/lib/mtk/io/jsound_input.rb +87 -0
- data/lib/mtk/io/jsound_output.rb +82 -0
- data/lib/mtk/io/midi_file.rb +209 -0
- data/lib/mtk/io/midi_input.rb +97 -0
- data/lib/mtk/io/midi_output.rb +195 -0
- data/lib/mtk/io/notation.rb +162 -0
- data/lib/mtk/io/unimidi_input.rb +117 -0
- data/lib/mtk/io/unimidi_output.rb +140 -0
- data/lib/mtk/lang/durations.rb +57 -0
- data/lib/mtk/lang/intensities.rb +61 -0
- data/lib/mtk/lang/intervals.rb +73 -0
- data/lib/mtk/lang/mtk_grammar.citrus +237 -0
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/lang/pitch_classes.rb +29 -0
- data/lib/mtk/lang/pitches.rb +52 -0
- data/lib/mtk/lang/pseudo_constants.rb +26 -0
- data/lib/mtk/lang/variable.rb +32 -0
- data/lib/mtk/numeric_extensions.rb +66 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/patterns/choice.rb +43 -0
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/patterns/lines.rb +54 -0
- data/lib/mtk/patterns/palindrome.rb +45 -0
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/sequencers/event_builder.rb +132 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/sequencers/sequencer.rb +111 -0
- data/lib/mtk/sequencers/step_sequencer.rb +26 -0
- data/spec/mtk/core/duration_spec.rb +372 -0
- data/spec/mtk/core/intensity_spec.rb +289 -0
- data/spec/mtk/core/interval_spec.rb +265 -0
- data/spec/mtk/core/pitch_class_spec.rb +343 -0
- data/spec/mtk/core/pitch_spec.rb +297 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/events/timeline_spec.rb +430 -0
- data/spec/mtk/groups/chord_spec.rb +85 -0
- data/spec/mtk/groups/collection_spec.rb +374 -0
- data/spec/mtk/groups/melody_spec.rb +225 -0
- data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
- data/spec/mtk/io/midi_file_spec.rb +243 -0
- data/spec/mtk/io/midi_output_spec.rb +102 -0
- data/spec/mtk/lang/durations_spec.rb +89 -0
- data/spec/mtk/lang/intensities_spec.rb +101 -0
- data/spec/mtk/lang/intervals_spec.rb +143 -0
- data/spec/mtk/lang/parser_spec.rb +603 -0
- data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
- data/spec/mtk/lang/pitches_spec.rb +56 -0
- data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/variable_spec.rb +52 -0
- data/spec/mtk/numeric_extensions_spec.rb +83 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/patterns/choice_spec.rb +97 -0
- data/spec/mtk/patterns/cycle_spec.rb +123 -0
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/patterns/function_spec.rb +120 -0
- data/spec/mtk/patterns/lines_spec.rb +77 -0
- data/spec/mtk/patterns/palindrome_spec.rb +108 -0
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test.mid +0 -0
- 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
|