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,132 @@
1
+ module Patch
2
+
3
+ # Terminal/Console output explaining the hub configuration
4
+ class Report
5
+
6
+ # @param [Hub] hub The hub to print a report for
7
+ # @return [Report]
8
+ def self.print(hub)
9
+ new(hub).print
10
+ end
11
+
12
+ # @param [Hub] hub The hub to report about
13
+ def initialize(hub)
14
+ @hub = hub
15
+ end
16
+
17
+ # Print a report to standard out
18
+ # @return [Report] self
19
+ def print
20
+ report = self.report
21
+ puts
22
+ print_logo
23
+ puts
24
+ puts Rainbow("IPs").cyan
25
+ puts Rainbow("———").cyan
26
+ report[:ips].each { |ip| puts ip }
27
+ puts
28
+ puts Rainbow("Nodes").cyan
29
+ puts Rainbow("———").cyan
30
+ report[:nodes].each do |node|
31
+ puts "#{node[:id]}: #{node[:name]}"
32
+ end
33
+ puts
34
+ if report[:patches].count > 0
35
+ puts Rainbow("Patches").cyan
36
+ puts Rainbow("———").cyan
37
+ report[:patches].each_with_index do |patch, i|
38
+ puts "#{i+1}. #{patch[:name]}"
39
+ puts Rainbow("|").cyan
40
+ puts Rainbow("| Node Map").cyan
41
+ puts Rainbow("| ———").cyan
42
+ patch[:maps].each { |map| puts Rainbow("| ").cyan + map }
43
+ puts Rainbow("|").cyan
44
+ puts Rainbow("| Actions").cyan
45
+ puts Rainbow("| ———").cyan
46
+ chunked_actions(patch[:actions]).each do |chunk|
47
+ puts Rainbow("| ").cyan + chunk
48
+ end
49
+ puts Rainbow("|").cyan
50
+ puts
51
+ end
52
+ else
53
+ puts
54
+ end
55
+ unless report[:log].nil?
56
+ puts "Logging to #{report[:log]}"
57
+ puts
58
+ end
59
+ self
60
+ end
61
+
62
+ # Construct the report hash
63
+ # @return [Hash]
64
+ def report
65
+ report = {}
66
+ report[:ips] = @hub.ips
67
+ report[:nodes] = @hub.nodes.sort_by(&:id).map { |node| node_report(node) }
68
+ report[:patches] = @hub.patches.map { |patch| patch_report(patch) }
69
+ report[:log] = @hub.log.path unless @hub.log.nil?
70
+ report
71
+ end
72
+
73
+ private
74
+
75
+ # Get a patch action formatted for terminal width
76
+ # @param [Array<String>] actions
77
+ # @return [Array<String>]
78
+ def chunked_actions(actions)
79
+ max_length = columns - 10
80
+ chunks = []
81
+ actions.each do |action|
82
+ if chunks.last.nil? || chunks.last.length >= max_length - action.length
83
+ chunks << ""
84
+ end
85
+ chunk = chunks.last
86
+ chunk << "#{action}"
87
+ chunk << ", " unless action == actions.last
88
+ end
89
+ chunks
90
+ end
91
+
92
+ # The number of columns of the terminal
93
+ # @return [Fixnum]
94
+ def columns
95
+ `tput cols`.to_i rescue 80
96
+ end
97
+
98
+ # Output the patch logo
99
+ # @return [Boolean]
100
+ def print_logo
101
+ color = :blue
102
+ puts Rainbow("██████╗ █████╗ ████████╗ ██████╗██╗ ██╗").send(color)
103
+ puts Rainbow("██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██║ ██║").send(color)
104
+ puts Rainbow("██████╔╝███████║ ██║ ██║ ███████║").send(color)
105
+ puts Rainbow("██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██║").send(color)
106
+ puts Rainbow("██║ ██║ ██║ ██║ ╚██████╗██║ ██║").send(color)
107
+ puts Rainbow("╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╣").send(color)
108
+ puts Rainbow("═≡≡≡▓▓▓═════════════════════════════════╝").send(color)
109
+ true
110
+ end
111
+
112
+ # Construct the report about a node
113
+ # @return [Hash]
114
+ def node_report(node)
115
+ report = {}
116
+ report[:id] = node.id
117
+ report[:name] = node.class.name
118
+ report
119
+ end
120
+
121
+ # Construct the report about a patch
122
+ # @return [Hash]
123
+ def patch_report(patch)
124
+ report = {}
125
+ report[:name] = patch.name
126
+ report[:maps] = patch.maps.map { |map| "#{map.from.map(&:id)} => #{map.to.map(&:id)}" }
127
+ report[:actions] = patch.actions.map { |mapping| mapping[:name] }
128
+ report
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,19 @@
1
+ module Patch
2
+
3
+ module Thread
4
+
5
+ def self.new(&block)
6
+ thread = ::Thread.new do
7
+ begin
8
+ yield
9
+ rescue Exception => exception
10
+ ::Thread.main.raise(exception)
11
+ end
12
+ end
13
+ thread.abort_on_exception = true
14
+ thread
15
+ end
16
+
17
+ end
18
+
19
+ end
data/lib/patch.rb ADDED
@@ -0,0 +1,42 @@
1
+ # Patch
2
+ # Controller message patching
3
+ #
4
+ # Ari Russo
5
+ # (c)2014-2015
6
+ # Licensed under Apache 2.0
7
+ #
8
+
9
+ # Libs
10
+ require "forwardable"
11
+ require "json"
12
+ require "socket"
13
+ require "yaml"
14
+
15
+ require "em-websocket"
16
+ require "midi-eye"
17
+ require "osc-ruby"
18
+ require "osc-ruby/em_server"
19
+ require "rainbow"
20
+ require "scale"
21
+
22
+ # Modules
23
+ require "patch/config"
24
+ require "patch/io"
25
+ require "patch/node"
26
+ require "patch/thread"
27
+
28
+ # Classes
29
+ require "patch/hub"
30
+ require "patch/log"
31
+ require "patch/message"
32
+ require "patch/patch"
33
+ require "patch/report"
34
+
35
+ # Patches
36
+ require "patch/em_patch"
37
+
38
+ module Patch
39
+
40
+ VERSION = "0.4.13"
41
+
42
+ end
@@ -0,0 +1,16 @@
1
+ :nodes:
2
+ - :id: 1
3
+ :type: websocket
4
+ :host: localhost
5
+ :port: 9006
6
+ - :id: 2
7
+ :type: midi
8
+ :direction: input
9
+ :name: Apple Inc. IAC Driver
10
+ - :id: 3
11
+ :type: osc
12
+ :server:
13
+ :port: 8000
14
+ :client:
15
+ :host: 192.168.1.118
16
+ :port: 9000
@@ -0,0 +1,41 @@
1
+ :patches:
2
+ :test_patch:
3
+ :node_map:
4
+ [2, 3]: 1
5
+ :actions:
6
+ - :name: Zoom
7
+ :key: zoom
8
+ :default:
9
+ :scale: !ruby/range 0.1..5.0
10
+ :midi:
11
+ :channel: 0
12
+ :index: 0
13
+ :osc:
14
+ :address: /1/rotaryA
15
+ :scale: !ruby/range 0..1.0
16
+
17
+ - :name: Contrast
18
+ :key: contrast
19
+ :default:
20
+ :scale: !ruby/range 0..5.0
21
+ :osc:
22
+ :address: /1/rotaryD
23
+ :scale: !ruby/range 0..1.0
24
+
25
+ - :name: Saturation
26
+ :key: saturation
27
+ :default:
28
+ :scale: !ruby/range 0..2.0
29
+ :osc:
30
+ :address: /faderM
31
+ :scale:
32
+ :osc: !ruby/range 0..1.0
33
+
34
+ - :name: Z Depth
35
+ :key: zDepth
36
+ :default:
37
+ :scale: !ruby/range 0..1000
38
+ :osc:
39
+ :address: /1/faderA
40
+ :scale:
41
+ :osc: !ruby/range 0..1.0
@@ -0,0 +1,216 @@
1
+ require "helper"
2
+
3
+ class Patch::SpecTest < Minitest::Test
4
+
5
+ context "Spec" do
6
+
7
+ setup do
8
+ load_test_data
9
+ end
10
+
11
+ context ".to_hub" do
12
+
13
+ setup do
14
+ @hub = Patch::Config.to_hub(@nodes_path, :patches => @patches_path)
15
+ end
16
+
17
+ should "have nodes" do
18
+ refute_nil @hub.nodes
19
+ end
20
+
21
+ should "have patches" do
22
+ refute_nil @hub.patches
23
+ end
24
+
25
+ end
26
+
27
+ context ".to_nodes" do
28
+
29
+ context "from files" do
30
+
31
+ setup do
32
+ @nodes = Patch::Config.to_nodes(@nodes_file)
33
+ end
34
+
35
+ should "populate" do
36
+ refute_nil @nodes
37
+ assert @nodes.kind_of?(Patch::Node::Container)
38
+ refute_empty @nodes
39
+ end
40
+
41
+ end
42
+
43
+ context "from strings" do
44
+
45
+ setup do
46
+ @nodes = Patch::Config.to_nodes(@nodes_path)
47
+ end
48
+
49
+ should "populate" do
50
+ refute_nil @nodes
51
+ assert @nodes.kind_of?(Patch::Node::Container)
52
+ refute_empty @nodes
53
+ end
54
+
55
+ end
56
+
57
+ context "from hashes" do
58
+
59
+ setup do
60
+ @nodes_hash = {
61
+ :nodes => [{
62
+ :type => "websocket",
63
+ :id => 1,
64
+ :host => "localhost",
65
+ :port =>9006
66
+ }]
67
+ }
68
+ @nodes = Patch::Config.to_nodes(@nodes_hash)
69
+ end
70
+
71
+ should "populate" do
72
+ refute_nil @nodes
73
+ assert @nodes.kind_of?(Patch::Node::Container)
74
+ refute_empty @nodes
75
+ assert_equal @nodes_hash[:nodes].count, @nodes.count
76
+ end
77
+
78
+ end
79
+
80
+ context ".to_node_maps" do
81
+
82
+ setup do
83
+ @hash = YAML.load(File.new(@patches_path))
84
+ @nodes = Patch::Config.to_nodes(@nodes_file)
85
+ @map_config = @hash[:patches][:test_patch][:node_map]
86
+ end
87
+
88
+ should "instantiate maps" do
89
+ @maps = Patch::Config.to_node_maps(@nodes, @map_config)
90
+ refute_nil @maps
91
+ refute_empty @maps
92
+ assert_equal @map_config.size, @maps.size
93
+ assert @maps.first.kind_of?(Patch::Node::Map)
94
+ end
95
+
96
+ end
97
+
98
+ context ".to_patches" do
99
+
100
+ setup do
101
+ @nodes = Patch::Config.to_nodes(@nodes_file)
102
+ @patches = Patch::Config.to_patches(@nodes, @patches_file)
103
+ end
104
+
105
+ should "create patches" do
106
+ refute_nil @patches
107
+ refute_empty @patches
108
+ assert @patches.first.kind_of?(Patch::Patch)
109
+ end
110
+
111
+ end
112
+
113
+ context ".to_patch" do
114
+
115
+ setup do
116
+ @nodes = Patch::Config.to_nodes(@nodes_file)
117
+ config = {
118
+ :node_map => [{ [1,2] => 3 }],
119
+ :action => []
120
+ }
121
+ @patch = Patch::Config.send(:to_patch, :test, @nodes, config)
122
+ end
123
+
124
+ should "create patch" do
125
+ refute_nil @patch
126
+ assert @patch.kind_of?(Patch::Patch)
127
+ refute_nil @patch.maps
128
+ refute_nil @patch.actions
129
+ end
130
+
131
+ end
132
+
133
+ context ".ensure_hash" do
134
+
135
+ setup do
136
+ @path = File.join(__dir__, "config/patches.yml")
137
+ @file = File.new(@path)
138
+ end
139
+
140
+ context "path" do
141
+
142
+ setup do
143
+ @config = Patch::Config.send(:ensure_hash, @path)
144
+ end
145
+
146
+ should "populate" do
147
+ refute_nil @config
148
+ refute_empty @config
149
+ assert_equal Hash, @config.class
150
+ assert_equal 1, @config.keys.count
151
+ end
152
+
153
+ should "deep freeze" do
154
+ assert @config.frozen?
155
+ assert @config[:patches].frozen?
156
+ assert @config[:patches][:test_patch].frozen?
157
+ assert @config[:patches][:test_patch][:node_map].frozen?
158
+ end
159
+
160
+ end
161
+
162
+ context "file" do
163
+
164
+ setup do
165
+ @config = Patch::Config.send(:ensure_hash, @file)
166
+ end
167
+
168
+ should "populate" do
169
+ refute_nil @config
170
+ refute_empty @config
171
+ assert_equal Hash, @config.class
172
+ assert_equal 1, @config.keys.count
173
+ end
174
+
175
+ should "deep freeze" do
176
+ assert @config.frozen?
177
+ assert @config[:patches].frozen?
178
+ assert @config[:patches][:test_patch].frozen?
179
+ assert @config[:patches][:test_patch][:node_map].frozen?
180
+ end
181
+
182
+ end
183
+
184
+ context "hash" do
185
+
186
+ setup do
187
+ @hash = {
188
+ :test => {
189
+ :node_map => [{ [1,2] => 3 }],
190
+ :action => []
191
+ }
192
+ }
193
+ @config = Patch::Config.send(:ensure_hash, @hash)
194
+ end
195
+
196
+ should "populate" do
197
+ refute_nil @config
198
+ refute_empty @config
199
+ assert_equal Hash, @config.class
200
+ assert_equal 1, @config.keys.count
201
+ end
202
+
203
+ should "deep freeze" do
204
+ assert @config.frozen?
205
+ assert @config[:test].frozen?
206
+ assert @config[:test][:node_map].frozen?
207
+ assert @config[:test][:action].frozen?
208
+ end
209
+
210
+ end
211
+
212
+ end
213
+
214
+ end
215
+ end
216
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,20 @@
1
+ $:.unshift(File.join("..", "lib"))
2
+
3
+ require "minitest/autorun"
4
+ require "mocha/test_unit"
5
+ require "shoulda-context"
6
+
7
+ require "patch"
8
+
9
+ UDPSocket.any_instance.stubs(:connect)
10
+ EventMachine.stubs(:open_datagram_socket).returns(true)
11
+ EventMachine.stubs(:open_udp_socket).returns(true)
12
+ EventMachine.stubs(:start_server).returns(true)
13
+ EventMachine.stubs(:start_tcp_server).returns(true)
14
+
15
+ def load_test_data
16
+ @nodes_path = File.join(__dir__,"/config/nodes.yml")
17
+ @nodes_file = File.new(@nodes_path)
18
+ @patches_path = File.join(__dir__,"config/patches.yml")
19
+ @patches_file = File.new(@patches_path)
20
+ end
data/test/hub_test.rb ADDED
@@ -0,0 +1,49 @@
1
+ require "helper"
2
+
3
+ class Patch::HubTest < Minitest::Test
4
+
5
+ context "Hub" do
6
+
7
+ setup do
8
+ load_test_data
9
+ @hub = Patch::Config.to_hub(@nodes_path, :patches => @patches_path)
10
+ end
11
+
12
+ context "#ips" do
13
+
14
+ should "have ips array" do
15
+ refute_nil @hub.ips
16
+ refute_empty @hub.ips
17
+ end
18
+
19
+ end
20
+
21
+ context "#listen" do
22
+
23
+ should "start listeners and controller" do
24
+ @hub.patches.each { |patch| patch.expects(:enable).once }
25
+ @hub.nodes.each { |node| node.expects(:enable).once }
26
+ assert @hub.listen(:background => true)
27
+ end
28
+
29
+ end
30
+
31
+ context "#nodes" do
32
+
33
+ should "be populated" do
34
+ refute_nil @hub.nodes
35
+ end
36
+
37
+ should "have nodes" do
38
+ refute_empty @hub.nodes
39
+ end
40
+
41
+ should "be node container" do
42
+ assert_equal Patch::Node::Container, @hub.nodes.class
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,82 @@
1
+ require "helper"
2
+
3
+ class Patch::IO::MIDI::ActionTest < Minitest::Test
4
+
5
+ context "Action" do
6
+
7
+ setup do
8
+ @actions = [
9
+ {
10
+ :name=>"Volume",
11
+ :key=>"volume",
12
+ :default=> {
13
+ :scale=>0..2.0
14
+ },
15
+ :midi => {
16
+ :channel=>4,
17
+ :index=>7
18
+ },
19
+ :osc=> {
20
+ :address=>"/faderM",
21
+ :scale=> {
22
+ :osc=>0..1.0
23
+ }
24
+ }
25
+ },
26
+ {
27
+ :name=>"Z Depth",
28
+ :key=>"zDepth",
29
+ :default=>{
30
+ :scale=>0..1000
31
+ },
32
+ :osc=>{
33
+ :address=>"/1/faderA",
34
+ :scale=> {
35
+ :osc=>0..1.0
36
+ }
37
+ }
38
+ }
39
+ ]
40
+ end
41
+
42
+ context "#midi_actions" do
43
+
44
+ should "return only midi actions" do
45
+ actions = Patch::IO::MIDI::Action.midi_actions(@actions)
46
+ refute_nil actions
47
+ refute_empty actions
48
+ assert_equal 1, actions.size
49
+ assert_equal @actions[0], actions.first
50
+ end
51
+
52
+ end
53
+
54
+ context "#midi?" do
55
+
56
+ should "return true for actions with midi" do
57
+ assert Patch::IO::MIDI::Action.midi?(@actions[0])
58
+ end
59
+
60
+ should "return false for actions without midi" do
61
+ refute Patch::IO::MIDI::Action.midi?(@actions[1])
62
+ end
63
+
64
+ end
65
+
66
+ context "#find_by_index" do
67
+
68
+ should "return action if it has midi" do
69
+ action = Patch::IO::MIDI::Action.find_by_index(@actions, 7)
70
+ refute_nil action
71
+ end
72
+
73
+ should "not return action if it doesn't have midi" do
74
+ action = Patch::IO::MIDI::Action.find_by_index(@actions, 1)
75
+ assert_nil action
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+
82
+ end