From 8ac5f63d63b298bb729dedde02e8c50d0efb17f4 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Tue, 5 Sep 2023 03:33:52 +0300 Subject: libsndfile related classes moved into sndfile-stream.lua --- sndfile-stream.lua | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 sndfile-stream.lua (limited to 'sndfile-stream.lua') diff --git a/sndfile-stream.lua b/sndfile-stream.lua new file mode 100644 index 0000000..68ed5cc --- /dev/null +++ b/sndfile-stream.lua @@ -0,0 +1,100 @@ +local sndfile = require "sndfile" + +SndfileStream = DeriveClass(Stream) + +function SndfileStream:ctor(filename, samplerate, channels, format) + -- FIXME: This fails if the file is not at the + -- correct sample rate. Need to resample... + -- NOTE: samplerate and channels are ignored unless SF_FORMAT_RAW + -- files are read. + local handle = sndfile:new(filename, "SFM_READ", + samplerate, channels, format) + self.filename = filename + self.samplerate = handle.info.samplerate + self.channel_no = handle.info.channels + self.format = handle.info.format + self.frames = tonumber(handle.info.frames) + handle:close() + + if self.channel_no > 1 then + local cached = self:cache() + local streams = {} + for i = 0, self.channel_no-1 do + streams[i+1] = cached:map(function(frame) + return tonumber(frame[i]) + end) + end + return MuxStream:new(unpack(streams)) + end +end + +function SndfileStream:gtick() + -- The file is reopened, so each tick has an independent + -- read pointer which is important when reusing the stream. + -- NOTE: We could do this with a single handle per object but + -- by maintaining our own read position and seeking before reading. + local handle = sndfile:new(self.filename, "SFM_READ", + self.samplerate, self.channel_no, self.format) + + -- Make sure that we are still reading the same file; + -- at least with the same meta-data. + -- Theoretically, the file could have changed since object + -- construction. + assert(handle.info.channels == self.channel_no and + handle.info.frames == self.frames, + "Sndfile changed") + + if self.channel_no == 1 then + local read = handle.read + + return function() + return read(handle) + end + else + -- For multi-channel audio files, we generate a stream + -- of frame buffers. + -- However, the user never sees these since they are translated + -- to a MuxStream automatically (see ctor()) + local readf = handle.readf + local frame = sndfile.frame_type(self.channel_no) + + return function() + return readf(handle, frame) and frame or nil + end + end +end + +function SndfileStream:len() return self.frames end + +-- TODO: Use a buffer to improve perfomance (e.g. 1024 samples) +function Stream:save(filename, format) + if self:len() == math.huge then + error("Cannot save infinite stream") + end + + local channels = self.channels + local hnd = sndfile:new(filename, "SFM_WRITE", + samplerate, channels, format) + + local frame_buffer = sndfile.frame_type(channels) + + self:foreach(function(frame) + -- NOTE: This should be (hopefully) automatically + -- unrolled for single-channel streams + -- Otherwise each loop copies an entire frame. + -- This should be faster than letting LuaJIT translate + -- the frame directly. + for i = 1, channels do + local sample = tonumber(frame[i]) + assert(sample ~= nil) + frame_buffer[i-1] = sample + end + + -- NOTE: Apparently we cannot use hnd:write() if a frame is larger than one sample + -- (i.e. multichannel streams) + -- FIXME: Check return value + hnd:writef(frame_buffer) + end) + + hnd:close() +end -- cgit v1.2.3