mtk 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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,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
|
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 <
|
9
|
+
class Choice < Pattern
|
10
10
|
|
11
|
-
# @param (see
|
12
|
-
# @option (see
|
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
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
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
|