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,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
|