180 lines
5.0 KiB
Ruby
180 lines
5.0 KiB
Ruby
# This is a native implementation of Ecasound's control interface for Ruby.
|
|
# Copyright (C) 2003 - 2004 Jan Weil <jan.weil@web.de>
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
# ---------------------------------------------------------------------------
|
|
=begin
|
|
= ruby-ecasound
|
|
|
|
Example:
|
|
|
|
require "ecasound"
|
|
eci = Ecasound::ControlInterface.new(ecasound_args)
|
|
ecasound-response = eci.command("iam-command-here")
|
|
...
|
|
|
|
TODO:
|
|
Is there a chance that the ecasound process gets zombified?
|
|
|
|
=end
|
|
|
|
require "timeout"
|
|
require "thread"
|
|
|
|
class File
|
|
def self::which(prog, path=ENV['PATH'])
|
|
path.split(File::PATH_SEPARATOR).each do |dir|
|
|
f = File::join(dir, prog)
|
|
if File::executable?(f) && ! File::directory?(f)
|
|
return f
|
|
end
|
|
end
|
|
end
|
|
end # File
|
|
|
|
class VersionString < String
|
|
attr_reader :numbers
|
|
|
|
def initialize(str)
|
|
if str.split(".").length() != 3
|
|
raise("Versioning scheme must be major.minor.micro")
|
|
end
|
|
super(str)
|
|
@numbers = []
|
|
str.split(".").each {|s| @numbers.push(s.to_i())}
|
|
end
|
|
|
|
def <=>(other)
|
|
numbers.each_index do |i|
|
|
if numbers[i] < other.numbers[i]
|
|
return -1
|
|
elsif numbers[i] > other.numbers[i]
|
|
return 1
|
|
elsif i < 2
|
|
next
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
end # VersionString
|
|
|
|
module Ecasound
|
|
|
|
REQUIRED_VERSION = VersionString.new("2.2.0")
|
|
TIMEOUT = 15 # seconds before sync is called 'lost'
|
|
|
|
class EcasoundError < RuntimeError; end
|
|
class EcasoundCommandError < EcasoundError
|
|
attr_accessor :command, :error
|
|
def initialize(command, error)
|
|
@command = command
|
|
@error = error
|
|
end
|
|
end
|
|
|
|
class ControlInterface
|
|
@@ecasound = ENV['ECASOUND'] || File::which("ecasound")
|
|
|
|
if not File::executable?(@@ecasound.to_s)
|
|
raise("ecasound executable not found")
|
|
else
|
|
@@version = VersionString.new(`#{@@ecasound} --version`.split("\n")[0][/\d\.\d\.\d/])
|
|
if @@version < REQUIRED_VERSION
|
|
raise("ecasound version #{REQUIRED_VERSION} or newer required, found: #{@@version}")
|
|
end
|
|
end
|
|
|
|
def initialize(args = nil)
|
|
@mutex = Mutex.new()
|
|
@ecapipe = IO.popen("-", "r+") # fork!
|
|
|
|
if @ecapipe.nil?
|
|
# child
|
|
$stderr.reopen(open("/dev/null", "w"))
|
|
exec("#{@@ecasound} #{args.to_s} -c -D -d:256 ")
|
|
else
|
|
@ecapipe.sync = true
|
|
# parent
|
|
command("int-output-mode-wellformed")
|
|
end
|
|
end
|
|
|
|
def cleanup()
|
|
@ecapipe.close()
|
|
end
|
|
|
|
def command(cmd)
|
|
@mutex.synchronize do
|
|
cmd.strip!()
|
|
#puts "command: #{cmd}"
|
|
|
|
@ecapipe.write(cmd + "\n")
|
|
|
|
# ugly hack but the process gets stuck otherwise -kvehmanen
|
|
if cmd == "quit"
|
|
return nil
|
|
end
|
|
|
|
response = ""
|
|
begin
|
|
# TimeoutError is raised unless response is complete
|
|
timeout(TIMEOUT) do
|
|
loop do
|
|
response += read()
|
|
break if response =~ /256 ([0-9]{1,5}) (\-|i|li|f|s|S|e)\r\n(.*)\r\n\r\n/m
|
|
end
|
|
end
|
|
rescue TimeoutError
|
|
raise(EcasoundError, "lost synchronisation to ecasound subprocess\nlast command was: '#{cmd}'")
|
|
end
|
|
|
|
content = $3[0, $1.to_i()]
|
|
|
|
#puts "type: '#{$2}'"
|
|
#puts "length: #{$1}"
|
|
#puts "content: #{content}"
|
|
|
|
case $2
|
|
when "e"
|
|
raise(EcasoundCommandError.new(cmd, content))
|
|
when "-"
|
|
return nil
|
|
when "s"
|
|
return content
|
|
when "S"
|
|
return content.split(",")
|
|
when "f"
|
|
return content.to_f()
|
|
when "i", "li"
|
|
return content.to_i()
|
|
else
|
|
raise(EcasoundError, "parsing of ecasound's output produced an unknown return type")
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def read()
|
|
buffer = ""
|
|
while select([@ecapipe], nil, nil, 0)
|
|
buffer += @ecapipe.read(1) || ""
|
|
end
|
|
return buffer
|
|
end
|
|
end # ControlInterface
|
|
|
|
end # Ecasound::
|