mtk 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. data/.yardopts +3 -2
  2. data/DEVELOPMENT_NOTES.md +114 -0
  3. data/INTRO.md +64 -8
  4. data/LICENSE.txt +1 -1
  5. data/README.md +31 -102
  6. data/Rakefile +56 -18
  7. data/bin/mtk +215 -0
  8. data/examples/crescendo.rb +5 -5
  9. data/examples/drum_pattern1.rb +23 -0
  10. data/examples/dynamic_pattern.rb +8 -11
  11. data/examples/gets_and_play.rb +26 -0
  12. data/examples/notation.rb +22 -0
  13. data/examples/play_midi.rb +8 -10
  14. data/examples/random_tone_row.rb +2 -2
  15. data/examples/syntax_to_midi.rb +28 -0
  16. data/examples/test_output.rb +8 -0
  17. data/examples/tone_row_melody.rb +6 -6
  18. data/lib/mtk.rb +52 -40
  19. data/lib/mtk/chord.rb +55 -0
  20. data/lib/mtk/constants/durations.rb +57 -0
  21. data/lib/mtk/constants/intensities.rb +61 -0
  22. data/lib/mtk/constants/intervals.rb +73 -0
  23. data/lib/mtk/constants/pitch_classes.rb +29 -0
  24. data/lib/mtk/constants/pitches.rb +52 -0
  25. data/lib/mtk/duration.rb +211 -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/helpers/collection.rb +164 -0
  30. data/lib/mtk/helpers/convert.rb +36 -0
  31. data/lib/mtk/helpers/lilypond.rb +162 -0
  32. data/lib/mtk/helpers/output_selector.rb +67 -0
  33. data/lib/mtk/helpers/pitch_collection.rb +23 -0
  34. data/lib/mtk/helpers/pseudo_constants.rb +26 -0
  35. data/lib/mtk/intensity.rb +156 -0
  36. data/lib/mtk/interval.rb +155 -0
  37. data/lib/mtk/lang/mtk_grammar.citrus +190 -13
  38. data/lib/mtk/lang/parser.rb +29 -0
  39. data/lib/mtk/melody.rb +94 -0
  40. data/lib/mtk/midi/dls_synth_device.rb +144 -0
  41. data/lib/mtk/midi/dls_synth_output.rb +62 -0
  42. data/lib/mtk/midi/file.rb +67 -32
  43. data/lib/mtk/midi/input.rb +97 -0
  44. data/lib/mtk/midi/jsound_input.rb +36 -17
  45. data/lib/mtk/midi/jsound_output.rb +48 -46
  46. data/lib/mtk/midi/output.rb +195 -0
  47. data/lib/mtk/midi/unimidi_input.rb +117 -0
  48. data/lib/mtk/midi/unimidi_output.rb +121 -0
  49. data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
  50. data/lib/mtk/patterns/chain.rb +49 -0
  51. data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
  52. data/lib/mtk/patterns/cycle.rb +18 -0
  53. data/lib/mtk/patterns/for_each.rb +71 -0
  54. data/lib/mtk/patterns/function.rb +39 -0
  55. data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
  56. data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
  57. data/lib/mtk/patterns/pattern.rb +171 -0
  58. data/lib/mtk/patterns/sequence.rb +20 -0
  59. data/lib/mtk/pitch.rb +7 -6
  60. data/lib/mtk/pitch_class.rb +124 -46
  61. data/lib/mtk/pitch_class_set.rb +58 -35
  62. data/lib/mtk/sequencers/event_builder.rb +131 -0
  63. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  64. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  65. data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
  66. data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
  67. data/lib/mtk/timeline.rb +39 -22
  68. data/lib/mtk/variable.rb +32 -0
  69. data/spec/mtk/chord_spec.rb +83 -0
  70. data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
  71. data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
  72. data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
  73. data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
  74. data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
  75. data/spec/mtk/duration_spec.rb +372 -0
  76. data/spec/mtk/events/event_spec.rb +234 -0
  77. data/spec/mtk/events/note_spec.rb +174 -0
  78. data/spec/mtk/events/parameter_spec.rb +220 -0
  79. data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
  80. data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
  81. data/spec/mtk/intensity_spec.rb +289 -0
  82. data/spec/mtk/interval_spec.rb +265 -0
  83. data/spec/mtk/lang/parser_spec.rb +597 -0
  84. data/spec/mtk/melody_spec.rb +223 -0
  85. data/spec/mtk/midi/file_spec.rb +16 -16
  86. data/spec/mtk/midi/jsound_input_spec.rb +11 -0
  87. data/spec/mtk/midi/jsound_output_spec.rb +11 -0
  88. data/spec/mtk/midi/output_spec.rb +102 -0
  89. data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
  90. data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
  91. data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
  92. data/spec/mtk/patterns/chain_spec.rb +110 -0
  93. data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
  94. data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
  95. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  96. data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
  97. data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
  98. data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
  99. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  100. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  101. data/spec/mtk/pitch_class_set_spec.rb +23 -21
  102. data/spec/mtk/pitch_class_spec.rb +151 -39
  103. data/spec/mtk/pitch_spec.rb +22 -1
  104. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  105. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  106. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  107. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  108. data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
  109. data/spec/mtk/timeline_spec.rb +109 -16
  110. data/spec/mtk/variable_spec.rb +52 -0
  111. data/spec/spec_coverage.rb +2 -0
  112. data/spec/spec_helper.rb +3 -0
  113. metadata +188 -91
  114. data/lib/mtk/_constants/durations.rb +0 -80
  115. data/lib/mtk/_constants/intensities.rb +0 -81
  116. data/lib/mtk/_constants/intervals.rb +0 -85
  117. data/lib/mtk/_constants/pitch_classes.rb +0 -35
  118. data/lib/mtk/_constants/pitches.rb +0 -49
  119. data/lib/mtk/event.rb +0 -70
  120. data/lib/mtk/helper/collection.rb +0 -114
  121. data/lib/mtk/helper/event_builder.rb +0 -85
  122. data/lib/mtk/helper/pseudo_constants.rb +0 -26
  123. data/lib/mtk/lang/grammar.rb +0 -17
  124. data/lib/mtk/note.rb +0 -63
  125. data/lib/mtk/pattern/abstract_pattern.rb +0 -132
  126. data/lib/mtk/pattern/cycle.rb +0 -51
  127. data/lib/mtk/pattern/enumerator.rb +0 -26
  128. data/lib/mtk/pattern/function.rb +0 -46
  129. data/lib/mtk/pattern/sequence.rb +0 -30
  130. data/lib/mtk/pitch_set.rb +0 -84
  131. data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
  132. data/lib/mtk/transform/invertible.rb +0 -15
  133. data/lib/mtk/transform/mappable.rb +0 -18
  134. data/lib/mtk/transform/set_theory_operations.rb +0 -34
  135. data/lib/mtk/transform/transposable.rb +0 -14
  136. data/spec/mtk/event_spec.rb +0 -139
  137. data/spec/mtk/helper/event_builder_spec.rb +0 -92
  138. data/spec/mtk/lang/grammar_spec.rb +0 -100
  139. data/spec/mtk/note_spec.rb +0 -115
  140. data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
  141. data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
  142. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
  143. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
  144. data/spec/mtk/pattern/sequence_spec.rb +0 -151
  145. data/spec/mtk/pitch_set_spec.rb +0 -198
  146. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
  147. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -1,44 +1,53 @@
1
1
  module MTK
2
2
 
3
- # An ordered Set of PitchClasses, for 12-tone set-theory pitch analysis and manipulations
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.uniq.freeze
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.span_for ordering
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, #to_a, #sort, Array]
127
+ # Compare for equality, ignoring order and duplicates
128
+ # @param other [#pitch_classes, Array, #to_a]
95
129
  def =~ other
96
- if other.is_a? Array and other.frozen?
97
- @pitch_classes.sort == other
98
- elsif other.respond_to? :pitch_classes
99
- @pitch_classes.sort == other.pitch_classes.sort
100
- elsif other.respond_to? :to_a
101
- @pitch_classes.sort == other.to_a.sort
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
- # Construct a {PitchClassSet} from any supported type
153
+
154
+ # Construct a {PitchClassSet}
155
+ # @see PitchClassSet#initialize
128
156
  def PitchClassSet(*anything)
129
- anything = anything.first if anything.size == 1
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 Sequencer
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 AbstractSequencer
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 #{Pattern::Enumerator#next} for the {Pattern}s in this Sequencer.
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, Helper::EventBuilder
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
- timeline[@time] = events if events
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