mtk 0.0.1 → 0.0.2
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 +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
|