dtas 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/Documentation/.gitignore +1 -1
  4. data/Documentation/GNUmakefile +3 -1
  5. data/Documentation/dtas-archive.txt +61 -0
  6. data/Documentation/dtas-console.txt +4 -3
  7. data/Documentation/dtas-ctl.txt +4 -3
  8. data/Documentation/dtas-cueedit.txt +4 -3
  9. data/Documentation/dtas-enq.txt +4 -3
  10. data/Documentation/dtas-env.txt +60 -0
  11. data/Documentation/dtas-msinkctl.txt +4 -3
  12. data/Documentation/dtas-player.txt +6 -3
  13. data/Documentation/dtas-player_effects.txt +4 -3
  14. data/Documentation/dtas-player_protocol.txt +27 -4
  15. data/Documentation/dtas-player_sink_examples.txt +7 -3
  16. data/Documentation/dtas-sinkedit.txt +20 -3
  17. data/Documentation/dtas-sourceedit.txt +21 -3
  18. data/Documentation/dtas-splitfx.txt +14 -5
  19. data/Documentation/dtas-tl.txt +4 -3
  20. data/Documentation/dtas-xdelay.txt +4 -3
  21. data/Documentation/update-footer.rb +52 -0
  22. data/GIT-VERSION-GEN +2 -2
  23. data/GNUmakefile +1 -1
  24. data/HACKING +3 -2
  25. data/INSTALL +6 -6
  26. data/README +1 -1
  27. data/Rakefile +2 -3
  28. data/TODO +1 -1
  29. data/bin/dtas-archive +187 -0
  30. data/bin/dtas-console +7 -1
  31. data/bin/dtas-ctl +1 -1
  32. data/bin/dtas-cueedit +3 -3
  33. data/bin/dtas-enq +1 -1
  34. data/bin/dtas-msinkctl +1 -1
  35. data/bin/dtas-partstats +10 -4
  36. data/bin/dtas-player +1 -1
  37. data/bin/dtas-sinkedit +82 -20
  38. data/bin/dtas-sourceedit +64 -22
  39. data/bin/dtas-splitfx +1 -1
  40. data/bin/dtas-tl +1 -1
  41. data/bin/dtas-xdelay +1 -1
  42. data/dtas-linux.gemspec +1 -1
  43. data/dtas-mpris.gemspec +1 -1
  44. data/dtas.gemspec +1 -1
  45. data/examples/splitfx.sample.yml +11 -3
  46. data/examples/{trimfx.sample.yml → tfx.sample.yml} +1 -1
  47. data/lib/dtas.rb +2 -1
  48. data/lib/dtas/buffer.rb +5 -5
  49. data/lib/dtas/buffer/read_write.rb +8 -5
  50. data/lib/dtas/buffer/splice.rb +29 -8
  51. data/lib/dtas/command.rb +2 -2
  52. data/lib/dtas/compat_onenine.rb +1 -1
  53. data/lib/dtas/cue_index.rb +3 -1
  54. data/lib/dtas/disclaimer.rb +1 -1
  55. data/lib/dtas/edit_client.rb +1 -1
  56. data/lib/dtas/fadefx.rb +100 -0
  57. data/lib/dtas/format.rb +4 -2
  58. data/lib/dtas/parse_time.rb +3 -1
  59. data/lib/dtas/partstats.rb +8 -10
  60. data/lib/dtas/pipe.rb +2 -1
  61. data/lib/dtas/player.rb +33 -11
  62. data/lib/dtas/player/client_handler.rb +43 -8
  63. data/lib/dtas/process.rb +6 -14
  64. data/lib/dtas/replaygain.rb +3 -3
  65. data/lib/dtas/rg_state.rb +1 -1
  66. data/lib/dtas/serialize.rb +3 -1
  67. data/lib/dtas/sigevent.rb +1 -1
  68. data/lib/dtas/sigevent/efd.rb +5 -4
  69. data/lib/dtas/sigevent/pipe.rb +4 -1
  70. data/lib/dtas/sink.rb +4 -4
  71. data/lib/dtas/source.rb +1 -1
  72. data/lib/dtas/source/av.rb +2 -2
  73. data/lib/dtas/source/av_ff_common.rb +22 -11
  74. data/lib/dtas/source/cmd.rb +3 -3
  75. data/lib/dtas/source/common.rb +3 -2
  76. data/lib/dtas/source/ff.rb +2 -2
  77. data/lib/dtas/source/file.rb +22 -4
  78. data/lib/dtas/source/mp3gain.rb +1 -1
  79. data/lib/dtas/source/sox.rb +7 -7
  80. data/lib/dtas/source/splitfx.rb +99 -0
  81. data/lib/dtas/spawn_fix.rb +10 -0
  82. data/lib/dtas/splitfx.rb +63 -24
  83. data/lib/dtas/state_file.rb +3 -1
  84. data/lib/dtas/{trimfx.rb → tfx.rb} +50 -24
  85. data/lib/dtas/tracklist.rb +2 -1
  86. data/lib/dtas/unix_accepted.rb +2 -2
  87. data/lib/dtas/unix_client.rb +2 -2
  88. data/lib/dtas/unix_server.rb +1 -1
  89. data/lib/dtas/util.rb +1 -1
  90. data/lib/dtas/watchable.rb +54 -0
  91. data/lib/dtas/writable_iter.rb +2 -1
  92. data/lib/dtas/xs.rb +2 -2
  93. data/perl/dtas-graph +1 -1
  94. data/test/covshow.rb +1 -1
  95. data/test/helper.rb +1 -1
  96. data/test/player_integration.rb +1 -1
  97. data/test/test_buffer.rb +1 -1
  98. data/test/test_env.rb +1 -1
  99. data/test/test_fadefx.rb +45 -0
  100. data/test/test_format.rb +1 -1
  101. data/test/test_format_change.rb +1 -1
  102. data/test/test_player.rb +1 -1
  103. data/test/test_player_client_handler.rb +1 -1
  104. data/test/test_player_integration.rb +8 -8
  105. data/test/test_process.rb +1 -1
  106. data/test/test_rg_integration.rb +1 -1
  107. data/test/test_rg_state.rb +1 -1
  108. data/test/test_sink.rb +1 -1
  109. data/test/test_sink_pipe_size.rb +2 -2
  110. data/test/test_sink_tee_integration.rb +1 -1
  111. data/test/test_source_av.rb +1 -1
  112. data/test/test_source_sox.rb +1 -1
  113. data/test/test_splitfx.rb +43 -13
  114. data/test/test_tfx.rb +85 -0
  115. data/test/test_tracklist.rb +1 -1
  116. data/test/test_unixserver.rb +1 -1
  117. data/test/test_util.rb +1 -1
  118. metadata +17 -6
  119. data/lib/dtas/compat_rbx.rb +0 -12
  120. data/test/test_trimfx.rb +0 -81
@@ -1,17 +1,17 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  require 'io/nonblock'
4
4
  require 'io/splice'
5
5
  require_relative '../../dtas'
6
6
  require_relative '../pipe'
7
7
 
8
+ # Used by -player on Linux systems with the "io-splice" RubyGem installed
8
9
  module DTAS::Buffer::Splice # :nodoc:
9
10
  MAX_AT_ONCE = 4096 # page size in Linux
10
11
  MAX_AT_ONCE_1 = 65536
11
12
  MAX_SIZE = File.read("/proc/sys/fs/pipe-max-size").to_i
12
13
  DEVNULL = File.open("/dev/null", "r+")
13
14
  F_MOVE = IO::Splice::F_MOVE
14
- WAITALL = IO::Splice::WAITALL
15
15
 
16
16
  def buffer_size
17
17
  @to_io.pipe_size
@@ -28,9 +28,10 @@ module DTAS::Buffer::Splice # :nodoc:
28
28
  IO.splice(@to_io, nil, DEVNULL, nil, bytes)
29
29
  end
30
30
 
31
- def broadcast_one(targets)
31
+ def broadcast_one(targets, limit = nil)
32
32
  # single output is always non-blocking
33
- s = IO.trysplice(@to_io, nil, targets[0], nil, MAX_AT_ONCE_1, F_MOVE)
33
+ limit ||= MAX_AT_ONCE_1
34
+ s = IO.trysplice(@to_io, nil, targets[0], nil, limit, F_MOVE)
34
35
  if Symbol === s
35
36
  targets # our one and only target blocked on write
36
37
  else
@@ -43,6 +44,26 @@ module DTAS::Buffer::Splice # :nodoc:
43
44
  nil # do not return error here, we already spewed an error message
44
45
  end
45
46
 
47
+ def __tee_in_full(src, dst, bytes)
48
+ rv = 0
49
+ while bytes > 0
50
+ s = IO.tee(src, dst, bytes)
51
+ bytes -= s
52
+ rv += s
53
+ end
54
+ rv
55
+ end
56
+
57
+ def __splice_in_full(src, dst, bytes, flags)
58
+ rv = 0
59
+ while bytes > 0
60
+ s = IO.splice(src, nil, dst, nil, bytes, flags)
61
+ rv += s
62
+ bytes -= s
63
+ end
64
+ rv
65
+ end
66
+
46
67
  # returns the largest value we teed
47
68
  def __broadcast_tee(blocked, targets, chunk_size)
48
69
  most_teed = 0
@@ -50,7 +71,7 @@ module DTAS::Buffer::Splice # :nodoc:
50
71
  begin
51
72
  t = (dst.nonblock? || most_teed == 0) ?
52
73
  IO.trytee(@to_io, dst, chunk_size) :
53
- IO.tee(@to_io, dst, chunk_size, WAITALL)
74
+ __tee_in_full(@to_io, dst, chunk_size)
54
75
  if Integer === t
55
76
  if t > most_teed
56
77
  chunk_size = t if most_teed == 0
@@ -68,7 +89,7 @@ module DTAS::Buffer::Splice # :nodoc:
68
89
  most_teed
69
90
  end
70
91
 
71
- def broadcast_inf(targets)
92
+ def broadcast_inf(targets, limit = nil)
72
93
  if targets.all?(&:ready_write_optimized?)
73
94
  blocked = []
74
95
  elsif targets.none?(&:nonblock?)
@@ -85,7 +106,7 @@ module DTAS::Buffer::Splice # :nodoc:
85
106
  end
86
107
 
87
108
  # don't pin too much on one target
88
- bytes = MAX_AT_ONCE
109
+ bytes = limit || MAX_AT_ONCE
89
110
  last = targets.pop # we splice to the last one, tee to the rest
90
111
 
91
112
  # this may return zero if all targets were non-blocking
@@ -116,7 +137,7 @@ module DTAS::Buffer::Splice # :nodoc:
116
137
  end
117
138
  else
118
139
  # the blocking case is simple
119
- s = IO.splice(@to_io, nil, last, nil, bytes, WAITALL|F_MOVE)
140
+ s = __splice_in_full(@to_io, last, bytes, F_MOVE)
120
141
  end
121
142
  @bytes_xfer += s
122
143
 
@@ -1,9 +1,9 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
- # common code for wrapping SoX/ecasound/... commands
4
3
  require_relative 'serialize'
5
4
  require 'shellwords'
6
5
 
6
+ # common code for wrapping SoX/ecasound/... commands
7
7
  module DTAS::Command # :nodoc:
8
8
  include DTAS::Serialize
9
9
  attr_reader :pid
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
 
4
4
  # Make Ruby 1.9.3 look like Ruby 2.0.0 to us
@@ -1,6 +1,8 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  require_relative '../dtas'
4
+
5
+ # embedded CUE sheet representation for -player
4
6
  class DTAS::CueIndex
5
7
  attr_reader :offset
6
8
  attr_reader :index
@@ -1,5 +1,5 @@
1
1
  # :enddoc:
2
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
2
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  DTAS_PROGNAME = File.basename($0)
5
5
  DTAS_DISCLAIMER = <<EOF
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  require 'tempfile'
4
4
  require 'yaml'
@@ -0,0 +1,100 @@
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative '../dtas'
4
+ require_relative 'parse_time'
5
+ require_relative 'xs'
6
+
7
+ # note: This is sox-specific
8
+ # --------- time --------->
9
+ # _____ _______ ______
10
+ # \ / \ /
11
+ # prev X cur X next
12
+ # _____/ \_______/ \______
13
+ #
14
+ # out_prev - controls the downward slope from prev
15
+ # in_cur - controls the upward slope into cur
16
+ # out_cur - controls the downward slope from cur
17
+ # in_next - controls the upward slope into next
18
+ class DTAS::FadeFX # :nodoc:
19
+ include DTAS::ParseTime
20
+ include DTAS::XS
21
+
22
+ attr_reader :out_prev, :in_cur, :out_cur, :in_next
23
+ F = Struct.new(:type, :flen)
24
+
25
+ def initialize(args)
26
+ args =~ /\A([^,]*),([^,]*);([^,]*),([^,]*)\z/ or
27
+ raise ArgumentError, "bad fade format"
28
+ fades = [ $1, $2, $3, $4 ]
29
+ %w(out_prev in_cur out_cur in_next).each do |iv|
30
+ instance_variable_set("@#{iv}", parse!(fades.shift))
31
+ end
32
+ end
33
+
34
+ def fade_cur_fx(format, tbeg, tlen, args = [])
35
+ fx = %W(trim #{tbeg}s #{tlen}s)
36
+ fx.concat(args)
37
+ if @in_cur && @out_cur && @in_cur.type == @out_cur.type
38
+ f = %W(fade #{@in_cur.type} #{@in_cur.flen} #{tlen}s #{@out_cur.flen})
39
+ fx.concat(f)
40
+ else # differing fade types for in/out, chain them:
41
+ fpart = @in_cur and
42
+ fx.concat(%W(fade #{fpart.type} #{fpart.flen} 0 0))
43
+ fpart = @out_cur and
44
+ fx.concat(%W(fade #{fpart.type} 0 #{tlen}s #{fpart.flen}))
45
+ end
46
+ fx
47
+ end
48
+
49
+ def fade_out_prev_fx(format, tbeg, tlen)
50
+ fx = %W(trim #{tbeg}s)
51
+
52
+ if fpart = @out_prev
53
+ out_len = format.hhmmss_to_samples(fpart.flen)
54
+ fx.concat(%W(fade #{fpart.type} 0 #{out_len}s #{out_len}s))
55
+ remain = tlen - out_len
56
+
57
+ # fade-out is longer than tlen, so truncate again:
58
+ remain < 0 and fx.concat(%W(trim 0 #{tlen}s))
59
+
60
+ # pad with silence, this is where fade_cur_fx goes
61
+ remain > 0 and fx.concat(%W(pad #{remain}s@#{out_len}s))
62
+ end
63
+ fx
64
+ end
65
+
66
+ def fade_in_next_fx(format, tbeg, tlen)
67
+ fpart = @in_next
68
+ flen = fpart ? fpart.flen : 0
69
+ nlen = format.hhmmss_to_samples(flen)
70
+ nbeg = tbeg + tlen - nlen
71
+ npad = nbeg - tbeg
72
+ if npad < 0
73
+ warn("in_next should not exceed range: #{inspect} @trim " \
74
+ "#{tbeg}s #{tlen}s\nclamping to #{tbeg}")
75
+ nbeg = tbeg
76
+ end
77
+
78
+ fx = %W(trim #{nbeg}s #{nlen}s)
79
+ nlen != 0 and
80
+ fx.concat(%W(fade #{fpart.type} #{nlen}s 0 0))
81
+
82
+ # likely, the pad section is where fade_cur_fx goes
83
+ npad > 0 and fx.concat(%W(pad #{npad}s@0s))
84
+ fx
85
+ end
86
+
87
+ # q - quarter of a sine wave
88
+ # h - half a sine wave
89
+ # t - linear (`triangular') slope
90
+ # l - logarithmic
91
+ # p - inverted parabola
92
+ # default is 't' (sox defaults to 'l', but triangular makes more sense
93
+ # when concatenating
94
+ def parse!(str)
95
+ return nil if str.empty?
96
+ type = "t"
97
+ str.sub!(/\A([a-z])/, "") and type = $1
98
+ F.new(type, parse_time(str))
99
+ end
100
+ end
@@ -1,10 +1,11 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
- # class represents an audio format (type/bits/channels/sample rate/...)
4
3
  require_relative '../dtas'
5
4
  require_relative 'process'
6
5
  require_relative 'serialize'
7
6
 
7
+ # class represents an audio format (type/bits/channels/sample rate/...)
8
+ # used throughout dtas
8
9
  class DTAS::Format # :nodoc:
9
10
  include DTAS::Process
10
11
  include DTAS::Serialize
@@ -164,6 +165,7 @@ class DTAS::Format # :nodoc:
164
165
  # HH:MM:SS.frac (don't bother with more complex times, too much code)
165
166
  # part of me wants to drop this feature from playq, feels like bloat...
166
167
  def hhmmss_to_samples(hhmmss)
168
+ Numeric === hhmmss and return hhmmss * @rate
167
169
  time = hhmmss.dup
168
170
  rv = 0
169
171
  if time.sub!(/\.(\d+)\z/, "")
@@ -1,7 +1,9 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  require_relative '../dtas'
4
+
4
5
  module DTAS::ParseTime
6
+ # convert a string time to seconds, returning a Flot or Integer
5
7
  def parse_time(time)
6
8
  case time
7
9
  when /\A\d+\z/
@@ -1,15 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
2
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
- # Unlike the stuff for dtas-player, dtas-partstats is fairly tied to sox
5
4
  require_relative '../dtas'
6
5
  require_relative 'xs'
7
6
  require_relative 'process'
8
7
  require_relative 'sigevent'
9
8
 
9
+ # backend for the dtas-partstats(1) command
10
+ # Unlike the stuff for dtas-player, dtas-partstats is fairly tied to sox
10
11
  class DTAS::PartStats
11
12
  CMD = 'sox "$INFILE" -n $TRIMFX $SOXFX stats $STATSOPTS'
12
13
  include DTAS::Process
14
+ include DTAS::SpawnFix
13
15
  attr_reader :key_idx
14
16
  attr_reader :key_width
15
17
 
@@ -49,18 +51,14 @@ class DTAS::PartStats
49
51
  rv
50
52
  end
51
53
 
52
- def spawn(trim_part, opts)
54
+ def partstats_spawn(trim_part, opts)
53
55
  rd, wr = IO.pipe
54
56
  env = opts[:env]
55
57
  env = env ? env.dup : {}
56
- env["INFILE"] = @infile
58
+ env["INFILE"] = xs(@infile)
57
59
  env["TRIMFX"] = "trim #{trim_part.tbeg}s #{trim_part.tlen}s"
58
60
  opts = { pgroup: true, close_others: true, err: wr }
59
- pid = begin
60
- Process.spawn(env, CMD, opts)
61
- rescue Errno::EINTR # Ruby bug?
62
- retry
63
- end
61
+ pid = spawn(env, CMD, opts)
64
62
  wr.close
65
63
  [ pid, rd ]
66
64
  end
@@ -74,7 +72,7 @@ class DTAS::PartStats
74
72
  stats = []
75
73
  fails = []
76
74
  do_spawn = lambda do |trim_part|
77
- pid, rpipe = spawn(trim_part, opts)
75
+ pid, rpipe = partstats_spawn(trim_part, opts)
78
76
  rset[rpipe] = [ trim_part, "" ]
79
77
  pids[pid] = [ trim_part, rpipe ]
80
78
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  begin
4
4
  require 'io/splice'
@@ -7,6 +7,7 @@ end
7
7
  require_relative '../dtas'
8
8
  require_relative 'writable_iter'
9
9
 
10
+ # pipe wrapper for -player sinks
10
11
  class DTAS::Pipe < IO # :nodoc:
11
12
  include DTAS::WritableIter
12
13
  attr_accessor :sink
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
1
+ # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  require 'yaml'
4
4
  require 'shellwords'
@@ -8,6 +8,7 @@ require_relative 'source'
8
8
  require_relative 'source/sox'
9
9
  require_relative 'source/av'
10
10
  require_relative 'source/ff'
11
+ require_relative 'source/splitfx'
11
12
  require_relative 'source/cmd'
12
13
  require_relative 'sink'
13
14
  require_relative 'unix_server'
@@ -17,6 +18,7 @@ require_relative 'rg_state'
17
18
  require_relative 'state_file'
18
19
  require_relative 'tracklist'
19
20
 
21
+ # the core of dtas-player(1)
20
22
  class DTAS::Player # :nodoc:
21
23
  require_relative 'player/client_handler'
22
24
  include DTAS::XS
@@ -43,10 +45,12 @@ class DTAS::Player # :nodoc:
43
45
  @sink_buf = DTAS::Buffer.new
44
46
  @current = nil
45
47
  @watchers = {}
48
+ @trim = nil
46
49
  @source_map = {
47
- "sox" => DTAS::Source::Sox.new,
50
+ "sox" => (sox = DTAS::Source::Sox.new),
48
51
  "av" => DTAS::Source::Av.new,
49
52
  "ff" => DTAS::Source::Ff.new,
53
+ "splitfx" => DTAS::Source::SplitFX.new(sox),
50
54
  }
51
55
  source_map_reload
52
56
  end
@@ -56,7 +60,7 @@ class DTAS::Player # :nodoc:
56
60
  end
57
61
 
58
62
  def wall(msg)
59
- __wall(xs(Array(msg)))
63
+ __wall(xs(msg))
60
64
  end
61
65
 
62
66
  def __wall(msg)
@@ -80,6 +84,7 @@ class DTAS::Player # :nodoc:
80
84
  rv = {}
81
85
  rv["socket"] = @socket
82
86
  rv["paused"] = @paused if @paused
87
+ rv["trim"] = @trim if @trim
83
88
  src_map = rv["source"] = {}
84
89
  @source_map.each do |name, src|
85
90
  src_hsh = src.to_state_hash
@@ -117,6 +122,10 @@ class DTAS::Player # :nodoc:
117
122
  rv
118
123
  end
119
124
 
125
+ def to_omap(hash)
126
+ YAML::Omap === hash ? hash : YAML::Omap.new.merge!(hash)
127
+ end
128
+
120
129
  def self.load(hash)
121
130
  rv = new
122
131
  rv.instance_eval do
@@ -128,7 +137,7 @@ class DTAS::Player # :nodoc:
128
137
  v = v["buffer_size"]
129
138
  @sink_buf.buffer_size = v
130
139
  end
131
- %w(socket queue paused bypass).each do |k|
140
+ %w(socket queue paused bypass trim).each do |k|
132
141
  v = hash[k] or next
133
142
  instance_variable_set("@#{k}", v)
134
143
  end
@@ -147,6 +156,7 @@ class DTAS::Player # :nodoc:
147
156
  @source_map.each do |name, src|
148
157
  src_hsh = v[name] or next
149
158
  src.load!(src_hsh)
159
+ src.env = to_omap(src.env)
150
160
  end
151
161
  source_map_reload
152
162
  end
@@ -158,6 +168,7 @@ class DTAS::Player # :nodoc:
158
168
  if sinks = hash["sinks"]
159
169
  sinks.each do |sink_hsh|
160
170
  sink = DTAS::Sink.load(sink_hsh)
171
+ sink.env = to_omap(sink.env)
161
172
  @sinks[sink.name] = sink
162
173
  end
163
174
  end
@@ -238,6 +249,8 @@ class DTAS::Player # :nodoc:
238
249
  io.emit(Dir.pwd)
239
250
  when "tl"
240
251
  tl_handler(io, msg)
252
+ when "trim"
253
+ trim_handler(io, msg)
241
254
  end
242
255
  end
243
256
 
@@ -346,7 +359,7 @@ class DTAS::Player # :nodoc:
346
359
  @sinks.each_value do |sink|
347
360
  sink.active or next
348
361
  next if sink.pid
349
- @targets.concat(sink.spawn(@format))
362
+ @targets.concat(sink.sink_spawn(@format))
350
363
  end
351
364
  if @targets[0]
352
365
  @targets.sort_by! { |t| t.sink.prio }
@@ -358,23 +371,25 @@ class DTAS::Player # :nodoc:
358
371
  end
359
372
  end
360
373
 
361
- def try_file(*args)
374
+ def try_file(file, offset = nil)
362
375
  @sources.each do |src|
363
- rv = src.try(*args) and return rv
376
+ rv = src.try(file, offset, @trim) and return rv
364
377
  end
365
378
 
366
379
  # keep going down the list until we find something
367
380
  while source_spec = @queue.shift
381
+ path, off = source_spec
368
382
  @sources.each do |src|
369
- rv = src.try(*source_spec) and return rv
383
+ rv = src.try(path, off, @trim) and return rv
370
384
  end
371
385
  end
372
386
 
373
387
  # don't get stuck in an infinite loop if @tl.repeat==true and we can't
374
388
  # decode anything (FS errors, sox uninstalled, etc...)
375
389
  while path_off = @tl.advance_track(false)
390
+ path, off = path_off
376
391
  @sources.each do |src|
377
- rv = src.try(*path_off) and return rv
392
+ rv = src.try(path, off, @trim) and return rv
378
393
  end
379
394
  end
380
395
 
@@ -383,6 +398,7 @@ class DTAS::Player # :nodoc:
383
398
  end
384
399
 
385
400
  def next_source(source_spec)
401
+ @current.respond_to?(:watch_end) and @current.watch_end(@srv)
386
402
  @current = nil
387
403
  if source_spec
388
404
  case source_spec
@@ -397,7 +413,7 @@ class DTAS::Player # :nodoc:
397
413
  msg = %W(command #{pending.command_string})
398
414
  end
399
415
 
400
- unless @bypass.empty?
416
+ if ! @bypass.empty? && pending.respond_to?(:format)
401
417
  new_fmt = bypass_match!(@format.dup, pending.format)
402
418
  if new_fmt != @format
403
419
  stop_sinks # we may fail to start below
@@ -410,7 +426,13 @@ class DTAS::Player # :nodoc:
410
426
 
411
427
  dst = @sink_buf
412
428
  pending.dst_assoc(dst)
413
- pending.spawn(@format, @rg, out: dst.wr, in: "/dev/null")
429
+ pending.src_spawn(@format, @rg, out: dst.wr, in: "/dev/null")
430
+
431
+ # watch and restart on modifications
432
+ pending.respond_to?(:watch_begin) and
433
+ @srv.wait_ctl(pending.watch_begin(method(:__current_requeue)),
434
+ :wait_readable)
435
+
414
436
  @current = pending
415
437
  @srv.wait_ctl(dst, :wait_readable)
416
438
  wall(msg)