Merge pull request #9 from jorio/master

Load/Save sfxr's original binary format
This commit is contained in:
nucular 2014-08-26 16:10:07 +02:00
commit 25736975fd
2 changed files with 220 additions and 15 deletions

View File

@ -374,7 +374,7 @@ function createActionButtons()
frt:SetPos(5, 30) frt:SetPos(5, 30)
local sb = lf.Create("button") local sb = lf.Create("button")
sb:SetText("Save") sb:SetText("Save Lua")
sb:SetWidth(67) sb:SetWidth(67)
sb.OnClick = function(o) sb.OnClick = function(o)
local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.lua" local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.lua"
@ -385,7 +385,7 @@ function createActionButtons()
f:AddItem(sb) f:AddItem(sb)
local lb = lf.Create("button") local lb = lf.Create("button")
lb:SetText("Load") lb:SetText("Load Lua")
lb:SetWidth(67) lb:SetWidth(67)
lb.OnClick = function(o) lb.OnClick = function(o)
local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.lua" local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.lua"
@ -396,6 +396,28 @@ function createActionButtons()
end end
f:AddItem(lb) f:AddItem(lb)
local bsb = lf.Create("button")
bsb:SetText("Save binary")
bsb:SetWidth(67)
bsb.OnClick = function(o)
local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.sfxr"
sound:saveBinary(p)
frt:SetText("Saved to\n" .. p)
fr:SetVisible(true):SetModal(true):Center()
end
f:AddItem(bsb)
local blb = lf.Create("button")
blb:SetText("Load binary")
blb:SetWidth(67)
blb.OnClick = function(o)
local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.sfxr"
sound:loadBinary(p)
frt:SetText("Loaded from\n" .. p)
fr:SetVisible(true):SetModal(true):Center()
end
f:AddItem(blb)
local eb = lf.Create("button") local eb = lf.Create("button")
eb:SetText("Export WAV") eb:SetText("Export WAV")
eb:SetWidth(140) eb:SetWidth(140)
@ -407,12 +429,14 @@ function createActionButtons()
end end
f:AddItem(eb) f:AddItem(eb)
f:SetPos(485, 485) f:SetPos(485, 455)
f:SetSize(150, 110) f:SetSize(150, 140)
-- well ugh -- well ugh
lb:SetPos(78, 47) lb:SetPos(78, 47)
eb:SetY(77) bsb:SetY(77)
blb:SetPos(78, 77)
eb:SetY(107)
end end
function createOther() function createOther()
@ -461,7 +485,7 @@ function createOther()
s:SetValue(sound.volume.sound) s:SetValue(sound.volume.sound)
f:AddItem(s) f:AddItem(s)
f:SetPos(485, 370) f:SetPos(485, 340)
f:SetWidth(150) f:SetWidth(150)

199
sfxr.lua
View File

@ -99,6 +99,66 @@ local function setseed(seed)
end end
end end
-- IEEE754 32-bit big-endian floating point numbers
-- source: https://stackoverflow.com/questions/14416734/
local function packIEEE754(number)
if number == 0 then
return string.char(0x00, 0x00, 0x00, 0x00)
elseif number ~= number then
return string.char(0xFF, 0xFF, 0xFF, 0xFF)
else
local sign = 0x00
if number < 0 then
sign = 0x80
number = -number
end
local mantissa, exponent = math.frexp(number)
exponent = exponent + 0x7F
if exponent <= 0 then
mantissa = math.ldexp(mantissa, exponent - 1)
exponent = 0
elseif exponent > 0 then
if exponent >= 0xFF then
return string.char(sign + 0x7F, 0x80, 0x00, 0x00)
elseif exponent == 1 then
exponent = 0
else
mantissa = mantissa * 2 - 1
exponent = exponent - 1
end
end
mantissa = math.floor(math.ldexp(mantissa, 23) + 0.5)
return string.char(
sign + math.floor(exponent / 2),
(exponent % 2) * 0x80 + math.floor(mantissa / 0x10000),
math.floor(mantissa / 0x100) % 0x100,
mantissa % 0x100)
end
end
local function unpackIEEE754(packed)
local b1, b2, b3, b4 = string.byte(packed, 1, 4)
local exponent = (b1 % 0x80) * 0x02 + math.floor(b2 / 0x80)
local mantissa = math.ldexp(((b2 % 0x80) * 0x100 + b3) * 0x100 + b4, -23)
if exponent == 0xFF then
if mantissa > 0 then
return 0 / 0
else
mantissa = math.huge
exponent = 0x7F
end
elseif exponent > 0 then
mantissa = mantissa + 1
else
exponent = exponent + 1
end
if b1 >= 0x80 then
mantissa = -mantissa
end
return math.ldexp(mantissa, exponent - 0x7F)
end
-- Constructor -- Constructor
function sfxr.newSound(...) function sfxr.newSound(...)
@ -756,7 +816,7 @@ function sfxr.Sound:randomHit(seed)
end end
self.frequency.start = random(0.2, 0.8) self.frequency.start = random(0.2, 0.8)
self.frequency.glide = -0.3 - random(0, 0.4) self.frequency.slide = -0.3 - random(0, 0.4)
self.envelope.attack = 0 self.envelope.attack = 0
self.envelope.sustain = random(0, 0.1) self.envelope.sustain = random(0, 0.1)
self.envelope.decay = random(0.1, 0.3) self.envelope.decay = random(0.1, 0.3)
@ -905,14 +965,6 @@ function sfxr.Sound:exportWAV(f, freq, bits)
end end
end end
function sfxr.Sound:load(f)
local close = false
if type(f) == "string" then
f = io.open(f, "wb")
close = true
end
end
function sfxr.Sound:save(f, compressed) function sfxr.Sound:save(f, compressed)
local close = false local close = false
if type(f) == "string" then if type(f) == "string" then
@ -995,4 +1047,133 @@ function sfxr.Sound:load(f)
end end
end end
function sfxr.Sound:saveBinary(f)
local close = false
if type(f) == "string" then
f = io.open(f, "w")
close = true
end
function writeFloat(x)
local packed = packIEEE754(x):reverse()
assert(packed:len() == 4)
f:write(packed)
end
f:write('\x66\x00\x00\x00') -- version 102
assert(self.wavetype < 256)
f:write(string.char(self.wavetype) .. '\x00\x00\x00')
writeFloat(self.volume.sound)
writeFloat(self.frequency.start)
writeFloat(self.frequency.min)
writeFloat(self.frequency.slide)
writeFloat(self.frequency.dslide)
writeFloat(self.duty.ratio)
writeFloat(self.duty.sweep)
writeFloat(self.vibrato.depth)
writeFloat(self.vibrato.speed)
writeFloat(self.vibrato.delay)
writeFloat(self.envelope.attack)
writeFloat(self.envelope.sustain)
writeFloat(self.envelope.decay)
writeFloat(self.envelope.punch)
f:write('\x00') -- unused filter_on boolean
writeFloat(self.lowpass.resonance)
writeFloat(self.lowpass.cutoff)
writeFloat(self.lowpass.sweep)
writeFloat(self.highpass.cutoff)
writeFloat(self.highpass.sweep)
writeFloat(self.phaser.offset)
writeFloat(self.phaser.sweep)
writeFloat(self.repeatspeed)
writeFloat(self.change.speed)
writeFloat(self.change.amount)
if close then
f:close()
end
end
function sfxr.Sound:loadBinary(f)
local close = false
if type(f) == "string" then
f = io.open(f, "r")
close = true
end
local s
if io.type(f) == "file" then
s = f:read("*a")
else
s = f:read()
end
if close then
f:close()
end
local off = 1
local function readFloat()
local f = unpackIEEE754(s:sub(off, off+3):reverse())
off = off + 4
return f
end
-- Start reading the string
local version = s:byte(off)
off = off + 4
if version < 100 or version > 102 then
return nil, "unknown version number "..version
end
self.wavetype = s:byte(off)
off = off + 4
self.volume.sound = version==102 and readFloat() or 0.5
self.frequency.start = readFloat()
self.frequency.min = readFloat()
self.frequency.slide = readFloat()
self.frequency.dslide = version>=101 and readFloat() or 0
self.duty.ratio = readFloat()
self.duty.sweep = readFloat()
self.vibrato.depth = readFloat()
self.vibrato.speed = readFloat()
self.vibrato.delay = readFloat()
self.envelope.attack = readFloat()
self.envelope.sustain = readFloat()
self.envelope.decay = readFloat()
self.envelope.punch = readFloat()
off = off + 1 -- filter_on - seems to be ignored in the C++ version
self.lowpass.resonance = readFloat()
self.lowpass.cutoff = readFloat()
self.lowpass.sweep = readFloat()
self.highpass.cutoff = readFloat()
self.highpass.sweep = readFloat()
self.phaser.offset = readFloat()
self.phaser.sweep = readFloat()
self.repeatspeed = readFloat()
if version >= 101 then
self.change.speed = readFloat()
self.change.amount = readFloat()
end
assert(off-1 == s:len())
end
return sfxr return sfxr