dtas 0.4.0 → 0.5.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 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