Community Projects

Started by loganmcbroom, December 18, 2016, 04:16:16 AM

Previous topic - Next topic

loganmcbroom

It seems like there are a good deal of people here willing to put a good deal of effort into software derived from the CDP. Maybe we could get some kind of community project going? What kinds of tools would be good to have? What things seem to be missing?

I think some in depth tools for generating all the various text files needed to automate parameters would be a good thing to have. It's one aspect of soundloom that seems the most dated to me.

loganmcbroom

#1
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

loganmcbroom

Here's a utility for converting midi files into notelists. The output isn't a complete notedata file, just a single notelist block per track in the input file.

https://drive.google.com/drive/folders/0BxzJlrC6KHmxald1R25JUmotU1E?usp=sharing