qlab-ruby 0.1.2
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/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +59 -0
- data/Rakefile +21 -0
- data/lib/qlab-ruby.rb +34 -0
- data/lib/qlab-ruby/commands.rb +223 -0
- data/lib/qlab-ruby/communicator.rb +51 -0
- data/lib/qlab-ruby/core-ext/osc-ruby.rb +6 -0
- data/lib/qlab-ruby/core-ext/osc-ruby/message.rb +25 -0
- data/lib/qlab-ruby/core-ext/osc-ruby/sending_socket.rb +47 -0
- data/lib/qlab-ruby/core-ext/osc-ruby/tcp.rb +15 -0
- data/lib/qlab-ruby/core-ext/osc-ruby/tcp_client.rb +125 -0
- data/lib/qlab-ruby/cue.rb +87 -0
- data/lib/qlab-ruby/cue_list.rb +72 -0
- data/lib/qlab-ruby/machine.rb +94 -0
- data/lib/qlab-ruby/reply.rb +51 -0
- data/lib/qlab-ruby/version.rb +3 -0
- data/lib/qlab-ruby/workspace.rb +110 -0
- data/qlab-ruby.gemspec +24 -0
- data/test/cue_test.rb +41 -0
- data/test/machine_test.rb +26 -0
- data/test/test_helper.rb +4 -0
- data/test/workspace_test.rb +35 -0
- metadata +106 -0
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
|
19
|
+
# local concerns
|
20
|
+
.DS_Store
|
21
|
+
.rake_tasks~
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Adam Bachman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# QLab
|
2
|
+
|
3
|
+
Interact with QLab from Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'qlab-ruby'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install qlab-ruby
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
> require 'qlab-ruby'
|
22
|
+
> machine = QLab.connect # defaults to ('localhost', 53000)
|
23
|
+
> machine.workspaces.first.go
|
24
|
+
|
25
|
+
And you're off.
|
26
|
+
|
27
|
+
A `machine` has one or more `workspaces`. A `workspace` has one or more
|
28
|
+
`cue_lists`. A `cue` can respond to any of the QLab OSC commands.
|
29
|
+
|
30
|
+
> cue.start
|
31
|
+
> cue.stop
|
32
|
+
|
33
|
+
etc.
|
34
|
+
|
35
|
+
Most OSC commands that accept a single value can be called by their `=`
|
36
|
+
versions. For example:
|
37
|
+
|
38
|
+
> cue.rate(0.5)
|
39
|
+
|
40
|
+
and
|
41
|
+
|
42
|
+
> cue.rate = 0.5
|
43
|
+
|
44
|
+
do the same thing.
|
45
|
+
|
46
|
+
OSC commands that require multiple arguments will still have to be called
|
47
|
+
as methods.
|
48
|
+
|
49
|
+
> cue.sliderLevel(1, -5)
|
50
|
+
|
51
|
+
To set slider 1 to the value -5.
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
1. Fork it
|
56
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
57
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
58
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
59
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
gem 'rdoc'
|
5
|
+
require 'rdoc/task'
|
6
|
+
|
7
|
+
Rake::TestTask.new do |t|
|
8
|
+
t.libs << 'test'
|
9
|
+
t.libs << 'qlab-ruby'
|
10
|
+
t.test_files = FileList['test/*_test.rb']
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :test
|
15
|
+
|
16
|
+
RDoc::Task.new do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = "qlab-ruby #{QLab::VERSION}"
|
19
|
+
rdoc.rdoc_files.include('README*')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
data/lib/qlab-ruby.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "qlab-ruby/version"
|
2
|
+
require "qlab-ruby/core-ext/osc-ruby"
|
3
|
+
|
4
|
+
module QLab
|
5
|
+
autoload :Commands, 'qlab-ruby/commands'
|
6
|
+
autoload :Communicator, 'qlab-ruby/communicator'
|
7
|
+
autoload :Cue, 'qlab-ruby/cue'
|
8
|
+
autoload :CueList, 'qlab-ruby/cue_list'
|
9
|
+
autoload :Machine, 'qlab-ruby/machine'
|
10
|
+
autoload :Reply, 'qlab-ruby/reply'
|
11
|
+
autoload :Workspace, 'qlab-ruby/workspace'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def connect machine_address='localhost', port=53000
|
15
|
+
begin
|
16
|
+
# global QLab connection
|
17
|
+
new_machine = Machine.new(machine_address, port)
|
18
|
+
rescue Errno::ECONNREFUSED
|
19
|
+
puts "Failed to connect to QLab machine at #{machine_address}:#{port}, please make sure your values are correct and QLab is running."
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
|
23
|
+
if new_machine.connected?
|
24
|
+
new_machine.alwaysReply = 1
|
25
|
+
end
|
26
|
+
|
27
|
+
new_machine
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug msg
|
31
|
+
# puts msg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module QLab
|
2
|
+
# All commands QLab accepts
|
3
|
+
module Commands
|
4
|
+
MACHINE = %w(
|
5
|
+
alwaysReply
|
6
|
+
connect
|
7
|
+
version
|
8
|
+
workingDirectory
|
9
|
+
workspaces
|
10
|
+
)
|
11
|
+
|
12
|
+
WORKSPACE = %w(
|
13
|
+
cueLists
|
14
|
+
go
|
15
|
+
hardStop
|
16
|
+
new
|
17
|
+
panic
|
18
|
+
pause
|
19
|
+
reset
|
20
|
+
resume
|
21
|
+
runningCues
|
22
|
+
runningOrPausedCues
|
23
|
+
select
|
24
|
+
selectedCues
|
25
|
+
stop
|
26
|
+
thump
|
27
|
+
toggleFullScreen
|
28
|
+
updates
|
29
|
+
)
|
30
|
+
|
31
|
+
CUE = %w(
|
32
|
+
actionElapsed
|
33
|
+
allowsEditingDuration
|
34
|
+
armed
|
35
|
+
children
|
36
|
+
colorName
|
37
|
+
continueMode
|
38
|
+
cueTargetId
|
39
|
+
cueTargetNumber
|
40
|
+
defaultName
|
41
|
+
displayName
|
42
|
+
duration
|
43
|
+
fileTarget
|
44
|
+
flagged
|
45
|
+
hardStop
|
46
|
+
hasCueTargets
|
47
|
+
hasFileTargets
|
48
|
+
isBroken
|
49
|
+
isLoaded
|
50
|
+
isPaused
|
51
|
+
isRunning
|
52
|
+
listName
|
53
|
+
load
|
54
|
+
loadAt
|
55
|
+
name
|
56
|
+
notes
|
57
|
+
number
|
58
|
+
panic
|
59
|
+
pause
|
60
|
+
percentActionElapsed
|
61
|
+
percentPostWaitElapsed
|
62
|
+
percentPreWaitElapsed
|
63
|
+
postWait
|
64
|
+
postWaitElapsed
|
65
|
+
preWait
|
66
|
+
preWaitElapsed
|
67
|
+
preview
|
68
|
+
reset
|
69
|
+
resume
|
70
|
+
sliderLevel
|
71
|
+
sliderLevels
|
72
|
+
start
|
73
|
+
stop
|
74
|
+
togglePause
|
75
|
+
type
|
76
|
+
uniqueID
|
77
|
+
valuesForKeys
|
78
|
+
)
|
79
|
+
|
80
|
+
GROUP_CUE = %w(
|
81
|
+
playbackPositionId
|
82
|
+
)
|
83
|
+
|
84
|
+
AUDIO_CUE = %w(
|
85
|
+
doFade
|
86
|
+
doPitchShift
|
87
|
+
endTime
|
88
|
+
infiniteLoop
|
89
|
+
level
|
90
|
+
patch
|
91
|
+
playCount
|
92
|
+
rate
|
93
|
+
sliderLevel
|
94
|
+
sliderLevels
|
95
|
+
startTime
|
96
|
+
)
|
97
|
+
|
98
|
+
FADE_CUE = %w(
|
99
|
+
level
|
100
|
+
sliderLevel
|
101
|
+
sliderLevels
|
102
|
+
)
|
103
|
+
|
104
|
+
MIC_CUE = %w(
|
105
|
+
level
|
106
|
+
sliderLevel
|
107
|
+
sliderLevels
|
108
|
+
)
|
109
|
+
|
110
|
+
VIDEO_CUE = %w(
|
111
|
+
cueSize
|
112
|
+
doEffect
|
113
|
+
doFade
|
114
|
+
doPitchShift
|
115
|
+
effect
|
116
|
+
effectSet
|
117
|
+
endTime
|
118
|
+
fullScreen
|
119
|
+
infiniteLoop
|
120
|
+
layer
|
121
|
+
level
|
122
|
+
opacity
|
123
|
+
patch
|
124
|
+
playCount
|
125
|
+
preserveAspectRatio
|
126
|
+
quaternion
|
127
|
+
rate
|
128
|
+
scaleX
|
129
|
+
scaleY
|
130
|
+
sliderLevel
|
131
|
+
sliderLevels
|
132
|
+
startTime
|
133
|
+
surfaceID
|
134
|
+
surfaceList
|
135
|
+
surfaceSize
|
136
|
+
translationX
|
137
|
+
translationY
|
138
|
+
)
|
139
|
+
|
140
|
+
# in blocks of: all, group, audio, video, osc, midi, devamp, script
|
141
|
+
SET_CUES = %w(
|
142
|
+
number
|
143
|
+
name
|
144
|
+
notes
|
145
|
+
fileTarget
|
146
|
+
cueTargetNumber
|
147
|
+
cueTargetId
|
148
|
+
preWait
|
149
|
+
duration
|
150
|
+
postWait
|
151
|
+
continueMode
|
152
|
+
flagged
|
153
|
+
autoLoad
|
154
|
+
armed
|
155
|
+
colorName
|
156
|
+
|
157
|
+
playbackPositionId
|
158
|
+
|
159
|
+
patch
|
160
|
+
startTime
|
161
|
+
endTime
|
162
|
+
playCount
|
163
|
+
infiniteLoop
|
164
|
+
rate
|
165
|
+
doPitchShift
|
166
|
+
doFade
|
167
|
+
|
168
|
+
surfaceID
|
169
|
+
patch
|
170
|
+
fullScreen
|
171
|
+
layer
|
172
|
+
opacity
|
173
|
+
preserveAspectRatio
|
174
|
+
translationX
|
175
|
+
translationY
|
176
|
+
scaleX
|
177
|
+
scaleY
|
178
|
+
doEffect
|
179
|
+
effectSet
|
180
|
+
|
181
|
+
messageType
|
182
|
+
qlabCommand
|
183
|
+
qlabCueNumber
|
184
|
+
qlabCueParameters
|
185
|
+
rawString
|
186
|
+
|
187
|
+
duration
|
188
|
+
messageType
|
189
|
+
status
|
190
|
+
channel
|
191
|
+
byte1
|
192
|
+
byte2
|
193
|
+
byteCombo
|
194
|
+
doFade
|
195
|
+
endValue
|
196
|
+
deviceID
|
197
|
+
commandFormat
|
198
|
+
command
|
199
|
+
qNumber
|
200
|
+
qList
|
201
|
+
qPath
|
202
|
+
macro
|
203
|
+
controlNumber
|
204
|
+
controlValue
|
205
|
+
timecodeString
|
206
|
+
hours
|
207
|
+
minutes
|
208
|
+
seconds
|
209
|
+
frames
|
210
|
+
subframes
|
211
|
+
timecodeFormat
|
212
|
+
rawString
|
213
|
+
|
214
|
+
startNextCueWhenSliceEnds
|
215
|
+
stopTargetWhenSliceEnds
|
216
|
+
|
217
|
+
scriptSource
|
218
|
+
)
|
219
|
+
|
220
|
+
ALL_CUES = (CUE + GROUP_CUE + AUDIO_CUE + FADE_CUE + MIC_CUE + VIDEO_CUE).uniq.sort
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module QLab
|
2
|
+
# An abstract class providing communication behavior for objects that need to
|
3
|
+
# interact with QLab.
|
4
|
+
class Communicator
|
5
|
+
|
6
|
+
def send_message osc_address, *osc_arguments
|
7
|
+
osc_address = format_osc_address(osc_address)
|
8
|
+
|
9
|
+
if osc_arguments.size > 0
|
10
|
+
osc_message = OSC::Message.new osc_address, *osc_arguments
|
11
|
+
else
|
12
|
+
osc_message = OSC::Message.new osc_address
|
13
|
+
end
|
14
|
+
responses = []
|
15
|
+
|
16
|
+
QLab.debug "[Action send_message] send #{ osc_message.encode }"
|
17
|
+
connection.send(osc_message) do |response|
|
18
|
+
responses << QLab::Reply.new(response)
|
19
|
+
end
|
20
|
+
|
21
|
+
if responses.size == 1
|
22
|
+
q_reply = responses[0]
|
23
|
+
QLab.debug "[Action send_message] got one response: #{q_reply.inspect}"
|
24
|
+
|
25
|
+
if q_reply.has_data?
|
26
|
+
QLab.debug "[Action send_message] single response has data: #{q_reply.data.inspect}"
|
27
|
+
q_reply.data
|
28
|
+
elsif q_reply.has_status?
|
29
|
+
QLab.debug "[Action send_message] single response has status: #{q_reply.status.inspect}"
|
30
|
+
q_reply.status
|
31
|
+
else
|
32
|
+
QLab.debug "[Action send_message] single response has no data or status: #{q_reply.inspect}"
|
33
|
+
q_reply
|
34
|
+
end
|
35
|
+
else
|
36
|
+
responses
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def format_osc_address address
|
43
|
+
if %r[^/] !~ address.to_s
|
44
|
+
"/#{ address }"
|
45
|
+
else
|
46
|
+
address
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module OSC
|
2
|
+
# Reopen the osc-ruby Message class to provide additional methods to support
|
3
|
+
# QLab's use of OSC.
|
4
|
+
class Message
|
5
|
+
def has_arguments?
|
6
|
+
to_a.size > 0
|
7
|
+
end
|
8
|
+
|
9
|
+
# attachable responder, for use with TCP::Server
|
10
|
+
def responder
|
11
|
+
@responder
|
12
|
+
end
|
13
|
+
|
14
|
+
def responder=(val)
|
15
|
+
@responder = val
|
16
|
+
end
|
17
|
+
|
18
|
+
def debug
|
19
|
+
types = to_a.map(&:class).map(&:to_s).join(', ')
|
20
|
+
args = to_a
|
21
|
+
|
22
|
+
"#{ip_address}:#{ip_port} -- #{address} -- [#{ types }] -- #{ args.inspect }"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module OSC
|
2
|
+
module TCP
|
3
|
+
# A wrapper around an open TCP socket providing SLIP encoding for outbound
|
4
|
+
# messages.
|
5
|
+
class SendingSocket
|
6
|
+
def initialize socket
|
7
|
+
@socket = socket
|
8
|
+
end
|
9
|
+
|
10
|
+
# send SLIP encoded OSC messages
|
11
|
+
def send msg
|
12
|
+
@socket_buffer = []
|
13
|
+
|
14
|
+
enc_msg = msg.encode
|
15
|
+
|
16
|
+
send_char CHAR_END
|
17
|
+
|
18
|
+
enc_msg.bytes.each do |b|
|
19
|
+
case b
|
20
|
+
when CHAR_END
|
21
|
+
send_char CHAR_ESC
|
22
|
+
send_char CHAR_ESC_END
|
23
|
+
when CHAR_ESC
|
24
|
+
send_char CHAR_ESC
|
25
|
+
send_char CHAR_ESC_ESC
|
26
|
+
else
|
27
|
+
send_char b
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
send_char CHAR_END
|
32
|
+
|
33
|
+
flush
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def flush
|
39
|
+
@socket.send @socket_buffer.join, 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def send_char c
|
43
|
+
@socket_buffer << [c].pack('C')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module OSC
|
2
|
+
# SLIP encoding constants.
|
3
|
+
module TCP
|
4
|
+
CHAR_END = 0300 # indicates end of packet
|
5
|
+
CHAR_ESC = 0333 # indicates byte stuffing
|
6
|
+
CHAR_ESC_END = 0334 # ESC ESC_END means END data byte
|
7
|
+
CHAR_ESC_ESC = 0335 # ESC ESC_ESC means ESC data byte
|
8
|
+
|
9
|
+
CHAR_END_ENC = [0300].pack('C') # indicates end of packet
|
10
|
+
CHAR_ESC_ENC = [0333].pack('C') # indicates byte stuffing
|
11
|
+
CHAR_ESC_END_ENC = [0334].pack('C') # ESC ESC_END means END data byte
|
12
|
+
CHAR_ESC_ESC_ENC = [0335].pack('C') # ESC ESC_ESC means ESC data byte
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# A responsive OSC TCP client that sends and receives OSC messages on a single socket using the SLIP protocol.
|
2
|
+
#
|
3
|
+
# http://www.ietf.org/rfc/rfc1055.txt
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
module OSC
|
7
|
+
module TCP
|
8
|
+
class Client
|
9
|
+
def initialize host, port, handler=nil
|
10
|
+
@host = host
|
11
|
+
@port = port
|
12
|
+
@handler = handler
|
13
|
+
@socket = TCPSocket.new host, port
|
14
|
+
@sending_socket = OSC::TCP::SendingSocket.new @socket
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
@socket.close unless closed?
|
19
|
+
end
|
20
|
+
|
21
|
+
def closed?
|
22
|
+
@socket.closed?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Send an OSC::Message and handle the response if one is given.
|
26
|
+
def send msg
|
27
|
+
@sending_socket.send(msg)
|
28
|
+
|
29
|
+
if block_given? || @handler
|
30
|
+
messages = response
|
31
|
+
if !messages.nil?
|
32
|
+
messages.each do |message|
|
33
|
+
if block_given?
|
34
|
+
yield message
|
35
|
+
else
|
36
|
+
@handler.handle message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def response
|
44
|
+
if received_messages = receive_raw
|
45
|
+
received_messages.map do |message|
|
46
|
+
OSCPacket.messages_from_network(message)
|
47
|
+
end.flatten
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
"#<OSC::TCP::Client:#{ object_id } @host:#{ @host.inspect }, @port:#{ @port.inspect }, @handler:#{ @handler.to_s }>"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def receive_raw
|
60
|
+
received = 0
|
61
|
+
messages = []
|
62
|
+
buffer = []
|
63
|
+
failed = false
|
64
|
+
received_any = false
|
65
|
+
|
66
|
+
loop do
|
67
|
+
begin
|
68
|
+
# get a character from the socket, fail if nothing is available
|
69
|
+
c = @socket.recv_nonblock(1)
|
70
|
+
|
71
|
+
received_any = true
|
72
|
+
|
73
|
+
case c
|
74
|
+
when CHAR_END_ENC
|
75
|
+
if received > 0
|
76
|
+
# add SLIP encoded message to list
|
77
|
+
messages << buffer.join
|
78
|
+
|
79
|
+
# reset state and keep reading from the port until there's nothing left
|
80
|
+
buffer.clear
|
81
|
+
received = 0
|
82
|
+
failed = false
|
83
|
+
end
|
84
|
+
when CHAR_ESC_ENC
|
85
|
+
# get next character, blocking is okay
|
86
|
+
c = @socket.recv(1)
|
87
|
+
case c
|
88
|
+
when CHAR_ESC_END_ENC
|
89
|
+
c = CHAR_END_ENC
|
90
|
+
when CHAR_ESC_ESC_ENC
|
91
|
+
c = CHAR_ESC_ENC
|
92
|
+
else
|
93
|
+
received += 1
|
94
|
+
buffer << c
|
95
|
+
end
|
96
|
+
else
|
97
|
+
received += 1
|
98
|
+
buffer << c
|
99
|
+
end
|
100
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
101
|
+
# If any messages have been received, assume sender is done sending.
|
102
|
+
if failed || received_any
|
103
|
+
break
|
104
|
+
end
|
105
|
+
|
106
|
+
# wait one second to see if the socket might become readable (and a
|
107
|
+
# response forthcoming). normal usage is send + receive response, but
|
108
|
+
# if app doesn't intend to respond we should eventually ignore it.
|
109
|
+
|
110
|
+
IO.select([@socket], [], [], 0.2)
|
111
|
+
failed = true
|
112
|
+
retry
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if messages.size > 0
|
117
|
+
messages
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module QLab
|
2
|
+
class Cue < Communicator
|
3
|
+
attr_accessor :data
|
4
|
+
|
5
|
+
# Load a cue with the attributes given in `data`
|
6
|
+
def initialize data, cue_list
|
7
|
+
self.data = data
|
8
|
+
@cue_list = cue_list
|
9
|
+
end
|
10
|
+
|
11
|
+
# All cue commands for all cue types as listed in Figure53 QLab OSC Reference
|
12
|
+
Commands::ALL_CUES.each do |command|
|
13
|
+
define_method(command.to_sym) do |*args|
|
14
|
+
if args.size > 0
|
15
|
+
send_message cue_command(command), *args
|
16
|
+
else
|
17
|
+
send_message cue_command(command)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Define commands with single settable values as command= methods
|
23
|
+
# for convenience. All command-as-method commands will act as setters
|
24
|
+
# if given an argument.
|
25
|
+
#
|
26
|
+
# For exmaple:
|
27
|
+
#
|
28
|
+
# cue.name = "A Name"
|
29
|
+
#
|
30
|
+
# is functionally equivalent to
|
31
|
+
#
|
32
|
+
# cue.name("A Name")
|
33
|
+
#
|
34
|
+
# as far as QLab is concerned
|
35
|
+
#
|
36
|
+
Commands::SET_CUES.each do |command|
|
37
|
+
define_method("#{ command }=".to_sym) do |value|
|
38
|
+
send_message cue_command(command), value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# The cue's `uniqueID`.
|
43
|
+
def id
|
44
|
+
data['uniqueID']
|
45
|
+
end
|
46
|
+
|
47
|
+
# Check whether this cue has nested cues.
|
48
|
+
def has_cues?
|
49
|
+
cues.size > 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get the list of nested cues.
|
53
|
+
def cues
|
54
|
+
if data['cues'].nil?
|
55
|
+
[]
|
56
|
+
else
|
57
|
+
data['cues'].map {|c| Cue.new(c, @cue_list)}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Compare with another Cue.
|
62
|
+
def ==(other)
|
63
|
+
if other.is_a?(Cue)
|
64
|
+
self.data = other.data
|
65
|
+
else
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# A reference to the cue's workspace.
|
71
|
+
def workspace
|
72
|
+
@cue_list.workspace
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def connection
|
78
|
+
workspace.connection
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def cue_command command
|
84
|
+
"/workspace/#{ workspace.id }/cue_id/#{ id }/#{ command }"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module QLab
|
2
|
+
# An array of cue objects:
|
3
|
+
#
|
4
|
+
# [
|
5
|
+
# {
|
6
|
+
# "uniqueID": string,
|
7
|
+
# "number": string
|
8
|
+
# "name": string
|
9
|
+
# "type": string
|
10
|
+
# "colorName": string
|
11
|
+
# "flagged": number
|
12
|
+
# "armed": number
|
13
|
+
# }
|
14
|
+
# ]
|
15
|
+
#
|
16
|
+
# If a given cue is a group, it will include the nested cues:
|
17
|
+
#
|
18
|
+
# [
|
19
|
+
# {
|
20
|
+
# "uniqueID": string,
|
21
|
+
# "number": string
|
22
|
+
# "name": string
|
23
|
+
# "type": string
|
24
|
+
# "colorName": string
|
25
|
+
# "flagged": number
|
26
|
+
# "armed": number
|
27
|
+
# "cues": [ { }, { }, { } ]
|
28
|
+
# }
|
29
|
+
# ]
|
30
|
+
|
31
|
+
class CueList
|
32
|
+
attr_accessor :data
|
33
|
+
|
34
|
+
# Load a cue list with the attributes given in `data`
|
35
|
+
def initialize data, workspace
|
36
|
+
self.data = data
|
37
|
+
@workspace = workspace
|
38
|
+
end
|
39
|
+
|
40
|
+
def workspace
|
41
|
+
@workspace
|
42
|
+
end
|
43
|
+
|
44
|
+
def id
|
45
|
+
data['uniqueID']
|
46
|
+
end
|
47
|
+
|
48
|
+
def name
|
49
|
+
data['listName']
|
50
|
+
end
|
51
|
+
|
52
|
+
def number
|
53
|
+
data['number']
|
54
|
+
end
|
55
|
+
|
56
|
+
def type
|
57
|
+
data['type']
|
58
|
+
end
|
59
|
+
|
60
|
+
def cues
|
61
|
+
if data['cues'].nil?
|
62
|
+
[]
|
63
|
+
else
|
64
|
+
data['cues'].map {|c| QLab::Cue.new(c, self)}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def has_cues?
|
69
|
+
cues.size > 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module QLab
|
2
|
+
class Machine < Communicator
|
3
|
+
attr_accessor :name, :address, :port
|
4
|
+
|
5
|
+
# Connect to a running QLab instance. `address` can be a domain name or an
|
6
|
+
# IP Address.
|
7
|
+
def initialize(_address, _port)
|
8
|
+
self.name = _address
|
9
|
+
self.address = _address
|
10
|
+
self.port = _port
|
11
|
+
connect
|
12
|
+
end
|
13
|
+
|
14
|
+
# auto-generate methods first so that manually defined methods will
|
15
|
+
# override
|
16
|
+
Commands::MACHINE.each do |command|
|
17
|
+
define_method(command.to_sym) do |*args|
|
18
|
+
if args.size > 0
|
19
|
+
send_message command, *args
|
20
|
+
else
|
21
|
+
send_message command
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Open and return a connection to the running QLab instance
|
27
|
+
def connect
|
28
|
+
if !connected?
|
29
|
+
@connection = OSC::TCP::Client.new(@address, @port)
|
30
|
+
else
|
31
|
+
connection
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Reference to the running QLab instance
|
36
|
+
def connection
|
37
|
+
@connection || connect
|
38
|
+
end
|
39
|
+
|
40
|
+
# The workspaces provided by the connected QLab instance
|
41
|
+
def workspaces
|
42
|
+
@workspaces || load_workspaces
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Find a workspace according to the given params.
|
47
|
+
def find_workspace params={}
|
48
|
+
workspaces.find do |ws|
|
49
|
+
matches = true
|
50
|
+
|
51
|
+
# match each key to the given workspace
|
52
|
+
params.keys.each do |key|
|
53
|
+
matches = matches && (ws.send(key.to_sym) == params[key])
|
54
|
+
end
|
55
|
+
|
56
|
+
matches
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def connected?
|
61
|
+
!(@connection.nil? || send_message('/version').nil?)
|
62
|
+
end
|
63
|
+
|
64
|
+
def close
|
65
|
+
@connection.close
|
66
|
+
@connection = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def refresh
|
70
|
+
close
|
71
|
+
connect
|
72
|
+
load_workspaces
|
73
|
+
end
|
74
|
+
|
75
|
+
def alwaysReply=(value)
|
76
|
+
# send set command
|
77
|
+
alwaysReply(value)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def load_workspaces
|
83
|
+
@workspaces = []
|
84
|
+
|
85
|
+
data = send_message('/workspaces')
|
86
|
+
|
87
|
+
data.map do |ws|
|
88
|
+
@workspaces << QLab::Workspace.new(ws, self)
|
89
|
+
end
|
90
|
+
|
91
|
+
@workspaces
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module QLab
|
4
|
+
# QLab OSC reply unpacker.
|
5
|
+
class Reply < Struct.new(:osc_message)
|
6
|
+
def address
|
7
|
+
@address ||= json['address']
|
8
|
+
end
|
9
|
+
|
10
|
+
def data
|
11
|
+
@data ||= json['data']
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
@status ||= json['status']
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_data?
|
19
|
+
!data.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_status?
|
23
|
+
!status.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def ok?
|
27
|
+
status == 'ok'
|
28
|
+
end
|
29
|
+
|
30
|
+
def empty?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"<QLab::Reply address:'#{address}' status:'#{status}' data:#{data.inspect}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# Actually perform the message unpacking
|
41
|
+
def json
|
42
|
+
@json ||= begin
|
43
|
+
JSON.parse(osc_message.to_a.first)
|
44
|
+
rescue => ex
|
45
|
+
puts ex.message
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module QLab
|
2
|
+
#
|
3
|
+
# "uniqueID": string,
|
4
|
+
# "displayName": string
|
5
|
+
# "hasPasscode": number
|
6
|
+
#
|
7
|
+
class Workspace < Communicator
|
8
|
+
attr_accessor :data, :passcode
|
9
|
+
|
10
|
+
# Load a workspace with the attributes given in `data`
|
11
|
+
def initialize data, machine
|
12
|
+
@machine = machine
|
13
|
+
self.data = data
|
14
|
+
end
|
15
|
+
|
16
|
+
Commands::WORKSPACE.each do |command|
|
17
|
+
define_method(command.to_sym) do |*args|
|
18
|
+
if args.size > 0
|
19
|
+
send_message workspace_command(command), *args
|
20
|
+
else
|
21
|
+
send_message workspace_command(command)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def connection
|
27
|
+
@machine.connection
|
28
|
+
end
|
29
|
+
|
30
|
+
def name
|
31
|
+
data['displayName']
|
32
|
+
end
|
33
|
+
|
34
|
+
def passcode?
|
35
|
+
!!data['hasPasscode']
|
36
|
+
end
|
37
|
+
|
38
|
+
def id
|
39
|
+
data['uniqueID']
|
40
|
+
end
|
41
|
+
|
42
|
+
# all cues in this workspace in a flat list
|
43
|
+
def cues
|
44
|
+
cue_lists.map do |cl|
|
45
|
+
load_cues(cl, [])
|
46
|
+
end.flatten.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_cues?
|
50
|
+
cues.size > 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def cue_lists
|
54
|
+
refresh
|
55
|
+
@cue_lists
|
56
|
+
end
|
57
|
+
|
58
|
+
def refresh
|
59
|
+
if passcode?
|
60
|
+
if passcode.nil?
|
61
|
+
raise WorkspaceConnectionError.new("Workspace '#{ name }' requires a passcode.")
|
62
|
+
end
|
63
|
+
|
64
|
+
args = [workspace_command('connect'), ('%04i' % passcode)]
|
65
|
+
else
|
66
|
+
args = [workspace_command('connect')]
|
67
|
+
end
|
68
|
+
connect_response = send_message(*args)
|
69
|
+
|
70
|
+
if connect_response == 'ok'
|
71
|
+
@cue_lists = []
|
72
|
+
|
73
|
+
cues_response = send_message workspace_command('cueLists')
|
74
|
+
cues_response.each do |cuelist|
|
75
|
+
@cue_lists << QLab::CueList.new(cuelist, self)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_cue params={}
|
81
|
+
cues.find do |cue|
|
82
|
+
matches = true
|
83
|
+
|
84
|
+
# match each key to the given cue
|
85
|
+
params.keys.each do |key|
|
86
|
+
matches = matches && (cue.send(key.to_sym) == params[key])
|
87
|
+
end
|
88
|
+
|
89
|
+
matches
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def workspace_command command
|
96
|
+
"/workspace/#{id}/#{ command }"
|
97
|
+
end
|
98
|
+
|
99
|
+
def load_cues parent_cue, cues
|
100
|
+
parent_cue.cues.each {|child_cue|
|
101
|
+
cues << child_cue
|
102
|
+
load_cues child_cue, cues
|
103
|
+
}
|
104
|
+
|
105
|
+
cues
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class WorkspaceConnectionError < Exception; end
|
110
|
+
end
|
data/qlab-ruby.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'qlab-ruby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "qlab-ruby"
|
8
|
+
gem.version = QLab::VERSION
|
9
|
+
gem.authors = ["Adam Bachman"]
|
10
|
+
gem.email = ["adam@figure53.com"]
|
11
|
+
gem.description = %q{Interact with QLab in Ruby.}
|
12
|
+
gem.summary = %q{Interact with QLab in Ruby.}
|
13
|
+
gem.homepage = "https://github.com/Figure53/qlab-ruby"
|
14
|
+
gem.license = 'MIT'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_runtime_dependency 'osc-ruby'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'rdoc'
|
24
|
+
end
|
data/test/cue_test.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CueTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@machine = QLab.connect 'localhost', 53000
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@machine.close
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_a_cue_is_reachable
|
13
|
+
workspace = @machine.workspaces.first
|
14
|
+
|
15
|
+
refute_nil workspace.cue_lists
|
16
|
+
refute_nil workspace.cue_lists.first
|
17
|
+
refute_nil workspace.cue_lists.first.cues
|
18
|
+
refute_nil workspace.cue_lists.first.cues.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_a_cue_has_a_name
|
22
|
+
workspace = @machine.workspaces.first
|
23
|
+
cue = workspace.cue_lists.first.cues.first
|
24
|
+
assert cue.name
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_changes_are_picked_up_immediately
|
28
|
+
original_name = "Name 1"
|
29
|
+
new_name = "Name 2"
|
30
|
+
|
31
|
+
workspace = @machine.workspaces.first
|
32
|
+
cue = workspace.cue_lists.first.cues.first
|
33
|
+
|
34
|
+
cue.name = original_name
|
35
|
+
assert_equal original_name, cue.name
|
36
|
+
|
37
|
+
cue.name = new_name
|
38
|
+
assert_equal new_name, cue.name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MachineTest < Minitest::Test
|
4
|
+
def test_can_connect
|
5
|
+
machine = QLab.connect 'localhost', 53000
|
6
|
+
assert machine.connected?
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_can_load_workspaces
|
10
|
+
machine = QLab.connect 'localhost', 53000
|
11
|
+
assert machine.workspaces.size > 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_can_find_workspaces
|
15
|
+
machine = QLab.connect 'localhost', 53000
|
16
|
+
assert machine.workspaces.size > 0
|
17
|
+
|
18
|
+
ws = machine.workspaces.first
|
19
|
+
|
20
|
+
assert_equal ws, machine.find_workspace(id: ws.id)
|
21
|
+
assert_equal ws, machine.find_workspace(name: ws.name)
|
22
|
+
|
23
|
+
refute machine.find_workspace(id: 'nonsense')
|
24
|
+
refute machine.find_workspace(name: 'nonsense')
|
25
|
+
end
|
26
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class WorkspaceTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@machine = QLab.connect 'localhost', 53000
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@machine.close
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_refresh_loads_cues
|
13
|
+
workspace = @machine.workspaces.first
|
14
|
+
|
15
|
+
refute_nil workspace.connection
|
16
|
+
refute_nil workspace.name
|
17
|
+
refute_nil workspace.id
|
18
|
+
|
19
|
+
refute_nil workspace.cues
|
20
|
+
assert workspace.cues.size > 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_finds_cues
|
24
|
+
workspace = @machine.workspaces.first
|
25
|
+
cue = workspace.cues.first
|
26
|
+
|
27
|
+
assert_equal cue, workspace.find_cue(id: cue.id)
|
28
|
+
assert_equal cue, workspace.find_cue(number: cue.number)
|
29
|
+
assert_equal cue, workspace.find_cue(name: cue.name)
|
30
|
+
|
31
|
+
refute workspace.find_cue(id: 'asdf asdf asdf asdf asdf')
|
32
|
+
refute workspace.find_cue(number: 'asdf asdf asdf asdf asdf')
|
33
|
+
refute workspace.find_cue(name: 'asdf asdf asdf asdf asdf')
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: qlab-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Bachman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: osc-ruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rdoc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Interact with QLab in Ruby.
|
47
|
+
email:
|
48
|
+
- adam@figure53.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/qlab-ruby.rb
|
59
|
+
- lib/qlab-ruby/commands.rb
|
60
|
+
- lib/qlab-ruby/communicator.rb
|
61
|
+
- lib/qlab-ruby/core-ext/osc-ruby.rb
|
62
|
+
- lib/qlab-ruby/core-ext/osc-ruby/message.rb
|
63
|
+
- lib/qlab-ruby/core-ext/osc-ruby/sending_socket.rb
|
64
|
+
- lib/qlab-ruby/core-ext/osc-ruby/tcp.rb
|
65
|
+
- lib/qlab-ruby/core-ext/osc-ruby/tcp_client.rb
|
66
|
+
- lib/qlab-ruby/cue.rb
|
67
|
+
- lib/qlab-ruby/cue_list.rb
|
68
|
+
- lib/qlab-ruby/machine.rb
|
69
|
+
- lib/qlab-ruby/reply.rb
|
70
|
+
- lib/qlab-ruby/version.rb
|
71
|
+
- lib/qlab-ruby/workspace.rb
|
72
|
+
- qlab-ruby.gemspec
|
73
|
+
- test/cue_test.rb
|
74
|
+
- test/machine_test.rb
|
75
|
+
- test/test_helper.rb
|
76
|
+
- test/workspace_test.rb
|
77
|
+
homepage: https://github.com/Figure53/qlab-ruby
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.25
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Interact with QLab in Ruby.
|
102
|
+
test_files:
|
103
|
+
- test/cue_test.rb
|
104
|
+
- test/machine_test.rb
|
105
|
+
- test/test_helper.rb
|
106
|
+
- test/workspace_test.rb
|