27

« **on:** December 20, 2016, 04:19:35 AM »
Here's a lua script for manipulating time-value pairs. For the sake of operations the object is considered a function, lerping for values between defined points (this is what the cdp does for most things). + - * / are overloaded for operating on two TVPairs or one TVPairs and a scalar. % is overloaded as time shift and ^ is overloaded as convolution. There is also a function to generate TVPairs from a function, as well as a function for generating a polynomial from a TVPairs (sample and unsample). I'm using a pure lua matrix solver so unsample gets pretty unusable beyond ten or so data points. The compile function won't work outside of my frontend due to it using the file generating function altar_text.

`local TVPairs = {}`

function TVPairs:new( o )

o = o or {}

setmetatable( o, self )

self.__index = self

return o:sort()

end

function TVPairs:__tostring()

local out = ""

for i = 1, #self do

out = out .. self[i][1] .. " " .. self[i][2] .. "\n"

end

return out

end

function TVPairs:__call( t )

if #self == 0 or self[1][1] > t or self[#self][1] < t then return 0 end

for i = 2, #self do

if self[i][1] >= t then --lerp it

local x1, x2, y1, y2 = self[i-1][1], self[i][1], self[i-1][2], self[i][2]

local m = (y1-y2)/(x1-x2)

return m*(t-x1) + y1

end

end

print "Error in TVPairs:_call"

end

--Math

local function tvp_operate( lhs, rhs, operation )

if type(lhs) == "number" then

local data = {}

for i = 1, #rhs do data[i] = {rhs[i][1], operation( rhs[i][2], lhs )} end

return TVPairs:new( data )

elseif type(rhs) == "number" then

local data = {}

for i = 1, #lhs do data[i] = {lhs[i][1], operation( lhs[i][2], rhs )} end

return TVPairs:new( data )

else

local times = {}

for i = 1, #lhs do times[i] = lhs[i][1] end

for i = 1, #rhs do times[i+#lhs] = rhs[i][1] end

table.sort( times )

for i = 1, #times - 1 do

if times[i] == times[i+1] then table.remove( times, i ) end

end

local data = {}

for i = 1, #times do

data[i] = { times[i], operation( lhs(times[i]), rhs(times[i]) ) }

end

return TVPairs:new( data )

end

end

function TVPairs:__unm()

local tvp = {}

for i = 1, #self do

tvp[i] = {self[i][1], -self[i][2]}

end

return TVPairs:new( tvp )

end

function TVPairs.__add( lhs, rhs ) return tvp_operate( lhs, rhs, function( a, b ) return a + b end ) end

function TVPairs.__sub( lhs, rhs ) return tvp_operate( lhs, rhs, function( a, b ) return a - b end ) end

function TVPairs.__mul( lhs, rhs ) return tvp_operate( lhs, rhs, function( a, b ) return a * b end ) end

function TVPairs.__div( lhs, rhs )

return tvp_operate( lhs, rhs, function( a, b )

if b == 0 then return 0

else return a / b

end

end )

end

function TVPairs.__mod( lhs, rhs ) --time shift

local data = {}

if type( lhs ) == "number" then

for i,v in ipairs(rhs) do data[i] = { rhs[i][1] + lhs, rhs[i][2] } end

else

for i,v in ipairs(lhs) do data[i] = { lhs[i][1] + rhs, lhs[i][2] } end

end

return TVPairs:new( data )

end

function TVPairs.__pow( lhs, rhs ) --convolution

local data = TVPairs:new()

for _,v in ipairs(lhs) do

data = data + ( ( rhs * v[2] ) % v[1] )

end

return TVPairs:new( data )

end

function TVPairs.__concat( lhs, rhs )

if getmetatable( lhs ) == TVPairs and getmetatable( rhs ) == TVPairs then --combining two TVPairs

local data = {}

for i = 1, #lhs do data[i ] = lhs[i] end

for i = 1, #rhs do data[i+#lhs] = rhs[i] end

table.sort( data, function( a, b ) return a[1] < b[1] end )

for i = 1, #data - 1 do

if data[i][1] == data[i+1][1] then table.remove( data, i+1 ) end

end

return TVPairs:new( data )

else -- Only one of the two was a TVPairs, assume the other can be converted to TVPairs

if getmetatable( lhs ) == TVPairs then return lhs .. TVPairs:new( rhs )

else return TVPairs:new( lhs ) .. rhs --It must be the case that rhs was TVPairs so don't bother checking

end

end

end

--Other

function TVPairs:sort() --Sort... appropriately

table.sort( self, function( a, b ) return a[1] < b[1] end )

return self

end

function TVPairs:compile() return compile( self ) end

function TVPairs:sample( length, rate, func ) return sample( length, rate, func ) end

function TVPairs:unsample() --returns a polynomial passing through all points in self

local function ToReducedRowEchelonForm ( M ) --From rosettacode

local lead = 1

local n_rows, n_cols = #M, #M[1]

for r = 1, n_rows do

if n_cols <= lead then break end

local i = r

while M[i][lead] == 0 do

i = i + 1

if n_rows == i then

i = r

lead = lead + 1

if n_cols == lead then break end

end

end

M[i], M[r] = M[r], M[i]

local m = M[r][lead]

for k = 1, n_cols do

M[r][k] = M[r][k] / m

end

for i = 1, n_rows do

if i ~= r then

local m = M[i][lead]

for k = 1, n_cols do

M[i][k] = M[i][k] - m * M[r][k]

end

end

end

lead = lead + 1

end

end

--determine coefficients by matrix reduction

local M = {}

for i = 1, #self do

M[i] = {}

for j = 1, #self do

M[i][j] = self[i][1]^(j-1)

end

M[i][#self + 1] = self[i][2]

end

ToReducedRowEchelonForm( M )

local coeff = {} --store in increasing degree order

for i = 1, #M do

coeff[i] = M[i][#M[i]]

end

return function( t )

local out = 0

for i = 1, #coeff do

out = out + coeff[i]*(t^(i-1))

end

return out

end

end

----------------------------------------------------------------------------------

function compile( ... )

local arg = {...}

local files = {}

for i = 1, #arg do

files[i] = altar_text( tostring( arg[i] ) )

end

return unpack( files )

end

function sample( length, rate, func )

local data = {}

local samples = math.floor( length / rate )

for i = 1, samples + 1 do

local t = rate * (i-1)

data[i] = {t, func(t)}

end

return TVPairs:new( data )

end

----------------------------------------------------------------------------------

local M = {}

M.TVPairs = TVPairs

M.compile = compile

M.sample = sample

return M

Here are some examples:

`local tvp = require( "TVPairs" )`

--Resample data at *10 resolution as a polynomial

local tr1 = tvp.TVPairs:new{ {0, 0}, {1, 1}, {2, 8}, {3, 6}, {4, 5}, {5, 4} }

local tr2 = tvp.sample( 5, .1, tr1:unsample() )

print( tostring( tr2 ) )

--Generate sinusoid TVPairs

function sample_sin( length, low, high, period, offset )

offset = offset or 0

local base = (low+high)/2.0

return tvp.sample( length, .1,

function( t )

return base + (high - base) * math.sin( (2.0*math.pi)*(t/period) + offset )

end

)

end