The Wave (2.0)
Posted: Wed Sep 30, 2015 7:20 pm
Hi! I wrote a utility to save PCM .wav files some time ago, but it was kinda buggy, it only worked properly with 16-bit files and only saved SoundDatas. I tried to rewrite it and it seems it works better (also, it's now 0.9.2 compatible), but I'm not completely sure about that. Here's how it works:
This is the simplest it gets. You require the lib, call wave.save giving it a SoundData, a filename, an optional callback which is called when the file is saved) and an optional "force" parameter (if true, The Wave will overwrite the file if it already exists). When saving SoundDatas, this is pretty much all you have to worry about. It automatically gets the parameters (bitrate, bit depth, channels number) from the SoundData and is fast enough you probably don't have to worry about a callback: if you don't like it being asynchronous, you can pass "false" (the value, not the string) as callback and it'll wait until the thread's done. The callback is only called if you call wave.update in your love.update though.
There is also a more complex way to use it:
wave.save also return a handy handler (hey! pun there! laugh!) that allows you to query Wave for the saving status (it can return "analyzing", "saving", "done" or "error"), how much it saved (only if you save a table though; it will be nil if you're saving a SoundData) and, if the thread errors, the error message. An error might be caused if you pass an invalid table or an invalid userdata (pretty much anything but SoundData). A table is only valid if it has an array part and that array only contains numbers, and if it has the fields bitDepth (which also has to be either 8 or 16), channel (which must be a positive number) and sampleRate (which must be a positive number). It can also error if the file exists and the 4th argument isn't true-ish, or if it can't write to the file.
wave.save can receive normal arguments (filename, soundData or table, callback, overwrite, exceptionMode) or a table {filename = string, sound = soundData or table, callback = func/false, overwrite = true, exceptionMode = string}.
The filename is, of course, the name/path the file will end up into in the save directory.
Sound is either a SoundData or the aforementioned table.
Callback is a function that will be called if the file is succesfully saved or if the thread encountered an error. It gets one argument, a boolean which is true if the thread's done and false if it errored. If the callback itself is false (not nil!), wave.save will behave synchronously and block everything till it's done. The function itself will return true if the file got saved correctly or nil, error if the thread encountered an error.
Overwrite is a boolean that tells Wave wether to overwrite a file, if it already exists.
ExceptionMode is the one single reason the API got changed a few hours after first release. It only makes sense if you're saving a table and it can have three values:
EDIT: A point has been brought up on IRC. Poll!
EDIT2: Poll's over. 0 results, but a good talk on IRC brought a solution. Thanks, z0rg and DesertRock!
Code: Select all
local wave = require "wave"
local sData = love.sound.newSoundData "Example.ogg"
wave.save{ filename = "Example.wav", sound = sData, overwrite = true, callback = function() love.graphics.setColor(255, 0, 0) end}
There is also a more complex way to use it:
Code: Select all
local t = {}
t.bitDepth = 16
t.channels = 1
t.sampleRate = 48000
local BIRATEPI = t.sampleRate/math.pi/2
local sine = function(s, v, a) return a*math.sin(s*v/BIRATEPI) end
for i = 1, t.sampleRate*15 do
t[i] = sine(i, 220, 2)
end
local sin = wave.save{ sound = t, filename = "Sine.wav", callback = function() love.system.openURL(love.filesystem.getSaveDirectory()) end, overwrite = true, exceptionMode = "normalize" }
love.update = function(dt)
wave.update(dt)
end
love.draw = function()
love.graphics.print("This is not a fancy demo. If everything is red, the .ogg file was saved as a wav file.\nWhen the following's done, a folder will automagically be opened.", 0, 0)
local p = sin:getPercent()
if p then
love.graphics.print(p .. "%", 0, 32)
end
local s = sin:getStatus()
if s then
love.graphics.print(s, 0, 48)
end
if sin:getError() then
love.graphics.print(sin:getError(), 0, 64)
end
end
wave.save can receive normal arguments (filename, soundData or table, callback, overwrite, exceptionMode) or a table {filename = string, sound = soundData or table, callback = func/false, overwrite = true, exceptionMode = string}.
The filename is, of course, the name/path the file will end up into in the save directory.
Sound is either a SoundData or the aforementioned table.
Callback is a function that will be called if the file is succesfully saved or if the thread encountered an error. It gets one argument, a boolean which is true if the thread's done and false if it errored. If the callback itself is false (not nil!), wave.save will behave synchronously and block everything till it's done. The function itself will return true if the file got saved correctly or nil, error if the thread encountered an error.
Overwrite is a boolean that tells Wave wether to overwrite a file, if it already exists.
ExceptionMode is the one single reason the API got changed a few hours after first release. It only makes sense if you're saving a table and it can have three values:
- "error": if any single sample is greater than 1 or lesser than -1, the thread will halt and Wave won't even try to save. Default if unspecified.
- "clip": if any sample is greater than 1 or lesser than -1, it becomes 1 or -1 respectively. Standard clipping.
- "normalize": normalizes the whole table, dividing every sample by the absolute value of the biggest one (thus making it 1 or -1 and every other one smaller)
EDIT: A point has been brought up on IRC. Poll!
EDIT2: Poll's over. 0 results, but a good talk on IRC brought a solution. Thanks, z0rg and DesertRock!