dtas 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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