diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2024-01-29 01:23:16 +0300 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2024-01-29 01:23:16 +0300 |
commit | 23e613456e9d1f1a109cedd9c6fa3b875ea5540c (patch) | |
tree | 87385a56a9a7b553537360e5a317a74bc8af554e | |
parent | d444a5d7405016a5094e26050cfb453bcd6f882a (diff) | |
download | applause2-23e613456e9d1f1a109cedd9c6fa3b875ea5540c.tar.gz |
revised Stream:resample() - it takes a stream factor now, so you can control playpack speed
* The old implementation was simple but severely limited:
* the resample factor (playback speed) could only be scalar
* for infinite source streams, the algorithm would require infinitely growing memory.
The algorithm therefore also wasn't realtime-safe and quite slow (probably because of reallocations).
* This could be made to work with stream-factors if the source stream is infinite,
but we couldn't get rid of the memory requirements due to being based on IndexStream.
* Instead, there is a custom ResampleStream now that backs Stream:resample().
It also performs linear interpolation but has constant memory requirements and works with factor-streams.
Even for finite source streams and constant factors this implementation will be faster and more
real-time safe.
* You cannot play backwards, due to missing buffering.
This can still be done manually using IndexStream if necessary.
-rw-r--r-- | applause.lua | 83 |
1 files changed, 64 insertions, 19 deletions
diff --git a/applause.lua b/applause.lua index ddc5cb7..a35dfe3 100644 --- a/applause.lua +++ b/applause.lua @@ -528,25 +528,6 @@ function Stream:pan(location) end end ---- Resample stream. --- This is a linear resampler thanks to the semantics of IndexStream. --- @number factor --- The resampling factor. --- If lower than 1, the stream will be slowed. --- If higher than 1, it will be sped up. --- @treturn Stream --- @todo It would be useful if factor would be StreamableNumber. --- However the time in line() would consequently also have to be StreamableNumber. -function Stream:resample(factor) - -- FIXME FIXME FIXME - -- self:len()-1 is a workaround for a mysterious bug in LuaJIT - -- (still not fixed in v2.1 branch) where a comparison in - -- IndexStream would mysteriously fail. - -- A better workaround would probably be to disable the optimization - -- responsible... - return self[line(1, math.floor(self:len() / factor), self:len()-1)] -end - -- -- Wave forms with names derived from ChucK: -- Can be written freq:SawOsc() or Stream.SawOsc(freq) @@ -1865,6 +1846,70 @@ function IndexStream:len() return self.index_stream:len() end +ResampleStream = DeriveClass(MuxableStream) +ResampleStream.sig_last_stream = 1 + +function ResampleStream:muxableCtor(stream, factor) + self.stream = stream + self.factor_stream = tostream(factor) + + local len = self.stream:len() + if len == math.huge then + self.length = self.factor_stream:len() + else + -- If factor would be a stream, we couldn't predict + -- the length of the resulting stream. + assert(type(factor) == "number", + "Resampling factor must be a number") + self.length = math.floor(len / factor) + end +end + +function ResampleStream:gtick() + local stream_tick = self.stream:gtick() + local factor_tick = self.factor_stream:gtick() + + local cache = {0, stream_tick()} + local index = 0 + + return function() + local factor = factor_tick() + if not cache[2] or not factor then return end + + index = index + factor + -- NOTE: We must allow some variance for numeric reasons. + -- Otherwise we are sometimes missing a single output sample. + while index > 1+1e-10 do + index = index - 1 + cache[1], cache[2] = cache[2], stream_tick() + if not cache[2] then return end + end + + -- linear interpolation + return cache[1] + (cache[2] - cache[1])*index + end +end + +function ResampleStream:len() + return self.length +end + +--- Resample stream. +-- This uses linear interpolation. +-- @StreamableNumber factor +-- The resampling factor. +-- If lower than 1, the stream will be slowed. +-- If higher than 1, it will be sped up. +-- This cannot be smaller than 0. +-- The factor can only be a mono-stream if the source stream is infinite. +-- @treturn Stream +-- If the source stream is infinite, the length is limited by the factor-stream. +-- Otherwise, it is `self:len()/factor`. +function Stream:resample(factor) +-- return self[iota(self:len()/factor) * factor] + return ResampleStream:new(self, factor) +end + MapStream = DeriveClass(MuxableStream) -- We have trailing non-stream arguments |