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.
- data/.yardopts +3 -2
- data/DEVELOPMENT_NOTES.md +114 -0
- data/INTRO.md +64 -8
- data/LICENSE.txt +1 -1
- data/README.md +31 -102
- data/Rakefile +56 -18
- data/bin/mtk +215 -0
- data/examples/crescendo.rb +5 -5
- data/examples/drum_pattern1.rb +23 -0
- data/examples/dynamic_pattern.rb +8 -11
- data/examples/gets_and_play.rb +26 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +8 -10
- data/examples/random_tone_row.rb +2 -2
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +8 -0
- data/examples/tone_row_melody.rb +6 -6
- data/lib/mtk.rb +52 -40
- data/lib/mtk/chord.rb +55 -0
- data/lib/mtk/constants/durations.rb +57 -0
- data/lib/mtk/constants/intensities.rb +61 -0
- data/lib/mtk/constants/intervals.rb +73 -0
- data/lib/mtk/constants/pitch_classes.rb +29 -0
- data/lib/mtk/constants/pitches.rb +52 -0
- data/lib/mtk/duration.rb +211 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/helpers/collection.rb +164 -0
- data/lib/mtk/helpers/convert.rb +36 -0
- data/lib/mtk/helpers/lilypond.rb +162 -0
- data/lib/mtk/helpers/output_selector.rb +67 -0
- data/lib/mtk/helpers/pitch_collection.rb +23 -0
- data/lib/mtk/helpers/pseudo_constants.rb +26 -0
- data/lib/mtk/intensity.rb +156 -0
- data/lib/mtk/interval.rb +155 -0
- data/lib/mtk/lang/mtk_grammar.citrus +190 -13
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/melody.rb +94 -0
- data/lib/mtk/midi/dls_synth_device.rb +144 -0
- data/lib/mtk/midi/dls_synth_output.rb +62 -0
- data/lib/mtk/midi/file.rb +67 -32
- data/lib/mtk/midi/input.rb +97 -0
- data/lib/mtk/midi/jsound_input.rb +36 -17
- data/lib/mtk/midi/jsound_output.rb +48 -46
- data/lib/mtk/midi/output.rb +195 -0
- data/lib/mtk/midi/unimidi_input.rb +117 -0
- data/lib/mtk/midi/unimidi_output.rb +121 -0
- data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
- data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/pitch.rb +7 -6
- data/lib/mtk/pitch_class.rb +124 -46
- data/lib/mtk/pitch_class_set.rb +58 -35
- data/lib/mtk/sequencers/event_builder.rb +131 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
- data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
- data/lib/mtk/timeline.rb +39 -22
- data/lib/mtk/variable.rb +32 -0
- data/spec/mtk/chord_spec.rb +83 -0
- data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
- data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
- data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
- data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
- data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
- data/spec/mtk/duration_spec.rb +372 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
- data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
- data/spec/mtk/intensity_spec.rb +289 -0
- data/spec/mtk/interval_spec.rb +265 -0
- data/spec/mtk/lang/parser_spec.rb +597 -0
- data/spec/mtk/melody_spec.rb +223 -0
- data/spec/mtk/midi/file_spec.rb +16 -16
- data/spec/mtk/midi/jsound_input_spec.rb +11 -0
- data/spec/mtk/midi/jsound_output_spec.rb +11 -0
- data/spec/mtk/midi/output_spec.rb +102 -0
- data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
- data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
- data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
- data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
- data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
- data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/pitch_class_set_spec.rb +23 -21
- data/spec/mtk/pitch_class_spec.rb +151 -39
- data/spec/mtk/pitch_spec.rb +22 -1
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
- data/spec/mtk/timeline_spec.rb +109 -16
- data/spec/mtk/variable_spec.rb +52 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- metadata +188 -91
- data/lib/mtk/_constants/durations.rb +0 -80
- data/lib/mtk/_constants/intensities.rb +0 -81
- data/lib/mtk/_constants/intervals.rb +0 -85
- data/lib/mtk/_constants/pitch_classes.rb +0 -35
- data/lib/mtk/_constants/pitches.rb +0 -49
- data/lib/mtk/event.rb +0 -70
- data/lib/mtk/helper/collection.rb +0 -114
- data/lib/mtk/helper/event_builder.rb +0 -85
- data/lib/mtk/helper/pseudo_constants.rb +0 -26
- data/lib/mtk/lang/grammar.rb +0 -17
- data/lib/mtk/note.rb +0 -63
- data/lib/mtk/pattern/abstract_pattern.rb +0 -132
- data/lib/mtk/pattern/cycle.rb +0 -51
- data/lib/mtk/pattern/enumerator.rb +0 -26
- data/lib/mtk/pattern/function.rb +0 -46
- data/lib/mtk/pattern/sequence.rb +0 -30
- data/lib/mtk/pitch_set.rb +0 -84
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
- data/lib/mtk/transform/invertible.rb +0 -15
- data/lib/mtk/transform/mappable.rb +0 -18
- data/lib/mtk/transform/set_theory_operations.rb +0 -34
- data/lib/mtk/transform/transposable.rb +0 -14
- data/spec/mtk/event_spec.rb +0 -139
- data/spec/mtk/helper/event_builder_spec.rb +0 -92
- data/spec/mtk/lang/grammar_spec.rb +0 -100
- data/spec/mtk/note_spec.rb +0 -115
- data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
- data/spec/mtk/pattern/sequence_spec.rb +0 -151
- data/spec/mtk/pitch_set_spec.rb +0 -198
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
- 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
|