mtk 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.yardopts +9 -0
  2. data/INTRO.md +73 -0
  3. data/LICENSE.txt +27 -0
  4. data/README.md +93 -18
  5. data/Rakefile +13 -1
  6. data/examples/crescendo.rb +20 -0
  7. data/examples/dynamic_pattern.rb +39 -0
  8. data/examples/play_midi.rb +19 -0
  9. data/examples/print_midi.rb +13 -0
  10. data/examples/random_tone_row.rb +18 -0
  11. data/examples/tone_row_melody.rb +21 -0
  12. data/lib/mtk/_constants/durations.rb +80 -0
  13. data/lib/mtk/_constants/intensities.rb +81 -0
  14. data/lib/mtk/{constants → _constants}/intervals.rb +10 -1
  15. data/lib/mtk/_constants/pitch_classes.rb +35 -0
  16. data/lib/mtk/_constants/pitches.rb +49 -0
  17. data/lib/mtk/{numeric_extensions.rb → _numeric_extensions.rb} +0 -0
  18. data/lib/mtk/event.rb +14 -5
  19. data/lib/mtk/helper/collection.rb +114 -0
  20. data/lib/mtk/helper/event_builder.rb +85 -0
  21. data/lib/mtk/{constants → helper}/pseudo_constants.rb +7 -6
  22. data/lib/mtk/lang/grammar.rb +17 -0
  23. data/lib/mtk/lang/mtk_grammar.citrus +60 -0
  24. data/lib/mtk/midi/file.rb +10 -15
  25. data/lib/mtk/midi/jsound_input.rb +68 -0
  26. data/lib/mtk/midi/jsound_output.rb +80 -0
  27. data/lib/mtk/note.rb +22 -3
  28. data/lib/mtk/pattern/abstract_pattern.rb +132 -0
  29. data/lib/mtk/pattern/choice.rb +25 -9
  30. data/lib/mtk/pattern/cycle.rb +51 -0
  31. data/lib/mtk/pattern/enumerator.rb +26 -0
  32. data/lib/mtk/pattern/function.rb +46 -0
  33. data/lib/mtk/pattern/lines.rb +60 -0
  34. data/lib/mtk/pattern/palindrome.rb +42 -0
  35. data/lib/mtk/pattern/sequence.rb +15 -50
  36. data/lib/mtk/pitch.rb +45 -6
  37. data/lib/mtk/pitch_class.rb +36 -35
  38. data/lib/mtk/pitch_class_set.rb +46 -14
  39. data/lib/mtk/pitch_set.rb +20 -31
  40. data/lib/mtk/sequencer/abstract_sequencer.rb +85 -0
  41. data/lib/mtk/sequencer/rhythmic_sequencer.rb +29 -0
  42. data/lib/mtk/sequencer/step_sequencer.rb +26 -0
  43. data/lib/mtk/timeline.rb +75 -22
  44. data/lib/mtk/transform/invertible.rb +15 -0
  45. data/lib/mtk/{util → transform}/mappable.rb +6 -2
  46. data/lib/mtk/transform/set_theory_operations.rb +34 -0
  47. data/lib/mtk/transform/transposable.rb +14 -0
  48. data/lib/mtk.rb +56 -22
  49. data/spec/mtk/_constants/durations_spec.rb +118 -0
  50. data/spec/mtk/{constants/dynamics_spec.rb → _constants/intensities_spec.rb} +48 -17
  51. data/spec/mtk/{constants → _constants}/intervals_spec.rb +21 -0
  52. data/spec/mtk/_constants/pitch_classes_spec.rb +58 -0
  53. data/spec/mtk/_constants/pitches_spec.rb +52 -0
  54. data/spec/mtk/{numeric_extensions_spec.rb → _numeric_extensions_spec.rb} +0 -0
  55. data/spec/mtk/event_spec.rb +19 -0
  56. data/spec/mtk/helper/collection_spec.rb +291 -0
  57. data/spec/mtk/helper/event_builder_spec.rb +92 -0
  58. data/spec/mtk/helper/pseudo_constants_spec.rb +20 -0
  59. data/spec/mtk/lang/grammar_spec.rb +100 -0
  60. data/spec/mtk/midi/file_spec.rb +41 -6
  61. data/spec/mtk/note_spec.rb +53 -3
  62. data/spec/mtk/pattern/abstract_pattern_spec.rb +45 -0
  63. data/spec/mtk/pattern/choice_spec.rb +89 -3
  64. data/spec/mtk/pattern/cycle_spec.rb +133 -0
  65. data/spec/mtk/pattern/function_spec.rb +133 -0
  66. data/spec/mtk/pattern/lines_spec.rb +93 -0
  67. data/spec/mtk/pattern/note_cycle_spec.rb.bak +116 -0
  68. data/spec/mtk/pattern/palindrome_spec.rb +124 -0
  69. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +47 -0
  70. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +37 -0
  71. data/spec/mtk/pattern/sequence_spec.rb +128 -31
  72. data/spec/mtk/pitch_class_set_spec.rb +240 -7
  73. data/spec/mtk/pitch_class_spec.rb +84 -18
  74. data/spec/mtk/pitch_set_spec.rb +45 -10
  75. data/spec/mtk/pitch_spec.rb +59 -0
  76. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +159 -0
  77. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +49 -0
  78. data/spec/mtk/sequencer/step_sequencer_spec.rb +71 -0
  79. data/spec/mtk/timeline_spec.rb +118 -15
  80. data/spec/spec_helper.rb +4 -3
  81. metadata +59 -22
  82. data/lib/mtk/chord.rb +0 -47
  83. data/lib/mtk/constants/dynamics.rb +0 -56
  84. data/lib/mtk/constants/pitch_classes.rb +0 -18
  85. data/lib/mtk/constants/pitches.rb +0 -24
  86. data/lib/mtk/pattern/note_sequence.rb +0 -60
  87. data/lib/mtk/pattern/pitch_sequence.rb +0 -22
  88. data/lib/mtk/patterns.rb +0 -4
  89. data/spec/mtk/chord_spec.rb +0 -74
  90. data/spec/mtk/constants/pitch_classes_spec.rb +0 -35
  91. data/spec/mtk/constants/pitches_spec.rb +0 -23
  92. data/spec/mtk/pattern/note_sequence_spec.rb +0 -121
  93. 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.from_s(name).should be_a 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.from_s(name).name.should == name }
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.from_s(name.upcase).name.should == name
39
- PitchClass.from_s(name.downcase).name.should == name
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.from_s('z').should be_nil
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 from_s" do
79
+ it "acts like .[]" do
52
80
  for name in ['C', 'bbb', 'z']
53
- PitchClass.from_name(name).should == PitchClass.from_s(name)
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['B#']
107
- C.should == PitchClass['Dbb']
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
@@ -83,15 +83,9 @@ describe MTK::PitchSet do
83
83
  end
84
84
  end
85
85
 
86
- describe '#+' do
86
+ describe '#transpose' do
87
87
  it 'transposes upward by the given semitones' do
88
- (pitch_set + 12).should == PitchSet.new([C5, D5, E5, F5, G5, A5, B5])
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 + 5
153
- c_major.nearest(G).should == c_major - 5
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
@@ -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