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