/*************************************************************************/ /* resource_format_wav.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* 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. */ /*************************************************************************/ #include "resource_format_wav.h" #include "os/file_access.h" #include "scene/resources/sample.h" RES ResourceFormatLoaderWAV::load(const String &p_path, const String &p_original_path, Error *r_error) { if (r_error) *r_error = ERR_FILE_CANT_OPEN; Error err; FileAccess *file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, RES()); if (r_error) *r_error = ERR_FILE_CORRUPT; /* CHECK RIFF */ char riff[5]; riff[4] = 0; file->get_buffer((uint8_t *)&riff, 4); //RIFF if (riff[0] != 'R' || riff[1] != 'I' || riff[2] != 'F' || riff[3] != 'F') { file->close(); memdelete(file); ERR_FAIL_V(RES()); } /* GET FILESIZE */ uint32_t filesize = file->get_32(); /* CHECK WAVE */ char wave[4]; file->get_buffer((uint8_t *)&wave, 4); //RIFF if (wave[0] != 'W' || wave[1] != 'A' || wave[2] != 'V' || wave[3] != 'E') { file->close(); memdelete(file); ERR_EXPLAIN("Not a WAV file (no WAVE RIFF Header)") ERR_FAIL_V(RES()); } uint16_t compression_code = 1; bool format_found = false; bool data_found = false; int format_bits = 0; int format_channels = 0; int format_freq = 0; Sample::LoopFormat loop = Sample::LOOP_NONE; int loop_begin = 0; int loop_end = 0; Ref sample(memnew(Sample)); while (!file->eof_reached()) { /* chunk */ char chunkID[4]; file->get_buffer((uint8_t *)&chunkID, 4); //RIFF /* chunk size */ uint32_t chunksize = file->get_32(); uint32_t file_pos = file->get_pos(); //save file pos, so we can skip to next chunk safely if (file->eof_reached()) { //ERR_PRINT("EOF REACH"); break; } if (chunkID[0] == 'f' && chunkID[1] == 'm' && chunkID[2] == 't' && chunkID[3] == ' ' && !format_found) { /* IS FORMAT CHUNK */ compression_code = file->get_16(); if (compression_code != 1 && compression_code != 3) { ERR_PRINT("Format not supported for WAVE file (not PCM). Save WAVE files as uncompressed PCM instead."); break; } format_channels = file->get_16(); if (format_channels != 1 && format_channels != 2) { ERR_PRINT("Format not supported for WAVE file (not stereo or mono)"); break; } format_freq = file->get_32(); //sampling rate file->get_32(); // average bits/second (unused) file->get_16(); // block align (unused) format_bits = file->get_16(); // bits per sample if (format_bits % 8) { ERR_PRINT("Strange number of bits in sample (not 8,16,24,32)"); break; } /* Dont need anything else, continue */ format_found = true; } if (chunkID[0] == 'd' && chunkID[1] == 'a' && chunkID[2] == 't' && chunkID[3] == 'a' && !data_found) { /* IS FORMAT CHUNK */ data_found = true; if (!format_found) { ERR_PRINT("'data' chunk before 'format' chunk found."); break; } int frames = chunksize; frames /= format_channels; frames /= (format_bits >> 3); /*print_line("chunksize: "+itos(chunksize)); print_line("channels: "+itos(format_channels)); print_line("bits: "+itos(format_bits)); */ sample->create( (format_bits == 8) ? Sample::FORMAT_PCM8 : Sample::FORMAT_PCM16, (format_channels == 2) ? true : false, frames); sample->set_mix_rate(format_freq); int len = frames; if (format_channels == 2) len *= 2; if (format_bits > 8) len *= 2; DVector data; data.resize(len); DVector::Write dataw = data.write(); void *data_ptr = dataw.ptr(); if (format_bits == 8) { int8_t *data_ptr8 = (int8_t *)data_ptr; for (int i = 0; i < frames * format_channels; i++) { // 8 bit samples are UNSIGNED uint8_t s = file->get_8(); data_ptr8[i] = (int8_t)(s - 128); } } else if (format_bits == 32 && compression_code == 3) { int16_t *data_ptr16 = (int16_t *)data_ptr; for (int i = 0; i < frames * format_channels; i++) { //32 bit IEEE Float float s = file->get_float(); data_ptr16[i] = (int32_t)(s * 32768.f); } } else if (format_bits == 16) { int16_t *data_ptr16 = (int16_t *)data_ptr; for (int i = 0; i < frames * format_channels; i++) { //16 bit SIGNED data_ptr16[i] = file->get_16(); } } else { int16_t *data_ptr16 = (int16_t *)data_ptr; for (int i = 0; i < frames * format_channels; i++) { //16+ bits samples are SIGNED // if sample is > 16 bits, just read extra bytes uint32_t s = 0; for (int b = 0; b < (format_bits >> 3); b++) { s |= ((uint32_t)file->get_8()) << (b * 8); } s <<= (32 - format_bits); data_ptr16[i] = s >> 16; } } dataw = DVector::Write(); sample->set_data(data); if (file->eof_reached()) { file->close(); memdelete(file); ERR_EXPLAIN("Premature end of file."); ERR_FAIL_V(RES()); } } if (chunkID[0] == 's' && chunkID[1] == 'm' && chunkID[2] == 'p' && chunkID[3] == 'l') { //loop point info! for (int i = 0; i < 10; i++) file->get_32(); // i wish to know why should i do this... no doc! loop = file->get_32() ? Sample::LOOP_PING_PONG : Sample::LOOP_FORWARD; loop_begin = file->get_32(); loop_end = file->get_32(); } file->seek(file_pos + chunksize); } sample->set_loop_format(loop); sample->set_loop_begin(loop_begin); sample->set_loop_end(loop_end); file->close(); memdelete(file); if (r_error) *r_error = OK; return sample; } void ResourceFormatLoaderWAV::get_recognized_extensions(List *p_extensions) const { p_extensions->push_back("wav"); } bool ResourceFormatLoaderWAV::handles_type(const String &p_type) const { return (p_type == "Sample"); } String ResourceFormatLoaderWAV::get_resource_type(const String &p_path) const { if (p_path.extension().to_lower() == "wav") return "Sample"; return ""; }