patch 0.4.13

Sign up to get free protection for your applications and to get access to all the features.
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