patch 0.4.13

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +176 -0
  4. data/bin/patchrb +40 -0
  5. data/lib/patch/config.rb +124 -0
  6. data/lib/patch/em_patch.rb +47 -0
  7. data/lib/patch/hub.rb +68 -0
  8. data/lib/patch/io/midi/action.rb +42 -0
  9. data/lib/patch/io/midi/input.rb +110 -0
  10. data/lib/patch/io/midi/message.rb +112 -0
  11. data/lib/patch/io/midi/output.rb +58 -0
  12. data/lib/patch/io/midi.rb +45 -0
  13. data/lib/patch/io/module.rb +35 -0
  14. data/lib/patch/io/osc/action.rb +43 -0
  15. data/lib/patch/io/osc/client.rb +60 -0
  16. data/lib/patch/io/osc/message.rb +109 -0
  17. data/lib/patch/io/osc/server.rb +159 -0
  18. data/lib/patch/io/osc.rb +43 -0
  19. data/lib/patch/io/websocket/node.rb +103 -0
  20. data/lib/patch/io/websocket/socket.rb +103 -0
  21. data/lib/patch/io/websocket.rb +27 -0
  22. data/lib/patch/io.rb +15 -0
  23. data/lib/patch/log.rb +97 -0
  24. data/lib/patch/message.rb +67 -0
  25. data/lib/patch/node/container.rb +69 -0
  26. data/lib/patch/node/map.rb +71 -0
  27. data/lib/patch/node.rb +10 -0
  28. data/lib/patch/patch.rb +59 -0
  29. data/lib/patch/report.rb +132 -0
  30. data/lib/patch/thread.rb +19 -0
  31. data/lib/patch.rb +42 -0
  32. data/test/config/nodes.yml +16 -0
  33. data/test/config/patches.yml +41 -0
  34. data/test/config_test.rb +216 -0
  35. data/test/helper.rb +20 -0
  36. data/test/hub_test.rb +49 -0
  37. data/test/io/midi/action_test.rb +82 -0
  38. data/test/io/midi/input_test.rb +130 -0
  39. data/test/io/midi/message_test.rb +54 -0
  40. data/test/io/midi/output_test.rb +44 -0
  41. data/test/io/midi_test.rb +94 -0
  42. data/test/io/module_test.rb +21 -0
  43. data/test/io/osc/action_test.rb +76 -0
  44. data/test/io/osc/client_test.rb +49 -0
  45. data/test/io/osc/message_test.rb +53 -0
  46. data/test/io/osc/server_test.rb +116 -0
  47. data/test/io/osc_test.rb +111 -0
  48. data/test/io/websocket/node_test.rb +96 -0
  49. data/test/io/websocket_test.rb +37 -0
  50. data/test/js/logger.js +67 -0
  51. data/test/js/message.js +62 -0
  52. data/test/js/qunit-1.18.0.js +3828 -0
  53. data/test/js/qunit.css +291 -0
  54. data/test/js/test.html +15 -0
  55. data/test/js/websocket.js +12 -0
  56. data/test/log_test.rb +96 -0
  57. data/test/message_test.rb +109 -0
  58. data/test/node/container_test.rb +104 -0
  59. data/test/node/map_test.rb +50 -0
  60. data/test/node_test.rb +14 -0
  61. data/test/patch_test.rb +57 -0
  62. data/test/report_test.rb +37 -0
  63. metadata +320 -0
@@ -0,0 +1,112 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ module MIDI
6
+
7
+ # Convert between MIDI message objects and Patch::Message objects
8
+ module Message
9
+
10
+ extend self
11
+
12
+ # @param [::Patch::Patch] patch
13
+ # @param [Array<::Patch::Message>, ::Patch::Message] messages
14
+ # @return [Array<::MIDIMessage>]
15
+ def to_midi_messages(patch, patch_messages)
16
+ patch_messages = [patch_messages].flatten
17
+ midi_messages = patch_messages.map do |patch_message|
18
+ unless (action = patch.actions.at(patch_message.index)).nil?
19
+ to_midi_message(action, patch_message) unless action[:midi].nil?
20
+ end
21
+ end
22
+ midi_messages.compact
23
+ end
24
+
25
+ # Convert the given MIDI message to Patch::Message objects using the context of the given patch
26
+ # @param [::Patch::Patch] patch
27
+ # @param [Array<MIDIMessage>, MIDIMessage] midi_messages
28
+ # @return [Array<::Patch::Message>]
29
+ def to_patch_messages(patch, midi_messages)
30
+ midi_messages = [midi_messages].flatten
31
+ patch_messages = midi_messages.map do |midi_message|
32
+ unless (action = Action.find_by_index(patch.actions, midi_message.index)).nil?
33
+ index = patch.actions.index(action)
34
+ to_patch_message(action, index, patch.name, midi_message)
35
+ end
36
+ end
37
+ patch_messages.compact
38
+ end
39
+
40
+ private
41
+
42
+ # @param [Hash] action An action to contextualize the conversion
43
+ # @param [::Patch::Message] patch_message
44
+ def get_midi_value_from_action(action, patch_message)
45
+ to = action[:midi][:scale]
46
+ to ||= 0..127
47
+ from = action[:default][:scale] unless action[:default].nil?
48
+ from ||= to
49
+ get_value(patch_message.value, from, to)
50
+ end
51
+
52
+ # @param [Hash] action An action to contextualize the conversion
53
+ # @param [MIDIMessage] midi_message
54
+ def get_patch_values_from_action(action, midi_message)
55
+ from = action[:midi][:scale]
56
+ from ||= 0..127
57
+ to = action[:default][:scale] unless action[:default].nil?
58
+ to ||= from
59
+ get_value(midi_message.value, from, to)
60
+ end
61
+
62
+ # Convert a patch message to a MIDI message
63
+ # @param [Hash] action An action to contextualize the conversion
64
+ # @param [::Patch::Message] patch_message
65
+ # @return [::MIDIMessage::ControlChange, nil]
66
+ def to_midi_message(action, patch_message)
67
+ if !action[:midi].nil?
68
+ index = action[:midi][:index] || patch_message.index
69
+ channel = action[:midi][:channel] || 0
70
+ value = get_midi_value_from_action(action, patch_message)
71
+ MIDIMessage::ControlChange.new(channel, index, value)
72
+ end
73
+ end
74
+
75
+ # Convert a MIDI message to a patch message
76
+ # @param [Hash] action An action to contextualize the conversion
77
+ # @param [Fixnum] index The index of the message
78
+ # @param [Symbol] patch_name A patch name
79
+ # @param [::MIDIMessage::ControlChange] midi_message
80
+ # @return [::Patch::Message, nil]
81
+ def to_patch_message(action, index, patch_name, midi_message)
82
+ if action[:midi][:channel].nil? || action[:midi][:channel] == midi_message.channel
83
+ value = get_patch_values_from_action(action, midi_message)
84
+ properties = {
85
+ :index => index,
86
+ :patch_name => patch_name,
87
+ :value => value
88
+ }
89
+ ::Patch::Message.new(properties)
90
+ end
91
+ end
92
+
93
+ # Translate a value
94
+ # @param [Fixnum] value
95
+ # @param [Range] from
96
+ # @param [Range] to
97
+ # @return [Fixnum]
98
+ def get_value(value, from, to)
99
+ if from == to
100
+ value
101
+ else
102
+ Scale.transform(value).from(from).to(to)
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,58 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ module MIDI
6
+
7
+ # MIDI Output functions
8
+ class Output
9
+
10
+ attr_reader :id, :device
11
+
12
+ # @param [Fixnum] id
13
+ # @param [String, UniMIDI::Output] device
14
+ # @param [Hash] options
15
+ # @option options [Debug] :log
16
+ def initialize(id, device, options = {})
17
+ @log = options[:log]
18
+ @id = id
19
+ @device = get_output(device)
20
+ end
21
+
22
+ # Convert Patch::Message objects to MIDI and send
23
+ # @param [Patch::Patch] patch Context
24
+ # @param [Array<Patch::Message>, Patch::Message] messages Message(s) to send via MIDI
25
+ # @return [Array<MIDIMessage>]
26
+ def puts(patch, patch_messages)
27
+ patch_messages = [patch_messages].flatten
28
+ messages = ::Patch::IO::MIDI::Message.to_midi_messages(patch, patch_messages)
29
+ @device.puts(messages) unless messages.empty?
30
+ messages
31
+ end
32
+
33
+ private
34
+
35
+ # Initialize the output device given a name or device object. If the name of the device is the string "choose",
36
+ # the user is prompted to select an availble MIDI output.
37
+ # @param [String, UniMIDI::Output, nil] device
38
+ # @return [UniMIDI::Output]
39
+ def get_output(device)
40
+ if device.kind_of?(String)
41
+ if device == "choose"
42
+ UniMIDI::Output.gets
43
+ else
44
+ UniMIDI::Output.find_by_name(device)
45
+ end
46
+ elsif device.respond_to?(:puts)
47
+ device.open if device.kind_of?(UniMIDI::Output)
48
+ device
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,45 @@
1
+ # Modules
2
+ require "patch/io/midi/action"
3
+ require "patch/io/midi/message"
4
+ # Classes
5
+ require "patch/io/midi/input"
6
+ require "patch/io/midi/output"
7
+
8
+ module Patch
9
+
10
+ module IO
11
+
12
+ # MIDI IO
13
+ module MIDI
14
+
15
+ # Key that will be used by Patch to identify the module
16
+ KEY = :midi
17
+ extend self
18
+ ::Patch::IO::Module.add(self)
19
+
20
+ # Instantiate a MIDI device based on the given config
21
+ # @param [Hash] config
22
+ # @param [Hash] options
23
+ # @option options [Log] :log
24
+ # @return [MIDI::Input, MIDI::Output]
25
+ def new_from_config(config, options = {})
26
+ klass = get_direction_class(config[:direction])
27
+ klass.new(config[:id], config[:name], :log => options[:log])
28
+ end
29
+
30
+ private
31
+
32
+ # Get the direction class for the given key
33
+ # @param [Symbol] key
34
+ # @return [Class]
35
+ def get_direction_class(key)
36
+ case key.to_sym
37
+ when :input then Input
38
+ when :output then Output
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ # Manage node modules
6
+ class Module
7
+
8
+ class << self
9
+
10
+ # Find an IO module by its key
11
+ # @param [Symbol] key
12
+ # @return [Module]
13
+ def find_by_key(key)
14
+ all.find { |mod| mod::KEY === key }
15
+ end
16
+
17
+ # Add an IO module to the list of modules available to Patch
18
+ # @param [Module] mod
19
+ # @return [Array<Module>]
20
+ def add(mod)
21
+ @modules ||= []
22
+ @modules << mod
23
+ end
24
+
25
+ # Mapping of node modules and names
26
+ # @return [Array<Module>]
27
+ def all
28
+ @modules ||= []
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ module OSC
6
+
7
+ # Find and identify OSC Actions
8
+ module Action
9
+
10
+ extend self
11
+
12
+ # Is the given action OSC?
13
+ # @param [Hash] action
14
+ # @return [Boolean]
15
+ def osc?(action)
16
+ !action[:osc].nil?
17
+ end
18
+
19
+ # Filter the given actions only to return OSC actions
20
+ # @param [Array<Hash>] actions
21
+ # @return [Array<Hash>]
22
+ def osc_actions(actions)
23
+ actions.select { |action| osc?(action) }
24
+ end
25
+
26
+ # Find an action by its OSC address
27
+ # @param [Array<Hash>] actions
28
+ # @param [String] address
29
+ # @return [Hash]
30
+ def find_by_address(actions, address)
31
+ osc_actions(actions).find do |action|
32
+ regex = Regexp.new(action[:osc][:address])
33
+ address.match(regex)
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,60 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ module OSC
6
+
7
+ # OSC Client
8
+ class Client
9
+
10
+ attr_reader :id
11
+
12
+ # @param [String] host
13
+ # @param [Fixnum] port
14
+ # @param [Hash] options
15
+ # @option options [Fixnum] :id
16
+ # @option options [Log] :log
17
+ def initialize(host, port, options = {})
18
+ @id = options[:id]
19
+ @log = options[:log]
20
+ @client = ::OSC::Client.new(host, port)
21
+ end
22
+
23
+ # Convert message objects to OSC and send
24
+ # @param [Patch::Patch] patch Context
25
+ # @param [Array<Patch::Message, ::OSC::Message>, ::OSC::Message, Patch::Message] messages Message(s) to send
26
+ # @return [Array<::OSC::Message>]]
27
+ def puts(patch, messages)
28
+ osc_messages = get_osc_messages(patch, messages)
29
+ osc_messages.each { |osc_message| @client.send(osc_message) }
30
+ osc_messages
31
+ end
32
+
33
+ private
34
+
35
+ # @param [::Patch::Patch] patch
36
+ # @param [Array<Patch::Message, ::OSC::Message>, ::OSC::Message, Patch::Message] messages Message(s) to send
37
+ # @return [Array<::OSC::Message>]]
38
+ def get_osc_messages(patch, messages)
39
+ messages = [messages].flatten
40
+ messages.map { |message| ensure_osc_message(patch, message) }
41
+ end
42
+
43
+ # @param [::Patch::Patch] patch
44
+ # @param [::OSC::Message, Patch::Message] message
45
+ # @return [::OSC::Message]
46
+ def ensure_osc_message(patch, message)
47
+ unless message.kind_of?(::OSC::Message)
48
+ osc_message = ::Patch::IO::OSC::Message.to_osc_messages(patch, message)
49
+ end
50
+ osc_message ||= message
51
+ osc_message
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,109 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ module OSC
6
+
7
+ # Convert between OSC message and Patch::Message objects
8
+ module Message
9
+
10
+ extend self
11
+
12
+ # Convert a message object to an OSC message given the context of the given patch
13
+ # @param [::Patch::Patch] patch
14
+ # @param [::Patch::Message] message
15
+ # @return [Array<::OSC::Message>]
16
+ def to_osc_messages(patch, patch_message)
17
+ messages = []
18
+ unless (action = get_osc_action(patch.actions, patch_message)).nil?
19
+ messages << get_osc_message(action, patch_message)
20
+ end
21
+ messages
22
+ end
23
+
24
+ # Convert the given OSC message to Patch::Message objects using the context of the given patch
25
+ # @param [::Patch::Patch] patch
26
+ # @param [Object] raw_osc
27
+ # @return [Array<::Patch::Message>]
28
+ def to_patch_messages(patch, raw_osc)
29
+ messages = []
30
+ unless (action = Action.find_by_address(patch.actions, raw_osc.address)).nil?
31
+ messages << get_patch_message(patch, action, raw_osc)
32
+ end
33
+ messages
34
+ end
35
+
36
+ private
37
+
38
+ # @param [::Patch::Patch] patch
39
+ # @param [Hash] action
40
+ # @param [Object] raw_osc
41
+ # @return [::Patch::Message]
42
+ def get_patch_message(patch, action, raw_osc)
43
+ index = patch.actions.index(action)
44
+ values = get_patch_values_from_action(raw_osc, action)
45
+ properties = {
46
+ :index => index,
47
+ :patch_name => patch.name,
48
+ :value => values[0]
49
+ }
50
+ ::Patch::Message.new(properties)
51
+ end
52
+
53
+ # @param [Hash] action
54
+ # @param [::Patch::Message] patch_message
55
+ # @return [::OSC::Message]
56
+ def get_osc_message(action, patch_message)
57
+ address = action[:osc][:address]
58
+ value = get_osc_value_from_action(patch_message.value, action)
59
+ ::OSC::Message.new(address, value)
60
+ end
61
+
62
+ # @param [Object] raw_osc
63
+ # @param [Hash] action
64
+ # @return [Array<Object>]
65
+ def get_patch_values_from_action(raw_osc, action)
66
+ from = action[:osc][:scale]
67
+ to = action[:default][:scale] unless action[:default].nil?
68
+ to ||= from
69
+ raw_osc.to_a.map { |value| get_value(value.to_f, from, to) }
70
+ end
71
+
72
+ # @param [Object] value
73
+ # @param [Hash] action
74
+ # @return [Object]
75
+ def get_osc_value_from_action(value, action)
76
+ to = action[:osc][:scale]
77
+ from = action[:default][:scale] unless action[:default].nil?
78
+ from ||= to
79
+ get_value(value, from, to)
80
+ end
81
+
82
+ # @param [Array<Hash>] actions
83
+ # @param [::Patch::Message] patch_message
84
+ # @return [Hash]
85
+ def get_osc_action(actions, patch_message)
86
+ action = actions.at(patch_message.index)
87
+ action unless action.nil? || action[:osc].nil?
88
+ end
89
+
90
+ # Translate a value
91
+ # @param [Fixnum] value
92
+ # @param [Range] from
93
+ # @param [Range] to
94
+ # @return [Fixnum]
95
+ def get_value(value, from, to)
96
+ if from == to
97
+ value
98
+ else
99
+ Scale.transform(value).from(from).to(to)
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,159 @@
1
+ module Patch
2
+
3
+ module IO
4
+
5
+ module OSC
6
+
7
+ # OSC server
8
+ class Server
9
+
10
+ attr_reader :id
11
+
12
+ # @param [Fixnum] id
13
+ # @param [Fixnum] port
14
+ # @param [Hash] options
15
+ # @option options [Hash] :echo
16
+ # @option options [Log] :log
17
+ def initialize(id, port, options = {})
18
+ @log = options[:log]
19
+ @server = nil
20
+ @active = false
21
+ @id = id
22
+ @is_failsafe = true
23
+
24
+ configure(port, options)
25
+ end
26
+
27
+ # Is the server active?
28
+ # @return [Boolean]
29
+ def active?
30
+ @active
31
+ end
32
+
33
+ # Start the server
34
+ # @return [Boolean] Whether the server was started
35
+ def start
36
+ @active = true
37
+ @connection = @server.run
38
+ true
39
+ end
40
+
41
+ # Stop the server
42
+ # @return [Boolean]
43
+ def stop
44
+ @active = false
45
+ true
46
+ end
47
+
48
+ # Disable the message handlers
49
+ # @return [Boolean]
50
+ def disable(patch)
51
+ addresses = get_addresses(patch)
52
+ addresses.select { |address| @server.remove_method(address) }.any?
53
+ end
54
+
55
+ # Listen for messages
56
+ # @param [::Patch::Patch] patch The patch to use for context
57
+ # @param [Proc] callback A callback to fire when messages are received
58
+ # @return [Boolean] Whether any actions were configured
59
+ def listen(patch, &callback)
60
+ addresses = get_addresses(patch)
61
+ addresses.select { |address| listen_for(address, patch, &callback) }.any?
62
+ end
63
+
64
+ protected
65
+
66
+ # @param [::Patch::Patch] patch The patch to use for context
67
+ # @return [Array<String, Regexp>]
68
+ def get_addresses(patch)
69
+ actions = ::Patch::IO::OSC::Action.osc_actions(patch.actions)
70
+ actions.map { |action| action[:osc][:address] }.compact.uniq
71
+ end
72
+
73
+ # Handle a new message
74
+ # @param [::Patch::Patch] patch A patch for context
75
+ # @param [OSC::Message] message The OSC message object
76
+ # @param [Proc] callback A callback to fire when a message or messages is received
77
+ # @return [Array<Patch::Message>]
78
+ def handle_message_received(patch, raw_osc, &callback)
79
+ messages = ::Patch::IO::OSC::Message.to_patch_messages(patch, raw_osc)
80
+ echo(patch, raw_osc) if echo?
81
+ # yield to custom behavior
82
+ yield(messages) if block_given?
83
+ messages
84
+ end
85
+
86
+ private
87
+
88
+ # @param [Fixnum] port
89
+ # @param [Hash] options
90
+ # @option options [Hash] :echo
91
+ # @return [Boolean]
92
+ def configure(port, options = {})
93
+ configure_server(port)
94
+ unless options[:echo].nil?
95
+ configure_echo(options[:echo][:host], options[:echo][:port])
96
+ end
97
+ true
98
+ end
99
+
100
+ # Listen for messages on the given address
101
+ # @param [::Patch::Patch] patch The patch to use for context
102
+ # @param [Proc] callback A callback to fire when messages are received
103
+ # @return [Boolean] Whether an action was configured
104
+ def listen_for(address, patch, &callback)
105
+ @server.add_method(address) do |message|
106
+ handle_message_received(patch, message, &callback)
107
+ end
108
+ true
109
+ end
110
+
111
+ # Configure the underlying server
112
+ # @param [Fixnum] port
113
+ # @return [::OSC::Server]
114
+ def configure_server(port)
115
+ @server = ::OSC::EMServer.new(port)
116
+ if @log
117
+ @server.add_method(/.*/) { |message| @log.puts("Received: #{message.address}") }
118
+ end
119
+ @server
120
+ end
121
+
122
+ # Will received messages be echoed back to the client?
123
+ # @return [Boolean]
124
+ def echo?
125
+ !@client.nil?
126
+ end
127
+
128
+ # Echo a message back to the client to update the UI or whatever
129
+ # @param [::Patch::Patch] patch
130
+ # @param [OSC::Message] osc_message
131
+ # @return [Boolean] Whether the echo occurred
132
+ def echo(patch, osc_message)
133
+ begin
134
+ @client.puts(patch, osc_message)
135
+ true
136
+ rescue Exception => exception # failsafe
137
+ @log.exception(exception) if @log
138
+ ::Thread.main.raise(exception) unless @is_failsafe
139
+ false
140
+ end
141
+ end
142
+
143
+ # Configure the echo client
144
+ # @param [String] host
145
+ # @param [Fixnum] echo
146
+ # @param [Hash] options
147
+ # @param [Log] :log
148
+ # @return [::Patch::IO::OSC::Client]
149
+ def configure_echo(host, port, options = {})
150
+ @client = Client.new(host, port, :log => options.fetch(:log, @log))
151
+ end
152
+
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,43 @@
1
+ # Modules
2
+ require "patch/io/osc/action"
3
+ require "patch/io/osc/message"
4
+ # Classes
5
+ require "patch/io/osc/client"
6
+ require "patch/io/osc/server"
7
+
8
+ module Patch
9
+
10
+ module IO
11
+
12
+ # Receive OSC messages and do something with them
13
+ module OSC
14
+
15
+ # Key that will be used by Patch to identify the module
16
+ KEY = :osc
17
+ extend self
18
+ ::Patch::IO::Module.add(self)
19
+
20
+ # Instantiate an OSC server and/or client using the given config
21
+ # @param [Hash] config
22
+ # @param [Hash] options
23
+ # @option options [Action::Container] :actions
24
+ # @option options [Log] :log
25
+ # @return [::Patch::IO::OSC::Server]
26
+ def new_from_config(config, options = {})
27
+ instance_options = {
28
+ :log => options[:log]
29
+ }
30
+ if config[:server].nil?
31
+ unless config[:client].nil?
32
+ instance_options[:id] = config[:id]
33
+ Client.new(config[:client][:host], config[:client][:port], instance_options)
34
+ end
35
+ else
36
+ instance_options[:echo] = config[:client]
37
+ Server.new(config[:id], config[:server][:port], instance_options)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end