dtas.git  about / heads / tags
duct tape audio suite for *nix
blob 0ec108cad3777d8799bf84b6695bdefef5edf683 3007 bytes (raw)
$ git show HEAD:lib/dtas/fadefx.rb	# shows this blob on the CLI

  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
 
# Copyright (C) all contributors <dtas-all@nongnu.org>
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
# frozen_string_literal: true
require_relative '../dtas'
require_relative 'parse_time'
require_relative 'xs'

# note: This is sox-specific
# --------- time --------->
# _____   _______   ______
#      \ /       \ /
# prev  X   cur   X   next
# _____/ \_______/ \______
#
# out_prev - controls the downward slope from prev
# in_cur   - controls the upward slope into cur
# out_cur  - controls the downward slope from cur
# in_next  - controls the upward slope into next
class DTAS::FadeFX # :nodoc:
  include DTAS::ParseTime
  include DTAS::XS

  attr_reader :out_prev, :in_cur, :out_cur, :in_next
  F = Struct.new(:type, :flen)

  def initialize(args)
    args =~ /\A([^,]*),([^,]*);([^,]*),([^,]*)\z/ or
      raise ArgumentError, "bad fade format"
    fades = [ $1, $2, $3, $4 ]
    %w(out_prev in_cur out_cur in_next).each do |iv|
      instance_variable_set("@#{iv}", parse!(fades.shift))
    end
  end

  def fade_cur_fx(format, tbeg, tlen, args = [])
    fx = %W(trim #{tbeg}s #{tlen}s)
    fx.concat(args)
    if @in_cur && @out_cur && @in_cur.type == @out_cur.type
      f = %W(fade #{@in_cur.type} #{@in_cur.flen} #{tlen}s #{@out_cur.flen})
      fx.concat(f)
    else # differing fade types for in/out, chain them:
      fpart = @in_cur and
        fx.concat(%W(fade #{fpart.type} #{fpart.flen} 0 0))
      fpart = @out_cur and
        fx.concat(%W(fade #{fpart.type} 0 #{tlen}s #{fpart.flen}))
    end
    fx
  end

  def fade_out_prev_fx(format, tbeg, tlen)
    fx = %W(trim #{tbeg}s)

    if fpart = @out_prev
      out_len = format.hhmmss_to_samples(fpart.flen)
      fx.concat(%W(fade #{fpart.type} 0 #{out_len}s #{out_len}s))
      remain = tlen - out_len

      # fade-out is longer than tlen, so truncate again:
      remain < 0 and fx.concat(%W(trim 0 #{tlen}s))

      # pad with silence, this is where fade_cur_fx goes
      remain > 0 and fx.concat(%W(pad #{remain}s@#{out_len}s))
    end
    fx
  end

  def fade_in_next_fx(format, tbeg, tlen)
    fpart = @in_next
    flen = fpart ? fpart.flen : 0
    nlen = format.hhmmss_to_samples(flen)
    nbeg = tbeg + tlen - nlen
    npad = nbeg - tbeg
    if npad < 0
      warn("in_next should not exceed range: #{inspect} @trim " \
           "#{tbeg}s #{tlen}s\nclamping to #{tbeg}")
      nbeg = tbeg
    end

    fx = %W(trim #{nbeg}s #{nlen}s)
    nlen != 0 and
      fx.concat(%W(fade #{fpart.type} #{nlen}s 0 0))

    # likely, the pad section is where fade_cur_fx goes
    npad > 0 and fx.concat(%W(pad #{npad}s@0s))
    fx
  end

  # q - quarter of a sine wave
  # h - half a sine wave
  # t - linear (`triangular') slope
  # l - logarithmic
  # p - inverted parabola
  # default is 't' (sox defaults to 'l', but triangular makes more sense
  # when concatenating
  def parse!(str)
    return nil if str.empty?
    type = "t"
    str.sub!(/\A([a-z])/, "") and type = -$1
    F.new(type, parse_time(str))
  end
end

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