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,430 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Events::Timeline do
4
+
5
+ let(:note1) { Note(C4, p, 1) }
6
+ let(:note2) { Note(G4, o, 2) }
7
+ let(:timeline_raw_data) { { 0.0 => note1, 1.0 => [note1, note2] } }
8
+ let(:timeline_hash) { { 0.0 => [note1], 1.0 => [note1, note2] } }
9
+ let(:timeline) { MTK::Events::Timeline.from_h(timeline_raw_data) }
10
+
11
+ let(:unquantized_data) { { 0.0 => [note1], 0.7 => [note1], 1.1 => [note2], 1.24 => [note1], 1.25 => [note1] } }
12
+ let(:unquantized_timeline) { MTK::Events::Timeline.from_h(unquantized_data) }
13
+ let(:quantization_interval) { 0.5 }
14
+ let(:quantized_data) { { 0.0 => [note1], 0.5 => [note1], 1.0 => [note2, note1], 1.5 => [note1] } }
15
+
16
+ let(:shifted_data) { { 5.0 => [note1], 6.0 => [note1, note2] } }
17
+ let(:reverse_shifted_data) { { -5.0 => [note1], -4.0 => [note1, note2] } }
18
+ let(:shift_amount) { 5 }
19
+
20
+ it "is Enumerable" do
21
+ MTK::Events::Timeline.new.should be_a Enumerable
22
+ end
23
+
24
+ it "wraps lone values in arrays" do
25
+ MTK::Events::Timeline.from_h(timeline_raw_data).should == MTK::Events::Timeline.from_h(timeline_hash)
26
+ end
27
+
28
+ describe "from_h" do
29
+ it "creates an empty timeline when the hash is empty" do
30
+ MTK::Events::Timeline.from_h({}).should be_empty
31
+ end
32
+
33
+ it "builds a Timeline from a map of times to single events" do
34
+ t = MTK::Events::Timeline.from_h({ 0 => note1, 1 => note2 })
35
+ t[0].should == [note1]
36
+ t[1].should == [note2]
37
+ end
38
+
39
+ it "builds a Timeline from a map of times to event lists" do
40
+ t = MTK::Events::Timeline.from_h({ 0 => [note1, note2], 1 => [note2] })
41
+ t[0].should == [note1, note2]
42
+ t[1].should == [note2]
43
+ end
44
+ end
45
+
46
+ describe "from_a" do
47
+ it "creates a timeline from an Enumerable" do
48
+ MTK::Events::Timeline.from_a(timeline_hash.to_a).should == timeline
49
+ end
50
+ end
51
+
52
+ describe "#to_h" do
53
+ it "returns the underlying Hash" do
54
+ timeline.to_h.should == timeline_hash
55
+ end
56
+ end
57
+
58
+ describe "#clear" do
59
+ it "clears the timeline" do
60
+ timeline.clear.should be_empty
61
+ end
62
+ end
63
+
64
+ describe "#merge" do
65
+ it "merges all the time,event pairs in the given Enumerable into this Timeline" do
66
+ timeline.merge({ 3 => note2 }).should == MTK::Events::Timeline.from_h( timeline_raw_data.merge({ 3 => note2 }) )
67
+ end
68
+ end
69
+
70
+ describe "#empty?" do
71
+ it "is true when the timeilne has no events" do
72
+ MTK::Events::Timeline.new.empty?.should be_true
73
+ end
74
+ end
75
+
76
+ describe "#[]" do
77
+ it "returns an array of the event(s) at the timepoint" do
78
+ timeline[0].should == [note1]
79
+ timeline[1].should == [note1, note2]
80
+ end
81
+ it "returns nil when no events exist at the timepoint" do
82
+ timeline[3].should == nil
83
+ end
84
+
85
+ it "coerces the argument for consistent lookup behavior" do
86
+ timeline[0].should == [note1]
87
+ timeline[0.0].should == [note1]
88
+ timeline[Rational(0)].should == [note1]
89
+ timeline[nil].should == [note1]
90
+ end
91
+ end
92
+
93
+
94
+ describe "#[]=" do
95
+ it "set a single event at the given timepoint" do
96
+ timeline[5] = note1
97
+ timeline[5].should == [note1]
98
+ end
99
+
100
+ it "set an array of events at the given timepoint" do
101
+ timeline[5] = [note1, note2]
102
+ timeline[5].should == [note1, note2]
103
+ end
104
+
105
+ it "replaces existing events at the timepoint" do
106
+ timeline[5] = note1
107
+ timeline[5] = note2
108
+ timeline[5].should == [note2]
109
+ end
110
+
111
+ it "coerces the argument to floating point for consistent lookup behavior" do
112
+ timeline = MTK::Events::Timeline.new
113
+ timeline[nil] = note1
114
+ timeline[1] = note1
115
+ timeline[Rational(3,2)] = note1
116
+ timeline.times.should == [0.0, 1.0, 1.5]
117
+ end
118
+ end
119
+
120
+
121
+ describe "#add" do
122
+ it "creates a new event list at a previously empty timepoint" do
123
+ timeline.add(5, note1)
124
+ timeline[5].should == [note1]
125
+ end
126
+
127
+ it "appends to existing event lists" do
128
+ timeline.add(5, note1)
129
+ timeline.add(5, note2)
130
+ timeline[5].should == [note1, note2]
131
+ end
132
+
133
+ it "accepts a list of events as its second argument" do
134
+ timeline.add 5, [note1, note2]
135
+ timeline[5].should == [note1, note2]
136
+ end
137
+
138
+ it "coerces the argument to floating point for consistent lookup behavior" do
139
+ timeline = MTK::Events::Timeline.new
140
+ timeline.add(nil, note1)
141
+ timeline.add(1, note1)
142
+ timeline.add(Rational(3,2), note1)
143
+ timeline.times.should == [0.0, 1.0, 1.5]
144
+ end
145
+ end
146
+
147
+ describe "#delete" do
148
+ it "removes an event list at the given time" do
149
+ timeline.delete(1)
150
+ timeline.should == { 0.0 => [note1] }
151
+ end
152
+
153
+ it "coerces the argument to floating point for consistent lookup behavior" do
154
+ timeline.delete(Rational(1))
155
+ timeline.should == { 0.0 => [note1] }
156
+ end
157
+ end
158
+
159
+ describe "#has_time?" do
160
+ it "returns true if the time has been assigned" do
161
+ (timeline.has_time? 1).should be_true
162
+ end
163
+ it "returns false if the time doesn't exist" do
164
+ (timeline.has_time? 3).should be_false
165
+ end
166
+ end
167
+
168
+ describe "#times" do
169
+ it "is the sorted list of times" do
170
+ timeline.times.should == [0,1]
171
+ end
172
+ end
173
+
174
+ describe "length" do
175
+ it "is the lastest time + the longest duration of events at that time" do
176
+ len = timeline.length
177
+ len.should be_a ::MTK::Core::Duration
178
+ len.should == ::MTK.Duration(3)
179
+ end
180
+ end
181
+
182
+ describe "empty?" do
183
+ it "is true when there are no events in the timeline" do
184
+ MTK::Events::Timeline.new.empty?.should be_true
185
+ end
186
+
187
+ it "is false where there are events in the timeline" do
188
+ timeline.empty?.should be_false
189
+ end
190
+ end
191
+
192
+ describe "#==" do
193
+ it "is true when the underlying Hashes are equal" do
194
+ timeline.should == MTK::Events::Timeline.from_h(timeline_hash)
195
+ end
196
+ it "is false when the underlying Hashes are not equal" do
197
+ timeline.should_not == MTK::Events::Timeline.from_h( {0 => [note2], 1 => [note1, note2]} )
198
+ end
199
+ it "allows for direct comparison to hashes" do
200
+ timeline.should == timeline_hash
201
+ end
202
+ end
203
+
204
+ describe "#events" do
205
+ it "is all events in a flattened array" do
206
+ timeline.events.should == [note1, note1, note2]
207
+ end
208
+ end
209
+
210
+ describe "#each" do
211
+ it "yields each |time,event_list| pair" do
212
+ yielded = []
213
+ timeline.each{|time,events| yielded << [time,events] }
214
+ yielded.should == [ [0,[note1]], [1,[note1,note2]] ]
215
+ end
216
+ end
217
+
218
+ describe "#map" do
219
+ it "returns a new MTK::Events::Timeline where each [time,event] pair is replaced by the result of block" do
220
+ mapped = timeline.map{|time,events| [time+1, events.map{|e| e.transpose(time+2) }] }
221
+ mapped.should == { 1.0 => [note1.transpose(2)], 2.0 => [note1.transpose(3), note2.transpose(3)] }
222
+ end
223
+
224
+ it "handle events from different times being mapped to the same time" do
225
+ timeline = MTK::Events::Timeline.from_h({ 0.0 => [note1], 1.0 => [note1], 2.0 => [note2] })
226
+ mapped = timeline.map do |time,events|
227
+ if events == [note1]
228
+ [1.0, events]
229
+ else
230
+ [2.0, events]
231
+ end
232
+ end
233
+ mapped.should == { 1.0 => [note1,note1], 2.0 => [note2] }
234
+ end
235
+
236
+ it "does not modify this MTK::Events::Timeline" do
237
+ timeline.map{|t,e| [0,nil] }
238
+ timeline.should == timeline_hash
239
+ end
240
+ end
241
+
242
+ describe "#map!" do
243
+ it "maps the MTK::Events::Timeline in place" do
244
+ timeline.map! {|time,events| [time+1, events.map{|e| e.transpose(time+2) }] }
245
+ timeline.should == { 1.0 => [note1.transpose(2)], 2.0 => [note1.transpose(3), note2.transpose(3)] }
246
+ end
247
+ end
248
+
249
+ describe "#map_events" do
250
+ it "maps the MTK::Events::Timeline in place" do
251
+ mapped = timeline.map_events {|event| event.transpose(1) }
252
+ mapped.should == { 0.0 => [note1.transpose(1)], 1.0 => [note1.transpose(1), note2.transpose(1)] }
253
+ end
254
+
255
+ it "does not modify this MTK::Events::Timeline" do
256
+ timeline.map_events {|event| event.transpose(1) }
257
+ timeline.should == timeline_hash
258
+ end
259
+ end
260
+
261
+ describe "#map_events!" do
262
+ it "maps the MTK::Events::Timeline in place" do
263
+ timeline.map_events! {|event| event.transpose(1.0) }
264
+ timeline.should == { 0.0 => [note1.transpose(1)], 1.0 => [note1.transpose(1), note2.transpose(1)] }
265
+ end
266
+ end
267
+
268
+ describe "#compact!" do
269
+ it "removes empty event lists" do
270
+ timeline[3] = []
271
+ timeline[4] = []
272
+ timeline.compact!
273
+ timeline.should == timeline_hash
274
+ end
275
+ end
276
+
277
+ describe "#quantize" do
278
+ it "maps all times to the nearest multiple of the given interval" do
279
+ unquantized_timeline.quantize(quantization_interval).should == quantized_data
280
+ end
281
+ it "returns a new MTK::Events::Timeline and does not modify the original" do
282
+ unquantized_timeline.quantize(quantization_interval)
283
+ unquantized_timeline.should == unquantized_data
284
+ end
285
+ end
286
+
287
+ describe "#quantize!" do
288
+ it "maps all times to the nearest multiple of the given interval" do
289
+ unquantized_timeline.quantize!(quantization_interval).should == quantized_data
290
+ end
291
+
292
+ it "modifies the MTK::Events::Timeline in place" do
293
+ unquantized_timeline.quantize!(quantization_interval)
294
+ unquantized_timeline.should == quantized_data
295
+ end
296
+ end
297
+
298
+ describe "#shift" do
299
+ it "shifts all times by the given amount" do
300
+ timeline.shift(shift_amount).should == shifted_data
301
+ end
302
+
303
+ it "goes back in time for negative arguments" do
304
+ timeline.shift(-shift_amount).should == reverse_shifted_data
305
+ end
306
+
307
+ it "returns an instance of the same type" do
308
+ timeline.shift(shift_amount).should be_a timeline.class
309
+ end
310
+
311
+ it "returns a new MTK::Events::Timeline and does not modify the original" do
312
+ timeline.shift(shift_amount).should_not equal timeline
313
+ end
314
+ end
315
+
316
+ describe "#shift!" do
317
+ it "shifts all times by the given amount" do
318
+ timeline.shift!(shift_amount).should == shifted_data
319
+ end
320
+
321
+ it "goes back in time for negative arguments" do
322
+ timeline.shift!(-shift_amount).should == reverse_shifted_data
323
+ end
324
+
325
+ it "modifies the timeline in place" do
326
+ timeline.shift!(shift_amount).should equal timeline
327
+ end
328
+ end
329
+
330
+ describe "#shift_to" do
331
+ it "shifts so the start is at the given time" do
332
+ MTK::Events::Timeline.from_h(shifted_data).shift_to(0).should == timeline
333
+ MTK::Events::Timeline.from_h(reverse_shifted_data).shift_to(0).should == timeline
334
+ end
335
+
336
+ it "returns an instance of the same type" do
337
+ timeline.shift_to(shift_amount).should be_a timeline.class
338
+ end
339
+
340
+ it "returns a new MTK::Events::Timeline and does not modify the original" do
341
+ timeline.shift_to(shift_amount).should_not equal timeline
342
+ end
343
+ end
344
+
345
+ describe "#shift_to!" do
346
+ it "shifts so the start is at the given time" do
347
+ MTK::Events::Timeline.from_h(shifted_data).shift_to!(0).should == timeline
348
+ MTK::Events::Timeline.from_h(reverse_shifted_data).shift_to!(0).should == timeline
349
+ end
350
+
351
+ it "modifies the timeline in place" do
352
+ timeline.shift_to!(shift_amount).should equal timeline
353
+ end
354
+ end
355
+
356
+ describe "#flatten" do
357
+ it "flattens nested timelines so that all nested subtimes are converted to absolute times in a single timeline" do
358
+ timeline[10] = MTK::Events::Timeline.from_h({ 0 => note2, 1 => note1 })
359
+ timeline.flatten.should == timeline_hash.merge({ 10.0 => [note2], 11.0 => [note1] })
360
+ end
361
+
362
+ it "handles nested timelines which have nested timelines inside of them" do
363
+ nested = MTK::Events::Timeline.from_h({ 0 => note1 })
364
+ timeline[10] = MTK::Events::Timeline.from_h({ 100 => nested })
365
+ timeline.flatten.should == timeline_hash.merge({ 110.0 => [note1] })
366
+ end
367
+
368
+ it "handles multiple nested timeslines at the same time point" do
369
+ timeline[10] = [ MTK::Events::Timeline.from_h({ 0 => note2, 1 => note1 }), MTK::Events::Timeline.from_h({ 2 => note1, 3 => note2 })]
370
+ timeline.flatten.should == timeline_hash.merge({ 10.0 => [note2], 11.0 => [note1], 12.0 => [note1], 13.0 => [note2] })
371
+ end
372
+
373
+ it "returns a new MTK::Events::Timeline" do
374
+ timeline.flatten.should_not equal(timeline)
375
+ end
376
+ end
377
+
378
+ describe "#clone" do
379
+ it "creates an equal MTK::Events::Timeline" do
380
+ timeline.clone.should == timeline
381
+ end
382
+
383
+ it "returns a new instance" do
384
+ timeline.clone.should_not equal(timeline)
385
+ end
386
+ end
387
+
388
+ describe ".quantize_time" do
389
+ it "takes a time and an interval, and returns the nearest multiple of the interval to the time" do
390
+ MTK::Events::Timeline.quantize_time(23,10).should == 20
391
+ MTK::Events::Timeline.quantize_time(27,10).should == 30
392
+ MTK::Events::Timeline.quantize_time(30,10).should == 30
393
+ end
394
+
395
+ it "rounds up when exactly between 2 intervals" do
396
+ MTK::Events::Timeline.quantize_time(25,10).should == 30
397
+ end
398
+
399
+ it "handles fractional intervals" do
400
+ MTK::Events::Timeline.quantize_time(13,2.5).should == 12.5
401
+ end
402
+ end
403
+
404
+ describe "#to_s" do
405
+ it "has one line per time" do
406
+ timeline.to_s.split("\n").size.should == timeline.times.size
407
+ end
408
+
409
+ it "has a time => event_list mapping on each line" do
410
+ for line in timeline.to_s.split("\n")
411
+ line.should =~ /=>/
412
+ end
413
+ end
414
+
415
+ it "pretty prints the output by aligning all '=>' arrows" do
416
+ # alignment is only a factor when the times have different numbers of digits:
417
+ timeline = MTK::Events::Timeline.new
418
+ timeline.add 0, note1
419
+ timeline.add 10, note1
420
+ timeline.add 1000, note1
421
+ lines = timeline.to_s.split("\n")
422
+ last = lines.pop
423
+ arrow_position = last =~ /=>/
424
+ for line in lines
425
+ (line =~ /=>/).should == arrow_position
426
+ end
427
+ end
428
+ end
429
+ end
430
+
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Groups::Chord do
4
+
5
+ CHORD = MTK::Groups::Chord
6
+
7
+ let(:c_major) { CHORD.new([C4,E4,G4]) }
8
+
9
+ describe ".new" do
10
+ it "removes duplicates" do
11
+ CHORD.new([C4, E4, G4, C4]).pitches.should == [C4, E4, G4]
12
+ end
13
+
14
+ it "sorts the pitches" do
15
+ CHORD.new([F4, G4, E4, D4, C4]).pitches.should == [C4, D4, E4, F4, G4]
16
+ end
17
+ end
18
+
19
+ describe '#inversion' do
20
+ it "adds an octave to the chord's pitches starting from the lowest, for each whole number in a postive argument" do
21
+ c_major.inversion(2).should == CHORD.new([G4,C5,E5])
22
+ end
23
+
24
+ it "subtracts an octave to the chord's pitches starting fromt he highest, for each whole number in a negative argument" do
25
+ c_major.inversion(-2).should == CHORD.new([E3,G3,C4])
26
+ end
27
+
28
+ it "wraps around to the lowest pitch when the argument is bigger than the number of pitches in the chord (positive argument)" do
29
+ c_major.inversion(4).should == CHORD.new([E5,G5,C6])
30
+ end
31
+
32
+ it "wraps around to the highest pitch when the magnitude of the argument is bigger than the number of pitches in the chord (negative argument)" do
33
+ c_major.inversion(-4).should == CHORD.new([G2,C3,E3])
34
+ end
35
+ end
36
+
37
+ describe "#nearest" do
38
+ it "returns the nearest Melody where the first Pitch has the given PitchClass" do
39
+ c_major.nearest(F).should == c_major.transpose(5)
40
+ c_major.nearest(G).should == c_major.transpose(-5)
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ describe MTK do
47
+
48
+ describe '#Chord' do
49
+
50
+ it "acts like new for a single Array argument" do
51
+ Chord([C4,D4]).should == CHORD.new([C4,D4])
52
+ end
53
+
54
+ it "acts like new for multiple arguments, by treating them like an Array (splat)" do
55
+ Chord(C4,D4).should == CHORD.new([C4,D4])
56
+ end
57
+
58
+ it "handles an Array with elements that can be converted to Pitches" do
59
+ Chord(['C4','D4']).should == CHORD.new([C4,D4])
60
+ end
61
+
62
+ it "handles multiple arguments that can be converted to a Pitch" do
63
+ Chord(:C4,:D4).should == CHORD.new([C4,D4])
64
+ end
65
+
66
+ it "handles a single Pitch" do
67
+ Chord(C4).should == CHORD.new([C4])
68
+ end
69
+
70
+ it "handles single elements that can be converted to a Pitch" do
71
+ Chord('C4').should == CHORD.new([C4])
72
+ end
73
+
74
+ it "returns the argument if it's already a Chord" do
75
+ pitch_set = CHORD.new([C4,D4,D4])
76
+ Chord(pitch_set).should == CHORD.new([C4,D4])
77
+ end
78
+
79
+ it "raises an error for types it doesn't understand" do
80
+ lambda{ Chord({:not => :compatible}) }.should raise_error
81
+ end
82
+
83
+ end
84
+
85
+ end