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,597 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Lang::Parser do
4
+
5
+ def chain *args
6
+ Patterns.Chain *args
7
+ end
8
+
9
+ def seq *args
10
+ Patterns.Sequence *args
11
+ end
12
+
13
+ def cycle *args
14
+ Patterns.Cycle *args
15
+ end
16
+
17
+ def choice *args
18
+ Patterns.Choice *args
19
+ end
20
+
21
+ def foreach *args
22
+ Patterns.ForEach *args
23
+ end
24
+
25
+ def var(name)
26
+ ::MTK::Variable.new(name)
27
+ end
28
+
29
+
30
+ def parse(*args)
31
+ MTK::Lang::Parser.parse(*args)
32
+ end
33
+
34
+
35
+ describe ".parse" do
36
+ context "default (root rule) behavior" do
37
+ it "parses a bare_sequencer" do
38
+ sequencer = parse('C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp')
39
+ sequencer.should be_a Sequencers::Sequencer
40
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
41
+ end
42
+
43
+ it "parses a sequencer" do
44
+ sequencer = parse('( C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp )')
45
+ sequencer.should be_a Sequencers::Sequencer
46
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
47
+ end
48
+
49
+ it "parses a timeline" do
50
+ parse("
51
+ {
52
+ 0 => C4:mp:q
53
+ 1 => D4:o:h
54
+ }
55
+ ").should == Timeline.from_hash({0 => chain(C4,mp,q), 1 => chain(D4,o,h)})
56
+ end
57
+
58
+ it "parses a chain of sequences" do
59
+ sequencer = parse("(C D E F G):(mp mf ff):(q h w)")
60
+ sequencer.should be_a Sequencers::Sequencer
61
+ sequencer.patterns.should == [ chain( seq(C,D,E,F,G), seq(mp,mf,ff), seq(q,h,w) ) ]
62
+ end
63
+
64
+ it "parses a chain of choices" do
65
+ sequencer = parse("<i|s>:<c|d|e>")
66
+ sequencer.patterns.should == [ chain( choice(i,s), choice(C,D,E) ) ]
67
+ end
68
+
69
+ it "parses a chain of choices" do
70
+ sequencer = parse("(<i|s>:<c|d|e>)&8")
71
+ sequencer.patterns.should == [ seq( chain( choice(i,s), choice(C,D,E) ), min_elements:8, max_elements:8 ) ]
72
+ end
73
+
74
+ it "parses the repetition of a basic note property" do
75
+ sequencer = parse("c*4")
76
+ sequencer.patterns.should == [ seq(C, max_cycles:4) ]
77
+ end
78
+ end
79
+
80
+
81
+ context 'bare_sequencer rule (no curly braces)' do
82
+ it "parses a sequence of pitch classes" do
83
+ sequencer = parse('C D E F G', :bare_sequencer)
84
+ sequencer.should be_a Sequencers::Sequencer
85
+ sequencer.patterns.should == [seq(C, D, E, F, G)]
86
+ end
87
+
88
+ it "parses a sequence of pitches" do
89
+ sequencer = parse('C4 D4 E4 F4 G3', :bare_sequencer)
90
+ sequencer.should be_a Sequencers::Sequencer
91
+ sequencer.patterns.should == [seq(C4, D4, E4, F4, G3)]
92
+ end
93
+
94
+ it "parses a sequence of pitch classes + pitches" do
95
+ sequencer = parse('C4 D E3 F G5', :bare_sequencer)
96
+ sequencer.should be_a Sequencers::Sequencer
97
+ sequencer.patterns.should == [seq(C4, D, E3, F, G5)]
98
+ end
99
+
100
+ it "parses pitchclass:duration chains" do
101
+ sequencer = parse('C:q D:q E:i F:i G:h', :bare_sequencer)
102
+ sequencer.should be_a Sequencers::Sequencer
103
+ sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,i), chain(F,i), chain(G,h))]
104
+ end
105
+
106
+ it "parses a mix of chained and unchained pitches, pitch classes, durations, and intensities" do
107
+ sequencer = parse('C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp', :bare_sequencer)
108
+ sequencer.should be_a Sequencers::Sequencer
109
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
110
+ end
111
+ end
112
+
113
+
114
+ context 'sequencer rule' do
115
+ it "parses a sequence of pitch classes" do
116
+ sequencer = parse('{C D E F G}', :sequencer)
117
+ sequencer.should be_a Sequencers::Sequencer
118
+ sequencer.patterns.should == [seq(C, D, E, F, G)]
119
+ end
120
+
121
+ it "parses a sequence of pitches" do
122
+ sequencer = parse('{ C4 D4 E4 F4 G3}', :sequencer)
123
+ sequencer.should be_a Sequencers::Sequencer
124
+ sequencer.patterns.should == [seq(C4, D4, E4, F4, G3)]
125
+ end
126
+
127
+ it "parses a sequence of pitch classes + pitches" do
128
+ sequencer = parse('{C4 D E3 F G5 }', :sequencer)
129
+ sequencer.should be_a Sequencers::Sequencer
130
+ sequencer.patterns.should == [seq(C4, D, E3, F, G5)]
131
+ end
132
+
133
+ it "parses pitchclass:duration chains" do
134
+ sequencer = parse('{ C:q D:q E:i F:i G:h }', :sequencer)
135
+ sequencer.should be_a Sequencers::Sequencer
136
+ sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,i), chain(F,i), chain(G,h))]
137
+ end
138
+
139
+ it "parses a mix of chained and unchained pitches, pitch classes, durations, and intensities" do
140
+ sequencer = parse('{ C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp }', :sequencer)
141
+ sequencer.should be_a Sequencers::Sequencer
142
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
143
+ end
144
+ end
145
+
146
+
147
+ context "timeline rule" do
148
+ it "parses a very simple Timeline" do
149
+ parse("{0 => C}", :timeline).should == Timeline.from_hash({0 => seq(C)})
150
+ end
151
+
152
+ it "parses a Timeline with one entry" do
153
+ parse("
154
+ {
155
+ 0 => C4:mp:q
156
+ }
157
+ ", :timeline).should == Timeline.from_hash({0 => chain(C4,mp,q)})
158
+ end
159
+
160
+ it "parses a Timeline with multiple entries" do
161
+ parse("
162
+ {
163
+ 0 => C4:mp:q
164
+ 1 => D4:o:h
165
+ }
166
+ ", :timeline).should == Timeline.from_hash({0 => chain(C4,mp,q), 1 => chain(D4,o,h)})
167
+ end
168
+
169
+ #it "parses a Timeline containing a chord" do
170
+ # parse("
171
+ # {
172
+ # 0 => [C4 E4 G4]:fff:w
173
+ # }
174
+ # ", :timeline).should == Timeline.from_hash({0 => chain(Chord(C4,E4,G4),fff,w)})
175
+ #end
176
+ end
177
+
178
+
179
+ context 'timepoint rule' do
180
+ it 'should evaluate a number followed by "=>" as the numerical value' do
181
+ parse('42 => ', :timepoint).should == 42
182
+ end
183
+ end
184
+
185
+
186
+ context "pattern rule" do
187
+ it 'parses bare sequences' do
188
+ parse("C4 D4 E4", :pattern).should == seq(C4, D4, E4)
189
+ end
190
+
191
+ it 'parses sequences' do
192
+ parse("(C4 D4 E4)", :pattern).should == seq(C4, D4, E4)
193
+ end
194
+
195
+ it 'parses chains and wraps them in a Sequence' do
196
+ # The wrapping behavior isn't completely desirable,
197
+ # but I'm having trouble making the grammar do exactly what I want in all scenarios, so this is a compromise
198
+ parse("C4:q:ff", :pattern).should == chain(C4, q, ff)
199
+ end
200
+
201
+ it "parses chains of sequencers" do
202
+ parse("(C D E F G):(mp mf ff):(q h w)", :pattern).should == chain( seq(C,D,E,F,G), seq(mp,mf,ff), seq(q,h,w) )
203
+ end
204
+
205
+ it "parses chains of sequencers with modifiers" do
206
+ pattern = parse('(C D E F)*3:(q h w)&2', :pattern)
207
+ pattern.should == chain( cycle(C,D,E,F, max_cycles:3), seq(q,h,w, min_elements:2, max_elements:2))
208
+ pattern.next
209
+ pattern.next
210
+ lambda{ pattern.next }.should raise_error StopIteration
211
+ end
212
+
213
+ it "parses chains of sequencers with modifiers" do
214
+ pattern = parse('(C D E F G):(q h w)&3', :pattern)
215
+ pattern.should == chain( seq(C,D,E,F,G), seq(q,h,w, min_elements:3, max_elements:3))
216
+ pattern.next
217
+ pattern.next
218
+ pattern.next
219
+ lambda{ pattern.next }.should raise_error StopIteration
220
+ end
221
+
222
+ it "ensures a single element is wrapped in a Pattern" do
223
+ parse("C", :pattern).should be_a ::MTK::Patterns::Pattern
224
+ end
225
+
226
+ it "parse 'recursively' nested constructs" do
227
+ parse('C <D|E <F|G F>> | A B:(q h <q|h:mp h:<mf|ff fff>>)', :pattern).should ==
228
+ choice(
229
+ seq(C, choice(D, seq(E, choice(F,seq(G,F))))),
230
+ seq(A, chain(B,seq(q, h, choice(q,seq(chain(h,mp),chain(h,choice(mf,seq(ff,fff))))))))
231
+ )
232
+ end
233
+ end
234
+
235
+
236
+ context 'bare_choice rule (no parentheses)' do
237
+ it "parses a choice of simple elements" do
238
+ parse('C | D | E', :bare_choice).should == choice(C,D,E)
239
+ end
240
+
241
+ it 'parses a choice of sequences (parentheses implied)' do
242
+ parse('C D E | D E F | E F', :bare_choice).should == choice(seq(C,D,E), seq(D,E,F), seq(E,F))
243
+ end
244
+
245
+ it 'parses a choice of sequences and simple elements' do
246
+ parse('C D E | G | E F | A', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F), A)
247
+ end
248
+
249
+ it "doesn't require spaces around the pipe characters"do
250
+ parse('C D E|G|E F', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F))
251
+ end
252
+
253
+ it 'parses choices containing chains' do
254
+ parse('C D:q | G | A:mp|E:fff:h F:s', :bare_choice).should ==
255
+ choice( seq(C,chain(D,q)), G, chain(A,mp), seq(chain(E,fff,h),chain(F,s)) )
256
+ end
257
+
258
+ it 'parses choices containing nested choices' do
259
+ parse('C <D|E> | F | G', :bare_choice).should == choice(seq(C,choice(D,E)), F, G)
260
+ end
261
+ end
262
+
263
+
264
+ context 'choice rule' do
265
+ it "parses a choice of simple elements" do
266
+ parse('<C | D | E>', :bare_choice).should == choice(C,D,E)
267
+ end
268
+
269
+ it 'parses a choice of sequences (parentheses implied)' do
270
+ parse('< C D E | D E F | E F>', :bare_choice).should == choice(seq(C,D,E), seq(D,E,F), seq(E,F))
271
+ end
272
+
273
+ it 'parses a choice of sequences and simple elements' do
274
+ parse('<C D E | G | E F | A >', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F), A)
275
+ end
276
+
277
+ it "doesn't require spaces around the pipe characters"do
278
+ parse('< C D E|G|E F >', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F))
279
+ end
280
+
281
+ it 'parses choices containing chains' do
282
+ parse('<C D:q | G | A:mp|E:fff:h F:s>', :bare_choice).should ==
283
+ choice( seq(C,chain(D,q)), G, chain(A,mp), seq(chain(E,fff,h),chain(F,s)) )
284
+ end
285
+
286
+ it 'parses choices containing nested choices' do
287
+ parse('< C <D|E> | F | G >', :bare_choice).should == choice(seq(C,choice(D,E)), F, G)
288
+ end
289
+ end
290
+
291
+
292
+ context 'bare_sequence rule (no parentheses)' do
293
+ it "parses pitch sequences" do
294
+ parse("C4 D4 E4", :bare_sequence).should == seq(C4, D4, E4)
295
+ end
296
+
297
+ #it "parses pitch sequences with chords" do
298
+ # parse("C4 [D4 E4]", :bare_sequence).should == seq( C4, Chord(D4,E4) )
299
+ #end
300
+
301
+ it "parses pitch sequences with pitch classes" do
302
+ parse("C4 D E4", :bare_sequence).should == seq( C4, D, E4 )
303
+ end
304
+
305
+ it "parses pitch sequences with intervals" do
306
+ parse("C4 m2", :bare_sequence).should == seq( C4, m2 )
307
+ end
308
+
309
+ it "parses intensity sequences" do
310
+ parse("ppp mf mp ff", :bare_sequence).should == seq(ppp, mf, mp, ff)
311
+ end
312
+
313
+ it "parses duration sequences" do
314
+ parse("q i q. ht", :bare_sequence).should == seq(q, i, q*Rational(1.5), h*Rational(2,3))
315
+ end
316
+ end
317
+
318
+
319
+ context "sequence rule" do
320
+ it "parses pitch sequences" do
321
+ parse("(C4 D4 E4)", :sequence).should == seq(C4, D4, E4)
322
+ end
323
+
324
+ #it "parses pitch sequences with chords" do
325
+ # parse("( C4 [D4 E4])", :sequence).should == seq( C4, Chord(D4,E4) )
326
+ #end
327
+
328
+ it "parses pitch sequences with pitch classes" do
329
+ parse("(C4 D E4 )", :sequence).should == seq( C4, D, E4 )
330
+ end
331
+
332
+ it "parses pitch sequences with intervals" do
333
+ parse("( C4 m2 )", :sequence).should == seq( C4, m2 )
334
+ end
335
+
336
+ it "parses intensity sequences" do
337
+ parse("( ppp mf mp ff )", :sequence).should == seq(ppp, mf, mp, ff)
338
+ end
339
+
340
+ it "parses duration sequences" do
341
+ parse("(q i q. ht)", :sequence).should == seq(q, i, q*Rational(1.5), h*Rational(2,3))
342
+ end
343
+
344
+ it "parses sequences with a max_cycles modifier" do
345
+ sequence = parse("(C D)*2", :sequence)
346
+ sequence.should == Patterns.Cycle(C,D, max_cycles: 2)
347
+ end
348
+
349
+ it "parses sequences with a min+max_elements modifier" do
350
+ sequence = parse("(C D E)&2", :sequence)
351
+ sequence.should == Patterns.Cycle(C,D,E, min_elements: 2, max_elements: 2)
352
+ end
353
+ end
354
+
355
+
356
+ context "foreach rule" do
357
+ it "parses a for each pattern with 2 subpatterns" do
358
+ foreach = parse('(C D)@(E F)', :foreach)
359
+ foreach.should == Patterns.ForEach(seq(C,D),seq(E,F))
360
+ end
361
+
362
+ it "parses a for each pattern with 3 subpatterns" do
363
+ foreach = parse('(C D)@(E F)@(G A B)', :foreach)
364
+ foreach.should == Patterns.ForEach(seq(C,D),seq(E,F),seq(G,A,B))
365
+ end
366
+
367
+ it "parses a for each pattern with '$' variables" do
368
+ foreach = parse('(C D)@(E F)@($ $$)', :foreach)
369
+ foreach.should == Patterns.ForEach(seq(C,D),seq(E,F),seq(var('$'),var('$$')))
370
+ end
371
+ end
372
+
373
+
374
+ context "chain rule" do
375
+ it "parses a basic chain of note properties" do
376
+ parse("G4:h.:ff", :chain).should == Patterns.Chain(G4,h+q,ff)
377
+ end
378
+
379
+ it "parses a chain of for each patterns" do
380
+ parse('(C D)@(E F):(G A)@(B C)', :chain).should == chain( foreach(seq(C,D),seq(E,F)), foreach(seq(G,A),seq(B,C)) )
381
+ end
382
+
383
+ it "parses chains of elements with max_cycles" do
384
+ parse('C*3:mp*4:q*5', :chain).should == chain( seq(C,max_cycles:3), seq(mp,max_cycles:4), seq(q,max_cycles:5))
385
+ end
386
+ end
387
+
388
+
389
+ context "chainable rule" do
390
+ it "parses a pitch" do
391
+ parse("C4", :chainable).should == C4
392
+ end
393
+
394
+ #it "parses a chord" do
395
+ # parse("[C4 D4]", :chainable).should == Chord(C4,D4)
396
+ #end
397
+
398
+ it "parses a pitch class" do
399
+ parse("C", :chainable).should == C
400
+ end
401
+
402
+ it "parses intervals" do
403
+ parse("m2", :chainable).should == m2
404
+ end
405
+
406
+ it "parses durations" do
407
+ parse("h", :chainable).should == h
408
+ end
409
+
410
+ it "parses rests" do
411
+ parse("-h", :chainable).should == -h
412
+ end
413
+
414
+ it "parses intensities" do
415
+ parse("ff", :chainable).should == ff
416
+ end
417
+ end
418
+
419
+ context 'element rule' do
420
+ it "parses the repetition of a basic note property as a sequence with a max_cycles option" do
421
+ sequence = parse("c*4", :element)
422
+ sequence.elements.should == [ C ]
423
+ sequence.max_cycles.should == 4
424
+ end
425
+ end
426
+
427
+
428
+ #context 'chord rule' do
429
+ # it "parses chords" do
430
+ # parse("[C4 E4 G4]", :chord).should == Chord(C4,E4,G4)
431
+ # end
432
+ #end
433
+
434
+
435
+ context 'pitch rule' do
436
+ it "parses pitches" do
437
+ for pitch_class_name in PitchClass::VALID_NAMES
438
+ pc = PitchClass[pitch_class_name]
439
+ for octave in -1..9
440
+ parse("#{pitch_class_name}#{octave}", :pitch).should == Pitch[pc,octave]
441
+ end
442
+ end
443
+ end
444
+ end
445
+
446
+
447
+ context 'pitch_class rule' do
448
+ it "parses pitch classes" do
449
+ for pitch_class_name in PitchClass::VALID_NAMES
450
+ parse(pitch_class_name, :pitch_class).should == PitchClass[pitch_class_name]
451
+ end
452
+ end
453
+ end
454
+
455
+
456
+ context 'interval rule' do
457
+ it "parses intervals" do
458
+ for interval_name in Interval::ALL_NAMES
459
+ parse(interval_name, :interval).should == Interval(interval_name)
460
+ end
461
+ end
462
+ end
463
+
464
+
465
+ context 'intensity rule' do
466
+ it "parses intensities" do
467
+ for intensity_name in Intensity::NAMES
468
+ parse(intensity_name, :intensity).should == Intensity(intensity_name)
469
+ end
470
+ end
471
+
472
+ it "parses intensities with + and - modifiers" do
473
+ for intensity_name in Intensity::NAMES
474
+ name = "#{intensity_name}+"
475
+ parse(name, :intensity).should == Intensity(name)
476
+ name = "#{intensity_name}-"
477
+ parse(name, :intensity).should == Intensity(name)
478
+ end
479
+ end
480
+ end
481
+
482
+
483
+ context 'duration rule' do
484
+ it "parses durations" do
485
+ for duration in Durations::DURATION_NAMES
486
+ parse(duration, :duration).should == Duration(duration)
487
+ end
488
+ end
489
+
490
+ it "parses durations with . and t modifiers" do
491
+ for duration in Durations::DURATION_NAMES
492
+ name = "#{duration}."
493
+ parse(name, :duration).should == Duration(name)
494
+ name = "#{duration}t"
495
+ parse(name, :duration).should == Duration(name)
496
+ name = "#{duration}..t.t"
497
+ parse(name, :duration).should == Duration(name)
498
+ end
499
+ end
500
+
501
+ it "parses durations with - modifier (it parses rests)" do
502
+ for duration in Durations::DURATION_NAMES
503
+ parse("-#{duration}", :duration).should == -1 * Duration(duration)
504
+ end
505
+ end
506
+
507
+ it "parses durations with integer multipliers" do
508
+ Durations::DURATION_NAMES.each_with_index do |duration, index|
509
+ multiplier = index+5
510
+ parse("#{multiplier}#{duration}", :duration).should == multiplier * Duration(duration)
511
+ end
512
+ end
513
+
514
+ it "parses durations with float multipliers" do
515
+ Durations::DURATION_NAMES.each_with_index do |duration, index|
516
+ multiplier = (index+1)*1.123
517
+ parse("#{multiplier}#{duration}", :duration).should == multiplier * Duration(duration)
518
+ end
519
+ end
520
+
521
+ it "parses durations with float multipliers" do
522
+ Durations::DURATION_NAMES.each_with_index do |duration, index|
523
+ multiplier = Rational(index+1, index+2)
524
+ parse("#{multiplier}#{duration}", :duration).should == multiplier * Duration(duration)
525
+ end
526
+ end
527
+
528
+ end
529
+
530
+
531
+ context 'variable rule' do
532
+ it "parses the '$' variable" do
533
+ parse('$', :variable).should == var('$')
534
+ end
535
+
536
+ it "parses the '$$', '$$$', etc variables" do
537
+ parse('$$', :variable).should == var('$$')
538
+ parse('$$$', :variable).should == var('$$$')
539
+ parse('$$$$', :variable).should == var('$$$$')
540
+ end
541
+ end
542
+
543
+
544
+ context 'number rule' do
545
+ it "parses floats as numbers" do
546
+ parse("1.23", :number).should == 1.23
547
+ end
548
+
549
+ it "parses rationals as numbers" do
550
+ parse("12/34", :number).should == Rational(12,34)
551
+ end
552
+
553
+ it "parses ints as numbers" do
554
+ parse("123", :number).should == 123
555
+ end
556
+
557
+ end
558
+
559
+ context 'float rule' do
560
+ it "parses floats" do
561
+ parse("1.23", :float).should == 1.23
562
+ end
563
+
564
+ it "parses negative floats" do
565
+ parse("-1.23", :float).should == -1.23
566
+ end
567
+ end
568
+
569
+ context 'rational rule' do
570
+ it 'parses rationals' do
571
+ parse('12/13', :rational).should == Rational(12,13)
572
+ end
573
+
574
+ it 'parses negative rationals' do
575
+ parse('-12/13', :rational).should == -Rational(12,13)
576
+ end
577
+ end
578
+
579
+
580
+ context 'int rule' do
581
+ it "parses ints" do
582
+ parse("123", :int).should == 123
583
+ end
584
+
585
+ it "parses negative ints" do
586
+ parse("-123", :int).should == -123
587
+ end
588
+ end
589
+
590
+
591
+ context 'space rule' do
592
+ it "returns nil as the value for whitespace" do
593
+ parse(" \t\n", :space).should == nil
594
+ end
595
+ end
596
+ end
597
+ end