mtk 0.0.3.3 → 0.4

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 (53) hide show
  1. checksums.yaml +15 -0
  2. data/INTRO.md +63 -31
  3. data/Rakefile +3 -1
  4. data/bin/mtk +75 -32
  5. data/examples/drum_pattern.rb +2 -2
  6. data/examples/dynamic_pattern.rb +1 -1
  7. data/examples/helpers/output_selector.rb +71 -0
  8. data/examples/notation.rb +5 -1
  9. data/examples/tone_row_melody.rb +1 -1
  10. data/lib/mtk.rb +1 -0
  11. data/lib/mtk/core/duration.rb +18 -3
  12. data/lib/mtk/core/intensity.rb +5 -3
  13. data/lib/mtk/core/interval.rb +21 -14
  14. data/lib/mtk/core/pitch.rb +2 -0
  15. data/lib/mtk/core/pitch_class.rb +6 -3
  16. data/lib/mtk/events/event.rb +2 -1
  17. data/lib/mtk/events/note.rb +1 -1
  18. data/lib/mtk/events/parameter.rb +1 -0
  19. data/lib/mtk/events/rest.rb +85 -0
  20. data/lib/mtk/events/timeline.rb +6 -2
  21. data/lib/mtk/io/jsound_input.rb +9 -3
  22. data/lib/mtk/io/midi_file.rb +38 -2
  23. data/lib/mtk/io/midi_input.rb +1 -1
  24. data/lib/mtk/io/midi_output.rb +95 -4
  25. data/lib/mtk/io/unimidi_input.rb +7 -3
  26. data/lib/mtk/lang/durations.rb +31 -26
  27. data/lib/mtk/lang/intensities.rb +29 -30
  28. data/lib/mtk/lang/intervals.rb +108 -41
  29. data/lib/mtk/lang/mtk_grammar.citrus +14 -4
  30. data/lib/mtk/lang/parser.rb +10 -5
  31. data/lib/mtk/lang/pitch_classes.rb +45 -17
  32. data/lib/mtk/lang/pitches.rb +169 -32
  33. data/lib/mtk/lang/tutorial.rb +279 -0
  34. data/lib/mtk/lang/tutorial_lesson.rb +87 -0
  35. data/lib/mtk/sequencers/event_builder.rb +29 -8
  36. data/spec/mtk/core/duration_spec.rb +14 -1
  37. data/spec/mtk/core/intensity_spec.rb +1 -1
  38. data/spec/mtk/events/event_spec.rb +10 -16
  39. data/spec/mtk/events/note_spec.rb +3 -3
  40. data/spec/mtk/events/rest_spec.rb +184 -0
  41. data/spec/mtk/events/timeline_spec.rb +5 -1
  42. data/spec/mtk/io/midi_file_spec.rb +13 -2
  43. data/spec/mtk/io/midi_output_spec.rb +42 -9
  44. data/spec/mtk/lang/durations_spec.rb +5 -5
  45. data/spec/mtk/lang/intensities_spec.rb +5 -5
  46. data/spec/mtk/lang/intervals_spec.rb +139 -13
  47. data/spec/mtk/lang/parser_spec.rb +65 -25
  48. data/spec/mtk/lang/pitch_classes_spec.rb +0 -11
  49. data/spec/mtk/lang/pitches_spec.rb +0 -15
  50. data/spec/mtk/patterns/chain_spec.rb +7 -7
  51. data/spec/mtk/patterns/for_each_spec.rb +2 -2
  52. data/spec/mtk/sequencers/event_builder_spec.rb +49 -17
  53. metadata +12 -22
@@ -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
 
@@ -48,15 +48,4 @@ describe MTK::Lang::PitchClasses do
48
48
  end
49
49
  end
50
50
 
51
- describe ".[]" do
52
- it "acts like PitchClass.[]" do
53
- for name in PitchClasses::PITCH_CLASS_NAMES
54
- PitchClasses[name].should == PitchClass[name]
55
- end
56
- end
57
-
58
- it "returns nil for arguments it doesn't understand" do
59
- PitchClasses[:invalid].should be_nil
60
- end
61
- end
62
51
  end
@@ -38,19 +38,4 @@ describe MTK::Lang::Pitches do
38
38
  end
39
39
  end
40
40
 
41
- describe ".[]" do
42
- it "acts like Pitch.from_s for the names in PITCH_NAMES, and also treats '_' like '-'" do
43
- for name in Pitches::PITCH_NAMES
44
- Pitches[name].should == Pitch.from_s(name.sub('_','-')) # the constant names need to use underscores, but Pitch.from_s doesn't understand that
45
- if name =~ /_/
46
- Pitches[name.sub('_','-')].should == Pitch.from_s(name.sub('_','-')) # make sure '-' works too
47
- end
48
- end
49
- end
50
-
51
- it "returns nil for arguments it doesn't understand" do
52
- Pitches[:invalid].should be_nil
53
- end
54
- end
55
-
56
41
  end
@@ -43,11 +43,11 @@ describe MTK::Patterns::Chain do
43
43
  end
44
44
 
45
45
  it 'combines 2 patterns, and returns #next until the last StopIteration of a nested Pattern occurs' do
46
- chain = CHAIN.new [Patterns.Sequence(C,D,E), Patterns.Sequence(w,h,q,i)]
46
+ chain = CHAIN.new [Patterns.Sequence(C,D,E), Patterns.Sequence(w,h,q,e)]
47
47
  chain.next.should == [C,w]
48
48
  chain.next.should == [D,h]
49
49
  chain.next.should == [E,q]
50
- chain.next.should == [C,i]
50
+ chain.next.should == [C,e]
51
51
  lambda{ chain.next }.should raise_error StopIteration
52
52
  end
53
53
 
@@ -56,18 +56,18 @@ describe MTK::Patterns::Chain do
56
56
 
57
57
 
58
58
  it 'combines Choice patterns with max_cycles' do
59
- chain = CHAIN.new [Patterns.Choice(i,s), Patterns.Choice(C,D,E)], max_cycles:100
59
+ chain = CHAIN.new [Patterns.Choice(e,s), Patterns.Choice(C,D,E)], max_cycles:100
60
60
  100.times do |time|
61
61
  attrs = chain.next
62
62
  attrs.length.should == 2
63
- (attrs[0]==i or attrs[0]==s).should be_true
63
+ (attrs[0]==e or attrs[0]==s).should be_true
64
64
  (attrs[1]==C or attrs[1]==D or attrs[1]==E).should be_true
65
65
  end
66
66
  lambda{ chain.next }.should raise_error StopIteration
67
67
  end
68
68
 
69
69
  it 'combines Choice patterns and emits a only single combination of attributes by default' do
70
- chain = CHAIN.new [Patterns.Choice(i,s), Patterns.Choice(C,D,E)]
70
+ chain = CHAIN.new [Patterns.Choice(e,s), Patterns.Choice(C,D,E)]
71
71
  chain.next
72
72
  lambda{ chain.next }.should raise_error StopIteration
73
73
  end
@@ -78,7 +78,7 @@ describe MTK::Patterns::Chain do
78
78
  end
79
79
 
80
80
  it 'throws StopIteration when any subpattern throws StopIteration with max_elements_exceeded' do
81
- chain = CHAIN.new [Patterns.Sequence(C,D,E,F,G), Patterns.Sequence(w,h,q,i, max_elements:3)]
81
+ chain = CHAIN.new [Patterns.Sequence(C,D,E,F,G), Patterns.Sequence(w,h,q,e, max_elements:3)]
82
82
  chain.next.should == [C,w]
83
83
  chain.next.should == [D,h]
84
84
  chain.next.should == [E,q]
@@ -100,7 +100,7 @@ describe MTK::Patterns::Chain do
100
100
  chain = CHAIN.new [C,q]
101
101
  chain.next
102
102
  chain.rewind
103
- lambda{ chain.next }.should_not raise_error StopIteration
103
+ lambda{ chain.next }.should_not raise_error
104
104
  lambda{ chain.next }.should raise_error StopIteration
105
105
  end
106
106
  end
@@ -83,11 +83,11 @@ describe MTK::Patterns::ForEach do
83
83
 
84
84
  it "evaluates nested variables" do
85
85
  # (C4 Bb Ab G)@( (C D C $):(q i i)*4:(mp mf) )
86
- foreach = FOREACH.new( [seq(G,A), chain(seq(C,D,var('$')),seq(q,i,s))] )
86
+ foreach = FOREACH.new( [seq(G,A), chain(seq(C,D,var('$')),seq(q,e,s))] )
87
87
  vals = []
88
88
  6.times{ vals << foreach.next }
89
89
  lambda{ foreach.next }.should raise_error StopIteration
90
- vals.should == [[C,q],[D,i],[G,s],[C,q],[D,i],[A,s]]
90
+ vals.should == [[C,q],[D,e],[G,s],[C,q],[D,e],[A,s]]
91
91
  end
92
92
 
93
93
  end
@@ -57,20 +57,26 @@ describe MTK::Sequencers::EventBuilder do
57
57
  event_builder.next.should == notes(C4)
58
58
  end
59
59
 
60
- it "defaults to intensity 'o' when no intensities are given" do
60
+ it "defaults to intensity 'f' when no intensities are given" do
61
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)]
62
+ event_builder.next.should == [Note(C4, f, 2)]
63
+ event_builder.next.should == [Note(D4, f, 2)]
64
+ event_builder.next.should == [Note(E4, f, 2)]
65
65
  end
66
66
 
67
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)]
68
+ event_builder = EVENT_BUILDER.new [Patterns.PitchSequence(C4, D4, E4), Patterns.IntensityCycle(p,f)]
69
69
  event_builder.next.should == [Note(C4, p, 1)]
70
- event_builder.next.should == [Note(D4, o, 1)]
70
+ event_builder.next.should == [Note(D4, f, 1)]
71
71
  event_builder.next.should == [Note(E4, p, 1)]
72
72
  end
73
73
 
74
+ it "builds notes from pitch class sets, selecting the first pitches nearest to the default pitch" do
75
+ pitch_class_sequence = MTK::Patterns::Sequence.new([PitchClassSet(C,G)])
76
+ event_builder = EVENT_BUILDER.new [pitch_class_sequence], :default_pitch => D3
77
+ event_builder.next.should == notes(C3,G3)
78
+ end
79
+
74
80
  it "builds notes from pitch class sets, selecting the nearest pitch classes to the previous/default pitch" do
75
81
  pitch_class_sequence = MTK::Patterns::Sequence.new([PitchClassSet(C,G),PitchClassSet(B,Eb),PitchClassSet(D,C)])
76
82
  event_builder = EVENT_BUILDER.new [pitch_class_sequence], :default_pitch => D3
@@ -108,17 +114,17 @@ describe MTK::Sequencers::EventBuilder do
108
114
  end
109
115
 
110
116
  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)]
117
+ event_builder = EVENT_BUILDER.new [Patterns.PitchCycle(C4, D4, E4), Patterns.IntensityCycle(p, f), Patterns.DurationCycle(1,2,3,4)]
112
118
  event_builder.next.should == [Note(C4, p, 1)]
113
- event_builder.next.should == [Note(D4, o, 2)]
119
+ event_builder.next.should == [Note(D4, f, 2)]
114
120
  event_builder.next.should == [Note(E4, p, 3)]
115
- event_builder.next.should == [Note(C4, o, 4)]
121
+ event_builder.next.should == [Note(C4, f, 4)]
116
122
  event_builder.next.should == [Note(D4, p, 1)]
117
- event_builder.next.should == [Note(E4, o, 2)]
123
+ event_builder.next.should == [Note(E4, f, 2)]
118
124
  end
119
125
 
120
126
  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)]
127
+ event_builder = EVENT_BUILDER.new [Patterns.PitchCycle(C4, D4, E4, F4, nil), Patterns.IntensityCycle(mp, mf, f, nil), Patterns.DurationCycle(1, 2, nil)]
122
128
  event_builder.next.should == [Note(C4, mp, 1)]
123
129
  event_builder.next.should == [Note(D4, mf, 2)]
124
130
  event_builder.next.should be_nil
@@ -166,10 +172,10 @@ describe MTK::Sequencers::EventBuilder do
166
172
  end
167
173
 
168
174
  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
175
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( mp, mf, f )], :default_pitch => G3
170
176
  event_builder.next.should == [Note(G3,mp,1)]
171
177
  event_builder.next.should == [Note(G3,mf,1)]
172
- event_builder.next.should == [Note(G3,o,1)]
178
+ event_builder.next.should == [Note(G3,f,1)]
173
179
  end
174
180
 
175
181
  it "handles chains of sequences" do
@@ -204,8 +210,8 @@ describe MTK::Sequencers::EventBuilder do
204
210
  end
205
211
 
206
212
  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
213
+ event_builder = EVENT_BUILDER.new( [Patterns.Chain(h,q,e,s)] )
214
+ event_builder.next[0].duration.should == h+q+e+s
209
215
  end
210
216
 
211
217
  it "averages chained intensities together" do
@@ -215,11 +221,11 @@ describe MTK::Sequencers::EventBuilder do
215
221
 
216
222
  it "defaults the intensity to the previous intensity" do
217
223
  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))]
224
+ [Patterns.Sequence(Patterns.Chain(C4,ppp,q), Patterns.Chain(D4,e), Patterns.Chain(E4,ff,h), Patterns.Chain(F4,e))]
219
225
  )
220
226
  notes = []
221
227
  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)]
228
+ notes.should == [Note(C4,ppp,q), Note(D4,ppp,e), Note(E4,ff,h), Note(F4,ff,e)]
223
229
  end
224
230
 
225
231
  it "defaults the duration to the previous duration" do
@@ -230,6 +236,32 @@ describe MTK::Sequencers::EventBuilder do
230
236
  4.times{ notes += event_builder.next }
231
237
  notes.should == [Note(C4,ppp,h), Note(D4,mp,h), Note(E4,ff,s), Note(F4,mf,s)]
232
238
  end
239
+
240
+ it "uses the previous pitch class in the chain to determine the octave of the current pitch class" do
241
+ event_builder = EVENT_BUILDER.new([Patterns.Chain(C4,E,G)])
242
+ event_builder.next.should == [Note(C4),Note(E4),Note(G4)]
243
+ end
244
+
245
+ it "returns a Rest event when the duration is negative" do
246
+ event_builder = EVENT_BUILDER.new([Patterns.Chain(C4,-q)])
247
+ event_builder.next.should == [Rest(q)]
248
+ end
249
+
250
+ it "doesn't uses the absolute value of the previous rest when generating the next event" do
251
+ event_builder = EVENT_BUILDER.new([Patterns.Sequence(Patterns.Chain(C4,q), -q, D4)])
252
+ event_builder.next.should == [Note(C4,q)]
253
+ event_builder.next.should == [Rest(q)]
254
+ event_builder.next.should == [Note(D4,q)]
255
+ end
256
+
257
+ it "makes all event chained to a rest be a rest" do
258
+ event_builder = EVENT_BUILDER.new(
259
+ [Patterns.Sequence(Patterns.Chain(C4,q), Patterns.Chain(-q, Patterns.Sequence(D4,E4)))]
260
+ )
261
+ event_builder.next.should == [Note(C4,q)]
262
+ event_builder.next.should == [Rest(q)]
263
+ event_builder.next.should == [Rest(q)]
264
+ end
233
265
  end
234
266
 
235
267
  describe "#rewind" do
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mtk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3.3
5
- prerelease:
4
+ version: '0.4'
6
5
  platform: ruby
7
6
  authors:
8
7
  - Adam Murray
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-06-13 00:00:00.000000000 Z
11
+ date: 2013-08-05 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: citrus
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
@@ -30,7 +27,6 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: midilib
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
@@ -46,7 +41,6 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: gamelan
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ~>
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ~>
60
53
  - !ruby/object:Gem::Version
@@ -62,7 +55,6 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: unimidi
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ~>
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ~>
76
67
  - !ruby/object:Gem::Version
@@ -94,6 +85,7 @@ files:
94
85
  - examples/drum_pattern.rb
95
86
  - examples/dynamic_pattern.rb
96
87
  - examples/gets_and_play.rb
88
+ - examples/helpers/output_selector.rb
97
89
  - examples/notation.rb
98
90
  - examples/play_midi.rb
99
91
  - examples/print_midi.rb
@@ -109,6 +101,7 @@ files:
109
101
  - lib/mtk/events/event.rb
110
102
  - lib/mtk/events/note.rb
111
103
  - lib/mtk/events/parameter.rb
104
+ - lib/mtk/events/rest.rb
112
105
  - lib/mtk/events/timeline.rb
113
106
  - lib/mtk/groups/chord.rb
114
107
  - lib/mtk/groups/collection.rb
@@ -133,6 +126,8 @@ files:
133
126
  - lib/mtk/lang/pitch_classes.rb
134
127
  - lib/mtk/lang/pitches.rb
135
128
  - lib/mtk/lang/pseudo_constants.rb
129
+ - lib/mtk/lang/tutorial.rb
130
+ - lib/mtk/lang/tutorial_lesson.rb
136
131
  - lib/mtk/lang/variable.rb
137
132
  - lib/mtk/numeric_extensions.rb
138
133
  - lib/mtk/patterns/chain.rb
@@ -158,6 +153,7 @@ files:
158
153
  - spec/mtk/events/event_spec.rb
159
154
  - spec/mtk/events/note_spec.rb
160
155
  - spec/mtk/events/parameter_spec.rb
156
+ - spec/mtk/events/rest_spec.rb
161
157
  - spec/mtk/events/timeline_spec.rb
162
158
  - spec/mtk/groups/chord_spec.rb
163
159
  - spec/mtk/groups/collection_spec.rb
@@ -192,34 +188,28 @@ files:
192
188
  - spec/spec_helper.rb
193
189
  - spec/test.mid
194
190
  homepage: http://github.com/adamjmurray/mtk
195
- licenses: []
191
+ licenses:
192
+ - BSD
193
+ metadata: {}
196
194
  post_install_message:
197
195
  rdoc_options: []
198
196
  require_paths:
199
197
  - lib
200
198
  required_ruby_version: !ruby/object:Gem::Requirement
201
- none: false
202
199
  requirements:
203
200
  - - ! '>='
204
201
  - !ruby/object:Gem::Version
205
202
  version: '0'
206
- segments:
207
- - 0
208
- hash: 3315185627907297240
209
203
  required_rubygems_version: !ruby/object:Gem::Requirement
210
- none: false
211
204
  requirements:
212
205
  - - ! '>='
213
206
  - !ruby/object:Gem::Version
214
207
  version: '0'
215
- segments:
216
- - 0
217
- hash: 3315185627907297240
218
208
  requirements: []
219
209
  rubyforge_project:
220
- rubygems_version: 1.8.25
210
+ rubygems_version: 2.0.6
221
211
  signing_key:
222
- specification_version: 3
212
+ specification_version: 4
223
213
  summary: Music Tool Kit for Ruby
224
214
  test_files: []
225
215
  has_rdoc: