dtas 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d233bbb0a87d1fc6c4bdb7f18eba692c721a3333
4
- data.tar.gz: 8f14c5645b6535bd6c22b1e4854ef39ec85d528a
3
+ metadata.gz: a4ce17f3d71d013e20442d569236e8ea2b56a091
4
+ data.tar.gz: f8177dd1cae23bbf4f679b8cfca1062f8583f5e2
5
5
  SHA512:
6
- metadata.gz: 93592a5294b214260e938746ad75408a32889aae8c3667a49ede5952364dd6a9f3d99b793a77c134a9bd66ec15a21eda0f078475d2d375543ebf153714170c68
7
- data.tar.gz: 8fc83ba70d8a7a57f2e4ca26c936bd237b0fd9f4130142f8c500d7eeb4a0f7b15542bf305b79e891c7a2acd7f5edb3750281776ad7d730edc95fcdd3078c5b1b
6
+ metadata.gz: 7d57e1c9be990a92163771a89075cf0432cbe6f4441f4c79ba7efe09288a41fb76b055faf37339ab6de2247a89ad9b36a0e45da5deff39f14b376e417d5e11ca
7
+ data.tar.gz: 5c3ae2d8e5d06f46afe06472b27c9de90c095e7651d69379d45b71589adb3e6d2fffdd24442b095f1e48b0335206ff98a52ff79edcd6845b92bef97fdee364d9
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
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
+ /GIT-VERSION-FILE
3
4
  /Manifest.txt
4
5
  /NEWS
5
6
  /pkg
@@ -10,3 +11,6 @@
10
11
  /man
11
12
  *.gem
12
13
  *.gz
14
+ .manifest
15
+ .gem-manifest
16
+ .tgz-manifest
@@ -32,7 +32,7 @@ Key bindings are inspired partially by mplayer(1)
32
32
  - 9/0 - decrease/increase ReplayGain preamp
33
33
  - 'f'/'F' - decrease/increase ReplayGain fallback_gain value
34
34
  - 'r'/'R' - cycle forward/backwards through ReplayGain modes
35
- - Ctrl-C - exit dtas-console
35
+ - 'q'/Ctrl-C - exit dtas-console
36
36
 
37
37
  # ENVIRONMENT
38
38
 
@@ -80,6 +80,23 @@ Commands here should be alphabetized according to `LC_ALL=C sort'
80
80
  * clear - clear current queue (current track/command continues running)
81
81
  PENDING: this may be renamed to "queue clear" or "queue-clear"
82
82
 
83
+ * cue - display the index/offsets of the file based on the embedded
84
+ cue sheet, if any
85
+
86
+ * cue next - skip to the next section of the track based on the
87
+ embedded cue sheet. This may skip to the next track if there is
88
+ no embedded cue sheet or if playing the last (embedded) track
89
+
90
+ * cue prev - skip to the previous section of the track based on
91
+ the embedded cue sheet. This may just seek to the beginning
92
+ if there is no embedded cue sheet or if we are playing the first
93
+ (embedded) track.
94
+
95
+ * cue goto INTEGER - go to the embedded track with cue index denoted
96
+ by INTEGER (0 is first track as returned by "cue"). Negative
97
+ values of INTEGER allows selecting track relative to the last
98
+ track (-1 is the last track, -2 is the penultimate, and so on).
99
+
83
100
  * current - output information about the currently-playing track/command
84
101
  in YAML. The structure of this is unstable and subject to change.
85
102
 
@@ -111,20 +128,30 @@ Commands here should be alphabetized according to `LC_ALL=C sort'
111
128
  Changing this will affect the $SOXFMT and $ECAFMT environments passed
112
129
  to source and sink commands. Changing this implies a "restart"
113
130
  Changing rate to 48000 is probably useful if you plan on playing to some
114
- laptop sound cards.
131
+ laptop sound cards. In all cases where "bypass" is supported, it
132
+ removes the guarantee of gapless playback as the audio device(s)
133
+ will likely need to be restarted.
134
+
115
135
 
116
- + channels=UNSIGNED - (default: 2 (stereo)) - number of channels
117
- to use internally. sox will internally invoke the remix effect
118
- when decoding.
136
+ + channels=(UNSIGNED|bypass) - (default: 2 (stereo)) - number of
137
+ channels to use internally. sox will internally invoke the remix
138
+ effect when decoding. This supports the value "bypass" (without
139
+ quotes) to avoid the automatic remix effect. Using "bypass" mode
140
+ removes the guarantee of gapless playback, as the audio device will
141
+ likely need to be restarted, introducing an audible gap.
119
142
  + endian=(big|little|swap) - (default: native) - there is probably no
120
143
  point in changing this unless you output over a network sink to
121
144
  a machine of different endianess.
122
- + bits=UNSIGNED - (default: implied from type) - sample precision (decoded)
145
+ + bits=(UNSIGNED|bypass) - (default: implied from type) - sample precision
146
+ (decoded)
123
147
  This may be pointless and removed in the future, since the sample
124
- precision is implied from type.
125
- + rate=UNSIGNED - (default: 44100) - sample rate of audio
148
+ precision is implied from type. This supports the value of "bypass"
149
+ to avoid dither/truncation in later stages.
150
+ + rate=(UNSIGNED|bypass) - (default: 44100) - sample rate of audio
126
151
  Typical values of rate are 44100, 48000, 88200, 96000. Not all
127
152
  DSP effects are compatible with all sampling rates/channels.
153
+ This supports the value of "bypass" as well to avoid introducing
154
+ software resamplers into the playback chain.
128
155
  + type=(s16|s24|s32|u16|u24|u32|f32|f64) - (default: s32)
129
156
  change the raw PCM format. s32 currently offers the best performance
130
157
  when only sox/play are used. f32 may offer better performance if
@@ -31,7 +31,7 @@ HH:MM:SS.FRAC format.
31
31
  * reto REGEXP [POS] - play first track with path matching REGEXP immediately,
32
32
  optionally seek to POS. POS should be a timestamp in HH:MM:SS.FRAC format.
33
33
  * next - play the next track in the tracklist
34
- * previous - play the previous track in the tracklist
34
+ * prev - play the previous track in the tracklist
35
35
  * repeat 1 - repeat the current track
36
36
  * repeat false - disable repeat
37
37
  * repeat true - enable repeat of the whole tracklist
@@ -54,7 +54,7 @@ to skip forward in the tracklist
54
54
 
55
55
  to skip backwards in the tracklist
56
56
 
57
- $ dtas-tl previous
57
+ $ dtas-tl prev
58
58
 
59
59
  # ENVIRONMENT
60
60
 
@@ -3,7 +3,8 @@
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  CONSTANT = "DTAS::VERSION"
5
5
  RVF = "lib/dtas/version.rb"
6
- DEF_VER = "v0.4.0"
6
+ GVF = "GIT-VERSION-FILE"
7
+ DEF_VER = "v0.5.0"
7
8
  vn = DEF_VER
8
9
 
9
10
  # First see if there is a version file (included in release tarballs),
@@ -28,4 +29,13 @@ if new_ruby_version != cur_ruby_version
28
29
  File.open(RVF, "w") { |fp| fp.write(new_ruby_version) }
29
30
  end
30
31
  File.chmod(0644, RVF)
32
+
33
+ # generate the makefile snippet
34
+ new_make_version = "VERSION = #{vn}\n"
35
+ cur_make_version = File.read(GVF) rescue nil
36
+ if new_make_version != cur_make_version
37
+ File.open(GVF, "w") { |fp| fp.write(new_make_version) }
38
+ end
39
+ File.chmod(0644, GVF)
40
+
31
41
  puts vn if $0 == __FILE__
@@ -1,7 +1,7 @@
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
3
  all::
4
-
4
+ pkg = dtas
5
5
  RUBY = ruby
6
6
  GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
7
7
  @./GIT-VERSION-GEN
@@ -10,10 +10,9 @@ lib := lib
10
10
 
11
11
  all:: test
12
12
  test_units := $(wildcard test/test_*.rb)
13
- test: test-unit
14
- test-unit: $(test_units)
13
+ test: $(test_units)
15
14
  $(test_units):
16
- $(RUBY) -w -I $(lib) $@ $(RUBY_TEST_OPTS)
15
+ $(RUBY) -w -I $(lib) $@ -v
17
16
 
18
17
  check-warnings:
19
18
  @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
@@ -26,5 +25,54 @@ coverage:
26
25
  $(MAKE) check
27
26
  $(RUBY) ./test/covshow.rb
28
27
 
29
- .PHONY: all .FORCE-GIT-VERSION-FILE test $(test_units)
30
- .PHONY: check-warnings
28
+ pkggem := pkg/$(pkg)-$(VERSION).gem
29
+ pkgtgz := pkg/$(pkg)-$(VERSION).tar.gz
30
+
31
+ fix-perms:
32
+ git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
33
+ git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
34
+
35
+ gem: $(pkggem)
36
+
37
+ install-gem: $(pkggem)
38
+ gem install $(CURDIR)/$<
39
+
40
+ $(pkggem): .gem-manifest
41
+ VERSION=$(VERSION) gem build $(pkg).gemspec
42
+ mkdir -p pkg
43
+ mv $(@F) $@
44
+
45
+ pkg_extra := GIT-VERSION-FILE lib/dtas/version.rb NEWS
46
+ NEWS:
47
+ rake -s $@
48
+ gem-man:
49
+ $(MAKE) -C Documentation/ gem-man
50
+ tgz-man:
51
+ $(MAKE) -C Documentation/ install-man mandir=$(CURDIR)/man
52
+ .PHONY: tgz-man gem-man
53
+
54
+ .gem-manifest: .manifest gem-man
55
+ (ls man/*.?; cat .manifest) | LC_ALL=C sort > $@+
56
+ cmp $@+ $@ || mv $@+ $@; rm -f $@+
57
+ .tgz-manifest: .manifest tgz-man
58
+ (ls man/*/*; cat .manifest) | LC_ALL=C sort > $@+
59
+ cmp $@+ $@ || mv $@+ $@; rm -f $@+
60
+ .manifest: NEWS fix-perms
61
+ rm -rf man
62
+ (git ls-files; \
63
+ for i in $(pkg_extra); do echo $$i; done) | \
64
+ LC_ALL=C sort > $@+
65
+ cmp $@+ $@ || mv $@+ $@; rm -f $@+
66
+ $(pkgtgz): distdir = pkg/$(pkg)-$(VERSION)
67
+ $(pkgtgz): .tgz-manifest
68
+ @test -n "$(distdir)"
69
+ $(RM) -r $(distdir)
70
+ mkdir -p $(distdir)
71
+ tar cf - $$(cat .tgz-manifest) | (cd $(distdir) && tar xf -)
72
+ cd pkg && tar cf - $(pkg)-$(VERSION) | gzip -9 > $(@F)+
73
+ mv $@+ $@
74
+
75
+ package: $(pkgtgz) $(pkggem)
76
+
77
+ .PHONY: all .FORCE-GIT-VERSION-FILE test $(test_units) NEWS
78
+ .PHONY: check-warnings fix-perms
data/INSTALL CHANGED
@@ -40,10 +40,10 @@ For future upgrades of dtas (upgrades to dtas-linux will be infrequent)
40
40
 
41
41
  Grab the latest tarball from our HTTP site:
42
42
 
43
- http://dtas.80x24.org/2013/dtas-0.3.0.tar.gz
43
+ http://dtas.80x24.org/2013/dtas-0.5.0.tar.gz
44
44
 
45
- $ tar zxvf dtas-0.3.0.tar.gz
46
- $ cd dtas-0.3.0
45
+ $ tar zxvf dtas-0.5.0.tar.gz
46
+ $ cd dtas-0.5.0
47
47
  $ sudo ruby setup.rb
48
48
 
49
49
  GNU/Linux users may optionally install "io_splice" and
data/Rakefile CHANGED
@@ -1,71 +1,39 @@
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
- load "./GIT-VERSION-GEN"
4
- manifest = "Manifest.txt"
5
- gitidx = File.stat(".git/index") rescue nil
6
- if ! File.exist?(manifest) || File.stat(manifest).mtime < gitidx.mtime
7
- system("git ls-files > #{manifest}")
8
- File.open(manifest, "a") do |fp|
9
- fp.puts "NEWS"
10
- fp.puts "lib/dtas/version.rb"
11
-
12
- if system("make -C Documentation")
13
- require 'fileutils'
14
- FileUtils.rm_rf 'man'
15
- if system("make -C Documentation gem-man")
16
- `git ls-files -o man`.split(/\n/).each do |man|
17
- fp.puts man
18
- end
19
- else
20
- warn "failed to install manpages for distribution"
21
- end
22
- else
23
- warn "failed to build manpages for distribution"
24
- end
25
- end
26
- File.open("NEWS", "w") do |fp|
27
- `git tag -l`.split(/\n/).reverse.each do |tag|
28
- %r{\Av(.+)} =~ tag or next
29
- version = $1
30
- header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
31
- header = header.split(/\n/)
32
- tagger = header.grep(/\Atagger /)[0]
33
- time = Time.at(tagger.split(/ /)[-2].to_i).utc
34
- date = time.strftime("%Y-%m-%d")
35
-
36
- fp.write("# #{version} / #{date}\n\n#{subject}\n\n#{body}")
37
- end
38
- fp.flush
39
- if fp.size <= 5
40
- fp.puts "Unreleased"
3
+ require 'tempfile'
4
+ include Rake::DSL
5
+ task "NEWS" do
6
+ latest = nil
7
+ fp = Tempfile.new("NEWS", ".")
8
+ fp.sync = true
9
+ `git tag -l`.split(/\n/).reverse.each do |tag|
10
+ %r{\Av(.+)} =~ tag or next
11
+ version = $1
12
+ header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
13
+ header = header.split(/\n/)
14
+ tagger = header.grep(/\Atagger /)[0]
15
+ time = Time.at(tagger.split(/ /)[-2].to_i).utc
16
+ latest ||= time
17
+ date = time.strftime("%Y-%m-%d")
18
+ fp.puts "# #{version} / #{date}\n\n#{subject}"
19
+ if body && body.strip.size > 0
20
+ fp.puts "\n\n#{body}"
41
21
  end
42
-
43
- fp.write("\n# COPYRIGHT\n")
44
- bdfl = 'Eric Wong <normalperson@yhbt.net>'
45
- fp.puts "Copyright (C) 2013, #{bdfl} and all contributors"
46
- fp.puts "License: GPLv3 or later (http://www.gnu.org/licenses/gpl-3.0.txt)"
22
+ fp.puts
47
23
  end
24
+ fp.puts "Unreleased" unless fp.size > 0
25
+ fp.puts "# COPYRIGHT"
26
+ bdfl = 'Eric Wong <normalperson@yhbt.net>'
27
+ fp.puts "Copyright (C) 2013, #{bdfl} and all contributors"
28
+ fp.puts "License: GPLv3 or later (http://www.gnu.org/licenses/gpl-3.0.txt)"
29
+ fp.rewind
30
+ assert_equal fp.read, File.read("NEWS") rescue nil
31
+ fp.chmod 0644
32
+ File.rename(fp.path, "NEWS")
33
+ fp.close!
48
34
  end
49
35
 
50
- require 'hoe'
51
- Hoe.plugin :git
52
- include Rake::DSL
53
-
54
- h = Hoe.spec('dtas') do |p|
55
- developer 'Eric Wong', 'e@80x24.org'
56
-
57
- self.readme_file = 'README'
58
- self.history_file = 'NEWS'
59
- self.urls = %w(http://dtas.80x24.org/)
60
- self.summary = x = File.readlines("README")[0].split(/\s+/)[1].chomp
61
- self.description = self.paragraphs_of("README", 1)
62
- # no public APIs, no HTML, either
63
- self.need_rdoc = false
64
- self.extra_rdoc_files = []
65
- license "GPLv3+"
66
- end
67
-
68
- task :rsync_docs do
36
+ task rsync_docs: "NEWS" do
69
37
  dest = ENV["RSYNC_DEST"] || "80x24.org:/srv/dtas/"
70
38
  top = %w(INSTALL NEWS README COPYING)
71
39
  files = []
@@ -73,9 +41,9 @@ task :rsync_docs do
73
41
  # git-set-file-times is distributed with rsync,
74
42
  # Also available at: http://yhbt.net/git-set-file-times
75
43
  # on Debian systems: /usr/share/doc/rsync/scripts/git-set-file-times.gz
76
- sh("git", "set-file-times", "Documentation")
44
+ sh("git", "set-file-times", "Documentation", "examples", *top)
77
45
 
78
- Dir['Documentation/*.txt'].to_a.concat(top).each do |txt|
46
+ `git ls-files Documentation/*.txt`.split(/\n/).concat(top).each do |txt|
79
47
  gz = "#{txt}.gz"
80
48
  tmp = "#{gz}.#$$"
81
49
  sh("gzip -9 < #{txt} > #{tmp}")
@@ -86,41 +54,7 @@ task :rsync_docs do
86
54
  files << gz
87
55
  end
88
56
  sh("rsync --chmod=Fugo=r -av #{files.join(' ')} #{dest}")
89
- end
90
-
91
- task :coverage do
92
- env = {
93
- "COVERAGE" => "1",
94
- "RUBYOPT" => "-r./test/helper",
95
- }
96
- File.open("coverage.dump", "w").close # clear
97
- pid = Process.spawn(env, "rake")
98
- _, status = Process.waitpid2(pid)
99
- require './test/covshow'
100
- exit status.exitstatus
101
- end
102
-
103
- base = "dtas-#{h.version}"
104
- task tarball: "pkg/#{base}" do
105
- Dir.chdir("pkg") do
106
- tgz = "#{base}.tar.gz"
107
- tmp = "#{tgz}.#$$"
108
- sh "tar cf - #{base} | gzip -9 > #{tmp}"
109
- File.rename(tmp, tgz)
110
- end
111
- end
112
57
 
113
- task "pkg/#{base}" => :fix_perms
114
-
115
- task :fix_perms do
116
- sh "git ls-tree -r HEAD | awk '/^100644 / {print $NF}' | xargs chmod 644"
117
- sh "git ls-tree -r HEAD | awk '/^100755 / {print $NF}' | xargs chmod 755"
118
- end
119
-
120
- task dist: [ :tarball, :package ] do
121
- Dir.chdir("pkg") do
122
- %w(dtas-linux dtas-mpris).each do |gem|
123
- sh "gem build ../#{gem}.gemspec"
124
- end
125
- end
58
+ examples = `git ls-files examples`.split("\n")
59
+ sh("rsync --chmod=Fugo=r -av #{examples.join(' ')} #{dest}/examples/")
126
60
  end
@@ -172,6 +172,7 @@ begin
172
172
  case key = Curses.getch
173
173
  when "j" then c.req_ok("seek -5")
174
174
  when "k" then c.req_ok("seek +5")
175
+ when "q" then exit(0)
175
176
  when Curses::KEY_DOWN then c.req_ok("seek -60")
176
177
  when Curses::KEY_UP then c.req_ok("seek +60")
177
178
  when Curses::KEY_LEFT then c.req_ok("seek -10")
@@ -18,8 +18,7 @@ orig = YAML.load(buf)
18
18
  tmp.write(buf << DTAS_DISCLAIMER)
19
19
  cmd = "#{editor} #{tmp.path}"
20
20
  system(cmd) or abort "#{cmd} failed: #$?"
21
- tmp.rewind
22
- sink = YAML.load(tmp.read)
21
+ sink = YAML.load(File.read(tmp.path))
23
22
 
24
23
  cmd = %W(sink ed #{name})
25
24
  update_cmd_env(cmd, orig, sink)
@@ -22,16 +22,16 @@ abort(buf) if buf =~ /\AERR/
22
22
  orig = YAML.load(buf)
23
23
 
24
24
  if st_in.file? || st_in.pipe?
25
- tmp = $stdin
25
+ buf = $stdin.read
26
26
  else
27
27
  tmp = tmpyaml
28
28
  tmp.write(buf << DTAS_DISCLAIMER)
29
29
  cmd = "#{editor} #{tmp.path}"
30
30
  system(cmd) or abort "#{cmd} failed: #$?"
31
- tmp.rewind
31
+ buf = File.read(tmp.path)
32
32
  end
33
33
 
34
- source = YAML.load(tmp.read)
34
+ source = YAML.load(buf)
35
35
  cmd = %W(source ed #{name})
36
36
  update_cmd_env(cmd, orig, source)
37
37
 
@@ -0,0 +1,15 @@
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
+ Gem::Specification.new do |s|
4
+ manifest = File.read('.gem-manifest').split(/\n/)
5
+ s.name = %q{dtas}
6
+ s.version = ENV["VERSION"]
7
+ s.authors = ["dtas hackers"]
8
+ s.summary = "duct tape audio suite for *nix"
9
+ s.description = File.read("README").split(/\n\n/)[1].strip
10
+ s.email = %q{e@80x24.org}
11
+ s.executables = manifest.grep(%r{\Abin/}).map { |s| s.sub(%r{\Abin/}, "") }
12
+ s.files = manifest
13
+ s.homepage = 'http://dtas.80x24.org/'
14
+ s.licenses = "GPLv3+"
15
+ end
@@ -0,0 +1,39 @@
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
+ class DTAS::CueIndex
5
+ attr_reader :offset
6
+ attr_reader :index
7
+
8
+ def initialize(index, offset)
9
+ @index = index.to_i
10
+
11
+ # must be compatible with the sox "trim" effect
12
+ @offset = offset # "#{INTEGER}s" (samples) or HH:MM:SS:FRAC
13
+ end
14
+
15
+ def to_hash
16
+ { "index" => @index, "offset" => @offset }
17
+ end
18
+
19
+ def offset_samples(format)
20
+ case @offset
21
+ when /\A(\d+)s\z/
22
+ $1.to_i
23
+ else
24
+ format.hhmmss_to_samples(@offset)
25
+ end
26
+ end
27
+
28
+ def pregap?
29
+ @index == 0
30
+ end
31
+
32
+ def track?
33
+ @index == 1
34
+ end
35
+
36
+ def subindex?
37
+ @index > 1
38
+ end
39
+ end
@@ -81,6 +81,14 @@ class DTAS::Format # :nodoc:
81
81
  ivars_to_hash(SIVS)
82
82
  end
83
83
 
84
+ def ==(other)
85
+ a = to_hash
86
+ b = other.to_hash
87
+ a["bits"] ||= bits_per_sample
88
+ b["bits"] ||= other.bits_per_sample
89
+ a == b
90
+ end
91
+
84
92
  # for the _decoded_ output
85
93
  def bits_per_sample
86
94
  return @bits if @bits
@@ -33,6 +33,7 @@ class DTAS::Player # :nodoc:
33
33
  @queue = [] # files for sources, or commands
34
34
  @paused = false
35
35
  @format = DTAS::Format.new
36
+ @bypass = [] # %w(rate bits channels) (not worth Hash overhead)
36
37
 
37
38
  @sinks = {} # { user-defined name => sink }
38
39
  @targets = [] # order matters
@@ -55,7 +56,10 @@ class DTAS::Player # :nodoc:
55
56
  end
56
57
 
57
58
  def wall(msg)
58
- msg = xs(Array(msg))
59
+ __wall(xs(Array(msg)))
60
+ end
61
+
62
+ def __wall(msg)
59
63
  @watchers.delete_if do |io, _|
60
64
  if io.closed?
61
65
  true
@@ -84,6 +88,7 @@ class DTAS::Player # :nodoc:
84
88
 
85
89
  # Arrays
86
90
  rv["queue"] = @queue
91
+ rv["bypass"] = @bypass.sort!
87
92
 
88
93
  %w(rg sink_buf format).each do |k|
89
94
  rv[k] = instance_variable_get("@#{k}").to_hsh
@@ -123,7 +128,7 @@ class DTAS::Player # :nodoc:
123
128
  v = v["buffer_size"]
124
129
  @sink_buf.buffer_size = v
125
130
  end
126
- %w(socket queue paused).each do |k|
131
+ %w(socket queue paused bypass).each do |k|
127
132
  v = hash[k] or next
128
133
  instance_variable_set("@#{k}", v)
129
134
  end
@@ -201,6 +206,8 @@ class DTAS::Player # :nodoc:
201
206
  io.emit("OK")
202
207
  when "rg"
203
208
  rg_handler(io, msg)
209
+ when "cue"
210
+ cue_handler(io, msg)
204
211
  when "skip"
205
212
  skip_handler(io, msg)
206
213
  when "sink"
@@ -366,31 +373,46 @@ class DTAS::Player # :nodoc:
366
373
  def next_source(source_spec)
367
374
  @current = nil
368
375
  if source_spec
369
- # restart sinks iff we were idle
370
- spawn_sinks(source_spec) or return
371
-
372
376
  case source_spec
373
377
  when String
374
378
  pending = try_file(source_spec) or return
375
- wall(%W(file #{pending.infile}))
379
+ msg = %W(file #{pending.infile})
376
380
  when Array
377
381
  pending = try_file(*source_spec) or return
378
- wall(%W(file #{pending.infile} #{pending.offset_samples}s))
382
+ msg = %W(file #{pending.infile} #{pending.offset_samples}s)
379
383
  else
380
384
  pending = DTAS::Source::Cmd.new(source_spec["command"])
381
- wall(%W(command #{pending.command_string}))
385
+ msg = %W(command #{pending.command_string})
386
+ end
387
+
388
+ unless @bypass.empty?
389
+ new_fmt = bypass_match!(@format.dup, pending.format)
390
+ if new_fmt != @format
391
+ stop_sinks # we may fail to start below
392
+ format_update!(new_fmt)
393
+ end
382
394
  end
383
395
 
396
+ # restart sinks iff we were idle
397
+ spawn_sinks(source_spec) or return
398
+
384
399
  dst = @sink_buf
385
400
  pending.dst_assoc(dst)
386
401
  pending.spawn(@format, @rg, out: dst.wr, in: "/dev/null")
387
402
  @current = pending
388
403
  @srv.wait_ctl(dst, :wait_readable)
404
+ wall(msg)
389
405
  else
390
406
  player_idle
391
407
  end
392
408
  end
393
409
 
410
+ def format_update!(fmt)
411
+ ary = fmt.to_hash.inject(%w(format)) { |m,(k,v)| v ? m << "#{k}=#{v}" : m }
412
+ @format = fmt
413
+ __wall(ary.join(' ')) # do not escape '='
414
+ end
415
+
394
416
  def player_idle
395
417
  stop_sinks if @sink_buf.inflight == 0
396
418
  @tl.reset unless @paused
@@ -457,4 +479,11 @@ class DTAS::Player # :nodoc:
457
479
  @sink_buf.close!
458
480
  @state_file.dump(self, true) if @state_file
459
481
  end
482
+
483
+ def bypass_match!(dst_fmt, src_fmt)
484
+ @bypass.each do |k|
485
+ dst_fmt.__send__("#{k}=", src_fmt.__send__(k))
486
+ end
487
+ dst_fmt
488
+ end
460
489
  end
@@ -295,6 +295,7 @@ module DTAS::Player::ClientHandler # :nodoc:
295
295
  end
296
296
  tmp["current_inflight"] = @sink_buf.inflight
297
297
  tmp["format"] = @format.to_hash.delete_if { |_,v| v.nil? }
298
+ tmp["bypass"] = @bypass.sort!
298
299
  tmp["paused"] = @paused
299
300
  rg = @rg.to_hsh
300
301
  tmp["rg"] = rg unless rg.empty?
@@ -350,6 +351,16 @@ module DTAS::Player::ClientHandler # :nodoc:
350
351
  @paused ? do_play : do_pause
351
352
  end
352
353
 
354
+ def seek_internal(cur, offset)
355
+ if cur.requeued
356
+ @queue[0][1] = offset
357
+ else
358
+ @queue.unshift([ cur.infile, offset ])
359
+ cur.requeued = true
360
+ __buf_reset(cur.dst) # trigger EPIPE
361
+ end
362
+ end
363
+
353
364
  def do_seek(io, offset)
354
365
  if @current
355
366
  if @current.respond_to?(:infile)
@@ -363,13 +374,7 @@ module DTAS::Player::ClientHandler # :nodoc:
363
374
  rescue ArgumentError
364
375
  return io.emit("ERR bad time format")
365
376
  end
366
- if @current.requeued
367
- @queue[0][1] = offset
368
- else
369
- @queue.unshift([ @current.infile, offset ])
370
- @current.requeued = true
371
- __buf_reset(@current.dst) # trigger EPIPE
372
- end
377
+ seek_internal(@current, offset)
373
378
  else
374
379
  return io.emit("ERR unseekable")
375
380
  end
@@ -402,20 +407,28 @@ module DTAS::Player::ClientHandler # :nodoc:
402
407
  new_fmt.valid_type?(v) or return io.emit("ERR invalid file type")
403
408
  new_fmt.type = v
404
409
  when "channels", "bits", "rate"
405
- rv = set_uint(io, kv, v, false) { |u| new_fmt.__send__("#{k}=", u) }
406
- rv == true or return rv
410
+ case v
411
+ when "bypass"
412
+ @bypass << k unless @bypass.include?(k)
413
+ else
414
+ rv = set_uint(io, kv, v, false) { |u| new_fmt.__send__("#{k}=", u) }
415
+ rv == true or return rv
416
+ @bypass.delete(k)
417
+ end
407
418
  when "endian"
408
419
  new_fmt.valid_endian?(v) or return io.emit("ERR invalid endian")
409
420
  new_fmt.endian = v
410
421
  end
411
422
  end
412
423
 
424
+ bypass_match!(new_fmt, @current.format) if @current
425
+
413
426
  if new_fmt != @format
414
427
  restart_pipeline # calls __current_requeue
415
428
 
416
429
  # we must assign this after __current_requeue since __current_requeue
417
430
  # relies on the old @format for calculation
418
- @format = new_fmt
431
+ format_update!(new_fmt)
419
432
  end
420
433
  io.emit("OK")
421
434
  end
@@ -576,6 +589,9 @@ module DTAS::Player::ClientHandler # :nodoc:
576
589
  offset = msg.shift # may be nil
577
590
  if @tl.go_to(track_id.to_i, offset)
578
591
  _tl_skip
592
+ if !(@current || @queue[0] || @paused)
593
+ next_source(_next)
594
+ end
579
595
  io.emit("OK")
580
596
  else
581
597
  io.emit("MISSING")
@@ -589,11 +605,71 @@ module DTAS::Player::ClientHandler # :nodoc:
589
605
  when "next"
590
606
  _tl_skip
591
607
  io.emit("OK")
592
- when "previous"
608
+ when "prev"
593
609
  @tl.previous!
594
610
  _tl_skip
595
611
  io.emit("OK")
596
612
  end
597
613
  end
614
+
615
+ def __bp_prev_next(io, msg, cur, bp)
616
+ case type = msg[1]
617
+ when nil, "track"
618
+ bp.keep_if { |ci| ci.track? }
619
+ when "pregap"
620
+ bp.keep_if { |ci| ci.pregap? }
621
+ when "subindex" # any subindex
622
+ bp.keep_if { |ci| ci.subindex? }
623
+ when /\A\d+\z/ # exact subindex match
624
+ si = type.to_i
625
+ bp.keep_if { |ci| ci.index == si }
626
+ when "any" # anything goes
627
+ else
628
+ return io.emit("INVALID TYPE")
629
+ end
630
+ fmt = cur.format
631
+ case msg[0]
632
+ when "next"
633
+ ds = __current_decoded_samples
634
+ bp.each do |ci|
635
+ next if ci.offset_samples(fmt) < ds
636
+ seek_internal(cur, ci.offset)
637
+ return io.emit("OK")
638
+ end
639
+ # go to the next (real) track if not found
640
+ __current_drop
641
+ when "prev"
642
+ os = cur.offset_samples # where we currently started
643
+ bp.reverse_each do |ci|
644
+ next if ci.offset_samples(fmt) >= os
645
+ seek_internal(cur, ci.offset)
646
+ return io.emit("OK")
647
+ end
648
+ # offset may be nil/zero if we couldn't find a previous breakpoint
649
+ seek_internal(cur, '0')
650
+ end
651
+ io.emit("OK")
652
+ end
653
+
654
+ def cue_handler(io, msg)
655
+ cur = @current
656
+ if cur.respond_to?(:cuebreakpoints)
657
+ bp = cur.cuebreakpoints
658
+ case msg[0]
659
+ when nil
660
+ tmp = { "infile" => cur.infile, "cue" => bp.map { |ci| ci.to_hash } }
661
+ io.emit(tmp.to_yaml)
662
+ when "next", "prev"
663
+ return __bp_prev_next(io, msg, cur, bp)
664
+ when "goto"
665
+ index = msg[1] or return io.emit("NOINDEX")
666
+ ci = bp[index.to_i] or return io.emit("BADINDEX")
667
+ seek_internal(cur, ci.offset)
668
+ return io.emit("OK")
669
+ end
670
+ else
671
+ io.emit("NOCUE")
672
+ end
673
+ end
598
674
  end
599
675
  # :startdoc:
@@ -5,6 +5,7 @@ require_relative '../source'
5
5
  require_relative '../command'
6
6
  require_relative '../format'
7
7
  require_relative '../process'
8
+ require_relative '../cue_index'
8
9
 
9
10
  module DTAS::Source::File # :nodoc:
10
11
  attr_reader :infile
@@ -33,6 +34,7 @@ module DTAS::Source::File # :nodoc:
33
34
  @offset = offset
34
35
  @comments = nil
35
36
  @samples = nil
37
+ @cuebp = nil
36
38
  @rg = nil
37
39
  end
38
40
 
@@ -90,4 +92,31 @@ module DTAS::Source::File # :nodoc:
90
92
  defaults = source_defaults # see dtas/source/{av,sox}.rb
91
93
  to_source_cat.delete_if { |k,v| v == defaults[k] }
92
94
  end
95
+
96
+ def cuebreakpoints
97
+ rv = @cuebp and return rv
98
+ rv = []
99
+ begin
100
+ str = qx(@env, %W(metaflac --export-cuesheet-to=- #@infile))
101
+ rescue
102
+ return rv
103
+ end
104
+ str.scan(/^ INDEX (\d+) (\S+)/) do |m|
105
+ index = m[0]
106
+ time = m[1].dup
107
+ case time
108
+ when /\A\d+\z/
109
+ time << "s" # sample count (flac 1.3.0)
110
+ else # HH:MM:SS:FF
111
+ # FF/75 CDDA frames per second, convert to fractional seconds
112
+ time.sub!(/:(\d+)\z/, "")
113
+ frames = $1.to_f
114
+ if frames > 0
115
+ time = sprintf("#{time}.%0.6g", frames / 75.0)
116
+ end
117
+ end
118
+ rv << DTAS::CueIndex.new(index, time)
119
+ end
120
+ @cuebp = rv
121
+ end
93
122
  end
@@ -12,6 +12,11 @@ class TestFormat < Testcase
12
12
  assert_equal({}, hash)
13
13
  end
14
14
 
15
+ def test_equal
16
+ fmt = DTAS::Format.new
17
+ assert_equal fmt, fmt.dup
18
+ end
19
+
15
20
  def test_nonstandard
16
21
  fmt = DTAS::Format.new
17
22
  fmt.type = "s16"
@@ -111,4 +111,47 @@ class TestSource < Testcase
111
111
  tmp.unlink
112
112
  end
113
113
  end
114
+
115
+ def test_flac_cuesheet_cdda
116
+ return if `which metaflac`.strip.size == 0
117
+ tmp = Tempfile.new(%W(tmp .flac))
118
+ x(%W(sox -n -r44100 -b16 -c2 #{tmp.path} synth 5 pluck vol -1dB))
119
+ cue = Tempfile.new(%W(tmp .cue))
120
+ cue.puts %Q(FILE "ignored.flac" FLAC)
121
+ cue.puts " TRACK 01 AUDIO"
122
+ cue.puts " INDEX 01 00:00:00"
123
+ cue.puts " TRACK 02 AUDIO"
124
+ cue.puts " INDEX 01 00:01:40"
125
+ cue.puts " TRACK 03 AUDIO"
126
+ cue.puts " INDEX 01 00:03:00"
127
+ cue.flush
128
+ x(%W(metaflac --import-cuesheet-from=#{cue.path} #{tmp.path}))
129
+ source = DTAS::Source::Sox.new.try(tmp.path)
130
+ offsets = source.cuebreakpoints.map(&:offset)
131
+ assert_equal %w(00:00 00:01.0.533333 00:03), offsets
132
+ source.cuebreakpoints.all?(&:track?)
133
+ end
134
+
135
+ def test_flac_cuesheet_48
136
+ return if `which metaflac`.strip.size == 0
137
+ ver = `flac --version`.split(/ /)[1].strip
138
+ ver.to_f >= 1.3 or return # flac 1.3.0 fixed non-44.1k rate support
139
+
140
+ tmp = Tempfile.new(%W(tmp .flac))
141
+ x(%W(sox -n -r48000 -c2 -b24 #{tmp.path} synth 5 pluck vol -1dB))
142
+ cue = Tempfile.new(%W(tmp .cue))
143
+ cue.puts %Q(FILE "ignored.flac" FLAC)
144
+ cue.puts " TRACK 01 AUDIO"
145
+ cue.puts " INDEX 01 00:00:00"
146
+ cue.puts " TRACK 02 AUDIO"
147
+ cue.puts " INDEX 01 00:01:00"
148
+ cue.puts " TRACK 03 AUDIO"
149
+ cue.puts " INDEX 01 00:03:00"
150
+ cue.flush
151
+ x(%W(metaflac --import-cuesheet-from=#{cue.path} #{tmp.path}))
152
+ source = DTAS::Source::Sox.new.try(tmp.path)
153
+ offsets = source.cuebreakpoints.map(&:offset)
154
+ assert_equal %w(0s 48000s 144000s), offsets
155
+ source.cuebreakpoints.all?(&:track?)
156
+ end
114
157
  end
metadata CHANGED
@@ -1,50 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dtas
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
- - Eric Wong
7
+ - dtas hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-22 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rdoc
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ~>
18
- - !ruby/object:Gem::Version
19
- version: '4.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ~>
25
- - !ruby/object:Gem::Version
26
- version: '4.0'
27
- - !ruby/object:Gem::Dependency
28
- name: hoe
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ~>
32
- - !ruby/object:Gem::Version
33
- version: '3.6'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ~>
39
- - !ruby/object:Gem::Version
40
- version: '3.6'
41
- description: '["Free Software command-line tools for audio playback, mastering, and\nwhatever
42
- else related to audio. dtas follows the worse-is-better\nphilosophy and acts as
43
- duct tape to combine existing command-line tools\nfor flexibility and ease-of-development. dtas
44
- is currently implemented\nin Ruby (and some embedded shell), but may use other languages
45
- in the\nfuture."]'
46
- email:
47
- - e@80x24.org
11
+ date: 2013-09-30 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |-
14
+ Free Software command-line tools for audio playback, mastering, and
15
+ whatever else related to audio. dtas follows the worse-is-better
16
+ philosophy and acts as duct tape to combine existing command-line tools
17
+ for flexibility and ease-of-development. dtas is currently implemented
18
+ in Ruby (and some embedded shell), but may use other languages in the
19
+ future.
20
+ email: e@80x24.org
48
21
  executables:
49
22
  - dtas-console
50
23
  - dtas-ctl
@@ -58,21 +31,7 @@ executables:
58
31
  - dtas-tl
59
32
  - dtas-xdelay
60
33
  extensions: []
61
- extra_rdoc_files:
62
- - Documentation/dtas-console.txt
63
- - Documentation/dtas-ctl.txt
64
- - Documentation/dtas-cueedit.txt
65
- - Documentation/dtas-enq.txt
66
- - Documentation/dtas-msinkctl.txt
67
- - Documentation/dtas-player.txt
68
- - Documentation/dtas-player_effects.txt
69
- - Documentation/dtas-player_protocol.txt
70
- - Documentation/dtas-player_sink_examples.txt
71
- - Documentation/dtas-sinkedit.txt
72
- - Documentation/dtas-sourceedit.txt
73
- - Documentation/dtas-splitfx.txt
74
- - Documentation/dtas-tl.txt
75
- - Documentation/dtas-xdelay.txt
34
+ extra_rdoc_files: []
76
35
  files:
77
36
  - .gitignore
78
37
  - COPYING
@@ -92,10 +51,12 @@ files:
92
51
  - Documentation/dtas-splitfx.txt
93
52
  - Documentation/dtas-tl.txt
94
53
  - Documentation/dtas-xdelay.txt
54
+ - GIT-VERSION-FILE
95
55
  - GIT-VERSION-GEN
96
56
  - GNUmakefile
97
57
  - HACKING
98
58
  - INSTALL
59
+ - NEWS
99
60
  - README
100
61
  - Rakefile
101
62
  - TODO
@@ -112,6 +73,7 @@ files:
112
73
  - bin/dtas-xdelay
113
74
  - dtas-linux.gemspec
114
75
  - dtas-mpris.gemspec
76
+ - dtas.gemspec
115
77
  - examples/README
116
78
  - examples/splitfx.sample.yml
117
79
  - lib/dtas.rb
@@ -120,6 +82,7 @@ files:
120
82
  - lib/dtas/buffer/splice.rb
121
83
  - lib/dtas/command.rb
122
84
  - lib/dtas/compat_onenine.rb
85
+ - lib/dtas/cue_index.rb
123
86
  - lib/dtas/disclaimer.rb
124
87
  - lib/dtas/edit_client.rb
125
88
  - lib/dtas/format.rb
@@ -150,8 +113,21 @@ files:
150
113
  - lib/dtas/unix_client.rb
151
114
  - lib/dtas/unix_server.rb
152
115
  - lib/dtas/util.rb
116
+ - lib/dtas/version.rb
153
117
  - lib/dtas/writable_iter.rb
154
118
  - lib/dtas/xs.rb
119
+ - man/dtas-console.1
120
+ - man/dtas-ctl.1
121
+ - man/dtas-enq.1
122
+ - man/dtas-msinkctl.1
123
+ - man/dtas-player.1
124
+ - man/dtas-player_protocol.7
125
+ - man/dtas-player_sink_examples.7
126
+ - man/dtas-sinkedit.1
127
+ - man/dtas-sourceedit.1
128
+ - man/dtas-splitfx.1
129
+ - man/dtas-tl.1
130
+ - man/dtas-xdelay.1
155
131
  - perl/dtas-graph
156
132
  - setup.rb
157
133
  - test/covshow.rb
@@ -176,29 +152,12 @@ files:
176
152
  - test/test_tracklist.rb
177
153
  - test/test_unixserver.rb
178
154
  - test/test_util.rb
179
- - NEWS
180
- - lib/dtas/version.rb
181
- - man/dtas-console.1
182
- - man/dtas-ctl.1
183
- - man/dtas-enq.1
184
- - man/dtas-msinkctl.1
185
- - man/dtas-player.1
186
- - man/dtas-player_protocol.7
187
- - man/dtas-player_sink_examples.7
188
- - man/dtas-sinkedit.1
189
- - man/dtas-sourceedit.1
190
- - man/dtas-splitfx.1
191
- - man/dtas-tl.1
192
- - man/dtas-xdelay.1
193
- - .gemtest
194
155
  homepage: http://dtas.80x24.org/
195
156
  licenses:
196
157
  - GPLv3+
197
158
  metadata: {}
198
159
  post_install_message:
199
- rdoc_options:
200
- - --main
201
- - README
160
+ rdoc_options: []
202
161
  require_paths:
203
162
  - lib
204
163
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -212,28 +171,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
171
  - !ruby/object:Gem::Version
213
172
  version: '0'
214
173
  requirements: []
215
- rubyforge_project: dtas
174
+ rubyforge_project:
216
175
  rubygems_version: 2.1.3
217
176
  signing_key:
218
177
  specification_version: 4
219
- summary: dtas
220
- test_files:
221
- - test/test_util.rb
222
- - test/test_process.rb
223
- - test/test_sink_tee_integration.rb
224
- - test/test_unixserver.rb
225
- - test/test_tracklist.rb
226
- - test/test_splitfx.rb
227
- - test/test_source_av.rb
228
- - test/test_source_sox.rb
229
- - test/test_player.rb
230
- - test/test_format_change.rb
231
- - test/test_env.rb
232
- - test/test_rg_integration.rb
233
- - test/test_buffer.rb
234
- - test/test_sink_pipe_size.rb
235
- - test/test_format.rb
236
- - test/test_player_integration.rb
237
- - test/test_player_client_handler.rb
238
- - test/test_rg_state.rb
239
- - test/test_sink.rb
178
+ summary: duct tape audio suite for *nix
179
+ test_files: []
data/.gemtest DELETED
File without changes