Richard Berg
5th February 2006, 14:43
I was writing a helper function yesterday and once again came across a situation where it would've been nice to have arrays & loops. After many years of people asking for these kind of features, but no progress aside from "someday, in AVS 3.xx", I assumed it was a hard problem. Turns out it only took a few hours :devil:
# rb-array.avsi -- functions for simulating arrays & loops in Avisynth 2.xx
#
# Last modified: 2006-02-05
#
# Written by Richard Berg and assigned to the public domain.
#
# Dim
#
# Create a collection of global variables that can be manipulated similar to an array. You can re-Dim
# an existing array, but the contents will be cleared. The type of the "initValue" parameter determines the type
# of the array. All Avisynth types are supported.
#
# PARAMETERS:
# "arr" : Name of the array variable. Pass this variable name to Deref and Set.
# "size" : Size of the array; 0-based, like C/C++. Dim("foo", 10) creates foo0-foo9.
# "initValue" : Each element is initialized to this value. Its type determines the array type. It uses For
# internally, so initValue can be parametrized by 'i'.
#
# USAGE: see example.avs
#
function Dim(string arr, int size, val initValue)
{
# create "base" string variable whose value is its name. every Eval in internal array functions
# needs this to build the element's name
Eval("global " + arr + "=" + chr(34) + arr + chr(34))
# store size so that Length() is O(1)
Eval("global " + arr + "Length = " + string(size))
# create each element
global DimThunkVal = initValue # thunk requires this in case it's a clip
for(0, "<="+string(size), "+1", arr + ".set(i, " + thunk(initValue, "DimThunkVal") + ")")
}
# DeRef
#
# Gives you programmatic access to the "arrays" created by Dim
#
# USAGE: foo.deref(2+2)
# ...is semantically equivalent* to foo[2+2] in C/C++
# ...is syntactically equivalent to foo4 in Avisynth
#
# *exception: the output of deref is NOT an lvalue. If you need to assign something to
# an array element, use Set.
#
function DeRef(string arr, int index)
{
Eval(arr + string(index))
}
# Set
#
# Allows you to programmatically assign values to the "arrays" created by Dim
#
# USAGE: foo.set(i, i*2)
# ...is semantically equivalent to foo[i] = i*2; in C/C++
# ...is syntactically equivalent to fooI = i*2 in Avisynth
#
function Set(string arr, int index, val val)
{
Eval("global " + arr + string(index) + "=" + thunk(val))
}
# Thunk
#
# Makes a generic (untyped) parameter suitable for building Eval strings.
#
# PARAMETERS:
# "clipName" : In order to assign clips to arrays with Eval, the rvalue has to be another Eval
# on a clip variable. By default, this variable is the parameter "val," as used
# in functions like Set. However, if you are building an Eval string for use with
# For, you'll need to set a global variable that can be accessed inside the loop. So
# as to not pollute the global namespace, you can create a unique variable name; pass
# it as 'clipName' to make Thunk work correctly.
#
#
function Thunk(val val, string "clipName")
{
clipName = Default(clipName, "val")
# string -> escape it
# clip -> double-thunk it
# everything else -> stringify it
return val.IsString()
\ ? chr(34) + val + chr(34)
\ : val.IsClip()
\ ? "Eval(" + chr(34) + clipName + chr(34) + ")"
\ : string(val)
}
# Length
#
# Returns the length of an "array" created by Dim
#
function Length(string arr)
{
try {
return Eval(arr + "Length")
}
catch (err_msg) {
Throw("Length: the array " + arr + " was not created by Dim or is corrupt")
}
}
# Length2
#
# Returns the length of an "array" that was not created by Dim, or has had elements manipulated directly. O(n) search.
#
# PARAMETERS:
# "startIndex" : Start searching at this index. Default = 0. Note: elements lower than startIndex are
# not counted in the length calculation.
#
function Length2(string arr, int "startIndex")
{
startIndex = Default(startIndex, 0)
# store size in Dim-style global so we don't have to do this again
Eval("global " + arr + "Length = " + string(Length2Loop(arr, startIndex)))
return arr.length
}
function Length2Loop(string arr, int index)
{
try {
# note: using deref won't work - after the callstack unwinds, index will be lost
Eval(arr + string(index))
return Length2Loop(arr, index + 1)
}
catch (err_msg) {
return index
}
}
# Fill
#
# Fill an array with values entered as a comma-delimited string literal.
# (thanks to James "stickboy" Lin for the idea)
#
#
# USAGE: array.Fill("3, -50, 124")
#
# PARAMETERS:
# "index" : Start writing values at this index. Default is 0.
# "string" : If true, fills the array with strings instead of parsing into values. Default is false.
#
function Fill(string arr, string values, int "index", bool "string")
{
index = Default(index, 0)
string = Default(string, false)
offset = FindStr(values, ",")
# FindStr returns 0 if not found
return (offset == 0)
\ ? NOP
\ : Eval("""
element = LeftStr(values, offset - 1)
element = string ? element : Value(element)
arr.Set(index, element)
arr.Fill(MidStr(values, offset + 1), index + 1)
""")
}
# For
#
# A simple for-loop construct for Avisynth 2.xx
#
# USAGE: for(1, "<=3", "+1", "global x = x*2" )
# ...is semantically equivalent to for(int i=1, i<=3, i=i+1) { x = x*2 } in C/C++
#
# Notes:
# - with For, the loop variable is always 'i', and the first parameter (its initial value) is always an int. If you
# want to use a custom loop variable (so you can nest loops, for instance, or have a floating-point counter) then use For2.
# - any script variables you want manipulate inside the loop must be globals
# - corollary: you cannot use the implicit 'last' variable. if you want to apply a filter inside the loop,
# you must use assignment syntax. Example: for(0, "<=2", "+1", "global clip = clip.FFT3DFilter(sigma=2.5, plane=i)" )
#
function For(int i, string cond, string iter, string block)
{
return Eval("i" + cond)
\ ? Eval("""
Eval(block)
For(Eval("i" + iter), cond, iter, block)
""")
\ : NOP
}
# For2
#
# A more generalized for-loop construct for Avisynth 2.xx
#
# USAGE: for("j=1", "j<=3", "j=j+1", "global x = x*2")
# ...is semantically equivalent to for(int j=1, j<=3, j=j+1) { x = x*2 } in C/C++
#
# Notes:
# - For's guidelines about manipulating globals apply to For2 as well. In addition, the loop variable must
# be a global (this is handled for you -- if this hack isn't sufficiently general, use For2Loop directly).
#
function For2(string init, string cond, string iter, string block)
{
For2Loop("global " + init, cond, "global " + iter, block)
}
function For2Loop(string init, string cond, string iter, string block)
{
Eval(init)
return Eval(cond)
\ ? Eval("""
Eval(block)
For2Loop(iter, cond, iter, block)
""")
\ : NOP
}
# For3
#
# Same as For, but implemented using For2. Mostly a proof of concept, but there's a practical difference: the
# loop variable here is global, so nesting For3's probably does weird things.
#
function For3(int i, string cond, string iter, string block)
{
For2("i=" + string(i), "i" + cond, "i=i" + iter, block)
}
# While
#
# A simple while-loop construct for Avisynth 2.xx
#
# USAGE: while(b, "DoSomething()")
# ...is semantically equivalent to while(b) { DoSomething() } in C/C++
#
# Notes:
# - If you put it into an infinite loop, the host app will crash after several seconds. An easy way to do this
# is to forget to use 'global' when manipulating variables that affect 'cond' inside the loop.
# - I honestly don't think this is a very useful function. Most of the time you want while()-like semantics, you
# really want to operate on frames (not clips or other variables), in which case ConditionalFilter is the far
# better choice.
#
function While(string cond, string block)
{
For2Loop("", cond, "", block)
}
A simple example.avs:
BlankClip(length=1025, color=color_royalblue)
JDL_FadeIO(end=last.framecount)
global fade = last
# create an array full of 0's, then fill it with the fibonacci sequence
Dim("fibArr", 25, 0)
for(0, "<=25", "+1",
\"""
global fade = fade.Subtitle("initial value #" + string(i) + " = " + string(fibArr.deref(i)), y = (i+1) * 18, align = 6)
fibArr.set(i, i>=2
\ ? fibArr.deref(i-1) + fibArr.deref(i-2)
\ : 1)
global fade = fade.Subtitle("Fibonacci #" + string(i) + " = " + string(fibArr.deref(i)), y = i * 18)
""")
# write a multiplication table
global table = fade
for2("x=1", "x<=8", "x=x+1",
\"""
for2("y=1", "y<=8", "y=y+1",
\"
global table = table.Subtitle(string(x*y), x=x*30 + 180, y=y*20 + 30)
")
""")
# create an array of clips: each has 1 frame that's a power of 2 in the original clip
global j=0
Dim("frameArr", 0, NullClip(fade)) # create empty array; we'll fill it manually
for(1, "<=table.FrameCount", "*2",
\"""
frameArr.set(
\ j, table.Trim3(i, length=1)
\ .Subtitle("original frame " + string(i), align=8)
\ .Subtitle("fibArr length " + string(fibArr.length), align=5)
\)
global j = j + 1
""")
# concatenate the frames from that clip-array into one clip
# we'll do it backwards just for fun
global powers = NullClip(fade)
len = Length("frameArr") # change this to a bogus array name and you'll get an Assert
global countdown = frameArr.Length2 - 1
while("countdown>=0",
\"""
global powers = powers + frameArr.deref(countdown).subtitle("powers frame " + string(countdown), align=8, y = 18)
global countdown = countdown-1
""")
# detecting the length of frameArr manually (see Length2 call above) is O(N) the first time, so we only want to
# do it once; afterward, Length() will work
len2 = frameArr.Length
return powers.Subtitle("frameArr len: before " + string(len) + ", after " + string(len2), align = 2)
FWIW, the function that started it all (FindClosest):
# rb-alignaudio.avsi -- AlignAudio & misc helper functions
#
# Last modified: 2006-02-05
#
# Written by Richard Berg and assigned to the public domain.
#
# AlignAudio
#
# Adjusts the length of the audio track to match the video. Uses BestResample if resampling is requested.
#
#
#
# PARAMETERS:
# "samplerate" : If defined, resample the audio to this rate. If not defined, then the audio samplerate is
# set to the standard that's closest to the input.
#
Function AlignAudio(clip clip, int "samplerate")
{
Assert(clip.HasAudio && clip.HasVideo, "RB_AlignAudio: clip must have both audio & video")
clip = Defined(samplerate)
\ ? clip.BestResample(samplerate)
\ : clip.AssumeSampleRate( FindClosest(clip.AudioRate, "11025, 22050, 44100, 88200, 12000, 24000, 48000, 96000, 192000") )
return clip.TimeStretch(100. * clip.AudioLength/clip.AudioRate / (clip.FrameCount/clip.FrameRate))
}
# BestResample
#
# Resamples a clip's audio. Intelligently chooses SSRC(fast = true), SSRC(fast = false), or ResampleAudio based on
# the guidelines in the Avisynth manual.
#
#
function BestResample(clip clip, int samplerate)
{
return CanSSRC(clip.AudioRate, samplerate)
\ ? WithinFactorOf(2, clip.AudioRate, samplerate)
\ ? clip.SSRC(samplerate)
\ : clip.SSRC(samplerate, fast=false)
\ : clip.ResampleAudio(samplerate)
}
# Given a list of numbers, returns the one closest to the input
#
# PARAMETERS:
# "input" : number to compare against
# "numbers" : comma-delimited list of numbers to search
#
#
function FindClosest(val input, string numbers)
{
# parse string into array
Dim("rates", 0, 0) # empty is ok - we'll fill it manually
Fill(rates, numbers)
# need Length2 here since we filled manually
global g_len = rates.Length2
global g_minDelta = input - rates0
global g_input = input
for(1, "<g_len", "+1",
\"""
global g_minDelta = abs(g_input - rates.deref(i)) < abs(g_minDelta)
\ ? g_input - rates.deref(i)
\ : g_minDelta
""")
ret = input - g_minDelta
return input.IsInt ? int(ret) : ret
}
# return true if f is less than the ratio between x & y
# Example: WithinFactorOf(10, x, y) == "x is within an order of magnitude of y"
#
function WithinFactorOf(float f, float x, float y)
{
ratio = x>y ? x/y : y/x
return ratio < f
}
# Computes the Greatest Common Divisor of two integers
# (Euclid's Algorithm)
#
function GCD(int a, int b)
{
return (b == 0)
\ ? a
\ : GCD(b, a % b)
}
# Returns true if Avisynth's Shibatch sample rate converter function (SSRC) can convert
# a clip with samplerate = src to dst, based on the documentation at <http://www.avisynth.org/SSRC>
#
function CanSSRC(int src, int dst)
{
fs1 = dst * src / GCD(src, dst)
return (fs1 / dst == 1) || (fs1 / dst % 2 == 0) || (fs1 / dst % 3 == 0)
}
Try it out, let me know what you think. I'm sure there are more features that can be added without much trouble, but I dunno what would be useful.
# rb-array.avsi -- functions for simulating arrays & loops in Avisynth 2.xx
#
# Last modified: 2006-02-05
#
# Written by Richard Berg and assigned to the public domain.
#
# Dim
#
# Create a collection of global variables that can be manipulated similar to an array. You can re-Dim
# an existing array, but the contents will be cleared. The type of the "initValue" parameter determines the type
# of the array. All Avisynth types are supported.
#
# PARAMETERS:
# "arr" : Name of the array variable. Pass this variable name to Deref and Set.
# "size" : Size of the array; 0-based, like C/C++. Dim("foo", 10) creates foo0-foo9.
# "initValue" : Each element is initialized to this value. Its type determines the array type. It uses For
# internally, so initValue can be parametrized by 'i'.
#
# USAGE: see example.avs
#
function Dim(string arr, int size, val initValue)
{
# create "base" string variable whose value is its name. every Eval in internal array functions
# needs this to build the element's name
Eval("global " + arr + "=" + chr(34) + arr + chr(34))
# store size so that Length() is O(1)
Eval("global " + arr + "Length = " + string(size))
# create each element
global DimThunkVal = initValue # thunk requires this in case it's a clip
for(0, "<="+string(size), "+1", arr + ".set(i, " + thunk(initValue, "DimThunkVal") + ")")
}
# DeRef
#
# Gives you programmatic access to the "arrays" created by Dim
#
# USAGE: foo.deref(2+2)
# ...is semantically equivalent* to foo[2+2] in C/C++
# ...is syntactically equivalent to foo4 in Avisynth
#
# *exception: the output of deref is NOT an lvalue. If you need to assign something to
# an array element, use Set.
#
function DeRef(string arr, int index)
{
Eval(arr + string(index))
}
# Set
#
# Allows you to programmatically assign values to the "arrays" created by Dim
#
# USAGE: foo.set(i, i*2)
# ...is semantically equivalent to foo[i] = i*2; in C/C++
# ...is syntactically equivalent to fooI = i*2 in Avisynth
#
function Set(string arr, int index, val val)
{
Eval("global " + arr + string(index) + "=" + thunk(val))
}
# Thunk
#
# Makes a generic (untyped) parameter suitable for building Eval strings.
#
# PARAMETERS:
# "clipName" : In order to assign clips to arrays with Eval, the rvalue has to be another Eval
# on a clip variable. By default, this variable is the parameter "val," as used
# in functions like Set. However, if you are building an Eval string for use with
# For, you'll need to set a global variable that can be accessed inside the loop. So
# as to not pollute the global namespace, you can create a unique variable name; pass
# it as 'clipName' to make Thunk work correctly.
#
#
function Thunk(val val, string "clipName")
{
clipName = Default(clipName, "val")
# string -> escape it
# clip -> double-thunk it
# everything else -> stringify it
return val.IsString()
\ ? chr(34) + val + chr(34)
\ : val.IsClip()
\ ? "Eval(" + chr(34) + clipName + chr(34) + ")"
\ : string(val)
}
# Length
#
# Returns the length of an "array" created by Dim
#
function Length(string arr)
{
try {
return Eval(arr + "Length")
}
catch (err_msg) {
Throw("Length: the array " + arr + " was not created by Dim or is corrupt")
}
}
# Length2
#
# Returns the length of an "array" that was not created by Dim, or has had elements manipulated directly. O(n) search.
#
# PARAMETERS:
# "startIndex" : Start searching at this index. Default = 0. Note: elements lower than startIndex are
# not counted in the length calculation.
#
function Length2(string arr, int "startIndex")
{
startIndex = Default(startIndex, 0)
# store size in Dim-style global so we don't have to do this again
Eval("global " + arr + "Length = " + string(Length2Loop(arr, startIndex)))
return arr.length
}
function Length2Loop(string arr, int index)
{
try {
# note: using deref won't work - after the callstack unwinds, index will be lost
Eval(arr + string(index))
return Length2Loop(arr, index + 1)
}
catch (err_msg) {
return index
}
}
# Fill
#
# Fill an array with values entered as a comma-delimited string literal.
# (thanks to James "stickboy" Lin for the idea)
#
#
# USAGE: array.Fill("3, -50, 124")
#
# PARAMETERS:
# "index" : Start writing values at this index. Default is 0.
# "string" : If true, fills the array with strings instead of parsing into values. Default is false.
#
function Fill(string arr, string values, int "index", bool "string")
{
index = Default(index, 0)
string = Default(string, false)
offset = FindStr(values, ",")
# FindStr returns 0 if not found
return (offset == 0)
\ ? NOP
\ : Eval("""
element = LeftStr(values, offset - 1)
element = string ? element : Value(element)
arr.Set(index, element)
arr.Fill(MidStr(values, offset + 1), index + 1)
""")
}
# For
#
# A simple for-loop construct for Avisynth 2.xx
#
# USAGE: for(1, "<=3", "+1", "global x = x*2" )
# ...is semantically equivalent to for(int i=1, i<=3, i=i+1) { x = x*2 } in C/C++
#
# Notes:
# - with For, the loop variable is always 'i', and the first parameter (its initial value) is always an int. If you
# want to use a custom loop variable (so you can nest loops, for instance, or have a floating-point counter) then use For2.
# - any script variables you want manipulate inside the loop must be globals
# - corollary: you cannot use the implicit 'last' variable. if you want to apply a filter inside the loop,
# you must use assignment syntax. Example: for(0, "<=2", "+1", "global clip = clip.FFT3DFilter(sigma=2.5, plane=i)" )
#
function For(int i, string cond, string iter, string block)
{
return Eval("i" + cond)
\ ? Eval("""
Eval(block)
For(Eval("i" + iter), cond, iter, block)
""")
\ : NOP
}
# For2
#
# A more generalized for-loop construct for Avisynth 2.xx
#
# USAGE: for("j=1", "j<=3", "j=j+1", "global x = x*2")
# ...is semantically equivalent to for(int j=1, j<=3, j=j+1) { x = x*2 } in C/C++
#
# Notes:
# - For's guidelines about manipulating globals apply to For2 as well. In addition, the loop variable must
# be a global (this is handled for you -- if this hack isn't sufficiently general, use For2Loop directly).
#
function For2(string init, string cond, string iter, string block)
{
For2Loop("global " + init, cond, "global " + iter, block)
}
function For2Loop(string init, string cond, string iter, string block)
{
Eval(init)
return Eval(cond)
\ ? Eval("""
Eval(block)
For2Loop(iter, cond, iter, block)
""")
\ : NOP
}
# For3
#
# Same as For, but implemented using For2. Mostly a proof of concept, but there's a practical difference: the
# loop variable here is global, so nesting For3's probably does weird things.
#
function For3(int i, string cond, string iter, string block)
{
For2("i=" + string(i), "i" + cond, "i=i" + iter, block)
}
# While
#
# A simple while-loop construct for Avisynth 2.xx
#
# USAGE: while(b, "DoSomething()")
# ...is semantically equivalent to while(b) { DoSomething() } in C/C++
#
# Notes:
# - If you put it into an infinite loop, the host app will crash after several seconds. An easy way to do this
# is to forget to use 'global' when manipulating variables that affect 'cond' inside the loop.
# - I honestly don't think this is a very useful function. Most of the time you want while()-like semantics, you
# really want to operate on frames (not clips or other variables), in which case ConditionalFilter is the far
# better choice.
#
function While(string cond, string block)
{
For2Loop("", cond, "", block)
}
A simple example.avs:
BlankClip(length=1025, color=color_royalblue)
JDL_FadeIO(end=last.framecount)
global fade = last
# create an array full of 0's, then fill it with the fibonacci sequence
Dim("fibArr", 25, 0)
for(0, "<=25", "+1",
\"""
global fade = fade.Subtitle("initial value #" + string(i) + " = " + string(fibArr.deref(i)), y = (i+1) * 18, align = 6)
fibArr.set(i, i>=2
\ ? fibArr.deref(i-1) + fibArr.deref(i-2)
\ : 1)
global fade = fade.Subtitle("Fibonacci #" + string(i) + " = " + string(fibArr.deref(i)), y = i * 18)
""")
# write a multiplication table
global table = fade
for2("x=1", "x<=8", "x=x+1",
\"""
for2("y=1", "y<=8", "y=y+1",
\"
global table = table.Subtitle(string(x*y), x=x*30 + 180, y=y*20 + 30)
")
""")
# create an array of clips: each has 1 frame that's a power of 2 in the original clip
global j=0
Dim("frameArr", 0, NullClip(fade)) # create empty array; we'll fill it manually
for(1, "<=table.FrameCount", "*2",
\"""
frameArr.set(
\ j, table.Trim3(i, length=1)
\ .Subtitle("original frame " + string(i), align=8)
\ .Subtitle("fibArr length " + string(fibArr.length), align=5)
\)
global j = j + 1
""")
# concatenate the frames from that clip-array into one clip
# we'll do it backwards just for fun
global powers = NullClip(fade)
len = Length("frameArr") # change this to a bogus array name and you'll get an Assert
global countdown = frameArr.Length2 - 1
while("countdown>=0",
\"""
global powers = powers + frameArr.deref(countdown).subtitle("powers frame " + string(countdown), align=8, y = 18)
global countdown = countdown-1
""")
# detecting the length of frameArr manually (see Length2 call above) is O(N) the first time, so we only want to
# do it once; afterward, Length() will work
len2 = frameArr.Length
return powers.Subtitle("frameArr len: before " + string(len) + ", after " + string(len2), align = 2)
FWIW, the function that started it all (FindClosest):
# rb-alignaudio.avsi -- AlignAudio & misc helper functions
#
# Last modified: 2006-02-05
#
# Written by Richard Berg and assigned to the public domain.
#
# AlignAudio
#
# Adjusts the length of the audio track to match the video. Uses BestResample if resampling is requested.
#
#
#
# PARAMETERS:
# "samplerate" : If defined, resample the audio to this rate. If not defined, then the audio samplerate is
# set to the standard that's closest to the input.
#
Function AlignAudio(clip clip, int "samplerate")
{
Assert(clip.HasAudio && clip.HasVideo, "RB_AlignAudio: clip must have both audio & video")
clip = Defined(samplerate)
\ ? clip.BestResample(samplerate)
\ : clip.AssumeSampleRate( FindClosest(clip.AudioRate, "11025, 22050, 44100, 88200, 12000, 24000, 48000, 96000, 192000") )
return clip.TimeStretch(100. * clip.AudioLength/clip.AudioRate / (clip.FrameCount/clip.FrameRate))
}
# BestResample
#
# Resamples a clip's audio. Intelligently chooses SSRC(fast = true), SSRC(fast = false), or ResampleAudio based on
# the guidelines in the Avisynth manual.
#
#
function BestResample(clip clip, int samplerate)
{
return CanSSRC(clip.AudioRate, samplerate)
\ ? WithinFactorOf(2, clip.AudioRate, samplerate)
\ ? clip.SSRC(samplerate)
\ : clip.SSRC(samplerate, fast=false)
\ : clip.ResampleAudio(samplerate)
}
# Given a list of numbers, returns the one closest to the input
#
# PARAMETERS:
# "input" : number to compare against
# "numbers" : comma-delimited list of numbers to search
#
#
function FindClosest(val input, string numbers)
{
# parse string into array
Dim("rates", 0, 0) # empty is ok - we'll fill it manually
Fill(rates, numbers)
# need Length2 here since we filled manually
global g_len = rates.Length2
global g_minDelta = input - rates0
global g_input = input
for(1, "<g_len", "+1",
\"""
global g_minDelta = abs(g_input - rates.deref(i)) < abs(g_minDelta)
\ ? g_input - rates.deref(i)
\ : g_minDelta
""")
ret = input - g_minDelta
return input.IsInt ? int(ret) : ret
}
# return true if f is less than the ratio between x & y
# Example: WithinFactorOf(10, x, y) == "x is within an order of magnitude of y"
#
function WithinFactorOf(float f, float x, float y)
{
ratio = x>y ? x/y : y/x
return ratio < f
}
# Computes the Greatest Common Divisor of two integers
# (Euclid's Algorithm)
#
function GCD(int a, int b)
{
return (b == 0)
\ ? a
\ : GCD(b, a % b)
}
# Returns true if Avisynth's Shibatch sample rate converter function (SSRC) can convert
# a clip with samplerate = src to dst, based on the documentation at <http://www.avisynth.org/SSRC>
#
function CanSSRC(int src, int dst)
{
fs1 = dst * src / GCD(src, dst)
return (fs1 / dst == 1) || (fs1 / dst % 2 == 0) || (fs1 / dst % 3 == 0)
}
Try it out, let me know what you think. I'm sure there are more features that can be added without much trouble, but I dunno what would be useful.