Visecas' issues related to Ecasound

New Message Reply About this list Date view Thread view Subject view Author view Other groups

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::


New Message Reply About this list Date view Thread view Subject view Author view Other groups

This archive was generated by hypermail 2b28 : Sat Jan 31 2004 - 14:30:29 EET