Open Gianclgar opened 4 years ago
Well... I finally managed to create a GDScript that somehow parses the .wav header and gets the right data into AudioStreamSample
.
https://github.com/Gianclgar/GDScriptAudioImport
Would still be nice to have something like this built-in to be as easy as importing .ogg files
The goal should not be to "remove" the wave header. The header contains valuable information, and in order to support arbitrary wave files it is needed for further processing. For instance, without the header information it isn't even possible to handle both mono and stereo files, unless you want to guess if your stream of samples may have been mono or stereo originally. The "solution" of ignoring the wave header is getting posted a lot in the Godot community (1, 2, 3), but it simply doesn't work in general.
In my opinion a AudioStreamSample.load_from_wav("filepath/filename.wav")
which abstracts away the handling of wave file format details (e.g. different bit sizes / sampling rates etc.) is the best solution. This would also nicely fit to the existing AudioStreamSample.save_to_wav
.
On the other hand, attaching the logic to the File
type would be a bit weird: A general purpose class like File
should not have knowledge about the intricacies of the wave file standard.
The title got renamed but I think the import part is the key to solving this perhaps, see #1632.
What we basically need is to retrieve the import functionality as implemented by internal ResourceImporterWAV
class.
The functionality could be either extracted from there and refactored into the proposed AudioStreamSample.load_from_wav
, or as proposed in #1632 to allow import any resource type at run-time this way without reinventing anything, although you'd have to compile the engine from source to benefit from such a feature anyway (the export templates would also need import functionality, it's very unlikely that official export templates would have that because this can potentially bloat the binary sizes somewhat). But there are not that many importers there as you think, so could add up to 3MB at most? If not less.
There's this, but it got reverted sadly: godotengine/godot#42524
I must say this one surprises me. Audio is SO important in video games. The easier the better. I am still scratching my head over how tedious it is to load an AudioStream by code. I hope this gets implemented.
@ondesic Please don't bump issues without contributing significant new information. Use the :+1: reaction button on the first post instead.
Another use case popped up: #2632.
Well... I finally managed to create a GDScript that somehow parses the .wav header and gets the right data into AudioStreamSample.
https://github.com/Gianclgar/GDScriptAudioImport
Would still be nice to have something like this built-in to be as easy as importing .ogg files
Is there an alternative for this in Godot 4?
Looking at the documentation it seems like AudioStreamOGGVorbis.data
was replaced in favor of AudioStreamOGGVorbis.packet_sequence
.
OggPacketSequence
doesn't look it can be set without parsing the ogg file. Looking at the source code I'm not really getting any smarter on what Godot expects for OggPacketSequence.granule_positions
and OggPacketSequence.packet_data
.
AudioStreamWAV.data
still seems to be a thing (for now?) so hitting up ffmpeg to convert to wav might be the best solution right now?
Hey @DomiStyle if I understand you correctly, you are also searching for a possibility to load ogg files. This might help (starting from Godot 4.2): https://github.com/godotengine/godot/pull/78084
Hello,
I'm unsure if this issue is still being looked into, but since I needed Wav files to be loaded at runtime, I have created my own GDscript function for this. I've only supported PCM wav files, however, I expose all header information so if anyone else is more knowledgeable on other wav audio formats, they should be able to take what I've done and add to it.
This function returns an AudioStreamWAV with all required parameters like bit-rate and sample rate changed depending on the given file from the file path.
#Take a Packed Byte Array and reverse it to read little endian data to an integer
func read_le_int(file:FileAccess, byte_size:int):
var file_buffer:PackedByteArray = file.get_buffer(byte_size)
file_buffer.reverse()
return file_buffer.hex_encode().hex_to_int()
func load_wav(path:String):
var wav_file:AudioStreamWAV = AudioStreamWAV.new()
var file:FileAccess = FileAccess.open(path, FileAccess.READ)
#CHUNK ID
var file_buffer:PackedByteArray = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "RIFF"):
push_error("[load_wav] Invalid file type - not RIFF")
return false
#CHUNK SIZE - Full byte size minus first 8 bytes
var chunk_size:int = read_le_int(file, 4)
var real_size:int = file.get_length()-8
if(chunk_size != real_size):
push_error("[load_wav] Chunk size does not match. Chunk: ", chunk_size,". Expected: ",real_size)
return false
#FORMAT
file_buffer = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "WAVE"):
push_error("[load_wav] Invalid file type - not WAVE")
return false
#SUB CHUNK1 ID
file_buffer = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "fmt "):
push_error("[load_wav] Invalid file type - not fmt")
return false
#SUB CHUNK1 SIZE
var s_chunk1_size:int = read_le_int(file, 4)
if(s_chunk1_size != 16):
push_error("[load_wav] Unsupported type. Only supports PCM.")
return false
#AUDIO FORMAT
var audio_format:int = read_le_int(file, 2)
if(audio_format != 1):
push_error("[load_wav] Unsupported type. Only supports PCM.")
return false
#NUMBER OF CHANNELS
var channels:int = read_le_int(file, 2)
if(channels > 2):
push_error("[load_wav] Unsupported channel amount. Only supports Mono or Stereo.")
return false
#SAMPLE RATE
var sample_rate:int = read_le_int(file, 4)
#BYTE RATE = SampleRate*NumChannels*BitsPerSample/8
var byte_rate:int = read_le_int(file, 4)
#Block Align = NumChannels*BitsPerSample/8
var block_align:int = read_le_int(file, 2)
#BITS PER SAMPLE
var bit_rate:int = read_le_int(file, 2)
#"DATA" TEXT
file_buffer = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "data"):
push_error("[load_wav] Invalid file type - not 'data'")
return false
#AUDIO DATA SIZE
var audio_data_size:int = read_le_int(file, 4)
#Confirming values
var expected_byte_rate:float = sample_rate * channels * bit_rate / 8.0
if(byte_rate != expected_byte_rate):
push_error("[load_wav] Invalid formatting, byte rate incorrect.")
return false
var expected_block_align:float = channels * bit_rate / 8.0
if(block_align != expected_block_align):
push_error("[load_wav] Invalid formatting, block align incorrect.")
return false
####Adding Data to AudioStreamWAV####
match(bit_rate):
8:
wav_file.format = AudioStreamWAV.FORMAT_8_BITS
16:
wav_file.format = AudioStreamWAV.FORMAT_16_BITS
_:
push_error("[load_wav] Unsupported bit rate")
return false
wav_file.mix_rate = sample_rate
if(channels == 2):
wav_file.stereo = true
else:
wav_file.stereo = false
#Audio Data's starting offset is the full file size minus the difference between chunk size and audio data size, minus 8 for the 8 bytes not included in chunk size
wav_file.data = file.get_buffer(file.get_length()-(chunk_size-audio_data_size)-8)
return wav_file
A few helpful resources for me were this Youtube video by Low Byte Productions https://www.youtube.com/watch?v=udbA7u1zYfc and http://soundfile.sapp.org/doc/WaveFormat/ I based my code on what Low Byte Production explained in his video for Javascript. Another useful link: https://isip.piconepress.com/projects/speech/software/tutorials/production/fundamentals/v1.0/section_02/s02_01_p05.html#:~:text=The%20WAV%20audio%20format%20was,quality%2016%2Dbit%20audio%20format.
EDIT: Modified the function a bit to include the "Data" text present in the Wav header, and the Audio Data Size chunk to properly know the audio data size to exclude any metadata at the end of the file. Also cleaned up the function at put the little endian conversion into its own function.
Hello, Looking forward to get a merge request for this, I am writing a software that dynamically loads resources. It works for images which are available via "Image.load(path)" I know ogg vorbis are loadable as well, but MP3s and more importantly here WAVs aren't.
Is this being looked into? Based on the fact imports do work for wav, it shouldnt be much hassle replicating what have been done for ogg vorbis, or am i mistaken?
Best regards,
Is this being looked into?
To my knowledge, nobody is currently working on implementing this, but a PR is welcome.
Based on the fact imports do work for wav, it shouldnt be much hassle replicating what have been done for ogg vorbis, or am i mistaken?
The way the importer code is organized makes this nontrivial, as there are many differences between each audio file type's handling.
Would it at least be feasible to expose the import process to tool scripts, or GDExtension?
So I tried a couple of these gdscripts to load wav files and they don't work consistently. They assume the data in the wav file is a certain way, but the header isn't always the same size and whatnot. Specifically, I was having trouble with files output from Reaper, as they include extra data.
This is a hot mess, but here's something I cobbled together from the various scripts with some tweaks to handle the extended header:
#GDScriptAudioImport v0.1
#MIT License
#
#Copyright (c) 2020 Gianclgar (Giannino Clemente) gianclgar@gmail.com
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
#I honestly don't care that much, Kopimi ftw, but it's my little baby and I want it to look nice :3
extends Node
#Take a Packed Byte Array and reverse it to read little endian data to an integer
# TODO: Can't we just do .read_32 or whatever?
func read_le_int(file:FileAccess, byte_size:int):
var file_buffer:PackedByteArray = file.get_buffer(byte_size)
file_buffer.reverse()
return file_buffer.hex_encode().hex_to_int()
func report_errors(err, filepath):
# See: https://docs.godotengine.org/en/latest/classes/class_@globalscope.html#enum-globalscope-error
var result_hash = {
ERR_FILE_NOT_FOUND: "File: not found",
ERR_FILE_BAD_DRIVE: "File: Bad drive error",
ERR_FILE_BAD_PATH: "File: Bad path error.",
ERR_FILE_NO_PERMISSION: "File: No permission error.",
ERR_FILE_ALREADY_IN_USE: "File: Already in use error.",
ERR_FILE_CANT_OPEN: "File: Can't open error.",
ERR_FILE_CANT_WRITE: "File: Can't write error.",
ERR_FILE_CANT_READ: "File: Can't read error.",
ERR_FILE_UNRECOGNIZED: "File: Unrecognized error.",
ERR_FILE_CORRUPT: "File: Corrupt error.",
ERR_FILE_MISSING_DEPENDENCIES: "File: Missing dependencies error.",
ERR_FILE_EOF: "File: End of file (EOF) error."
}
if err in result_hash:
print("Error: ", result_hash[err], " ", filepath)
else:
print("Unknown error with file ", filepath, " error code: ", err)
func load_file(filepath : String) -> AudioStream:
# if File is wav
if filepath.ends_with(".wav"):
return load_wav(filepath)
var file := FileAccess.open(filepath, FileAccess.READ)
if (!file):
var err := FileAccess.get_open_error()
report_errors(err, filepath)
return AudioStreamWAV.new()
var bytes := file.get_buffer(file.get_length())
# if File is wav
#if filepath.ends_with(".wav"):
#var newstream := AudioStreamWAV.new()
#
##---------------------------
##parrrrseeeeee!!! :D
#
#var bits_per_sample = 0
#
#for i in range(0, 100):
#var those4bytes = str(char(bytes[i])+char(bytes[i+1])+char(bytes[i+2])+char(bytes[i+3]))
#
#if those4bytes == "RIFF":
#print ("RIFF OK at bytes " + str(i) + "-" + str(i+3))
##RIP bytes 4-7 integer for now
#if those4bytes == "WAVE":
#print ("WAVE OK at bytes " + str(i) + "-" + str(i+3))
#
#if those4bytes == "fmt ":
#print ("fmt OK at bytes " + str(i) + "-" + str(i+3))
#
##get format subchunk size, 4 bytes next to "fmt " are an int32
#var formatsubchunksize = bytes[i+4] + (bytes[i+5] << 8) + (bytes[i+6] << 16) + (bytes[i+7] << 24)
#print ("Format subchunk size: " + str(formatsubchunksize))
#
##using formatsubchunk index so it's easier to understand what's going on
#var fsc0 = i+8 #fsc0 is byte 8 after start of "fmt "
#
##get format code [Bytes 0-1]
#var format_code = bytes[fsc0] + (bytes[fsc0+1] << 8)
#var format_name
#if format_code == 0:
#format_name = "8_BITS"
#elif format_code == 1:
#format_name = "16_BITS"
#elif format_code == 2:
#format_name = "IMA_ADPCM"
#else:
#format_name = "UNKNOWN (trying to interpret as 16_BITS)"
#format_code = 1
#print ("Format: " + str(format_code) + " " + format_name)
##assign format to our AudioStreamSample
#newstream.format = format_code
#
##get channel num [Bytes 2-3]
#var channel_num = bytes[fsc0+2] + (bytes[fsc0+3] << 8)
#print ("Number of channels: " + str(channel_num))
##set our AudioStreamSample to stereo if needed
#if channel_num == 2: newstream.stereo = true
#
##get sample rate [Bytes 4-7]
#var sample_rate = bytes[fsc0+4] + (bytes[fsc0+5] << 8) + (bytes[fsc0+6] << 16) + (bytes[fsc0+7] << 24)
#print ("Sample rate: " + str(sample_rate))
##set our AudioStreamSample mixrate
#newstream.mix_rate = sample_rate
#
##get byte_rate [Bytes 8-11] because we can
#var byte_rate = bytes[fsc0+8] + (bytes[fsc0+9] << 8) + (bytes[fsc0+10] << 16) + (bytes[fsc0+11] << 24)
#print ("Byte rate: " + str(byte_rate))
#
##same with bits*sample*channel [Bytes 12-13]
#var bits_sample_channel = bytes[fsc0+12] + (bytes[fsc0+13] << 8)
#print ("BitsPerSample * Channel / 8: " + str(bits_sample_channel))
#
##aaaand bits per sample/bitrate [Bytes 14-15]
#bits_per_sample = bytes[fsc0+14] + (bytes[fsc0+15] << 8)
#print ("Bits per sample: " + str(bits_per_sample))
#
#if those4bytes == "data":
#assert(bits_per_sample != 0)
#
#var audio_data_size = bytes[i+4] + (bytes[i+5] << 8) + (bytes[i+6] << 16) + (bytes[i+7] << 24)
#print ("Audio data/stream size is " + str(audio_data_size) + " bytes")
#
#var data_entry_point = (i+8)
#print ("Audio data starts at byte " + str(data_entry_point))
#
##var data = bytes.subarray(data_entry_point, data_entry_point+audio_data_size-1)
#var data := bytes.slice(data_entry_point, data_entry_point+audio_data_size-1)
#
#if bits_per_sample in [24, 32]:
#newstream.data = convert_to_16bit(data, bits_per_sample)
#else:
#newstream.data = data
## end of parsing
##---------------------------
#
##get samples and set loop end
#var samplenum = newstream.data.size() / 4
#newstream.loop_end = samplenum
#newstream.loop_mode = 1 #change to 0 or delete this line if you don't want loop, also check out modes 2 and 3 in the docs
#return newstream #:D
#if file is ogg
if filepath.ends_with(".ogg"):
var newstream := AudioStreamOggVorbis.new()
newstream.loop = true #set to false or delete this line if you don't want to loop
newstream.data = bytes
return newstream
#if file is mp3
elif filepath.ends_with(".mp3"):
var newstream := AudioStreamMP3.new()
newstream.loop = true #set to false or delete this line if you don't want to loop
newstream.data = bytes
return newstream
else:
print ("ERROR: Wrong filetype or format")
file.close()
return AudioStreamWAV.new()
func load_wav(path:String) -> AudioStreamWAV:
var wav_file:AudioStreamWAV = AudioStreamWAV.new()
var file:FileAccess = FileAccess.open(path, FileAccess.READ)
#CHUNK ID
var file_buffer:PackedByteArray = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "RIFF"):
push_error("[load_wav] Invalid file type - not RIFF")
return null
#CHUNK SIZE - Full byte size minus first 8 bytes
var chunk_size:int = read_le_int(file, 4)
var real_size:int = file.get_length()-8
if(chunk_size != real_size):
push_error("[load_wav] Chunk size does not match. Chunk: ", chunk_size,". Expected: ",real_size)
return null
#FORMAT
file_buffer = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "WAVE"):
push_error("[load_wav] Invalid file type - not WAVE")
return null
#SUB CHUNK1 ID
file_buffer = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "fmt "):
push_error("[load_wav] Invalid file type - not fmt")
return null
#SUB CHUNK1 SIZE
var s_chunk1_size:int = read_le_int(file, 4)
if(s_chunk1_size != 16):
push_error("[load_wav] Unsupported type. Only supports PCM.")
return null
#AUDIO FORMAT
var audio_format:int = read_le_int(file, 2)
if(audio_format != 1):
push_error("[load_wav] Unsupported type. Only supports PCM.")
return null
#NUMBER OF CHANNELS
var channels:int = read_le_int(file, 2)
if(channels > 2):
push_error("[load_wav] Unsupported channel amount. Only supports Mono or Stereo.")
return null
#SAMPLE RATE
var sample_rate:int = read_le_int(file, 4)
#BYTE RATE = SampleRate*NumChannels*BitsPerSample/8
var byte_rate:int = read_le_int(file, 4)
#Block Align = NumChannels*BitsPerSample/8
var block_align:int = read_le_int(file, 2)
#BITS PER SAMPLE
var bit_rate:int = read_le_int(file, 2)
#"DATA" TEXT
file_buffer = file.get_buffer(4)
while (file_buffer.get_string_from_ascii() != "data"): # Might be some other header junk.
var block_size := file.get_32()
print("Extra wav block: ", file_buffer, block_size)
if (file.eof_reached()):
push_error("Failed to find data block of wav.")
return null
file.seek(file.get_position() + block_size)
file_buffer = file.get_buffer(4)
if(file_buffer.get_string_from_ascii() != "data"):
push_error("[load_wav] Invalid file type - not 'data'")
return null
#AUDIO DATA SIZE
var audio_data_size:int = read_le_int(file, 4)
#Confirming values
var expected_byte_rate:float = sample_rate * channels * bit_rate / 8.0
if(byte_rate != expected_byte_rate):
push_error("[load_wav] Invalid formatting, byte rate incorrect.")
return null
var expected_block_align:float = channels * bit_rate / 8.0
if(block_align != expected_block_align):
push_error("[load_wav] Invalid formatting, block align incorrect.")
return null
####Adding Data to AudioStreamWAV####
match(bit_rate):
8:
wav_file.format = AudioStreamWAV.FORMAT_8_BITS
16:
wav_file.format = AudioStreamWAV.FORMAT_16_BITS
_:
push_error("[load_wav] Unsupported bit rate")
return null
wav_file.mix_rate = sample_rate
if(channels == 2):
wav_file.stereo = true
else:
wav_file.stereo = false
#Audio Data's starting offset is the full file size minus the difference between chunk size and audio data size, minus 8 for the 8 bytes not included in chunk size
wav_file.data = file.get_buffer(file.get_length()-(chunk_size-audio_data_size)-8)
return wav_file
# TODO: Convert 24bit
# Converts .wav data from 24 or 32 bits to 16
#
# These conversions are SLOW in GDScript
# on my one test song, 32 -> 16 was around 3x slower than 24 -> 16
#
# I couldn't get threads to help very much
# They made the 24bit case about 2x faster in my test file
# And the 32bit case abour 50% slower
# I don't wanna risk it always being slower on other files
# And really, the solution would be to handle it in a low-level language
func convert_to_16bit(data: PackedByteArray, from: int) -> PackedByteArray:
print("converting to 16-bit from %d" % from)
var time = Time.get_ticks_msec()
# 24 bit .wav's are typically stored as integers
# so we just grab the 2 most significant bytes and ignore the other
if from == 24:
var j = 0
for i in range(0, data.size(), 3):
data[j] = data[i+1]
data[j+1] = data[i+2]
j += 2
data.resize(data.size() * 2 / 3)
# 32 bit .wav's are typically stored as floating point numbers
# so we need to grab all 4 bytes and interpret them as a float first
if from == 32:
var spb := StreamPeerBuffer.new()
var single_float: float
var value: int
for i in range(0, data.size(), 4):
spb.data_array = data.slice(i, i+3)#data.subarray(i, i+3)
single_float = spb.get_float()
value = single_float * 32768
data[i/2] = value
data[i/2+1] = value >> 8
data.resize(data.size() / 2)
print("Took %f seconds for slow conversion" % ((Time.get_ticks_msec() - time) / 1000.0))
return data
# ---------- REFERENCE ---------------
# note: typical values doesn't always match
#Positions Typical Value Description
#
#1 - 4 "RIFF" Marks the file as a RIFF multimedia file.
# Characters are each 1 byte long.
#
#5 - 8 (integer) The overall file size in bytes (32-bit integer)
# minus 8 bytes. Typically, you'd fill this in after
# file creation is complete.
#
#9 - 12 "WAVE" RIFF file format header. For our purposes, it
# always equals "WAVE".
#
#13-16 "fmt " Format sub-chunk marker. Includes trailing null.
#
#17-20 16 Length of the rest of the format sub-chunk below.
#
#21-22 1 Audio format code, a 2 byte (16 bit) integer.
# 1 = PCM (pulse code modulation).
#
#23-24 2 Number of channels as a 2 byte (16 bit) integer.
# 1 = mono, 2 = stereo, etc.
#
#25-28 44100 Sample rate as a 4 byte (32 bit) integer. Common
# values are 44100 (CD), 48000 (DAT). Sample rate =
# number of samples per second, or Hertz.
#
#29-32 176400 (SampleRate * BitsPerSample * Channels) / 8
# This is the Byte rate.
#
#33-34 4 (BitsPerSample * Channels) / 8
# 1 = 8 bit mono, 2 = 8 bit stereo or 16 bit mono, 4
# = 16 bit stereo.
#
#35-36 16 Bits per sample.
#
#37-40 "data" Data sub-chunk header. Marks the beginning of the
# raw data section.
#
#41-44 (integer) The number of bytes of the data section below this
# point. Also equal to (#ofSamples * #ofChannels *
# BitsPerSample) / 8
#
#45+ The raw audio data.
I was going to try to clean this up a bit, but I've been forced to close this tab and move on.
Describe the project you are working on: A game able to load an play with external audio files at runtime.
Describe the problem or limitation you are having in your project: I'm opening the file this way:
The problem I'm having is that this method adds 44 bytes of data at the beginning of the audio stream.
Seems to be the WAV header. After a lot of research this can be figured and find a way to remove those first 44 bytes, but it seems that the WAV header could be bigger in some files. So far I've not been able to find a way to "detect" that header size and remove it so only the audio data gets in the stream.
I don't know if this is the right way to do it, is the only one I've found so far looking at Godot documentation and Q&A.
Describe the feature / enhancement and how it helps to overcome the problem or limitation:
I think it could be some method like
file.get_audio()
,file.get_buffer_as_audio
or similar, that automatically removes the audio header, or format thePoolByteArray
it in a way thatAudioStreamSample
gets it right.Or maybe it would be even easier if added a method to
AudioStreamSample
that loads a wav file, e.g.AudioStreamSample.load_from_wav("filepath/filename.wav")
Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams: In the first case, somehow detecting the size of the header, and pass relevant data to
AudioStreamSample
as well as the audio data.In second case, would be automating all this process into a single method in
AudioStreamSample
.If this enhancement will not be used often, can it be worked around with a few lines of script?:
I guess, but after many hours trying I still don't know how.
Is there a reason why this should be core and not an add-on in the asset library?:
Is a very simple and useful improvement. I think it would also be useful to audio files generated at runtime, and get the audio files metadata.