jmtk 0.0.3.3-java → 0.4-java

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 (56) hide show
  1. checksums.yaml +15 -0
  2. data/DEVELOPMENT_NOTES.md +20 -0
  3. data/INTRO.md +63 -31
  4. data/README.md +9 -3
  5. data/Rakefile +42 -42
  6. data/bin/jmtk +75 -32
  7. data/bin/mtk +75 -32
  8. data/examples/drum_pattern.rb +2 -2
  9. data/examples/dynamic_pattern.rb +1 -1
  10. data/examples/helpers/output_selector.rb +71 -0
  11. data/examples/notation.rb +5 -1
  12. data/examples/tone_row_melody.rb +1 -1
  13. data/lib/mtk.rb +1 -0
  14. data/lib/mtk/core/duration.rb +18 -3
  15. data/lib/mtk/core/intensity.rb +5 -3
  16. data/lib/mtk/core/interval.rb +21 -14
  17. data/lib/mtk/core/pitch.rb +2 -0
  18. data/lib/mtk/core/pitch_class.rb +6 -3
  19. data/lib/mtk/events/event.rb +2 -1
  20. data/lib/mtk/events/note.rb +1 -1
  21. data/lib/mtk/events/parameter.rb +1 -0
  22. data/lib/mtk/events/rest.rb +85 -0
  23. data/lib/mtk/events/timeline.rb +6 -2
  24. data/lib/mtk/io/jsound_input.rb +9 -3
  25. data/lib/mtk/io/midi_file.rb +38 -2
  26. data/lib/mtk/io/midi_input.rb +1 -1
  27. data/lib/mtk/io/midi_output.rb +95 -4
  28. data/lib/mtk/io/unimidi_input.rb +7 -3
  29. data/lib/mtk/lang/durations.rb +31 -26
  30. data/lib/mtk/lang/intensities.rb +29 -30
  31. data/lib/mtk/lang/intervals.rb +108 -41
  32. data/lib/mtk/lang/mtk_grammar.citrus +14 -4
  33. data/lib/mtk/lang/parser.rb +10 -5
  34. data/lib/mtk/lang/pitch_classes.rb +45 -17
  35. data/lib/mtk/lang/pitches.rb +169 -32
  36. data/lib/mtk/lang/tutorial.rb +279 -0
  37. data/lib/mtk/lang/tutorial_lesson.rb +87 -0
  38. data/lib/mtk/sequencers/event_builder.rb +29 -8
  39. data/spec/mtk/core/duration_spec.rb +14 -1
  40. data/spec/mtk/core/intensity_spec.rb +1 -1
  41. data/spec/mtk/events/event_spec.rb +10 -16
  42. data/spec/mtk/events/note_spec.rb +3 -3
  43. data/spec/mtk/events/rest_spec.rb +184 -0
  44. data/spec/mtk/events/timeline_spec.rb +5 -1
  45. data/spec/mtk/io/midi_file_spec.rb +13 -2
  46. data/spec/mtk/io/midi_output_spec.rb +42 -9
  47. data/spec/mtk/lang/durations_spec.rb +5 -5
  48. data/spec/mtk/lang/intensities_spec.rb +5 -5
  49. data/spec/mtk/lang/intervals_spec.rb +139 -13
  50. data/spec/mtk/lang/parser_spec.rb +65 -25
  51. data/spec/mtk/lang/pitch_classes_spec.rb +0 -11
  52. data/spec/mtk/lang/pitches_spec.rb +0 -15
  53. data/spec/mtk/patterns/chain_spec.rb +7 -7
  54. data/spec/mtk/patterns/for_each_spec.rb +2 -2
  55. data/spec/mtk/sequencers/event_builder_spec.rb +49 -17
  56. metadata +12 -22
@@ -29,12 +29,12 @@ describe MTK::Lang::Durations do
29
29
  end
30
30
  end
31
31
 
32
- describe 'i' do
32
+ describe 'e' do
33
33
  it 'is 1/2 of a beat' do
34
- i.value.should == 1.0/2
34
+ e.value.should == 1.0/2
35
35
  end
36
36
  it 'is available via a module property and via mixin' do
37
- Durations::i.should == i
37
+ Durations::e.should == e
38
38
  end
39
39
  end
40
40
 
@@ -67,7 +67,7 @@ describe MTK::Lang::Durations do
67
67
 
68
68
  describe "DURATIONS" do
69
69
  it "contains all Durations pseudo-constants" do
70
- Durations::DURATIONS.should =~ [w, h, q, i, s, r, x]
70
+ Durations::DURATIONS.should =~ [w, h, q, e, s, r, x]
71
71
  end
72
72
 
73
73
  it "is immutable" do
@@ -77,7 +77,7 @@ describe MTK::Lang::Durations do
77
77
 
78
78
  describe "DURATION_NAMES" do
79
79
  it "contains all Durations pseudo-constants names as strings" do
80
- Durations::DURATION_NAMES.should =~ ['w', 'h', 'q', 'i', 's', 'r', 'x']
80
+ Durations::DURATION_NAMES.should =~ ['w', 'h', 'q', 'e', 's', 'r', 'x']
81
81
  end
82
82
 
83
83
  it "is immutable" do
@@ -47,12 +47,12 @@ describe MTK::Lang::Intensities do
47
47
  end
48
48
  end
49
49
 
50
- describe 'o' do # AKA forte
50
+ describe 'f' do # AKA forte
51
51
  it 'is equivalent to MIDI velocity 95' do
52
- (o.value * 127).round.should == 95
52
+ (f.value * 127).round.should == 95
53
53
  end
54
54
  it 'is available via a module property and via mixin' do
55
- Intensities::o.should == o
55
+ Intensities::f.should == f
56
56
  end
57
57
  it "does not overwrite the PitchClass constant 'F'" do
58
58
  F.should be_a PitchClass
@@ -79,7 +79,7 @@ describe MTK::Lang::Intensities do
79
79
 
80
80
  describe "INTENSITIES" do
81
81
  it "contains all Intensities pseudo-constants" do
82
- Intensities::INTENSITIES.should =~ [ppp, pp, p, mp, mf, o, ff, fff]
82
+ Intensities::INTENSITIES.should =~ [ppp, pp, p, mp, mf, f, ff, fff]
83
83
  end
84
84
 
85
85
  it "is immutable" do
@@ -89,7 +89,7 @@ describe MTK::Lang::Intensities do
89
89
 
90
90
  describe "INTENSITY_NAMES" do
91
91
  it "contains all Intensities pseudo-constants names as strings" do
92
- Intensities::INTENSITY_NAMES.should =~ ['ppp', 'pp', 'p', 'mp', 'mf', 'o', 'ff', 'fff']
92
+ Intensities::INTENSITY_NAMES.should =~ ['ppp', 'pp', 'p', 'mp', 'mf', 'f', 'ff', 'fff']
93
93
  end
94
94
 
95
95
  it "is immutable" do
@@ -11,12 +11,30 @@ describe MTK::Lang::Intervals do
11
11
  end
12
12
  end
13
13
 
14
+ describe 'd2' do
15
+ it 'is 0 semitones' do
16
+ d2.should == Interval[0]
17
+ end
18
+ it 'is available via a module property and via mixin' do
19
+ Intervals::d2.should == d2
20
+ end
21
+ end
22
+
14
23
  describe 'm2' do
15
24
  it 'is 1 semitone' do
16
25
  m2.should == Interval[1]
17
26
  end
18
27
  it 'is available via a module property and via mixin' do
19
- Intervals::P1.should == P1
28
+ Intervals::m2.should == m2
29
+ end
30
+ end
31
+
32
+ describe 'a1' do
33
+ it 'is 1 semitone' do
34
+ a1.should == Interval[1]
35
+ end
36
+ it 'is available via a module property and via mixin' do
37
+ Intervals::a1.should == a1
20
38
  end
21
39
  end
22
40
 
@@ -25,7 +43,16 @@ describe MTK::Lang::Intervals do
25
43
  M2.should == Interval[2]
26
44
  end
27
45
  it 'is available via a module property and via mixin' do
28
- Intervals::P1.should == P1
46
+ Intervals::M2.should == M2
47
+ end
48
+ end
49
+
50
+ describe 'd3' do
51
+ it 'is 2 semitones' do
52
+ d3.should == Interval[2]
53
+ end
54
+ it 'is available via a module property and via mixin' do
55
+ Intervals::d3.should == d3
29
56
  end
30
57
  end
31
58
 
@@ -34,7 +61,16 @@ describe MTK::Lang::Intervals do
34
61
  m3.should == Interval[3]
35
62
  end
36
63
  it 'is available via a module property and via mixin' do
37
- Intervals::P1.should == P1
64
+ Intervals::m3.should == m3
65
+ end
66
+ end
67
+
68
+ describe 'a2' do
69
+ it 'is 3 semitones' do
70
+ a2.should == Interval[3]
71
+ end
72
+ it 'is available via a module property and via mixin' do
73
+ Intervals::a2.should == a2
38
74
  end
39
75
  end
40
76
 
@@ -43,7 +79,16 @@ describe MTK::Lang::Intervals do
43
79
  M3.should == Interval[4]
44
80
  end
45
81
  it 'is available via a module property and via mixin' do
46
- Intervals::P1.should == P1
82
+ Intervals::M3.should == M3
83
+ end
84
+ end
85
+
86
+ describe 'd4' do
87
+ it 'is 4 semitones' do
88
+ d4.should == Interval[4]
89
+ end
90
+ it 'is available via a module property and via mixin' do
91
+ Intervals::d4.should == d4
47
92
  end
48
93
  end
49
94
 
@@ -52,7 +97,16 @@ describe MTK::Lang::Intervals do
52
97
  P4.should == Interval[5]
53
98
  end
54
99
  it 'is available via a module property and via mixin' do
55
- Intervals::P1.should == P1
100
+ Intervals::P4.should == P4
101
+ end
102
+ end
103
+
104
+ describe 'a3' do
105
+ it 'is 5 semitones' do
106
+ a3.should == Interval[5]
107
+ end
108
+ it 'is available via a module property and via mixin' do
109
+ Intervals::a3.should == a3
56
110
  end
57
111
  end
58
112
 
@@ -61,7 +115,25 @@ describe MTK::Lang::Intervals do
61
115
  TT.should == Interval[6]
62
116
  end
63
117
  it 'is available via a module property and via mixin' do
64
- Intervals::P1.should == P1
118
+ Intervals::TT.should == TT
119
+ end
120
+ end
121
+
122
+ describe 'a4' do
123
+ it 'is 6 semitones' do
124
+ a4.should == Interval[6]
125
+ end
126
+ it 'is available via a module property and via mixin' do
127
+ Intervals::a4.should == a4
128
+ end
129
+ end
130
+
131
+ describe 'd5' do
132
+ it 'is 6 semitones' do
133
+ d5.should == Interval[6]
134
+ end
135
+ it 'is available via a module property and via mixin' do
136
+ Intervals::d5.should == d5
65
137
  end
66
138
  end
67
139
 
@@ -70,7 +142,16 @@ describe MTK::Lang::Intervals do
70
142
  P5.should == Interval[7]
71
143
  end
72
144
  it 'is available via a module property and via mixin' do
73
- Intervals::P1.should == P1
145
+ Intervals::P5.should == P5
146
+ end
147
+ end
148
+
149
+ describe 'd6' do
150
+ it 'is 7 semitones' do
151
+ d6.should == Interval[7]
152
+ end
153
+ it 'is available via a module property and via mixin' do
154
+ Intervals::d6.should == d6
74
155
  end
75
156
  end
76
157
 
@@ -83,12 +164,30 @@ describe MTK::Lang::Intervals do
83
164
  end
84
165
  end
85
166
 
167
+ describe 'a5' do
168
+ it 'is 8 semitones' do
169
+ a5.should == Interval[8]
170
+ end
171
+ it 'is available via a module property and via mixin' do
172
+ Intervals::a5.should == a5
173
+ end
174
+ end
175
+
86
176
  describe 'M6' do
87
177
  it 'is 9 semitones' do
88
178
  M6.should == Interval[9]
89
179
  end
90
180
  it 'is available via a module property and via mixin' do
91
- Intervals::P1.should == P1
181
+ Intervals::M6.should == M6
182
+ end
183
+ end
184
+
185
+ describe 'd7' do
186
+ it 'is 9 semitones' do
187
+ d7.should == Interval[9]
188
+ end
189
+ it 'is available via a module property and via mixin' do
190
+ Intervals::d7.should == d7
92
191
  end
93
192
  end
94
193
 
@@ -97,7 +196,16 @@ describe MTK::Lang::Intervals do
97
196
  m7.should == Interval[10]
98
197
  end
99
198
  it 'is available via a module property and via mixin' do
100
- Intervals::P1.should == P1
199
+ Intervals::m7.should == m7
200
+ end
201
+ end
202
+
203
+ describe 'a6' do
204
+ it 'is 10 semitones' do
205
+ a6.should == Interval[10]
206
+ end
207
+ it 'is available via a module property and via mixin' do
208
+ Intervals::a6.should == a6
101
209
  end
102
210
  end
103
211
 
@@ -106,7 +214,16 @@ describe MTK::Lang::Intervals do
106
214
  M7.should == Interval[11]
107
215
  end
108
216
  it 'is available via a module property and via mixin' do
109
- Intervals::P1.should == P1
217
+ Intervals::M7.should == M7
218
+ end
219
+ end
220
+
221
+ describe 'd8' do
222
+ it 'is 11 semitones' do
223
+ d8.should == Interval[11]
224
+ end
225
+ it 'is available via a module property and via mixin' do
226
+ Intervals::d8.should == d8
110
227
  end
111
228
  end
112
229
 
@@ -115,14 +232,23 @@ describe MTK::Lang::Intervals do
115
232
  P8.should == Interval[12]
116
233
  end
117
234
  it 'is available via a module property and via mixin' do
118
- Intervals::P1.should == P1
235
+ Intervals::P8.should == P8
236
+ end
237
+ end
238
+
239
+ describe 'a7' do
240
+ it 'is 12 semitones' do
241
+ a7.should == Interval[12]
242
+ end
243
+ it 'is available via a module property and via mixin' do
244
+ Intervals::a7.should == a7
119
245
  end
120
246
  end
121
247
 
122
248
 
123
249
  describe "INTERVALS" do
124
250
  it "contains all intervals constants/pseudo-constants" do
125
- Intervals::INTERVALS.should =~ [P1, m2, M2, m3, M3, P4, TT, P5, m6, M6, m7, M7, P8]
251
+ Intervals::INTERVALS.should =~ MTK::Core::Interval::ALL_NAMES.map{|name| MTK::Core::Interval.from_name(name) }
126
252
  end
127
253
 
128
254
  it "is immutable" do
@@ -132,7 +258,7 @@ describe MTK::Lang::Intervals do
132
258
 
133
259
  describe "INTERVAL_NAMES" do
134
260
  it "contains all intervals constants/pseudo-constant names" do
135
- Intervals::INTERVAL_NAMES.should =~ ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 'TT', 'P5', 'm6', 'M6', 'm7', 'M7', 'P8']
261
+ Intervals::INTERVAL_NAMES.should =~ MTK::Core::Interval::ALL_NAMES
136
262
  end
137
263
 
138
264
  it "is immutable" do
@@ -34,31 +34,31 @@ describe MTK::Lang::Parser do
34
34
 
35
35
  describe ".parse" do
36
36
  it "can parse a single pitch class and play it" do
37
- sequencer = MTK::Lang::Parser.parse('c')
37
+ sequencer = MTK::Lang::Parser.parse('C')
38
38
  timeline = sequencer.to_timeline
39
39
  timeline.should == MTK::Events::Timeline.from_h({0 => MTK.Note(C4)})
40
40
  end
41
41
 
42
42
  context "default (root rule) behavior" do
43
43
  it "parses a bare_sequencer" do
44
- sequencer = parse('C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp')
44
+ sequencer = parse('C:q:mp D4:ff A e:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp')
45
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 )]
46
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(e,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
47
47
  end
48
48
 
49
49
  it "parses a sequencer" do
50
- sequencer = parse('( C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp )')
50
+ sequencer = parse('( C:q:mp D4:ff A e:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp )')
51
51
  sequencer.should be_a Sequencers::Sequencer
52
- 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 )]
52
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(e,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
53
53
  end
54
54
 
55
55
  it "parses a timeline" do
56
56
  parse("
57
57
  {
58
58
  0 => C4:mp:q
59
- 1 => D4:o:h
59
+ 1 => D4:f:h
60
60
  }
61
- ").should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q), 1 => chain(D4,o,h)})
61
+ ").should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q), 1 => chain(D4,f,h)})
62
62
  end
63
63
 
64
64
  it "parses a chain of sequences" do
@@ -68,17 +68,17 @@ describe MTK::Lang::Parser do
68
68
  end
69
69
 
70
70
  it "parses a chain of choices" do
71
- sequencer = parse("<i|s>:<c|d|e>")
72
- sequencer.patterns.should == [ chain( choice(i,s), choice(C,D,E) ) ]
71
+ sequencer = parse("<e|s>:<C|D|E>")
72
+ sequencer.patterns.should == [ chain( choice(e,s), choice(C,D,E) ) ]
73
73
  end
74
74
 
75
75
  it "parses a chain of choices" do
76
- sequencer = parse("(<i|s>:<c|d|e>)&8")
77
- sequencer.patterns.should == [ seq( chain( choice(i,s), choice(C,D,E) ), min_elements:8, max_elements:8 ) ]
76
+ sequencer = parse("(<e|s>:<C|D|E>)&8")
77
+ sequencer.patterns.should == [ seq( chain( choice(e,s), choice(C,D,E) ), min_elements:8, max_elements:8 ) ]
78
78
  end
79
79
 
80
80
  it "parses the repetition of a basic note property" do
81
- sequencer = parse("c*4")
81
+ sequencer = parse("C*4")
82
82
  sequencer.patterns.should == [ seq(C, max_cycles:4) ]
83
83
  end
84
84
  end
@@ -104,15 +104,15 @@ describe MTK::Lang::Parser do
104
104
  end
105
105
 
106
106
  it "parses pitchclass:duration chains" do
107
- sequencer = parse('C:q D:q E:i F:i G:h', :bare_sequencer)
107
+ sequencer = parse('C:q D:q E:e F:e G:h', :bare_sequencer)
108
108
  sequencer.should be_a Sequencers::Sequencer
109
- sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,i), chain(F,i), chain(G,h))]
109
+ sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,e), chain(F,e), chain(G,h))]
110
110
  end
111
111
 
112
112
  it "parses a mix of chained and unchained pitches, pitch classes, durations, and intensities" do
113
- sequencer = parse('C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp', :bare_sequencer)
113
+ sequencer = parse('C:q:mp D4:ff A e:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp', :bare_sequencer)
114
114
  sequencer.should be_a Sequencers::Sequencer
115
- 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 )]
115
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(e,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
116
116
  end
117
117
  end
118
118
 
@@ -137,15 +137,15 @@ describe MTK::Lang::Parser do
137
137
  end
138
138
 
139
139
  it "parses pitchclass:duration chains" do
140
- sequencer = parse('{ C:q D:q E:i F:i G:h }', :sequencer)
140
+ sequencer = parse('{ C:q D:q E:e F:e G:h }', :sequencer)
141
141
  sequencer.should be_a Sequencers::Sequencer
142
- sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,i), chain(F,i), chain(G,h))]
142
+ sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,e), chain(F,e), chain(G,h))]
143
143
  end
144
144
 
145
145
  it "parses a mix of chained and unchained pitches, pitch classes, durations, and intensities" do
146
- sequencer = parse('{ C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp }', :sequencer)
146
+ sequencer = parse('{ C:q:mp D4:ff A e:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp }', :sequencer)
147
147
  sequencer.should be_a Sequencers::Sequencer
148
- 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 )]
148
+ sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(e,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
149
149
  end
150
150
  end
151
151
 
@@ -167,9 +167,9 @@ describe MTK::Lang::Parser do
167
167
  parse("
168
168
  {
169
169
  0 => C4:mp:q
170
- 1 => D4:o:h
170
+ 1 => D4:f:h
171
171
  }
172
- ", :timeline).should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q), 1 => chain(D4,o,h)})
172
+ ", :timeline).should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q), 1 => chain(D4,f,h)})
173
173
  end
174
174
 
175
175
  #it "parses a Timeline containing a chord" do
@@ -317,7 +317,7 @@ describe MTK::Lang::Parser do
317
317
  end
318
318
 
319
319
  it "parses duration sequences" do
320
- parse("q i q. ht", :bare_sequence).should == seq(q, i, q*Rational(1.5), h*Rational(2,3))
320
+ parse("q e q. ht", :bare_sequence).should == seq(q, e, q*Rational(1.5), h*Rational(2,3))
321
321
  end
322
322
  end
323
323
 
@@ -344,7 +344,7 @@ describe MTK::Lang::Parser do
344
344
  end
345
345
 
346
346
  it "parses duration sequences" do
347
- parse("(q i q. ht)", :sequence).should == seq(q, i, q*Rational(1.5), h*Rational(2,3))
347
+ parse("(q e q. ht)", :sequence).should == seq(q, e, q*Rational(1.5), h*Rational(2,3))
348
348
  end
349
349
 
350
350
  it "parses sequences with a max_cycles modifier" do
@@ -424,7 +424,7 @@ describe MTK::Lang::Parser do
424
424
 
425
425
  context 'element rule' do
426
426
  it "parses the repetition of a basic note property as a sequence with a max_cycles option" do
427
- sequence = parse("c*4", :element)
427
+ sequence = parse("C*4", :element)
428
428
  sequence.elements.should == [ C ]
429
429
  sequence.max_cycles.should == 4
430
430
  end
@@ -456,6 +456,46 @@ describe MTK::Lang::Parser do
456
456
  parse(pitch_class_name, :pitch_class).should == PitchClass[pitch_class_name]
457
457
  end
458
458
  end
459
+
460
+ it "doesn't allow a sharp and flat to be applied to the same diatonic pitch class" do
461
+ for pitch_class_name in %w(A B C D E F G)
462
+ lambda{ parse(pitch_class_name + '#b', :pitch_class) }.should raise_error
463
+ lambda{ parse(pitch_class_name + 'b#', :pitch_class) }.should raise_error
464
+ end
465
+ end
466
+ end
467
+
468
+
469
+ context 'diatonic_pitch_class rule' do
470
+ it "parses upper case diatonic pitch classes" do
471
+ for diatonic_pitch_class_name in %w(A B C D E F G)
472
+ parse(diatonic_pitch_class_name, :diatonic_pitch_class).should == PitchClass[diatonic_pitch_class_name]
473
+ end
474
+ end
475
+ end
476
+
477
+
478
+ context 'accidental rule' do
479
+ it "parses a single flat 'b'" do
480
+ lambda{ parse('b', :accidental) }.should_not raise_error
481
+ end
482
+
483
+ it "parses a double flat 'bb'" do
484
+ lambda{ parse('bb', :accidental) }.should_not raise_error
485
+ end
486
+
487
+ it "parses a single sharp '#'" do
488
+ lambda{ parse('#', :accidental) }.should_not raise_error
489
+ end
490
+
491
+ it "parses a double sharp '##'" do
492
+ lambda{ parse('##', :accidental) }.should_not raise_error
493
+ end
494
+
495
+ it "doesn't parse trip flats or sharps" do
496
+ lambda{ parse('bbb', :accidental) }.should raise_error
497
+ lambda{ parse('###', :accidental) }.should raise_error
498
+ end
459
499
  end
460
500
 
461
501