mtk 0.0.2 → 0.0.3

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 (147) hide show
  1. data/.yardopts +3 -2
  2. data/DEVELOPMENT_NOTES.md +114 -0
  3. data/INTRO.md +64 -8
  4. data/LICENSE.txt +1 -1
  5. data/README.md +31 -102
  6. data/Rakefile +56 -18
  7. data/bin/mtk +215 -0
  8. data/examples/crescendo.rb +5 -5
  9. data/examples/drum_pattern1.rb +23 -0
  10. data/examples/dynamic_pattern.rb +8 -11
  11. data/examples/gets_and_play.rb +26 -0
  12. data/examples/notation.rb +22 -0
  13. data/examples/play_midi.rb +8 -10
  14. data/examples/random_tone_row.rb +2 -2
  15. data/examples/syntax_to_midi.rb +28 -0
  16. data/examples/test_output.rb +8 -0
  17. data/examples/tone_row_melody.rb +6 -6
  18. data/lib/mtk.rb +52 -40
  19. data/lib/mtk/chord.rb +55 -0
  20. data/lib/mtk/constants/durations.rb +57 -0
  21. data/lib/mtk/constants/intensities.rb +61 -0
  22. data/lib/mtk/constants/intervals.rb +73 -0
  23. data/lib/mtk/constants/pitch_classes.rb +29 -0
  24. data/lib/mtk/constants/pitches.rb +52 -0
  25. data/lib/mtk/duration.rb +211 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/helpers/collection.rb +164 -0
  30. data/lib/mtk/helpers/convert.rb +36 -0
  31. data/lib/mtk/helpers/lilypond.rb +162 -0
  32. data/lib/mtk/helpers/output_selector.rb +67 -0
  33. data/lib/mtk/helpers/pitch_collection.rb +23 -0
  34. data/lib/mtk/helpers/pseudo_constants.rb +26 -0
  35. data/lib/mtk/intensity.rb +156 -0
  36. data/lib/mtk/interval.rb +155 -0
  37. data/lib/mtk/lang/mtk_grammar.citrus +190 -13
  38. data/lib/mtk/lang/parser.rb +29 -0
  39. data/lib/mtk/melody.rb +94 -0
  40. data/lib/mtk/midi/dls_synth_device.rb +144 -0
  41. data/lib/mtk/midi/dls_synth_output.rb +62 -0
  42. data/lib/mtk/midi/file.rb +67 -32
  43. data/lib/mtk/midi/input.rb +97 -0
  44. data/lib/mtk/midi/jsound_input.rb +36 -17
  45. data/lib/mtk/midi/jsound_output.rb +48 -46
  46. data/lib/mtk/midi/output.rb +195 -0
  47. data/lib/mtk/midi/unimidi_input.rb +117 -0
  48. data/lib/mtk/midi/unimidi_output.rb +121 -0
  49. data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
  50. data/lib/mtk/patterns/chain.rb +49 -0
  51. data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
  52. data/lib/mtk/patterns/cycle.rb +18 -0
  53. data/lib/mtk/patterns/for_each.rb +71 -0
  54. data/lib/mtk/patterns/function.rb +39 -0
  55. data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
  56. data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
  57. data/lib/mtk/patterns/pattern.rb +171 -0
  58. data/lib/mtk/patterns/sequence.rb +20 -0
  59. data/lib/mtk/pitch.rb +7 -6
  60. data/lib/mtk/pitch_class.rb +124 -46
  61. data/lib/mtk/pitch_class_set.rb +58 -35
  62. data/lib/mtk/sequencers/event_builder.rb +131 -0
  63. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  64. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  65. data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
  66. data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
  67. data/lib/mtk/timeline.rb +39 -22
  68. data/lib/mtk/variable.rb +32 -0
  69. data/spec/mtk/chord_spec.rb +83 -0
  70. data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
  71. data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
  72. data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
  73. data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
  74. data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
  75. data/spec/mtk/duration_spec.rb +372 -0
  76. data/spec/mtk/events/event_spec.rb +234 -0
  77. data/spec/mtk/events/note_spec.rb +174 -0
  78. data/spec/mtk/events/parameter_spec.rb +220 -0
  79. data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
  80. data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
  81. data/spec/mtk/intensity_spec.rb +289 -0
  82. data/spec/mtk/interval_spec.rb +265 -0
  83. data/spec/mtk/lang/parser_spec.rb +597 -0
  84. data/spec/mtk/melody_spec.rb +223 -0
  85. data/spec/mtk/midi/file_spec.rb +16 -16
  86. data/spec/mtk/midi/jsound_input_spec.rb +11 -0
  87. data/spec/mtk/midi/jsound_output_spec.rb +11 -0
  88. data/spec/mtk/midi/output_spec.rb +102 -0
  89. data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
  90. data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
  91. data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
  92. data/spec/mtk/patterns/chain_spec.rb +110 -0
  93. data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
  94. data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
  95. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  96. data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
  97. data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
  98. data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
  99. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  100. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  101. data/spec/mtk/pitch_class_set_spec.rb +23 -21
  102. data/spec/mtk/pitch_class_spec.rb +151 -39
  103. data/spec/mtk/pitch_spec.rb +22 -1
  104. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  105. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  106. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  107. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  108. data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
  109. data/spec/mtk/timeline_spec.rb +109 -16
  110. data/spec/mtk/variable_spec.rb +52 -0
  111. data/spec/spec_coverage.rb +2 -0
  112. data/spec/spec_helper.rb +3 -0
  113. metadata +188 -91
  114. data/lib/mtk/_constants/durations.rb +0 -80
  115. data/lib/mtk/_constants/intensities.rb +0 -81
  116. data/lib/mtk/_constants/intervals.rb +0 -85
  117. data/lib/mtk/_constants/pitch_classes.rb +0 -35
  118. data/lib/mtk/_constants/pitches.rb +0 -49
  119. data/lib/mtk/event.rb +0 -70
  120. data/lib/mtk/helper/collection.rb +0 -114
  121. data/lib/mtk/helper/event_builder.rb +0 -85
  122. data/lib/mtk/helper/pseudo_constants.rb +0 -26
  123. data/lib/mtk/lang/grammar.rb +0 -17
  124. data/lib/mtk/note.rb +0 -63
  125. data/lib/mtk/pattern/abstract_pattern.rb +0 -132
  126. data/lib/mtk/pattern/cycle.rb +0 -51
  127. data/lib/mtk/pattern/enumerator.rb +0 -26
  128. data/lib/mtk/pattern/function.rb +0 -46
  129. data/lib/mtk/pattern/sequence.rb +0 -30
  130. data/lib/mtk/pitch_set.rb +0 -84
  131. data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
  132. data/lib/mtk/transform/invertible.rb +0 -15
  133. data/lib/mtk/transform/mappable.rb +0 -18
  134. data/lib/mtk/transform/set_theory_operations.rb +0 -34
  135. data/lib/mtk/transform/transposable.rb +0 -14
  136. data/spec/mtk/event_spec.rb +0 -139
  137. data/spec/mtk/helper/event_builder_spec.rb +0 -92
  138. data/spec/mtk/lang/grammar_spec.rb +0 -100
  139. data/spec/mtk/note_spec.rb +0 -115
  140. data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
  141. data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
  142. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
  143. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
  144. data/spec/mtk/pattern/sequence_spec.rb +0 -151
  145. data/spec/mtk/pitch_set_spec.rb +0 -198
  146. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
  147. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -0,0 +1,245 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Sequencers::EventBuilder do
4
+
5
+ EVENT_BUILDER = MTK::Sequencers::EventBuilder
6
+
7
+ let(:pitch) { EVENT_BUILDER::DEFAULT_PITCH }
8
+ let(:intensity) { EVENT_BUILDER::DEFAULT_INTENSITY }
9
+ let(:duration) { EVENT_BUILDER::DEFAULT_DURATION }
10
+
11
+ def notes(*pitches)
12
+ pitches.map{|pitch| Note(pitch, intensity, duration) }
13
+ end
14
+
15
+ describe "#new" do
16
+ it "allows default pitch to be specified" do
17
+ event_builder = EVENT_BUILDER.new [Patterns.IntervalCycle(0)], :default_pitch => Gb4
18
+ event_builder.next.should == [Note(Gb4, intensity, duration)]
19
+ end
20
+ it "allows default intensity to be specified" do
21
+ event_builder = EVENT_BUILDER.new [Patterns.IntervalCycle(0)], :default_intensity => ppp
22
+ event_builder.next.should == [Note(pitch, ppp, duration)]
23
+ end
24
+ it "allows default duration to be specified" do
25
+ event_builder = EVENT_BUILDER.new [Patterns.IntervalCycle(0)], :default_duration => 5.25
26
+ event_builder.next.should == [Note(pitch, 5.25, intensity)]
27
+ end
28
+ end
29
+
30
+ describe "#next" do
31
+ it "builds a single-note list from a single-pitch pattern argument" do
32
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle(C4)]
33
+ event_builder.next.should == notes(C4)
34
+ end
35
+
36
+ it "builds a list of notes from any pitches in the argument" do
37
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle(C4), Patterns.Cycle(D4)]
38
+ event_builder.next.should == notes(C4, D4)
39
+ end
40
+
41
+ it "builds a list of notes from pitch sets" do
42
+ event_builder = EVENT_BUILDER.new [ Patterns.Cycle( Chord(C4,D4) ) ]
43
+ event_builder.next.should == notes(C4, D4)
44
+ end
45
+
46
+ it "builds notes from pitch classes and a default_pitch, selecting the nearest pitch class to the previous pitch" do
47
+ event_builder = EVENT_BUILDER.new [Patterns.Sequence(C,G,B,Eb,D,C)], :default_pitch => D3
48
+ notes = []
49
+ loop do
50
+ notes << event_builder.next
51
+ end
52
+ notes.flatten.should == notes(C3,G2,B2,Eb3,D3,C3)
53
+ end
54
+
55
+ it "defaults to a starting point of C4 (middle C)" do
56
+ event_builder = EVENT_BUILDER.new [Patterns.Sequence(C4)]
57
+ event_builder.next.should == notes(C4)
58
+ end
59
+
60
+ it "defaults to intensity 'o' when no intensities are given" do
61
+ event_builder = EVENT_BUILDER.new [Patterns.PitchSequence(C4, D4, E4), Patterns.DurationCycle(2)]
62
+ event_builder.next.should == [Note(C4, o, 2)]
63
+ event_builder.next.should == [Note(D4, o, 2)]
64
+ event_builder.next.should == [Note(E4, o, 2)]
65
+ end
66
+
67
+ it "defaults to duration 1 when no durations are given" do
68
+ event_builder = EVENT_BUILDER.new [Patterns.PitchSequence(C4, D4, E4), Patterns.IntensityCycle(p,o)]
69
+ event_builder.next.should == [Note(C4, p, 1)]
70
+ event_builder.next.should == [Note(D4, o, 1)]
71
+ event_builder.next.should == [Note(E4, p, 1)]
72
+ end
73
+
74
+ it "builds notes from pitch class sets, selecting the nearest pitch classes to the previous/default pitch" do
75
+ pitch_class_sequence = MTK::Patterns::Sequence.new([PitchClassSet(C,G),PitchClassSet(B,Eb),PitchClassSet(D,C)])
76
+ event_builder = EVENT_BUILDER.new [pitch_class_sequence], :default_pitch => D3
77
+ event_builder.next.should == notes(C3,G3)
78
+ event_builder.next.should == notes(B3,Eb3)
79
+ event_builder.next.should == notes(D3,C3)
80
+ end
81
+
82
+ it "builds notes from by adding Numeric intervals in :pitch type Patterns to the previous Pitch" do
83
+ event_builder = EVENT_BUILDER.new [ Patterns.Sequence( C4, M3, m3, -P5 ) ]
84
+ nexts = []
85
+ loop { nexts << event_builder.next }
86
+ nexts.should == [notes(C4), notes(E4), notes(G4), notes(C4)]
87
+ end
88
+
89
+ it "builds notes from by adding Numeric intervals in :pitch type Patterns to all pitches in the previous Chord" do
90
+ event_builder = EVENT_BUILDER.new [ Patterns.Sequence( Chord(C4,Eb4), M3, m3, -P5) ]
91
+ nexts = []
92
+ loop { nexts << event_builder.next }
93
+ nexts.should == [notes(C4,Eb4), notes(E4,G4), notes(G4,Bb4), notes(C4,Eb4)]
94
+ end
95
+
96
+ it "builds notes from intensities" do
97
+ event_builder = EVENT_BUILDER.new [ Patterns.Cycle(C4), Patterns.Sequence(mf, p, fff) ]
98
+ nexts = []
99
+ loop { nexts += event_builder.next }
100
+ nexts.should == [Note(C4, mf, duration), Note(C4, p, duration), Note(C4, fff, duration)]
101
+ end
102
+
103
+ it "builds notes from durations" do
104
+ event_builder = EVENT_BUILDER.new [ Patterns.PitchCycle(C4), Patterns.DurationSequence(1,2,3) ]
105
+ nexts = []
106
+ loop { nexts += event_builder.next }
107
+ nexts.should == [Note(C4, intensity, 1), Note(C4, intensity, 2), Note(C4, intensity, 3)]
108
+ end
109
+
110
+ it "iterates through the pitch, intensity, and duration list in parallel to emit Notes" do
111
+ event_builder = EVENT_BUILDER.new [Patterns.PitchCycle(C4, D4, E4), Patterns.IntensityCycle(p, o), Patterns.DurationCycle(1,2,3,4)]
112
+ event_builder.next.should == [Note(C4, p, 1)]
113
+ event_builder.next.should == [Note(D4, o, 2)]
114
+ event_builder.next.should == [Note(E4, p, 3)]
115
+ event_builder.next.should == [Note(C4, o, 4)]
116
+ event_builder.next.should == [Note(D4, p, 1)]
117
+ event_builder.next.should == [Note(E4, o, 2)]
118
+ end
119
+
120
+ it "returns nil (for a rest) when it encounters a nil value" do
121
+ event_builder = EVENT_BUILDER.new [Patterns.PitchCycle(C4, D4, E4, F4, nil), Patterns.IntensityCycle(mp, mf, o, nil), Patterns.DurationCycle(1, 2, nil)]
122
+ event_builder.next.should == [Note(C4, mp, 1)]
123
+ event_builder.next.should == [Note(D4, mf, 2)]
124
+ event_builder.next.should be_nil
125
+ event_builder.next.should be_nil
126
+ event_builder.next.should be_nil
127
+ end
128
+
129
+ it "goes to the nearest Pitch for any PitchClasses in the pitch list" do
130
+ event_builder = EVENT_BUILDER.new [Patterns::Cycle(C4, F, C, G, C)]
131
+ event_builder.next.should == notes(C4)
132
+ event_builder.next.should == notes(F4)
133
+ event_builder.next.should == notes(C4)
134
+ event_builder.next.should == notes(G3)
135
+ event_builder.next.should == notes(C4)
136
+ end
137
+
138
+ it "does not endlessly ascend or descend when alternating between two pitch classes a tritone apart" do
139
+ event_builder = EVENT_BUILDER.new [Patterns::Cycle(C4, Gb, C, Gb, C)]
140
+ event_builder.next.should == notes(C4)
141
+ event_builder.next.should == notes(Gb4)
142
+ event_builder.next.should == notes(C4)
143
+ event_builder.next.should == notes(Gb4)
144
+ event_builder.next.should == notes(C4)
145
+ end
146
+
147
+ it "handles pitches and chords intermixed" do
148
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( Chord(C4, E4, G4), C4, Chord(D4, F4, A4) )]
149
+ event_builder.next.should == notes(C4,E4,G4)
150
+ event_builder.next.should == notes(C4)
151
+ event_builder.next.should == notes(D4,F4,A4)
152
+ end
153
+
154
+ it "adds numeric intervals to Chord" do
155
+ event_builder = EVENT_BUILDER.new [Patterns::Cycle( Chord(C4, E4, G4), M2 )]
156
+ event_builder.next.should == notes(C4,E4,G4)
157
+ event_builder.next.should == notes(D4,Gb4,A4)
158
+ end
159
+
160
+ it "goes to the nearest Pitch relative to the lowest note in the Chord for any PitchClasses in the pitch list" do
161
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( Chord(C4, E4, G4), F, D, Bb )]
162
+ event_builder.next.should == notes(C4,E4,G4)
163
+ event_builder.next.should == notes(F4)
164
+ event_builder.next.should == notes(D4)
165
+ event_builder.next.should == notes(Bb3)
166
+ end
167
+
168
+ it "uses the default_pitch when no pitch pattern is provided" do
169
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( mp, mf, o )], :default_pitch => G3
170
+ event_builder.next.should == [Note(G3,mp,1)]
171
+ event_builder.next.should == [Note(G3,mf,1)]
172
+ event_builder.next.should == [Note(G3,o,1)]
173
+ end
174
+
175
+ it "handles chains of sequences" do
176
+ event_builder = EVENT_BUILDER.new [ Patterns.Chain( Patterns.Sequence(C4,D4,E4), Patterns.Sequence(mp,mf,ff), Patterns.Sequence(q,h,w) ) ]
177
+ event_builder.next.should == [Note(C4,mp,q)]
178
+ event_builder.next.should == [Note(D4,mf,h)]
179
+ event_builder.next.should == [Note(E4,ff,w)]
180
+ end
181
+
182
+ it "enforces the max_interval option for rising intervals" do
183
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5)], max_interval:12 )
184
+ pitches = []
185
+ 13.times{ pitches << event_builder.next[0].pitch }
186
+ pitches.should == [C4,G4,D4,A4,E4,B4,Gb4,Db4,Ab4,Eb4,Bb4,F4,C5]
187
+
188
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5)], max_interval:11 )
189
+ pitches = []
190
+ 13.times{ pitches << event_builder.next[0].pitch }
191
+ pitches.should == [C4,G4,D4,A4,E4,B4,Gb4,Db4,Ab4,Eb4,Bb4,F4,C4]
192
+ end
193
+
194
+ it "enforces the max_interval option for falling intervals" do
195
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5)], max_interval:12 )
196
+ pitches = []
197
+ 13.times{ pitches << event_builder.next[0].pitch }
198
+ pitches.should == [C4,F3,Bb3,Eb3,Ab3,Db3,Gb3,B3,E3,A3,D3,G3,C3]
199
+
200
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5)], max_interval:11 )
201
+ pitches = []
202
+ 13.times{ pitches << event_builder.next[0].pitch }
203
+ pitches.should == [C4,F3,Bb3,Eb3,Ab3,Db3,Gb3,B3,E3,A3,D3,G3,C4]
204
+ end
205
+
206
+ it "adds chained durations together" do
207
+ event_builder = EVENT_BUILDER.new( [Patterns.Chain(h,q,i,s)] )
208
+ event_builder.next[0].duration.should == h+q+i+s
209
+ end
210
+
211
+ it "averages chained intensities together" do
212
+ event_builder = EVENT_BUILDER.new( [Patterns.IntensityChain(0.1, 0.2, 0.3, 0.4)] )
213
+ event_builder.next[0].intensity.should == Intensity(0.25)
214
+ end
215
+
216
+ it "defaults the intensity to the previous intensity" do
217
+ event_builder = EVENT_BUILDER.new(
218
+ [Patterns.Sequence(Patterns.Chain(C4,ppp,q), Patterns.Chain(D4,i), Patterns.Chain(E4,ff,h), Patterns.Chain(F4,i))]
219
+ )
220
+ notes = []
221
+ 4.times{ notes += event_builder.next }
222
+ notes.should == [Note(C4,ppp,q), Note(D4,ppp,i), Note(E4,ff,h), Note(F4,ff,i)]
223
+ end
224
+
225
+ it "defaults the duration to the previous duration" do
226
+ event_builder = EVENT_BUILDER.new(
227
+ [Patterns.Sequence(Patterns.Chain(C4,ppp,h), Patterns.Chain(D4,mp), Patterns.Chain(E4,ff,s), Patterns.Chain(F4,mf))]
228
+ )
229
+ notes = []
230
+ 4.times{ notes += event_builder.next }
231
+ notes.should == [Note(C4,ppp,h), Note(D4,mp,h), Note(E4,ff,s), Note(F4,mf,s)]
232
+ end
233
+ end
234
+
235
+ describe "#rewind" do
236
+ it "resets the state of the Chain" do
237
+ event_builder = EVENT_BUILDER.new [ Patterns.Sequence(C,P8) ]
238
+ event_builder.next.should == [Note(C4,intensity,duration)]
239
+ event_builder.next.should == [Note(C5,intensity,duration)]
240
+ event_builder.rewind
241
+ event_builder.next.should == [Note(C4,intensity,duration)]
242
+ end
243
+ end
244
+
245
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Sequencers::LegatoSequencer do
4
+
5
+ LEGATO_SEQUENCER = Sequencers::LegatoSequencer
6
+
7
+ let(:pitches) { Patterns.PitchSequence(C4, D4, E4, C4) }
8
+ let(:durations) { Patterns.DurationSequence(1, 0.5, 1.5, 4) }
9
+ let(:intensities) { Patterns.IntensitySequence(0.3, 0.6, 0.9, 1.0) }
10
+ let(:legato_sequencer) { LEGATO_SEQUENCER.new [pitches, durations, intensities] }
11
+
12
+
13
+ describe "#to_timeline" do
14
+ it "contains notes assembled from the given patterns, with Timeline time deltas from the max event duration at the previous step" do
15
+ legato_sequencer.to_timeline.should == Timeline.from_hash({
16
+ 0 => Note(C4,1,0.3),
17
+ 1.0 => Note(D4,0.5,0.6),
18
+ 1.5 => Note(E4,1.5,0.9),
19
+ 3.0 => Note(C4,4,1.0)
20
+ })
21
+ end
22
+
23
+ it "treats negative durations as rests" do
24
+ legato_sequencer = LEGATO_SEQUENCER.new( [pitches, Patterns.DurationSequence(-1,-0.5,-1.5,4), intensities] )
25
+ legato_sequencer.to_timeline.should == Timeline.from_hash({
26
+ 3.0 => Note(C4,4,1.0)
27
+ })
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+
34
+ describe MTK::Sequencers do
35
+
36
+ describe "#LegatoSequencer" do
37
+ it "creates a LegatoSequencer" do
38
+ MTK::Sequencers.LegatoSequencer(1,2,3, rhythm:1).should be_a MTK::Sequencers::LegatoSequencer
39
+ end
40
+
41
+ it "sets #patterns from the varargs" do
42
+ MTK::Sequencers.LegatoSequencer(1,2,3, rhythm:1).patterns.should == [1,2,3]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Sequencers::RhythmicSequencer do
4
+
5
+ RHYTHMIC_SEQUENCER = Sequencers::RhythmicSequencer
6
+
7
+ let(:pitches) { Patterns.PitchSequence(C4, D4, E4, C4) }
8
+ let(:intensities) { Patterns.IntensitySequence(0.3, 0.6, 0.9, 1.0) }
9
+ let(:durations) { Patterns.DurationSequence(1, 1, 2, 1) }
10
+ let(:rhythm) { Patterns.RhythmSequence(0.5, 1.5, 4) }
11
+ let(:rhythmic_sequencer) { RHYTHMIC_SEQUENCER.new [pitches, intensities, durations], rhythm: 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 [], rhythm: :mock_pattern, 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, with Timeline time deltas from the :rhythm type pattern" do
30
+ rhythmic_sequencer.to_timeline.should == Timeline.from_hash({
31
+ 0 => Note(C4,1,0.3),
32
+ 0.5 => Note(D4,1,0.6),
33
+ 2.0 => Note(E4,2,0.9),
34
+ 6.0 => Note(C4,1,1.0)
35
+ })
36
+ end
37
+
38
+
39
+ it "uses the absolute value of any negative durations in the rhythm pattern" do
40
+ timeline = RHYTHMIC_SEQUENCER.new( [pitches, intensities, durations], rhythm: Patterns.RhythmSequence(-0.5, 1.5, -4) ).to_timeline
41
+ timeline.should == Timeline.from_hash({
42
+ 0 => Note(C4,1,0.3),
43
+ 0.5 => Note(D4,1,0.6),
44
+ 2.0 => Note(E4,2,0.9),
45
+ 6.0 => Note(C4,1,1.0)
46
+ })
47
+ end
48
+ end
49
+
50
+ describe "#max_steps" do
51
+ it "controls the maximum number of times in the generated timeline" do
52
+ rhythmic_sequencer.max_steps = 2
53
+ rhythmic_sequencer.to_timeline.should == Timeline.from_hash({
54
+ 0 => Note(C4,1,0.3),
55
+ 0.5 => Note(D4,1,0.6)
56
+ })
57
+ end
58
+ end
59
+
60
+ describe "#rewind" do
61
+ it "rewinds the rhythm pattern (in addition to normal #rewind behavior)" do
62
+ rhythm.length.times{ rhythmic_sequencer.send :advance }
63
+ # now the next call would normally throw a StopIteration exception
64
+ rhythmic_sequencer.rewind
65
+ rhythm.length.times{ rhythmic_sequencer.send :advance }
66
+ # if we didn't get an exception, then #rewind did it's job
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+
73
+ describe MTK::Sequencers do
74
+
75
+ describe "#RhythmicSequencer" do
76
+ it "creates a RhythmicSequencer" do
77
+ MTK::Sequencers.RhythmicSequencer(1,2,3, rhythm:1).should be_a MTK::Sequencers::RhythmicSequencer
78
+ end
79
+
80
+ it "sets #patterns from the varargs" do
81
+ MTK::Sequencers.RhythmicSequencer(1,2,3, rhythm:1).patterns.should == [1,2,3]
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,215 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Sequencers::Sequencer do
4
+
5
+ ABSTRACT_SEQUENCER = Sequencers::Sequencer
6
+
7
+ class MockEventBuilder < Patterns::Chain
8
+ attr_accessor :mock_attribute
9
+ end
10
+
11
+ let(:patterns) { [Patterns.PitchCycle(C4,D4)] }
12
+ let(:sequencer) { ABSTRACT_SEQUENCER.new patterns }
13
+ let(:pitch) { ::MTK::Sequencers::EventBuilder::DEFAULT_PITCH }
14
+ let(:intensity) { ::MTK::Sequencers::EventBuilder::DEFAULT_INTENSITY }
15
+ let(:duration) { ::MTK::Sequencers::EventBuilder::DEFAULT_DURATION }
16
+
17
+ describe "#new" do
18
+ it "defaults @max_steps to nil" do
19
+ sequencer.max_steps.should be_nil
20
+ end
21
+
22
+ it "sets @max_steps from the options hash" do
23
+ sequencer = ABSTRACT_SEQUENCER.new patterns, :max_steps => 4
24
+ sequencer.max_steps.should == 4
25
+ end
26
+
27
+ it "defaults @max_time to nil" do
28
+ sequencer.max_time.should be_nil
29
+ end
30
+
31
+ it "sets @max_time from the options hash" do
32
+ sequencer = ABSTRACT_SEQUENCER.new patterns, :max_time => 4
33
+ sequencer.max_time.should == 4
34
+ end
35
+
36
+ it "defaults @event_builder to ::MTK::Sequencers::EventBuilder" do
37
+ sequencer.event_builder.should be_a ::MTK::Sequencers::EventBuilder
38
+ end
39
+
40
+ it "sets @event_buidler from the options hash" do
41
+ sequencer = ABSTRACT_SEQUENCER.new patterns, :event_builder => MockEventBuilder
42
+ sequencer.event_builder.should be_a MockEventBuilder
43
+ end
44
+
45
+ it "allows default pitch to be specified" do
46
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.IntervalCycle(0)], :default_pitch => Gb4
47
+ sequencer.next.should == [Note(Gb4, intensity, duration)]
48
+ end
49
+ it "allows default intensity to be specified" do
50
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.IntervalCycle(0)], :default_intensity => ppp
51
+ sequencer.next.should == [Note(pitch, ppp, duration)]
52
+ end
53
+ it "allows default duration to be specified" do
54
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.IntervalCycle(0)], :default_duration => 5.25
55
+ sequencer.next.should == [Note(pitch, intensity, 5.25)]
56
+ end
57
+ end
58
+
59
+ describe "#event_builder" do
60
+ it "provides access to the internal EventBuilder" do
61
+ sequencer = ABSTRACT_SEQUENCER.new patterns, :event_builder => MockEventBuilder
62
+ sequencer.event_builder.mock_attribute = :value
63
+ sequencer.event_builder.mock_attribute.should == :value
64
+ end
65
+ end
66
+
67
+ describe "#to_timeline" do
68
+ it "combines pitch, intensity, and duration patterns into notes" do
69
+ pitches = Patterns.PitchSequence(C4, D4, E4)
70
+ intensities = Patterns.IntensitySequence(0.3, 0.7, 1.0)
71
+ durations = Patterns.DurationSequence(1, 2, 3)
72
+ sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
73
+ # default implementation just increments the time by 1 for each event (more interesting behavior is provided by subclasses)
74
+ sequencer.to_timeline.should == {
75
+ 0.0 => [Note(C4,1,0.3)],
76
+ 1.0 => [Note(D4,2,0.7)],
77
+ 2.0 => [Note(E4,3,1.0)]
78
+ }
79
+ end
80
+
81
+ it "combines patterns of different types and lengths" do
82
+ pitches = Patterns.PitchSequence(C4, D4, E4, F4, G4, A4, B4, C5)
83
+ intensities = Patterns.IntensityCycle(0.5, 1.0)
84
+ durations = Patterns.DurationPalindrome(1, 2, 3)
85
+ sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
86
+ # default implementation just increments the time by 1 for each event (more interesting behavior is provided by subclasses)
87
+ sequencer.to_timeline.should == {
88
+ 0.0 => [Note(C4,1,0.5)],
89
+ 1.0 => [Note(D4,2,1.0)],
90
+ 2.0 => [Note(E4,3,0.5)],
91
+ 3.0 => [Note(F4,2,1.0)],
92
+ 4.0 => [Note(G4,1,0.5)],
93
+ 5.0 => [Note(A4,2,1.0)],
94
+ 6.0 => [Note(B4,3,0.5)],
95
+ 7.0 => [Note(C5,2,1.0)]
96
+ }
97
+ end
98
+
99
+ it "produces consistent results by reseting the patterns each time" do
100
+ pitches = Patterns.PitchSequence(C4, D4, E4)
101
+ intensities = Patterns.IntensityCycle(1)
102
+ durations = Patterns.DurationCycle(1, 2)
103
+ sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
104
+ sequencer.to_timeline.should == sequencer.to_timeline
105
+ end
106
+ end
107
+
108
+ describe "#time" do
109
+ it "is the current timeline time that the sequencer is generating events for" do
110
+ # AbstractSequencer just advances by 1 each step
111
+ sequencer.next # time doesn't advance until the second #next call
112
+ sequencer.time.should == 0
113
+ sequencer.next
114
+ sequencer.time.should == 1
115
+ sequencer.next
116
+ sequencer.time.should == 2
117
+ end
118
+ end
119
+
120
+ describe "#step" do
121
+ it "is the index for how many of times #next has been called (i.e. count starting from 0)" do
122
+ sequencer.step.should == -1 # -1 indicates #next has not yet been called
123
+ sequencer.next
124
+ sequencer.step.should == 0
125
+ sequencer.next
126
+ sequencer.step.should == 1
127
+ sequencer.next
128
+ sequencer.step.should == 2
129
+ end
130
+ end
131
+
132
+ describe "#next" do
133
+ it "returns a list of notes formed from the patterns in the sequencer" do
134
+ sequencer.next.should == [Note(C4,intensity,duration)]
135
+ sequencer.next.should == [Note(D4,intensity,duration)]
136
+ sequencer.next.should == [Note(C4,intensity,duration)]
137
+ end
138
+
139
+ it "returns a filtered list of notes if the sequencer was constructed with a options[:filter] lambda" do
140
+ sequencer = ABSTRACT_SEQUENCER.new patterns, :filter => lambda{|events| events.map{|event| event.transpose(P8) } }
141
+ sequencer.next.should == [Note(C5,intensity,duration)]
142
+ sequencer.next.should == [Note(D5,intensity,duration)]
143
+ end
144
+
145
+ context "pitch patterns" do
146
+ it "adds Numeric elements (intervals) to the previous pitch" do
147
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, m2, M2, m3)]
148
+ sequencer.next.should == [Note(C4,intensity,duration)]
149
+ sequencer.next.should == [Note(C4+1,intensity,duration)]
150
+ sequencer.next.should == [Note(C4+1+2,intensity,duration)]
151
+ sequencer.next.should == [Note(C4+1+2+3,intensity,duration)]
152
+ end
153
+
154
+ it "returns a note with the given pitch when encountering a Pitch after another type" do
155
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, m2, C4)]
156
+ sequencer.next
157
+ sequencer.next
158
+ sequencer.next.should == [Note(C4,intensity,duration)]
159
+ end
160
+
161
+ it "goes to the nearest Pitch for any PitchClasses in the pitch list" do
162
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, F, C, G, C)]
163
+ sequencer.next.should == [Note(C4,intensity,duration)]
164
+ sequencer.next.should == [Note(F4,intensity,duration)]
165
+ sequencer.next.should == [Note(C4,intensity,duration)]
166
+ sequencer.next.should == [Note(G3,intensity,duration)]
167
+ sequencer.next.should == [Note(C4,intensity,duration)]
168
+ end
169
+
170
+ it "does not endlessly ascend or descend when alternating between two pitch classes a tritone apart" do
171
+ sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, Gb, C, Gb, C)]
172
+ sequencer.next.should == [Note(C4,intensity,duration)]
173
+ sequencer.next.should == [Note(Gb4,intensity,duration)]
174
+ sequencer.next.should == [Note(C4,intensity,duration)]
175
+ sequencer.next.should == [Note(Gb4,intensity,duration)]
176
+ sequencer.next.should == [Note(C4,intensity,duration)]
177
+ end
178
+ end
179
+
180
+ end
181
+
182
+ describe "#rewind" do
183
+ it "resets the sequencer and its patterns" do
184
+ sequencer.next
185
+ sequencer.rewind
186
+ sequencer.step.should == -1
187
+ sequencer.time.should == 0
188
+ sequencer.next.should == [Note(C4,intensity,duration)]
189
+ end
190
+
191
+ it "resets pitches properly for patterns that rely on previous pitches" do
192
+ relative_pitch_pattern = Patterns.Sequence(C,P8)
193
+ sequencer = ABSTRACT_SEQUENCER.new [relative_pitch_pattern]
194
+ sequencer.next.should == [Note(C4,intensity,duration)]
195
+ sequencer.next.should == [Note(C5,intensity,duration)]
196
+ sequencer.rewind
197
+ sequencer.next.should == [Note(C4,intensity,duration)] # if the internal EventChain is not properly reset, the Note would be C5
198
+ end
199
+ end
200
+
201
+ describe "#max_steps" do
202
+ it "controls the maximum number of entries in the generated timeline" do
203
+ sequencer.max_steps = 2
204
+ sequencer.to_timeline.times.length.should == 2
205
+ end
206
+ end
207
+
208
+ describe "#max_time" do
209
+ it "controls the maximum time in the generated timeline" do
210
+ sequencer.max_time = 4
211
+ sequencer.to_timeline.times.last.should == 4
212
+ end
213
+ end
214
+
215
+ end