diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-04-07 20:20:01 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2015-04-07 20:20:01 +0200 |
commit | 47f8968a10d2f3e418e7a66f3e53495233ce27c3 (patch) | |
tree | 9b8cb20b103e328cd5514ede6ea5b4c1789ef570 /applause.lua | |
parent | 9520bcaf4e923a0b9504b1695c4c860cf0958543 (diff) | |
download | applause2-47f8968a10d2f3e418e7a66f3e53495233ce27c3.tar.gz |
replaced co-routine based implementation with closures
* this improves performance by 10 times (both lua5.1 and luajit)
* also, when using luajit, there appears to be little peformance
loss when using stream compositions instead of maps (which are
sometimes even slower!?)
Diffstat (limited to 'applause.lua')
-rw-r--r-- | applause.lua | 225 |
1 files changed, 153 insertions, 72 deletions
diff --git a/applause.lua b/applause.lua index 10da958..4aa81a7 100644 --- a/applause.lua +++ b/applause.lua @@ -61,7 +61,9 @@ Stream.is_a_stream = true -- A stream, produces an infinite number of the same value by default -- (eternal quietness by default) function Stream:tick() - while true do coroutine.yield(self.value) end + return function() + return self.value + end end function Stream:map(fnc) @@ -79,6 +81,14 @@ function Stream:sqrt() return self:map(math.sqrt) end function Stream:tan() return self:map(math.tan) end function Stream:tanh() return self:map(math.tanh) end +function Stream:scan(fnc) + return ScanStream:new(self, fnc) +end + +function Stream:fold(fnc) + return FoldStream:new(self, fnc) +end + function Stream:sub(i, j) return SubStream:new(self, i, j) end @@ -98,11 +108,12 @@ function Stream:__len() return self:len() end function Stream:__call() if self:len() == math.huge then error("Cannot serialize infinite stream") end - local co = coroutine.wrap(self.tick) + local tick = self:tick() local vector = {} while true do - local sample = co(self) + local sample = tick() + if not sample then break end table.insert(vector, sample) end @@ -168,8 +179,11 @@ VectorStream = DeriveClass(Stream, function(self, vector) end) function VectorStream:tick() - for _, v in ipairs(self.vector) do - coroutine.yield(tonumber(v)) + local i = 0 + + return function() + i = i + 1 + return self.vector[i] end end @@ -194,8 +208,22 @@ ConcatStream = DeriveClass(Stream, function(self, ...) end) function ConcatStream:tick() - for _, stream in ipairs(self.streams) do - stream:tick() + local i = 1 + local ticks = {} + + for k = 1, #self.streams do + ticks[k] = self.streams[k]:tick() + end + + return function() + while i <= #self.streams do + local sample = ticks[i]() + + if sample then return sample end + + -- try next stream + i = i + 1 + end end end @@ -226,7 +254,13 @@ IotaStream = DeriveClass(Stream, function(self, v1, v2) end) function IotaStream:tick() - for i = self.from, self.to do coroutine.yield(i) end + local i = self.from-1 + + return function() + if i >= self.to then return end + i = i + 1 + return i + end end function IotaStream:len() @@ -252,13 +286,19 @@ SubStream = DeriveClass(Stream, function(self, stream, i, j) end) function SubStream:tick() - local co = coroutine.wrap(self.stream.tick) + local tick = self.stream:tick() - -- skip until self.i - for _ = 1, self.i - 1 do co(self.stream) end + -- OPTIMIZE: Perhaps ask stream to skip the first + -- self.i-1 samples + for _ = 1, self.i-1 do tick() end - -- produce samples i to j - for _ = self.i, self.j do coroutine.yield(co(self.stream)) end + local i = self.i + + return function() + if i > self.j then return end + i = i + 1 + return tick() + end end function SubStream:len() @@ -272,15 +312,17 @@ IndexStream = DeriveClass(Stream, function(self, stream, index_stream) end) function IndexStream:tick() + local stream_tick = self.stream:tick() + local index_tick = self.index_stream:tick() + local stream_len = self.stream:len() - local co = coroutine.wrap(self.stream.tick) - local index_co = coroutine.wrap(self.index_stream.tick) -- cache of samples generated by stream local cache = {} - while true do - local index_sample = index_co(self.index_stream) + return function() + local index_sample = index_tick() + if not index_sample then return end if index_sample < 1 or index_sample > stream_len or @@ -292,14 +334,14 @@ function IndexStream:tick() math.ceil(index_sample) while #cache < index_ceil do - table.insert(cache, co(self.stream)) + table.insert(cache, stream_tick()) end -- applies linear interpolation if index_sample is -- not an integer - coroutine.yield(cache[index_floor] + - (cache[index_ceil] - cache[index_floor])* - (index_sample - index_floor)) + return cache[index_floor] + + (cache[index_ceil] - cache[index_floor])* + (index_sample - index_floor) end end @@ -319,28 +361,29 @@ AddStream = DeriveClass(Stream, function(self, ...) end) function AddStream:tick() - local coroutines = {} + local running = true + local ticks = {} - for k, stream in pairs(self.streams) do - coroutines[k] = coroutine.create(stream.tick) + for i = 1, #self.streams do + ticks[i] = self.streams[i]:tick() end - while true do + return function() + if not running then return end + local sum = 0 - local running = false - - for k = 1, #self.streams do - if coroutine.status(coroutines[k]) ~= "dead" then - local state, sample = coroutine.resume(coroutines[k], self.streams[k]) - if not state then error(sample) end - - running = running or sample - sum = sum + (sample or 0) + + running = nil + for i = 1, #ticks do + local sample = ticks[i]() + + if sample then + running = true + sum = sum + sample end end - if not running then return end - coroutine.yield(sum) + return running and sum end end @@ -362,28 +405,29 @@ MulStream = DeriveClass(Stream, function(self, ...) end) function MulStream:tick() - local coroutines = {} + local running = true + local ticks = {} - for k, stream in pairs(self.streams) do - coroutines[k] = coroutine.create(stream.tick) + for i = 1, #self.streams do + ticks[i] = self.streams[i]:tick() end - while true do + return function() + if not running then return end + local product = 1 - local running = false - - for k = 1, #self.streams do - if coroutine.status(coroutines[k]) ~= "dead" then - local state, sample = coroutine.resume(coroutines[k], self.streams[k]) - if not state then error(sample) end - - running = running or sample - product = product * (sample or 1) + + running = nil + for i = 1, #ticks do + local sample = ticks[i]() + + if sample then + running = true + product = product * sample end end - if not running then return end - coroutine.yield(product) + return running and product end end @@ -403,12 +447,11 @@ MapStream = DeriveClass(Stream, function(self, stream, fnc) end) function MapStream:tick() - local co = coroutine.wrap(self.stream.tick) + local tick = self.stream:tick() - while true do - local sample = co(self.stream) - if not sample then return end - coroutine.yield(self.fnc(sample)) + return function() + local sample = tick() + return sample and self.fnc(sample) end end @@ -416,6 +459,54 @@ function MapStream:len() return self.stream:len() end +ScanStream = DeriveClass(Stream, function(self, stream, fnc) + self.stream = tostream(stream) + self.fnc = fnc +end) + +function ScanStream:tick() + local tick = self.stream:tick() + local last_sample = nil + + return function() + local sample = tick() + if not sample then return end + + last_sample = self.fnc(last_sample, sample) + return last_sample + end +end + +function ScanStream:len() + return self.stream:len() +end + +FoldStream = DeriveClass(Stream, function(self, stream, fnc) + self.stream = tostream(stream) + self.fnc = fnc +end) + +function FoldStream:tick() + local tick = self.stream:tick() + + return function() + local l, r + + while true do + r = tick() + if not r then break end + + l = l and self.fnc(l, r) or r + end + + return l + end +end + +function FoldStream:len() + return self.stream:len() > 0 and 1 or 0 +end + -- primitives function tostream(v) @@ -436,27 +527,17 @@ samplerate = 44100 -- Time units: Convert between time and sample numbers -- These are functions, so we can round the result -function seconds(x) return math.floor(samplerate*x) end -function mseconds(x) return seconds(x/1000) end +-- automatically +function secs(x) return math.floor(samplerate*x) end +function msecs(x) return secs(x/1000) end -- Wave forms function SawOsc(freq) - local accu = 0 - - return MapStream:new(freq, function(x) - accu = (accu + x/samplerate) % 1 - return accu + return ScanStream:new(freq, function(accu, f) + return ((accu or 0) + f/samplerate) % 1 end) end function SinOsc(freq) - -- The following mapping is equivalent to but more efficient as: - -- return (SawOsc(freq)*(2*math.pi)):sin() - - local accu = 0 - - return MapStream:new(freq, function(x) - accu = (accu + x/samplerate) % 1 - return math.sin(accu*2*math.pi) - end) + return (SawOsc(freq)*(2*math.pi)):sin() end |