mtk 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.yardopts +3 -2
- data/DEVELOPMENT_NOTES.md +114 -0
- data/INTRO.md +64 -8
- data/LICENSE.txt +1 -1
- data/README.md +31 -102
- data/Rakefile +56 -18
- data/bin/mtk +215 -0
- data/examples/crescendo.rb +5 -5
- data/examples/drum_pattern1.rb +23 -0
- data/examples/dynamic_pattern.rb +8 -11
- data/examples/gets_and_play.rb +26 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +8 -10
- data/examples/random_tone_row.rb +2 -2
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +8 -0
- data/examples/tone_row_melody.rb +6 -6
- data/lib/mtk.rb +52 -40
- data/lib/mtk/chord.rb +55 -0
- data/lib/mtk/constants/durations.rb +57 -0
- data/lib/mtk/constants/intensities.rb +61 -0
- data/lib/mtk/constants/intervals.rb +73 -0
- data/lib/mtk/constants/pitch_classes.rb +29 -0
- data/lib/mtk/constants/pitches.rb +52 -0
- data/lib/mtk/duration.rb +211 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/helpers/collection.rb +164 -0
- data/lib/mtk/helpers/convert.rb +36 -0
- data/lib/mtk/helpers/lilypond.rb +162 -0
- data/lib/mtk/helpers/output_selector.rb +67 -0
- data/lib/mtk/helpers/pitch_collection.rb +23 -0
- data/lib/mtk/helpers/pseudo_constants.rb +26 -0
- data/lib/mtk/intensity.rb +156 -0
- data/lib/mtk/interval.rb +155 -0
- data/lib/mtk/lang/mtk_grammar.citrus +190 -13
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/melody.rb +94 -0
- data/lib/mtk/midi/dls_synth_device.rb +144 -0
- data/lib/mtk/midi/dls_synth_output.rb +62 -0
- data/lib/mtk/midi/file.rb +67 -32
- data/lib/mtk/midi/input.rb +97 -0
- data/lib/mtk/midi/jsound_input.rb +36 -17
- data/lib/mtk/midi/jsound_output.rb +48 -46
- data/lib/mtk/midi/output.rb +195 -0
- data/lib/mtk/midi/unimidi_input.rb +117 -0
- data/lib/mtk/midi/unimidi_output.rb +121 -0
- data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
- data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/pitch.rb +7 -6
- data/lib/mtk/pitch_class.rb +124 -46
- data/lib/mtk/pitch_class_set.rb +58 -35
- data/lib/mtk/sequencers/event_builder.rb +131 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
- data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
- data/lib/mtk/timeline.rb +39 -22
- data/lib/mtk/variable.rb +32 -0
- data/spec/mtk/chord_spec.rb +83 -0
- data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
- data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
- data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
- data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
- data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
- data/spec/mtk/duration_spec.rb +372 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
- data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
- data/spec/mtk/intensity_spec.rb +289 -0
- data/spec/mtk/interval_spec.rb +265 -0
- data/spec/mtk/lang/parser_spec.rb +597 -0
- data/spec/mtk/melody_spec.rb +223 -0
- data/spec/mtk/midi/file_spec.rb +16 -16
- data/spec/mtk/midi/jsound_input_spec.rb +11 -0
- data/spec/mtk/midi/jsound_output_spec.rb +11 -0
- data/spec/mtk/midi/output_spec.rb +102 -0
- data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
- data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
- data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
- data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
- data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
- data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/pitch_class_set_spec.rb +23 -21
- data/spec/mtk/pitch_class_spec.rb +151 -39
- data/spec/mtk/pitch_spec.rb +22 -1
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
- data/spec/mtk/timeline_spec.rb +109 -16
- data/spec/mtk/variable_spec.rb +52 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- metadata +188 -91
- data/lib/mtk/_constants/durations.rb +0 -80
- data/lib/mtk/_constants/intensities.rb +0 -81
- data/lib/mtk/_constants/intervals.rb +0 -85
- data/lib/mtk/_constants/pitch_classes.rb +0 -35
- data/lib/mtk/_constants/pitches.rb +0 -49
- data/lib/mtk/event.rb +0 -70
- data/lib/mtk/helper/collection.rb +0 -114
- data/lib/mtk/helper/event_builder.rb +0 -85
- data/lib/mtk/helper/pseudo_constants.rb +0 -26
- data/lib/mtk/lang/grammar.rb +0 -17
- data/lib/mtk/note.rb +0 -63
- data/lib/mtk/pattern/abstract_pattern.rb +0 -132
- data/lib/mtk/pattern/cycle.rb +0 -51
- data/lib/mtk/pattern/enumerator.rb +0 -26
- data/lib/mtk/pattern/function.rb +0 -46
- data/lib/mtk/pattern/sequence.rb +0 -30
- data/lib/mtk/pitch_set.rb +0 -84
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
- data/lib/mtk/transform/invertible.rb +0 -15
- data/lib/mtk/transform/mappable.rb +0 -18
- data/lib/mtk/transform/set_theory_operations.rb +0 -34
- data/lib/mtk/transform/transposable.rb +0 -14
- data/spec/mtk/event_spec.rb +0 -139
- data/spec/mtk/helper/event_builder_spec.rb +0 -92
- data/spec/mtk/lang/grammar_spec.rb +0 -100
- data/spec/mtk/note_spec.rb +0 -115
- data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
- data/spec/mtk/pattern/sequence_spec.rb +0 -151
- data/spec/mtk/pitch_set_spec.rb +0 -198
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -0,0 +1,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
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Sequencers::LegatoSequencer do
|
4
|
+
|
5
|
+
LEGATO_SEQUENCER = Sequencers::LegatoSequencer
|
6
|
+
|
7
|
+
let(:pitches) { Patterns.PitchSequence(C4, D4, E4, C4) }
|
8
|
+
let(:durations) { Patterns.DurationSequence(1, 0.5, 1.5, 4) }
|
9
|
+
let(:intensities) { Patterns.IntensitySequence(0.3, 0.6, 0.9, 1.0) }
|
10
|
+
let(:legato_sequencer) { LEGATO_SEQUENCER.new [pitches, durations, intensities] }
|
11
|
+
|
12
|
+
|
13
|
+
describe "#to_timeline" do
|
14
|
+
it "contains notes assembled from the given patterns, with Timeline time deltas from the max event duration at the previous step" do
|
15
|
+
legato_sequencer.to_timeline.should == Timeline.from_hash({
|
16
|
+
0 => Note(C4,1,0.3),
|
17
|
+
1.0 => Note(D4,0.5,0.6),
|
18
|
+
1.5 => Note(E4,1.5,0.9),
|
19
|
+
3.0 => Note(C4,4,1.0)
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
it "treats negative durations as rests" do
|
24
|
+
legato_sequencer = LEGATO_SEQUENCER.new( [pitches, Patterns.DurationSequence(-1,-0.5,-1.5,4), intensities] )
|
25
|
+
legato_sequencer.to_timeline.should == Timeline.from_hash({
|
26
|
+
3.0 => Note(C4,4,1.0)
|
27
|
+
})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
describe MTK::Sequencers do
|
35
|
+
|
36
|
+
describe "#LegatoSequencer" do
|
37
|
+
it "creates a LegatoSequencer" do
|
38
|
+
MTK::Sequencers.LegatoSequencer(1,2,3, rhythm:1).should be_a MTK::Sequencers::LegatoSequencer
|
39
|
+
end
|
40
|
+
|
41
|
+
it "sets #patterns from the varargs" do
|
42
|
+
MTK::Sequencers.LegatoSequencer(1,2,3, rhythm:1).patterns.should == [1,2,3]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Sequencers::RhythmicSequencer do
|
4
|
+
|
5
|
+
RHYTHMIC_SEQUENCER = Sequencers::RhythmicSequencer
|
6
|
+
|
7
|
+
let(:pitches) { Patterns.PitchSequence(C4, D4, E4, C4) }
|
8
|
+
let(:intensities) { Patterns.IntensitySequence(0.3, 0.6, 0.9, 1.0) }
|
9
|
+
let(:durations) { Patterns.DurationSequence(1, 1, 2, 1) }
|
10
|
+
let(:rhythm) { Patterns.RhythmSequence(0.5, 1.5, 4) }
|
11
|
+
let(:rhythmic_sequencer) { RHYTHMIC_SEQUENCER.new [pitches, intensities, durations], rhythm: rhythm }
|
12
|
+
|
13
|
+
describe "#new" do
|
14
|
+
it "defaults @max_steps to nil" do
|
15
|
+
rhythmic_sequencer.max_steps.should be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "sets @max_steps from the options hash" do
|
19
|
+
rhythmic_sequencer = RHYTHMIC_SEQUENCER.new [], rhythm: :mock_pattern, max_steps: 4
|
20
|
+
rhythmic_sequencer.max_steps.should == 4
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#to_timeline" do
|
25
|
+
it "returns a Timeline" do
|
26
|
+
rhythmic_sequencer.to_timeline.should be_a Timeline
|
27
|
+
end
|
28
|
+
|
29
|
+
it "contains notes assembled from the given patterns, with Timeline time deltas from the :rhythm type pattern" do
|
30
|
+
rhythmic_sequencer.to_timeline.should == Timeline.from_hash({
|
31
|
+
0 => Note(C4,1,0.3),
|
32
|
+
0.5 => Note(D4,1,0.6),
|
33
|
+
2.0 => Note(E4,2,0.9),
|
34
|
+
6.0 => Note(C4,1,1.0)
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it "uses the absolute value of any negative durations in the rhythm pattern" do
|
40
|
+
timeline = RHYTHMIC_SEQUENCER.new( [pitches, intensities, durations], rhythm: Patterns.RhythmSequence(-0.5, 1.5, -4) ).to_timeline
|
41
|
+
timeline.should == Timeline.from_hash({
|
42
|
+
0 => Note(C4,1,0.3),
|
43
|
+
0.5 => Note(D4,1,0.6),
|
44
|
+
2.0 => Note(E4,2,0.9),
|
45
|
+
6.0 => Note(C4,1,1.0)
|
46
|
+
})
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#max_steps" do
|
51
|
+
it "controls the maximum number of times in the generated timeline" do
|
52
|
+
rhythmic_sequencer.max_steps = 2
|
53
|
+
rhythmic_sequencer.to_timeline.should == Timeline.from_hash({
|
54
|
+
0 => Note(C4,1,0.3),
|
55
|
+
0.5 => Note(D4,1,0.6)
|
56
|
+
})
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#rewind" do
|
61
|
+
it "rewinds the rhythm pattern (in addition to normal #rewind behavior)" do
|
62
|
+
rhythm.length.times{ rhythmic_sequencer.send :advance }
|
63
|
+
# now the next call would normally throw a StopIteration exception
|
64
|
+
rhythmic_sequencer.rewind
|
65
|
+
rhythm.length.times{ rhythmic_sequencer.send :advance }
|
66
|
+
# if we didn't get an exception, then #rewind did it's job
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
describe MTK::Sequencers do
|
74
|
+
|
75
|
+
describe "#RhythmicSequencer" do
|
76
|
+
it "creates a RhythmicSequencer" do
|
77
|
+
MTK::Sequencers.RhythmicSequencer(1,2,3, rhythm:1).should be_a MTK::Sequencers::RhythmicSequencer
|
78
|
+
end
|
79
|
+
|
80
|
+
it "sets #patterns from the varargs" do
|
81
|
+
MTK::Sequencers.RhythmicSequencer(1,2,3, rhythm:1).patterns.should == [1,2,3]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Sequencers::Sequencer do
|
4
|
+
|
5
|
+
ABSTRACT_SEQUENCER = Sequencers::Sequencer
|
6
|
+
|
7
|
+
class MockEventBuilder < Patterns::Chain
|
8
|
+
attr_accessor :mock_attribute
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:patterns) { [Patterns.PitchCycle(C4,D4)] }
|
12
|
+
let(:sequencer) { ABSTRACT_SEQUENCER.new patterns }
|
13
|
+
let(:pitch) { ::MTK::Sequencers::EventBuilder::DEFAULT_PITCH }
|
14
|
+
let(:intensity) { ::MTK::Sequencers::EventBuilder::DEFAULT_INTENSITY }
|
15
|
+
let(:duration) { ::MTK::Sequencers::EventBuilder::DEFAULT_DURATION }
|
16
|
+
|
17
|
+
describe "#new" do
|
18
|
+
it "defaults @max_steps to nil" do
|
19
|
+
sequencer.max_steps.should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets @max_steps from the options hash" do
|
23
|
+
sequencer = ABSTRACT_SEQUENCER.new patterns, :max_steps => 4
|
24
|
+
sequencer.max_steps.should == 4
|
25
|
+
end
|
26
|
+
|
27
|
+
it "defaults @max_time to nil" do
|
28
|
+
sequencer.max_time.should be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets @max_time from the options hash" do
|
32
|
+
sequencer = ABSTRACT_SEQUENCER.new patterns, :max_time => 4
|
33
|
+
sequencer.max_time.should == 4
|
34
|
+
end
|
35
|
+
|
36
|
+
it "defaults @event_builder to ::MTK::Sequencers::EventBuilder" do
|
37
|
+
sequencer.event_builder.should be_a ::MTK::Sequencers::EventBuilder
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets @event_buidler from the options hash" do
|
41
|
+
sequencer = ABSTRACT_SEQUENCER.new patterns, :event_builder => MockEventBuilder
|
42
|
+
sequencer.event_builder.should be_a MockEventBuilder
|
43
|
+
end
|
44
|
+
|
45
|
+
it "allows default pitch to be specified" do
|
46
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.IntervalCycle(0)], :default_pitch => Gb4
|
47
|
+
sequencer.next.should == [Note(Gb4, intensity, duration)]
|
48
|
+
end
|
49
|
+
it "allows default intensity to be specified" do
|
50
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.IntervalCycle(0)], :default_intensity => ppp
|
51
|
+
sequencer.next.should == [Note(pitch, ppp, duration)]
|
52
|
+
end
|
53
|
+
it "allows default duration to be specified" do
|
54
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.IntervalCycle(0)], :default_duration => 5.25
|
55
|
+
sequencer.next.should == [Note(pitch, intensity, 5.25)]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#event_builder" do
|
60
|
+
it "provides access to the internal EventBuilder" do
|
61
|
+
sequencer = ABSTRACT_SEQUENCER.new patterns, :event_builder => MockEventBuilder
|
62
|
+
sequencer.event_builder.mock_attribute = :value
|
63
|
+
sequencer.event_builder.mock_attribute.should == :value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#to_timeline" do
|
68
|
+
it "combines pitch, intensity, and duration patterns into notes" do
|
69
|
+
pitches = Patterns.PitchSequence(C4, D4, E4)
|
70
|
+
intensities = Patterns.IntensitySequence(0.3, 0.7, 1.0)
|
71
|
+
durations = Patterns.DurationSequence(1, 2, 3)
|
72
|
+
sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
|
73
|
+
# default implementation just increments the time by 1 for each event (more interesting behavior is provided by subclasses)
|
74
|
+
sequencer.to_timeline.should == {
|
75
|
+
0.0 => [Note(C4,1,0.3)],
|
76
|
+
1.0 => [Note(D4,2,0.7)],
|
77
|
+
2.0 => [Note(E4,3,1.0)]
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
it "combines patterns of different types and lengths" do
|
82
|
+
pitches = Patterns.PitchSequence(C4, D4, E4, F4, G4, A4, B4, C5)
|
83
|
+
intensities = Patterns.IntensityCycle(0.5, 1.0)
|
84
|
+
durations = Patterns.DurationPalindrome(1, 2, 3)
|
85
|
+
sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
|
86
|
+
# default implementation just increments the time by 1 for each event (more interesting behavior is provided by subclasses)
|
87
|
+
sequencer.to_timeline.should == {
|
88
|
+
0.0 => [Note(C4,1,0.5)],
|
89
|
+
1.0 => [Note(D4,2,1.0)],
|
90
|
+
2.0 => [Note(E4,3,0.5)],
|
91
|
+
3.0 => [Note(F4,2,1.0)],
|
92
|
+
4.0 => [Note(G4,1,0.5)],
|
93
|
+
5.0 => [Note(A4,2,1.0)],
|
94
|
+
6.0 => [Note(B4,3,0.5)],
|
95
|
+
7.0 => [Note(C5,2,1.0)]
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it "produces consistent results by reseting the patterns each time" do
|
100
|
+
pitches = Patterns.PitchSequence(C4, D4, E4)
|
101
|
+
intensities = Patterns.IntensityCycle(1)
|
102
|
+
durations = Patterns.DurationCycle(1, 2)
|
103
|
+
sequencer = ABSTRACT_SEQUENCER.new [pitches, intensities, durations]
|
104
|
+
sequencer.to_timeline.should == sequencer.to_timeline
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#time" do
|
109
|
+
it "is the current timeline time that the sequencer is generating events for" do
|
110
|
+
# AbstractSequencer just advances by 1 each step
|
111
|
+
sequencer.next # time doesn't advance until the second #next call
|
112
|
+
sequencer.time.should == 0
|
113
|
+
sequencer.next
|
114
|
+
sequencer.time.should == 1
|
115
|
+
sequencer.next
|
116
|
+
sequencer.time.should == 2
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#step" do
|
121
|
+
it "is the index for how many of times #next has been called (i.e. count starting from 0)" do
|
122
|
+
sequencer.step.should == -1 # -1 indicates #next has not yet been called
|
123
|
+
sequencer.next
|
124
|
+
sequencer.step.should == 0
|
125
|
+
sequencer.next
|
126
|
+
sequencer.step.should == 1
|
127
|
+
sequencer.next
|
128
|
+
sequencer.step.should == 2
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "#next" do
|
133
|
+
it "returns a list of notes formed from the patterns in the sequencer" do
|
134
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
135
|
+
sequencer.next.should == [Note(D4,intensity,duration)]
|
136
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
137
|
+
end
|
138
|
+
|
139
|
+
it "returns a filtered list of notes if the sequencer was constructed with a options[:filter] lambda" do
|
140
|
+
sequencer = ABSTRACT_SEQUENCER.new patterns, :filter => lambda{|events| events.map{|event| event.transpose(P8) } }
|
141
|
+
sequencer.next.should == [Note(C5,intensity,duration)]
|
142
|
+
sequencer.next.should == [Note(D5,intensity,duration)]
|
143
|
+
end
|
144
|
+
|
145
|
+
context "pitch patterns" do
|
146
|
+
it "adds Numeric elements (intervals) to the previous pitch" do
|
147
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, m2, M2, m3)]
|
148
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
149
|
+
sequencer.next.should == [Note(C4+1,intensity,duration)]
|
150
|
+
sequencer.next.should == [Note(C4+1+2,intensity,duration)]
|
151
|
+
sequencer.next.should == [Note(C4+1+2+3,intensity,duration)]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "returns a note with the given pitch when encountering a Pitch after another type" do
|
155
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, m2, C4)]
|
156
|
+
sequencer.next
|
157
|
+
sequencer.next
|
158
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
159
|
+
end
|
160
|
+
|
161
|
+
it "goes to the nearest Pitch for any PitchClasses in the pitch list" do
|
162
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, F, C, G, C)]
|
163
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
164
|
+
sequencer.next.should == [Note(F4,intensity,duration)]
|
165
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
166
|
+
sequencer.next.should == [Note(G3,intensity,duration)]
|
167
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
168
|
+
end
|
169
|
+
|
170
|
+
it "does not endlessly ascend or descend when alternating between two pitch classes a tritone apart" do
|
171
|
+
sequencer = ABSTRACT_SEQUENCER.new [Patterns.Cycle(C4, Gb, C, Gb, C)]
|
172
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
173
|
+
sequencer.next.should == [Note(Gb4,intensity,duration)]
|
174
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
175
|
+
sequencer.next.should == [Note(Gb4,intensity,duration)]
|
176
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "#rewind" do
|
183
|
+
it "resets the sequencer and its patterns" do
|
184
|
+
sequencer.next
|
185
|
+
sequencer.rewind
|
186
|
+
sequencer.step.should == -1
|
187
|
+
sequencer.time.should == 0
|
188
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
189
|
+
end
|
190
|
+
|
191
|
+
it "resets pitches properly for patterns that rely on previous pitches" do
|
192
|
+
relative_pitch_pattern = Patterns.Sequence(C,P8)
|
193
|
+
sequencer = ABSTRACT_SEQUENCER.new [relative_pitch_pattern]
|
194
|
+
sequencer.next.should == [Note(C4,intensity,duration)]
|
195
|
+
sequencer.next.should == [Note(C5,intensity,duration)]
|
196
|
+
sequencer.rewind
|
197
|
+
sequencer.next.should == [Note(C4,intensity,duration)] # if the internal EventChain is not properly reset, the Note would be C5
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "#max_steps" do
|
202
|
+
it "controls the maximum number of entries in the generated timeline" do
|
203
|
+
sequencer.max_steps = 2
|
204
|
+
sequencer.to_timeline.times.length.should == 2
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#max_time" do
|
209
|
+
it "controls the maximum time in the generated timeline" do
|
210
|
+
sequencer.max_time = 4
|
211
|
+
sequencer.to_timeline.times.last.should == 4
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|