diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-01-04 16:10:37 +0100 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-01-04 16:10:37 +0100 |
commit | 85311841ceb326b3b2bd14ff5364049df0d2f14f (patch) | |
tree | 88b66a8f3b82061b0a581f0fb220ef5da93974a9 | |
parent | ae2f864873a8af097b1b4ec5ff020ec2d8d33d85 (diff) | |
download | applause2-85311841ceb326b3b2bd14ff5364049df0d2f14f.tar.gz |
major class system revision: Support :instanceof(), dedicated ctor() methods and construction using the Class() idiom
* Class:instanceof() deprecates several fields like is_azipstream;
is_a_stream is still useful
* Constructors are now defined as ordinary methods (looks better)
* every object and class table now has a `base` field that points to the
base class since getmetatable() will no longer point to the base table/class.
* Objects can be constructed both using Class:new() and Class().
The old behaviour of stream() for serialization is used less frequently.
* Streams can be serialized using the Stream:eval() method.
I don't think that the object system should be tweaked even more to support
different metamethods for class tables and objects as it is quite convoluted already.
-rw-r--r-- | applause.lua | 230 |
1 files changed, 156 insertions, 74 deletions
diff --git a/applause.lua b/applause.lua index 1455dc5..54930cd 100644 --- a/applause.lua +++ b/applause.lua @@ -67,15 +67,13 @@ function msec(x) return sec((x or 1)/1000) end local clock_signal = false function clockCycle() clock_signal = not clock_signal end -function DeriveClass(base, ctor) - local class = {} +function DeriveClass(base) + local class = {base = base} if base then - class = base:new() - -- we cannot derive metamethod tables, so we -- copy all relevant metamethods - for _, m in pairs{"len", "call", "tostring", + for _, m in pairs{"len", "tostring", "add", "sub", "mul", "div", "mod", "pow", "unm", "concat", "lt", "le", "gc"} do @@ -83,35 +81,70 @@ function DeriveClass(base, ctor) end end - -- objects constructed from new class get the - -- class table as their metatable, so __index is set up - -- to look into the class table - function class:__index(key) - if type(key) == "string" then return getmetatable(self)[key] end - - -- non-string keys create IndexTables - return IndexStream:new(self, key) - end + -- Metamethods should work even on root class tables + -- However, this way we cannot use metatables to track the + -- class inheritance. This is done using the `base` field. + setmetatable(class, class) function class:new(...) + -- Try to call the parent constructor local obj = base and base:new() or {} + obj.base = self setmetatable(obj, self) -- Allow constructors to return something else -- than an instance of the class. - return ctor and ctor(obj, ...) or obj + return obj.ctor and obj:ctor(...) or obj + end + + -- The call metamethod is synonymous to :new() + class.__call = class.new + + -- All objects have the class table as their metatable, + -- so it must look into the class and possibly invoke metamethods + -- on the base class. + -- A simple `class.__index = base` does not work since + -- we want indexing to create IndexStreams. + -- NOTE: __index methods get the original table looked up + -- as their self argument (e.g. the stream object). + function class:__index(key) + if type(key) == "string" then + return rawget(class, key) or (base and base[key]) + end + + -- non-string keys create IndexStreams + return IndexStream:new(self, key) + end + + -- Checks whether object is instance of other_class. + -- Will work with class templates as well. + -- This is not using getmetatable() since class metatables + -- point to themselves (see above). + function class:instanceof(other_class) + repeat + -- Better use rawequal() in case we support the + -- __eq metamethod someday. + if rawequal(self, other_class) then return true end + self = self.base + until not self + + return false end return class end -- Stream base class -Stream = DeriveClass(nil, function(self, value) +Stream = DeriveClass() + +function Stream:ctor(value) self.value = tonumber(value) or 0 -end) +end --- used by tostream(): +-- There is Stream:instanceof(), but testing Stream.is_a_stream +-- is sometimes faster (for generator functions) and can be done +-- without knowing that the table at hand is an object Stream.is_a_stream = true -- A stream, produces an infinite number of the same value by default @@ -328,8 +361,10 @@ end -- The len() method is the main way to get a stream's -- length (at least in this code) and classes should overwrite --- this method since LuaJIT has problems --- with invoking the __len metamethod (# operator). +-- this method. +-- The __len metamethod is also defined but it currently cannot +-- work since Lua 5.1 does not consider a table's metamethod when +-- evaluating the length (#) operator. function Stream:len() return math.huge -- infinity end @@ -390,6 +425,12 @@ function Stream:totable() return vector end +-- Effectively eager-evaluates the stream returning +-- an array-backed stream. +function Stream:eval() + return VectorStream:new(self:totable()) +end + function Stream:toplot(rows, cols) rows = rows or 25 cols = cols or 80 @@ -432,12 +473,10 @@ end -- Stream metamethods +-- NOTE: Currently non-functional since Lua 5.1 does not +-- consider metamethods when evaluating the length operator. function Stream:__len() return self:len() end -function Stream:__call() - return VectorStream:new(self:totable()) -end - function Stream:__tostring() local t @@ -507,9 +546,11 @@ function Stream.__le(op1, op2) return op1:len() <= op2:len() end -SyncedStream = DeriveClass(Stream, function(self, stream) +SyncedStream = DeriveClass(Stream) + +function SyncedStream:ctor(stream) self.streams = {stream} -end) +end function SyncedStream:reset() self.syncedTick = nil @@ -535,9 +576,11 @@ function SyncedStream:tick() return self.syncedTick end -VectorStream = DeriveClass(Stream, function(self, vector) +VectorStream = DeriveClass(Stream) + +function VectorStream:ctor(vector) self.vector = vector -end) +end function VectorStream:tick() local vector = self.vector @@ -559,11 +602,13 @@ end -- synced stream manually. -- FIXME: This will no longer be necessary when syncing -- streams automatically in an optimization phase. -SndfileStream = DeriveClass(Stream, function(self, filename) +SndfileStream = DeriveClass(Stream) + +function SndfileStream:ctor(filename) -- FIXME: This fails if the file is not at the -- correct sample rate. Need to resample... self.handle = sndfile:new(filename, "SFM_READ") -end) +end function SndfileStream:reset() self.handle:seek(0) @@ -588,19 +633,20 @@ function SndfileStream:close() self.handle:close() end -ConcatStream = DeriveClass(Stream, function(self, ...) - self.is_concatstream = true +ConcatStream = DeriveClass(Stream) +function ConcatStream:ctor(...) self.streams = {} for _, v in ipairs{...} do - if v.is_concatstream then + v = tostream(v) + if v:instanceof(ConcatStream) then -- Optimization: Avoid redundant -- ConcatStream objects for _, s in ipairs(v.streams) do table.insert(self.streams, s) end else - table.insert(self.streams, tostream(v)) + table.insert(self.streams, v) end end @@ -612,7 +658,7 @@ ConcatStream = DeriveClass(Stream, function(self, ...) error("Stream "..i.." is infinite") end end -end) +end function ConcatStream:tick() local i = 1 @@ -645,10 +691,12 @@ function ConcatStream:len() return len end -RepeatStream = DeriveClass(Stream, function(self, stream, repeats) +RepeatStream = DeriveClass(Stream) + +function RepeatStream:ctor(stream, repeats) self.streams = {tostream(stream)} self.repeats = repeats or math.huge -end) +end function RepeatStream:tick() local i = 1 @@ -678,9 +726,11 @@ end -- This removes one level of nesting from nested streams -- (e.g. streams of streams), and is semantically similar -- to folding the stream with the Concat operation. -RavelStream = DeriveClass(Stream, function(self, stream) +RavelStream = DeriveClass(Stream) + +function RavelStream:ctor(stream) self.streams = {tostream(stream)} -end) +end function RavelStream:tick() local stream_tick = self.streams[1]:tick() @@ -696,6 +746,8 @@ function RavelStream:tick() local value = stream_tick() + -- NOTE: We don't use instanceof() here for performance + -- reasons if type(value) == "table" and value.is_a_stream then current_tick = value:tick() else @@ -714,7 +766,7 @@ function RavelStream:len() end local len = 0 - local t = self.streams[1]() + local t = self.streams[1]:totable() for i = 1, #t do len = len + (type(t[i]) == "table" and t[i].is_a_stream and @@ -724,7 +776,9 @@ function RavelStream:len() return len end -IotaStream = DeriveClass(Stream, function(self, v1, v2) +IotaStream = DeriveClass(Stream) + +function IotaStream:ctor(v1, v2) if not v2 then self.from = 1 self.to = v1 or math.huge @@ -737,7 +791,7 @@ IotaStream = DeriveClass(Stream, function(self, v1, v2) self.from > self.to then error("Invalid iota range ["..self.from..","..self.to.."]") end -end) +end function IotaStream:tick() local i = self.from-1 @@ -755,7 +809,9 @@ function IotaStream:len() end -- i and j have the same semantics as in string.sub() -SubStream = DeriveClass(Stream, function(self, stream, i, j) +SubStream = DeriveClass(Stream) + +function SubStream:ctor(stream, i, j) self.streams = {tostream(stream)} self.i = i self.j = j or -1 @@ -769,7 +825,7 @@ SubStream = DeriveClass(Stream, function(self, stream, i, j) self.i > self.j then error("Invalid sub-stream range ["..self.i..","..self.j.."]") end -end) +end function SubStream:tick() local tick = self.streams[1]:tick() @@ -795,10 +851,12 @@ end -- FIXME: Will not work for non-samlpe streams -- This should be split into a generic (index) and -- sample-only (interpolate) operation -IndexStream = DeriveClass(Stream, function(self, stream, index_stream) +IndexStream = DeriveClass(Stream) + +function IndexStream:ctor(stream, index_stream) self.streams = {tostream(stream)} self.index_stream = tostream(index_stream) -end) +end function IndexStream:tick() local stream_tick = self.streams[1]:tick() @@ -843,10 +901,12 @@ function IndexStream:len() return self.index_stream:len() end -MapStream = DeriveClass(Stream, function(self, stream, fnc) +MapStream = DeriveClass(Stream) + +function MapStream:ctor(stream, fnc) self.streams = {tostream(stream)} self.fnc = fnc -end) +end function MapStream:tick() local tick = self.streams[1]:tick() @@ -861,10 +921,12 @@ function MapStream:len() return self.streams[1]:len() end -ScanStream = DeriveClass(Stream, function(self, stream, fnc) +ScanStream = DeriveClass(Stream) + +function ScanStream:ctor(stream, fnc) self.streams = {tostream(stream)} self.fnc = fnc -end) +end function ScanStream:tick() local tick = self.streams[1]:tick() @@ -883,10 +945,12 @@ function ScanStream:len() return self.streams[1]:len() end -FoldStream = DeriveClass(Stream, function(self, stream, fnc) +FoldStream = DeriveClass(Stream) + +function FoldStream:ctor(stream, fnc) self.streams = {tostream(stream)} self.fnc = fnc -end) +end function FoldStream:tick() local tick = self.streams[1]:tick() @@ -912,15 +976,15 @@ end -- ZipStream combines any number of streams into a single -- stream using a function. This is the basis of the "+" -- and "*" operations. -ZipStream = DeriveClass(Stream, function(self, fnc, ...) - self.is_zipstream = true +ZipStream = DeriveClass(Stream) +function ZipStream:ctor(fnc, ...) self.fnc = fnc self.streams = {} for _, v in ipairs{...} do v = tostream(v) - if v.is_zipstream and v.fnc == fnc then + if v:instanceof(ZipStream) and v.fnc == fnc then -- Optimization: Avoid redundant -- ZipStream objects for _, s in ipairs(v.streams) do @@ -930,7 +994,7 @@ ZipStream = DeriveClass(Stream, function(self, fnc, ...) table.insert(self.streams, v) end end -end) +end function ZipStream:tick() local running = true @@ -1006,10 +1070,12 @@ end -- -- Velocity of NOTE ON for a specific note on a channel -MIDIVelocityStream = DeriveClass(Stream, function(self, note, channel) +MIDIVelocityStream = DeriveClass(Stream) + +function MIDIVelocityStream:ctor(note, channel) self.note = note self.channel = channel or 1 -end) +end -- implemented in applause.c, private! function MIDIVelocityStream.getValue(note, channel) @@ -1031,9 +1097,11 @@ end -- (of the NOTE ON message). -- The MIDI note is the lower byte and the velocity the -- upper byte of the word. -MIDINoteStream = DeriveClass(Stream, function(self, channel) +MIDINoteStream = DeriveClass(Stream) + +function MIDINoteStream:ctor(channel) self.channel = channel or 1 -end) +end -- implemented in applause.c, private! function MIDINoteStream.getValue(channel) @@ -1049,10 +1117,12 @@ function MIDINoteStream:tick() end end -MIDICCStream = DeriveClass(Stream, function(self, control, channel) +MIDICCStream = DeriveClass(Stream) + +function MIDICCStream:ctor(control, channel) self.control = control self.channel = channel or 1 -end) +end -- implemented in applause.c, private! function MIDICCStream.getValue(control, channel) @@ -1137,10 +1207,12 @@ local function Blackman(n, window) alpha*0.5*math.cos((4*math.pi*n)/(window-1)) end -FIRStream = DeriveClass(Stream, function(self, stream, freq_stream) +FIRStream = DeriveClass(Stream) + +function FIRStream:ctor(stream, freq_stream) self.streams = {tostream(stream)} self.freq_stream = tostream(freq_stream) -end) +end function FIRStream:tick() local window = {} @@ -1203,10 +1275,12 @@ local function ddn(f) (f < -1e-15 and f > -1e15 and f or 0) end -LPFStream = DeriveClass(Stream, function(self, stream, freq) +LPFStream = DeriveClass(Stream) + +function LPFStream:ctor(stream, freq) self.streams = {tostream(stream)} self.freq_stream = tostream(freq) -end) +end function LPFStream:tick() local a0, b1, b2 @@ -1260,10 +1334,12 @@ function LPFStream:len() return self.streams[1]:len() end -HPFStream = DeriveClass(Stream, function(self, stream, freq) +HPFStream = DeriveClass(Stream) + +function HPFStream:ctor(stream, freq) self.streams = {tostream(stream)} self.freq_stream = tostream(freq) -end) +end function HPFStream:tick() local a0, b1, b2 @@ -1324,12 +1400,14 @@ end -- NOTE: The quality factor, indirectly proportional -- to the passband width -BPFStream = DeriveClass(Stream, function(self, stream, freq, quality) +BPFStream = DeriveClass(Stream) + +function BPFStream:ctor(stream, freq, quality) self.streams = {tostream(stream)} self.freq_stream = tostream(freq) -- FIXME: Does this make sense to be a stream? self.quality = quality -end) +end function BPFStream:tick() local a0, b1, b2 @@ -1388,12 +1466,14 @@ end -- NOTE: The quality factor, indirectly proportional -- to the passband width -BRFStream = DeriveClass(Stream, function(self, stream, freq, quality) +BRFStream = DeriveClass(Stream) + +function BRFStream:ctor(stream, freq, quality) self.streams = {tostream(stream)} self.freq_stream = tostream(freq) -- FIXME: Does this make sense to be a stream? self.quality = quality -end) +end function BRFStream:tick() local a0, b1, b2 @@ -1460,9 +1540,11 @@ ffi.cdef[[ int kill(int pid, int sig); ]] -Client = DeriveClass(null, function(self, pid) +Client = DeriveClass() + +function Client:ctor(pid) self.pid = pid -end) +end function Client:play() ffi.C.kill(self.pid, 10); -- SIGUSR1 |