Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
jack-aloop-daemon
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'jack'
raise "You need jack-ffi, not jack gem." unless JACK
require 'yaml'
include JACK
CONFIG_FILE = File.expand_path("~/.jack-aloop-daemon.yaml")
begin
CONFIG = YAML.load(File.open(CONFIG_FILE, "r").read)
rescue Errno::ENOENT
CONFIG = {
"daemonize" => false,
"channels" => 2,
"sample_rate" => 44100,
"buffer_size" => 1024,
"launch_info" => {
"in" => {
"type" => "capture",
# nickname, device
"devices" => {
"cloop" => "cloop",
},
"command" => "alsa_in -j %1$s -d %2$s -c %3$d -r %4$d -p %5$d",
},
"out" => {
"type" => "playback",
"devices" => {
"ploop" => "ploop",
},
"command" => "alsa_out -j %1$s -d %2$s -c %3$d -r %4$d -p %5$d",
},
},
"connect" => {
"cloop" => ["system"],
"system" => ["ploop"],
},
}
File.open(CONFIG_FILE, "w") do |fp|
fp.print CONFIG.to_yaml
end
end
Client.new("jack-aloop-daemon") do |jack|
CONFIG["launch_info"].each do |key, val|
devs = val["devices"]
fail "no devices in launch_info #{key}" unless devs.respond_to?(:keys)
fail "no devices in launch_info #{key}" if devs.size == 0
nicks = devs.keys
type = val["type"]
fail "type must be \"capture\" or \"playback\"." unless
%w[capture playback].any? { |x| x == type }
nicks.each do |nick|
port_name = "%s:%s_1" % [nick, type]
begin
jack.port_by_name(port_name)
fail "Jack Server already has a port for #{nick}."
rescue Errors::NoSuchPortError
# NOP
end
end
end
end
AloopDaemon = Struct.new(:type, :nick, :channels, :conns,
:cmd, :pid, :thread) do
def reconnect
if self.type == "capture"
from = [self.nick]
to = self.conns
else
from = self.conns
to = [self.nick]
end
Client.new("jack-aloo-daemon_connector_#{self.nick}") do |jack|
from.each do |fr|
to.each do |tt|
(1..self.channels).each do |i|
tries = 0
begin
jack.connect("#{fr}:capture_#{i}", "#{tt}:playback_#{i}")
rescue Errors::NoSuchPortError
sleep 1
tries += 1
retry if tries < 5
raise
end
end
end
end
end
end
end
$aloopdaemons = Array.new
Process.daemon if CONFIG["daemonize"] == true
channels = CONFIG["channels"]
sample_rate = CONFIG["sample_rate"]
buffer_size = CONFIG["buffer_size"]
begin
channels = Integer(channels)
sample_rate = Integer(sample_rate)
buffer_size = Integer(buffer_size)
rescue ArgumentError
fail "channels, sameple_rate and buffer_size must be integers."
end
CONFIG["launch_info"].each do |key, val|
devices = val["devices"]
cmd = val["command"]
type = val["type"]
fail "no command to #{key} launch_info" unless cmd
devices.each do |nick, dev|
cmdline = cmd % [nick, dev, channels, sample_rate, buffer_size]
conns = CONFIG["connect"].select do |key, val|
if type == "capture"
key == nick
else
val.any? { |x| x == nick }
end
end
if type == "capture"
conns = conns.values.flatten
else
conns = conns.keys
end
add = AloopDaemon.new(type, nick, channels, conns, cmdline)
thread = Thread.new(add) do |add|
first = true
loop do
add.pid = Process.spawn(add.cmd, :in => "/dev/null", :out => "/dev/null")
add.reconnect unless first
first = false
Process.wait(add.pid)
end
end
add.thread = thread
$aloopdaemons << add
end
end
Client.new("jack-aloop-daemon") do |jack|
(1..channels).each do |i|
CONFIG["connect"].each do |fr, arr_to|
arr_to.each do |to|
tries = 0
begin
jack.connect("#{fr}:capture_#{i}", "#{to}:playback_#{i}")
rescue Errors::NoSuchPortError
sleep 1
tries += 1
retry if tries < 5
raise
end
end
end
end
end
Signal.trap("INT") do
Process.kill("TERM", Process.pid)
end
Signal.trap("TERM") do
$aloopdaemons.each do |add|
Thread.kill(add.thread)
Process.kill("TERM", add.pid)
end
Process.waitall
exit
end
sleep
@lugia-kun

This comment has been minimized.

Copy link
Owner Author

@lugia-kun lugia-kun commented May 5, 2013

Ruby version of http://pastebin.com/uyyVzEhM and some improvements. Please see details in my comment.

  • You needs jack-ffi gem (jack gem must not be installed; they are conflicted).
  • Configuration for command string will be parsed in simple strategy: Quotes will act as same as other regular characters.
  • To kill this daemon, send INT or TERM signal; KILL or others will not cleanup on exit.
  • You may also use secondary ALSA physical cards with this script and send playback sounds to main card, network, firewire etc.
  • I'm not sure whether the alsa_in and alsa_out commands will terminate safely by sending TERM signal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.