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