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
data/lib/mtk/pitch_class_set.rb
CHANGED
@@ -1,44 +1,53 @@
|
|
1
1
|
module MTK
|
2
2
|
|
3
|
-
# An ordered
|
3
|
+
# An ordered collection of {PitchClass}es.
|
4
|
+
#
|
5
|
+
# Unlike a mathematical Set, a PitchClassSet is ordered and may contain duplicates.
|
6
|
+
#
|
7
|
+
# @see Melody
|
8
|
+
# @see Chord
|
4
9
|
#
|
5
10
|
class PitchClassSet
|
6
|
-
|
7
|
-
include Helper::Collection
|
8
|
-
include Transform::Mappable
|
9
|
-
include Transform::Transposable
|
10
|
-
include Transform::Invertible
|
11
|
-
include Transform::SetTheoryOperations
|
11
|
+
include Helpers::PitchCollection
|
12
12
|
|
13
13
|
attr_reader :pitch_classes
|
14
14
|
|
15
15
|
def self.random_row
|
16
|
-
new(PitchClasses::PITCH_CLASSES.shuffle)
|
16
|
+
new(Constants::PitchClasses::PITCH_CLASSES.shuffle)
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.all
|
20
|
-
@all ||= new(PitchClasses::PITCH_CLASSES)
|
20
|
+
@all ||= new(Constants::PitchClasses::PITCH_CLASSES)
|
21
21
|
end
|
22
22
|
|
23
|
+
# @param pitch_classes [#to_a] the collection of pitch classes
|
24
|
+
#
|
25
|
+
# @see MTK#PitchClassSet
|
26
|
+
#
|
23
27
|
def initialize(pitch_classes)
|
24
|
-
@pitch_classes = pitch_classes.to_a.
|
28
|
+
@pitch_classes = pitch_classes.to_a.clone.freeze
|
25
29
|
end
|
26
30
|
|
31
|
+
# @see Helper::Collection
|
27
32
|
def elements
|
28
33
|
@pitch_classes
|
29
34
|
end
|
30
35
|
|
36
|
+
# Convert to an Array of pitch_classes.
|
37
|
+
# @note this returns a mutable copy the underlying @pitch_classes attribute, which is otherwise unmutable
|
38
|
+
alias :to_pitch_classes :to_a
|
39
|
+
|
31
40
|
def self.from_a enumerable
|
32
41
|
new enumerable
|
33
42
|
end
|
34
43
|
|
35
44
|
def normal_order
|
36
|
-
ordering = Array.new(@pitch_classes.sort)
|
45
|
+
ordering = Array.new(@pitch_classes.uniq.sort)
|
37
46
|
min_span, start_index_for_normal_order = nil, nil
|
38
47
|
|
39
48
|
# check every rotation for the minimal span:
|
40
49
|
size.times do |index|
|
41
|
-
span = self.class.
|
50
|
+
span = self.class.span_between ordering.first, ordering.last
|
42
51
|
|
43
52
|
if min_span.nil? or span < min_span
|
44
53
|
# best so far
|
@@ -79,6 +88,31 @@ module MTK
|
|
79
88
|
norder.map{|pitch_class| (pitch_class.to_i - first_pc_val) % 12 }
|
80
89
|
end
|
81
90
|
|
91
|
+
# the collection of elements present in both sets
|
92
|
+
def intersection(other)
|
93
|
+
self.class.from_a(to_a & other.to_a)
|
94
|
+
end
|
95
|
+
|
96
|
+
# the collection of all elements present in either set
|
97
|
+
def union(other)
|
98
|
+
self.class.from_a(to_a | other.to_a)
|
99
|
+
end
|
100
|
+
|
101
|
+
# the collection of elements from this set with any elements from the other set removed
|
102
|
+
def difference(other)
|
103
|
+
self.class.from_a(to_a - other.to_a)
|
104
|
+
end
|
105
|
+
|
106
|
+
# the collection of elements that are members of exactly one of the sets
|
107
|
+
def symmetric_difference(other)
|
108
|
+
union(other).difference( intersection(other) )
|
109
|
+
end
|
110
|
+
|
111
|
+
# the collection of elements that are not members of this set
|
112
|
+
def complement
|
113
|
+
self.class.all.difference(self)
|
114
|
+
end
|
115
|
+
|
82
116
|
# @param other [#pitch_classes, #to_a, Array]
|
83
117
|
def == other
|
84
118
|
if other.respond_to? :pitch_classes
|
@@ -90,19 +124,15 @@ module MTK
|
|
90
124
|
end
|
91
125
|
end
|
92
126
|
|
93
|
-
# Compare for equality, ignoring order
|
94
|
-
# @param other [#pitch_classes,
|
127
|
+
# Compare for equality, ignoring order and duplicates
|
128
|
+
# @param other [#pitch_classes, Array, #to_a]
|
95
129
|
def =~ other
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
elsif other.respond_to? :sort
|
103
|
-
@pitch_classes.sort == other.sort
|
104
|
-
else
|
105
|
-
@pitch_classes.sort == other
|
130
|
+
@normalized_pitch_classes ||= @pitch_classes.uniq.sort
|
131
|
+
@normalized_pitch_classes == case
|
132
|
+
when other.respond_to?(:pitch_classes) then other.pitch_classes.uniq.sort
|
133
|
+
when (other.is_a? Array and other.frozen?) then other
|
134
|
+
when other.respond_to?(:to_a) then other.to_a.uniq.sort
|
135
|
+
else other
|
106
136
|
end
|
107
137
|
end
|
108
138
|
|
@@ -114,24 +144,17 @@ module MTK
|
|
114
144
|
@pitch_classes.inspect
|
115
145
|
end
|
116
146
|
|
117
|
-
def self.span_for(pitch_classes)
|
118
|
-
span_between pitch_classes.first, pitch_classes.last
|
119
|
-
end
|
120
|
-
|
121
147
|
def self.span_between(pc1, pc2)
|
122
148
|
(pc2.to_i - pc1.to_i) % 12
|
123
149
|
end
|
124
150
|
|
125
151
|
end
|
126
152
|
|
127
|
-
|
153
|
+
|
154
|
+
# Construct a {PitchClassSet}
|
155
|
+
# @see PitchClassSet#initialize
|
128
156
|
def PitchClassSet(*anything)
|
129
|
-
|
130
|
-
case anything
|
131
|
-
when Array then PitchClassSet.new(anything.map{|elem| PitchClass(elem) })
|
132
|
-
when PitchClassSet then anything
|
133
|
-
else PitchClassSet.new([PitchClass(anything)])
|
134
|
-
end
|
157
|
+
PitchClassSet.new Helpers::Convert.to_pitch_classes(*anything)
|
135
158
|
end
|
136
159
|
module_function :PitchClassSet
|
137
160
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module MTK
|
2
|
+
module Sequencers
|
3
|
+
|
4
|
+
# A special pattern that takes a list of event properties and/or patterns and emits lists of {Events::Event}s
|
5
|
+
class EventBuilder
|
6
|
+
|
7
|
+
DEFAULT_PITCH = ::MTK::Constants::Pitches::C4
|
8
|
+
DEFAULT_DURATION = ::MTK::Constants::Durations::q
|
9
|
+
DEFAULT_INTENSITY = ::MTK::Constants::Intensities::o
|
10
|
+
|
11
|
+
def initialize(patterns, options={})
|
12
|
+
@patterns = patterns
|
13
|
+
@options = options
|
14
|
+
@default_pitch = if options.has_key? :default_pitch then MTK::Pitch( options[:default_pitch]) else DEFAULT_PITCH end
|
15
|
+
@default_duration = if options.has_key? :default_duration then MTK::Duration( options[:default_duration]) else DEFAULT_DURATION end
|
16
|
+
@default_intensity = if options.has_key? :default_intensity then MTK::Intensity(options[:default_intensity]) else DEFAULT_INTENSITY end
|
17
|
+
@max_interval = options.fetch(:max_interval, 127)
|
18
|
+
rewind
|
19
|
+
end
|
20
|
+
|
21
|
+
# Build a list of events from the next element in each {Patterns::Pattern}
|
22
|
+
# @return [Array] an array of events
|
23
|
+
def next
|
24
|
+
pitches = []
|
25
|
+
intensities = []
|
26
|
+
duration = nil
|
27
|
+
|
28
|
+
@patterns.each do |pattern|
|
29
|
+
pattern_value = pattern.next
|
30
|
+
|
31
|
+
elements = pattern_value.is_a?(Enumerable) ? pattern_value : [pattern_value]
|
32
|
+
elements.each do |element|
|
33
|
+
return nil if element.nil? or element == :skip
|
34
|
+
|
35
|
+
case element
|
36
|
+
when ::MTK::Pitch then pitches << element
|
37
|
+
when ::MTK::PitchClass then pitches += pitches_for_pitch_classes([element], @previous_pitch)
|
38
|
+
when ::MTK::PitchClassSet then pitches += pitches_for_pitch_classes(element, @previous_pitch)
|
39
|
+
when ::MTK::Helpers::PitchCollection then pitches += element.pitches # this must be after the PitchClassSet case, because that is also a PitchCollection
|
40
|
+
|
41
|
+
when ::MTK::Duration
|
42
|
+
duration ||= 0
|
43
|
+
duration += element
|
44
|
+
|
45
|
+
when ::MTK::Intensity
|
46
|
+
intensities << element
|
47
|
+
|
48
|
+
when ::MTK::Interval
|
49
|
+
if @previous_pitches
|
50
|
+
pitches += @previous_pitches.map{|pitch| pitch + element }
|
51
|
+
else
|
52
|
+
pitches << (@previous_pitch + element)
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO? String/Symbols for special behaviors like :skip, or :break (something like StopIteration for the current Pattern?)
|
56
|
+
|
57
|
+
else STDERR.puts "#{self.class}#next: Unexpected type '#{element.class}'"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
pitches << @previous_pitch if pitches.empty?
|
64
|
+
duration ||= @previous_duration
|
65
|
+
|
66
|
+
if intensities.empty?
|
67
|
+
intensity = @previous_intensity
|
68
|
+
else
|
69
|
+
intensity = MTK::Intensity[intensities.map{|i| i.to_f }.inject(:+)/intensities.length] # average the intensities
|
70
|
+
end
|
71
|
+
|
72
|
+
# Not using this yet, maybe later...
|
73
|
+
# return nil if duration==:skip or intensities.include? :skip or pitches.include? :skip
|
74
|
+
|
75
|
+
constrain_pitch(pitches)
|
76
|
+
|
77
|
+
@previous_pitch = pitches.last # Consider doing something different, maybe averaging?
|
78
|
+
@previous_pitches = pitches.length > 1 ? pitches : nil
|
79
|
+
@previous_intensity = intensity
|
80
|
+
@previous_duration = duration
|
81
|
+
|
82
|
+
pitches.map{|pitch| MTK::Events::Note.new(pitch,duration,intensity) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Reset the EventBuilder to its initial state
|
86
|
+
def rewind
|
87
|
+
@previous_pitch = @default_pitch
|
88
|
+
@previous_pitches = [@default_pitch]
|
89
|
+
@previous_intensity = @default_intensity
|
90
|
+
@previous_duration = @default_duration
|
91
|
+
@max_pitch = nil
|
92
|
+
@min_pitch = nil
|
93
|
+
@patterns.each{|pattern| pattern.rewind if pattern.is_a? MTK::Patterns::Pattern }
|
94
|
+
end
|
95
|
+
|
96
|
+
########################
|
97
|
+
private
|
98
|
+
|
99
|
+
def pitches_for_pitch_classes(pitch_classes, previous_pitch)
|
100
|
+
pitch_classes.map{|pitch_class| previous_pitch.nearest(pitch_class) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def constrain_pitch(pitches)
|
104
|
+
if @max_pitch.nil? or @min_pitch.nil?
|
105
|
+
first_pitch = pitches.first
|
106
|
+
|
107
|
+
@max_pitch = first_pitch + @max_interval
|
108
|
+
@max_pitch = 127 if @max_pitch > 127
|
109
|
+
|
110
|
+
@min_pitch = first_pitch - @max_interval
|
111
|
+
@min_pitch = 0 if @min_pitch < 0
|
112
|
+
|
113
|
+
@small_max_span = (@max_pitch - @min_pitch < 12)
|
114
|
+
end
|
115
|
+
|
116
|
+
pitches.map! do |pitch|
|
117
|
+
if @small_max_span
|
118
|
+
pitch = @max_pitch if pitch > @max_pitch
|
119
|
+
pitch = @min_pitch if pitch < @max_pitch
|
120
|
+
else
|
121
|
+
pitch -= 12 while pitch > @max_pitch
|
122
|
+
pitch += 12 while pitch < @min_pitch
|
123
|
+
end
|
124
|
+
pitch
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MTK
|
2
|
+
module Sequencers
|
3
|
+
|
4
|
+
# A Sequencer which uses the longest duration of the events at each step to determine
|
5
|
+
# the delta times between entries in the {Timeline}.
|
6
|
+
class LegatoSequencer < Sequencer
|
7
|
+
|
8
|
+
# (see Sequencer#next)
|
9
|
+
def next
|
10
|
+
@previous_events = super
|
11
|
+
end
|
12
|
+
|
13
|
+
########################
|
14
|
+
protected
|
15
|
+
|
16
|
+
# (see Sequencer#advance)
|
17
|
+
def advance
|
18
|
+
@time += @previous_events.map{|event| event.length }.max
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MTK
|
2
|
+
module Sequencers
|
3
|
+
|
4
|
+
# A Sequencer which uses a :rhythm type {Patterns::Pattern} to determine the delta times between entries in the {Timeline}.
|
5
|
+
class RhythmicSequencer < Sequencer
|
6
|
+
|
7
|
+
def initialize(patterns, options={})
|
8
|
+
super
|
9
|
+
@rhythm = options[:rhythm] or raise ArgumentError.new(":rhythm option is required")
|
10
|
+
end
|
11
|
+
|
12
|
+
def rewind
|
13
|
+
super
|
14
|
+
@rhythm.rewind if @rhythm
|
15
|
+
end
|
16
|
+
|
17
|
+
########################
|
18
|
+
protected
|
19
|
+
|
20
|
+
# (see Sequencer#advance)
|
21
|
+
def advance
|
22
|
+
@time += @rhythm.next.length
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module MTK
|
2
|
-
module
|
2
|
+
module Sequencers
|
3
3
|
|
4
|
-
# A Sequencer produces {Timeline}s from a collection of {Pattern}s.
|
4
|
+
# A Sequencer produces {Timeline}s from a collection of {Patterns::Pattern}s.
|
5
5
|
#
|
6
6
|
# @abstract Subclass and override {#advance} to implement a Sequencer.
|
7
7
|
#
|
8
|
-
class
|
8
|
+
class Sequencer
|
9
9
|
|
10
10
|
# The maximum number of [time,event_list] entries that will be generated for the {Timeline}.
|
11
11
|
# nil means no maximum (be careful of infinite loops!)
|
@@ -15,7 +15,7 @@ module MTK
|
|
15
15
|
# nil means no maximum (be careful of infinite loops!)
|
16
16
|
attr_accessor :max_time
|
17
17
|
|
18
|
-
# Used by {#to_timeline} to builds event lists from the results of
|
18
|
+
# Used by {#to_timeline} to builds event lists from the results of {Patterns::Pattern#next} for the {Patterns::Pattern}s in this Sequencer.
|
19
19
|
attr_reader :event_builder
|
20
20
|
|
21
21
|
# The current time offset for the sequencer. Used for the {Timeline} times.
|
@@ -24,24 +24,37 @@ module MTK
|
|
24
24
|
# The current sequencer step index (the number of times-1 that {#next} has been called), or -1 if the sequencer has not yet started.
|
25
25
|
attr_reader :step
|
26
26
|
|
27
|
+
attr_reader :patterns
|
28
|
+
|
29
|
+
# @param patterns [Array] the list of patterns to be sequenced into a {Timeline}
|
30
|
+
# @param options [Hash] the options to create a message with.
|
31
|
+
# @option options [String] :max_steps set {#max_steps}
|
32
|
+
# @option options [String] :max_time set {#max_time}
|
33
|
+
# @option options [Proc] :filter a Proc that will replace the events generated by {#next} with the results of the Proc[events]
|
34
|
+
# @option options [Class] :event_builder replace the {Sequencers::EventBuilder} with a custom Event pattern
|
27
35
|
def initialize(patterns, options={})
|
28
36
|
@patterns = patterns
|
29
37
|
@max_steps = options[:max_steps]
|
30
38
|
@max_time = options[:max_time]
|
39
|
+
@filter = options[:filter]
|
31
40
|
|
32
|
-
event_builder_class = options.fetch :event_builder,
|
41
|
+
event_builder_class = options.fetch :event_builder, ::MTK::Sequencers::EventBuilder
|
33
42
|
@event_builder = event_builder_class.new(patterns, options)
|
43
|
+
|
34
44
|
rewind
|
35
45
|
end
|
36
46
|
|
37
47
|
|
38
|
-
# Produce a {Timeline} from the {Pattern}s in this Sequencer.
|
48
|
+
# Produce a {Timeline} from the {Patterns::Pattern}s in this Sequencer.
|
39
49
|
def to_timeline
|
40
50
|
rewind
|
41
51
|
timeline = Timeline.new
|
42
52
|
loop do
|
43
53
|
events = self.next
|
44
|
-
|
54
|
+
if events
|
55
|
+
events = events.reject{|e| e.rest? }
|
56
|
+
timeline[@time] = events unless events.empty?
|
57
|
+
end
|
45
58
|
end
|
46
59
|
timeline
|
47
60
|
end
|
@@ -52,12 +65,14 @@ module MTK
|
|
52
65
|
# so you can ignore this method unless you want to hack on sequencers at a lower level.
|
53
66
|
def next
|
54
67
|
if @step >= 0
|
55
|
-
advance
|
68
|
+
advance
|
56
69
|
raise StopIteration if @max_time and @time > @max_time
|
57
70
|
end
|
58
71
|
@step += 1
|
59
72
|
raise StopIteration if @max_steps and @step >= @max_steps
|
60
|
-
@event_builder.next
|
73
|
+
events = @event_builder.next
|
74
|
+
events = @filter[events] if @filter
|
75
|
+
events
|
61
76
|
end
|
62
77
|
|
63
78
|
|
@@ -67,7 +82,7 @@ module MTK
|
|
67
82
|
def rewind
|
68
83
|
@time = 0
|
69
84
|
@step = -1
|
70
|
-
event_builder.rewind
|
85
|
+
@event_builder.rewind
|
71
86
|
end
|
72
87
|
|
73
88
|
|
@@ -75,10 +90,21 @@ module MTK
|
|
75
90
|
protected
|
76
91
|
|
77
92
|
# Advance @time to the next time for the {Timeline} being produced by {#to_timeline}
|
78
|
-
def advance
|
93
|
+
def advance
|
79
94
|
@time += 1 # default behavior simply advances one beat at a time
|
80
95
|
end
|
81
96
|
|
97
|
+
def self.inherited(subclass)
|
98
|
+
# Define a convenience method like MTK::Patterns.Sequence()
|
99
|
+
# that can handle varargs or a single array argument, plus any Hash options
|
100
|
+
classname = subclass.name.sub /.*::/, '' # Strip off module prefixes
|
101
|
+
MTK::Sequencers.define_singleton_method classname do |*args|
|
102
|
+
options = (args[-1].is_a? Hash) ? args.pop : {}
|
103
|
+
args = args[0] if args.length == 1 and args[0].is_a? Array
|
104
|
+
subclass.new(args,options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
82
108
|
end
|
83
109
|
|
84
110
|
end
|