jmtk 0.0.3.3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.yardopts +10 -0
  2. data/DEVELOPMENT_NOTES.md +115 -0
  3. data/INTRO.md +129 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.md +50 -0
  6. data/Rakefile +102 -0
  7. data/bin/jmtk +250 -0
  8. data/bin/mtk +250 -0
  9. data/examples/crescendo.rb +20 -0
  10. data/examples/drum_pattern.rb +23 -0
  11. data/examples/dynamic_pattern.rb +36 -0
  12. data/examples/gets_and_play.rb +27 -0
  13. data/examples/notation.rb +22 -0
  14. data/examples/play_midi.rb +17 -0
  15. data/examples/print_midi.rb +13 -0
  16. data/examples/random_tone_row.rb +18 -0
  17. data/examples/syntax_to_midi.rb +28 -0
  18. data/examples/test_output.rb +7 -0
  19. data/examples/tone_row_melody.rb +23 -0
  20. data/lib/mtk.rb +76 -0
  21. data/lib/mtk/core/duration.rb +213 -0
  22. data/lib/mtk/core/intensity.rb +158 -0
  23. data/lib/mtk/core/interval.rb +157 -0
  24. data/lib/mtk/core/pitch.rb +154 -0
  25. data/lib/mtk/core/pitch_class.rb +194 -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/events/timeline.rb +232 -0
  30. data/lib/mtk/groups/chord.rb +56 -0
  31. data/lib/mtk/groups/collection.rb +196 -0
  32. data/lib/mtk/groups/melody.rb +96 -0
  33. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  34. data/lib/mtk/groups/pitch_collection.rb +23 -0
  35. data/lib/mtk/io/dls_synth_device.rb +146 -0
  36. data/lib/mtk/io/dls_synth_output.rb +62 -0
  37. data/lib/mtk/io/jsound_input.rb +87 -0
  38. data/lib/mtk/io/jsound_output.rb +82 -0
  39. data/lib/mtk/io/midi_file.rb +209 -0
  40. data/lib/mtk/io/midi_input.rb +97 -0
  41. data/lib/mtk/io/midi_output.rb +195 -0
  42. data/lib/mtk/io/notation.rb +162 -0
  43. data/lib/mtk/io/unimidi_input.rb +117 -0
  44. data/lib/mtk/io/unimidi_output.rb +140 -0
  45. data/lib/mtk/lang/durations.rb +57 -0
  46. data/lib/mtk/lang/intensities.rb +61 -0
  47. data/lib/mtk/lang/intervals.rb +73 -0
  48. data/lib/mtk/lang/mtk_grammar.citrus +237 -0
  49. data/lib/mtk/lang/parser.rb +29 -0
  50. data/lib/mtk/lang/pitch_classes.rb +29 -0
  51. data/lib/mtk/lang/pitches.rb +52 -0
  52. data/lib/mtk/lang/pseudo_constants.rb +26 -0
  53. data/lib/mtk/lang/variable.rb +32 -0
  54. data/lib/mtk/numeric_extensions.rb +66 -0
  55. data/lib/mtk/patterns/chain.rb +49 -0
  56. data/lib/mtk/patterns/choice.rb +43 -0
  57. data/lib/mtk/patterns/cycle.rb +18 -0
  58. data/lib/mtk/patterns/for_each.rb +71 -0
  59. data/lib/mtk/patterns/function.rb +39 -0
  60. data/lib/mtk/patterns/lines.rb +54 -0
  61. data/lib/mtk/patterns/palindrome.rb +45 -0
  62. data/lib/mtk/patterns/pattern.rb +171 -0
  63. data/lib/mtk/patterns/sequence.rb +20 -0
  64. data/lib/mtk/sequencers/event_builder.rb +132 -0
  65. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  66. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  67. data/lib/mtk/sequencers/sequencer.rb +111 -0
  68. data/lib/mtk/sequencers/step_sequencer.rb +26 -0
  69. data/spec/mtk/core/duration_spec.rb +372 -0
  70. data/spec/mtk/core/intensity_spec.rb +289 -0
  71. data/spec/mtk/core/interval_spec.rb +265 -0
  72. data/spec/mtk/core/pitch_class_spec.rb +343 -0
  73. data/spec/mtk/core/pitch_spec.rb +297 -0
  74. data/spec/mtk/events/event_spec.rb +234 -0
  75. data/spec/mtk/events/note_spec.rb +174 -0
  76. data/spec/mtk/events/parameter_spec.rb +220 -0
  77. data/spec/mtk/events/timeline_spec.rb +430 -0
  78. data/spec/mtk/groups/chord_spec.rb +85 -0
  79. data/spec/mtk/groups/collection_spec.rb +374 -0
  80. data/spec/mtk/groups/melody_spec.rb +225 -0
  81. data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
  82. data/spec/mtk/io/midi_file_spec.rb +243 -0
  83. data/spec/mtk/io/midi_output_spec.rb +102 -0
  84. data/spec/mtk/lang/durations_spec.rb +89 -0
  85. data/spec/mtk/lang/intensities_spec.rb +101 -0
  86. data/spec/mtk/lang/intervals_spec.rb +143 -0
  87. data/spec/mtk/lang/parser_spec.rb +603 -0
  88. data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
  89. data/spec/mtk/lang/pitches_spec.rb +56 -0
  90. data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
  91. data/spec/mtk/lang/variable_spec.rb +52 -0
  92. data/spec/mtk/numeric_extensions_spec.rb +83 -0
  93. data/spec/mtk/patterns/chain_spec.rb +110 -0
  94. data/spec/mtk/patterns/choice_spec.rb +97 -0
  95. data/spec/mtk/patterns/cycle_spec.rb +123 -0
  96. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  97. data/spec/mtk/patterns/function_spec.rb +120 -0
  98. data/spec/mtk/patterns/lines_spec.rb +77 -0
  99. data/spec/mtk/patterns/palindrome_spec.rb +108 -0
  100. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  101. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  102. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  103. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  104. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  105. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  106. data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
  107. data/spec/spec_coverage.rb +2 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/test.mid +0 -0
  110. metadata +226 -0
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Patterns::Palindrome do
4
+
5
+ PALINDROME = MTK::Patterns::Palindrome
6
+
7
+ describe "#next" do
8
+ it "reverses direction when the ends of the list are reached" do
9
+ palindrome = PALINDROME.new [1,2,3,4]
10
+ nexts = []
11
+ 12.times { nexts << palindrome.next }
12
+ nexts.should == [1,2,3, 4,3,2, 1,2,3, 4,3,2]
13
+ end
14
+
15
+ it "reverses direction when the end of the list is reached, and repeats the end (first/last) elements" do
16
+ palindrome = PALINDROME.new [1,2,3,4], :repeat_ends => true
17
+ nexts = []
18
+ 12.times { nexts << palindrome.next }
19
+ nexts.should == [1,2,3,4, 4,3,2,1, 1,2,3,4]
20
+ end
21
+
22
+ it "enumerates nested sequences" do
23
+ palindrome = PALINDROME.new [1, MTK::Patterns.Sequence(2,3), 4]
24
+ nexts = []
25
+ 10.times { nexts << palindrome.next }
26
+ nexts.should == [1,2,3,4, 2,3,1, 2,3,4] # note sequence goes forward in both directions!
27
+ end
28
+
29
+ it "enumerates nested sequences, and repeats the last element" do
30
+ palindrome = PALINDROME.new [1, MTK::Patterns.Sequence(2,3), 4], :repeat_ends => true
31
+ nexts = []
32
+ 9.times { nexts << palindrome.next }
33
+ nexts.should == [1,2,3,4, 4,2,3,1, 1] # note sequence goes forward in both directions!
34
+ end
35
+ end
36
+
37
+ describe "#rewind" do
38
+ it "restarts the Palindrome" do
39
+ palindrome = PALINDROME.new [1,2,3,4]
40
+ 5.times { palindrome.next }
41
+ palindrome.rewind
42
+ palindrome.next.should == 1
43
+ palindrome.next.should == 2
44
+ end
45
+ end
46
+
47
+ describe "#repeat_ends?" do
48
+ it "is true if the :repeat_ends option is true" do
49
+ PALINDROME.new([], :repeat_ends => true).repeat_ends?.should be_true
50
+ end
51
+
52
+ it "is false if the :repeat_ends option is true" do
53
+ PALINDROME.new([], :repeat_ends => false).repeat_ends?.should be_false
54
+ end
55
+ end
56
+
57
+ describe "#==" do
58
+ it "is false if the :repeat_ends options are different" do
59
+ PALINDROME.new([1,2,3], :repeat_ends => true).should_not == PALINDROME.new([1,2,3], :repeat_ends => false)
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+
66
+ describe MTK::Patterns do
67
+
68
+ describe "#Palindrome" do
69
+ it "creates a Palindrome" do
70
+ MTK::Patterns.Palindrome(1,2,3).should be_a MTK::Patterns::Palindrome
71
+ end
72
+
73
+ it "sets #elements from the varargs" do
74
+ MTK::Patterns.Palindrome(1,2,3).elements.should == [1,2,3]
75
+ end
76
+ end
77
+
78
+ describe "#PitchPalindrome" do
79
+ it "creates a Palindrome" do
80
+ MTK::Patterns.PitchPalindrome(1,2,3).should be_a MTK::Patterns::Palindrome
81
+ end
82
+
83
+ it "sets #elements from the varargs" do
84
+ MTK::Patterns.PitchPalindrome(1,2,3).elements.should == [Pitch(1),Pitch(2),Pitch(3)]
85
+ end
86
+ end
87
+
88
+ describe "#IntensityPalindrome" do
89
+ it "creates a Palindrome" do
90
+ MTK::Patterns.IntensityPalindrome(1,2,3).should be_a MTK::Patterns::Palindrome
91
+ end
92
+
93
+ it "sets #elements from the varargs" do
94
+ MTK::Patterns.IntensityPalindrome(1,2,3).elements.should == [Intensity(1),Intensity(2),Intensity(3)]
95
+ end
96
+ end
97
+
98
+ describe "#DurationPalindrome" do
99
+ it "creates a Palindrome" do
100
+ MTK::Patterns.DurationPalindrome(1,2,3).should be_a MTK::Patterns::Palindrome
101
+ end
102
+
103
+ it "sets #elements from the varargs" do
104
+ MTK::Patterns.DurationPalindrome(1,2,3).elements.should == [Duration(1),Duration(2),Duration(3)]
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Patterns::Pattern do
4
+
5
+ PATTERN = MTK::Patterns::Pattern
6
+
7
+ let(:elements) { [1,2,3] }
8
+
9
+
10
+ describe "@min_elements" do
11
+ it "is the :min_elements option the pattern was constructed with" do
12
+ PATTERN.new([], min_elements: 1).min_elements.should == 1
13
+ end
14
+
15
+ it "is nil by default" do
16
+ PATTERN.new([]).min_elements.should be_nil
17
+ end
18
+
19
+ it "prevents a StopIteration error until min_elements have been emitted (overriding max_elements if necessary)" do
20
+ pattern = PATTERN.new([1,2,3], min_elements: 5, max_elements: 3)
21
+ 5.times{ pattern.next }
22
+ lambda{ pattern.next }.should raise_error StopIteration
23
+ end
24
+
25
+ it "sets exactly how many elements will be emitted when set to the same values as max_elements" do
26
+ pattern = PATTERN.new([1,2,3], min_elements: 5, max_elements: 5)
27
+ 5.times{ pattern.next }
28
+ lambda{ pattern.next }.should raise_error StopIteration
29
+ end
30
+
31
+ it "is maintained when applying Collection operations" do
32
+ PATTERN.new(elements, min_elements: 2).reverse.min_elements.should == 2
33
+ end
34
+ end
35
+
36
+ describe '#min_elements_unmet?' do
37
+ it "is true until min_elements have been emitted" do
38
+ pattern = PATTERN.new([:anything], min_elements: 5)
39
+ 5.times { pattern.min_elements_unmet?.should be_true and pattern.next }
40
+ pattern.min_elements_unmet?.should be_false
41
+ end
42
+
43
+ it "is always false if min_elements is not set" do
44
+ pattern = PATTERN.new([:anything], min_elements: nil)
45
+ 5.times { pattern.min_elements_unmet?.should be_false and pattern.next }
46
+ end
47
+
48
+ it "is always false if min_elements is 0" do
49
+ pattern = PATTERN.new([:anything], min_elements: 0)
50
+ 5.times { pattern.min_elements_unmet?.should be_false and pattern.next }
51
+ end
52
+ end
53
+
54
+
55
+ describe "@max_elements" do
56
+ it "is the :max_elements option the pattern was constructed with" do
57
+ PATTERN.new([], max_elements: 1).max_elements.should == 1
58
+ end
59
+
60
+ it "is nil by default" do
61
+ PATTERN.new([]).max_elements.should be_nil
62
+ end
63
+
64
+ it "causes a StopIteration exception once max_elements have been emitted" do
65
+ pattern = PATTERN.new([:anything], max_elements: 5)
66
+ 5.times { pattern.next }
67
+ lambda { pattern.next }.should raise_error
68
+ end
69
+
70
+ it "raises a StopIteration error when a nested pattern has emitted more than max_elements" do
71
+ pattern = PATTERN.new([Patterns.Cycle(1,2)], max_elements: 5)
72
+ 5.times{ pattern.next }
73
+ lambda{ pattern.next }.should raise_error StopIteration
74
+ end
75
+
76
+ it "is maintained when applying Collection operations" do
77
+ PATTERN.new(elements, max_elements: 2).reverse.max_elements.should == 2
78
+ end
79
+ end
80
+
81
+
82
+ describe '#max_elements_exceeded?' do
83
+ it "is false until max_elements have been emitted" do
84
+ pattern = PATTERN.new([:anything], max_elements: 5)
85
+ 5.times { pattern.max_elements_exceeded?.should be_false and pattern.next }
86
+ pattern.max_elements_exceeded?.should be_true
87
+ end
88
+
89
+ it "is always false when max_elements is not set" do
90
+ pattern = PATTERN.new([:anything], max_elements: nil)
91
+ 5.times { pattern.max_elements_exceeded?.should be_false and pattern.next }
92
+ end
93
+ end
94
+
95
+
96
+ describe "@max_cycles" do
97
+ it "is the :max_cycles option the pattern was constructed with" do
98
+ PATTERN.new([], max_cycles: 2).max_cycles.should == 2
99
+ end
100
+
101
+ it "is 1 by default" do
102
+ PATTERN.new([]).max_cycles.should == 1
103
+ end
104
+
105
+ it "is maintained when applying Collection operations" do
106
+ PATTERN.new(elements, max_cycles: 2).reverse.max_cycles.should == 2
107
+ end
108
+ end
109
+
110
+
111
+ describe "#next" do
112
+ it "raises StopIteration if elements is empty" do
113
+ lambda{ PATTERN.new([]).next }.should raise_error StopIteration
114
+ end
115
+ end
116
+
117
+
118
+ describe "#==" do
119
+ it "is true if the elements and types are equal" do
120
+ PATTERN.new(elements, :type => :some_type).should == PATTERN.new(elements, :type => :some_type)
121
+ end
122
+
123
+ it "is false if the elements are not equal" do
124
+ PATTERN.new(elements, :type => :some_type).should_not == PATTERN.new(elements + [4], :type => :some_type)
125
+ end
126
+
127
+ it "is false if the types are not equal" do
128
+ PATTERN.new(elements, :type => :some_type).should_not == PATTERN.new(elements, :type => :another_type)
129
+ end
130
+ end
131
+
132
+ end
@@ -0,0 +1,203 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Patterns::Sequence do
4
+
5
+ SEQUENCE = MTK::Patterns::Sequence
6
+
7
+ let(:elements) { [1,2,3] }
8
+ let(:sequence) { SEQUENCE.new(elements) }
9
+
10
+ it "is a MTK::Collection" do
11
+ sequence.should be_a MTK::Groups::Collection
12
+ # and now we won't test any other collection features here... see collection_spec
13
+ end
14
+
15
+ describe ".from_a" do
16
+ it "acts like .new" do
17
+ SEQUENCE.from_a(elements).should == sequence
18
+ end
19
+ end
20
+
21
+ describe "#elements" do
22
+ it "is the array the sequence was constructed with" do
23
+ sequence.elements.should == elements
24
+ end
25
+ end
26
+
27
+ describe "#next" do
28
+ it "enumerates the elements" do
29
+ nexts = []
30
+ elements.length.times do
31
+ nexts << sequence.next
32
+ end
33
+ nexts.should == elements
34
+ end
35
+
36
+ it "raises StopIteration when the end of the Sequence is reached" do
37
+ elements.length.times{ sequence.next }
38
+ lambda{ sequence.next }.should raise_error(StopIteration)
39
+ end
40
+
41
+ it "should automatically break out of Kernel#loop" do
42
+ nexts = []
43
+ loop do # loop rescues StopIteration and exits the loop
44
+ nexts << sequence.next
45
+ end
46
+ nexts.should == elements
47
+ end
48
+
49
+ it "enumerates the elements in sub-sequences" do
50
+ sub_sequence = SEQUENCE.new [2,3]
51
+ sequence = SEQUENCE.new [1,sub_sequence,4]
52
+ nexts = []
53
+ loop { nexts << sequence.next }
54
+ nexts.should == [1,2,3,4]
55
+ end
56
+
57
+ it "skips over empty sub-sequences" do
58
+ sub_sequence = SEQUENCE.new []
59
+ sequence = SEQUENCE.new [1,sub_sequence,4]
60
+ nexts = []
61
+ loop { nexts << sequence.next }
62
+ nexts.should == [1,4]
63
+ end
64
+
65
+ end
66
+
67
+ describe "@min_elements" do
68
+ it "prevents a StopIteration error until min_elements have been emitted" do
69
+ pattern = SEQUENCE.new([1,2,3], cycle_count: 1, min_elements: 5)
70
+ 5.times{ pattern.next }
71
+ lambda{ pattern.next }.should raise_error StopIteration
72
+ end
73
+ end
74
+
75
+
76
+ describe "@max_cycles" do
77
+ it "is the :max_cycles option the pattern was constructed with" do
78
+ SEQUENCE.new( [], max_cycles: 2 ).max_cycles.should == 2
79
+ end
80
+
81
+ it "is 1 by default" do
82
+ SEQUENCE.new( [] ).max_cycles.should == 1
83
+ end
84
+
85
+ it "loops indefinitely when it's nil" do
86
+ sequence = SEQUENCE.new( [1], max_cycles: nil )
87
+ lambda { 100.times { sequence.next } }.should_not raise_error
88
+ end
89
+
90
+ it "causes a StopIteration exception after the number of cycles has completed" do
91
+ sequence = SEQUENCE.new( elements, max_cycles: 2 )
92
+ 2.times do
93
+ elements.length.times { sequence.next } # one full cycle
94
+ end
95
+ lambda { sequence.next }.should raise_error StopIteration
96
+ end
97
+ end
98
+
99
+
100
+ describe "#max_cycles_exceeded?" do
101
+ it "is false until max_elements have been emitted" do
102
+ sequence = SEQUENCE.new( elements, max_cycles: 2 )
103
+ 2.times do
104
+ sequence.max_cycles_exceeded?.should be_false
105
+ elements.length.times { sequence.next } # one full cycle
106
+ end
107
+ # unlike with element_count and max_elements_exceeded?, cycle_count doesn't get bumped until
108
+ # you ask for the next element and a StopIteration is thrown
109
+ lambda { sequence.next }.should raise_error StopIteration
110
+ sequence.max_cycles_exceeded?.should be_true
111
+ end
112
+
113
+ it "is always false when max_cycles is not set" do
114
+ pattern = PATTERN.new([:anything], max_cycles: nil)
115
+ 15.times { pattern.max_cycles_exceeded?.should be_false and pattern.next }
116
+ end
117
+ end
118
+
119
+
120
+ it "has max_elements_exceeded once max_elements have been emitted (edge case, has same number of elements)" do
121
+ sequence = SEQUENCE.new([1,2,3,4,5], :max_elements => 5)
122
+ 5.times { sequence.max_elements_exceeded?.should(be_false) and sequence.next }
123
+ sequence.max_elements_exceeded?.should be_true
124
+ lambda { sequence.next }.should raise_error StopIteration
125
+ end
126
+
127
+
128
+ it "raises a StopIteration error when a nested pattern has emitted more than max_elements" do
129
+ sequence = SEQUENCE.new([Patterns.Cycle(1,2)], :max_elements => 5)
130
+ 5.times { sequence.next }
131
+ lambda{ sequence.next }.should raise_error StopIteration
132
+ end
133
+
134
+
135
+ describe "#rewind" do
136
+ it "restarts at the beginning of the sequence" do
137
+ loop { sequence.next }
138
+ sequence.rewind
139
+ sequence.next.should == elements.first
140
+ end
141
+
142
+ it "returns self, so it can be chained to #next" do
143
+ first = sequence.next
144
+ sequence.rewind.next.should == first
145
+ end
146
+
147
+ it "causes sub-sequences to start from the beginning when encountered again after #rewind" do
148
+ sub_sequence = SEQUENCE.new [2,3]
149
+ sequence = SEQUENCE.new [1,sub_sequence,4]
150
+ loop { sequence.next }
151
+ sequence.rewind
152
+ nexts = []
153
+ loop { nexts << sequence.next }
154
+ nexts.should == [1,2,3,4]
155
+ end
156
+ end
157
+
158
+ end
159
+
160
+
161
+ describe MTK::Patterns do
162
+
163
+ describe "#Sequence" do
164
+ it "creates a Sequence" do
165
+ MTK::Patterns.Sequence(1,2,3).should be_a MTK::Patterns::Sequence
166
+ end
167
+
168
+ it "sets #elements from the varargs" do
169
+ MTK::Patterns.Sequence(1,2,3).elements.should == [1,2,3]
170
+ end
171
+ end
172
+
173
+ describe "#PitchSequence" do
174
+ it "creates a Sequence" do
175
+ MTK::Patterns.PitchSequence(1,2,3).should be_a MTK::Patterns::Sequence
176
+ end
177
+
178
+ it "sets #elements from the varargs" do
179
+ MTK::Patterns.PitchSequence(1,2,3).elements.should == [Pitch(1),Pitch(2),Pitch(3)]
180
+ end
181
+ end
182
+
183
+ describe "#IntensitySequence" do
184
+ it "creates a Sequence" do
185
+ MTK::Patterns.IntensitySequence(1,2,3).should be_a MTK::Patterns::Sequence
186
+ end
187
+
188
+ it "sets #elements from the varargs" do
189
+ MTK::Patterns.IntensitySequence(1,2,3).elements.should == [Intensity(1),Intensity(2),Intensity(3)]
190
+ end
191
+ end
192
+
193
+ describe "#DurationSequence" do
194
+ it "creates a Sequence" do
195
+ MTK::Patterns.DurationSequence(1,2,3).should be_a MTK::Patterns::Sequence
196
+ end
197
+
198
+ it "sets #elements from the varargs" do
199
+ MTK::Patterns.DurationSequence(1,2,3).elements.should == [Duration(1),Duration(2),Duration(3)]
200
+ end
201
+ end
202
+
203
+ end
@@ -0,0 +1,245 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Sequencers::EventBuilder do
4
+
5
+ EVENT_BUILDER = MTK::Sequencers::EventBuilder
6
+
7
+ let(:pitch) { EVENT_BUILDER::DEFAULT_PITCH }
8
+ let(:intensity) { EVENT_BUILDER::DEFAULT_INTENSITY }
9
+ let(:duration) { EVENT_BUILDER::DEFAULT_DURATION }
10
+
11
+ def notes(*pitches)
12
+ pitches.map{|pitch| Note(pitch, intensity, duration) }
13
+ end
14
+
15
+ describe "#new" do
16
+ it "allows default pitch to be specified" do
17
+ event_builder = EVENT_BUILDER.new [Patterns.IntervalCycle(0)], :default_pitch => Gb4
18
+ event_builder.next.should == [Note(Gb4, intensity, duration)]
19
+ end
20
+ it "allows default intensity to be specified" do
21
+ event_builder = EVENT_BUILDER.new [Patterns.IntervalCycle(0)], :default_intensity => ppp
22
+ event_builder.next.should == [Note(pitch, ppp, duration)]
23
+ end
24
+ it "allows default duration to be specified" do
25
+ event_builder = EVENT_BUILDER.new [Patterns.IntervalCycle(0)], :default_duration => 5.25
26
+ event_builder.next.should == [Note(pitch, 5.25, intensity)]
27
+ end
28
+ end
29
+
30
+ describe "#next" do
31
+ it "builds a single-note list from a single-pitch pattern argument" do
32
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle(C4)]
33
+ event_builder.next.should == notes(C4)
34
+ end
35
+
36
+ it "builds a list of notes from any pitches in the argument" do
37
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle(C4), Patterns.Cycle(D4)]
38
+ event_builder.next.should == notes(C4, D4)
39
+ end
40
+
41
+ it "builds a list of notes from pitch sets" do
42
+ event_builder = EVENT_BUILDER.new [ Patterns.Cycle( Chord(C4,D4) ) ]
43
+ event_builder.next.should == notes(C4, D4)
44
+ end
45
+
46
+ it "builds notes from pitch classes and a default_pitch, selecting the nearest pitch class to the previous pitch" do
47
+ event_builder = EVENT_BUILDER.new [Patterns.Sequence(C,G,B,Eb,D,C)], :default_pitch => D3
48
+ notes = []
49
+ loop do
50
+ notes << event_builder.next
51
+ end
52
+ notes.flatten.should == notes(C3,G2,B2,Eb3,D3,C3)
53
+ end
54
+
55
+ it "defaults to a starting point of C4 (middle C)" do
56
+ event_builder = EVENT_BUILDER.new [Patterns.Sequence(C4)]
57
+ event_builder.next.should == notes(C4)
58
+ end
59
+
60
+ it "defaults to intensity 'o' when no intensities are given" do
61
+ event_builder = EVENT_BUILDER.new [Patterns.PitchSequence(C4, D4, E4), Patterns.DurationCycle(2)]
62
+ event_builder.next.should == [Note(C4, o, 2)]
63
+ event_builder.next.should == [Note(D4, o, 2)]
64
+ event_builder.next.should == [Note(E4, o, 2)]
65
+ end
66
+
67
+ it "defaults to duration 1 when no durations are given" do
68
+ event_builder = EVENT_BUILDER.new [Patterns.PitchSequence(C4, D4, E4), Patterns.IntensityCycle(p,o)]
69
+ event_builder.next.should == [Note(C4, p, 1)]
70
+ event_builder.next.should == [Note(D4, o, 1)]
71
+ event_builder.next.should == [Note(E4, p, 1)]
72
+ end
73
+
74
+ it "builds notes from pitch class sets, selecting the nearest pitch classes to the previous/default pitch" do
75
+ pitch_class_sequence = MTK::Patterns::Sequence.new([PitchClassSet(C,G),PitchClassSet(B,Eb),PitchClassSet(D,C)])
76
+ event_builder = EVENT_BUILDER.new [pitch_class_sequence], :default_pitch => D3
77
+ event_builder.next.should == notes(C3,G3)
78
+ event_builder.next.should == notes(B3,Eb3)
79
+ event_builder.next.should == notes(D3,C3)
80
+ end
81
+
82
+ it "builds notes from by adding Numeric intervals in :pitch type Patterns to the previous Pitch" do
83
+ event_builder = EVENT_BUILDER.new [ Patterns.Sequence( C4, M3, m3, -P5 ) ]
84
+ nexts = []
85
+ loop { nexts << event_builder.next }
86
+ nexts.should == [notes(C4), notes(E4), notes(G4), notes(C4)]
87
+ end
88
+
89
+ it "builds notes from by adding Numeric intervals in :pitch type Patterns to all pitches in the previous Chord" do
90
+ event_builder = EVENT_BUILDER.new [ Patterns.Sequence( Chord(C4,Eb4), M3, m3, -P5) ]
91
+ nexts = []
92
+ loop { nexts << event_builder.next }
93
+ nexts.should == [notes(C4,Eb4), notes(E4,G4), notes(G4,Bb4), notes(C4,Eb4)]
94
+ end
95
+
96
+ it "builds notes from intensities" do
97
+ event_builder = EVENT_BUILDER.new [ Patterns.Cycle(C4), Patterns.Sequence(mf, p, fff) ]
98
+ nexts = []
99
+ loop { nexts += event_builder.next }
100
+ nexts.should == [Note(C4, mf, duration), Note(C4, p, duration), Note(C4, fff, duration)]
101
+ end
102
+
103
+ it "builds notes from durations" do
104
+ event_builder = EVENT_BUILDER.new [ Patterns.PitchCycle(C4), Patterns.DurationSequence(1,2,3) ]
105
+ nexts = []
106
+ loop { nexts += event_builder.next }
107
+ nexts.should == [Note(C4, intensity, 1), Note(C4, intensity, 2), Note(C4, intensity, 3)]
108
+ end
109
+
110
+ it "iterates through the pitch, intensity, and duration list in parallel to emit Notes" do
111
+ event_builder = EVENT_BUILDER.new [Patterns.PitchCycle(C4, D4, E4), Patterns.IntensityCycle(p, o), Patterns.DurationCycle(1,2,3,4)]
112
+ event_builder.next.should == [Note(C4, p, 1)]
113
+ event_builder.next.should == [Note(D4, o, 2)]
114
+ event_builder.next.should == [Note(E4, p, 3)]
115
+ event_builder.next.should == [Note(C4, o, 4)]
116
+ event_builder.next.should == [Note(D4, p, 1)]
117
+ event_builder.next.should == [Note(E4, o, 2)]
118
+ end
119
+
120
+ it "returns nil (for a rest) when it encounters a nil value" do
121
+ event_builder = EVENT_BUILDER.new [Patterns.PitchCycle(C4, D4, E4, F4, nil), Patterns.IntensityCycle(mp, mf, o, nil), Patterns.DurationCycle(1, 2, nil)]
122
+ event_builder.next.should == [Note(C4, mp, 1)]
123
+ event_builder.next.should == [Note(D4, mf, 2)]
124
+ event_builder.next.should be_nil
125
+ event_builder.next.should be_nil
126
+ event_builder.next.should be_nil
127
+ end
128
+
129
+ it "goes to the nearest Pitch for any PitchClasses in the pitch list" do
130
+ event_builder = EVENT_BUILDER.new [Patterns::Cycle(C4, F, C, G, C)]
131
+ event_builder.next.should == notes(C4)
132
+ event_builder.next.should == notes(F4)
133
+ event_builder.next.should == notes(C4)
134
+ event_builder.next.should == notes(G3)
135
+ event_builder.next.should == notes(C4)
136
+ end
137
+
138
+ it "does not endlessly ascend or descend when alternating between two pitch classes a tritone apart" do
139
+ event_builder = EVENT_BUILDER.new [Patterns::Cycle(C4, Gb, C, Gb, C)]
140
+ event_builder.next.should == notes(C4)
141
+ event_builder.next.should == notes(Gb4)
142
+ event_builder.next.should == notes(C4)
143
+ event_builder.next.should == notes(Gb4)
144
+ event_builder.next.should == notes(C4)
145
+ end
146
+
147
+ it "handles pitches and chords intermixed" do
148
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( Chord(C4, E4, G4), C4, Chord(D4, F4, A4) )]
149
+ event_builder.next.should == notes(C4,E4,G4)
150
+ event_builder.next.should == notes(C4)
151
+ event_builder.next.should == notes(D4,F4,A4)
152
+ end
153
+
154
+ it "adds numeric intervals to Chord" do
155
+ event_builder = EVENT_BUILDER.new [Patterns::Cycle( Chord(C4, E4, G4), M2 )]
156
+ event_builder.next.should == notes(C4,E4,G4)
157
+ event_builder.next.should == notes(D4,Gb4,A4)
158
+ end
159
+
160
+ it "goes to the nearest Pitch relative to the lowest note in the Chord for any PitchClasses in the pitch list" do
161
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( Chord(C4, E4, G4), F, D, Bb )]
162
+ event_builder.next.should == notes(C4,E4,G4)
163
+ event_builder.next.should == notes(F4)
164
+ event_builder.next.should == notes(D4)
165
+ event_builder.next.should == notes(Bb3)
166
+ end
167
+
168
+ it "uses the default_pitch when no pitch pattern is provided" do
169
+ event_builder = EVENT_BUILDER.new [Patterns.Cycle( mp, mf, o )], :default_pitch => G3
170
+ event_builder.next.should == [Note(G3,mp,1)]
171
+ event_builder.next.should == [Note(G3,mf,1)]
172
+ event_builder.next.should == [Note(G3,o,1)]
173
+ end
174
+
175
+ it "handles chains of sequences" do
176
+ event_builder = EVENT_BUILDER.new [ Patterns.Chain( Patterns.Sequence(C4,D4,E4), Patterns.Sequence(mp,mf,ff), Patterns.Sequence(q,h,w) ) ]
177
+ event_builder.next.should == [Note(C4,mp,q)]
178
+ event_builder.next.should == [Note(D4,mf,h)]
179
+ event_builder.next.should == [Note(E4,ff,w)]
180
+ end
181
+
182
+ it "enforces the max_interval option for rising intervals" do
183
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5)], max_interval:12 )
184
+ pitches = []
185
+ 13.times{ pitches << event_builder.next[0].pitch }
186
+ pitches.should == [C4,G4,D4,A4,E4,B4,Gb4,Db4,Ab4,Eb4,Bb4,F4,C5]
187
+
188
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5,P5)], max_interval:11 )
189
+ pitches = []
190
+ 13.times{ pitches << event_builder.next[0].pitch }
191
+ pitches.should == [C4,G4,D4,A4,E4,B4,Gb4,Db4,Ab4,Eb4,Bb4,F4,C4]
192
+ end
193
+
194
+ it "enforces the max_interval option for falling intervals" do
195
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5)], max_interval:12 )
196
+ pitches = []
197
+ 13.times{ pitches << event_builder.next[0].pitch }
198
+ pitches.should == [C4,F3,Bb3,Eb3,Ab3,Db3,Gb3,B3,E3,A3,D3,G3,C3]
199
+
200
+ event_builder = EVENT_BUILDER.new( [ Patterns.Sequence(C4,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5,-P5)], max_interval:11 )
201
+ pitches = []
202
+ 13.times{ pitches << event_builder.next[0].pitch }
203
+ pitches.should == [C4,F3,Bb3,Eb3,Ab3,Db3,Gb3,B3,E3,A3,D3,G3,C4]
204
+ end
205
+
206
+ it "adds chained durations together" do
207
+ event_builder = EVENT_BUILDER.new( [Patterns.Chain(h,q,i,s)] )
208
+ event_builder.next[0].duration.should == h+q+i+s
209
+ end
210
+
211
+ it "averages chained intensities together" do
212
+ event_builder = EVENT_BUILDER.new( [Patterns.IntensityChain(0.1, 0.2, 0.3, 0.4)] )
213
+ event_builder.next[0].intensity.should == Intensity(0.25)
214
+ end
215
+
216
+ it "defaults the intensity to the previous intensity" do
217
+ event_builder = EVENT_BUILDER.new(
218
+ [Patterns.Sequence(Patterns.Chain(C4,ppp,q), Patterns.Chain(D4,i), Patterns.Chain(E4,ff,h), Patterns.Chain(F4,i))]
219
+ )
220
+ notes = []
221
+ 4.times{ notes += event_builder.next }
222
+ notes.should == [Note(C4,ppp,q), Note(D4,ppp,i), Note(E4,ff,h), Note(F4,ff,i)]
223
+ end
224
+
225
+ it "defaults the duration to the previous duration" do
226
+ event_builder = EVENT_BUILDER.new(
227
+ [Patterns.Sequence(Patterns.Chain(C4,ppp,h), Patterns.Chain(D4,mp), Patterns.Chain(E4,ff,s), Patterns.Chain(F4,mf))]
228
+ )
229
+ notes = []
230
+ 4.times{ notes += event_builder.next }
231
+ notes.should == [Note(C4,ppp,h), Note(D4,mp,h), Note(E4,ff,s), Note(F4,mf,s)]
232
+ end
233
+ end
234
+
235
+ describe "#rewind" do
236
+ it "resets the state of the Chain" do
237
+ event_builder = EVENT_BUILDER.new [ Patterns.Sequence(C,P8) ]
238
+ event_builder.next.should == [Note(C4,intensity,duration)]
239
+ event_builder.next.should == [Note(C5,intensity,duration)]
240
+ event_builder.rewind
241
+ event_builder.next.should == [Note(C4,intensity,duration)]
242
+ end
243
+ end
244
+
245
+ end