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