~ubuntu-branches/ubuntu/jaunty/ecasound2.2/jaunty

« back to all changes in this revision

Viewing changes to rubyecasound/ecasound.rb

  • Committer: Bazaar Package Importer
  • Author(s): Junichi Uekawa
  • Date: 2005-04-14 09:15:48 UTC
  • Revision ID: james.westby@ubuntu.com-20050414091548-o7kgb47z0tcunh0s
Tags: upstream-2.4.1
ImportĀ upstreamĀ versionĀ 2.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This is a native implementation of Ecasound's control interface for Ruby.
 
2
# Copyright (C) 2003 - 2004  Jan Weil <jan.weil@web.de>
 
3
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
# ---------------------------------------------------------------------------
 
18
=begin
 
19
= ruby-ecasound
 
20
 
 
21
Example:
 
22
 
 
23
require "ecasound"
 
24
eci = Ecasound::ControlInterface.new(ecasound_args)
 
25
ecasound-response = eci.command("iam-command-here")
 
26
...
 
27
 
 
28
TODO:
 
29
Is there a chance that the ecasound process gets zombified?
 
30
 
 
31
=end
 
32
 
 
33
require "timeout"
 
34
require "thread"
 
35
 
 
36
class File
 
37
    def self::which(prog, path=ENV['PATH'])
 
38
        path.split(File::PATH_SEPARATOR).each do |dir|
 
39
            f = File::join(dir, prog)
 
40
            if File::executable?(f) && ! File::directory?(f)
 
41
                return f
 
42
            end
 
43
        end
 
44
    end
 
45
end # File
 
46
 
 
47
class VersionString < String
 
48
    attr_reader :numbers
 
49
 
 
50
    def initialize(str)
 
51
        if str.split(".").length() != 3
 
52
            raise("Versioning scheme must be major.minor.micro")
 
53
        end
 
54
        super(str)
 
55
        @numbers = []
 
56
        str.split(".").each {|s| @numbers.push(s.to_i())}
 
57
    end
 
58
    
 
59
    def <=>(other)
 
60
        numbers.each_index do |i|
 
61
            if numbers[i] < other.numbers[i]
 
62
                return -1
 
63
            elsif numbers[i] > other.numbers[i]
 
64
                return 1
 
65
            elsif i < 2
 
66
                next
 
67
            end
 
68
        end
 
69
        return 0
 
70
    end
 
71
end # VersionString
 
72
 
 
73
module Ecasound
 
74
 
 
75
REQUIRED_VERSION = VersionString.new("2.2.0")
 
76
TIMEOUT = 15 # seconds before sync is called 'lost'
 
77
 
 
78
class EcasoundError < RuntimeError; end
 
79
class EcasoundCommandError < EcasoundError
 
80
    attr_accessor :command, :error
 
81
    def initialize(command, error)
 
82
        @command = command
 
83
        @error = error
 
84
    end
 
85
end
 
86
 
 
87
class ControlInterface
 
88
    @@ecasound = ENV['ECASOUND'] || File::which("ecasound")
 
89
    
 
90
    if not File::executable?(@@ecasound.to_s)
 
91
        raise("ecasound executable not found")
 
92
    else
 
93
        @@version = VersionString.new(`#{@@ecasound} --version`.split("\n")[0][/\d\.\d\.\d/])
 
94
        if @@version < REQUIRED_VERSION
 
95
            raise("ecasound version #{REQUIRED_VERSION} or newer required, found: #{@@version}")
 
96
        end
 
97
    end
 
98
    
 
99
    def initialize(args = nil)
 
100
        @mutex = Mutex.new()
 
101
        @ecapipe = IO.popen("-", "r+") # fork!
 
102
        
 
103
        if @ecapipe.nil?
 
104
            # child
 
105
            $stderr.reopen(open("/dev/null", "w"))
 
106
            exec("#{@@ecasound} #{args.to_s} -c -D -d:256 ")
 
107
        else
 
108
            @ecapipe.sync = true
 
109
            # parent
 
110
            command("int-output-mode-wellformed")
 
111
        end
 
112
    end
 
113
 
 
114
    def cleanup()
 
115
        @ecapipe.close()
 
116
    end
 
117
 
 
118
    def command(cmd)
 
119
        @mutex.synchronize do
 
120
            cmd.strip!()
 
121
            #puts "command: #{cmd}"
 
122
            
 
123
            @ecapipe.write(cmd + "\n")
 
124
 
 
125
            # ugly hack but the process gets stuck otherwise -kvehmanen
 
126
            if cmd == "quit"
 
127
                return nil
 
128
            end
 
129
 
 
130
            response = ""
 
131
            begin
 
132
                # TimeoutError is raised unless response is complete
 
133
                timeout(TIMEOUT) do
 
134
                    loop do
 
135
                        response += read()
 
136
                        break if response =~ /256 ([0-9]{1,5}) (\-|i|li|f|s|S|e)\r\n(.*)\r\n\r\n/m
 
137
                    end
 
138
                end
 
139
            rescue TimeoutError
 
140
                raise(EcasoundError, "lost synchronisation to ecasound subprocess\nlast command was: '#{cmd}'")
 
141
            end
 
142
            
 
143
            content = $3[0, $1.to_i()]
 
144
 
 
145
            #puts "type: '#{$2}'"
 
146
            #puts "length: #{$1}"
 
147
            #puts "content: #{content}"
 
148
 
 
149
            case $2
 
150
                when "e"
 
151
                    raise(EcasoundCommandError.new(cmd, content))
 
152
                when "-"
 
153
                    return nil
 
154
                when "s"
 
155
                    return content
 
156
                when "S"
 
157
                    return content.split(",")
 
158
                when "f"
 
159
                    return content.to_f()
 
160
                when "i", "li"
 
161
                    return content.to_i()
 
162
                else
 
163
                    raise(EcasoundError, "parsing of ecasound's output produced an unknown return type")
 
164
            end
 
165
        end
 
166
    end
 
167
 
 
168
    private
 
169
 
 
170
    def read()
 
171
        buffer = ""
 
172
        while select([@ecapipe], nil, nil, 0)
 
173
            buffer += @ecapipe.read(1) || ""
 
174
        end
 
175
        return buffer
 
176
    end
 
177
end # ControlInterface
 
178
 
 
179
end # Ecasound::