xi-lang 0.1.0

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.
@@ -0,0 +1,302 @@
1
+ module Xi
2
+ class Pattern
3
+ module Transforms
4
+ # Negates every number in the pattern
5
+ #
6
+ # Non-numeric values are ignored.
7
+ #
8
+ # @example
9
+ # peek -[10, 20, 30].p #=> [-10, -20, -30]
10
+ # peek -[1, -2, 3].p #=> [-1, 2, -3]
11
+ #
12
+ # @return [Pattern]
13
+ #
14
+ def -@
15
+ Pattern.new(self) do |y|
16
+ each { |v| y << (v.respond_to?(:-@) ? -v : v) }
17
+ end
18
+ end
19
+
20
+ # Concatenate +object+ pattern or perform a scalar sum with +object+
21
+ #
22
+ # If +object+ is a Pattern, concatenate the two patterns.
23
+ # Else, for each value from pattern, sum with +object+.
24
+ # Values that do not respond to #+ are ignored.
25
+ #
26
+ # @example Concatenation of patterns
27
+ # peek [1, 2, 3].p + [4, 5, 6].p #=> [1, 2, 3, 4, 5, 6]
28
+ #
29
+ # @example Scalar sum
30
+ # peek [1, 2, 3].p + 60 #=> [61, 62, 63]
31
+ # peek [0.25, 0.5].p + 0.125 #=> [0.375, 0.625]
32
+ # peek [0, :foo, 2].p + 1 #=> [1, :foo, 3]
33
+ #
34
+ # @param object [Pattern, Numeric] pattern or numeric
35
+ # @return [Pattern]
36
+ #
37
+ def +(object)
38
+ if object.is_a?(Pattern)
39
+ Pattern.new(self, size: size + object.size) do |y|
40
+ each { |v| y << v }
41
+ object.each { |v| y << v }
42
+ end
43
+ else
44
+ Pattern.new(self) do |y|
45
+ each { |v| y << (v.respond_to?(:+) ? v + object : v) }
46
+ end
47
+ end
48
+ end
49
+
50
+ # Performs a scalar substraction with +numeric+
51
+ #
52
+ # For each value from pattern, substract with +numeric+.
53
+ # Values that do not respond to #- are ignored.
54
+ #
55
+ # @example
56
+ # peek [1, 2, 3].p - 10 #=> [-9, -8, -7]
57
+ # peek [1, :foo, 3].p - 10 #=> [-9, :foo, -7]
58
+ #
59
+ # @param numeric [Numeric]
60
+ # @return [Pattern]
61
+ #
62
+ def -(numeric)
63
+ Pattern.new(self) do |y|
64
+ each { |v| y << (v.respond_to?(:-) ? v - numeric : v) }
65
+ end
66
+ end
67
+
68
+ # Performs a scalar multiplication with +numeric+
69
+ #
70
+ # For each value from pattern, multiplicate with +numeric+.
71
+ # Values that do not respond to #* are ignored.
72
+ #
73
+ # @example
74
+ # peek [1, 2, 4].p * 2 #=> [2, 4, 8]
75
+ # peek [1, :foo].p * 2 #=> [2, :foo]
76
+ #
77
+ # @param numeric [Numeric]
78
+ # @return [Pattern]
79
+ #
80
+ def *(numeric)
81
+ Pattern.new(self) do |y|
82
+ each { |v| y << (v.respond_to?(:*) ? v * numeric : v) }
83
+ end
84
+ end
85
+
86
+ # Performs a scalar division by +numeric+
87
+ #
88
+ # For each value from pattern, divide by +numeric+.
89
+ # Values that do not respond to #/ are ignored.
90
+ #
91
+ # @example
92
+ # peek [1, 2, 4].p / 2 #=> [(1/2), (1/1), (2/1)]
93
+ # peek [0.5, :foo].p / 2 #=> [0.25, :foo]
94
+ #
95
+ # @param numeric [Numeric]
96
+ # @return [Pattern]
97
+ #
98
+ def /(numeric)
99
+ Pattern.new(self) do |y|
100
+ each { |v| y << (v.respond_to?(:/) ? v / numeric : v) }
101
+ end
102
+ end
103
+
104
+ # Performs a scalar modulo against +numeric+
105
+ #
106
+ # For each value from pattern, return modulo of value divided by +numeric+.
107
+ # Values from pattern that do not respond to #% are ignored.
108
+ #
109
+ # @example
110
+ # peek (1..5).p % 2 #=> [1, 0, 1, 0, 1]
111
+ # peek [0, 1, 2, :bar, 4, 5, 6].p % 3 #=> [0, 1, 2, :bar, l, 2, 0]
112
+ #
113
+ # @param numeric [Numeric]
114
+ # @return [Pattern]
115
+ #
116
+ def %(numeric)
117
+ Pattern.new(self) do |y|
118
+ each { |v| y << (v.respond_to?(:%) ? v % numeric : v) }
119
+ end
120
+ end
121
+
122
+ # Raises each value to the power of +numeric+, which may be negative or
123
+ # fractional.
124
+ #
125
+ # Values from pattern that do not respond to #** are ignored.
126
+ #
127
+ # @example
128
+ # peek (0..5).p ** 2 #=> [0, 1, 4, 9, 16, 25]
129
+ # peek [1, 2, 3].p ** -2 #=> [1, (1/4), (1/9)]
130
+ #
131
+ # @param numeric [Numeric]
132
+ # @return [Pattern]
133
+ #
134
+ def **(numeric)
135
+ Pattern.new(self) do |y|
136
+ each { |v| y << (v.respond_to?(:**) ? v ** numeric : v) }
137
+ end
138
+ end
139
+ alias_method :^, :**
140
+
141
+ # Cycles pattern +repeats+ number of times, shifted by +offset+
142
+ #
143
+ # @example
144
+ # peek [1, 2, 3].p.seq #=> [1, 2, 3]
145
+ # peek [1, 2, 3].p.seq(2) #=> [1, 2, 3, 1, 2, 3]
146
+ # peek [1, 2, 3].p.seq(1, 1) #=> [2, 3, 1]
147
+ # peek [1, 2, 3].p.seq(2, 2) #=> [3, 2, 1, 3, 2, 1]
148
+ # peek [1, 2].p.seq(inf, 1) #=> [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
149
+ #
150
+ # @param repeats [Fixnum, Symbol] number or inf (defaut: 1)
151
+ # @param offset [Fixnum] (default: 0)
152
+ # @return [Pattern]
153
+ #
154
+ def seq(repeats=1, offset=0)
155
+ unless (repeats.is_a?(Fixnum) && repeats >= 0) || repeats == inf
156
+ fail ArgumentError, "repeats must be a non-negative Fixnum or inf"
157
+ end
158
+ unless offset.is_a?(Fixnum) && offset >= 0
159
+ fail ArgumentError, "offset must be a non-negative Fixnum"
160
+ end
161
+
162
+ Pattern.new(self, size: size * repeats) do |y|
163
+ rep = repeats
164
+
165
+ loop do
166
+ if rep != inf
167
+ rep -= 1
168
+ break if rep < 0
169
+ end
170
+
171
+ c = offset
172
+ offset_items = []
173
+
174
+ is_empty = true
175
+ each do |v|
176
+ is_empty = false
177
+ if c > 0
178
+ offset_items << v
179
+ c -= 1
180
+ else
181
+ y << v
182
+ end
183
+ end
184
+
185
+ offset_items.each { |v| y << v }
186
+
187
+ break if is_empty
188
+ end
189
+ end
190
+ end
191
+
192
+ # Traverses the pattern in order and then in reverse order
193
+ #
194
+ # @example
195
+ # peek (0..3).p.bounce #=> [0, 1, 2, 3, 3, 2, 1, 0]
196
+ #
197
+ # @return [Pattern]
198
+ #
199
+ def bounce
200
+ Pattern.new(self, size: size * 2 - 1) do |y|
201
+ each.with_index { |v, i| y << v if i > 0 }
202
+ reverse_each.with_index { |v, i| y << v if i > 0 }
203
+ end
204
+ end
205
+
206
+ # Normalizes a pattern of values that range from +min+ to +max+ to 0..1
207
+ #
208
+ # Values from pattern that do not respond to #- are ignored.
209
+ #
210
+ # @example
211
+ # peek (1..5).p.normalize(0, 100)
212
+ # #=> [(1/100), (1/50), (3/100), (1/25), (1/20)]
213
+ # peek [0, 0x40, 0x80, 0xc0].p.normalize(0, 0x100)
214
+ # #=> [(0/1), (1/4), (1/2), (3/4)]
215
+ #
216
+ # @param min [Numeric]
217
+ # @param max [Numeric]
218
+ # @return [Pattern]
219
+ #
220
+ def normalize(min, max)
221
+ Pattern.new(self) do |y|
222
+ each { |v| y << (v.respond_to?(:-) ? (v - min) / (max - min) : v) }
223
+ end
224
+ end
225
+
226
+ # Scales a pattern of normalized values (0..1) to a custom range
227
+ # +min+..+max+
228
+ #
229
+ # This is inverse of {#normalize}
230
+ # Values from pattern that do not respond to #* are ignored.
231
+ #
232
+ # @example
233
+ # peek [0.01, 0.02, 0.03, 0.04, 0.05].p.denormalize(0, 100)
234
+ # #=> [1.0, 2.0, 3.0, 4.0, 5.0]
235
+ # peek [0, 0.25, 0.50, 0.75].p.denormalize(0, 0x100)
236
+ # #=> [0, 64.0, 128.0, 192.0]
237
+ #
238
+ # @param min [Numeric]
239
+ # @param max [Numeric]
240
+ # @return [Pattern]
241
+ #
242
+ def denormalize(min, max)
243
+ Pattern.new(self) do |y|
244
+ each { |v| y << (v.respond_to?(:*) ? (max - min) * v + min : v) }
245
+ end
246
+ end
247
+
248
+ # Scale from one range of values to another range of values
249
+ #
250
+ # @example
251
+ # peek [0,2,4,1,3,6].p.scale(0, 6, 0, 0x7f)
252
+ #
253
+ # @param min_from [Numeric]
254
+ # @param max_from [Numeric]
255
+ # @param min_to [Numeric]
256
+ # @param max_to [Numeric]
257
+ # @return [Pattern]
258
+ #
259
+ def scale(min_from, max_from, min_to, max_to)
260
+ normalize(min_from, max_from).denormalize(min_to, max_to)
261
+ end
262
+
263
+ # TODO Document
264
+ def decelerate(num)
265
+ Pattern.new(self) do |y|
266
+ each_event { |e| y << E[e.value, e.start * num, e.duration * num] }
267
+ end
268
+ end
269
+
270
+ # TODO Document
271
+ def accelerate(num)
272
+ Pattern.new(self) do |y|
273
+ each_event { |e| y << E[e.value, e.start / num, e.duration / num] }
274
+ end
275
+ end
276
+
277
+ # Based on +probability+, it yields original value or nil
278
+ # TODO Document
279
+ #
280
+ def sometimes(probability=0.5)
281
+ prob_pat = probability.p
282
+ Pattern.new(self, size: size * prob_pat.size) do |y|
283
+ prob_pat.each do |prob|
284
+ each { |v| y << (rand < prob ? v : nil) }
285
+ end
286
+ end
287
+ end
288
+
289
+ # Repeats each value +times+
290
+ # TODO Document
291
+ #
292
+ def repeat_each(times)
293
+ times_pat = times.p
294
+ Pattern.new(self, size: size * times_pat.size) do |y|
295
+ times_pat.each do |t|
296
+ each { |v| t.times { y << v } }
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
data/lib/xi/pattern.rb ADDED
@@ -0,0 +1,150 @@
1
+ require 'forwardable'
2
+ require 'xi/event'
3
+ require 'xi/pattern/transforms'
4
+ require 'xi/pattern/generators'
5
+
6
+ module Xi
7
+ class Pattern
8
+ include Enumerable
9
+ include Transforms
10
+ include Generators
11
+ extend Forwardable
12
+
13
+ attr_reader :source, :event_duration, :metadata, :total_duration
14
+
15
+ alias_method :dur, :event_duration
16
+
17
+ def_delegators :@source, :size
18
+
19
+ def initialize(enum=nil, size: nil, **metadata)
20
+ size ||= enum.size if enum.respond_to?(:size)
21
+
22
+ @source = if block_given?
23
+ Enumerator.new(size) { |y| yield y }
24
+ elsif enum
25
+ enum
26
+ else
27
+ fail ArgumentError, 'must provide source or block'
28
+ end
29
+
30
+ @is_infinite = @source.size.nil? || @source.size == Float::INFINITY
31
+
32
+ @event_duration = metadata.delete(:dur) || metadata.delete(:event_duration)
33
+ @event_duration ||= enum.event_duration if enum.respond_to?(:event_duration)
34
+ @event_duration ||= 1
35
+
36
+ @metadata = enum.respond_to?(:metadata) ? enum.metadata : {}
37
+ @metadata.merge!(metadata)
38
+
39
+ if @is_infinite
40
+ @total_duration = @event_duration
41
+ else
42
+ last_ev = each_event.take(@source.size).last
43
+ @total_duration = last_ev ? last_ev.start + last_ev.duration : 0
44
+ end
45
+ end
46
+
47
+ def self.[](*args, **metadata)
48
+ new(args, **metadata)
49
+ end
50
+
51
+ def infinite?
52
+ @is_infinite
53
+ end
54
+
55
+ def finite?
56
+ !infinite?
57
+ end
58
+
59
+ def ==(o)
60
+ self.class == o.class &&
61
+ source == o.source &&
62
+ event_duration == o.event_duration &&
63
+ metadata == o.metadata
64
+ end
65
+
66
+ def p(dur=nil, **metadata)
67
+ Pattern.new(@source, dur: dur || @event_duration,
68
+ **@metadata.merge(metadata))
69
+ end
70
+
71
+ def each_event
72
+ return enum_for(__method__) unless block_given?
73
+
74
+ dur = @event_duration
75
+ pos = 0
76
+
77
+ @source.each do |value|
78
+ if value.is_a?(Pattern)
79
+ value.each do |v|
80
+ yield Event.new(v, pos, dur)
81
+ pos += dur
82
+ end
83
+ elsif value.is_a?(Event)
84
+ yield value
85
+ pos += value.duration
86
+ else
87
+ yield Event.new(value, pos, dur)
88
+ pos += dur
89
+ end
90
+ end
91
+ end
92
+
93
+ def each
94
+ return enum_for(__method__) unless block_given?
95
+ each_event { |e| yield e.value }
96
+ end
97
+
98
+ def inspect
99
+ ss = if @source.respond_to?(:join)
100
+ @source.map(&:inspect).join(', ')
101
+ elsif @source.is_a?(Enumerator)
102
+ "?enum"
103
+ else
104
+ @source.inspect
105
+ end
106
+
107
+ ms = @metadata.reject { |_, v| v.nil? }
108
+ ms.merge!(dur: dur) if dur != 1
109
+ ms = ms.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
110
+
111
+ "P[#{ss}#{", #{ms}" unless ms.empty?}]"
112
+ end
113
+ alias_method :to_s, :inspect
114
+
115
+ def map_events
116
+ return enum_for(__method__) unless block_given?
117
+ Pattern.new(dur: dur, **metadata) { |y| each_event { |e| y << yield(e) } }
118
+ end
119
+ alias_method :collect_events, :map_events
120
+
121
+ def select_events
122
+ return enum_for(__method__) unless block_given?
123
+ Pattern.new { |y| each_event { |e| y << e if yield(e) } }
124
+ end
125
+ alias_method :find_all_events, :select_events
126
+
127
+ def reject_events
128
+ return enum_for(__method__) unless block_given?
129
+ Pattern.new { |y| each_event { |e| y << e unless yield(e) } }
130
+ end
131
+
132
+ def to_events
133
+ each_event.to_a
134
+ end
135
+
136
+ def peek(limit=10)
137
+ values = take(limit + 1)
138
+ puts "There are more than #{limit} values..." if values.size > limit
139
+ values.take(limit)
140
+ end
141
+
142
+ def peek_events(limit=10)
143
+ events = each_event.take(limit + 1)
144
+ puts "There are more than #{limit} events..." if events.size > limit
145
+ events.take(limit)
146
+ end
147
+ end
148
+ end
149
+
150
+ P = Xi::Pattern
data/lib/xi/repl.rb ADDED
@@ -0,0 +1,66 @@
1
+ require "pry"
2
+ require 'io/console'
3
+ require "xi/error_log"
4
+
5
+ module Xi
6
+ module REPL
7
+ extend self
8
+
9
+ CONFIG_PATH = File.expand_path("~/.config/xi")
10
+ HISTORY_FILE = "history"
11
+ INIT_SCRIPT_FILE = "init.rb"
12
+
13
+ DEFAULT_INIT_SCRIPT =
14
+ "# Here you can customize or define functions that will be available in\n" \
15
+ "# Xi, e.g. new streams or a custom clock."
16
+
17
+ def start
18
+ configure
19
+ load_init_script
20
+
21
+ Pry.start
22
+ end
23
+
24
+ def configure
25
+ prepare_config_dir
26
+
27
+ if ENV["INSIDE_EMACS"]
28
+ Pry.config.correct_indent = false
29
+ Pry.config.pager = false
30
+ Pry.config.prompt = [ proc { "" }, proc { "" }]
31
+ else
32
+ Pry.config.prompt = [ proc { "xi> " }, proc { "..> " }]
33
+ end
34
+
35
+ Pry.config.history.file = history_path
36
+
37
+ Pry.hooks.add_hook(:after_eval, "check_for_errors") do |result, pry|
38
+ more_errors = ErrorLog.instance.more_errors?
39
+ ErrorLog.instance.each do |msg|
40
+ puts "(╯°□°)╯︵ ɹoɹɹǝ #{msg}"
41
+ end
42
+ puts "(⌣_⌣”) There were more errors..." if more_errors
43
+ end
44
+ end
45
+
46
+ def load_init_script
47
+ require(init_script_path)
48
+ end
49
+
50
+ def prepare_config_dir
51
+ FileUtils.mkdir_p(CONFIG_PATH)
52
+
53
+ unless File.exists?(init_script_path)
54
+ File.write(init_script_path, DEFAULT_INIT_SCRIPT)
55
+ end
56
+ end
57
+
58
+ def history_path
59
+ File.join(CONFIG_PATH, HISTORY_FILE)
60
+ end
61
+
62
+ def init_script_path
63
+ File.join(CONFIG_PATH, INIT_SCRIPT_FILE)
64
+ end
65
+ end
66
+ end
data/lib/xi/stream.rb ADDED
@@ -0,0 +1,203 @@
1
+ require 'set'
2
+
3
+ module Xi
4
+ class Stream
5
+ WINDOW_SEC = 0.05
6
+
7
+ attr_reader :clock, :source, :source_patterns, :state, :event_duration, :gate
8
+
9
+ def initialize(clock)
10
+ @mutex = Mutex.new
11
+ @playing = false
12
+ @state = {}
13
+ @new_sound_object_id = 0
14
+ @changed_params = [].to_set
15
+
16
+ self.clock = clock
17
+ end
18
+
19
+ def set(event_duration: nil, gate: nil, **source)
20
+ @mutex.synchronize do
21
+ @source = source
22
+ @gate = gate if gate
23
+ @event_duration = event_duration if event_duration
24
+ update_internal_structures
25
+ end
26
+ play
27
+ self
28
+ end
29
+ alias_method :<<, :set
30
+
31
+ def event_duration=(new_value)
32
+ @mutex.synchronize do
33
+ @event_duration = new_value
34
+ update_internal_structures
35
+ end
36
+ end
37
+
38
+ def gate=(new_value)
39
+ @mutex.synchronize do
40
+ @gate = new_value
41
+ update_internal_structures
42
+ end
43
+ end
44
+
45
+ def clock=(new_clock)
46
+ @clock.unsubscribe(self) if @clock
47
+ new_clock.subscribe(self) if playing?
48
+ @clock = new_clock
49
+ end
50
+
51
+ def playing?
52
+ @mutex.synchronize { @playing }
53
+ end
54
+
55
+ def stopped?
56
+ !playing?
57
+ end
58
+
59
+ def play
60
+ @mutex.synchronize do
61
+ @playing = true
62
+ @clock.subscribe(self)
63
+ end
64
+ self
65
+ end
66
+ alias_method :start, :play
67
+
68
+ def stop
69
+ @mutex.synchronize do
70
+ @playing = false
71
+ @state.clear
72
+ @clock.unsubscribe(self)
73
+ end
74
+ self
75
+ end
76
+ alias_method :pause, :play
77
+
78
+ def inspect
79
+ "#<#{self.class.name}:#{"0x%014x" % object_id} clock=#{@clock.inspect} #{playing? ? :playing : :stopped}>"
80
+ rescue => err
81
+ logger.error(err)
82
+ end
83
+
84
+ def notify(now)
85
+ return unless playing? && @source
86
+
87
+ @mutex.synchronize do
88
+ @changed_params.clear
89
+
90
+ forward_enums(now) if @must_forward
91
+
92
+ gate_on, gate_off = play_enums(now)
93
+
94
+ do_gate_off_change(gate_off) unless gate_off.empty?
95
+ do_state_change if state_changed?
96
+ do_gate_on_change(gate_on) unless gate_on.empty?
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def changed_state
103
+ @state.select { |k, _| @changed_params.include?(k) }
104
+ end
105
+
106
+ def forward_enums(now)
107
+ @enums.each do |p, (enum, total_dur)|
108
+ cur_pos = now % total_dur
109
+ next_ev = enum.peek
110
+
111
+ while distance = (cur_pos - next_ev.start) % total_dur do
112
+ enum.next
113
+
114
+ break if distance <= next_ev.duration
115
+ next_ev = enum.peek
116
+ end
117
+ end
118
+ @must_forward = false
119
+ end
120
+
121
+ def play_enums(now)
122
+ gate_off = []
123
+ gate_on = []
124
+
125
+ @enums.each do |p, (enum, total_dur)|
126
+ cur_pos = now % total_dur
127
+ next_ev = enum.peek
128
+
129
+ # Check if there are any currently playing sound objects that
130
+ # must be gated off
131
+ @playing_sound_objects.dup.each do |end_pos, so_ids|
132
+ if (cur_pos - end_pos) % total_dur <= WINDOW_SEC
133
+ gate_off = so_ids
134
+ @playing_sound_objects.delete(end_pos)
135
+ end
136
+ end
137
+
138
+ # Do we need to play next event now? If not, skip this parameter
139
+ if (cur_pos - next_ev.start) % total_dur <= WINDOW_SEC
140
+ # Update state based on pattern value
141
+ update_state(p, next_ev.value)
142
+
143
+ # If this parameter is a gate, mark it as gate on as
144
+ # a new sound object
145
+ if p == @gate
146
+ new_so_ids = Array(next_ev.value).size.times.map do
147
+ so_id = @new_sound_object_id
148
+ @new_sound_object_id += 1
149
+ so_id
150
+ end
151
+ gate_on = new_so_ids
152
+ @playing_sound_objects[next_ev.end] = new_so_ids
153
+ end
154
+
155
+ # Because we already processed event, advance enumerator
156
+ enum.next
157
+ end
158
+ end
159
+
160
+ [gate_on, gate_off]
161
+ end
162
+
163
+ def update_internal_structures
164
+ @playing_sound_objects ||= {}
165
+ @must_forward = true
166
+ @enums = @source.map { |k, v|
167
+ pat = v.p(@event_duration)
168
+ [k, [infinite_enum(pat), pat.total_duration]]
169
+ }.to_h
170
+ end
171
+
172
+ def do_gate_on_change(ss)
173
+ logger.info "Gate on change: #{ss}"
174
+ end
175
+
176
+ def do_gate_off_change(ss)
177
+ logger.info "Gate off change: #{ss}"
178
+ end
179
+
180
+ def do_state_change
181
+ logger.info "State change: #{@state.select { |k, v| @changed_params.include?(k) }.to_h}"
182
+ end
183
+
184
+ def update_state(p, v)
185
+ logger.debug "Update state of :#{p}: #{v}"
186
+ @changed_params << p if v != @state[p]
187
+ @state[p] = v
188
+ end
189
+
190
+ def state_changed?
191
+ !@changed_params.empty?
192
+ end
193
+
194
+ def infinite_enum(p)
195
+ Enumerator.new { |y| loop { p.each_event { |e| y << e } } }
196
+ end
197
+
198
+ def logger
199
+ # FIXME this should be configurable
200
+ @logger ||= Logger.new("/tmp/xi.log")
201
+ end
202
+ end
203
+ end