jmtk 0.0.3.3-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.
- data/.yardopts +10 -0
- data/DEVELOPMENT_NOTES.md +115 -0
- data/INTRO.md +129 -0
- data/LICENSE.txt +27 -0
- data/README.md +50 -0
- data/Rakefile +102 -0
- data/bin/jmtk +250 -0
- data/bin/mtk +250 -0
- data/examples/crescendo.rb +20 -0
- data/examples/drum_pattern.rb +23 -0
- data/examples/dynamic_pattern.rb +36 -0
- data/examples/gets_and_play.rb +27 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +17 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +7 -0
- data/examples/tone_row_melody.rb +23 -0
- data/lib/mtk.rb +76 -0
- data/lib/mtk/core/duration.rb +213 -0
- data/lib/mtk/core/intensity.rb +158 -0
- data/lib/mtk/core/interval.rb +157 -0
- data/lib/mtk/core/pitch.rb +154 -0
- data/lib/mtk/core/pitch_class.rb +194 -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/events/timeline.rb +232 -0
- data/lib/mtk/groups/chord.rb +56 -0
- data/lib/mtk/groups/collection.rb +196 -0
- data/lib/mtk/groups/melody.rb +96 -0
- data/lib/mtk/groups/pitch_class_set.rb +163 -0
- data/lib/mtk/groups/pitch_collection.rb +23 -0
- data/lib/mtk/io/dls_synth_device.rb +146 -0
- data/lib/mtk/io/dls_synth_output.rb +62 -0
- data/lib/mtk/io/jsound_input.rb +87 -0
- data/lib/mtk/io/jsound_output.rb +82 -0
- data/lib/mtk/io/midi_file.rb +209 -0
- data/lib/mtk/io/midi_input.rb +97 -0
- data/lib/mtk/io/midi_output.rb +195 -0
- data/lib/mtk/io/notation.rb +162 -0
- data/lib/mtk/io/unimidi_input.rb +117 -0
- data/lib/mtk/io/unimidi_output.rb +140 -0
- data/lib/mtk/lang/durations.rb +57 -0
- data/lib/mtk/lang/intensities.rb +61 -0
- data/lib/mtk/lang/intervals.rb +73 -0
- data/lib/mtk/lang/mtk_grammar.citrus +237 -0
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/lang/pitch_classes.rb +29 -0
- data/lib/mtk/lang/pitches.rb +52 -0
- data/lib/mtk/lang/pseudo_constants.rb +26 -0
- data/lib/mtk/lang/variable.rb +32 -0
- data/lib/mtk/numeric_extensions.rb +66 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/patterns/choice.rb +43 -0
- 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/patterns/lines.rb +54 -0
- data/lib/mtk/patterns/palindrome.rb +45 -0
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/sequencers/event_builder.rb +132 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/sequencers/sequencer.rb +111 -0
- data/lib/mtk/sequencers/step_sequencer.rb +26 -0
- data/spec/mtk/core/duration_spec.rb +372 -0
- data/spec/mtk/core/intensity_spec.rb +289 -0
- data/spec/mtk/core/interval_spec.rb +265 -0
- data/spec/mtk/core/pitch_class_spec.rb +343 -0
- data/spec/mtk/core/pitch_spec.rb +297 -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/events/timeline_spec.rb +430 -0
- data/spec/mtk/groups/chord_spec.rb +85 -0
- data/spec/mtk/groups/collection_spec.rb +374 -0
- data/spec/mtk/groups/melody_spec.rb +225 -0
- data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
- data/spec/mtk/io/midi_file_spec.rb +243 -0
- data/spec/mtk/io/midi_output_spec.rb +102 -0
- data/spec/mtk/lang/durations_spec.rb +89 -0
- data/spec/mtk/lang/intensities_spec.rb +101 -0
- data/spec/mtk/lang/intervals_spec.rb +143 -0
- data/spec/mtk/lang/parser_spec.rb +603 -0
- data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
- data/spec/mtk/lang/pitches_spec.rb +56 -0
- data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/variable_spec.rb +52 -0
- data/spec/mtk/numeric_extensions_spec.rb +83 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/patterns/choice_spec.rb +97 -0
- data/spec/mtk/patterns/cycle_spec.rb +123 -0
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/patterns/function_spec.rb +120 -0
- data/spec/mtk/patterns/lines_spec.rb +77 -0
- data/spec/mtk/patterns/palindrome_spec.rb +108 -0
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- 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/sequencers/step_sequencer_spec.rb +93 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test.mid +0 -0
- 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
|