dtas 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +9 -0
  4. data/.rsync_doc +3 -0
  5. data/COPYING +674 -0
  6. data/Documentation/.gitignore +3 -0
  7. data/Documentation/GNUmakefile +46 -0
  8. data/Documentation/dtas-console.txt +42 -0
  9. data/Documentation/dtas-ctl.txt +64 -0
  10. data/Documentation/dtas-cueedit.txt +24 -0
  11. data/Documentation/dtas-enq.txt +29 -0
  12. data/Documentation/dtas-msinkctl.txt +45 -0
  13. data/Documentation/dtas-player.txt +110 -0
  14. data/Documentation/dtas-player_effects.txt +45 -0
  15. data/Documentation/dtas-player_protocol.txt +181 -0
  16. data/Documentation/dtas-sinkedit.txt +41 -0
  17. data/Documentation/dtas-sourceedit.txt +33 -0
  18. data/Documentation/dtas-xdelay.txt +57 -0
  19. data/Documentation/troubleshooting.txt +13 -0
  20. data/GIT-VERSION-GEN +30 -0
  21. data/GNUmakefile +9 -0
  22. data/HACKING +12 -0
  23. data/INSTALL +53 -0
  24. data/README +103 -0
  25. data/Rakefile +97 -0
  26. data/TODO +4 -0
  27. data/bin/dtas-console +160 -0
  28. data/bin/dtas-ctl +10 -0
  29. data/bin/dtas-cueedit +78 -0
  30. data/bin/dtas-enq +13 -0
  31. data/bin/dtas-msinkctl +51 -0
  32. data/bin/dtas-player +34 -0
  33. data/bin/dtas-sinkedit +58 -0
  34. data/bin/dtas-sourceedit +48 -0
  35. data/bin/dtas-xdelay +85 -0
  36. data/dtas-linux.gemspec +18 -0
  37. data/dtas-mpris.gemspec +16 -0
  38. data/examples/dtas_state.yml +18 -0
  39. data/lib/dtas.rb +7 -0
  40. data/lib/dtas/buffer.rb +90 -0
  41. data/lib/dtas/buffer/read_write.rb +102 -0
  42. data/lib/dtas/buffer/splice.rb +142 -0
  43. data/lib/dtas/command.rb +43 -0
  44. data/lib/dtas/compat_onenine.rb +18 -0
  45. data/lib/dtas/disclaimer.rb +18 -0
  46. data/lib/dtas/format.rb +151 -0
  47. data/lib/dtas/pipe.rb +39 -0
  48. data/lib/dtas/player.rb +393 -0
  49. data/lib/dtas/player/client_handler.rb +463 -0
  50. data/lib/dtas/process.rb +87 -0
  51. data/lib/dtas/replaygain.rb +41 -0
  52. data/lib/dtas/rg_state.rb +99 -0
  53. data/lib/dtas/serialize.rb +9 -0
  54. data/lib/dtas/sigevent.rb +10 -0
  55. data/lib/dtas/sigevent/efd.rb +20 -0
  56. data/lib/dtas/sigevent/pipe.rb +28 -0
  57. data/lib/dtas/sink.rb +121 -0
  58. data/lib/dtas/source.rb +147 -0
  59. data/lib/dtas/source/command.rb +40 -0
  60. data/lib/dtas/source/common.rb +14 -0
  61. data/lib/dtas/source/mp3.rb +37 -0
  62. data/lib/dtas/state_file.rb +33 -0
  63. data/lib/dtas/unix_accepted.rb +76 -0
  64. data/lib/dtas/unix_client.rb +51 -0
  65. data/lib/dtas/unix_server.rb +110 -0
  66. data/lib/dtas/util.rb +15 -0
  67. data/lib/dtas/writable_iter.rb +22 -0
  68. data/perl/dtas-graph +129 -0
  69. data/pkg.mk +26 -0
  70. data/setup.rb +1586 -0
  71. data/test/covshow.rb +30 -0
  72. data/test/helper.rb +76 -0
  73. data/test/player_integration.rb +121 -0
  74. data/test/test_buffer.rb +216 -0
  75. data/test/test_format.rb +61 -0
  76. data/test/test_format_change.rb +49 -0
  77. data/test/test_player.rb +47 -0
  78. data/test/test_player_client_handler.rb +86 -0
  79. data/test/test_player_integration.rb +220 -0
  80. data/test/test_rg_integration.rb +117 -0
  81. data/test/test_rg_state.rb +32 -0
  82. data/test/test_sink.rb +32 -0
  83. data/test/test_sink_tee_integration.rb +34 -0
  84. data/test/test_source.rb +102 -0
  85. data/test/test_unixserver.rb +66 -0
  86. data/test/test_util.rb +15 -0
  87. metadata +208 -0
data/bin/dtas-xdelay ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
4
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
5
+ USAGE = "Usage: #$0 [-x FREQ] [-l] /dev/fd/LO /dev/fd/HI DELAY [DELAY ...]"
6
+ require 'optparse'
7
+ dryrun = false
8
+ xover = '80'
9
+ delay_lo = []
10
+ delay_hi = []
11
+ adj_delay = delay_hi
12
+ out_channels = out_rate = out_type = nil
13
+
14
+ lowpass = 'lowpass %s lowpass %s'
15
+ highpass = 'highpass %s highpass %s'
16
+
17
+ op = OptionParser.new('', 24, ' ') do |opts|
18
+ opts.banner = USAGE
19
+ opts.on('-x', '--crossover-frequency FREQ') do |freq|
20
+ xover = freq
21
+ end
22
+ opts.on('-l', '--lowpass-delay') { adj_delay = delay_lo }
23
+ opts.on('-c', '--channels INTEGER') { |val| out_channels = val }
24
+ opts.on('-r', '--rate RATE') { |val| out_rate = val }
25
+ opts.on('-t', '--type FILE-TYPE') { |val| out_type = val }
26
+ opts.on('-n', '--dry-run') { dryrun = true }
27
+ opts.on('--lowpass FORMAT_STRING') { |s| lowpass = s }
28
+ opts.on('--highpass FORMAT_STRING') { |s| highpass = s }
29
+ opts.parse!(ARGV)
30
+ end
31
+
32
+ dev_fd_lo = ARGV.shift
33
+ dev_fd_hi = ARGV.shift
34
+ if ARGV.delete('-')
35
+ # we re-add the '-' below
36
+ out_channels && out_rate && out_type or
37
+ abort "-c, -r, and -t must all be specified for standard output"
38
+ cmd = "sox"
39
+ elsif out_channels || out_rate || out_type
40
+ abort "standard output (`-') must be specified with -c, -r, or -t"
41
+ else
42
+ cmd = "play"
43
+ end
44
+ soxfmt = ENV["SOXFMT"] or abort "#$0 SOXFMT undefined"
45
+
46
+ # configure the sox "delay" effect
47
+ delay = ARGV.dup
48
+ delay[0] or abort USAGE
49
+ channels = ENV['CHANNELS'] or abort "#$0 CHANNELS env must be set"
50
+ channels = channels.to_i
51
+ adj_delay.replace(delay.dup)
52
+ until adj_delay.size == channels
53
+ adj_delay << delay.last
54
+ end
55
+ adj_delay.unshift("delay")
56
+
57
+ # prepare two inputs:
58
+ delay_lo = delay_lo.join(' ')
59
+ delay_hi = delay_hi.join(' ')
60
+
61
+ lowpass_args = []
62
+ lowpass.gsub('%s') { |s| lowpass_args << xover; s }
63
+ highpass_args = []
64
+ highpass.gsub('%s') { |s| highpass_args << xover; s }
65
+
66
+ lo = "|exec sox #{soxfmt} #{dev_fd_lo} -p " \
67
+ "#{sprintf(lowpass, *lowpass_args)} #{delay_lo}".strip
68
+ hi = "|exec sox #{soxfmt} #{dev_fd_hi} -p " \
69
+ "#{sprintf(highpass, *highpass_args)} #{delay_hi}".strip
70
+
71
+ args = [ "-m", "-v1", lo, "-v1", hi ]
72
+ case cmd
73
+ when "sox"
74
+ args.unshift "sox"
75
+ args.concat(%W(-t#{out_type} -c#{out_channels} -r#{out_rate} -))
76
+ when "play"
77
+ args.unshift "play"
78
+ else
79
+ abort "BUG: bad cmd=#{cmd.inspect}"
80
+ end
81
+ if dryrun
82
+ p args
83
+ else
84
+ exec *args, close_others: false
85
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding: binary -*-
2
+ # this just declares dependencies to make gem installation a little easier
3
+ # for Linux users
4
+ Gem::Specification.new do |s|
5
+ s.name = %q{dtas-linux}
6
+ s.version = '1.0.0'
7
+ s.authors = ["dtas hackers"]
8
+ s.summary = "meta-package for dtas users on the Linux kernel"
9
+ s.description = "gives small performance improvements for dtas users\n" \
10
+ "via tee(), splice() and eventfd() on Linux"
11
+ s.email = %q{e@80x24.org}
12
+ s.files = []
13
+ s.homepage = 'http://dtas.80x24.org/'
14
+ s.add_dependency(%q<dtas>)
15
+ s.add_dependency(%q<io_splice>, '~> 4')
16
+ s.add_dependency(%q<sleepy_penguin>, '~> 3')
17
+ s.licenses = "GPLv3+"
18
+ end
@@ -0,0 +1,16 @@
1
+ # -*- encoding: binary -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = %q{dtas-mpris}
4
+ s.version = '0.0.0'
5
+ s.authors = ["dtas hackers"]
6
+ s.summary = "meta-package for the dtas-mpris proxy"
7
+ s.description =
8
+ "this allows controlling dtas-player via MPRIS or MPRIS 2.0\n" \
9
+ "This is currently a dummy package as dtas-mpris is not implemented"
10
+ s.email = %q{e@80x24.org}
11
+ s.files = []
12
+ s.homepage = 'http://dtas.80x24.org/'
13
+ s.add_dependency(%q<dtas>)
14
+ s.add_dependency(%q<ruby-dbus>)
15
+ s.licenses = "GPLv2+"
16
+ end
@@ -0,0 +1,18 @@
1
+ ---
2
+ socket: .dtas/player.sock
3
+ sinks:
4
+ - name: ODAC
5
+ command: |-
6
+ sox -t $SOX_FILETYPE -r $RATE -c $CHANNELS - \
7
+ -t s$SINK_BITS -r $SINK_RATE -c $SINK_CHANNELS - | \
8
+ aplay -D hw:DAC_1 -v -q -M --buffer-size=500000 --period-size=500 \
9
+ --disable-softvol --start-delay=100 \
10
+ --disable-format --disable-resample --disable-channels \
11
+ -t raw -c $SINK_CHANNELS -f S${SINK_BITS}_3LE -r $SINK_RATE
12
+ env:
13
+ SINK_BITS: 24
14
+ SINK_RATE: 44100
15
+ SINK_CHANNELS: 2
16
+ - name: play
17
+ prio: -2
18
+ command: env AUDIODEV=hw:DAC play -t $SOX_FILETYPE -r $RATE -c $CHANNELS -
data/lib/dtas.rb ADDED
@@ -0,0 +1,7 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ module DTAS # :nodoc:
5
+ end
6
+
7
+ require 'dtas/compat_onenine'
@@ -0,0 +1,90 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ require_relative '../dtas'
5
+
6
+ class DTAS::Buffer # :nodoc:
7
+ begin
8
+ raise LoadError, "no splice with _DTAS_POSIX" if ENV["_DTAS_POSIX"]
9
+ require 'io/splice' # splice is only in Linux for now...
10
+ require_relative 'buffer/splice'
11
+ include DTAS::Buffer::Splice
12
+ rescue LoadError
13
+ require_relative 'buffer/read_write'
14
+ include DTAS::Buffer::ReadWrite
15
+ end
16
+
17
+ attr_reader :to_io # call nread on this
18
+ attr_reader :wr # processes (sources) should redirect to this
19
+ attr_accessor :bytes_xfer
20
+
21
+ def initialize
22
+ @bytes_xfer = 0
23
+ @buffer_size = nil
24
+ @to_io, @wr = DTAS::Pipe.new
25
+ end
26
+
27
+ def self.load(hash)
28
+ buf = new
29
+ if hash
30
+ bs = hash["buffer_size"] and buf.buffer_size = bs
31
+ end
32
+ buf
33
+ end
34
+
35
+ def to_hsh
36
+ @buffer_size ? { "buffer_size" => @buffer_size } : {}
37
+ end
38
+
39
+ def __dst_error(dst, e)
40
+ warn "dropping #{dst.inspect} due to error: #{e.message} (#{e.class})"
41
+ dst.close unless dst.closed?
42
+ end
43
+
44
+ # This will modify targets
45
+ # returns one of:
46
+ # - :wait_readable
47
+ # - subset of targets array for :wait_writable
48
+ # - some type of StandardError
49
+ # - nil
50
+ def broadcast(targets)
51
+ bytes = inflight
52
+ return :wait_readable if 0 == bytes # spurious wakeup
53
+
54
+ case targets.size
55
+ when 0
56
+ :ignore # this will pause decoders
57
+ when 1
58
+ broadcast_one(targets, bytes)
59
+ else # infinity
60
+ broadcast_inf(targets, bytes)
61
+ end
62
+ end
63
+
64
+ def readable_iter
65
+ # this calls DTAS::Buffer#broadcast from DTAS::Player
66
+ yield(self, nil)
67
+ end
68
+
69
+ def inflight
70
+ @to_io.nread
71
+ end
72
+
73
+ # don't really close the pipes under normal circumstances, just clear data
74
+ def close
75
+ bytes = inflight
76
+ discard(bytes) if bytes > 0
77
+ end
78
+
79
+ def buf_reset
80
+ close!
81
+ @bytes_xfer = 0
82
+ @to_io, @wr = DTAS::Pipe.new
83
+ @wr.pipe_size = @buffer_size if @buffer_size
84
+ end
85
+
86
+ def close!
87
+ @to_io.close
88
+ @wr.close
89
+ end
90
+ end
@@ -0,0 +1,102 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ require 'io/wait'
5
+ require 'io/nonblock'
6
+ require_relative '../../dtas'
7
+ require_relative '../pipe'
8
+
9
+ module DTAS::Buffer::ReadWrite # :nodoc:
10
+ MAX_AT_ONCE = 512 # min PIPE_BUF value in POSIX
11
+ attr_accessor :buffer_size
12
+
13
+ def _rbuf
14
+ Thread.current[:dtas_pbuf] ||= ""
15
+ end
16
+
17
+ # be sure to only call this with nil when all writers to @wr are done
18
+ def discard(bytes)
19
+ buf = _rbuf
20
+ begin
21
+ @to_io.read(bytes, buf) or break # EOF
22
+ bytes -= buf.bytesize
23
+ end until bytes == 0
24
+ end
25
+
26
+ # always block when we have a single target
27
+ def broadcast_one(targets, bytes)
28
+ buf = _rbuf
29
+ @to_io.read(bytes, buf)
30
+ n = targets[0].write(buf) # IO#write has write-in-full behavior
31
+ @bytes_xfer += n
32
+ :wait_readable
33
+ rescue Errno::EPIPE, IOError => e
34
+ __dst_error(targets[0], e)
35
+ targets.clear
36
+ nil # do not return error here, we already spewed an error message
37
+ end
38
+
39
+ def broadcast_inf(targets, bytes)
40
+ nr_nb = targets.count { |sink| sink.nonblock? }
41
+ if nr_nb == 0 || nr_nb == targets.size
42
+ # if all targets are full, don't start until they're all writable
43
+ r = IO.select(nil, targets, nil, 0) or return targets
44
+ blocked = targets - r[1]
45
+
46
+ # tell DTAS::UNIXServer#run_once to wait on the blocked targets
47
+ return blocked if blocked[0]
48
+
49
+ # all writable, yay!
50
+ else
51
+ blocked = []
52
+ end
53
+
54
+ again = {}
55
+
56
+ # don't pin too much on one target
57
+ bytes = bytes > MAX_AT_ONCE ? MAX_AT_ONCE : bytes
58
+ buf = _rbuf
59
+ @to_io.read(bytes, buf)
60
+ @bytes_xfer += buf.bytesize
61
+
62
+ targets.delete_if do |dst|
63
+ begin
64
+ if dst.nonblock?
65
+ w = dst.write_nonblock(buf)
66
+ again[dst] = buf.byteslice(w, n) if w < n
67
+ else
68
+ dst.write(buf)
69
+ end
70
+ false
71
+ rescue Errno::EAGAIN
72
+ blocked << dst
73
+ false
74
+ rescue IOError, Errno::EPIPE => e
75
+ again.delete(dst)
76
+ __dst_error(dst, e)
77
+ true
78
+ end
79
+ end
80
+
81
+ # try to write as much as possible
82
+ again.delete_if do |dst, sbuf|
83
+ begin
84
+ w = dst.write_nonblock(sbuf)
85
+ n = sbuf.bytesize
86
+ if w < n
87
+ again[dst] = sbuf.byteslice(w, n)
88
+ false
89
+ else
90
+ true
91
+ end
92
+ rescue Errno::EAGAIN
93
+ blocked << dst
94
+ true
95
+ rescue IOError, Errno::EPIPE => e
96
+ __dst_error(dst, e)
97
+ true
98
+ end
99
+ end until again.empty?
100
+ targets[0] ? :wait_readable : nil
101
+ end
102
+ end
@@ -0,0 +1,142 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ require 'io/wait'
5
+ require 'io/nonblock'
6
+ require 'io/splice'
7
+ require_relative '../../dtas'
8
+ require_relative '../pipe'
9
+
10
+ module DTAS::Buffer::Splice # :nodoc:
11
+ MAX_AT_ONCE = 4096 # page size in Linux
12
+ MAX_SIZE = File.read("/proc/sys/fs/pipe-max-size").to_i
13
+ DEVNULL = File.open("/dev/null", "r+")
14
+ F_MOVE = IO::Splice::F_MOVE
15
+ WAITALL = IO::Splice::WAITALL
16
+
17
+ def buffer_size
18
+ @to_io.pipe_size
19
+ end
20
+
21
+ # nil is OK, won't reset existing pipe, either...
22
+ def buffer_size=(bytes)
23
+ @to_io.pipe_size = bytes if bytes
24
+ @buffer_size = bytes
25
+ end
26
+
27
+ # be sure to only call this with nil when all writers to @wr are done
28
+ def discard(bytes)
29
+ IO.splice(@to_io, nil, DEVNULL, nil, bytes)
30
+ end
31
+
32
+ def broadcast_one(targets, bytes)
33
+ # single output is always non-blocking
34
+ s = IO.trysplice(@to_io, nil, targets[0], nil, bytes, F_MOVE)
35
+ if Symbol === s
36
+ targets # our one and only target blocked on write
37
+ else
38
+ @bytes_xfer += s
39
+ :wait_readable # we want to read more from @to_io soon
40
+ end
41
+ rescue Errno::EPIPE, IOError => e
42
+ __dst_error(targets[0], e)
43
+ targets.clear
44
+ nil # do not return error here, we already spewed an error message
45
+ end
46
+
47
+ # returns the largest value we teed
48
+ def __broadcast_tee(blocked, targets, chunk_size)
49
+ most_teed = 0
50
+ targets.delete_if do |dst|
51
+ begin
52
+ t = dst.nonblock? ?
53
+ IO.trytee(@to_io, dst, chunk_size) :
54
+ IO.tee(@to_io, dst, chunk_size, WAITALL)
55
+ if Integer === t
56
+ most_teed = t if t > most_teed
57
+ else
58
+ blocked << dst
59
+ end
60
+ false
61
+ rescue IOError, Errno::EPIPE => e
62
+ __dst_error(dst, e)
63
+ true
64
+ end
65
+ end
66
+ most_teed
67
+ end
68
+
69
+ def broadcast_inf(targets, bytes)
70
+ if targets.none? { |sink| sink.nonblock? }
71
+ # if all targets are blocking, don't start until they're all writable
72
+ r = IO.select(nil, targets, nil, 0) or return targets
73
+ blocked = targets - r[1]
74
+
75
+ # tell DTAS::UNIXServer#run_once to wait on the blocked targets
76
+ return blocked if blocked[0]
77
+
78
+ # all writable, yay!
79
+ else
80
+ blocked = []
81
+ end
82
+
83
+ # don't pin too much on one target
84
+ bytes = bytes > MAX_AT_ONCE ? MAX_AT_ONCE : bytes
85
+
86
+ last = targets.pop # we splice to the last one, tee to the rest
87
+ most_teed = __broadcast_tee(blocked, targets, bytes)
88
+
89
+ # don't splice more than the largest amount we successfully teed
90
+ bytes = most_teed if most_teed > 0
91
+
92
+ begin
93
+ targets << last
94
+ if last.nonblock?
95
+ s = IO.trysplice(@to_io, nil, last, nil, bytes, F_MOVE)
96
+ if Symbol === s
97
+ blocked << last
98
+
99
+ # we accomplished nothing!
100
+ # If _all_ writers are blocked, do not discard data,
101
+ # stay blocked on :wait_writable
102
+ return blocked if most_teed == 0
103
+
104
+ # the tees targets win, drop data intended for last
105
+ if most_teed > 0
106
+ discard(most_teed)
107
+ @bytes_xfer += most_teed
108
+ # do not watch for writability of last, last is non-blocking
109
+ return :wait_readable
110
+ end
111
+ end
112
+ else
113
+ # the blocking case is simple
114
+ s = IO.splice(@to_io, nil, last, nil, bytes, WAITALL|F_MOVE)
115
+ end
116
+ @bytes_xfer += s
117
+
118
+ # if we can't splice everything
119
+ # discard it so the early targets do not get repeated data
120
+ if s < bytes && most_teed > 0
121
+ discard(bytes - s)
122
+ end
123
+ :wait_readable
124
+ rescue IOError, Errno::EPIPE => e # last failed, drop it
125
+ __dst_error(last, e)
126
+ targets.pop # we're no longer a valid target
127
+
128
+ if most_teed == 0
129
+ # nothing accomplished, watch any targets
130
+ return blocked if blocked[0]
131
+ else
132
+ # some progress, discard the data we could not splice
133
+ @bytes_xfer += most_teed
134
+ discard(most_teed)
135
+ end
136
+
137
+ # stop decoding if we're completely errored out
138
+ # returning nil will trigger close
139
+ return targets[0] ? :wait_readable : nil
140
+ end
141
+ end
142
+ end