dtas 0.5.0 → 0.6.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.
- 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/
|