259 lines
10 KiB
GDScript
259 lines
10 KiB
GDScript
#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
|
|
|
|
class_name AudioLoader
|
|
|
|
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 loadfile(filepath):
|
|
var file_access = FileAccess.open(filepath, FileAccess.READ)
|
|
if file_access.get_error() != OK:
|
|
report_errors(file_access.get_error(), filepath)
|
|
file_access.close()
|
|
return AudioSample.new()
|
|
|
|
var bytes = FileAccess.get_file_as_bytes(filepath)
|
|
# if File is wav
|
|
if filepath.ends_with(".wav"):
|
|
var newstream = AudioStreamWAV.load_from_buffer(bytes)
|
|
newstream.take_over_path(filepath)
|
|
|
|
# #---------------------------
|
|
# #parrrrseeeeee!!! :D
|
|
|
|
# var bits_per_sample = 0
|
|
# var i = 0
|
|
# while true:
|
|
# if i >= len(bytes) - 4: # Failsafe, if there is no data bytes
|
|
# print("Data byte not found")
|
|
# break
|
|
|
|
# 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 AudioSample
|
|
# 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 AudioSample 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 AudioSample 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)
|
|
|
|
# if bits_per_sample in [24, 32]:
|
|
# newstream.data = convert_to_16bit(data, bits_per_sample)
|
|
# else:
|
|
# newstream.data = data
|
|
|
|
# break # the data will be at the end, end searching here
|
|
|
|
# i += 1
|
|
# # 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
|
|
elif filepath.ends_with(".ogg"):
|
|
var newstream = AudioStreamOggVorbis.load_from_buffer(bytes)
|
|
newstream.take_over_path(filepath)
|
|
# newstream.loop = true #set to false or delete this line if you don't want to loop
|
|
return newstream
|
|
|
|
#if file is mp3
|
|
elif filepath.ends_with(".mp3"):
|
|
var newstream = AudioStreamMP3.load_from_buffer(bytes)
|
|
newstream.take_over_path(filepath)
|
|
# newstream.loop = true #set to false or delete this line if you don't want to loop
|
|
return newstream
|
|
|
|
else:
|
|
print ("ERROR: Wrong filetype or format")
|
|
|
|
# # 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.subarray(i, i+3)
|
|
# spb.data_array = data.slice(i, i+4)
|
|
# 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.
|