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.
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
@@ -0,0 +1,117 @@
1
+ require 'unimidi'
2
+ require 'ostruct'
3
+
4
+ module MTK
5
+ module MIDI
6
+
7
+ # Provides realtime MIDI input for MRI/YARV Ruby via the unimidi gem.
8
+ # @note This class is optional and only available if you require 'mtk/midi/unimidi_input'.
9
+ # It depends on the 'unimidi' gem.
10
+ class UniMIDIInput < Input
11
+
12
+ public_class_method :new
13
+
14
+ def self.devices
15
+ @devices ||= ::UniMIDI::Input.all.reject{|d| d.name.strip.empty? }
16
+ end
17
+
18
+ def self.devices_by_name
19
+ @devices_by_name ||= devices.each_with_object( Hash.new ){|device,hash| hash[device.name] = device }
20
+ end
21
+
22
+
23
+ attr_reader :device, :recording, :thread
24
+
25
+ def initialize(input_device, options={})
26
+ super
27
+ @open_time = Time.now.to_f
28
+ end
29
+
30
+ def record(options={})
31
+ @recording = [] unless options[:append] and @recording
32
+ monitor = options[:monitor]
33
+
34
+ stop
35
+ @thread = Thread.new do
36
+ @start_time = Time.now.to_f
37
+ loop do
38
+ @device.gets.each do |data|
39
+ puts data if monitor
40
+ record_raw_data data
41
+ end
42
+ sleep 0.001
43
+ end
44
+ end
45
+
46
+ time_limit = options[:time_limit]
47
+ if time_limit
48
+ puts "Blocking current thread for #{time_limit} seconds to record MIDI input."
49
+ @thread.join(time_limit)
50
+ end
51
+ end
52
+
53
+ def stop
54
+ Thread.kill @thread if @thread
55
+ end
56
+
57
+ def to_timeline(options={})
58
+ return nil if not @recording
59
+
60
+ bpm = options.fetch :bmp, 120
61
+ beats_per_second = bpm.to_f/60
62
+ timeline = Timeline.new
63
+ note_ons = {}
64
+ start = nil
65
+
66
+ @recording.each do |message, time|
67
+ start ||= time
68
+ time -= start
69
+ time /= beats_per_second
70
+
71
+ if message.is_a? MTK::Events::Event
72
+ timeline.add time,message
73
+ else
74
+ case message.type
75
+ when :note_on
76
+ pitch = message.pitch
77
+ note_ons[pitch] = [message,time]
78
+
79
+ when :note_off
80
+ pitch = message.pitch
81
+ if note_ons.has_key? pitch
82
+ note_on, start_time = note_ons.delete(pitch)
83
+ duration = time - start_time
84
+ note = MTK::Events::Note.from_midi pitch, note_on.velocity, duration
85
+ timeline.add time,note
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ timeline.quantize! options[:quantize] if options.key? :quantize
92
+ timeline.shift_to! options[:shift_to] if options.key? :shift_to
93
+
94
+ timeline
95
+ end
96
+
97
+
98
+ #######################
99
+ private
100
+
101
+ def record_raw_data raw
102
+ status, data1, data2 = *raw[:data] # the 3 bytes of raw message data
103
+ message = (
104
+ case status & 0xF0
105
+ when 0x80 then OpenStruct.new({:type => :note_off, :pitch => data1, :velocity => data2})
106
+ when 0x90 then OpenStruct.new({:type => :note_on, :pitch => data1, :velocity => data2})
107
+ else MTK::Events::Parameter.from_midi(status,data1,data2)
108
+ end
109
+ )
110
+ time = raw[:timestamp]/1000 - (@start_time - @open_time)
111
+ @recording << [message, time]
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
@@ -0,0 +1,121 @@
1
+ require 'unimidi'
2
+
3
+ module MTK
4
+ module MIDI
5
+
6
+ # Provides realtime MIDI output for "standard" Ruby (MRI) via the unimidi and gamelan gems.
7
+ # @note This class is optional and only available if you require 'mtk/midi/unimidi_output'.
8
+ # It depends on the 'unimidi' and 'gamelan' gems.
9
+ class UniMIDIOutput < Output
10
+
11
+ public_class_method :new
12
+
13
+ def self.devices
14
+ @devices ||= ::UniMIDI::Output.all.reject{|d| d.name.strip.empty? }
15
+ end
16
+
17
+ def self.devices_by_name
18
+ @devices_by_name ||= devices.each_with_object( Hash.new ){|device,hash| hash[device.name] = device }
19
+ end
20
+
21
+
22
+ ######################
23
+ protected
24
+
25
+ # (see Output#note_on)
26
+ def note_on(pitch, velocity, channel)
27
+ @device.puts(0x90|channel, pitch, velocity)
28
+ end
29
+
30
+ # (see Output#note_off)
31
+ def note_off(pitch, velocity, channel)
32
+ @device.puts(0x80|channel, pitch, velocity)
33
+ end
34
+
35
+ # (see Output#control)
36
+ def control(number, midi_value, channel)
37
+ @device.puts(0xB0|channel, number, midi_value)
38
+ end
39
+
40
+ # (see Output#channel_pressure)
41
+ def channel_pressure(midi_value, channel)
42
+ @device.puts(0xD0|channel, midi_value, 0)
43
+ end
44
+
45
+ # (see Output#poly_pressure)
46
+ def poly_pressure(pitch, midi_value, channel)
47
+ @device.puts(0xA0|channel, pitch, midi_value)
48
+ end
49
+
50
+ # (see Output#bend)
51
+ def bend(midi_value, channel)
52
+ @device.puts(0xE0|channel, midi_value & 127, (midi_value >> 7) & 127)
53
+ end
54
+
55
+ # (see Output#program)
56
+ def program(number, channel)
57
+ @device.puts(0xC0|channel, number, 0)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ #####################################################################
66
+ # MONKEY PATCHING for https://github.com/arirusso/ffi-coremidi/pull/2
67
+ # This can be removed once that pull request is released.
68
+
69
+ # @private
70
+ module CoreMIDI
71
+ class Device
72
+ def initialize(id, device_pointer, options = {})
73
+ include_if_offline = options[:include_offline] || false
74
+ @id = id
75
+ @resource = device_pointer
76
+ @entities = []
77
+
78
+ prop = Map::CF.CFStringCreateWithCString( nil, "name", 0 )
79
+ begin
80
+ name_ptr = FFI::MemoryPointer.new(:pointer)
81
+ Map::MIDIObjectGetStringProperty(@resource, prop, name_ptr)
82
+ name = name_ptr.read_pointer
83
+ len = Map::CF.CFStringGetMaximumSizeForEncoding(Map::CF.CFStringGetLength(name), :kCFStringEncodingUTF8)
84
+ bytes = FFI::MemoryPointer.new(len + 1)
85
+ raise RuntimeError.new("CFStringGetCString") unless Map::CF.CFStringGetCString(name, bytes, len, :kCFStringEncodingUTF8)
86
+ @name = bytes.read_string
87
+ ensure
88
+ Map::CF.CFRelease(name) unless name.nil? || name.null?
89
+ Map::CF.CFRelease(prop) unless prop.null?
90
+ end
91
+ populate_entities(:include_offline => include_if_offline)
92
+ end
93
+
94
+ end
95
+
96
+ module Map
97
+ module CF
98
+
99
+ extend FFI::Library
100
+ ffi_lib '/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation'
101
+
102
+ typedef :pointer, :CFStringRef
103
+ typedef :long, :CFIndex
104
+ enum :CFStringEncoding, [ :kCFStringEncodingUTF8, 0x08000100 ]
105
+
106
+ # CFString* CFStringCreateWithCString( ?, CString, encoding)
107
+ attach_function :CFStringCreateWithCString, [:pointer, :string, :int], :pointer
108
+ # CString* CFStringGetCStringPtr(CFString*, encoding)
109
+ attach_function :CFStringGetCStringPtr, [:pointer, :int], :pointer
110
+
111
+ attach_function :CFStringGetLength, [ :CFStringRef ], :CFIndex
112
+
113
+ attach_function :CFStringGetMaximumSizeForEncoding, [ :CFIndex, :CFStringEncoding ], :long
114
+
115
+ attach_function :CFStringGetCString, [ :CFStringRef, :pointer, :CFIndex, :CFStringEncoding ], :bool
116
+
117
+ attach_function :CFRelease, [ :pointer ], :void
118
+
119
+ end
120
+ end
121
+ end
@@ -1,5 +1,17 @@
1
+ # Optional Numeric methods for converting a number to common intervals.
2
+ #
3
+ # @note you must require 'mtk/numeric_extensions' to use these methods.
4
+ #
1
5
  class Numeric
2
6
 
7
+ def beats
8
+ MTK::Duration(self)
9
+ end
10
+ alias beat beats
11
+
12
+
13
+ # TODO: these should all return intervals
14
+
3
15
  def semitones
4
16
  self
5
17
  end
@@ -0,0 +1,49 @@
1
+ module MTK
2
+ module Patterns
3
+
4
+ # A pattern that takes a list of patterns and combines their values into a list of event properties.
5
+ class Chain < Pattern
6
+
7
+ def initialize(elements, options={})
8
+ super
9
+ @stop_after_first = true # assume everything's an element until we inspect them in the loop below
10
+ @stop_after_first = true if @elements.all?{|element| not element.is_a? ::MTK::Patterns::Pattern }
11
+ end
12
+
13
+ ###################
14
+ protected
15
+
16
+ # (see Pattern#rewind_or_cycle)
17
+ def rewind_or_cycle(is_cycling=false)
18
+ @is_element_done = Array.new(elements.size)
19
+ super
20
+ end
21
+
22
+ # (see Pattern#advance)
23
+ def advance
24
+ @current = @elements.map.with_index do |element,index|
25
+ if element.is_a? ::MTK::Patterns::Pattern
26
+ begin
27
+ element.next
28
+ rescue StopIteration
29
+ raise StopIteration if element.max_elements_exceeded?
30
+ @is_element_done[index] = true
31
+ element.rewind
32
+ element.next
33
+ end
34
+ else
35
+ element
36
+ end
37
+ end.flatten
38
+
39
+ raise StopIteration if @is_element_done.all?{|done| done }
40
+
41
+ @elements.each.with_index do |element,index|
42
+ @is_element_done[index] = true unless element.is_a? ::MTK::Patterns::Pattern
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -1,15 +1,15 @@
1
1
  module MTK
2
- module Pattern
2
+ module Patterns
3
3
 
4
4
  # Randomly choose from a list of elements.
5
5
  #
6
6
  # Supports giving different weights to different choices.
7
7
  # Default is to weight all choices equally.
8
8
  #
9
- class Choice < AbstractPattern
9
+ class Choice < Pattern
10
10
 
11
- # @param (see AbstractPattern#initialize)
12
- # @option (see AbstractPattern#initialize)
11
+ # @param (see Pattern#initialize)
12
+ # @option (see Pattern#initialize)
13
13
  # @option options [Array] :weights a list of chances that each corresponding element will be selected (normalized against the total weight)
14
14
  # @example choose the second element twice as often as the first or third:
15
15
  # MTK::Pattern::Choice.new [:first,:second,:third], :weights => [1,2,1]
@@ -22,12 +22,18 @@ module MTK
22
22
  #####################
23
23
  protected
24
24
 
25
- # (see AbstractPattern#current)
26
- def current
25
+ def advance
26
+ @index += 1
27
+ raise StopIteration if @index > 0
28
+
27
29
  target = rand * @total_weight
28
30
  @weights.each_with_index do |weight,index|
29
- return @elements[index] if target < weight
30
- target -= weight
31
+ if target < weight
32
+ @current = @elements[index]
33
+ break
34
+ else
35
+ target -= weight
36
+ end
31
37
  end
32
38
  end
33
39
 
@@ -0,0 +1,18 @@
1
+ module MTK
2
+ module Patterns
3
+
4
+ # An endless enumerator that outputs an element one at a time from a list of elements,
5
+ # looping back to the beginning when elements run out.
6
+ # This is the same as a Sequence but by default has unlimited @max_cycles
7
+ class Cycle < Sequence
8
+
9
+ def initialize(elements, options={})
10
+ super
11
+ # Base Pattern & Sequence default to 1 max_cycle, this defaults to nil which is unlimited cycles
12
+ @max_cycles = options[:max_cycles]
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,71 @@
1
+ module MTK
2
+ module Patterns
3
+
4
+ # For each value in the first sub-pattern, iterate over the second sub-pattern and chain the resulting values.
5
+ #
6
+ class ForEach < Pattern
7
+
8
+ # (see Pattern#next)
9
+ def next
10
+ @index = 0 if @index < 0
11
+
12
+ last_index = @elements.length-1
13
+ while @index <= last_index
14
+ # assume all elements are Patterns, otherwise this construct doesn't really have a point...
15
+ pattern = @elements[@index]
16
+ begin
17
+ element = pattern.next
18
+ value = evaluate_variables(element)
19
+
20
+ if @index == last_index # then emit values
21
+ @current = value
22
+ return emit(value)
23
+
24
+ else # not last element, so store variables
25
+ @vars.push value
26
+ @index += 1
27
+ end
28
+
29
+ rescue StopIteration
30
+ if @index==0
31
+ raise # We're done when the first pattern is done
32
+ else
33
+ pattern.rewind
34
+ @vars.pop
35
+ @index -= 1
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ ###################
43
+ protected
44
+
45
+ # (see Pattern#rewind_or_cycle)
46
+ def rewind_or_cycle(is_cycling=false)
47
+ @vars = []
48
+ @elements.each{|elem| elem.rewind }
49
+ super
50
+ end
51
+
52
+
53
+ ####################
54
+ private
55
+
56
+ def evaluate_variables(element)
57
+ case element
58
+ when ::MTK::Variable
59
+ if element.implicit?
60
+ return @vars[-element.name.length] # '$' is most recently pushed value, $$' goes back 2 levels, '$$$' goes back 3, etc
61
+ end
62
+ when Array
63
+ return element.map{|e| evaluate_variables(e) }
64
+ end
65
+ return element
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,39 @@
1
+ module MTK
2
+ module Patterns
3
+
4
+ # An arbitrary function that dynamically generates elements.
5
+ #
6
+ class Function < Pattern
7
+
8
+ attr_reader :function
9
+
10
+ def initialize(elements, options={})
11
+ # unpack from the varargs Array that may be passed in from the "convenience constructor methods" defined in MTK::Pattern \
12
+ @function = if elements.is_a? Enumerable then elements.first else elements end
13
+ super [@function], options
14
+ end
15
+
16
+
17
+ ###################
18
+ protected
19
+
20
+ # (see Pattern#rewind_or_cycle)
21
+ def rewind_or_cycle(is_cycling=false)
22
+ @function_call_count = -1
23
+ super
24
+ end
25
+
26
+ def advance
27
+ @function_call_count += 1
28
+ @current = case @function.arity
29
+ when 0 then @function.call
30
+ when 1 then @function.call(@current)
31
+ when 2 then @function.call(@current, @function_call_count)
32
+ else @function.call(@current, @function_call_count, @element_count)
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end