mtk 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +9 -0
- data/INTRO.md +73 -0
- data/LICENSE.txt +27 -0
- data/README.md +93 -18
- data/Rakefile +13 -1
- data/examples/crescendo.rb +20 -0
- data/examples/dynamic_pattern.rb +39 -0
- data/examples/play_midi.rb +19 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/tone_row_melody.rb +21 -0
- data/lib/mtk/_constants/durations.rb +80 -0
- data/lib/mtk/_constants/intensities.rb +81 -0
- data/lib/mtk/{constants → _constants}/intervals.rb +10 -1
- data/lib/mtk/_constants/pitch_classes.rb +35 -0
- data/lib/mtk/_constants/pitches.rb +49 -0
- data/lib/mtk/{numeric_extensions.rb → _numeric_extensions.rb} +0 -0
- data/lib/mtk/event.rb +14 -5
- data/lib/mtk/helper/collection.rb +114 -0
- data/lib/mtk/helper/event_builder.rb +85 -0
- data/lib/mtk/{constants → helper}/pseudo_constants.rb +7 -6
- data/lib/mtk/lang/grammar.rb +17 -0
- data/lib/mtk/lang/mtk_grammar.citrus +60 -0
- data/lib/mtk/midi/file.rb +10 -15
- data/lib/mtk/midi/jsound_input.rb +68 -0
- data/lib/mtk/midi/jsound_output.rb +80 -0
- data/lib/mtk/note.rb +22 -3
- data/lib/mtk/pattern/abstract_pattern.rb +132 -0
- data/lib/mtk/pattern/choice.rb +25 -9
- data/lib/mtk/pattern/cycle.rb +51 -0
- data/lib/mtk/pattern/enumerator.rb +26 -0
- data/lib/mtk/pattern/function.rb +46 -0
- data/lib/mtk/pattern/lines.rb +60 -0
- data/lib/mtk/pattern/palindrome.rb +42 -0
- data/lib/mtk/pattern/sequence.rb +15 -50
- data/lib/mtk/pitch.rb +45 -6
- data/lib/mtk/pitch_class.rb +36 -35
- data/lib/mtk/pitch_class_set.rb +46 -14
- data/lib/mtk/pitch_set.rb +20 -31
- data/lib/mtk/sequencer/abstract_sequencer.rb +85 -0
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +29 -0
- data/lib/mtk/sequencer/step_sequencer.rb +26 -0
- data/lib/mtk/timeline.rb +75 -22
- data/lib/mtk/transform/invertible.rb +15 -0
- data/lib/mtk/{util → transform}/mappable.rb +6 -2
- data/lib/mtk/transform/set_theory_operations.rb +34 -0
- data/lib/mtk/transform/transposable.rb +14 -0
- data/lib/mtk.rb +56 -22
- data/spec/mtk/_constants/durations_spec.rb +118 -0
- data/spec/mtk/{constants/dynamics_spec.rb → _constants/intensities_spec.rb} +48 -17
- data/spec/mtk/{constants → _constants}/intervals_spec.rb +21 -0
- data/spec/mtk/_constants/pitch_classes_spec.rb +58 -0
- data/spec/mtk/_constants/pitches_spec.rb +52 -0
- data/spec/mtk/{numeric_extensions_spec.rb → _numeric_extensions_spec.rb} +0 -0
- data/spec/mtk/event_spec.rb +19 -0
- data/spec/mtk/helper/collection_spec.rb +291 -0
- data/spec/mtk/helper/event_builder_spec.rb +92 -0
- data/spec/mtk/helper/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/grammar_spec.rb +100 -0
- data/spec/mtk/midi/file_spec.rb +41 -6
- data/spec/mtk/note_spec.rb +53 -3
- data/spec/mtk/pattern/abstract_pattern_spec.rb +45 -0
- data/spec/mtk/pattern/choice_spec.rb +89 -3
- data/spec/mtk/pattern/cycle_spec.rb +133 -0
- data/spec/mtk/pattern/function_spec.rb +133 -0
- data/spec/mtk/pattern/lines_spec.rb +93 -0
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +116 -0
- data/spec/mtk/pattern/palindrome_spec.rb +124 -0
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +47 -0
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +37 -0
- data/spec/mtk/pattern/sequence_spec.rb +128 -31
- data/spec/mtk/pitch_class_set_spec.rb +240 -7
- data/spec/mtk/pitch_class_spec.rb +84 -18
- data/spec/mtk/pitch_set_spec.rb +45 -10
- data/spec/mtk/pitch_spec.rb +59 -0
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +159 -0
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +49 -0
- data/spec/mtk/sequencer/step_sequencer_spec.rb +71 -0
- data/spec/mtk/timeline_spec.rb +118 -15
- data/spec/spec_helper.rb +4 -3
- metadata +59 -22
- data/lib/mtk/chord.rb +0 -47
- data/lib/mtk/constants/dynamics.rb +0 -56
- data/lib/mtk/constants/pitch_classes.rb +0 -18
- data/lib/mtk/constants/pitches.rb +0 -24
- data/lib/mtk/pattern/note_sequence.rb +0 -60
- data/lib/mtk/pattern/pitch_sequence.rb +0 -22
- data/lib/mtk/patterns.rb +0 -4
- data/spec/mtk/chord_spec.rb +0 -74
- data/spec/mtk/constants/pitch_classes_spec.rb +0 -35
- data/spec/mtk/constants/pitches_spec.rb +0 -23
- data/spec/mtk/pattern/note_sequence_spec.rb +0 -121
- data/spec/mtk/pattern/pitch_sequence_spec.rb +0 -47
@@ -23,34 +23,62 @@ describe MTK::PitchClass do
|
|
23
23
|
it "is the 12 note names in western chromatic scale" do
|
24
24
|
PitchClass::NAMES =~ ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']
|
25
25
|
end
|
26
|
+
|
27
|
+
it "is immutable" do
|
28
|
+
lambda{ PitchClass::NAMES << 'H' }.should raise_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'VALID_NAMES' do
|
33
|
+
it "is all enharmonic spellings of NAMES including sharps, flats, double-sharps, and double-flats" do
|
34
|
+
PitchClass::VALID_NAMES =~ names
|
35
|
+
end
|
36
|
+
|
37
|
+
it "is immutable" do
|
38
|
+
lambda{ PitchClass::VALID_NAMES << 'H' }.should raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.new' do
|
43
|
+
it "is private" do
|
44
|
+
lambda{ PitchClass.new('C',0) }.should raise_error
|
45
|
+
end
|
26
46
|
end
|
27
47
|
|
28
48
|
describe '.from_s' do
|
29
49
|
context "the argument is a valid name" do
|
30
50
|
it "returns a PitchClass" do
|
31
|
-
names.each { |name| PitchClass
|
51
|
+
names.each { |name| PitchClass[name].should be_a PitchClass }
|
32
52
|
end
|
33
53
|
it "returns an object with that name" do
|
34
|
-
names.each { |name| PitchClass
|
54
|
+
names.each { |name| PitchClass[name].name.should == name }
|
35
55
|
end
|
36
56
|
it "ignores case" do
|
37
57
|
for name in names
|
38
|
-
PitchClass
|
39
|
-
PitchClass
|
58
|
+
PitchClass[name.upcase].name.should == name
|
59
|
+
PitchClass[name.downcase].name.should == name
|
40
60
|
end
|
41
61
|
end
|
42
62
|
end
|
43
63
|
context "the argument is not a valid name" do
|
44
64
|
it "returns nil, if the name doesn't exist" do
|
45
|
-
PitchClass
|
65
|
+
PitchClass['z'].should be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '.from_s' do
|
71
|
+
it "acts like .[]" do
|
72
|
+
for name in ['C', 'bbb', 'z']
|
73
|
+
PitchClass.from_s(name).should == PitchClass[name]
|
46
74
|
end
|
47
75
|
end
|
48
76
|
end
|
49
77
|
|
50
78
|
describe '.from_name' do
|
51
|
-
it "acts like
|
79
|
+
it "acts like .[]" do
|
52
80
|
for name in ['C', 'bbb', 'z']
|
53
|
-
PitchClass.from_name(name).should == PitchClass
|
81
|
+
PitchClass.from_name(name).should == PitchClass[name]
|
54
82
|
end
|
55
83
|
end
|
56
84
|
end
|
@@ -59,21 +87,13 @@ describe MTK::PitchClass do
|
|
59
87
|
it "returns the PitchClass with that value" do
|
60
88
|
PitchClass.from_i(2).should == D
|
61
89
|
end
|
90
|
+
|
62
91
|
it "returns the PitchClass with that value mod 12" do
|
63
92
|
PitchClass.from_i(14).should == D
|
64
93
|
PitchClass.from_i(-8).should == E
|
65
94
|
end
|
66
95
|
end
|
67
96
|
|
68
|
-
describe '.[]' do
|
69
|
-
it "acts like from_name if the argument is a string" do
|
70
|
-
PitchClass['D'].should == PitchClass.from_name('D')
|
71
|
-
end
|
72
|
-
it "acts like from_i if the argument is a number" do
|
73
|
-
PitchClass[3].should == PitchClass.from_i(3)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
97
|
describe '#name' do
|
78
98
|
it "is the name of the pitch class" do
|
79
99
|
C.name.should == 'C'
|
@@ -103,8 +123,8 @@ describe MTK::PitchClass do
|
|
103
123
|
C.should_not == D
|
104
124
|
end
|
105
125
|
it "treats enharmonic names as equal" do
|
106
|
-
C.should == PitchClass
|
107
|
-
C.should == PitchClass
|
126
|
+
C.should == PitchClass('B#')
|
127
|
+
C.should == PitchClass('Dbb')
|
108
128
|
end
|
109
129
|
end
|
110
130
|
|
@@ -125,6 +145,12 @@ describe MTK::PitchClass do
|
|
125
145
|
end
|
126
146
|
end
|
127
147
|
|
148
|
+
describe "#transpose" do
|
149
|
+
it "behaves like #+" do
|
150
|
+
C.transpose(2.semitones).should == C + 2
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
128
154
|
describe '#-' do
|
129
155
|
it "subtracts the integer value of the argument from #to_i" do
|
130
156
|
(E - 2).should == D
|
@@ -135,6 +161,20 @@ describe MTK::PitchClass do
|
|
135
161
|
end
|
136
162
|
end
|
137
163
|
|
164
|
+
describe "#invert" do
|
165
|
+
it 'inverts the pitch class around the given center pitch class' do
|
166
|
+
E.invert(D).should == C
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'wraps around octaves as needed (always returns a valid pitch class)' do
|
170
|
+
E.invert(B).should == Gb
|
171
|
+
end
|
172
|
+
|
173
|
+
it "returns the pitch class when the argument is the same pitch class" do
|
174
|
+
E.invert(E).should == E
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
138
178
|
describe "#distance_to" do
|
139
179
|
it "is the distance in semitones between 2 PitchClass objects" do
|
140
180
|
C.distance_to(D).should == 2
|
@@ -163,3 +203,29 @@ describe MTK::PitchClass do
|
|
163
203
|
end
|
164
204
|
|
165
205
|
end
|
206
|
+
|
207
|
+
describe MTK do
|
208
|
+
|
209
|
+
describe '#PitchClass' do
|
210
|
+
it "acts like from_s if the argument is a String" do
|
211
|
+
PitchClass('D').should == PitchClass.from_s('D')
|
212
|
+
end
|
213
|
+
|
214
|
+
it "acts like from_s if the argument is a Symbol" do
|
215
|
+
PitchClass(:D).should == PitchClass.from_s('D')
|
216
|
+
end
|
217
|
+
|
218
|
+
it "acts like from_i if the argument is a Numberic" do
|
219
|
+
PitchClass(3).should == PitchClass.from_i(3)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "returns the argument if it's already a PitchClass" do
|
223
|
+
PitchClass(C).should be_equal C
|
224
|
+
end
|
225
|
+
|
226
|
+
it "raises an error for types it doesn't understand" do
|
227
|
+
lambda{ PitchClass({:not => :compatible}) }.should raise_error
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
data/spec/mtk/pitch_set_spec.rb
CHANGED
@@ -83,15 +83,9 @@ describe MTK::PitchSet do
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
describe '
|
86
|
+
describe '#transpose' do
|
87
87
|
it 'transposes upward by the given semitones' do
|
88
|
-
(
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
describe '#-' do
|
93
|
-
it 'transposes downward by the given semitones' do
|
94
|
-
(pitch_set - 12).should == PitchSet.new([C3, D3, E3, F3, G3, A3, B3])
|
88
|
+
pitch_set.transpose(12).should == PitchSet.new([C5, D5, E5, F5, G5, A5, B5])
|
95
89
|
end
|
96
90
|
end
|
97
91
|
|
@@ -149,8 +143,8 @@ describe MTK::PitchSet do
|
|
149
143
|
|
150
144
|
describe "#nearest" do
|
151
145
|
it "returns the nearest PitchSet where the first Pitch has the given PitchClass" do
|
152
|
-
c_major.nearest(F).should == c_major
|
153
|
-
c_major.nearest(G).should == c_major
|
146
|
+
c_major.nearest(F).should == c_major.transpose(5.semitones)
|
147
|
+
c_major.nearest(G).should == c_major.transpose(-5.semitones)
|
154
148
|
end
|
155
149
|
end
|
156
150
|
|
@@ -161,3 +155,44 @@ describe MTK::PitchSet do
|
|
161
155
|
end
|
162
156
|
|
163
157
|
end
|
158
|
+
|
159
|
+
describe MTK do
|
160
|
+
|
161
|
+
describe '#PitchSet' do
|
162
|
+
|
163
|
+
it "acts like new for a single Array argument" do
|
164
|
+
PitchSet([C4,D4]).should == PitchSet.new([C4,D4])
|
165
|
+
end
|
166
|
+
|
167
|
+
it "acts like new for multiple arguments, by treating them like an Array (splat)" do
|
168
|
+
PitchSet(C4,D4).should == PitchSet.new([C4,D4])
|
169
|
+
end
|
170
|
+
|
171
|
+
it "handles an Array with elements that can be converted to Pitches" do
|
172
|
+
PitchSet(['C4','D4']).should == PitchSet.new([C4,D4])
|
173
|
+
end
|
174
|
+
|
175
|
+
it "handles multiple arguments that can be converted to a Pitch" do
|
176
|
+
PitchSet(:C4,:D4).should == PitchSet.new([C4,D4])
|
177
|
+
end
|
178
|
+
|
179
|
+
it "handles a single Pitch" do
|
180
|
+
PitchSet(C4).should == PitchSet.new([C4])
|
181
|
+
end
|
182
|
+
|
183
|
+
it "handles single elements that can be converted to a Pitch" do
|
184
|
+
PitchSet('C4').should == PitchSet.new([C4])
|
185
|
+
end
|
186
|
+
|
187
|
+
it "returns the argument if it's already a PitchSet" do
|
188
|
+
pitch_set = PitchSet.new([C4,D4])
|
189
|
+
PitchSet(pitch_set).should be_equal pitch_set
|
190
|
+
end
|
191
|
+
|
192
|
+
it "raises an error for types it doesn't understand" do
|
193
|
+
lambda{ PitchSet({:not => :compatible}) }.should raise_error
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
data/spec/mtk/pitch_spec.rb
CHANGED
@@ -11,9 +11,14 @@ describe MTK::Pitch do
|
|
11
11
|
it "constructs and caches a pitch with the given pitch_class and octave" do
|
12
12
|
Pitch[C,4].should be_equal Pitch[C,4]
|
13
13
|
end
|
14
|
+
|
14
15
|
it "retains the new() method's ability to construct uncached objects" do
|
15
16
|
Pitch.new(C,4).should_not be_equal Pitch[C,4]
|
16
17
|
end
|
18
|
+
|
19
|
+
it "can handle any type for the first argument that's supported by MTK::PitchClass()" do
|
20
|
+
Pitch['C',4].should == Pitch[0, 4]
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
describe '#pitch_class' do
|
@@ -62,6 +67,16 @@ describe MTK::Pitch do
|
|
62
67
|
it("converts 'C4' to middle c") { Pitch.from_s('C4').should == middle_c }
|
63
68
|
it("converts 'c4' to middle c") { Pitch.from_s('c4').should == middle_c }
|
64
69
|
it("converts 'B#4' to middle c") { Pitch.from_s('B#4').should == middle_c }
|
70
|
+
it("converts 'C-1' to a low c, 5 octaves below middle C") { Pitch.from_s('C-1').should == middle_c - 60 }
|
71
|
+
it("converts 'C4+50.0cents' to middle C and 50 cents") { Pitch.from_s('C4+50.0cents').should == middle_c_and_50_cents }
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.from_name' do
|
75
|
+
it "acts like .from_s" do
|
76
|
+
for name in ['C4', 'c4', 'B#4', 'C-1', 'C4+50.0cents']
|
77
|
+
Pitch.from_name(name).should == Pitch.from_s(name)
|
78
|
+
end
|
79
|
+
end
|
65
80
|
end
|
66
81
|
|
67
82
|
describe ".from_hash" do
|
@@ -139,6 +154,12 @@ describe MTK::Pitch do
|
|
139
154
|
end
|
140
155
|
end
|
141
156
|
|
157
|
+
describe "#transpose" do
|
158
|
+
it "behaves like #+" do
|
159
|
+
middle_c.transpose(2.semitones).should == middle_c + 2
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
142
163
|
describe '#-' do
|
143
164
|
it 'subtracts the integer value of the argument from #to_i' do
|
144
165
|
(middle_c - 2).should == Pitch.from_i(58)
|
@@ -168,6 +189,10 @@ describe MTK::Pitch do
|
|
168
189
|
middle_c.invert(Pitch.from_i 54).should == Pitch.from_i(48)
|
169
190
|
end
|
170
191
|
end
|
192
|
+
|
193
|
+
it "returns an equal pitch when given itself as an argument" do
|
194
|
+
middle_c.invert(middle_c).should == middle_c
|
195
|
+
end
|
171
196
|
end
|
172
197
|
|
173
198
|
describe "#nearest" do
|
@@ -215,3 +240,37 @@ describe MTK::Pitch do
|
|
215
240
|
end
|
216
241
|
|
217
242
|
end
|
243
|
+
|
244
|
+
describe MTK do
|
245
|
+
|
246
|
+
describe '#Pitch' do
|
247
|
+
it "acts like from_s if the argument is a String" do
|
248
|
+
Pitch('D4').should == Pitch.from_s('D4')
|
249
|
+
end
|
250
|
+
|
251
|
+
it "acts like from_s if the argument is a Symbol" do
|
252
|
+
Pitch(:D4).should == Pitch.from_s(:D4)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "acts like from_f if the argument is a Numberic" do
|
256
|
+
Pitch(3).should == Pitch.from_f(3)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "returns the argument if it's already a PitchClass" do
|
260
|
+
Pitch(C4).should be_equal C4
|
261
|
+
end
|
262
|
+
|
263
|
+
it "acts like Pitch[] for a 2-element Array" do
|
264
|
+
Pitch(C,4).should == Pitch[C,4]
|
265
|
+
end
|
266
|
+
|
267
|
+
it "acts like Pitch.new() for a 3-element Array" do
|
268
|
+
Pitch(C, 4, 0.5).should == Pitch.new(C, 4, 0.5)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "raises an error for types it doesn't understand" do
|
272
|
+
lambda{ Pitch({:not => :compatible}) }.should raise_error
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Sequencer::AbstractSequencer do
|
4
|
+
|
5
|
+
ABSTRACT_SEQUENCER = Sequencer::AbstractSequencer
|
6
|
+
|
7
|
+
class MockEventBuilder < Helper::EventBuilder
|
8
|
+
attr_accessor :mock_attribute
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:patterns) { [Pattern.PitchCycle(C4,D4)] }
|
12
|
+
let(:sequencer) { ABSTRACT_SEQUENCER.new patterns }
|
13
|
+
let(:intensity) { Helper::EventBuilder::DEFAULT_INTENSITY }
|
14
|
+
let(:duration) { Helper::EventBuilder::DEFAULT_DURATION }
|
15
|
+
|
16
|
+
describe "#new" do
|
17
|
+
it "defaults @max_steps to nil" do
|
18
|
+
sequencer.max_steps.should be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "sets @max_steps from the options hash" do
|
22
|
+
sequencer = RHYTHMIC_SEQUENCER.new patterns, :max_steps => 4
|
23
|
+
sequencer.max_steps.should == 4
|
24
|
+
end
|
25
|
+
|
26
|
+
it "defaults @max_time to nil" do
|
27
|
+
sequencer.max_time.should be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "sets @max_time from the options hash" do
|
31
|
+
sequencer = RHYTHMIC_SEQUENCER.new patterns, :max_time => 4
|
32
|
+
sequencer.max_time.should == 4
|
33
|
+
end
|
34
|
+
|
35
|
+
it "defaults @event_builder to MTK::Helper::EventBuilder" do
|
36
|
+
sequencer.event_builder.should be_a MTK::Helper::EventBuilder
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sets @event_buidler from the options hash" do
|
40
|
+
sequencer = RHYTHMIC_SEQUENCER.new patterns, :event_builder => MockEventBuilder
|
41
|
+
sequencer.event_builder.should be_a MockEventBuilder
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#event_builder" do
|
46
|
+
it "provides access to the internal EventBuilder" do
|
47
|
+
sequencer = RHYTHMIC_SEQUENCER.new patterns, :event_builder => MockEventBuilder
|
48
|
+
sequencer.event_builder.mock_attribute = :value
|
49
|
+
sequencer.event_builder.mock_attribute.should == :value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#to_timeline" do
|
54
|
+
it "combines pitch, intensity, and duration patterns into notes" do
|
55
|
+
pitches = Pattern.PitchSequence(C4, D4, E4)
|
56
|
+
intensities = Pattern.IntensitySequence(0.3, 0.7, 1.0)
|
57
|
+
durations = Pattern.DurationSequence(1, 2, 3)
|
58
|
+
sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
|
59
|
+
# default implementation just increments the time by 1 for each event (more interesting behavior is provided by subclasses)
|
60
|
+
sequencer.to_timeline.should == {
|
61
|
+
0 => [Note(C4,0.3,1)],
|
62
|
+
1 => [Note(D4,0.7,2)],
|
63
|
+
2 => [Note(E4,1.0,3)]
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
it "combines patterns of different types and lengths" do
|
68
|
+
pitches = Pattern.PitchSequence(C4, D4, E4, F4, G4, A4, B4, C5)
|
69
|
+
intensities = Pattern.IntensityCycle(0.5, 1.0)
|
70
|
+
durations = Pattern.DurationPalindrome(1, 2, 3)
|
71
|
+
sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
|
72
|
+
# default implementation just increments the time by 1 for each event (more interesting behavior is provided by subclasses)
|
73
|
+
sequencer.to_timeline.should == {
|
74
|
+
0 => [Note(C4,0.5,1)],
|
75
|
+
1 => [Note(D4,1.0,2)],
|
76
|
+
2 => [Note(E4,0.5,3)],
|
77
|
+
3 => [Note(F4,1.0,2)],
|
78
|
+
4 => [Note(G4,0.5,1)],
|
79
|
+
5 => [Note(A4,1.0,2)],
|
80
|
+
6 => [Note(B4,0.5,3)],
|
81
|
+
7 => [Note(C5,1.0,2)]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "produces consistent results by reseting the patterns each time" do
|
86
|
+
pitches = Pattern.PitchSequence(C4, D4, E4)
|
87
|
+
intensities = Pattern.IntensityCycle(1)
|
88
|
+
durations = Pattern.DurationCycle(1, 2)
|
89
|
+
sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
|
90
|
+
sequencer.to_timeline.should == sequencer.to_timeline
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#time" do
|
95
|
+
it "is the current timeline time that the sequencer is generating events for" do
|
96
|
+
# AbstractSequencer just advances by 1 each step
|
97
|
+
sequencer.next # time doesn't advance until the second #next call
|
98
|
+
sequencer.time.should == 0
|
99
|
+
sequencer.next
|
100
|
+
sequencer.time.should == 1
|
101
|
+
sequencer.next
|
102
|
+
sequencer.time.should == 2
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#step" do
|
107
|
+
it "is the index for how many of times #next has been called (i.e. count starting from 0)" do
|
108
|
+
sequencer.step.should == -1 # -1 indicates #next has not yet been called
|
109
|
+
sequencer.next
|
110
|
+
sequencer.step.should == 0
|
111
|
+
sequencer.next
|
112
|
+
sequencer.step.should == 1
|
113
|
+
sequencer.next
|
114
|
+
sequencer.step.should == 2
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#next" do
|
119
|
+
it "returns a list of notes formed from the patterns in the sequencer" do
|
120
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
121
|
+
sequencer.next.should == [Note(D4,intensity,duration)]
|
122
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#rewind" do
|
127
|
+
it "resets the sequencer and its patterns" do
|
128
|
+
sequencer.next
|
129
|
+
sequencer.rewind
|
130
|
+
sequencer.step.should == -1
|
131
|
+
sequencer.time.should == 0
|
132
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
133
|
+
end
|
134
|
+
|
135
|
+
it "resets pitches properly for patterns that rely on previous pitches" do
|
136
|
+
relative_pitch_pattern = Pattern.PitchSequence(C,P8)
|
137
|
+
sequencer = ABSTRACT_SEQUENCER.new [relative_pitch_pattern]
|
138
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
139
|
+
sequencer.next.should == [Note(C5,intensity,duration)]
|
140
|
+
sequencer.rewind
|
141
|
+
sequencer.next.should == [Note(C4,intensity,duration)] # if the internal EventBuilder is not properly reset, the Note would be C5
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "#max_steps" do
|
146
|
+
it "controls the maximum number of entries in the generated timeline" do
|
147
|
+
sequencer.max_steps = 2
|
148
|
+
sequencer.to_timeline.times.length.should == 2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "#max_time" do
|
153
|
+
it "controls the maximum time in the generated timeline" do
|
154
|
+
sequencer.max_time = 4
|
155
|
+
sequencer.to_timeline.times.last.should == 4
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Sequencer::RhythmicSequencer do
|
4
|
+
|
5
|
+
RHYTHMIC_SEQUENCER = Sequencer::RhythmicSequencer
|
6
|
+
|
7
|
+
let(:pitches) { Pattern.PitchSequence(C4, D4, E4, C4) }
|
8
|
+
let(:intensities) { Pattern.IntensitySequence(0.3, 0.6, 0.9, 1.0) }
|
9
|
+
let(:durations) { Pattern.DurationSequence(1, 1, 2, 1) }
|
10
|
+
let(:rhythm) { Pattern.RhythmSequence(0.5, 1.5, 4) }
|
11
|
+
let(:rhythmic_sequencer) { RHYTHMIC_SEQUENCER.new [pitches, durations, intensities, rhythm] }
|
12
|
+
|
13
|
+
describe "#new" do
|
14
|
+
it "defaults @max_steps to nil" do
|
15
|
+
rhythmic_sequencer.max_steps.should be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "sets @max_steps from the options hash" do
|
19
|
+
rhythmic_sequencer = RHYTHMIC_SEQUENCER.new [], :max_steps => 4
|
20
|
+
rhythmic_sequencer.max_steps.should == 4
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#to_timeline" do
|
25
|
+
it "returns a Timeline" do
|
26
|
+
rhythmic_sequencer.to_timeline.should be_a Timeline
|
27
|
+
end
|
28
|
+
|
29
|
+
it "contains notes assembled from the given patterns" do
|
30
|
+
rhythmic_sequencer.to_timeline.should == Timeline.from_hash({
|
31
|
+
0 => Note(C4,0.3,1),
|
32
|
+
0.5 => Note(D4,0.6,1),
|
33
|
+
2.0 => Note(E4,0.9,2),
|
34
|
+
6.0 => Note(C4,1.0,1)
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#max_steps" do
|
40
|
+
it "controls the maximum number of times in the generated timeline" do
|
41
|
+
rhythmic_sequencer.max_steps = 2
|
42
|
+
rhythmic_sequencer.to_timeline.should == Timeline.from_hash({
|
43
|
+
0 => Note(C4,0.3,1),
|
44
|
+
0.5 => Note(D4,0.6,1)
|
45
|
+
})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Sequencer::StepSequencer do
|
4
|
+
|
5
|
+
STEP_SEQUENCER = Sequencer::StepSequencer
|
6
|
+
|
7
|
+
let(:pitches) { Pattern.PitchSequence(C4, D4, E4) }
|
8
|
+
let(:intensities) { Pattern.IntensitySequence(0.3, 0.7, 1.0) }
|
9
|
+
let(:durations) { Pattern.DurationSequence(1, 1, 2) }
|
10
|
+
let(:step_sequencer) { STEP_SEQUENCER.new [pitches, intensities, durations] }
|
11
|
+
|
12
|
+
describe "#new" do
|
13
|
+
it "defaults @step_size to 1" do
|
14
|
+
step_sequencer.step_size.should == 1
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets @step_size from the options hash" do
|
18
|
+
step_sequencer = STEP_SEQUENCER.new [], :step_size => 0.25
|
19
|
+
step_sequencer.step_size.should == 0.25
|
20
|
+
end
|
21
|
+
|
22
|
+
it "defaults @max_steps to nil" do
|
23
|
+
step_sequencer.max_steps.should be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sets @max_steps from the options hash" do
|
27
|
+
step_sequencer = STEP_SEQUENCER.new [], :max_steps => 4
|
28
|
+
step_sequencer.max_steps.should == 4
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#to_timeline" do
|
33
|
+
it "returns a Timeline" do
|
34
|
+
timeline = step_sequencer.to_timeline
|
35
|
+
timeline.should be_a Timeline
|
36
|
+
end
|
37
|
+
|
38
|
+
it "contains notes assembled from the given patterns" do
|
39
|
+
timeline = step_sequencer.to_timeline
|
40
|
+
timeline.should == Timeline.from_hash({
|
41
|
+
0 => Note(C4,0.3,1),
|
42
|
+
1 => Note(D4,0.7,1),
|
43
|
+
2 => Note(E4,1.0,2)
|
44
|
+
})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#step_size" do
|
49
|
+
it "controls the delta between each time in the generated timeline" do
|
50
|
+
step_sequencer.step_size = 2
|
51
|
+
timeline = step_sequencer.to_timeline
|
52
|
+
timeline.should == Timeline.from_hash({
|
53
|
+
0 => Note(C4,0.3,1),
|
54
|
+
2 => Note(D4,0.7,1),
|
55
|
+
4 => Note(E4,1.0,2)
|
56
|
+
})
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#max_steps" do
|
61
|
+
it "controls the maximum number of times in the generated timeline" do
|
62
|
+
step_sequencer.max_steps = 2
|
63
|
+
timeline = step_sequencer.to_timeline
|
64
|
+
timeline.should == Timeline.from_hash({
|
65
|
+
0 => Note(C4,0.3,1),
|
66
|
+
1 => Note(D4,0.7,1)
|
67
|
+
})
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|