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
@@ -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