Created
October 1, 2015 23:42
-
-
Save FichteFoll/08b9d98699438b768552 to your computer and use it in GitHub Desktop.
Karaoke effect using Automation 4 for Ore no Imouto TR 15 (Ending)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- This script was written by FichteFoll for http://akatsuki-subs.net | |
-- You can see this FX in Ore no Imouto TR [True Route] 15 (Ending) | |
-- MIT license, (c) FichteFoll | |
--[[ | |
Remarks: | |
I don't really know what I should mention here. | |
Overall work time on this file (including searching for inspiration): 7 hours | |
2014-12-24 | |
]] | |
require("karaskel") | |
require("op_overloads") -- https://gist.github.com/b6815727db496ceed1ae | |
-- use the never-changing parts for my Automation FXes from this include file | |
require("FX_utils") | |
script_name = "Oreimo TR15 FX" | |
script_description = "Nothing yet" | |
script_author = "FichteFoll" | |
script_version = 0.1 | |
math.randomseed(1337) | |
-------------------------------------------------------------------------------- | |
-- variables/constants | |
local tmp = { | |
} | |
local _line = { | |
fadein = { | |
start = -100, | |
dur = 250, | |
mid = 150, | |
diff = 40, | |
accel = 0.5, | |
scale_y = 20, | |
bord_scale = 2/3.0 | |
}, | |
fadeout = { -- subs only | |
dur = 500 | |
}, | |
shad = 1, | |
blur = 2 | |
} | |
local _syl = { | |
hl = { | |
bord = 5, | |
blur = 3, | |
color = "&HFFFFFF&", | |
alpha = "&H10&", | |
accel = 0.25, | |
scale = 100, | |
startfade = 0.4, | |
fadeaccel = 2, | |
extend = 150 | |
}, | |
hl2 = { | |
move = 50, | |
diff = 70 | |
}, | |
fadeout = { | |
dur = 200, | |
accel = 0.2 | |
} | |
} | |
-------------------------------------------------------------------------------- | |
local log | |
function init(subs, meta, styles) | |
log = aegisub.log | |
end | |
function do_fx(subs, meta, styles, line, baseline) | |
if not line.style:startswith("ed15_") then | |
return -- not processed | |
end | |
-- use to differentiate between karaoke styles and the actual subtitles | |
local romaji = line.style:startswith("ed15_rom") | |
local kanji = line.style:startswith("ed15_kan") | |
local trans = line.style:startswith("ed15_ger") | |
-- pre-process1 | |
parse_style_colors(line, styles[line.style]) | |
-- parse_line_colors(line) | |
-- pre-process2 | |
if trans then | |
-- create syllables for each word in translation (but not in comments) | |
line.text = "{\\k1}" .. line.text:gsub("%s*{.-}%s*", ''):gsub(" ", " {\\k1}") | |
end | |
karaskel.preproc_line(subs, meta, styles, line) | |
parse_syls(line) | |
-- general line parts | |
line._pos = "\\an5\\pos(%d,%d)" % {line.center, line.middle} -- will be overwritten, likely | |
line._pre = "\\shad%s\\blur%s" % {_line.shad, _line.blur} | |
line._color = "" | |
local slide_length = not romaji | |
and line.width + line.style.margin_l + 20 | |
or line.width + line.style.margin_v + 20 | |
-- differ between romaji and kanji (x and y); [true] is for romaji | |
local dimensions = {[true] = "y", [false] = "x"} | |
local slide = {[true] = slide_length, [false] = 0} | |
if trans then | |
-- log(repr(line._noblank) .. "\n") | |
end | |
for si, syl in ipairs(line._noblank) do | |
for ci, char in ipairs(get_charsyls(syl)) do | |
-- no need to handle whitespaces | |
if (syl.text_stripped == ' ' or syl.text_stripped == ' ') then | |
goto skipchar -- functions as `continue` | |
end | |
local l = table.copy(line) | |
-- fadein and hl1 are per syl for romaji & trans | |
if kanji or ci == 1 then | |
local middle = not kanji | |
and line.middle | |
or line.styleref.margin_v + char.left | |
local center = not kanji | |
and line.left + syl.center | |
or meta.res_x - line.styleref.margin_r - char.width/2.0 | |
-- Fadein ----------------- | |
local start_rel = (si - 1) * -_line.fadein.diff - _line.fadein.dur | |
l._pos = "\\an5\\move(%.2f,%.2f,%.2f,%.2f,%d,%d)" % { | |
center - slide[not kanji], | |
middle - slide[kanji], | |
center, middle, | |
0, _line.fadein.mid | |
} | |
l._fadein = | |
"\\fsc%%s%.2f\\%%sbord%.1f\\t(%d,%d,%.2f,\\fsc%%s%.2f\\%%sbord%.1f)" % { | |
_line.fadein.scale_y, line.styleref.outline * _line.fadein.bord_scale, | |
_line.fadein.mid, _line.fadein.dur, _line.fadein.accel, | |
line.styleref.scale_y, line.styleref.outline | |
} | |
l._fadein = l._fadein % { | |
dimensions[not kanji], dimensions[not kanji], | |
dimensions[not kanji], dimensions[not kanji] | |
} | |
l.start_time = line.start_time + start_rel | |
-- handle entire trans line here | |
if trans then | |
l._pre = l._pre .. "\\fad(0,300)" | |
else | |
l.end_time = line.start_time + syl.start_time | |
end | |
-- construct line text | |
l.text = string.concat( | |
'{', l._pos, l._pre, l._fadein, '}', | |
(kanji and char or syl).text_stripped | |
) | |
subs.append(l) | |
if trans then | |
goto skipchar -- `continue` | |
end | |
-- Highlight --------------- | |
l._pos = "\\an5\\pos(%.2f,%.2f)" % { | |
center, middle | |
} | |
l._pre = "\\shad0\\blur%s" % {_syl.hl.blur} | |
l._hl = "\\bord%s\\1a&HFF&\\3c%s\\3a%s" % { | |
_syl.hl.bord, _syl.hl.color, _syl.hl.alpha | |
} | |
.. | |
"\\t(%.2f,\\fscx%.2f\\fscy%.2f)\\t(%d,%d,%.2f,\\3a&HFF&)" % { | |
_syl.hl.accel, _syl.hl.scale, _syl.hl.scale, | |
syl.duration * _syl.hl.startfade, syl.duration + _syl.hl.extend, | |
_syl.hl.fadeaccel | |
} | |
l.start_time = l.end_time | |
l.end_time = l.start_time + syl.duration + _syl.hl.extend | |
-- construct line text | |
l.text = string.concat( | |
'{', l._pos, l._pre, l._hl, '}', (kanji and char or syl).text_stripped | |
) | |
subs.append(l) | |
end | |
-- per char this time | |
local middle = romaji | |
and line.middle | |
or line.styleref.margin_v + char.left | |
local center = romaji | |
and line.left + char.center | |
or meta.res_x - line.styleref.margin_r - char.width/2.0 | |
-- Highlight2 -------------- (per char also for romaji) | |
l = table.copy(line) | |
local move_speed = 1.0 * _syl.hl2.move / syl.duration | |
local hl_end = (ci-1) * _syl.hl2.diff + syl.duration | |
local slide_hl = {[true] = hl_end * move_speed, [false] = 0} | |
l._pos = "\\an5\\move(%.2f,%.2f,%.2f,%.2f)" % { | |
center, middle, | |
center - slide_hl[romaji], | |
middle - slide_hl[not romaji] | |
} | |
l._hl = "" | |
l.start_time = line.start_time + syl.start_time | |
l.end_time = l.start_time + hl_end | |
-- construct line text | |
l.text = string.concat( | |
'{', l._pos, l._pre, l._hl, '}', char.text_stripped | |
) | |
subs.append(l) | |
-- Fadeout -------------- (per char also for romaji) | |
local slide_out = {[true] = _syl.fadeout.dur * move_speed, | |
[false] = romaji and -line.height/2 or meta.res_x + char.width/2} | |
local out_pos = {[true] = romaji | |
and center - slide_hl[romaji] - slide_out[romaji] | |
or middle - slide_hl[not romaji] - slide_out[not romaji], | |
[false] = slide_out[false]} | |
l._pos = "\\an5\\move(%.2f,%.2f,%.2f,%.2f)" % { | |
center - slide_hl[romaji], | |
middle - slide_hl[not romaji], | |
out_pos[romaji], | |
out_pos[not romaji] | |
} | |
l._fadeout = "\\t(%.2f,\\fr%s90)" % {_syl.fadeout.accel, dimensions[not romaji]} | |
l.start_time = l.end_time | |
l.end_time = l.start_time + _syl.fadeout.dur | |
-- construct line text | |
l.text = string.concat( | |
'{', l._pos, l._pre, l._fadeout, '}', char.text_stripped | |
) | |
subs.append(l) | |
end | |
::skipchar:: -- goto anchor | |
end | |
return 1 | |
end | |
--[[ | |
Outline: | |
- fadein from left side | |
- slightly move left on hl | |
- fadeout upwards, by syl and char | |
Fadein: | |
Effect: | |
- slide in from left, very thin vertically | |
- expand to full size once at target postion | |
- right to left? | |
- iterative | |
- somewhat short | |
Timeline: | |
[---step1----| ] | |
[ |-step2-] | |
step1 | |
- \move (left to normal) | |
- \fscy < 20, or \fry | |
- \bord < normal | |
step2 | |
- \fscy -> 100, or \fry -> 0 | |
- \bord -> normal | |
Fadeout: | |
- move upwards after hl, per char | |
- carry over highlight movement | |
Highlight: | |
Effect: | |
- Move to left | |
- Also display shadow-ish border behind original position | |
Timeline: | |
[---------step1--------] | |
[---------step2--------] | |
[ |-----step3----] | |
step1 | |
- movement | |
step2 (accelerated) | |
- "grow" from shadow (currently none) | |
step3 (accelerated) | |
- fade | |
Subtitles: | |
- | |
]] | |
register() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
A template script used for Automation 4 karaoke FXes with Aegisub | |
You will want to call `register()` at the end of your script or whenever at least `script_name` is defined. | |
Functions to be defined: | |
* init(subs, meta, styles) [optional] | |
Called after all possibly existing previous fx lines have been deleted and the karaoke lines restored. | |
Do whatever you like in here. Won't be called if `init` is nil. | |
@subs | |
The subs table passed by aegisub. | |
@meta, @styles | |
Results of karaskel.collect_head(subs, false). | |
* do_fx(subs, meta, styles, line, baseline) | |
Called when is_parseble(line) returns `true`, i.e. when you should parse that line. | |
The parsed line will be commented and its effect set to "karaoke" iff this function returns a value evaluating | |
to `true`. | |
@subs | |
Same as for init(). | |
@meta, @styles | |
Same as for init(). | |
@line | |
This is the line source you will be using. You can do anything with it, it's in fact a table.copy of | |
@baseline with its effect set to "fx" and layer to 2. | |
Has a field named "_i" which defines the line's index in `subs` and a field named "_li" which counts up | |
every time do_fx is called. | |
@baseline | |
The table representing the line. | |
This is a REFERENCE, you need to table.copy() this before if you want to insert a new copy. | |
The line itself should not be modified at all, e.g. when you plan to run this macro a few times. | |
Also defines "_i" and "_li" (see @line). | |
Variables to be defined (before calling `register()`): | |
* script_name | |
Includes a variety of utility functions. | |
By FichteFoll, last modified: 2014-12-23 | |
]] | |
require("karaskel") | |
-- ############################# handler funcs ################################# | |
function macro_script_handler(subs) | |
aegisub.progress.title("Apply "..script_name) | |
script_handler(subs) | |
aegisub.set_undo_point('"Apply '..script_name..'"') | |
end | |
function script_handler(subs) | |
aegisub.progress.task("Getting header data...") | |
local meta, styles = karaskel.collect_head(subs, false) | |
-- undo the fx before parsing the karaoke data | |
aegisub.progress.task("Removing old karaoke lines...") | |
undo_fx(subs) | |
aegisub.progress.task("Applying effect...") | |
local i, maxi = 1, #subs | |
local li = 0 | |
if init then | |
init(subs, meta, styles) | |
end | |
while i <= maxi do | |
local l = subs[i] | |
if aegisub.progress.is_cancelled() then | |
if aegisub.cancel then aegisub.cancel() end | |
return | |
end | |
aegisub.progress.task(string.format("Applying effect (%d/%d)...", i, maxi)) | |
aegisub.progress.set((i-1) / maxi * 100) | |
if line_is_parseable(l) then | |
-- karaskel.preproc_line(subs, meta, styles, l) | |
li = li + 1 | |
l._li = li | |
l._i = i | |
-- prepare the to-be-copyied line | |
local line = table.copy(l) | |
line.effect = "fx" | |
line.layer = 2 | |
if do_fx(subs, meta, styles, line, l) then | |
l.comment = true | |
l.effect = "karaoke" | |
end | |
subs[i] = l | |
end | |
i = i + 1 | |
end | |
aegisub.progress.task("Finished!") | |
aegisub.progress.set(100) | |
end | |
function undo_macro_script_handler(subs, ...) | |
aegisub.progress.title("Undoing "..script_name) | |
undo_fx(subs) | |
aegisub.set_undo_point("\"Undo "..script_name.."\"") | |
end | |
function undo_fx(subs) | |
aegisub.progress.task("Unapplying effect...") | |
local i, maxi = 1, #subs | |
local ai, maxai = i, maxi | |
while i <= maxi do | |
if aegisub.progress.is_cancelled() then | |
if aegisub.cancel then aegisub.cancel() end | |
return | |
end | |
aegisub.progress.task(string.format("Unapplying effect (%d/%d)...", ai, maxai)) | |
aegisub.progress.set((ai-1) / maxai * 100) | |
local l = subs[i] | |
if (l.class == "dialogue" and not l.comment and l.effect == "fx") then | |
subs.delete(i) | |
maxi = maxi - 1 | |
else | |
if (l.class == "dialogue" and l.comment and l.effect == "karaoke") then | |
l.comment = false | |
l.effect = '' | |
subs[i] = l | |
end | |
i = i + 1 | |
end | |
end | |
end | |
-- ############################### parsing funcs ############################### | |
function parse_syls(line) | |
-- parse the line's syllables and add them to the `line._noblank` table | |
-- requires you to call karaskel.preproc_line first | |
line._noblank = {n = 0} | |
for i = 1, line.kara.n do | |
local syl = line.kara[i] | |
if (syl.duration > 0 and syl.text_stripped ~= '' | |
and syl.text_stripped ~= ' ' and syl.text_stripped ~= ' ') then | |
line._noblank.n = line._noblank.n + 1 | |
line._noblank[line._noblank.n] = syl | |
syl.i = line._noblank.n | |
syl._blank = false | |
else | |
syl._blank = true | |
end | |
end | |
end | |
function parse_style_colors(line, style) | |
line._c = {} | |
line._a = {} | |
for i, c, a in colors_from_style(style) do | |
line._c[i] = c | |
line._a[i] = a | |
end | |
end | |
-- Parse the color overrides on the line (assuming they are the only blocks before the {\k}-block) | |
-- and add them to line._colors | |
-- Lines should look like this: | |
-- {\c&H8D566C&\2c&HC982AE&\3c&H1A0110&}{\t(720,720,,\c&HFFFFFF&\2c&HA99E4F&\3c&H161501&)} | |
-- {\t(1900,1900,\2c&HBCA2C6&\c&H605064&\3c&H0B0006&)}{\k24}sho{\k24}se{\k16}n ... | |
function parse_line_colors(line) | |
line._colors = {_all = ""} | |
local colors_str, text = line.text:match("^(.-)({\\k.+)$") | |
if not colors_str then | |
-- we are probably in a "sub" block, just consume all the override blocks | |
-- wat? | |
colors_str = "" | |
text = line.text:gsub("({.-})", function (override) | |
colors_str = colors_str .. override | |
return "" | |
end) | |
end | |
if not colors_str then log("No match on line: %s\n", line.text); end | |
if colors_str and #colors_str > 0 then | |
-- collect color blocks | |
for str in colors_str:gmatch("{(.-)}") do | |
-- always assuming there is only one \t override tag each | |
local start, stop, colors = str:match("\\t%((%d+),(%d+),(%S-)%)") | |
-- log("start: %s, stop: %s, colors: %s\n" % {start or "nil", stop or "nil", colors or "nil"}) | |
if not colors then | |
colors = str:match("\\t%((%S-)%)") | |
end | |
local block = { | |
text = str, | |
start = tonumber(start) or 0, | |
stop = tonumber(stop) or (colors and line.duration or 0), | |
colors = colors or str | |
} | |
if not colors then -- supposed to happen only once | |
line._colors._base = block | |
else | |
table.insert(line._colors, block) | |
end | |
line._colors._all = line._colors._all .. str | |
end | |
-- log(repr(line._colors) .. "\n") | |
end | |
end | |
-- ############################### helping funcs ############################### | |
-- Returns the selected color `i` (or the first one) at `timestamp` given a few override codes. | |
-- Does not check for validity anywhere; do not abuse. Returns nil if color `i` was not found. | |
function color_at(col, timestamp, i) | |
if not i then i = "[1-4]"; end -- match any color number in the pattern | |
if i == 1 then i = "1?"; end -- 1 can be omitted | |
function _ret(col) | |
-- used to select the specified color "i"; nil if not found | |
return col.colors:match("\\"..i.."c(&H%w+&)") or nil | |
end | |
local active, last | |
-- iterate over override blocks for the currently active transition, consider only the last | |
local start, stop | |
for _, block in ipairs(col) do | |
if (timestamp > block.start and timestamp < block.stop and _ret(block)) then | |
active = block | |
end | |
end | |
-- ... for the lastly active transition | |
for _, block in ipairs(col) do | |
if (timestamp > block.stop and _ret(block)) then | |
last = block | |
end | |
end | |
if not last then last = col._base; end | |
if not active then | |
return _ret(last) -- just return the last color | |
end | |
-- select the specified color | |
local start, stop = _ret(last), _ret(active) | |
-- either no color or no "new" color found | |
if not stop then | |
-- log("stcol: %s\n", last.colors) | |
-- log("start: %s\n", start) | |
return start or nil | |
end | |
-- interpolate the color | |
local pct = (timestamp - active.start) * 1.0 / (active.stop - active.start) | |
return interpolate_color(pct, start, stop) | |
end | |
-- Searches for \t-tag times and shifts them by `by` | |
function shift_ttags(str, by) | |
return str:gsub("\\t%((%d+),(%d+)", function (start, stop) | |
return "\\t(%d,%d" % {tostring(tonumber(start) - by), | |
tostring(tonumber(stop) - by)} | |
end) | |
end | |
-- Create a list of charsyls used in char templates | |
function get_charsyls(syl) | |
local charsyls = {} | |
local charsyl = table.copy(syl) | |
local left = syl.left | |
for c in unicode.chars(syl.text_stripped) do | |
charsyl.text = c | |
charsyl.text_stripped = c | |
charsyl.text_spacestripped = c | |
charsyl.prespace, charsyl.postspace = "", "" -- for whatever anyone might use these for | |
charsyl.width = aegisub.text_extents(syl.style, c) | |
charsyl.left = left | |
charsyl.center = left + charsyl.width/2 | |
charsyl.right = left + charsyl.width | |
charsyl.prespacewidth, charsyl.postspacewidth = 0, 0 -- whatever... | |
left = left + charsyl.width | |
table.insert(charsyls, table.copy(charsyl)) | |
end | |
return charsyls | |
end | |
function repr(val) | |
if type(val) == "table" then | |
local str = "{" --"#%d:{" % #val | |
for k, v in pairs(val) do | |
str = str .. ("%s = %s, "):format(k, repr(v)) | |
end | |
return str:sub(1, -3) .. "}" -- trim last ", " | |
elseif type(val) == "string" then | |
return '"%s"' % val | |
else | |
return tostring(val) | |
end | |
end | |
function colors_from_style(style) | |
local i = 0 | |
function enum_colors() | |
i = i + 1 | |
if (i > 4) then | |
return nil | |
end | |
-- i, color, alpha | |
return i, color_from_style(style["color"..tostring(i)]), alpha_from_style(style["color"..tostring(i)]) | |
end | |
return enum_colors, nil, nil | |
end | |
function strip_comments(str) | |
return str:gsub("{.-}", "") | |
end | |
function xor(...) | |
-- return the first parameter which evaluates to `true` | |
args = {...} | |
for v in args do | |
if v then return v; end | |
end | |
return args[#args] -- return the last element if none is true | |
end | |
function _if(test, a, b) | |
return test and a or b | |
end | |
function line_is_parseable(l) | |
return (l.class == "dialogue" and not l.comment) | |
or (l.class == "dialogue" and l.comment and l.effect == "karaoke") | |
end | |
function round(num, idp) | |
local mult = 10^(idp or 0) | |
if num >= 0 then return math.floor(num * mult + 0.5) / mult | |
else return math.ceil(num * mult - 0.5) / mult end | |
end | |
function randomfloat(min, max) | |
return math.random() * (max - min) + min | |
end | |
function string.concat(...) | |
ret = "" | |
for _, str in pairs{...} do | |
-- there shouldn't be any nils here | |
ret = ret..tostring(str) | |
end | |
return ret | |
end | |
function string.startswith(self, piece) | |
return string.sub(self, 1, string.len(piece)) == piece | |
end | |
function string.endswith(self, piece) | |
return string.sub(self, -string.len(piece)) == piece | |
end | |
-- ############################### validation funcs ############################ | |
function macro_validation(subs) | |
for i = 1, #subs do | |
local l = subs[i] | |
if line_is_parseable(l) then | |
return true | |
end | |
end | |
return false, "No parseable line found" | |
end | |
function undo_macro_validation(subs) | |
for i = 1, #subs do | |
local l = subs[i] | |
if (l.class == "dialogue" and ( | |
(not l.comment and l.effect == "fx") or | |
( l.comment and l.effect == "karaoke") )) then | |
return true | |
end | |
end | |
return false, "No karaoke line found" | |
end | |
-- ############################## registering ################################## | |
function register() | |
aegisub.register_macro("Apply "..script_name, | |
"Processing script as templater", | |
macro_script_handler, | |
macro_validation) | |
-- should not be needed (for my setup at least, I have a separate macro which does this) | |
-- aegisub.register_macro("Undo "..script_name, "Removing templated lines", undo_macro_script_handler, undo_macro_validation) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
These are small operator overloads for general use in Lua, mainly to my likings. | |
Written by FichteFoll; thanks to Sleepy_Coder | |
2012-12-24 | |
]] | |
local STR, NUM, BOOL, NIL = {}, {}, {}, {} | |
STR = getmetatable('') | |
-- ("hello")[2] -> e | |
STR.__index | |
= function (self, key) | |
if type(key) == 'number' then | |
if key < 1 or key > self:len() then | |
error(("Attempt to get index %d which is not in the range of the string's length"):format(key), 2) | |
end | |
return self:sub(key, key) | |
end | |
return string[key] | |
end | |
-- str = "heeeello"; str[3] = "is" -> "heisello" || DOES NOT WORK! | |
STR.__newindex = | |
function (self, key, value) | |
value = tostring(value) | |
if type(key) == 'number' and type(value) == 'string' then | |
if key < 1 or key > self:len() then | |
error(("Attempt to set index %d which is not in the range of the string's length"):format(key), 2) | |
end | |
-- seems like strings are not referenced ... | |
self = self:sub(1, key-1) .. value .. self:sub(key+value:len(), -1) | |
-- print(("new value: %s; key: %d"):format(self, key)) | |
return self | |
end | |
end | |
-- string * num -> string.rep | |
STR.__mul = | |
function (op1, op2) | |
return type(op2) == 'number' and op1:rep(op2) or error("Invalid type for arithmetic on string", 2) | |
end | |
-- string % table -> string.format | |
STR.__mod = | |
function (op1, op2) | |
if type(op2) == 'table' then | |
if #op2 > 0 then | |
-- make `nil` to string | |
for k,v in pairs(op2) do | |
if v == nil then op2[k] = "nil"; end | |
end | |
return op1:format(unpack(op2)) -- sadly I can not forward errors happening here | |
else | |
error("Format table is empty", 2) | |
end | |
else | |
return op1:format(op2 == nil and "nil" or op2) | |
end | |
end | |
-- #string -> count chars | |
STR.__len = | |
function (self) | |
return self:len() | |
end | |
-- e.g. num = 1234.5; num:floor() | |
NUM.__index = math | |
-- rarely useful | |
BOOL.__index = BOOL | |
-- a wrapper for boolean tests | |
BOOL.b2n = | |
function (bool) | |
return type(bool) ~= 'boolean' and bool or (bool and 1 or 0) | |
end | |
-- various arithmetics on booleans by converting `false` to `0` and `true` to `1`. | |
-- `nil` will be converted to `0` as well, btw. | |
BOOL.__add = | |
function (op1, op2) | |
-- op2.b2n() won't work because it is possible that one of these ops is not a boolean | |
return BOOL.b2n(op1) + BOOL.b2n(op2) | |
end | |
BOOL.__sub = | |
function (op1, op2) | |
return BOOL.b2n(op1) - BOOL.b2n(op2) | |
end | |
BOOL.__mul = | |
function (op1, op2) | |
return BOOL.b2n(op1) * BOOL.b2n(op2) | |
end | |
BOOL.__div = | |
function (op1, op2) | |
return BOOL.b2n(op1) / BOOL.b2n(op2) | |
end | |
BOOL.__pow = | |
function (op1, op2) | |
return BOOL.b2n(op1) ^ BOOL.b2n(op2) | |
end | |
BOOL.__unm = | |
function (self) | |
return not self | |
end | |
-- copy BOOL's functions over to NIL and remove a few values | |
for key, val in pairs(BOOL) do | |
NIL[key] = val | |
end | |
NIL.b2n = nil | |
NIL.__unm = nil | |
-- nil[3] -> nil (no error) - is this behaviour useful? | |
NIL.__index = NIL | |
-- Apparently, Aegisub does not provide the debug module ... | |
if debug then | |
debug.setmetatable( 0, NUM ) | |
debug.setmetatable(true, BOOL) | |
debug.setmetatable( nil, NIL ) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment