diff options
Diffstat (limited to 'applause.lua')
-rw-r--r-- | applause.lua | 180 |
1 files changed, 121 insertions, 59 deletions
diff --git a/applause.lua b/applause.lua index ee75701..d77f1cd 100644 --- a/applause.lua +++ b/applause.lua @@ -55,6 +55,18 @@ samplerate = 44100 function sec(x) return math.floor(samplerate*(x or 1)) end function msec(x) return sec((x or 1)/1000) end +-- The clock signal: +-- In order to support (re)using the same stream more than +-- once in a complex stream without recalculating everything, +-- tick closures must be shared among all usages of a stream. +-- A clock signal is necessary to "trigger" the recalculation +-- of a stream's current sample. +-- The clock signal is a boolean oscillating between true and +-- false. The global function is for advancing the clock more +-- or less efficiently from the play() method implemented in C. +local clock_signal = false +function clockCycle() clock_signal = not clock_signal end + function DeriveClass(base, ctor) local class = {} @@ -110,6 +122,19 @@ function Stream:tick() end end +Stream.streams = {} +function Stream:reset() + for i = 1, #self.streams do + self.streams[i]:reset() + end +end + +-- Explicitly clock-sync a stream. +-- FIXME: This should be done automatically by an optimizer stage. +function Stream:sync() + return SyncedStream:new(self) +end + function Stream:map(fnc) return MapStream:new(self, fnc) end @@ -323,9 +348,13 @@ function Stream:save(filename, format) local hnd = sndfile:new(filename, "SFM_WRITE", samplerate, 1, format) + self:reset() + local tick = self:tick() while true do + clockCycle() + local sample = tick() if not sample then break end @@ -340,13 +369,17 @@ function Stream:totable() error("Cannot serialize infinite stream") end + self:reset() + local tick = self:tick() local vector = table.new(self:len(), 0) while true do - local value = tick() + clockCycle() + local value = tick() if not value then break end + table.insert(vector, value) end @@ -472,16 +505,45 @@ function Stream.__le(op1, op2) return op1:len() <= op2:len() end +SyncedStream = DeriveClass(Stream, function(self, stream) + self.streams = {stream} +end) + +function SyncedStream:reset() + self.syncedTick = nil + Stream.reset(self) +end + +function SyncedStream:tick() + if not self.syncedTick then + local last_clock + local last_sample + + local tick = self.streams[1]:tick() + + self.syncedTick = function() + if clock_signal ~= last_clock then + last_clock = clock_signal + last_sample = tick() + end + return last_sample + end + end + + return self.syncedTick +end + VectorStream = DeriveClass(Stream, function(self, vector) self.vector = vector end) function VectorStream:tick() + local vector = self.vector local i = 0 return function() i = i + 1 - return self.vector[i] + return vector[i] end end @@ -489,39 +551,39 @@ function VectorStream:len() return #self.vector end +-- NOTE: A SndfileStream itself cannot currently be reused within +-- one high-level stream (i.e. UGen graph). +-- SndfileStream:sync() must be called to wrap it in a +-- synced stream manually. +-- FIXME: This will no longer be necessary when syncing +-- streams automatically in an optimization phase. SndfileStream = DeriveClass(Stream, function(self, filename) - self.filename = filename -end) - -function SndfileStream:tick() - -- NOTE: Opening the file here has the advantage - -- that the stream can be reused multiple times -- FIXME: This fails if the file is not at the -- correct sample rate. Need to resample... - local handle = sndfile:new(self.filename, "SFM_READ") + self.handle = sndfile:new(filename, "SFM_READ") +end) - return function() - if not handle then return end +function SndfileStream:reset() + self.handle:seek(0) + Stream.reset(self) +end - local sample = handle:read() - if not sample then - handle:close() - handle = nil - end +function SndfileStream:tick() + local handle = self.handle - return sample + return function() + return handle:read() end end function SndfileStream:len() - -- Since the file is not yet opened, - -- we must assume that its size never changes between - -- calling len() and depending on the length reported - local handle = sndfile:new(self.filename, "SFM_READ") - local len = tonumber(handle.info.frames) + return tonumber(self.handle.info.frames) +end - handle:close() - return len +-- Sometimes it may be useful to explicitly close the file +-- handle behind a SndfileStream +function SndfileStream:close() + self.handle:close() end ConcatStream = DeriveClass(Stream, function(self, ...) @@ -586,11 +648,11 @@ end -- (e.g. streams of streams), and is semantically similar -- to folding the stream with the Concat operation. RavelStream = DeriveClass(Stream, function(self, stream) - self.stream = tostream(stream) + self.streams = {tostream(stream)} end) function RavelStream:tick() - local stream_tick = self.stream:tick() + local stream_tick = self.streams[1]:tick() local current_tick = nil return function() @@ -613,7 +675,7 @@ function RavelStream:tick() end function RavelStream:len() - if self.stream:len() == math.huge then + if self.streams[1]:len() == math.huge then -- FIXME: Actually, it is possible that the stream -- is infinite but consists only of empty streams. -- In this case, tick() will be stuck in an infinite loop... @@ -621,7 +683,7 @@ function RavelStream:len() end local len = 0 - local t = self.stream() + local t = self.streams[1]() for i = 1, #t do len = len + (type(t[i]) == "table" and t[i].is_a_stream and @@ -663,11 +725,11 @@ end -- i and j have the same semantics as in string.sub() SubStream = DeriveClass(Stream, function(self, stream, i, j) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.i = i self.j = j or -1 - local stream_len = self.stream:len() + local stream_len = self.streams[1]:len() if self.i < 0 then self.i = self.i + stream_len + 1 end if self.j < 0 then self.j = self.j + stream_len + 1 end @@ -679,7 +741,7 @@ SubStream = DeriveClass(Stream, function(self, stream, i, j) end) function SubStream:tick() - local tick = self.stream:tick() + local tick = self.streams[1]:tick() -- OPTIMIZE: Perhaps ask stream to skip the first -- self.i-1 samples @@ -703,15 +765,15 @@ end -- This should be split into a generic (index) and -- sample-only (interpolate) operation IndexStream = DeriveClass(Stream, function(self, stream, index_stream) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.index_stream = tostream(index_stream) end) function IndexStream:tick() - local stream_tick = self.stream:tick() + local stream_tick = self.streams[1]:tick() local index_tick = self.index_stream:tick() - local stream_len = self.stream:len() + local stream_len = self.streams[1]:len() -- avoid math table lookup at sample rate local huge = math.huge @@ -751,12 +813,12 @@ function IndexStream:len() end MapStream = DeriveClass(Stream, function(self, stream, fnc) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.fnc = fnc end) function MapStream:tick() - local tick = self.stream:tick() + local tick = self.streams[1]:tick() return function() local sample = tick() @@ -765,16 +827,16 @@ function MapStream:tick() end function MapStream:len() - return self.stream:len() + return self.streams[1]:len() end ScanStream = DeriveClass(Stream, function(self, stream, fnc) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.fnc = fnc end) function ScanStream:tick() - local tick = self.stream:tick() + local tick = self.streams[1]:tick() local last_sample = nil return function() @@ -787,16 +849,16 @@ function ScanStream:tick() end function ScanStream:len() - return self.stream:len() + return self.streams[1]:len() end FoldStream = DeriveClass(Stream, function(self, stream, fnc) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.fnc = fnc end) function FoldStream:tick() - local tick = self.stream:tick() + local tick = self.streams[1]:tick() return function() local l, r @@ -813,7 +875,7 @@ function FoldStream:tick() end function FoldStream:len() - return self.stream:len() > 0 and 1 or 0 + return self.streams[1]:len() > 0 and 1 or 0 end -- ZipStream combines any number of streams into a single @@ -1045,7 +1107,7 @@ local function Blackman(n, window) end FIRStream = DeriveClass(Stream, function(self, stream, freq_stream) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.freq_stream = tostream(freq_stream) end) @@ -1056,14 +1118,14 @@ function FIRStream:tick() -- this is the max. latency introduced by the filter -- since the window must be filled before we can generate -- (filtered) samples - local window_size = math.min(1024, self.stream:len()) + local window_size = math.min(1024, self.streams[1]:len()) local window_p = window_size-1 local accu = 0 local blackman = {} for i = 1, window_size do blackman[i] = Blackman(i-1, window_size) end - local tick = self.stream:tick() + local tick = self.streams[1]:tick() local freq_tick = self.freq_stream:tick() return function() @@ -1092,7 +1154,7 @@ function FIRStream:tick() end function FIRStream:len() - return self.stream:len() + return self.streams[1]:len() end ]==] @@ -1111,7 +1173,7 @@ local function ddn(f) end LPFStream = DeriveClass(Stream, function(self, stream, freq) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.freq_stream = tostream(freq) end) @@ -1126,7 +1188,7 @@ function LPFStream:tick() -- some cached math table lookups local tan = math.tan - local tick = self.stream:tick() + local tick = self.streams[1]:tick() local freq_tick = self.freq_stream:tick() local cur_freq = nil @@ -1164,11 +1226,11 @@ function LPFStream:tick() end function LPFStream:len() - return self.stream:len() + return self.streams[1]:len() end HPFStream = DeriveClass(Stream, function(self, stream, freq) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.freq_stream = tostream(freq) end) @@ -1183,7 +1245,7 @@ function HPFStream:tick() -- some cached math table lookups local tan = math.tan - local tick = self.stream:tick() + local tick = self.streams[1]:tick() local freq_tick = self.freq_stream:tick() local cur_freq = nil @@ -1226,13 +1288,13 @@ function HPFStream:tick() end function HPFStream:len() - return self.stream:len() + return self.streams[1]:len() end -- NOTE: The quality factor, indirectly proportional -- to the passband width BPFStream = DeriveClass(Stream, function(self, stream, freq, quality) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.freq_stream = tostream(freq) -- FIXME: Does this make sense to be a stream? self.quality = quality @@ -1250,7 +1312,7 @@ function BPFStream:tick() local tan = math.tan local cos = math.cos - local tick = self.stream:tick() + local tick = self.streams[1]:tick() local freq_tick = self.freq_stream:tick() local cur_freq = nil @@ -1290,13 +1352,13 @@ function BPFStream:tick() end function BPFStream:len() - return self.stream:len() + return self.streams[1]:len() end -- NOTE: The quality factor, indirectly proportional -- to the passband width BRFStream = DeriveClass(Stream, function(self, stream, freq, quality) - self.stream = tostream(stream) + self.streams = {tostream(stream)} self.freq_stream = tostream(freq) -- FIXME: Does this make sense to be a stream? self.quality = quality @@ -1314,7 +1376,7 @@ function BRFStream:tick() local tan = math.tan local cos = math.cos - local tick = self.stream:tick() + local tick = self.streams[1]:tick() local freq_tick = self.freq_stream:tick() local cur_freq = nil @@ -1355,7 +1417,7 @@ function BRFStream:tick() end function BRFStream:len() - return self.stream:len() + return self.streams[1]:len() end -- |