Container operators
Introduction
Having a container without a means to conveniently operate on all of its elements is quite disappointing; why then to have it in the first place? That's why all programming and scripting languages that support containers provide a means to effectively operate on them.
In most of these languages the means is special looping language constructs (such as the for, while, do..loop, etc.). In AVSLib, since the underlying script language does not support natively containers at all, it is a set of special AVSLib functions, collectively called container operators.
Container operators give the ability to operate on all elements of a container in a single step. In addition, coupled with the subrange selection functions that AVSLib provides for each container type, they give the ability to operate on subranges of a container in a single step.
Currently AVSLib supports only one container type, the array.
Array operators
There are five fundamental array operator functions, from which a whole family of array operator functions can be derived. They are presented below:
The first four of the above functions operate on the individual elements of the array (or arrays) passed as arguments and produce a new array. The fifth operates both on the individual elements of the array and the array as a whole producing a single value[1], which is the analog of the integral of the array. Let's have a closer look on them.
ArrayOpValue
The operator function performs the requested operation between a scalar (a single value) and every array element. In effect the operator performs the [vector] <op> scalar or scalar <op> [vector] action.
The operation, which is provided as a string, can be anyone that Avisynth script language supports for the types of scalar and element (for example "+", "-", "*", "/", "%", "&&", etc.).
The operator function performs the operation with element as the first operand (ie the expression evaluated is [element] <op> scalar). If this is not the desired order, the optional argument array_first can be set to false to reverse the order of the operands (ie to evaluate the expression scalar <op> [element]).
Examples
a1 = ArrayCreate(4, 2, 6, 3, 5, 1, 7)
# lets add 3 to all a1 elements
a2 = ArrayOpValue(a1, 3, "+") # a2 == "7,5,9,6,8,4,10"
# lets divide all a1 elements by 3
a3 = ArrayOpValue(a1, 3, "/") # a3 == "1,0,2,1,1,0,2"
# lets divide 3 by each element of a1 (we need the "false", in order this to work)
a4 = ArrayOpValue(a1, 3, "/", false) # a4 == "0,1,0,1,0,3,0"
# what happened? well all operands are ints and so int division was performed
# now lets force float division by making 3 -> 3.0
a5 = ArrayOpValue(a1, 3.0, "/") # a5 == "1.3333,0.6667,2.0,1.0,1.6667,0.3333,2.3333"
a6 = ArrayOpValue(a1, 3.0, "/", false) # a6 == "0.75,1.5,0.5,1.0,0.6,3.0,0.4286"
# lets create an array of clips and add start_logo and end_logo at their
# beggining and end respectively (single clips are also scalar values)
c1 = AVIsource(...)
...
c10 = AVIsource(...)
start_logo = AVIsource(...)
end_logo = AVIsource(...)
ac1 = ArrayCreate(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)
# since we are interested in the final result we reassign ac1 to reuse identifiers;
# we also use OOP notation, for its better readability
ac1 = ac1.ArrayOpValue(start_logo, "+", false)
ac1 = ac1.ArrayOpValue(end_logo, "+")
# we are done; note the false in the first call, to put start_logo as the left operand
ArrayOpFunc
The operator function applies a user-defined function to every array element. In effect the operator performs the f([vector]) action.
The function must accept element as its first argument. Additional arguments can be defined as an argument string (a string-representation of the additional arguments passed to the function separated by commas).
Examples
# lets calculate the y's of a curve on the interval [40, 200]
# the curve is y = 2.345x-1 + 3.12x - 4.002x1.5
Function my_curve(float x) {
coef = "5.347,-2.11,1.002"
pows = "-1,1,1.2"
return PowSeriesAA(x, coef, pows)
}
cvx = ArrayCreate(40,80,120,160,200,240,280,320,360,400)
cvy = cvx.ArrayOpFunc("my_curve")
# now pass the cvx, cvy to an animation filter such as PolygonAnim()
...
# a custom filter that resizes a clip to NTSC dimensions, adjusts hue,
# brightness and contrast and softens a little between frames
Function my_filter(clip c) {
ret = c.ConvertToYUY2()
ret = ret.BilinearResize(720,480)
ret = ret.Tweak(hue=15, bright=2.5, cont=1.2, coring=false)
ret = ret.TemporalSoften(4,4,8,15,2)
return ret
}
# lets create an array of clips and then apply my_filter() to them
c1 = AVIsource(...)
...
c10 = AVIsource(...)
ac1 = ArrayCreate(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)
# lets convert all clips' framerate its easier to do in one step
# after array creation; that's operators are made for
ac1 = ac1.ArrayOpFunc("AssumeFPS", "25")
# now apply your custom filter
ac1 = ac1.ArrayOpFunc("my_filter")
ArrayOpArray
The operator function performs the requested operation between every element pair of the two arrays supplied as arguments. In effect the operator performs the [vector1] <op> [vector2] action.
The operation, which is provided as a string, can be anyone that Avisynth script language supports for the types of element pairs (for example "+", "-", "*", "/", "%", "&&", etc.).
The order of operands in the expression evaluated ([element] <op> [element]) is decided by the ordering of the arrays in the argument list; the elements of the first array are always the left operand of the expression.
Examples
# titles and movies are arrays of 20 clips each (say a collection of family clips
# produced during your vacations and the intros for each one, respectively)
titles = ArrayCreate(tvac1, ..., tvac20)
movies = ArrayCreate(mvac1, ..., mvac20)
# convert to common format (if all clips have the same, skip this step)
titles = titles.ArrayOpFunc("ConvertToYUY2")
movies = movies.ArrayOpFunc("ConvertToYUY2")
# now lets combine the intros with the respective family clips
stories = ArrayOpArray(titles, movies, "+")
...
# (x0,y0) and (x0,y4) define two curves on our clip coordinates
x0 = "100,180,260,340,420,500,580"
y0 = "0,100,350,480,350,250,0"
y4 = "50,200,450,580,450,350,50"
# lets generate 3 more curves (x0,y1), (x0,y2), (x0,y3) evenly distributed between
# (x0,y0) and (x0,y4), in order to to feed them all later to an animation function
# such as PolygonAnim()
# 1st step: calculate the span between curves (y4[i] - y0[i])
ysp = ArrayOpArray(y4, y0, "-")
# 2nd step: divide span by 4 and round to integer
ysp = ysp.ArrayOpValue(4.0, "/").ArrayOpFunc("Round")
# 3rd step: get curve n+1 by adding final ysp to curve n
y1 = ArrayOpArray(y0, ysp, "+")
y2 = ArrayOpArray(y1, ysp, "+")
y3 = ArrayOpArray(y2, ysp, "+")
ArrayOpArrayFunc
The operator function applies a user-defined function to every element pair of the two arrays supplied as arguments. In effect the operator performs the f([vector1],[vector2]) action.
The function must accept element[vector1], element[vector2] as its two first argument. Additional arguments can be defined as an argument string (a string-representation of the additional arguments passed to the function separated by commas).
Examples
Function my_subtitle(clip c, string s) {
return c.SubTitle(s, size=24, text_color=$ffffff)
}
c1 = AVIsource(...)
...
c4 = AVIsource(...)
# ac1 is a clip array; at1 is a string array containing subtitles
ac1 = ArrayCreate(c1, c2, c3, c4)
at1 = "subtitle 1,subtitle 2,subtitle 3,subtitle 4"
# create an array with subtitled clips
sc = ArrayOpArrayFunc(ac1, at1, "my_subtitle")
...
# return a simple overlay of the clips passed as arguments
Function my_overlay(clip base, clip ovl, string "omode") {
omode = Default(omode, "blend")
opac = omode == "blend" ? 0.5 : 1.0
return Overlay(base, ovl, mode=omode, opacity=opac)
}
# ac1 and ac2 are two clip arrays with 4 elements each
ac1 = ArrayCreate(c1, c2, c3, c4)
ac2 = ArrayCreate(d1, d2, d3, d4)
# lets overlay them
ov1 = ArrayOpArrayFunc(ac1, ac2, "my_overlay") # this will use 'blend' as mode
# string arguments to user-supplied functions must be quoted
ov2 = ArrayOpArrayFunc(ac1, ac2, "my_overlay", StrQuote("add"))
# lets overlay the results one more time to see what happens
fin = ArrayOpArrayFunc(ov1, ov2, "my_overlay", StrQuote("luma"))
...
ArraySum
The operator function applies an optional elm_func user-defined function to every array element (if none is supplied it defaults to element itself). It then sums the return values of elm_func with an optional sum_func user-defined function (if none is supplied it defaults to simple addition: "+"). In effect the operator performs the sum_func(elm_func([vector])) action which is the equivalent of integrating over the array elements.
Usually, the precise order of application of sum_func will not be of importance. However, if the outcome of sum_func is not symmetrical to its main operands (ie the first two required arguments which correspond to array elements), it may be. Therefore, the following paragraph presents the internals of the summing process.
The summing of values is always performed in the backward direction, from array end (element with index array.ArrayLen()) to start (element with index 0).
Thus, the full expansion of the sum operation has the form (values inside brackets denote array elements with the designated index, ie [0] means array[0]):
sum_func(elm_func([0], elm_args), sum_func(elm_func([1], elm_args),
sum_func(elm_func([2], elm_args), sum_func( ... sum_func(
elm_func([ArrayLen-2], elm_args),
elm_func([ArrayLen-1], elm_args),
sum_args) ... , sum_args), sum_args), sum_args), sum_args)
Therefore, the first argument of sum_func is always the result of the application of elm_func on an array element, while the second is the result of the succesive application of sum_func on all elm_func results for array elements with index > element's index. For the last two array elements sum_func is applied directly to the outcomes of elm_func on them.
Since the order of operation of sum_func is fixed inside the operator function's code, if for any reason a different order is required, the only way to accomplish this is to use the array manipulation functions (such as ArrayInvert()) for setting the desired order of array element's before passing it to ArraySum.
Examples
# lets use the array 'ac1' produced by the second example of ArrayOpValue
# 'ac1' contains 10 clips with a front and back-cover logo
# now lets concatenate all stories to a single clip
my_clip = ac1.ArraySum()
# lets use the array 'stories' produced by the first example of ArrayOpArray
# 'stories' contains 20 clips with a front-cover title
# now lets concatenate all stories to a single clip, using Dissolve
# (with overlap=5) to create a smoother transition
my_movie = stories.ArraySum(sum_func="Dissolve", sum_args="5")
# lets calculate an integral and print the results
# the function defining the curve
# y = 2.345x-1 + 3.12x - 4.002x1.5
Function my_f(float x) {
coef = "2.345,3.12,-4.002"
pows = "-1,1,1.5"
return PowSeriesAA(x, coef, pows)
}
# the function calculating the area using trapezoid rule
Function t_area(float y1, float y2, float dx) {
Assert(dx > 0, "dx must be greater than zero")
return (y1 + y2) * dx / 2
}
# divide [0.5..1] to subintervals with width 0.05
ax = ArrayRange(0.5, 1, step=0.05)
# now calculate the integral of a function f(x) ie the area between y=0 and y=f(x)
# use as f(x) 'my_f'
# to calculate the integral use the trapezoid rule (in sum_func)
area = ax.ArraySum(elm_func="my_f", sum_func="t_area", sum_args="0.05")
return Print("The xs are:", ax, "The ys are:", \
ax.ArrayOpFunc("my_f"), "The area is:", area)
Usage (what to do and what not to do)
In order to be able to operate on every container regardless of its elements' type(s) without imposing significant overhead, operator functions do not check the validity of the parameters passed to them (except some trivial checks, such as equality of the number of elements of arrays when >1 array are passed as arguments). This has the consequence that common errors made by the user during the coding of the script, for example
- constructing an array with erroneous values or format (this more easily happens when you hard-code an array as a string) and then passing it to the operator,
- mispelling a function name,
- forgetting to quote strings (with the StrQuote() function) contained inside the extra argument strings that a user-supplied function may require,
- forgetting to place commas between the individual arguments composing an extra argument string,
- putting excess commas at the start or end of an extra argument string,
- selecting an operation not supported by the array's elements type (such as "||" for numeric types),
- logic errors inside user-supplied functions that return invalid results,
etc. may result in an error message box by Avisynth pointing inside the code body of the operator function.
There are also many ways (at least for earlier versions of Avisynth) for a call to an operator to crash Avisynth and the video rendering application, due to the limitations imposed to AVSLib by the host application (Avisynth). Two classes of possible ways are the following:
- (Avisynth versions 2.5.5 and earlier) If the user instructs the operator function to return a value with a much larger string representation than the original element (for example an entire array) for each element of the array parsed and the array is significant in size, a container exceeding the maximum allowed string limit may be created and a fatal buffer overflow condition will eventually arise.
- If a complex iterrative function for processing container elements is passed to the operator along with a large container, the Avisynth stack space may well be exausted.
Thus, the following list of best usage procedures should be followed in order to maximize the benefits from operators and containers altogether.
- Carefully review the library specifications section of the documentation, in order to avoid passing arrays of inappropriate size to operator functions.
- Before supplying a user function to an operator, test it passing single values as arguments. It is easier to debug a single function call and you also avoid tampering with the source code of AVSLib.
- Confirm that the arrays passed to the operator have elements with the correct type and value. The debugging helpers Break and BreakIf are especially useful for this purpose. Simply, break before invoking the operator function to inspect variables and assure everything looks normal.
- Confirm the validity of all arguments passed to the operator. In particular, ensure that argument strings are properly constructed.
- Avoid putting break points inside the AVSLib source code; you risk accidentally modifying it.
They are rarely needed also. For example:
- If you want to break inside the ArrayOpValue or ArrayOpArray operator functions, you can use a custom function to perform the desired operation and then place the breakpoint inside this function and use ArrayOpFunc or ArrayOpArrayFunc, respectively to achieve the same operation.
- If you want to break inside the ArrayOpFunc or ArrayOpArrayFunc operator functions, simply place the breakpoint inside the user function that you supply.
- If you want to break inside ArraySum, provide
custom copies of the Self and
Sum2 functions (you can copy the definitions from
the base :: core module or provide them by your self;
they simply return x and x1 + x2) for the elm_func and
sum_func arguments of ArraySum, respectively. Then place the breakpoint inside
those functions.
If you already supplying a custom function for any of the above arguments, simply place the breakpoint inside the custom function.
- If you ever need to put a breakpoint inside the AVSLib source code, then make a copy of the original module(s) file(s) and after debugging replace the modified files with the original copies.
[1]: This is the usual case. It is though possible, with proper coding of the elm_func and sum_func arguments of ArraySum function (user-supplied functions) to return an array as a result.