dumping ground for random patches and texts
 help / color / mirror / Atom feed
* [PATCH 1/3] introduce epoch for scheduling tfx bits
@ 2015-07-05  2:21 Eric Wong
  2015-07-05  2:21 ` [PATCH 2/3] parse_time: pass through floats and integers Eric Wong
  2015-07-05  2:21 ` [PATCH 3/3] huge revamp Eric Wong
  0 siblings, 2 replies; 3+ messages in thread
From: Eric Wong @ 2015-07-05  2:21 UTC (permalink / raw)
  To: spew

From: Eric Wong <normalperson@yhbt.net>

This will end up being part of an editing pipeline between
multiple processes.
---
 lib/dtas/epoch.rb | 29 +++++++++++++++++++++++++++++
 lib/dtas/tfx.rb   | 24 ++++++++++++++----------
 2 files changed, 43 insertions(+), 10 deletions(-)
 create mode 100644 lib/dtas/epoch.rb

diff --git a/lib/dtas/epoch.rb b/lib/dtas/epoch.rb
new file mode 100644
index 0000000..f6f385c
--- /dev/null
+++ b/lib/dtas/epoch.rb
@@ -0,0 +1,29 @@
+# Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require_relative 'buffer'
+=begin
+  tbeg, tlen = 5, 20
+  DTAS::TFX.expand(all_tfx, 100 * 48000).each do |ary|
+    DTAS::Epoch.new(ary, tbeg)
+  end
+=end
+class DTAS::Epoch
+  attr_reader :soxin, :soxout
+
+  def initialize(tfx_ary, tbeg = 0) # array from DTAS::TFX.expand
+    @tbeg = tbeg
+    @tfx_ary = tfx_ary
+    @soxin = DTAS::Buffer.new
+    @passed_bytes = 0
+  end
+
+  def epoch_run(soxout, format)
+    @tfx_ary.each do |tfx|
+      next if tfx.tend < @tbeg
+      @soxin.broadcast(tfx.inputs)
+      soxin
+      @soxin.broadcast(soxout)
+      tfx.tbeg
+    end
+  end
+end
diff --git a/lib/dtas/tfx.rb b/lib/dtas/tfx.rb
index 479ff5c..607d26b 100644
--- a/lib/dtas/tfx.rb
+++ b/lib/dtas/tfx.rb
@@ -10,8 +10,8 @@ require 'shellwords'
 class DTAS::TFX
   include DTAS::ParseTime
 
-  attr_reader :tbeg
-  attr_reader :tlen
+  attr_reader :tbeg # samples
+  attr_reader :tlen # samples
   attr_reader :cmd
 
   def initialize(args, format = DTAS::Format.new)
@@ -67,21 +67,25 @@ class DTAS::TFX
   # This takes _time_ arguments only, not sample counts;
   # otherwise, deviations from sox are considered bugs in dtas
   def parse_trim!(args)
-    tbeg = parse_time(args.shift)
+    beg = parse_time(args.shift)
     if args[0] =~ /\A=?[\d\.]+\z/
-      tlen = args.shift
-      is_stop_time = tlen.sub!(/\A=/, "") ? true : false
-      tlen = parse_time(tlen)
-      tlen = tlen - tbeg if is_stop_time
-      @tlen = (tlen * @format.rate).round
+      len = args.shift
+      is_stop_time = len.sub!(/\A=/, "") ? true : false
+      len = parse_time(len)
+      len = len - beg if is_stop_time
+      @tlen = (len * @format.rate).round
     else
       @tlen = nil
     end
-    @tbeg = (tbeg * @format.rate).round
+    @tbeg = (beg * @format.rate).round
   end
 
   def <=>(other)
-    tbeg <=> other.tbeg
+    @tbeg <=> other.tbeg
+  end
+
+  def tend
+    @tbeg + @tlen
   end
 
   # for stable sorting
-- 
EW


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/3] parse_time: pass through floats and integers
  2015-07-05  2:21 [PATCH 1/3] introduce epoch for scheduling tfx bits Eric Wong
@ 2015-07-05  2:21 ` Eric Wong
  2015-07-05  2:21 ` [PATCH 3/3] huge revamp Eric Wong
  1 sibling, 0 replies; 3+ messages in thread
From: Eric Wong @ 2015-07-05  2:21 UTC (permalink / raw)
  To: spew

This makes it easier to use in a user-friendly scripting interface
---
 lib/dtas/parse_time.rb | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/dtas/parse_time.rb b/lib/dtas/parse_time.rb
index 7dd41c6..aa4ac0d 100644
--- a/lib/dtas/parse_time.rb
+++ b/lib/dtas/parse_time.rb
@@ -3,9 +3,11 @@
 require_relative '../dtas'
 
 module DTAS::ParseTime
-  # convert a string time to seconds, returning a Flot or Integer
+  # convert a string time to seconds, returning a Floot or Integer
   def parse_time(time)
     case time
+    when Float, Integer
+      time
     when /\A\d+\z/
       time.to_i
     when /\A[\d\.]+\z/
-- 
EW


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 3/3] huge revamp
  2015-07-05  2:21 [PATCH 1/3] introduce epoch for scheduling tfx bits Eric Wong
  2015-07-05  2:21 ` [PATCH 2/3] parse_time: pass through floats and integers Eric Wong
@ 2015-07-05  2:21 ` Eric Wong
  1 sibling, 0 replies; 3+ messages in thread
From: Eric Wong @ 2015-07-05  2:21 UTC (permalink / raw)
  To: spew

---
 bin/dtas-script           |  63 +++++++++++++++
 examples/script.sample.rb |  65 +++++++++++++++
 examples/tfx.sample.yml   |  27 -------
 lib/dtas/parse_freq.rb    |  14 ++++
 lib/dtas/script.rb        | 201 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/dtas/tfx.rb           |  72 ++++-------------
 test/test_script.rb       |  53 ++++++++++++
 test/test_tfx.rb          |  69 ++++------------
 8 files changed, 424 insertions(+), 140 deletions(-)
 create mode 100755 bin/dtas-script
 create mode 100644 examples/script.sample.rb
 delete mode 100644 examples/tfx.sample.yml
 create mode 100644 lib/dtas/parse_freq.rb
 create mode 100644 lib/dtas/script.rb
 create mode 100644 test/test_script.rb

diff --git a/bin/dtas-script b/bin/dtas-script
new file mode 100755
index 0000000..05778cf
--- /dev/null
+++ b/bin/dtas-script
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+# Copyright (C) 2015 all contributors <dtas-all@nongnu.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require 'dtas/script'
+require 'optionparser'
+$stdout.sync = $stderr.sync = false
+usage = "#$0 SCRIPT.rb [OPTIONS] [trim START [LENGTH]]"
+outfmt = {}
+out = $stdout
+dry_run = false
+OptionParser.new('', 24, '  ') do |op|
+  op.banner = usage
+  op.on('-p', '--sox-pipe', 'alias for `-t sox -') { outfmt[:type] = 'sox' }
+  op.on('-t', '--type TYPE') { |type| outfmt[:type] = type }
+  op.on('-r', '--rate RATE') { |rate|
+    include DTAS::ParseFreq
+    outfmt[:rate] = parse_freq(rate)
+  }
+  op.on('-c', '--channels CHANNELS', Integer) { |c| outfmt[:channels] = c }
+  op.on('-b', '--bits BITS', Integer) { |bits| outfmt[:bits] = bits }
+  op.on('-o', '--output FILE') do |file|
+    out = File.open(file, 'w')
+    out.sync = false
+    file =~ /\.(\w+)\z/ and outfmt[:type] ||= $1
+  end
+  op.on('-n', '--dry-run', 'only print commands, do not run them') do
+    dry_run = true
+  end
+  op.on('-h', '--help') do
+    puts(op.to_s)
+    exit
+  end
+  op.parse!(ARGV)
+end
+
+s = DTAS::Script.new
+script = ARGV.shift or abort usage
+
+# trim may be the first arg
+case arg = ARGV.shift
+when 'trim'
+  require 'dtas/parse_time'
+  include DTAS::ParseTime
+  tbeg = ARGV.shift or abort usage
+  tbeg = parse_time(tbeg)
+  case ARGV[0]
+  when nil # ok, play until the end
+  when /\A=?[\d\.:]+\z/
+    tlen = ARGV.shift
+    absolute = tlen.sub!(/\A=/, '') # 44:00 =44:55
+    tlen = parse_time(tlen)
+    tlen -= tbeg if absolute
+  else
+    abort usage
+  end
+end
+
+# ARGV now contains other effects we add at the end...
+
+s.instance_eval(File.read(script), script)
+format = s.format
+outfmt.each { |k, v| format.__send__("#{k}=", v) }
+s.emit(out, format, tbeg, tlen)
diff --git a/examples/script.sample.rb b/examples/script.sample.rb
new file mode 100644
index 0000000..2031938
--- /dev/null
+++ b/examples/script.sample.rb
@@ -0,0 +1,65 @@
+#!/usr/bin/env dtas-script
+# To the extent possible under law, Eric Wong has waived all copyright and
+# related or neighboring rights to this example.
+# Note: be sure to update test/test_lang.rb if you change this,
+# test_lang.rb relies on this.
+
+# declare the primary backing file (which may be :null and a format for
+# a silent backing file)
+
+input 'foo.flac'
+
+# The above maps channel 1 and 2 of `omnis.flac' to channels 3 and 4 for
+# further processing.  `:bypass' is a shortcut
+
+# instead of updating ENV directly, `env' is locally scoped
+env.update(SOX_OPTS: "#{ENV['SOX_OPTS']} -R")
+
+# rumble filter for the entire file
+at(0) { sox %w(highpass 20) }
+# at(0) { %w(sox highpass 20) } # just an array is fine, too
+
+# the following commands are equivalent, start the effect at
+# 52 seconds and have it last for 1 second:
+at(52..53) { sox %w(gain -6) }
+at(52, 1) { sh 'sox $SOXIN $SOXOUT $TRIMFX gain -6' }
+# TODO
+# at(52, 1) { sh 'sox', :SOXIN, :SOXOUT, :TRIMFX, 'gain', '-6' ) # safer
+
+# as are the following (for little endian machines)
+at(52, 1) { eca %w(-eadb:-6) }
+at(52, 1) {
+  sh 'sox $SOXIN $SOX2ECA $TRIMFX | ' \
+     'ecasound $ECAFMT -i stdin -o stdout -eadb:-6 | ' \
+     'sox $ECA2SOX - $SOXOUT'
+}
+
+# TODO
+at(55..60) do
+  # Linkwitz-Riley filters (highpass 400 highpass 400 lowpass 460 lowpass 460)
+  # to isolate a region and apply limiter to it.
+  freq(400..480) { sox %w(ladspa -lr tap_limiter -5 4) }
+
+  # use a LR lowpass at everything below 400 Hz
+  freq(-400) { sox %w(compand 6:0.01,0.05 6:-4,-6,0,-6) }
+
+  # ditto for anything above 4000Hz
+  freq(4000) { sox %w(compand 6:0.01,0.05 6:-4,-6,0,-6) }
+
+  # shortcuts also work
+  freq('4k') { sox %w(compand 6:0.01,0.05 6:-4,-6,0,-6) }
+end if false
+
+# Anything may be limited to a single or a group of channels
+ch(1) { sox %w(delay 0.001) }
+ch(1, 2) { sox %w(delay 0.001) }
+
+ch(1..3) { sox %w(delay 0.01) }
+
+# the final mix down into stereo
+at(0) do
+  sox 'remix 1v0.5,3v0.5 2v0.5,4v0.5'
+end
+
+# SOX2ECA='-tf32 -c$CHANNELS -r$RATE'
+# ECAFMT='-f32_le,$CHANNELS,$RATE
diff --git a/examples/tfx.sample.yml b/examples/tfx.sample.yml
deleted file mode 100644
index b8add0b..0000000
--- a/examples/tfx.sample.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-# To the extent possible under law, Eric Wong has waived all copyright and
-# related or neighboring rights to this example.
-# Note: be sure to update test/test_trimfx.rb if you change this,
-# test_trimfx.rb relies on this.
----
-infile: foo.flac
-env: !omap
-  PATH: $PATH
-  SOX_OPTS: $SOX_OPTS -R
-  I2: second.flac
-  I3: third.flac
-comments:
-  ARTIST: John Smith
-  ALBUM: Hello World
-  YEAR: 2013
-track_start: 1
-effects:
-# the following commands are equivalent
-- trim 52 =53 sh sox $SOXIN $SOXOUT $TRIMFX gain -6
-- trim 52 1 sox gain -6 # shorthand
-
-# as are the following (for little endian machines)
-- trim 52 1 eca -eadb:-6
-- trim 52 1 sh sox $SOXIN $SOX2ECA $TRIMFX | ecasound $ECAFMT
-  -i stdin -o stdout -eadb:-6 | sox $ECA2SOX - $SOXOUT
-# SOX2ECA='-tf32 -c$CHANNELS -r$RATE'
-# ECAFMT='-f32_le,$CHANNELS,$RATE
diff --git a/lib/dtas/parse_freq.rb b/lib/dtas/parse_freq.rb
new file mode 100644
index 0000000..b079eae
--- /dev/null
+++ b/lib/dtas/parse_freq.rb
@@ -0,0 +1,14 @@
+# Copyright (C) 2015 all contributors <dtas-all@nongnu.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+module DTAS::ParseFreq
+  def parse_freq(val)
+    case val
+    when String
+      val = val.dup
+      mult = val.sub!(/k\z/, '') ? 1000 : 1
+      (val.to_f * mult).to_i
+    else
+      val.to_i
+    end
+  end
+end
diff --git a/lib/dtas/script.rb b/lib/dtas/script.rb
new file mode 100644
index 0000000..4446c66
--- /dev/null
+++ b/lib/dtas/script.rb
@@ -0,0 +1,201 @@
+# Copyright (C) 2015 all contributors <dtas-all@nongnu.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+
+require_relative '../dtas'
+require_relative 'epoch'
+require_relative 'tfx'
+require_relative 'parse_time'
+require_relative 'parse_freq'
+
+class DTAS::Script
+  # env hash
+  attr_reader :env
+
+  # for dtas-script use only
+  def format
+    @format ||= make_format
+  end
+
+  # runs the effects described in the given block at the specified time
+  def at(tbeg, tlen = nil, fade: nil, &block)
+    case tbeg
+    when Range # seconds
+      tlen and err('at(start..end) takes only one Range')
+      r = tbeg
+      r.exclude_end? and err('at(start..end) cannot exclude end')
+
+      tbeg = parse_time(r.first)
+      tend = r.last
+      case tend
+      when String # account for '=POSITION'
+        tlen = parse_tlen(tend, tbeg, 'at(start..end)')
+      else
+        tlen = parse_time(tend) - tbeg
+      end
+    when Numeric # seconds
+      usage = 'at(START_TIME, [ END_POSITION])'
+      tlen = parse_tlen(tlen, tbeg, usage)
+    when String
+      if tbeg.include?(' ')
+        usage = 'at("START_POSITION END_POSITION")'
+        tlen and err(usage)
+        tmp = tbeg.split(/\s+/)
+        tmp.size == 2 or err(usage)
+
+        tbeg = parse_time(tmp[0])
+        tlen = parse_tlen(tmp[1], tbeg, usage)
+      else
+        usage = 'at("START_POSITION", "END_POSITION")'
+        tbeg = parse_time(tbeg)
+        tlen = parse_tlen(tlen, tbeg, usage)
+      end
+    else
+      err('at(start..end) OR at("START_POSITION END_POSITION")')
+    end
+
+    check_time_overlaps(tbeg, tlen)
+    continue(:at, tbeg, tlen, fade, block)
+  end # at
+
+  def sox(effects); cmd(:sox, effects); end
+  def eca(effects); cmd(:eca, effects); end
+  def sh(args); cmd(:sh, args); end
+
+  def ch(channel, *rest, &block)
+    channels = [ channel, *channels ].inject([]) do |m,c|
+      case c
+      when Integer
+        m << c
+      else # Range or Array
+        m.concat(Array(c))
+      end
+    end
+    err("ch(CHANNELS): no channels specified") unless channels
+    continue(:ch, channels, block)
+  end
+
+  # returns 'file'
+  def input(file, infmt = nil)
+    @inputs.empty? or err('only one input currently supported')
+    case file
+    when String
+      @inputs << file
+      infmt and err('format must not be specified with input file')
+      file
+    when Numeric
+      # input(length, rate: 48000, channels: 2)
+      infmt ||= {}
+      infmt[:rate] ||= 44100
+      infmt[:channels] ||= 2
+      unknown = infmt.keys - [ :rate, :channels ]
+      unknown.empty? or err("unknown keys in format: #{unknown.inspect}")
+
+      ret = [ length, infmt ]
+      @inputs << ret
+      ret
+    when Hash # TODO
+      # input 'cards.flac' => :bypass,
+      #       'omnis.flac' => { 1 => 3, 2 => 4 },
+      # The above maps channel 1 and 2 of `omnis.flac' to channels 3 and 4 for
+      # further processing.  `:bypass' is a shortcut for {1 => 1, 2 => 2, ... }
+    else
+      err('input(file)')
+    end
+  end
+
+  def emit(io, outfmt, tbeg = 0, tlen = nil)
+    p [ io, ouitfmt ]
+  end
+
+private
+  include DTAS::ParseTime
+
+  def cmd(type, args)
+    tfx = nil
+    chan = nil # all channels
+    @scope.reverse_each do |s|
+      case s[0]
+      when :at
+        tbeg, tlen, fade = s[1], s[2], s[3]
+        tfx ||= [ type, tbeg, tlen, fade, args ]
+        break if chan
+      when :ch
+        chan ||= s[1]
+        break if tfx
+      end
+    end
+    tfx ||= [ type, 0, nil, nil, args ]
+    @tfx << [ chan, tfx ]
+  end
+
+  def continue(*args)
+    @scope << args
+    begin
+      return args.last.call
+    ensure
+      @scope.pop
+    end
+  end
+
+  def err(msg)
+    raise ArgumentError, "usage: #{msg}"
+  end
+
+  def parse_tlen(tlen, tbeg, usage)
+    case tlen
+    when String
+      absolute = tlen.sub!(/\A=/, '') # 44:00 =44:55
+      tlen = parse_time(tlen)
+      tlen -= tbeg if absolute
+    when nil, Numeric
+      # OK
+    else
+      err(usage)
+    end
+    tlen
+  end
+
+  def check_time_overlaps(tbeg, tlen)
+    @scope.each do |s|
+      next if s[0] != :at
+
+      tbeg < s[1] and
+        err("nested at scope starts before existing scope #{s[1]}")
+
+      if tlen && s[2]
+        tend = tbeg + tlen
+        send = s[1] + s[2]
+        tend > send and
+          err("nested at scope ends after existing scope #{s[2]}")
+      end
+    end
+  end
+
+  def make_format
+    input = @inputs[0]
+    case input
+    when String
+      DTAS::Format.from_file(env_s, input)
+    else # [ length, format_hash ]
+      # stringify keys
+      h = {}
+      input[1].each { |k,v| h[k.to_s.freeze] = v }
+      DTAS::Format.load(h)
+    end
+  end
+
+  def env_s
+    h = {}
+    @env.each { |k,v| h[k.to_s.freeze] = v }
+    h
+  end
+
+  def initialize
+    @scope = []
+    @at = []
+    @inputs = []
+    @tfx = []
+    @env = {}
+    @format = nil # lazy, wait for input
+  end
+end
diff --git a/lib/dtas/tfx.rb b/lib/dtas/tfx.rb
index 607d26b..f9870aa 100644
--- a/lib/dtas/tfx.rb
+++ b/lib/dtas/tfx.rb
@@ -1,42 +1,26 @@
 # Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
 # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
 require_relative '../dtas'
-require_relative 'parse_time'
 require_relative 'format'
 require 'shellwords'
 
-# this will represent a trim section inside -splitfx for applying
-# effects to only a part of the output
 class DTAS::TFX
-  include DTAS::ParseTime
-
   attr_reader :tbeg # samples
   attr_reader :tlen # samples
   attr_reader :cmd
 
-  def initialize(args, format = DTAS::Format.new)
-    @format = format
-    args = args.dup
-    case args.shift
+  def initialize(type, tbeg, tlen, args = nil)
+    @type = type
+    @tbeg = tbeg
+    case type
     when :pad # [ :pad, start_time, end_time ]
-      @tbeg = args.shift
-      @tlen = args.shift - @tbeg
-    when "trim"
-      parse_trim!(args)
-    when "all"
-      @tbeg = 0
-      @tlen = nil
-    else
-      raise ArgumentError, "#{args.inspect} not understood"
-    end
-    case tmp = args.shift
-    when "sh" then @cmd = args
-    when "sox" then tfx_sox(args)
-    when "eca" then tfx_eca(args)
-    when nil
-      @cmd = []
+      @tlen = tlen ? tlen - @tbeg : nil
+      raise ArgumentError, 'args must not be specified for pad' if args
+    when :sox, :eca, :sh
+      @tlen = tlen
+      @args = args
     else
-      raise ArgumentError, "unknown effect type: #{tmp}"
+      raise ArgumentError, "#{type.inspect} not understood"
     end
   end
 
@@ -52,34 +36,6 @@ class DTAS::TFX
     @cmd.concat(%w(| sox $ECA2SOX - $SOXOUT))
   end
 
-  def to_sox_arg
-    if @tbeg && @tlen
-      %W(trim #{@tbeg}s #{@tlen}s)
-    elsif @tbeg
-      return [] if @tbeg == 0
-      %W(trim #{@tbeg}s)
-    else
-      []
-    end
-  end
-
-  # tries to interpret "trim" time args the same way the sox trim effect does
-  # This takes _time_ arguments only, not sample counts;
-  # otherwise, deviations from sox are considered bugs in dtas
-  def parse_trim!(args)
-    beg = parse_time(args.shift)
-    if args[0] =~ /\A=?[\d\.]+\z/
-      len = args.shift
-      is_stop_time = len.sub!(/\A=/, "") ? true : false
-      len = parse_time(len)
-      len = len - beg if is_stop_time
-      @tlen = (len * @format.rate).round
-    else
-      @tlen = nil
-    end
-    @tbeg = (beg * @format.rate).round
-  end
-
   def <=>(other)
     @tbeg <=> other.tbeg
   end
@@ -145,7 +101,7 @@ class DTAS::TFX
 
   # like schedule, but fills in the gaps with pass-through (no-op) TFX objs
   # This does not change the number of epochs.
-  def self.expand(ary, total_samples)
+  def self.expand(ary, total_samples = nil)
     rv = []
     schedule(ary).each_with_index do |sary, epoch|
       tip = 0
@@ -153,14 +109,14 @@ class DTAS::TFX
       while tfx = sary.shift
         if tfx.tbeg > tip
           # fill in the previous gap
-          nfx = new([:pad, tip, tfx.tbeg])
+          nfx = new(:pad, tip, tfx.tbeg)
           dst << nfx
           dst << tfx
           tip = tfx.tbeg + tfx.tlen
         end
       end
-      if tip < total_samples # fill until the last chunk
-        nfx = new([:pad, tip, total_samples])
+      if total_samples && tip < total_samples # fill until the last chunk
+        nfx = new(:pad, tip, total_samples)
         dst << nfx
       end
     end
diff --git a/test/test_script.rb b/test/test_script.rb
new file mode 100644
index 0000000..ab225e2
--- /dev/null
+++ b/test/test_script.rb
@@ -0,0 +1,53 @@
+# Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require_relative 'helper'
+require 'dtas/script'
+
+class TestScript < Testcase
+  def setup
+    @s = DTAS::Script.new
+  end
+
+  def test_ch
+    t = self
+    @s.instance_eval do
+      ch(1) { sox %w(gain -1) }
+    end
+    tfx = @s.instance_variable_get(:@tfx)
+    assert_equal [[[1], [:sox, 0, nil, nil, %w(gain -1)]]], tfx
+
+    @s.instance_eval do
+      at(6..66) do
+        ch(2) { sox %w(gain -9) }
+      end
+    end
+
+    assert_equal 2, tfx.size
+    assert_equal [[2], [:sox, 6, 60, nil, %w(gain -9)]], tfx[1]
+  end
+
+  def test_at
+    check = inner = nil
+    t = self
+    @s.instance_eval do
+      at(5..6) do
+        t.assert_equal 1, t.scope.size
+        t.assert_equal [:at, 5, 1], t.scope[0][0..2]
+        check = 'ok'
+
+        at(5.5..5.7) do
+          t.assert_equal 2, t.scope.size
+          inner = 'ok'
+        end
+      end
+      t.assert_equal 0, t.scope.size
+      t.assert_equal 'ok', check, 'block called'
+      t.assert_equal 'ok', inner, 'inner block called'
+    end
+    assert_equal 0, scope.size
+  end
+
+  def scope
+    @s.instance_variable_get(:@scope)
+  end
+end
diff --git a/test/test_tfx.rb b/test/test_tfx.rb
index 59aa697..c261c5e 100644
--- a/test/test_tfx.rb
+++ b/test/test_tfx.rb
@@ -5,77 +5,36 @@ require 'dtas/tfx'
 require 'dtas/format'
 require 'yaml'
 
+# internal class for dtas/script
 class TestTFX < Testcase
-  def rate
-    44100
-  end
-
-  def test_example
-    ex = YAML.load(File.read("examples/tfx.sample.yml"))
-    effects = []
-    ex["effects"].each do |line|
-      words = Shellwords.split(line)
-      case words[0]
-      when "trim"
-        tfx = DTAS::TFX.new(words)
-        assert_equal 52 * rate, tfx.tbeg
-        assert_equal rate, tfx.tlen
-        effects << tfx
-      end
-    end
-    assert_equal 4, effects.size
-  end
-
-  def test_all
-    tfx = DTAS::TFX.new(%w(all))
-    assert_equal 0, tfx.tbeg
-    assert_nil tfx.tlen
-    assert_equal [], tfx.to_sox_arg
-  end
-
-  def test_time
-    tfx = DTAS::TFX.new(%w(trim 2:30 3.1))
-    assert_equal 150 * rate, tfx.tbeg
-    assert_equal((3.1 * rate).round, tfx.tlen)
-  end
-
-  def test_to_sox_arg
-    tfx = DTAS::TFX.new(%w(trim 1 0.5))
-    assert_equal %w(trim 44100s 22050s), tfx.to_sox_arg
-
-    tfx = DTAS::TFX.new(%w(trim 1 sox vol -1dB))
-    assert_equal %w(trim 44100s), tfx.to_sox_arg
-  end
-
-  def test_tfx_effects
-    tfx = DTAS::TFX.new(%w(trim 1 sox vol -1dB))
-    assert_equal %w(sox $SOXIN $SOXOUT $TRIMFX vol -1dB), tfx.cmd
+  def rate(ary)
+    ary.map { |x| x * 44100 }
   end
 
   def test_schedule_simple
     fx = [
-      DTAS::TFX.new(%w(trim 1 0.3)),
-      DTAS::TFX.new(%w(trim 2 0.2)),
-      DTAS::TFX.new(%w(trim 0.5 0.5)),
+      DTAS::TFX.new(:sox, 1, 0.3),
+      DTAS::TFX.new(:sox, 2, 0.2),
+      DTAS::TFX.new(:sox, 0.5, 0.5),
     ].shuffle
     ary = DTAS::TFX.schedule(fx)
     assert_operator 1, :==, ary.size
-    assert_equal [ 22050, 44100, 88200 ], ary[0].map(&:tbeg)
-    assert_equal [ 22050, 13230, 8820 ], ary[0].map(&:tlen)
+    assert_equal [ 22050, 44100, 88200 ], rate(ary[0].map(&:tbeg))
+    assert_equal [ 22050, 13230, 8820 ], rate(ary[0].map(&:tlen))
   end
 
   def test_schedule_overlaps
     fx = [
-      DTAS::TFX.new(%w(trim 1 0.3 sox)),
-      DTAS::TFX.new(%w(trim 1.1 0.2 sox)),
-      DTAS::TFX.new(%w(trim 0.5 0.5 sox)),
+      DTAS::TFX.new(:sox, 1, 0.3),
+      DTAS::TFX.new(:sox, 1.1, 0.2),
+      DTAS::TFX.new(:sox, 0.5, 0.5),
     ]
     ary = DTAS::TFX.schedule(fx)
     assert_equal 2, ary.size
-    assert_equal [ 22050, 44100 ], ary[0].map(&:tbeg)
-    assert_equal [ 48510 ], ary[1].map(&:tbeg)
+    assert_equal [ 22050, 44100 ], rate(ary[0].map(&:tbeg))
+    assert_equal [ 48510 ], rate(ary[1].map(&:tbeg)).map(&:round) # :x
 
-    ex = DTAS::TFX.expand(fx, 10 * rate)
+    ex = DTAS::TFX.expand(fx, 10)
     assert_equal 2, ex.size
     assert_equal 0, ex[0][0].tbeg
     assert_equal 3, ex[0].size
-- 
EW


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2015-07-05  2:21 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-07-05  2:21 [PATCH 1/3] introduce epoch for scheduling tfx bits Eric Wong
2015-07-05  2:21 ` [PATCH 2/3] parse_time: pass through floats and integers Eric Wong
2015-07-05  2:21 ` [PATCH 3/3] huge revamp Eric Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).