H3VZFEW4P3GU2TFCMNLGARZCVPGZHHJ6C3O7Z3HEFK3MNGZRXOVQC Function 'create_context' requires 2 arguments: a filename and a mode, which can be "r" (read) or "w" (write).A call returns one table with methods depending on the used mode.On reading, following methods are callable:- get_filename()- get_mode()- get_file_size()- get_channels_number()- get_sample_rate()- get_byte_rate()- get_block_align()- get_bits_per_sample()- get_samples_per_channel()- get_sample_from_ms(ms)- get_ms_from_sample(sample)- get_min_max_amplitude()- get_position()- set_position(pos)- get_samples_interlaced(n)- get_samples(n)On writing, following methods are callable:- get_filename()- get_mode()
Function 'create_context' requires a filename and returns a table with thefollowing methods:
-- Byte-string(unsigend integer,little endian)<->Lua-number converterslocal function bton(s)local bytes = {s:byte(1,#s)}local n, bytes_n = 0, #bytesfor i = 0, bytes_n-1 don = n + bytes[1+i] * 2^(i*8)endreturn nendlocal unpack = table.unpack or unpack -- Lua 5.1 or 5.2 table unpacker
-- Byte-string(unsigned integer,little endian)<-Lua-number converter
local file_size, channels_number, sample_rate, byte_rate, block_align, bits_per_sample, samples_per_channel-- Audio samples file arealocal data_begin, data_end-- Read file typeif file:read(4) ~= "RIFF" thenerror("not a RIFF file", 2)endfile_size = file:read(4)if not file_size thenerror("file header incomplete (file size)")endfile_size = bton(file_size) + 8if file:read(4) ~= "WAVE" thenerror("not a WAVE file", 2)end-- Read file chunkslocal chunk_id, chunk_sizewhile true do-- Read chunk headerchunk_id, chunk_size = file:read(4), file:read(4)if not chunk_size thenbreakendchunk_size = bton(chunk_size)-- Identify chunk typeif chunk_id == "fmt " then-- Read format informationslocal bytes = file:read(2)if not bytes or bton(bytes) ~= 1 thenerror("data must be in PCM format", 2)endbytes = file:read(2)if not bytes thenerror("channels number not found", 2)endchannels_number = bton(bytes)bytes = file:read(4)if not bytes thenerror("sample rate not found", 2)endsample_rate = bton(bytes)bytes = file:read(4)if not bytes thenerror("byte rate not found", 2)endbyte_rate = bton(bytes)bytes = file:read(2)if not bytes thenerror("block align not found", 2)endblock_align = bton(bytes)bytes = file:read(2)if not bytes thenerror("bits per sample not found")endbits_per_sample = bton(bytes)if bits_per_sample ~= 8 and bits_per_sample ~= 16 and bits_per_sample ~= 24 and bits_per_sample ~= 32 thenerror("bits per sample must be 8, 16, 24 or 32", 2)endfile:seek("cur", chunk_size-16)elseif chunk_id == "data" then-- Read samplesif not block_align thenerror("format informations must be defined before sample data", 2)endsamples_per_channel = chunk_size / block_aligndata_begin = file:seek()data_end = data_begin + chunk_sizebreak -- Stop here for later readingelse-- Skip chunkfile:seek("cur", chunk_size)endend-- Enough informations available?if not bits_per_sample thenerror("no format informations found", 2)end-- Return audio handlerlocal objobj = {get_filename = function()return filenameend,get_mode = function()return modeend,get_file_size = function()return file_sizeend,get_channels_number = function()return channels_numberend,get_sample_rate = function()return sample_rateend,get_byte_rate = function()return byte_rateend,get_block_align = function()return block_alignend,get_bits_per_sample = function()return bits_per_sampleend,get_samples_per_channel = function()return samples_per_channelend,get_sample_from_ms = function(ms)if not isint(ms) or ms < 0 thenerror("positive integer expected", 2)endreturn ms * 0.001 * sample_rateend,get_ms_from_sample = function(sample)if not isint(sample) or sample < 0 thenerror("positive integer expected", 2)endreturn sample / sample_rate * 1000end,get_min_max_amplitude = function()local half_level = 2^bits_per_sample / 2return -half_level, half_level - 1end,get_position = function()if not data_begin thenerror("no samples available", 2)endreturn (file:seek() - data_begin) / block_alignend,set_position = function(pos)if not isint(pos) or pos < 0 thenerror("positive integer expected", 2)elseif not data_begin thenerror("no samples available", 2)elseif data_begin + pos * block_align > data_end thenerror("tried to set position behind data end", 2)endfile:seek("set", data_begin + pos * block_align)end,get_samples_interlaced = function(n)if not isint(n) or n <= 0 thenerror("positive integer greater zero expected", 2)elseif not data_begin thenerror("no samples available", 2)elseif file:seek() + n * block_align > data_end thenerror("tried to read over data end", 2)endlocal bytes, sample, output = file:read(n * block_align), nil, {n = 0}local bytes_n = #bytesif bits_per_sample == 8 thenfor i=1, bytes_n, 1 dosample = bton(bytes:sub(i,i))output.n = output.n + 1output[output.n] = sample > 127 and sample - 256 or sampleendelseif bits_per_sample == 16 thenfor i=1, bytes_n, 2 dosample = bton(bytes:sub(i,i+1))output.n = output.n + 1output[output.n] = sample > 32767 and sample - 65536 or sampleendelseif bits_per_sample == 24 thenfor i=1, bytes_n, 3 dosample = bton(bytes:sub(i,i+2))output.n = output.n + 1output[output.n] = sample > 8388607 and sample - 16777216 or sampleendelse -- if bits_per_sample == 32 thenfor i=1, bytes_n, 4 dosample = bton(bytes:sub(i,i+3))output.n = output.n + 1output[output.n] = sample > 2147483647 and sample - 4294967296 or sampleendendreturn outputend,get_samples = function(n)local success, samples = pcall(obj.get_samples_interlaced, n)if not success thenerror(samples, 2)endlocal output, channel_samples = {n = channels_number}for c=1, output.n dochannel_samples = {n = samples.n / channels_number}for s=1, channel_samples.n dochannel_samples[s] = samples[c + (s-1) * channels_number]endoutput[c] = channel_samplesendreturn outputend}return obj-- Initialize write processelse-- Audio meta informations
--[[Analyses frequencies of audio samples.Function 'create_frequency_analyzer' requires 2 arguments: a table with audio samples and the relating sample rate.A call returns one table with following methods:- get_frequencies()- get_frequency_weight(freq)- get_frequency_range_weight(freq_min, freq_max)(FFT: http://www.relisoft.com/science/physics/fft.html)]]create_frequency_analyzer = function(samples, sample_rate)-- Check function parametersif type(samples) ~= "table" ortype(sample_rate) ~= "number" or sample_rate ~= math.floor(sample_rate) or sample_rate < 2 thenerror("samples table and sample rate expected", 2)endlocal samples_n = #samplesif samples_n ~= math.ceil_pow2(samples_n) thenerror("table size has to be a power of two", 2)endfor _, sample in ipairs(samples) doif type(sample) ~= "number" thenerror("table has only to contain numbers", 2)elseif sample > 1 or sample < -1 thenerror("numbers should be normalized / limited to -1 until 1", 2)endend-- Complex numberslocal complex_tdolocal complex = {}local function tocomplex(a, b)if getmetatable(b) ~= complex then return a, {r = b, i = 0}elseif getmetatable(a) ~= complex then return {r = a, i = 0}, belse return a, b endendcomplex.__add = function(a, b)local c1, c2 = tocomplex(a, b)return setmetatable({r = c1.r + c2.r, i = c1.i + c2.i}, complex)endcomplex.__sub = function(a, b)local c1, c2 = tocomplex(a, b)return setmetatable({r = c1.r - c2.r, i = c1.i - c2.i}, complex)endcomplex.__mul = function(a, b)local c1, c2 = tocomplex(a, b)return setmetatable({r = c1.r * c2.r - c1.i * c2.i, i = c1.r * c2.i + c1.i * c2.r}, complex)endcomplex.__index = complexcomplex_t = function(r, i)return setmetatable({r = r, i = i}, complex)endendlocal function polar(theta)return complex_t(math.cos(theta), math.sin(theta))endlocal function magnitude(c)return math.sqrt(c.r^2 + c.i^2)end-- Fast Fourier Transformlocal function fft(x)-- Check recursion breaklocal N = x.nif N > 1 then-- Dividelocal even, odd = {n = 0}, {n = 0}for i=1, N, 2 doeven.n = even.n + 1even[even.n] = x[i]endfor i=2, N, 2 doodd.n = odd.n + 1odd[odd.n] = x[i]end-- Conquerfft(even)fft(odd)--Combinelocal tfor k = 1, N/2 dot = polar(-2 * math.pi * (k-1) / N) * odd[k]x[k] = even[k] + tx[k+N/2] = even[k] - tendendend-- Numbers to complex numberslocal data = {n = samples_n}for i = 1, data.n dodata[i] = complex_t(samples[i], 0)end-- Process FFTfft(data)-- Complex numbers to numbersfor i = 1, data.n dodata[i] = magnitude(data[i])end-- Calculate ordered frequencieslocal frequencies, frequency_sum, sample_rate_half = {n = data.n / 2}, 0, sample_rate / 2for i=1, frequencies.n dofrequency_sum = frequency_sum + data[i]endif frequency_sum > 0 thenfor i=1, frequencies.n dofrequencies[i] = {freq = (i-1) / (frequencies.n-1) * sample_rate_half, weight = data[i] / frequency_sum}endelsefrequencies[1] = {freq = 0, weight = 1}for i=2, frequencies.n dofrequencies[i] = {freq = (i-1) / (frequencies.n-1) * sample_rate_half, weight = 0}endend-- Return frequencies getterreturn {get_frequencies = function()local out = {n = frequencies.n}for i=1, frequencies.n doout[i] = {freq = frequencies[i].freq, weight = frequencies[i].weight}endreturn outend,get_frequency_weight = function(freq)if type(freq) ~= "number" or freq < 0 or freq > sample_rate_half thenerror("valid frequency expected", 2)endfor i, frequency in ipairs(frequencies) doif frequency.freq == freq thenreturn frequency.weightelseif frequency.freq > freq thenlocal frequency_last = frequencies[i-1]return (freq - frequency_last.freq) / (frequency.freq - frequency_last.freq) * (frequency.weight - frequency_last.weight) + frequency_last.weightendendend,get_frequency_range_weight = function(freq_min, freq_max)if type(freq_min) ~= "number" or freq_min < 0 or freq_min > sample_rate_half ortype(freq_max) ~= "number" or freq_max < 0 or freq_max > sample_rate_half orfreq_min > freq_max thenerror("valid frequencies expected", 2)endlocal weight_sum = 0for _, frequency in ipairs(frequencies) doif frequency.freq >= freq_min and frequency.freq <= freq_max thenweight_sum = weight_sum + frequency.weightendendreturn weight_sumend}end
--[[Rounds up number to power of 2.]]function math.ceil_pow2(x)if type(x) ~= "number" thenerror("number expected", 2)endlocal p = 2while p < x dop = p * 2endreturn pend--[[Rounds down number to power of 2.]]function math.floor_pow2(x)if type(x) ~= "number" thenerror("number expected", 2)endlocal y = math.ceil_pow2(x)return x == y and x or y / 2end--[[Rounds number nearest to power of 2.]]function math.round_pow2(x)if type(x) ~= "number" thenerror("number expected", 2)endlocal min, max = math.floor_pow2(x), math.ceil_pow2(x)return (x - min) / (max-min) < 0.5 and min or maxend--[[Converts samples into an ASS (Advanced Substation Alpha) subtitle shape code.]]function audio_to_ass(samples, wave_width, wave_height_scale, wave_thickness)-- Check function parametersif type(samples) ~= "table" or not samples[1] ortype(wave_width) ~= "number" or wave_width <= 0 ortype(wave_height_scale) ~= "number" ortype(wave_thickness) ~= "number" or wave_thickness <= 0 thenerror("samples table, positive wave width, height scale and thickness expected", 2)endfor _, sample in ipairs(samples) doif type(sample) ~= "number" thenerror("table has only to contain numbers", 2)endend-- Better fitting forms of known variables for most uselocal thick2, samples_n = wave_thickness / 2, #samples-- Build shapelocal shape = string.format("m 0 %d l", samples[1] * wave_height_scale - thick2)for i=2, samples_n doshape = string.format("%s %d %d", shape, (i-1) / (samples_n-1) * wave_width, samples[i] * wave_height_scale - thick2)endfor i=samples_n, 1, -1 doshape = string.format("%s %d %d", shape, (i-1) / (samples_n-1) * wave_width, samples[i] * wave_height_scale + thick2)endreturn shapeend