PageRenderTime 45ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/scruby/server.rb

http://github.com/maca/scruby
Ruby | 285 lines | 207 code | 64 blank | 14 comment | 9 complexity | 38e0338c010fd6e325f096d62a7ffb50 MD5 | raw file
  1. require "ruby-osc"
  2. require "concurrent-edge"
  3. require "forwardable"
  4. module Scruby
  5. class Server
  6. class ServerError < StandardError; end
  7. class ClientInfo < Struct.new(:id, :max_logins); end
  8. extend Forwardable
  9. include OSC
  10. include Sclang::Helpers
  11. include PrettyInspectable
  12. include Encode
  13. include Concurrent
  14. attr_reader :osc_client, :message_queue, :process, :options,
  15. :client_info
  16. private :message_queue, :process
  17. def_delegators :options, :host, :port, :num_buffers
  18. def initialize(host: "127.0.0.1", port: 57_110, **options)
  19. @osc_client = OSC::Client.new(port, host)
  20. @message_queue = MessageQueue.new(self)
  21. @options = Options.new(**options, bind_address: host, port: port)
  22. @nodes = Nodes.new(self)
  23. end
  24. def alive?
  25. process_alive?
  26. end
  27. alias running? alive?
  28. def client_id
  29. client_info&.id
  30. end
  31. def max_logins
  32. client_info&.max_logins
  33. end
  34. def nodes
  35. @nodes.any? ? @nodes : update_nodes.value!
  36. end
  37. def node(idx)
  38. nodes[idx]
  39. end
  40. def add_node(node)
  41. nodes.add(node)
  42. end
  43. def next_node_id
  44. node_counter.increment
  45. end
  46. def next_buffer_id
  47. buffer_counter.increment
  48. end
  49. def boot(binary: "scsynth", **opts)
  50. boot_async(binary: binary, **opts).value!
  51. end
  52. def boot_async(binary: "scsynth", **opts)
  53. return sync.then { self } if process_alive?
  54. @options = Options.new(**options, **opts)
  55. @num_buffers = options.num_buffers
  56. @process = Process.spawn(binary, options.flags, env: options.env)
  57. wait_for_booted
  58. .then_flat_future { message_queue.sync }
  59. .then_flat_future { register_client }
  60. .then { create_root_group }
  61. .then { self }
  62. .on_rejection { process.kill }
  63. end
  64. def quit
  65. quit_async.value!
  66. end
  67. alias stop quit
  68. def quit_async
  69. # return Promises.fulfilled_future(self) unless alive?
  70. send_msg("/quit")
  71. receive { |msg| msg.to_a == %w(/done /quit) }
  72. .then { sleep 0.1 while process_alive? }
  73. .then { message_queue.flush }
  74. .then { @client_info = nil }
  75. .then { self }
  76. end
  77. alias stop_async quit_async
  78. def reboot
  79. reboot_async.value!
  80. end
  81. def reboot_async
  82. quit_async.then_flat_future { boot_async }
  83. end
  84. def free_all
  85. send_msg("/g_freeAll", 0)
  86. send_msg("/clearSched")
  87. create_root_group
  88. end
  89. # def mute
  90. # forward_async :mute
  91. # end
  92. # def unmute
  93. # forward_async :unmute
  94. # end
  95. def status
  96. status_async.value!
  97. end
  98. def status_async
  99. keys = %i(ugens synths groups synth_defs avg_cpu peak_cpu
  100. sample_rate actual_sample_rate)
  101. send_msg Message.new("/status")
  102. receive("/status.reply", timeout: 0.2)
  103. .then { |msg| keys.zip(msg.args[1..-1]).to_h }
  104. end
  105. # Sends an OSC command or +Message+ to the scsyth server.
  106. # E.g. +server.send('/dumpOSC', 1)+
  107. def send_msg(message, *args)
  108. # Fix in ruby osc gem
  109. args = args.map { |a|
  110. case a
  111. when true then 1
  112. when false then 0
  113. else
  114. a
  115. end
  116. }
  117. case message
  118. when Message, Bundle
  119. osc_client.send(message)
  120. else
  121. osc_client.send Message.new(message, *args)
  122. end
  123. self
  124. end
  125. def send_bundle(*messages, timestamp: nil)
  126. bundle = messages.map do |msg|
  127. msg.is_a?(Message) ? msg : Message.new(*msg)
  128. end
  129. send_msg Bundle.new(timestamp, *bundle)
  130. end
  131. # Encodes and sends a synth graph to the scsynth server
  132. def send_graph(graph, completion_message = nil)
  133. blob = Blob.new(graph.encode)
  134. on_completion = graph_completion_blob(completion_message)
  135. send_bundle Message.new("/d_recv", blob, on_completion)
  136. end
  137. def inspect
  138. super(host: host, port: port, running: running?)
  139. end
  140. def receive(address = nil, **args, &pred)
  141. message_queue.receive(address, **args, &pred)
  142. end
  143. def dump_server_messages(status = nil)
  144. return !!message_queue.debug if status.nil?
  145. message_queue.debug = !!status
  146. end
  147. def dump_osc(code = 1)
  148. send_msg("/dumpOSC", code)
  149. end
  150. def socket
  151. osc_client.instance_variable_get(:@socket)
  152. end
  153. private
  154. def process_alive?
  155. process&.alive? || false
  156. end
  157. def sync
  158. message_queue.sync
  159. end
  160. def update_nodes
  161. send_msg("/g_queryTree", 0, 1)
  162. receive("/g_queryTree.reply")
  163. .then { |msg| @nodes.decode_and_update(msg.args) }
  164. end
  165. def nodes_changed
  166. nodes.clear
  167. listen_node_changes
  168. end
  169. def listen_node_changes
  170. receive(Regexp.union(*ServerNode::MESSAGES), timeout: nil)
  171. .then_flat_future { nodes_changed }
  172. receive("/fail", timeout: nil) { |m|
  173. ServerNode::MESSAGES.include?(m.args.first)
  174. }.then_flat_future { nodes_changed }
  175. end
  176. def node_counter
  177. # client id is 5 bits and node id is 26 bits long
  178. # TODO: no specs
  179. @node_counter ||= AtomicFixnum.new((client_id << 26) + 1)
  180. end
  181. def buffer_counter
  182. # TODO: no specs
  183. @buffer_counter ||=
  184. AtomicFixnum.new((num_buffers / max_logins) * client_id)
  185. end
  186. def create_root_group
  187. send_msg("/g_new", 1, 0, 0)
  188. end
  189. def register_client
  190. send_msg("/notify", 1, 0)
  191. receive("/done")
  192. .then { |m| m.args.slice(1, 2) }
  193. .then { |a| a.each { |v| raise ServerError, v if String === v } }
  194. .then { |args| @client_info = ClientInfo.new(*args) }
  195. .then { listen_node_changes }
  196. end
  197. def graph_completion_blob(message)
  198. return message || 0 unless message.is_a? Message
  199. Blob.new(message.encode)
  200. end
  201. def wait_for_booted
  202. Promises.future(Cancellation.timeout(5)) do |cancel|
  203. loop do
  204. cancel.check!
  205. line = process.read
  206. if /Address already in use/ === line
  207. raise ServerError, "could not open port"
  208. end
  209. break cancel if /server ready/ === line
  210. end
  211. cancel
  212. end
  213. end
  214. class << self
  215. attr_writer :default
  216. def boot(**args)
  217. new(**args).boot
  218. end
  219. end
  220. end
  221. end