everything related to duct tape audio suite (dtas)
 help / color / mirror / code / Atom feed
blob d875c75b39b43f61102ec3a6514efd797b4070ac 3854 bytes (raw)
name: lib/dtas/trimfx.rb 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
 
# Copyright (C) 2013-2014, Eric Wong <e@80x24.org> and all contributors
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
require_relative '../dtas'
require_relative 'parse_time'
require 'shellwords'

class DTAS::TrimFX
  include DTAS::ParseTime

  attr_reader :tbeg
  attr_reader :tlen
  attr_reader :cmd

  def initialize(args)
    args = args.dup
    case args.shift
    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 = []
    else
      raise ArgumentError, "unknown effect type: #{tmp}"
    end
  end

  def tfx_sox(args)
    @cmd = %w(sox $SOXIN $SOXOUT $TRIMFX)
    @cmd.concat(args)
  end

  def tfx_eca(args)
    @cmd = %w(sox $SOXIN $SOX2ECA $TRIMFX)
    @cmd.concat(%w(| ecasound $ECAFMT -i stdin -o stdout))
    @cmd.concat(args)
    @cmd.concat(%w(| sox $ECA2SOX - $SOXOUT))
  end

  def to_sox_arg(format)
    if @tbeg && @tlen
      beg = @tbeg * format.rate
      len = @tlen * format.rate
      %W(trim #{beg.round}s #{len.round}s)
    elsif @tbeg
      return [] if @tbeg == 0
      beg = @tbeg * format.rate
      %W(trim #{beg.round}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)
    tbeg = 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
    else
      tlen = nil
    end
    @tbeg = tbeg
    @tlen = tlen
  end

  def <=>(other)
    tbeg <=> other.tbeg
  end

  # for stable sorting
  class TFXSort < Struct.new(:tfx, :idx)
    def <=>(other)
      cmp = tfx <=> other.tfx
      0 == cmp ? idx <=> other.idx : cmp
    end
  end

  # sorts and converts an array of TrimFX objects into non-overlapping arrays
  # of epochs
  #
  # input:
  #   [ tfx1, tfx2, tfx3, ... ]
  #
  # output:
  #   [
  #     [ tfx1 ],         # first epoch
  #     [ tfx2, tfx3 ],   # second epoch
  #     ...
  #   ]
  # There are multiple epochs only if ranges overlap,
  # There is only one epoch if there are no overlaps
  def self.schedule(ary)
    sorted = []
    ary.each_with_index { |tfx, i| sorted << TFXSort[tfx, i] }
    sorted.sort!
    rv = []
    epoch = 0
    prev_end = 0
    defer = []

    begin
      while tfxsort = sorted.shift
        tfx = tfxsort.tfx
        if tfx.tbeg >= prev_end
          # great, no overlap, append to the current epoch
          prev_end = tfx.tbeg + tfx.tlen
          (rv[epoch] ||= []) << tfx
        else
          # overlapping region, we'll need a new epoch
          defer << tfxsort
        end
      end

      if defer[0] # do we need another epoch?
        epoch += 1
        sorted = defer
        defer = []
        prev_end = 0
      end
    end while sorted[0]

    rv
  end

  # like schedule, but fills in the gaps with pass-through (no-op) TrimFX objs
  # This does not change the number of epochs.
  def self.expand(ary, total_len)
    rv = []
    schedule(ary).each_with_index do |sary, epoch|
      tip = 0
      dst = rv[epoch] = []
      while tfx = sary.shift
        if tfx.tbeg > tip
          # fill in the previous gap
          nfx = new(%W(trim #{tip} =#{tfx.tbeg}))
          dst << nfx
          dst << tfx
          tip = tfx.tbeg + tfx.tlen
        end
      end
      if tip < total_len # fill until the last chunk
        nfx = new(%W(trim #{tip} =#{total_len}))
        dst << nfx
      end
    end
    rv
  end
end

debug log:

solving d875c75 ...
found d875c75 in https://80x24.org/dtas.git/

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://80x24.org/dtas.git/

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).