aboutsummaryrefslogtreecommitdiffhomepage
path: root/applause.lua
diff options
context:
space:
mode:
Diffstat (limited to 'applause.lua')
-rw-r--r--applause.lua225
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