Last active
December 16, 2015 23:59
-
-
Save lugia-kun/5517620 to your computer and use it in GitHub Desktop.
jack-aloop-daemon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ruby version of http://pastebin.com/uyyVzEhM and some improvements. Please see details in my comment.
jack-ffi
gem (jack
gem must not be installed; they are conflicted).INT
orTERM
signal;KILL
or others will not cleanup on exit.alsa_in
andalsa_out
commands will terminate safely by sendingTERM
signal.