Wednesday, October 22, 2014

[Lua]Get BPM/Tempo from WAV file

How do i get the tempo from WAV file? nah some Lua code can do that.

local function num2float (c)	-- http://stackoverflow.com/questions/18886447/convert-signed-ieee-754-float-to-hexadecimal-representation
	if c == 0 then return 0.0 end
	local c = string.gsub(string.format("%X", c),"(..)",function (x) return string.char(tonumber(x, 16)) end)
	local b1,b2,b3,b4 = string.byte(c, 1, 4)
	local sign = b1 > 0x7F
	local expo = (b1 % 0x80) * 0x2 + math.floor(b2 / 0x80)
	local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4
	if sign then
		sign = -1
	else
		sign = 1
	end
	local n
	if mant == 0 and expo == 0 then
		n = sign * 0.0
	elseif expo == 0xFF then
		if mant == 0 then
			n = sign * math.huge
		else
			n = 0.0/0.0
		end
	else
		n = sign * math.ldexp(1.0 + mant / 0x800000, expo - 0x7F)
	end
	return n
end
 
local function int2char(int)
	return string.char(int%256)..string.char(math.floor(int/256%256))..string.char(math.floor(int/256/256%256))..string.char(math.floor(int/256/256/256))
end
 
local function char2int(char)
	return char:sub(1,1):byte()+char:sub(2,2):byte()*256+char:sub(3,3):byte()*65536+char:sub(4,4):byte()*16777216
end
 
function getWAVTempo(wav,verbose)
	local vp=function() end
	local f
	if verbose then vp=print end
	vp("getWAVTempo start!")
	if type(wav)=="userdata" then
		vp("#1 type is userdata. Assuming it's a file type!")
		f=wav
	else
		vp("#1 type is string. It's path to wav file")
		f=assert(io.open(wav,"rb"))
	end
	if verbose then vp=function(txt) print("["..string.format("%08X",f:seek("cur")).."] "..txt) end end
	local curSeek=f:seek("cur")
	local fileSize=f:seek("end")
	f:seek("set")
	if f:read(12)=="RIFF"..int2char(fileSize-8).."WAVE" then
		vp("WAV File header correct. Reading chunks!")
		while(f:seek("cur")~=fileSize)do
			local chunk=f:read(4)
			if(chunk=="acid")then
				vp("\"acid\" chunk found. Getting tempo data!")
				f:read(24)
				tempo=num2float(char2int(f:read(4)))
				vp("Tempo data found!")
				if type(wav)=="string" then f:close()
				else f:seek("set",curSeek) end
				return tempo
			else
				local size=char2int(f:read(4))
				vp("\""..chunk.."\" chunk found with size of "..size..". Skipping...")
				if size%2==1 then size=size+1 end
				f:seek("cur",size)
			end
		end
	else
		error("Invalid wav file!")
	end
	if verbose then print("Tempo data not found!") end
	if type(wav)=="userdata"then f:seek(curSeek)
	else f:close() end
	return 0
end

What do you need to do is:
  1. Save code above to file
  2. dofile it
  3. call getWAVTempo(file[,verbose])
    getWAVTempo Parameters:
    file - file handle(from io.open) or string to filename
    verbose - show more message. If you set this to true, you can see what's going on.
  4. a. If it returns 0, then the wav file does not come with embedded tempo data
    b. If it returns value more than 0, then that's the tempo. Please note that the decimals is stripped on return.
That's it.
Feel free to use some (or all) parts of code above.

No comments:

Post a Comment