dtas 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/GIT-VERSION-GEN +1 -1
- data/bin/dtas-partstats +39 -0
- data/bin/dtas-tl +1 -1
- data/examples/trimfx.sample.yml +30 -0
- data/lib/dtas/buffer.rb +4 -5
- data/lib/dtas/buffer/read_write.rb +11 -6
- data/lib/dtas/buffer/splice.rb +13 -9
- data/lib/dtas/compat_rbx.rb +12 -0
- data/lib/dtas/fadefx.rb +32 -0
- data/lib/dtas/format.rb +26 -0
- data/lib/dtas/parse_time.rb +29 -0
- data/lib/dtas/partstats.rb +187 -0
- data/lib/dtas/pipe.rb +5 -0
- data/lib/dtas/player.rb +6 -3
- data/lib/dtas/player/client_handler.rb +17 -14
- data/lib/dtas/source/sox.rb +1 -19
- data/lib/dtas/splitfx.rb +7 -16
- data/lib/dtas/tracklist.rb +18 -6
- data/lib/dtas/trimfx.rb +78 -0
- data/lib/dtas/unix_accepted.rb +1 -0
- data/lib/dtas/unix_client.rb +2 -1
- data/lib/dtas/unix_server.rb +2 -2
- data/test/test_buffer.rb +0 -20
- data/test/test_fadefx.rb +18 -0
- data/test/test_trimfx.rb +50 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95cea9c51fc4befa828c208dabf229b37dbc3582
|
4
|
+
data.tar.gz: 4bb3019027d4864ea6d6e9fd8026b236c2f0070f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 795146ad7d81aeabfa30ad36b1de0bf92402428f1c6388f9ba5e2e51df95a773feaf312d38889aa88ee9ab635d61e6b9f082518a03b9614636107b4ebb5dc331
|
7
|
+
data.tar.gz: 75076a786083803900a61adee4847326866f6e08cd8bf624c1b3b4fa021e1f80a991c37be78782caae3888ec5d3c26c6869f29de6bc016c5b65dd90c499e3a6c
|
data/GIT-VERSION-GEN
CHANGED
data/bin/dtas-partstats
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
# TODO
|
5
|
+
# - option parsing: sox effects, stats effect options
|
6
|
+
# - support piping out to external processes
|
7
|
+
# - configurable output formatting
|
8
|
+
# - Sequel/SQLite support
|
9
|
+
require 'dtas/partstats'
|
10
|
+
infile = ARGV[0] or abort "usage: #$0 INFILE"
|
11
|
+
ps = DTAS::PartStats.new(infile)
|
12
|
+
opts = {
|
13
|
+
jobs: `nproc 2>/dev/null || echo 2`.to_i
|
14
|
+
}
|
15
|
+
stats = ps.run(opts)
|
16
|
+
|
17
|
+
headers = ps.key_idx.to_a
|
18
|
+
headers = headers.sort_by! { |(n,i)| i }.map! { |(n,_)| n }
|
19
|
+
width = ps.key_width
|
20
|
+
print " time "
|
21
|
+
puts(headers.map do |h|
|
22
|
+
cols = width[h]
|
23
|
+
sprintf("% #{(cols * 6)+cols-1}s", h.tr(' ','_'))
|
24
|
+
end.join(" | "))
|
25
|
+
|
26
|
+
stats.each do |row|
|
27
|
+
trim_part = row.shift
|
28
|
+
print "#{trim_part.hhmmss} "
|
29
|
+
puts(row.map do |group|
|
30
|
+
group.map do |f|
|
31
|
+
case f
|
32
|
+
when Float
|
33
|
+
sprintf("% 6.2f", f)
|
34
|
+
else
|
35
|
+
sprintf("% 6s", f)
|
36
|
+
end
|
37
|
+
end.join(" ")
|
38
|
+
end.join(" | "))
|
39
|
+
end
|
data/bin/dtas-tl
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
# To the extent possible under law, Eric Wong has waived all copyright and
|
2
|
+
# related or neighboring rights to this example.
|
3
|
+
# Note: be sure to update test/test_trimfx.rb if you change this,
|
4
|
+
# test_trimfx.rb relies on this.
|
5
|
+
---
|
6
|
+
infile: foo.flac
|
7
|
+
env:
|
8
|
+
PATH: $PATH
|
9
|
+
SOX_OPTS: $SOX_OPTS -R
|
10
|
+
I2: second.flac
|
11
|
+
I3: third.flac
|
12
|
+
comments:
|
13
|
+
ARTIST: John Smith
|
14
|
+
ALBUM: Hello World
|
15
|
+
YEAR: 2013
|
16
|
+
track_start: 1
|
17
|
+
effects:
|
18
|
+
# the fade parameter sets the default fade for every subsequent effect
|
19
|
+
- fade=t1,t1;t1,t1 # fade-out-prev,fade-in-main;fade-out-main,fade-in-next
|
20
|
+
|
21
|
+
# the following commands are equivalent
|
22
|
+
- trim 52 =53 sh sox $SOXIN $SOXOUT $TRIMFX vol -6dB $FADEFX
|
23
|
+
- trim 52 1 sox vol -6dB # shorthand
|
24
|
+
|
25
|
+
# as are the following (for little endian machines)
|
26
|
+
- trim 52 1 eca -eadb:-6
|
27
|
+
- trim 52 1 sh sox $SOXIN $SOX2ECA $TRIMFX | ecasound $ECAFMT
|
28
|
+
-i stdin -o stdout -eadb:-6 | sox $ECA2SOX - $SOXOUT $FADEFX
|
29
|
+
# SOX2ECA='-tf32 -c$CHANNELS -r$RATE'
|
30
|
+
# ECAFMT='-f32_le,$CHANNELS,$RATE
|
data/lib/dtas/buffer.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
2
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require 'io/wait'
|
3
4
|
require_relative '../dtas'
|
5
|
+
require_relative 'compat_rbx' # IO#nread
|
4
6
|
|
5
7
|
class DTAS::Buffer # :nodoc:
|
6
8
|
begin
|
@@ -47,16 +49,13 @@ class DTAS::Buffer # :nodoc:
|
|
47
49
|
# - some type of StandardError
|
48
50
|
# - nil
|
49
51
|
def broadcast(targets)
|
50
|
-
bytes = inflight
|
51
|
-
return :wait_readable if 0 == bytes # spurious wakeup
|
52
|
-
|
53
52
|
case targets.size
|
54
53
|
when 0
|
55
54
|
:ignore # this will pause decoders
|
56
55
|
when 1
|
57
|
-
broadcast_one(targets
|
56
|
+
broadcast_one(targets)
|
58
57
|
else # infinity
|
59
|
-
broadcast_inf(targets
|
58
|
+
broadcast_inf(targets)
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
2
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
-
require 'io/wait'
|
4
3
|
require 'io/nonblock'
|
5
4
|
require_relative '../../dtas'
|
6
5
|
require_relative '../pipe'
|
@@ -17,25 +16,30 @@ module DTAS::Buffer::ReadWrite # :nodoc:
|
|
17
16
|
def discard(bytes)
|
18
17
|
buf = _rbuf
|
19
18
|
begin
|
20
|
-
@to_io.
|
19
|
+
@to_io.readpartial(bytes, buf)
|
21
20
|
bytes -= buf.bytesize
|
21
|
+
rescue EOFError
|
22
|
+
return
|
22
23
|
end until bytes == 0
|
23
24
|
end
|
24
25
|
|
25
26
|
# always block when we have a single target
|
26
|
-
def broadcast_one(targets
|
27
|
+
def broadcast_one(targets)
|
27
28
|
buf = _rbuf
|
28
|
-
@to_io.
|
29
|
+
@to_io.readpartial(MAX_AT_ONCE, buf)
|
29
30
|
n = targets[0].write(buf) # IO#write has write-in-full behavior
|
30
31
|
@bytes_xfer += n
|
31
32
|
:wait_readable
|
33
|
+
rescue EOFError
|
34
|
+
nil
|
32
35
|
rescue Errno::EPIPE, IOError => e
|
33
36
|
__dst_error(targets[0], e)
|
34
37
|
targets.clear
|
35
38
|
nil # do not return error here, we already spewed an error message
|
36
39
|
end
|
37
40
|
|
38
|
-
def broadcast_inf(targets
|
41
|
+
def broadcast_inf(targets)
|
42
|
+
bytes = inflight
|
39
43
|
nr_nb = targets.count { |sink| sink.nonblock? }
|
40
44
|
if nr_nb == 0 || nr_nb == targets.size
|
41
45
|
# if all targets are full, don't start until they're all writable
|
@@ -56,7 +60,8 @@ module DTAS::Buffer::ReadWrite # :nodoc:
|
|
56
60
|
bytes = bytes > MAX_AT_ONCE ? MAX_AT_ONCE : bytes
|
57
61
|
buf = _rbuf
|
58
62
|
@to_io.read(bytes, buf)
|
59
|
-
|
63
|
+
n = buf.bytesize
|
64
|
+
@bytes_xfer += n
|
60
65
|
|
61
66
|
targets.delete_if do |dst|
|
62
67
|
begin
|
data/lib/dtas/buffer/splice.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
2
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
-
require 'io/wait'
|
4
3
|
require 'io/nonblock'
|
5
4
|
require 'io/splice'
|
6
5
|
require_relative '../../dtas'
|
@@ -8,6 +7,7 @@ require_relative '../pipe'
|
|
8
7
|
|
9
8
|
module DTAS::Buffer::Splice # :nodoc:
|
10
9
|
MAX_AT_ONCE = 4096 # page size in Linux
|
10
|
+
MAX_AT_ONCE_1 = 65536
|
11
11
|
MAX_SIZE = File.read("/proc/sys/fs/pipe-max-size").to_i
|
12
12
|
DEVNULL = File.open("/dev/null", "r+")
|
13
13
|
F_MOVE = IO::Splice::F_MOVE
|
@@ -28,9 +28,9 @@ 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)
|
32
32
|
# single output is always non-blocking
|
33
|
-
s = IO.trysplice(@to_io, nil, targets[0], nil,
|
33
|
+
s = IO.trysplice(@to_io, nil, targets[0], nil, MAX_AT_ONCE_1, F_MOVE)
|
34
34
|
if Symbol === s
|
35
35
|
targets # our one and only target blocked on write
|
36
36
|
else
|
@@ -48,11 +48,14 @@ module DTAS::Buffer::Splice # :nodoc:
|
|
48
48
|
most_teed = 0
|
49
49
|
targets.delete_if do |dst|
|
50
50
|
begin
|
51
|
-
t = dst.nonblock? ?
|
51
|
+
t = (dst.nonblock? || most_teed == 0) ?
|
52
52
|
IO.trytee(@to_io, dst, chunk_size) :
|
53
53
|
IO.tee(@to_io, dst, chunk_size, WAITALL)
|
54
54
|
if Integer === t
|
55
|
-
|
55
|
+
if t > most_teed
|
56
|
+
chunk_size = t if most_teed == 0
|
57
|
+
most_teed = t
|
58
|
+
end
|
56
59
|
else
|
57
60
|
blocked << dst
|
58
61
|
end
|
@@ -65,7 +68,7 @@ module DTAS::Buffer::Splice # :nodoc:
|
|
65
68
|
most_teed
|
66
69
|
end
|
67
70
|
|
68
|
-
def broadcast_inf(targets
|
71
|
+
def broadcast_inf(targets)
|
69
72
|
if targets.none? { |sink| sink.nonblock? }
|
70
73
|
# if all targets are blocking, don't start until they're all writable
|
71
74
|
r = IO.select(nil, targets, nil, 0) or return targets
|
@@ -80,9 +83,10 @@ module DTAS::Buffer::Splice # :nodoc:
|
|
80
83
|
end
|
81
84
|
|
82
85
|
# don't pin too much on one target
|
83
|
-
bytes =
|
84
|
-
|
86
|
+
bytes = MAX_AT_ONCE
|
85
87
|
last = targets.pop # we splice to the last one, tee to the rest
|
88
|
+
|
89
|
+
# this may return zero if all targets were non-blocking
|
86
90
|
most_teed = __broadcast_tee(blocked, targets, bytes)
|
87
91
|
|
88
92
|
# don't splice more than the largest amount we successfully teed
|
@@ -90,7 +94,7 @@ module DTAS::Buffer::Splice # :nodoc:
|
|
90
94
|
|
91
95
|
begin
|
92
96
|
targets << last
|
93
|
-
if last.nonblock?
|
97
|
+
if last.nonblock? || most_teed == 0
|
94
98
|
s = IO.trysplice(@to_io, nil, last, nil, bytes, F_MOVE)
|
95
99
|
if Symbol === s
|
96
100
|
blocked << last
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
|
4
|
+
# ref: https://github.com/rubysl/rubysl-io-wait/issues/1
|
5
|
+
# this ignores buffers and is Linux-only
|
6
|
+
class IO
|
7
|
+
def nread
|
8
|
+
buf = "\0" * 8
|
9
|
+
ioctl(0x541B, buf)
|
10
|
+
buf.unpack("l_")[0]
|
11
|
+
end
|
12
|
+
end if ! IO.method_defined?(:nread) && RUBY_PLATFORM =~ /linux/
|
data/lib/dtas/fadefx.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative '../dtas'
|
4
|
+
require_relative 'parse_time'
|
5
|
+
|
6
|
+
class DTAS::FadeFX
|
7
|
+
include DTAS::ParseTime
|
8
|
+
attr_reader :out_prev, :in_main, :out_main, :in_next
|
9
|
+
F = Struct.new(:type, :len)
|
10
|
+
|
11
|
+
def initialize(args)
|
12
|
+
args =~ /\Afade=([^,]*),([^,]*);([^,]*),([^,]*)\z/ or
|
13
|
+
raise ArgumentError, "bad fade format"
|
14
|
+
fades = [ $1, $2, $3, $4 ]
|
15
|
+
%w(out_prev in_main out_main in_next).each do |iv|
|
16
|
+
instance_variable_set("@#{iv}", parse!(fades.shift))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# q - quarter of a sine wave
|
21
|
+
# h - half a sine wave
|
22
|
+
# t - linear (`triangular') slope
|
23
|
+
# l - logarithmic
|
24
|
+
# p - inverted parabola
|
25
|
+
# default is 't' (sox defaults to 'l', but triangular makes more sense
|
26
|
+
# when concatenating
|
27
|
+
def parse!(str)
|
28
|
+
type = "t"
|
29
|
+
str.sub!(/\A([a-z])/, "") and type = $1
|
30
|
+
F[type, parse_time(str)]
|
31
|
+
end
|
32
|
+
end
|
data/lib/dtas/format.rb
CHANGED
@@ -35,6 +35,32 @@ class DTAS::Format # :nodoc:
|
|
35
35
|
fmt
|
36
36
|
end
|
37
37
|
|
38
|
+
# some of these are sox-only, but that's what we mainly care about
|
39
|
+
# for audio-editing. We only use ffmpeg/avconv for odd files during
|
40
|
+
# playback.
|
41
|
+
|
42
|
+
extend DTAS::Process
|
43
|
+
|
44
|
+
def self.precision(env, infile)
|
45
|
+
# sox.git f4562efd0aa3
|
46
|
+
qx(env, %W(soxi -p #{infile}), err: "/dev/null").to_i
|
47
|
+
rescue # fallback to parsing the whole output
|
48
|
+
s = qx(env, %W(soxi #{infile}), err: "/dev/null")
|
49
|
+
s =~ /Precision\s+:\s*(\d+)-bit/n
|
50
|
+
v = $1.to_i
|
51
|
+
return v if v > 0
|
52
|
+
raise TypeError, "could not determine precision for #{infile}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.from_file(env, infile)
|
56
|
+
fmt = new
|
57
|
+
fmt.channels = qx(env, %W(soxi -c #{infile})).to_i
|
58
|
+
fmt.type = qx(env, %W(soxi -t #{infile})).strip
|
59
|
+
fmt.rate = qx(env, %W(soxi -r #{infile})).to_i
|
60
|
+
fmt.bits ||= precision(env, infile)
|
61
|
+
fmt
|
62
|
+
end
|
63
|
+
|
38
64
|
def initialize
|
39
65
|
FORMAT_DEFAULTS.each do |k,v|
|
40
66
|
instance_variable_set("@#{k}", v)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative '../dtas'
|
4
|
+
module DTAS::ParseTime
|
5
|
+
def parse_time(time)
|
6
|
+
case time
|
7
|
+
when /\A\d+\z/
|
8
|
+
time.to_i
|
9
|
+
when /\A[\d\.]+\z/
|
10
|
+
time.to_f
|
11
|
+
when /\A[:\d\.]+\z/
|
12
|
+
hhmmss = time.dup
|
13
|
+
rv = hhmmss.sub!(/\.(\d+)\z/, "") ? "0.#$1".to_f : 0
|
14
|
+
|
15
|
+
# deal with HH:MM:SS
|
16
|
+
t = hhmmss.split(/:/)
|
17
|
+
raise ArgumentError, "Bad time format: #{hhmmss}" if t.size > 3
|
18
|
+
|
19
|
+
mult = 1
|
20
|
+
while part = t.pop
|
21
|
+
rv += part.to_i * mult
|
22
|
+
mult *= 60
|
23
|
+
end
|
24
|
+
rv
|
25
|
+
else
|
26
|
+
raise ArgumentError, "unparseable: #{time.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
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
|
+
require_relative '../dtas'
|
6
|
+
require_relative 'xs'
|
7
|
+
require_relative 'process'
|
8
|
+
require_relative 'sigevent'
|
9
|
+
|
10
|
+
class DTAS::PartStats
|
11
|
+
CMD = 'sox "$INFILE" -n $TRIMFX $SOXFX stats $STATSOPTS'
|
12
|
+
include DTAS::Process
|
13
|
+
attr_reader :key_idx
|
14
|
+
attr_reader :key_width
|
15
|
+
|
16
|
+
class TrimPart < Struct.new(:tbeg, :tlen, :rate)
|
17
|
+
def sec
|
18
|
+
tbeg / rate
|
19
|
+
end
|
20
|
+
|
21
|
+
def hhmmss
|
22
|
+
Time.at(sec).strftime("%H:%M:%S")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(infile)
|
27
|
+
@infile = infile
|
28
|
+
%w(samples rate channels).each do |iv|
|
29
|
+
sw = iv[0] # -s, -r, -c
|
30
|
+
i = qx(%W(soxi -#{sw} #@infile)).to_i
|
31
|
+
raise ArgumentError, "invalid #{iv}: #{i}" if i <= 0
|
32
|
+
instance_variable_set("@#{iv}", i)
|
33
|
+
end
|
34
|
+
|
35
|
+
# "Pk lev dB" => 1, "RMS lev dB" => 2, ...
|
36
|
+
@key_nr = 0
|
37
|
+
@key_idx = Hash.new { |h,k| h[k] = (@key_nr += 1) }
|
38
|
+
@key_width = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def partitions(chunk_sec)
|
42
|
+
n = 0
|
43
|
+
part_samples = chunk_sec * @rate
|
44
|
+
rv = []
|
45
|
+
begin
|
46
|
+
rv << TrimPart.new(n, part_samples, @rate)
|
47
|
+
n += part_samples
|
48
|
+
end while n < @samples
|
49
|
+
rv
|
50
|
+
end
|
51
|
+
|
52
|
+
def spawn(trim_part, opts)
|
53
|
+
rd, wr = IO.pipe
|
54
|
+
env = opts[:env]
|
55
|
+
env = env ? env.dup : {}
|
56
|
+
env["INFILE"] = @infile
|
57
|
+
env["TRIMFX"] = "trim #{trim_part.tbeg}s #{trim_part.tlen}s"
|
58
|
+
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
|
64
|
+
wr.close
|
65
|
+
[ pid, rd ]
|
66
|
+
end
|
67
|
+
|
68
|
+
def run(opts = {})
|
69
|
+
sev = DTAS::Sigevent.new
|
70
|
+
trap(:CHLD) { sev.signal }
|
71
|
+
jobs = opts[:jobs] || 2
|
72
|
+
pids = {}
|
73
|
+
rset = {}
|
74
|
+
stats = []
|
75
|
+
fails = []
|
76
|
+
do_spawn = lambda do |trim_part|
|
77
|
+
pid, rpipe = spawn(trim_part, opts)
|
78
|
+
rset[rpipe] = [ trim_part, "" ]
|
79
|
+
pids[pid] = [ trim_part, rpipe ]
|
80
|
+
end
|
81
|
+
|
82
|
+
parts = partitions(opts[:chunk_length] || 10)
|
83
|
+
jobs.times do
|
84
|
+
trim_part = parts.shift or break
|
85
|
+
do_spawn.call(trim_part)
|
86
|
+
end
|
87
|
+
|
88
|
+
rset[sev] = true
|
89
|
+
|
90
|
+
while pids.size > 0
|
91
|
+
r = IO.select(rset.keys) or next
|
92
|
+
r[0].each do |rd|
|
93
|
+
if DTAS::Sigevent === rd
|
94
|
+
rd.readable_iter do |_,_|
|
95
|
+
begin
|
96
|
+
pid, status = Process.waitpid2(-1, Process::WNOHANG)
|
97
|
+
pid or break
|
98
|
+
done = pids.delete(pid)
|
99
|
+
done_part = done[0]
|
100
|
+
if status.success?
|
101
|
+
trim_part = parts.shift and do_spawn.call(trim_part)
|
102
|
+
puts "DONE #{done_part}" if $DEBUG
|
103
|
+
else
|
104
|
+
fails << [ done_part, status ]
|
105
|
+
end
|
106
|
+
rescue Errno::ECHILD
|
107
|
+
break
|
108
|
+
end while true
|
109
|
+
end
|
110
|
+
else
|
111
|
+
# spurious wakeup should not happen on local pipes,
|
112
|
+
# so readpartial should be safe
|
113
|
+
trim_part, buf = rset[rd]
|
114
|
+
begin
|
115
|
+
buf << rd.readpartial(666)
|
116
|
+
rescue EOFError
|
117
|
+
rset.delete(rd)
|
118
|
+
rd.close
|
119
|
+
parse_stats(stats, trim_part, buf)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
return stats if fails.empty? && parts.empty?
|
126
|
+
fails.each do |(trim_part,status)|
|
127
|
+
warn "FAIL #{status.inspect} #{trim_part}"
|
128
|
+
end
|
129
|
+
false
|
130
|
+
ensure
|
131
|
+
sev.close
|
132
|
+
end
|
133
|
+
|
134
|
+
# "sox INFILE -n stats" example output
|
135
|
+
=begin
|
136
|
+
Overall Left Right
|
137
|
+
DC offset 0.001074 0.000938 0.001074
|
138
|
+
Min level -0.997711 -0.997711 -0.997711
|
139
|
+
Max level 0.997681 0.997681 0.997681
|
140
|
+
Pk lev dB -0.02 -0.02 -0.02
|
141
|
+
RMS lev dB -10.38 -9.90 -10.92
|
142
|
+
RMS Pk dB -4.62 -4.62 -5.10
|
143
|
+
RMS Tr dB -87.25 -86.58 -87.25
|
144
|
+
Crest factor - 3.12 3.51
|
145
|
+
Flat factor 19.41 19.66 18.89
|
146
|
+
Pk count 117k 156k 77.4k
|
147
|
+
Bit-depth 16/16 16/16 16/16
|
148
|
+
Num samples 17.2M
|
149
|
+
Length s 389.373
|
150
|
+
Scale max 1.000000
|
151
|
+
Window s 0.050
|
152
|
+
|
153
|
+
becomes:
|
154
|
+
[
|
155
|
+
TrimPart,
|
156
|
+
[ -0.02, -0.02, -0.02 ], # Pk lev dB
|
157
|
+
[ -10.38, -9.90, -10.92 ], # RMS lev dB
|
158
|
+
...
|
159
|
+
]
|
160
|
+
=end
|
161
|
+
|
162
|
+
def parse_stats(stats, trim_part, buf)
|
163
|
+
trim_row = [ trim_part ]
|
164
|
+
buf.split(/\n/).each do |line|
|
165
|
+
do_map = true
|
166
|
+
case line
|
167
|
+
when /\A(\S+ \S+ dB)\s/, /\A(Crest factor)\s+-\s/
|
168
|
+
nshift = 3
|
169
|
+
when /\A(Flat factor)\s/
|
170
|
+
nshift = 2
|
171
|
+
when /\A(Pk count)\s/
|
172
|
+
nshift = 2
|
173
|
+
do_map = false
|
174
|
+
else
|
175
|
+
next
|
176
|
+
end
|
177
|
+
key = $1
|
178
|
+
key.freeze
|
179
|
+
key_idx = @key_idx[key]
|
180
|
+
parts = line.split(/\s+/)
|
181
|
+
nshift.times { parts.shift } # remove stuff we don't need
|
182
|
+
@key_width[key] = parts.size
|
183
|
+
trim_row[key_idx] = do_map ? parts.map!(&:to_f) : parts
|
184
|
+
end
|
185
|
+
stats[trim_part.tbeg / trim_part.tlen] = trim_row
|
186
|
+
end
|
187
|
+
end
|
data/lib/dtas/pipe.rb
CHANGED
data/lib/dtas/player.rb
CHANGED
@@ -165,9 +165,13 @@ class DTAS::Player # :nodoc:
|
|
165
165
|
rv
|
166
166
|
end
|
167
167
|
|
168
|
+
def need_to_queue
|
169
|
+
@current || @queue[0] || @paused
|
170
|
+
end
|
171
|
+
|
168
172
|
def enq_handler(io, msg)
|
169
173
|
# check @queue[0] in case we have no sinks
|
170
|
-
if
|
174
|
+
if need_to_queue
|
171
175
|
@queue << msg
|
172
176
|
else
|
173
177
|
next_source(msg)
|
@@ -177,7 +181,7 @@ class DTAS::Player # :nodoc:
|
|
177
181
|
|
178
182
|
def do_enq_head(io, msg)
|
179
183
|
# check @queue[0] in case we have no sinks
|
180
|
-
if
|
184
|
+
if need_to_queue
|
181
185
|
@queue.unshift(msg)
|
182
186
|
else
|
183
187
|
next_source(msg)
|
@@ -415,7 +419,6 @@ class DTAS::Player # :nodoc:
|
|
415
419
|
|
416
420
|
def player_idle
|
417
421
|
stop_sinks if @sink_buf.inflight == 0
|
418
|
-
@tl.reset unless @paused
|
419
422
|
wall("idle")
|
420
423
|
end
|
421
424
|
|
@@ -344,7 +344,12 @@ module DTAS::Player::ClientHandler # :nodoc:
|
|
344
344
|
# no wall, next_source will wall on new track
|
345
345
|
@paused = false
|
346
346
|
return if @current
|
347
|
-
|
347
|
+
n = _next
|
348
|
+
unless n
|
349
|
+
@tl.reset
|
350
|
+
n = _next
|
351
|
+
end
|
352
|
+
next_source(n)
|
348
353
|
end
|
349
354
|
|
350
355
|
def do_play_pause
|
@@ -549,12 +554,11 @@ module DTAS::Player::ClientHandler # :nodoc:
|
|
549
554
|
rescue ArgumentError => e
|
550
555
|
return io.emit("ERR #{e.message}")
|
551
556
|
end
|
552
|
-
_tl_skip if set_as_current
|
553
557
|
|
554
|
-
#
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
+
_tl_skip if set_as_current # if @current is playing, it will restart soon
|
559
|
+
|
560
|
+
# start playing if we're currently idle
|
561
|
+
next_source(_next) unless need_to_queue
|
558
562
|
io.emit("#{track_id}")
|
559
563
|
when "repeat"
|
560
564
|
case msg.shift
|
@@ -568,15 +572,16 @@ module DTAS::Player::ClientHandler # :nodoc:
|
|
568
572
|
when "remove"
|
569
573
|
track_id = msg.shift or return io.emit("ERR track_id not specified")
|
570
574
|
track_id = track_id.to_i
|
571
|
-
|
575
|
+
@tl.remove_track(track_id) or return io.emit("MISSING")
|
572
576
|
|
573
577
|
# skip if we're removing the currently playing track
|
574
|
-
if
|
575
|
-
|
578
|
+
if @current && @current.respond_to?(:infile) &&
|
579
|
+
@current.infile.object_id == track_id
|
576
580
|
_tl_skip
|
577
581
|
end
|
578
|
-
|
579
|
-
|
582
|
+
# drop it from the queue, too, in case it just got requeued or paused
|
583
|
+
@queue.delete_if { |t| Array === t && t[0].object_id == track_id }
|
584
|
+
io.emit("OK")
|
580
585
|
when "get"
|
581
586
|
res = @tl.get_tracks(msg.map! { |i| i.to_i })
|
582
587
|
res.map! { |tid, file| "#{tid}=#{file ? Shellwords.escape(file) : ''}" }
|
@@ -589,9 +594,7 @@ module DTAS::Player::ClientHandler # :nodoc:
|
|
589
594
|
offset = msg.shift # may be nil
|
590
595
|
if @tl.go_to(track_id.to_i, offset)
|
591
596
|
_tl_skip
|
592
|
-
|
593
|
-
next_source(_next)
|
594
|
-
end
|
597
|
+
next_source(_next) unless need_to_queue
|
595
598
|
io.emit("OK")
|
596
599
|
else
|
597
600
|
io.emit("MISSING")
|
data/lib/dtas/source/sox.rb
CHANGED
@@ -47,26 +47,8 @@ class DTAS::Source::Sox # :nodoc:
|
|
47
47
|
source_file_dup(infile, offset)
|
48
48
|
end
|
49
49
|
|
50
|
-
def precision
|
51
|
-
qx(@env, %W(soxi -p #@infile), err: "/dev/null").to_i # sox.git f4562efd0aa3
|
52
|
-
rescue # fallback to parsing the whole output
|
53
|
-
s = qx(@env, %W(soxi #@infile), err: "/dev/null")
|
54
|
-
s =~ /Precision\s+:\s*(\d+)-bit/n
|
55
|
-
v = $1.to_i
|
56
|
-
return v if v > 0
|
57
|
-
raise TypeError, "could not determine precision for #@infile"
|
58
|
-
end
|
59
|
-
|
60
50
|
def format
|
61
|
-
@format ||=
|
62
|
-
fmt = DTAS::Format.new
|
63
|
-
path = @infile
|
64
|
-
fmt.channels = qx(@env, %W(soxi -c #{path})).to_i
|
65
|
-
fmt.type = qx(@env, %W(soxi -t #{path})).strip
|
66
|
-
fmt.rate = qx(@env, %W(soxi -r #{path})).to_i
|
67
|
-
fmt.bits ||= precision
|
68
|
-
fmt
|
69
|
-
end
|
51
|
+
@format ||= DTAS::Format.from_file(@env, @infile)
|
70
52
|
end
|
71
53
|
|
72
54
|
# This is the number of samples according to the samples in the source
|
data/lib/dtas/splitfx.rb
CHANGED
@@ -134,27 +134,12 @@ class DTAS::SplitFX # :nodoc:
|
|
134
134
|
load_tracks!(hash)
|
135
135
|
end
|
136
136
|
|
137
|
-
# FIXME: duplicate from dtas/source/sox
|
138
|
-
def precision
|
139
|
-
qx(@env, %W(soxi -p #@infile), err: "/dev/null").to_i # sox.git f4562efd0aa3
|
140
|
-
rescue # fallback to parsing the whole output
|
141
|
-
s = qx(@env, %W(soxi #@infile), err: "/dev/null")
|
142
|
-
s =~ /Precision\s+:\s*(\d+)-bit/n
|
143
|
-
v = $1.to_i
|
144
|
-
return v if v > 0
|
145
|
-
raise TypeError, "could not determine precision for #@infile"
|
146
|
-
end
|
147
|
-
|
148
137
|
def load_input!(hash)
|
149
138
|
@infile = hash["infile"] or raise ArgumentError, "'infile' not specified"
|
150
139
|
if infmt = hash["infmt"] # rarely needed
|
151
140
|
@infmt = DTAS::Format.load(infmt)
|
152
141
|
else # likely
|
153
|
-
@infmt = DTAS::Format.
|
154
|
-
@infmt.channels = qx(@env, %W(soxi -c #@infile)).to_i
|
155
|
-
@infmt.rate = qx(@env, %W(soxi -r #@infile)).to_i
|
156
|
-
@infmt.bits ||= precision
|
157
|
-
# we don't care for type
|
142
|
+
@infmt = DTAS::Format.from_file(@env, @infile)
|
158
143
|
end
|
159
144
|
end
|
160
145
|
|
@@ -167,6 +152,12 @@ class DTAS::SplitFX # :nodoc:
|
|
167
152
|
def spawn(target, t, opts)
|
168
153
|
target = @targets[target] || generic_target(target)
|
169
154
|
outfmt = target["format"]
|
155
|
+
|
156
|
+
# default format:
|
157
|
+
unless outfmt
|
158
|
+
outfmt = @infmt.dup
|
159
|
+
outfmt.type = "flac"
|
160
|
+
end
|
170
161
|
env = outfmt.to_env
|
171
162
|
|
172
163
|
# set very high quality resampling if using 24-bit or higher output
|
data/lib/dtas/tracklist.rb
CHANGED
@@ -91,21 +91,33 @@ class DTAS::Tracklist # :nodoc:
|
|
91
91
|
idx = by_track_id[after_track_id] or
|
92
92
|
raise ArgumentError, "after_track_id invalid"
|
93
93
|
@list[idx, 1] = [ @list[idx], track ]
|
94
|
-
|
94
|
+
if set_as_current
|
95
|
+
@pos = idx + 1
|
96
|
+
else
|
97
|
+
@pos += 1 if @pos > idx
|
98
|
+
end
|
95
99
|
else # nil = first_track
|
96
100
|
@list.unshift(track)
|
97
|
-
|
101
|
+
if set_as_current
|
102
|
+
@pos = 0
|
103
|
+
else
|
104
|
+
@pos += 1 if @pos >= 0
|
105
|
+
end
|
98
106
|
end
|
99
107
|
track.object_id
|
100
108
|
end
|
101
109
|
|
102
110
|
def remove_track(track_id)
|
103
111
|
by_track_id = _track_id_map
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
112
|
+
idx = by_track_id.delete(track_id) or return false
|
113
|
+
@list[idx] = nil
|
114
|
+
@list.compact!
|
115
|
+
len = @list.size
|
116
|
+
if @pos >= len
|
117
|
+
@pos = len == 0 ? TL_DEFAULTS["pos"] : len
|
108
118
|
end
|
119
|
+
@goto_pos = @goto_pos = nil # TODO: reposition?
|
120
|
+
true
|
109
121
|
end
|
110
122
|
|
111
123
|
def go_to(track_id, offset_hhmmss = nil)
|
data/lib/dtas/trimfx.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
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 'shellwords'
|
6
|
+
|
7
|
+
class DTAS::TrimFX
|
8
|
+
include DTAS::ParseTime
|
9
|
+
|
10
|
+
attr_reader :tbeg
|
11
|
+
attr_reader :tlen
|
12
|
+
attr_reader :cmd
|
13
|
+
|
14
|
+
def initialize(args)
|
15
|
+
args = args.dup
|
16
|
+
case args.shift
|
17
|
+
when "trim"
|
18
|
+
parse_trim!(args)
|
19
|
+
when "all"
|
20
|
+
@tbeg = 0
|
21
|
+
@tlen = nil
|
22
|
+
else
|
23
|
+
raise ArgumentError, "#{args.inspect} not understood"
|
24
|
+
end
|
25
|
+
case tmp = args.shift
|
26
|
+
when "sh" then @cmd = args
|
27
|
+
when "sox" then tfx_sox(args)
|
28
|
+
when "eca" then tfx_eca(args)
|
29
|
+
when nil
|
30
|
+
@cmd = []
|
31
|
+
else
|
32
|
+
raise ArgumentError, "unknown effect type: #{tmp}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def tfx_sox(args)
|
37
|
+
@cmd = %w(sox $SOXIN $SOXOUT $TRIMFX)
|
38
|
+
@cmd.concat(args)
|
39
|
+
@cmd.concat(%w($FADEFX))
|
40
|
+
end
|
41
|
+
|
42
|
+
def tfx_eca(args)
|
43
|
+
@cmd = %w(sox $SOXIN $SOX2ECA $TRIMFX)
|
44
|
+
@cmd.concat(%w(| ecasound $ECAFMT -i stdin -o stdout))
|
45
|
+
@cmd.concat(args)
|
46
|
+
@cmd.concat(%w(| sox $ECA2SOX - $SOXOUT $FADEFX))
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_sox_arg(format)
|
50
|
+
if @tbeg && @tlen
|
51
|
+
beg = @tbeg * format.rate
|
52
|
+
len = @tlen * format.rate
|
53
|
+
%W(trim #{beg.round}s #{len.round}s)
|
54
|
+
elsif @tbeg
|
55
|
+
return [] if @tbeg == 0
|
56
|
+
beg = @tbeg * format.rate
|
57
|
+
%W(trim #{beg.round}s)
|
58
|
+
else
|
59
|
+
[]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_trim!(args)
|
64
|
+
tbeg = parse_time(args.shift)
|
65
|
+
if args[0] =~ /\A=?[\d\.]+\z/
|
66
|
+
tlen = args.shift
|
67
|
+
is_stop_time = tlen.sub!(/\A=/, "") ? true : false
|
68
|
+
tlen = parse_time(tlen)
|
69
|
+
if is_stop_time
|
70
|
+
tlen = tlen - tbeg
|
71
|
+
end
|
72
|
+
else
|
73
|
+
tlen = nil
|
74
|
+
end
|
75
|
+
@tbeg = tbeg
|
76
|
+
@tlen = tlen
|
77
|
+
end
|
78
|
+
end
|
data/lib/dtas/unix_accepted.rb
CHANGED
data/lib/dtas/unix_client.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative 'xs'
|
|
5
5
|
require 'socket'
|
6
6
|
require 'io/wait'
|
7
7
|
require 'shellwords'
|
8
|
+
require_relative 'compat_rbx'
|
8
9
|
|
9
10
|
class DTAS::UNIXClient # :nodoc:
|
10
11
|
attr_reader :to_io
|
@@ -16,7 +17,7 @@ class DTAS::UNIXClient # :nodoc:
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def initialize(path = self.class.default_path)
|
19
|
-
@to_io = Socket.new(:
|
20
|
+
@to_io = Socket.new(:UNIX, :SEQPACKET, 0)
|
20
21
|
@to_io.connect(Socket.pack_sockaddr_un(path))
|
21
22
|
end
|
22
23
|
|
data/lib/dtas/unix_server.rb
CHANGED
@@ -26,13 +26,13 @@ class DTAS::UNIXServer # :nodoc:
|
|
26
26
|
# lock down access by default, arbitrary commands may run as the
|
27
27
|
# same user dtas-player runs as:
|
28
28
|
old_umask = File.umask(0077)
|
29
|
-
@to_io = Socket.new(:
|
29
|
+
@to_io = Socket.new(:UNIX, :SEQPACKET, 0)
|
30
30
|
addr = Socket.pack_sockaddr_un(path)
|
31
31
|
begin
|
32
32
|
@to_io.bind(addr)
|
33
33
|
rescue Errno::EADDRINUSE
|
34
34
|
# maybe we have an old path leftover from a killed process
|
35
|
-
tmp = Socket.new(:
|
35
|
+
tmp = Socket.new(:UNIX, :SEQPACKET, 0)
|
36
36
|
begin
|
37
37
|
tmp.connect(addr)
|
38
38
|
raise RuntimeError, "socket `#{path}' is in use", []
|
data/test/test_buffer.rb
CHANGED
@@ -51,13 +51,9 @@ class TestBuffer < Testcase
|
|
51
51
|
def test_broadcast_1
|
52
52
|
buf = new_buffer
|
53
53
|
r, w = IO.pipe
|
54
|
-
assert_equal :wait_readable, buf.broadcast([w])
|
55
|
-
assert_equal 0, buf.bytes_xfer
|
56
54
|
buf.wr.write "HIHI"
|
57
55
|
assert_equal :wait_readable, buf.broadcast([w])
|
58
56
|
assert_equal 4, buf.bytes_xfer
|
59
|
-
assert_equal :wait_readable, buf.broadcast([w])
|
60
|
-
assert_equal 4, buf.bytes_xfer
|
61
57
|
tmp = [w]
|
62
58
|
r.close
|
63
59
|
buf.wr.write "HIHI"
|
@@ -90,20 +86,6 @@ class TestBuffer < Testcase
|
|
90
86
|
a[1].nonblock = false
|
91
87
|
b[0].read(b[0].nread)
|
92
88
|
b[1].write(max)
|
93
|
-
t = Thread.new do
|
94
|
-
sleep 0.005
|
95
|
-
[ a[0].read(max.size).size, b[0].read(max.size).size ]
|
96
|
-
end
|
97
|
-
assert_equal 5, buf.__broadcast_tee(blocked, [a[1], b[1]], 5)
|
98
|
-
assert_equal [a[1]], blocked
|
99
|
-
assert_equal [ max.size, max.size ], t.value
|
100
|
-
b[0].close
|
101
|
-
tmp = [a[1], b[1]]
|
102
|
-
|
103
|
-
newerr = tmperr { assert_equal 5, buf.__broadcast_tee(blocked, tmp, 5) }
|
104
|
-
assert_equal [a[1]], blocked
|
105
|
-
assert_match(%r{dropping}, newerr.string)
|
106
|
-
assert_equal [a[1]], tmp
|
107
89
|
end
|
108
90
|
|
109
91
|
def test_broadcast
|
@@ -115,8 +97,6 @@ class TestBuffer < Testcase
|
|
115
97
|
assert_equal 5, buf.bytes_xfer
|
116
98
|
assert_equal "HELLO", a[0].read(5)
|
117
99
|
assert_equal "HELLO", b[0].read(5)
|
118
|
-
assert_equal :wait_readable, buf.broadcast([a[1], b[1]])
|
119
|
-
assert_equal 5, buf.bytes_xfer
|
120
100
|
|
121
101
|
return unless b[1].respond_to?(:pipe_size)
|
122
102
|
|
data/test/test_fadefx.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'dtas/fadefx'
|
5
|
+
|
6
|
+
class TestFadeFX < Testcase
|
7
|
+
def test_fadefx
|
8
|
+
ffx = DTAS::FadeFX.new("fade=t1,t3.1;l4,t1")
|
9
|
+
assert_equal 't', ffx.out_prev.type
|
10
|
+
assert_equal 1, ffx.out_prev.len
|
11
|
+
assert_equal 't', ffx.in_main.type
|
12
|
+
assert_equal 3.1, ffx.in_main.len
|
13
|
+
assert_equal 'l', ffx.out_main.type
|
14
|
+
assert_equal 4, ffx.out_main.len
|
15
|
+
assert_equal 't', ffx.in_next.type
|
16
|
+
assert_equal 1, ffx.in_next.len
|
17
|
+
end
|
18
|
+
end
|
data/test/test_trimfx.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require './test/helper'
|
4
|
+
require 'dtas/trimfx'
|
5
|
+
require 'dtas/format'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class TestTrimFX < Testcase
|
9
|
+
def test_example
|
10
|
+
ex = YAML.load(File.read("examples/trimfx.sample.yml"))
|
11
|
+
effects = []
|
12
|
+
ex["effects"].each do |line|
|
13
|
+
words = Shellwords.split(line)
|
14
|
+
case words[0]
|
15
|
+
when "trim"
|
16
|
+
tfx = DTAS::TrimFX.new(words)
|
17
|
+
assert_equal 52.0, tfx.tbeg
|
18
|
+
assert_equal 1.0, tfx.tlen
|
19
|
+
effects << tfx
|
20
|
+
end
|
21
|
+
end
|
22
|
+
assert_equal 4, effects.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_all
|
26
|
+
tfx = DTAS::TrimFX.new(%w(all))
|
27
|
+
assert_equal 0, tfx.tbeg
|
28
|
+
assert_nil tfx.tlen
|
29
|
+
assert_equal [], tfx.to_sox_arg(DTAS::Format.new)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_time
|
33
|
+
tfx = DTAS::TrimFX.new(%w(trim 2:30 3.1))
|
34
|
+
assert_equal 150, tfx.tbeg
|
35
|
+
assert_equal 3.1, tfx.tlen
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_to_sox_arg
|
39
|
+
tfx = DTAS::TrimFX.new(%w(trim 1 0.5))
|
40
|
+
assert_equal %w(trim 44100s 22050s), tfx.to_sox_arg(DTAS::Format.new)
|
41
|
+
|
42
|
+
tfx = DTAS::TrimFX.new(%w(trim 1 sox vol -1dB))
|
43
|
+
assert_equal %w(trim 44100s), tfx.to_sox_arg(DTAS::Format.new)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_tfx_effects
|
47
|
+
tfx = DTAS::TrimFX.new(%w(trim 1 sox vol -1dB))
|
48
|
+
assert_equal %w(sox $SOXIN $SOXOUT $TRIMFX vol -1dB $FADEFX), tfx.cmd
|
49
|
+
end
|
50
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dtas
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dtas hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |-
|
14
14
|
Free Software command-line tools for audio playback, mastering, and
|
@@ -24,6 +24,7 @@ executables:
|
|
24
24
|
- dtas-cueedit
|
25
25
|
- dtas-enq
|
26
26
|
- dtas-msinkctl
|
27
|
+
- dtas-partstats
|
27
28
|
- dtas-player
|
28
29
|
- dtas-sinkedit
|
29
30
|
- dtas-sourceedit
|
@@ -65,6 +66,7 @@ files:
|
|
65
66
|
- bin/dtas-cueedit
|
66
67
|
- bin/dtas-enq
|
67
68
|
- bin/dtas-msinkctl
|
69
|
+
- bin/dtas-partstats
|
68
70
|
- bin/dtas-player
|
69
71
|
- bin/dtas-sinkedit
|
70
72
|
- bin/dtas-sourceedit
|
@@ -76,16 +78,21 @@ files:
|
|
76
78
|
- dtas.gemspec
|
77
79
|
- examples/README
|
78
80
|
- examples/splitfx.sample.yml
|
81
|
+
- examples/trimfx.sample.yml
|
79
82
|
- lib/dtas.rb
|
80
83
|
- lib/dtas/buffer.rb
|
81
84
|
- lib/dtas/buffer/read_write.rb
|
82
85
|
- lib/dtas/buffer/splice.rb
|
83
86
|
- lib/dtas/command.rb
|
84
87
|
- lib/dtas/compat_onenine.rb
|
88
|
+
- lib/dtas/compat_rbx.rb
|
85
89
|
- lib/dtas/cue_index.rb
|
86
90
|
- lib/dtas/disclaimer.rb
|
87
91
|
- lib/dtas/edit_client.rb
|
92
|
+
- lib/dtas/fadefx.rb
|
88
93
|
- lib/dtas/format.rb
|
94
|
+
- lib/dtas/parse_time.rb
|
95
|
+
- lib/dtas/partstats.rb
|
89
96
|
- lib/dtas/pipe.rb
|
90
97
|
- lib/dtas/player.rb
|
91
98
|
- lib/dtas/player/client_handler.rb
|
@@ -109,6 +116,7 @@ files:
|
|
109
116
|
- lib/dtas/splitfx.rb
|
110
117
|
- lib/dtas/state_file.rb
|
111
118
|
- lib/dtas/tracklist.rb
|
119
|
+
- lib/dtas/trimfx.rb
|
112
120
|
- lib/dtas/unix_accepted.rb
|
113
121
|
- lib/dtas/unix_client.rb
|
114
122
|
- lib/dtas/unix_server.rb
|
@@ -135,6 +143,7 @@ files:
|
|
135
143
|
- test/player_integration.rb
|
136
144
|
- test/test_buffer.rb
|
137
145
|
- test/test_env.rb
|
146
|
+
- test/test_fadefx.rb
|
138
147
|
- test/test_format.rb
|
139
148
|
- test/test_format_change.rb
|
140
149
|
- test/test_player.rb
|
@@ -150,6 +159,7 @@ files:
|
|
150
159
|
- test/test_source_sox.rb
|
151
160
|
- test/test_splitfx.rb
|
152
161
|
- test/test_tracklist.rb
|
162
|
+
- test/test_trimfx.rb
|
153
163
|
- test/test_unixserver.rb
|
154
164
|
- test/test_util.rb
|
155
165
|
homepage: http://dtas.80x24.org/
|