OpenMediaLib User and Development Guide
- OpenMediaLib User Development Guide
- Introduction
- High Level Use
- Reverse Polish Notation
- Applying RPN to Video/Audio
- Clip Modifications
- Compositing
- Playlists
- Stack Manipulations
- Advanced Stack Usage
- Aspect Ratio Considerations
- The Encoding Filter Graph
- Compositing Revisited
- Really, Really Advanced Stack Usage
- General Audio Issues
- Python
- Interpolation
- Threading
Advanced Stack Usage
As with the previous section, this section describes features of the OML RPN stack which may or may not be useful – they're certainly not 'needed' in the sense that the functionality they provide is unavailable without them, but they provide more insight into the stack mechanism than has previously been shown, and the logic is almost certainly useful.
The first feature is the ability to define functions – as with other stack syntax, the roots lie firmly in Forth. Forths syntax for function definition is defined as:
: name operations ;
and OML has an identical structure.
Traditionally, Forth documentation tends to revolve around the redefinition of words that have already been introduced and this documentation will follow that convention.
So, let's assume we wish to 'reinvent' the swap operation defined in the previous section, we could use the following:
: swap a ! b ! a b ;
Note that this implies that the a and b variables are local to the function.
The use and result of the function is identical to that of the original word:
<input1> <input2> swap == <input2> <input1>
A more interesting definition is the following:
: insert-greyscale head decap! greyscale head ; : greyscale filter:chroma ;
And a valid use could be:
<input1> <input2> filter:composite insert-greyscale
The interesting point here is that functions can be defined in any order – insert-greyscale invokes greyscale and it is perfectly valid to have greyscale defined after (as shown).
In fact, you can redefine functions in the same manner – any subsequent use of a redefined function (whether direct or indirect) will always result in the most recent definition being used.
To understand why, you simply have to remember that OMLs RPN usage is lazy – there's no compilation implied – under the hood, the function is simply remembered in the manner it was defined (as a sequence of string operators), and on invocation, those operators are simply pushed on to the stack.
Note that in its current state (sans branching and conditional logic), recursion is not supported. There is no attempt to make the 'language' of the OML RPN stack Turing Complete. It is merely included as a non-obtrusive/minimal overhead convenience.
From the command line, you can use the function definition syntax directly, however, you need to be aware of the conventions imposed by your shell – for example, in standard bourne shell, the ; is used to concatenate command sequences:
ls ; cd ..
would run the UNIX ls command, followed by cd .. - this means that in order to use the function syntax from the command line, we need to impose shell specific guards to avoid ambiguity – bourne provides this in the form of the \ guard.
As a result, the following is valid:
./rpn.py : greyscale filter:chroma \; input1.avi greyscale
Without the \, the shell would attempt to treat input1.avi as a command, and being non-executable, the result would be a shell error.
There is a further important distinction between Forth and OML – in Forth, the stack is 'active' which means that lazy evaluation can't be assumed – to clarify, consider:
1 2 + 3 4 *
In Forth, the resultant stack would be:
3 12
In other words, both expressions are operated on as soon as they're pushed on to the stack.
By contrast, OML evaluates on a pop, so when you push strings on to the stack as follows:
<input1> <input2> filter:composite <input3> <input4> filter:composite
the pop will convert the right most expression only.
Note that this is not entirely consistently applied – consider:
<input> input ! colour: input filter:composite
If the assignment were not operated on immediately, the right hand expression would not be able to resolved the 'input' variable (this would result in the composite filer being connected to a Null operator for the foreground – this is 'valid' in that the graph can be constructed in such a manner, but unless that connection is properly resolved before use, the graph will fail to execute correctly).
This extends to all words we have discussed to date – it is necessary to evaluate for assignment, swap, dup, clone, decap and any user defined function which involves any of those operations.
To clarify – operations which involve the introduction of new inputs and filters have a lazy evaluation treatment. Operations which involve stack manipulations are immediate.
This contradiction, while possibly confusing at first, has an important implication.
In an earlier section, we introduced the playlist filter with the following example of use:
<input1> <input2> filter:playlist slots=2
Now consider what would happen if the 'filter:playlist' operator were immediate – the slots=2 modifier would be applied after the initial construction of the playlist (which, by default, has an arbitrarily assigned slots value of 1).
Thus, the node itself would need to be aware of the stack in order to handle the post connection 'slots=2' assignment.
By using lazy evaluation, we can create the operator and all its modifiers before use. As a result, the underlying filter implementation is entirely divorced from the stack (or any other mechanism for input acquisition).
This could be easily rectified if we were to swap things round:
<input1> <input2> slots=2 filter:playlist
This would also be more consistent with RPN and would allow both lazy and immediate evaluation.
So why not do it that way? Because lazy evaluation was considered desirable in this domain, and the author considered the right hand property modifier placement simpler for other developers to understand.
