Packages
vtc
0.9.0
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/timecode_eval.ex
defmodule Vtc.Timecode.Eval do
@moduledoc false
alias Vtc.Framerate
alias Vtc.Source.Frames
alias Vtc.Timecode
@self_alias [:Vtc, :Timecode, :Eval]
# Escapes variable that the default rate for the eval will be kept in,
@default_rate_var_ast Macro.var(:default_rate, :vtc_eval)
@spec eval([at: Framerate.t() | number() | Ratio.t(), ntsc: Framerate.ntsc()], Macro.input()) :: Macro.t()
def eval(opts, body) do
body =
case body do
[do: body] -> body
body -> body
end
default_rate = Keyword.get(opts, :at, nil)
ntsc = Keyword.take(opts, [:ntsc])
setup_default = aliased_func_call(@self_alias, :setup_rate, [], [default_rate, ntsc])
set_default_var = {:=, [], [@default_rate_var_ast, setup_default]}
body = Macro.postwalk(body, &replace_ops_ast/1)
quote do
with do
unquote(set_default_var)
unquote(body)
end
end
end
@tc_ops %{
{:+, 2} => :add,
{:-, 2} => :sub,
{:==, 2} => :eq?,
{:<, 2} => :lt?,
{:<=, 2} => :lte?,
{:>, 2} => :gt?,
{:>=, 2} => :gte?,
{:*, 2} => :mult,
{:/, 2} => :div,
{:%, 2} => :rem,
{:div, 2} => :div,
{:divrem, 2} => :divrem,
{:-, 1} => :minus,
{:abs, 1} => :abs
}
@single_tc_ops [
{:*, 2},
{:/, 2},
{:%, 2},
{:div, 2},
{:divrem, 2},
{:-, 1},
{:abs, 1}
]
# Replaces operators with calls to a `Timecode` function.
@spec replace_ops_ast(Macro.t()) :: Macro.t()
defp replace_ops_ast({op, _, args} = ast) when is_map_key(@tc_ops, {op, length(args)}) do
tc_func_name = Map.fetch!(@tc_ops, {op, length(args)})
tc_arg_count = if {op, length(args)} in @single_tc_ops, do: 1, else: 2
replace_tc_op(tc_func_name, tc_arg_count, ast)
end
defp replace_ops_ast(ast), do: ast
# Replaces ops where both args are timecodes.
@spec replace_tc_op(atom(), pos_integer(), Macro.t()) :: Macro.t()
defp replace_tc_op(tc_func_name, tc_arg_count, ast) do
{_, meta, args} = ast
args
|> Enum.with_index()
|> Enum.map(fn
{arg_ast, i} when i < tc_arg_count -> wrap_tc_val_in_cast(arg_ast, meta)
{arg_ast, _} -> arg_ast
end)
|> then(&timecode_func(tc_func_name, meta, &1))
end
# Wraps timecode args in `cast_timecode_arg/2`.
@spec wrap_tc_val_in_cast(Macro.t(), Macro.metadata()) :: Macro.t()
def wrap_tc_val_in_cast({:num, _, [arg]}, _), do: arg
def wrap_tc_val_in_cast(arg_ast, meta),
do: aliased_func_call([:Vtc, :Timecode, :Eval], :cast_timecode_arg, meta, [arg_ast, @default_rate_var_ast])
# Casts `frames` to `default` rate if one was provided, and `frames` is not already
# a timecode value.
@doc false
@spec cast_timecode_arg(Timecode.t() | Frames.t(), Framerate.t() | nil) :: Timecode.t() | Frames.t()
def cast_timecode_arg(%Timecode{} = timecode, _), do: timecode
def cast_timecode_arg(frames, nil), do: frames
def cast_timecode_arg(frames, default_rate) do
if Frames.impl_for(frames) do
Timecode.with_frames!(frames, default_rate)
else
frames
end
end
# Inserted into the head of each eval block. Sets up the default framerate variable.
@spec setup_rate(Framerate.t() | number() | Ratio.t() | nil, ntsc: Framerate.ntsc()) :: Framerate.t() | nil
def setup_rate(nil, _), do: nil
def setup_rate(%Framerate{} = rate, _), do: rate
def setup_rate(rate, opts) do
opts = if is_float(rate), do: Keyword.put_new(opts, :coerce_ntsc?, true), else: opts
Framerate.new!(rate, opts)
end
# Escapes a timecode function name for use in an ast.
@spec timecode_func(atom(), Macro.metadata(), [Macro.t()]) :: Macro.t()
defp timecode_func(name, meta, args), do: aliased_func_call([:Vtc, :Timecode], name, meta, args)
# Constructs the AST for an alias + function call.
@spec aliased_func_call([atom()], atom(), Macro.metadata(), [Macro.t()]) :: Macro.t()
defp aliased_func_call(aliases, func_name, meta, args) do
func_call = {:., meta, [{:__aliases__, meta, aliases}, func_name]}
{func_call, meta, args}
end
end