Subject: Visecas' issues related to Ecasound
From: Jan Weil (jawebada_AT_mailbox.tu-berlin.de)
Date: Sat Jan 31 2004 - 14:32:41 EET
Hi Kai,
I'm using the opportunity since you are actively posting...
In the course of Visecas' development I encountered some problems which
are related to Ecasound itself.
I'd appreciate your comments/suggestions.
1.) 'cs-save' does not save chainoperator parameters if the operator is
a preset.
2.) While you take care of ','s in 'map-ladspa-(id-)list' (i. e.
ECA_CONTROL::operator_descriptions_helper) the name_rep of
EFFECT_LADSPA::EFFECT_LADSPA is directly mapped to the plugin's Name.
A LADSPA plugin's Name which includes a ',' thereby breaks 'cop-list'
(which is supposed to be a ',' separated list).
Try those from allpass_1895.so of swh plugins as an example.
Fix: s/,/_/ when assigning name_rep.
3.) Similarly loop devices break a(i/o)-list since their names always
(!) contain a ','. I have no idea how to solve this while keeping
backwards compatibility but I'd suggest something like 'bus1.loop'
instead of 'loop,1' (i. e. 'loop$' as regex).
Cheers,
Jan
P.S. Attached is the current version of Ruby's ECI which is also
included in Visecas. Contains some code cleanup and more detailed
exceptions.
# This is a native implementation of Ecasound's control interface for Ruby.
# Copyright (C) 2003 - 2004 Jan Weil <jan.weil_AT_web.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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")
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::
This archive was generated by hypermail 2b28 : Sat Jan 31 2004 - 14:30:29 EET