2017-03-05 15:47:28 +01:00
/**************************************************************************/
/* resource_importer_wav.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2017-02-03 04:08:50 +01:00
# include "resource_importer_wav.h"
2017-02-03 02:51:26 +01:00
2021-06-11 14:51:48 +02:00
# include "core/io/file_access.h"
2018-09-11 18:13:45 +02:00
# include "core/io/marshalls.h"
# include "core/io/resource_saver.h"
2022-07-23 16:34:36 +02:00
# include "scene/resources/audio_stream_wav.h"
2017-02-03 02:51:26 +01:00
2019-04-03 19:11:08 +02:00
const float TRIM_DB_LIMIT = - 50 ;
const int TRIM_FADE_OUT_FRAMES = 500 ;
2017-02-03 02:51:26 +01:00
String ResourceImporterWAV : : get_importer_name ( ) const {
return " wav " ;
}
String ResourceImporterWAV : : get_visible_name ( ) const {
return " Microsoft WAV " ;
}
2020-05-14 14:29:06 +02:00
2017-02-03 02:51:26 +01:00
void ResourceImporterWAV : : get_recognized_extensions ( List < String > * p_extensions ) const {
p_extensions - > push_back ( " wav " ) ;
}
2020-05-14 14:29:06 +02:00
2017-02-03 02:51:26 +01:00
String ResourceImporterWAV : : get_save_extension ( ) const {
2017-06-16 00:44:11 +02:00
return " sample " ;
2017-02-03 02:51:26 +01:00
}
String ResourceImporterWAV : : get_resource_type ( ) const {
2022-07-23 16:34:36 +02:00
return " AudioStreamWAV " ;
2017-02-03 02:51:26 +01:00
}
2022-05-13 15:04:37 +02:00
bool ResourceImporterWAV : : get_option_visibility ( const String & p_path , const String & p_option , const HashMap < StringName , Variant > & p_options ) const {
2019-07-07 18:53:21 +02:00
if ( p_option = = " force/max_rate_hz " & & ! bool ( p_options [ " force/max_rate " ] ) ) {
return false ;
}
2022-03-15 16:09:39 +01:00
// Don't show begin/end loop points if loop mode is auto-detected or disabled.
if ( ( int ) p_options [ " edit/loop_mode " ] < 2 & & ( p_option = = " edit/loop_begin " | | p_option = = " edit/loop_end " ) ) {
return false ;
}
2017-02-03 02:51:26 +01:00
return true ;
}
int ResourceImporterWAV : : get_preset_count ( ) const {
return 0 ;
}
2020-05-14 14:29:06 +02:00
2017-02-03 02:51:26 +01:00
String ResourceImporterWAV : : get_preset_name ( int p_idx ) const {
return String ( ) ;
}
2021-11-14 18:02:38 +01:00
void ResourceImporterWAV : : get_import_options ( const String & p_path , List < ImportOption > * r_options , int p_preset ) const {
2017-02-03 02:51:26 +01:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " force/8_bit " ) , false ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " force/mono " ) , false ) ) ;
2019-07-07 18:53:21 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " force/max_rate " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED ) , false ) ) ;
Fix editor suffixes and degrees conversion
* Functions to convert to/from degrees are all gone. Conversion is done by the editor.
* Use PROPERTY_HINT_ANGLE instead of PROPERTY_HINT_RANGE to edit radian angles in degrees.
* Added possibility to add suffixes to range properties, use "min,max[,step][,suffix:<something>]" example "0,100,1,suffix:m"
* In general, can add suffixes for EditorSpinSlider
Not covered by this PR, will have to be addressed by future ones:
* Ability to switch radians/degrees in the inspector for angle properties (if actually wanted).
* Animations previously made will most likely break, need to add a way to make old ones compatible.
* Only added a "px" suffix to 2D position and a "m" one to 3D position, someone needs to go through the rest of the engine and add all remaining suffixes.
* Likely also need to track down usage of EditorSpinSlider outside properties to add suffixes to it too.
2021-06-29 21:42:12 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : FLOAT , " force/max_rate_hz " , PROPERTY_HINT_RANGE , " 11025,192000,1,exp " ) , 44100 ) ) ;
2019-08-30 11:46:50 +02:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " edit/trim " ) , false ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : BOOL , " edit/normalize " ) , false ) ) ;
2022-07-23 16:34:36 +02:00
// Keep the `edit/loop_mode` enum in sync with AudioStreamWAV::LoopMode (note: +1 offset due to "Detect From WAV").
2022-03-15 16:09:39 +01:00
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " edit/loop_mode " , PROPERTY_HINT_ENUM , " Detect From WAV,Disabled,Forward,Ping-Pong,Backward " , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED ) , 0 ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " edit/loop_begin " ) , 0 ) ) ;
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " edit/loop_end " ) , - 1 ) ) ;
2024-08-31 02:32:22 +02:00
// Quite OK Audio is lightweight enough and supports virtually every significant AudioStreamWAV feature.
r_options - > push_back ( ImportOption ( PropertyInfo ( Variant : : INT , " compress/mode " , PROPERTY_HINT_ENUM , " PCM (Uncompressed),IMA ADPCM,Quite OK Audio " ) , 2 ) ) ;
2017-02-03 02:51:26 +01:00
}
2022-05-13 15:04:37 +02:00
Error ResourceImporterWAV : : import ( const String & p_source_file , const String & p_save_path , const HashMap < StringName , Variant > & p_options , List < String > * r_platform_variants , List < String > * r_gen_files , Variant * r_metadata ) {
2017-02-03 02:51:26 +01:00
/* STEP 1, READ WAVE FILE */
Error err ;
2022-03-23 10:08:58 +01:00
Ref < FileAccess > file = FileAccess : : open ( p_source_file , FileAccess : : READ , & err ) ;
2017-02-03 02:51:26 +01:00
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_V_MSG ( err ! = OK , ERR_CANT_OPEN , " Cannot open file ' " + p_source_file + " '. " ) ;
2017-02-03 02:51:26 +01:00
/* 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 ' ) {
2022-10-24 22:30:37 +02:00
ERR_FAIL_V_MSG ( ERR_FILE_UNRECOGNIZED , vformat ( " Not a WAV file. File should start with 'RIFF', but found '%s', in file of size %d bytes " , riff , file - > get_length ( ) ) ) ;
2017-02-03 02:51:26 +01:00
}
/* GET FILESIZE */
2017-09-02 22:32:31 +02:00
file - > get_32 ( ) ; // filesize
2017-02-03 02:51:26 +01:00
/* CHECK WAVE */
2022-10-24 22:30:37 +02:00
char wave [ 5 ] ;
wave [ 4 ] = 0 ;
file - > get_buffer ( ( uint8_t * ) & wave , 4 ) ; //WAVE
2017-02-03 02:51:26 +01:00
if ( wave [ 0 ] ! = ' W ' | | wave [ 1 ] ! = ' A ' | | wave [ 2 ] ! = ' V ' | | wave [ 3 ] ! = ' E ' ) {
2022-10-24 22:30:37 +02:00
ERR_FAIL_V_MSG ( ERR_FILE_UNRECOGNIZED , vformat ( " Not a WAV file. Header should contain 'WAVE', but found '%s', in file of size %d bytes " , wave , file - > get_length ( ) ) ) ;
2017-02-03 02:51:26 +01:00
}
2022-03-15 16:09:39 +01:00
// Let users override potential loop points from the WAV.
// We parse the WAV loop points only with "Detect From WAV" (0).
int import_loop_mode = p_options [ " edit/loop_mode " ] ;
2017-02-03 02:51:26 +01:00
int format_bits = 0 ;
int format_channels = 0 ;
2022-07-23 16:34:36 +02:00
AudioStreamWAV : : LoopMode loop_mode = AudioStreamWAV : : LOOP_DISABLED ;
2017-08-01 17:48:10 +02:00
uint16_t compression_code = 1 ;
2017-02-03 02:51:26 +01:00
bool format_found = false ;
bool data_found = false ;
int format_freq = 0 ;
int loop_begin = 0 ;
int loop_end = 0 ;
2017-09-01 22:33:39 +02:00
int frames = 0 ;
2017-02-03 02:51:26 +01:00
Vector < float > data ;
while ( ! file - > eof_reached ( ) ) {
/* chunk */
char chunkID [ 4 ] ;
file - > get_buffer ( ( uint8_t * ) & chunkID , 4 ) ; //RIFF
/* chunk size */
uint32_t chunksize = file - > get_32 ( ) ;
2017-09-10 15:37:49 +02:00
uint32_t file_pos = file - > get_position ( ) ; //save file pos, so we can skip to next chunk safely
2017-02-03 02:51:26 +01:00
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 */
2017-07-07 01:01:46 +02:00
//Issue: #7755 : Not a bug - usage of other formats (format codes) are unsupported in current importer version.
//Consider revision for engine version 3.0
2017-08-01 17:48:10 +02:00
compression_code = file - > get_16 ( ) ;
if ( compression_code ! = 1 & & compression_code ! = 3 ) {
2022-05-18 19:21:07 +02:00
ERR_FAIL_V_MSG ( ERR_INVALID_DATA , " Format not supported for WAVE file (not PCM). Save WAVE files as uncompressed PCM or IEEE float instead. " ) ;
2017-02-03 02:51:26 +01:00
}
format_channels = file - > get_16 ( ) ;
if ( format_channels ! = 1 & & format_channels ! = 2 ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_V_MSG ( ERR_INVALID_DATA , " Format not supported for WAVE file (not stereo or mono). " ) ;
2017-02-03 02:51:26 +01:00
}
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
2018-08-20 13:50:20 +02:00
if ( format_bits % 8 | | format_bits = = 0 ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_V_MSG ( ERR_INVALID_DATA , " Invalid amount of bits in the sample (should be one of 8, 16, 24 or 32). " ) ;
2017-02-03 02:51:26 +01:00
}
2022-05-18 19:21:07 +02:00
if ( compression_code = = 3 & & format_bits % 32 ) {
ERR_FAIL_V_MSG ( ERR_INVALID_DATA , " Invalid amount of bits in the IEEE float sample (should be 32 or 64). " ) ;
}
2017-03-24 21:45:31 +01:00
/* Don't need anything else, continue */
2017-02-03 02:51:26 +01:00
format_found = true ;
}
if ( chunkID [ 0 ] = = ' d ' & & chunkID [ 1 ] = = ' a ' & & chunkID [ 2 ] = = ' t ' & & chunkID [ 3 ] = = ' a ' & & ! data_found ) {
2018-08-20 13:50:20 +02:00
/* IS DATA CHUNK */
2017-02-03 02:51:26 +01:00
data_found = true ;
if ( ! format_found ) {
ERR_PRINT ( " 'data' chunk before 'format' chunk found. " ) ;
break ;
}
frames = chunksize ;
2019-07-20 08:09:57 +02:00
if ( format_channels = = 0 ) {
ERR_FAIL_COND_V ( format_channels = = 0 , ERR_INVALID_DATA ) ;
}
2017-02-03 02:51:26 +01:00
frames / = format_channels ;
frames / = ( format_bits > > 3 ) ;
/*print_line("chunksize: "+itos(chunksize));
print_line ( " channels: " + itos ( format_channels ) ) ;
print_line ( " bits: " + itos ( format_bits ) ) ;
2018-08-24 09:35:07 +02:00
*/
2017-02-03 02:51:26 +01:00
data . resize ( frames * format_channels ) ;
2022-05-18 19:21:07 +02:00
if ( compression_code = = 1 ) {
if ( format_bits = = 8 ) {
for ( int i = 0 ; i < frames * format_channels ; i + + ) {
// 8 bit samples are UNSIGNED
2017-02-03 02:51:26 +01:00
2022-05-18 19:21:07 +02:00
data . write [ i ] = int8_t ( file - > get_8 ( ) - 128 ) / 128.f ;
}
} else if ( format_bits = = 16 ) {
for ( int i = 0 ; i < frames * format_channels ; i + + ) {
//16 bit SIGNED
2017-02-03 02:51:26 +01:00
2022-05-18 19:21:07 +02:00
data . write [ i ] = int16_t ( file - > get_16 ( ) ) / 32768.f ;
}
} else {
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 . write [ i ] = ( int32_t ( s ) > > 16 ) / 32768.f ;
}
2017-08-01 17:48:10 +02:00
}
2022-05-18 19:21:07 +02:00
} else if ( compression_code = = 3 ) {
if ( format_bits = = 32 ) {
for ( int i = 0 ; i < frames * format_channels ; i + + ) {
//32 bit IEEE Float
2017-02-03 02:51:26 +01:00
2022-05-18 19:21:07 +02:00
data . write [ i ] = file - > get_float ( ) ;
2017-02-03 02:51:26 +01:00
}
2022-05-18 19:21:07 +02:00
} else if ( format_bits = = 64 ) {
for ( int i = 0 ; i < frames * format_channels ; i + + ) {
//64 bit IEEE Float
2017-08-01 17:48:10 +02:00
2022-05-18 19:21:07 +02:00
data . write [ i ] = file - > get_double ( ) ;
}
2017-02-03 02:51:26 +01:00
}
}
if ( file - > eof_reached ( ) ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_V_MSG ( ERR_FILE_CORRUPT , " Premature end of file. " ) ;
2017-02-03 02:51:26 +01:00
}
}
2022-03-15 16:09:39 +01:00
if ( import_loop_mode = = 0 & & chunkID [ 0 ] = = ' s ' & & chunkID [ 1 ] = = ' m ' & & chunkID [ 2 ] = = ' p ' & & chunkID [ 3 ] = = ' l ' ) {
// Loop point info!
2017-02-03 02:51:26 +01:00
2017-07-07 01:01:46 +02:00
/**
2021-10-28 15:43:36 +02:00
* Consider exploring next document :
* http : //www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/RIFFNEW.pdf
* Especially on page :
* 16 - 17
* Timestamp :
* 22 : 38 06.07 .2017 GMT
* */
2017-07-07 01:01:46 +02:00
2020-05-14 16:41:43 +02:00
for ( int i = 0 ; i < 10 ; i + + ) {
2017-02-03 02:51:26 +01:00
file - > get_32 ( ) ; // i wish to know why should i do this... no doc!
2020-05-14 16:41:43 +02:00
}
2017-02-03 02:51:26 +01:00
2018-10-28 19:37:14 +01:00
// only read 0x00 (loop forward), 0x01 (loop ping-pong) and 0x02 (loop backward)
// Skip anything else because it's not supported, reserved for future uses or sampler specific
2018-01-05 22:29:37 +01:00
// from https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl (loop type values table)
int loop_type = file - > get_32 ( ) ;
2018-10-28 19:37:14 +01:00
if ( loop_type = = 0x00 | | loop_type = = 0x01 | | loop_type = = 0x02 ) {
if ( loop_type = = 0x00 ) {
2022-07-23 16:34:36 +02:00
loop_mode = AudioStreamWAV : : LOOP_FORWARD ;
2018-10-28 19:37:14 +01:00
} else if ( loop_type = = 0x01 ) {
2022-07-23 16:34:36 +02:00
loop_mode = AudioStreamWAV : : LOOP_PINGPONG ;
2018-10-28 19:37:14 +01:00
} else if ( loop_type = = 0x02 ) {
2022-07-23 16:34:36 +02:00
loop_mode = AudioStreamWAV : : LOOP_BACKWARD ;
2018-10-28 19:37:14 +01:00
}
2018-01-05 22:29:37 +01:00
loop_begin = file - > get_32 ( ) ;
loop_end = file - > get_32 ( ) ;
}
2017-02-03 02:51:26 +01:00
}
2023-12-04 20:01:34 +01:00
// Move to the start of the next chunk. Note that RIFF requires a padding byte for odd
// chunk sizes.
file - > seek ( file_pos + chunksize + ( chunksize & 1 ) ) ;
2017-02-03 02:51:26 +01:00
}
// STEP 2, APPLY CONVERSIONS
bool is16 = format_bits ! = 8 ;
int rate = format_freq ;
2018-08-24 09:35:07 +02:00
/*
2017-02-03 02:51:26 +01:00
print_line ( " Input Sample: " ) ;
print_line ( " \t frames: " + itos ( frames ) ) ;
print_line ( " \t format_channels: " + itos ( format_channels ) ) ;
print_line ( " \t 16bits: " + itos ( is16 ) ) ;
print_line ( " \t rate: " + itos ( rate ) ) ;
print_line ( " \t loop: " + itos ( loop ) ) ;
print_line ( " \t loop begin: " + itos ( loop_begin ) ) ;
print_line ( " \t loop end: " + itos ( loop_end ) ) ;
2018-08-24 09:35:07 +02:00
*/
2017-02-03 02:51:26 +01:00
//apply frequency limit
bool limit_rate = p_options [ " force/max_rate " ] ;
int limit_rate_hz = p_options [ " force/max_rate_hz " ] ;
2017-08-01 02:57:58 +02:00
if ( limit_rate & & rate > limit_rate_hz & & rate > 0 & & frames > 0 ) {
2018-08-24 09:35:07 +02:00
// resample!
2018-02-01 23:12:36 +01:00
int new_data_frames = ( int ) ( frames * ( float ) limit_rate_hz / ( float ) rate ) ;
2017-02-03 02:51:26 +01:00
Vector < float > new_data ;
new_data . resize ( new_data_frames * format_channels ) ;
for ( int c = 0 ; c < format_channels ; c + + ) {
2018-02-01 23:12:36 +01:00
float frac = .0f ;
int ipos = 0 ;
2017-02-03 02:51:26 +01:00
for ( int i = 0 ; i < new_data_frames ; i + + ) {
2024-03-02 00:06:48 +01:00
// Cubic interpolation should be enough.
2018-02-01 23:12:36 +01:00
2017-02-03 02:51:26 +01:00
float y0 = data [ MAX ( 0 , ipos - 1 ) * format_channels + c ] ;
float y1 = data [ ipos * format_channels + c ] ;
float y2 = data [ MIN ( frames - 1 , ipos + 1 ) * format_channels + c ] ;
float y3 = data [ MIN ( frames - 1 , ipos + 2 ) * format_channels + c ] ;
2024-04-26 17:39:18 +02:00
new_data . write [ i * format_channels + c ] = Math : : cubic_interpolate ( y1 , y2 , y0 , y3 , frac ) ;
2018-02-01 23:12:36 +01:00
// update position and always keep fractional part within ]0...1]
// in order to avoid 32bit floating point precision errors
frac + = ( float ) rate / ( float ) limit_rate_hz ;
int tpos = ( int ) Math : : floor ( frac ) ;
ipos + = tpos ;
frac - = tpos ;
2017-02-03 02:51:26 +01:00
}
}
2022-03-15 16:09:39 +01:00
if ( loop_mode ) {
2018-02-01 23:12:36 +01:00
loop_begin = ( int ) ( loop_begin * ( float ) new_data_frames / ( float ) frames ) ;
loop_end = ( int ) ( loop_end * ( float ) new_data_frames / ( float ) frames ) ;
2017-02-03 02:51:26 +01:00
}
2018-02-01 23:12:36 +01:00
2017-02-03 02:51:26 +01:00
data = new_data ;
rate = limit_rate_hz ;
frames = new_data_frames ;
}
bool normalize = p_options [ " edit/normalize " ] ;
if ( normalize ) {
float max = 0 ;
for ( int i = 0 ; i < data . size ( ) ; i + + ) {
float amp = Math : : abs ( data [ i ] ) ;
2020-05-14 16:41:43 +02:00
if ( amp > max ) {
2017-02-03 02:51:26 +01:00
max = amp ;
2020-05-14 16:41:43 +02:00
}
2017-02-03 02:51:26 +01:00
}
if ( max > 0 ) {
float mult = 1.0 / max ;
for ( int i = 0 ; i < data . size ( ) ; i + + ) {
2018-07-25 03:11:03 +02:00
data . write [ i ] * = mult ;
2017-02-03 02:51:26 +01:00
}
}
}
bool trim = p_options [ " edit/trim " ] ;
2023-03-23 16:19:20 +01:00
if ( trim & & ( loop_mode = = AudioStreamWAV : : LOOP_DISABLED ) & & format_channels > 0 ) {
2017-02-03 02:51:26 +01:00
int first = 0 ;
2019-04-03 19:11:08 +02:00
int last = ( frames / format_channels ) - 1 ;
2017-02-03 02:51:26 +01:00
bool found = false ;
2022-08-13 17:45:42 +02:00
float limit = Math : : db_to_linear ( TRIM_DB_LIMIT ) ;
2019-04-03 19:11:08 +02:00
for ( int i = 0 ; i < data . size ( ) / format_channels ; i + + ) {
float ampChannelSum = 0 ;
for ( int j = 0 ; j < format_channels ; j + + ) {
ampChannelSum + = Math : : abs ( data [ ( i * format_channels ) + j ] ) ;
}
float amp = Math : : abs ( ampChannelSum / ( float ) format_channels ) ;
2017-02-03 02:51:26 +01:00
if ( ! found & & amp > limit ) {
2019-04-11 17:17:33 +02:00
first = i ;
2017-02-03 02:51:26 +01:00
found = true ;
}
if ( found & & amp > limit ) {
2019-04-11 17:17:33 +02:00
last = i ;
2017-02-03 02:51:26 +01:00
}
}
if ( first < last ) {
Vector < float > new_data ;
2019-04-03 19:11:08 +02:00
new_data . resize ( ( last - first ) * format_channels ) ;
for ( int i = first ; i < last ; i + + ) {
float fadeOutMult = 1 ;
if ( last - i < TRIM_FADE_OUT_FRAMES ) {
fadeOutMult = ( ( float ) ( last - i - 1 ) / ( float ) TRIM_FADE_OUT_FRAMES ) ;
}
for ( int j = 0 ; j < format_channels ; j + + ) {
new_data . write [ ( ( i - first ) * format_channels ) + j ] = data [ ( i * format_channels ) + j ] * fadeOutMult ;
}
2017-02-03 02:51:26 +01:00
}
data = new_data ;
frames = data . size ( ) / format_channels ;
}
}
2022-03-15 16:09:39 +01:00
if ( import_loop_mode > = 2 ) {
2022-07-23 16:34:36 +02:00
loop_mode = ( AudioStreamWAV : : LoopMode ) ( import_loop_mode - 1 ) ;
2022-03-15 16:09:39 +01:00
loop_begin = p_options [ " edit/loop_begin " ] ;
loop_end = p_options [ " edit/loop_end " ] ;
// Wrap around to max frames, so `-1` can be used to select the end, etc.
if ( loop_begin < 0 ) {
loop_begin = CLAMP ( loop_begin + frames + 1 , 0 , frames ) ;
}
if ( loop_end < 0 ) {
loop_end = CLAMP ( loop_end + frames + 1 , 0 , frames ) ;
}
2017-02-03 02:51:26 +01:00
}
int compression = p_options [ " compress/mode " ] ;
bool force_mono = p_options [ " force/mono " ] ;
if ( force_mono & & format_channels = = 2 ) {
Vector < float > new_data ;
new_data . resize ( data . size ( ) / 2 ) ;
for ( int i = 0 ; i < frames ; i + + ) {
2018-07-25 03:11:03 +02:00
new_data . write [ i ] = ( data [ i * 2 + 0 ] + data [ i * 2 + 1 ] ) / 2.0 ;
2017-02-03 02:51:26 +01:00
}
data = new_data ;
format_channels = 1 ;
}
bool force_8_bit = p_options [ " force/8_bit " ] ;
if ( force_8_bit ) {
is16 = false ;
}
2024-04-17 01:33:29 +02:00
Vector < uint8_t > pcm_data ;
2022-07-23 16:34:36 +02:00
AudioStreamWAV : : Format dst_format ;
2017-02-03 02:51:26 +01:00
if ( compression = = 1 ) {
2022-07-23 16:34:36 +02:00
dst_format = AudioStreamWAV : : FORMAT_IMA_ADPCM ;
2017-02-03 02:51:26 +01:00
if ( format_channels = = 1 ) {
2024-04-17 01:33:29 +02:00
_compress_ima_adpcm ( data , pcm_data ) ;
2017-02-03 02:51:26 +01:00
} else {
//byte interleave
Vector < float > left ;
Vector < float > right ;
int tframes = data . size ( ) / 2 ;
left . resize ( tframes ) ;
right . resize ( tframes ) ;
for ( int i = 0 ; i < tframes ; i + + ) {
2018-07-25 03:11:03 +02:00
left . write [ i ] = data [ i * 2 + 0 ] ;
right . write [ i ] = data [ i * 2 + 1 ] ;
2017-02-03 02:51:26 +01:00
}
2020-02-17 22:06:54 +01:00
Vector < uint8_t > bleft ;
Vector < uint8_t > bright ;
2017-02-03 02:51:26 +01:00
_compress_ima_adpcm ( left , bleft ) ;
_compress_ima_adpcm ( right , bright ) ;
int dl = bleft . size ( ) ;
2024-04-17 01:33:29 +02:00
pcm_data . resize ( dl * 2 ) ;
2017-02-03 02:51:26 +01:00
2024-04-17 01:33:29 +02:00
uint8_t * w = pcm_data . ptrw ( ) ;
2020-02-17 22:06:54 +01:00
const uint8_t * rl = bleft . ptr ( ) ;
const uint8_t * rr = bright . ptr ( ) ;
2017-02-03 02:51:26 +01:00
for ( int i = 0 ; i < dl ; i + + ) {
w [ i * 2 + 0 ] = rl [ i ] ;
w [ i * 2 + 1 ] = rr [ i ] ;
}
}
} else {
2022-07-23 16:34:36 +02:00
dst_format = is16 ? AudioStreamWAV : : FORMAT_16_BITS : AudioStreamWAV : : FORMAT_8_BITS ;
2024-04-17 01:33:29 +02:00
bool enforce16 = is16 | | compression = = 2 ;
pcm_data . resize ( data . size ( ) * ( enforce16 ? 2 : 1 ) ) ;
2017-02-03 02:51:26 +01:00
{
2024-04-17 01:33:29 +02:00
uint8_t * w = pcm_data . ptrw ( ) ;
2017-02-03 02:51:26 +01:00
int ds = data . size ( ) ;
for ( int i = 0 ; i < ds ; i + + ) {
2024-04-17 01:33:29 +02:00
if ( enforce16 ) {
2017-02-03 02:51:26 +01:00
int16_t v = CLAMP ( data [ i ] * 32768 , - 32768 , 32767 ) ;
encode_uint16 ( v , & w [ i * 2 ] ) ;
} else {
int8_t v = CLAMP ( data [ i ] * 128 , - 128 , 127 ) ;
w [ i ] = v ;
}
}
}
}
2024-04-17 01:33:29 +02:00
Vector < uint8_t > dst_data ;
if ( compression = = 2 ) {
dst_format = AudioStreamWAV : : FORMAT_QOA ;
2024-08-28 04:56:26 +02:00
qoa_desc desc = { } ;
2024-04-17 01:33:29 +02:00
uint32_t qoa_len = 0 ;
desc . samplerate = rate ;
desc . samples = frames ;
desc . channels = format_channels ;
2024-08-28 04:56:26 +02:00
void * encoded = qoa_encode ( ( short * ) pcm_data . ptr ( ) , & desc , & qoa_len ) ;
if ( encoded ) {
dst_data . resize ( qoa_len ) ;
memcpy ( dst_data . ptrw ( ) , encoded , qoa_len ) ;
QOA_FREE ( encoded ) ;
}
2024-04-17 01:33:29 +02:00
} else {
dst_data = pcm_data ;
}
2022-07-23 16:34:36 +02:00
Ref < AudioStreamWAV > sample ;
2021-06-18 00:03:09 +02:00
sample . instantiate ( ) ;
2017-02-03 02:51:26 +01:00
sample - > set_data ( dst_data ) ;
sample - > set_format ( dst_format ) ;
sample - > set_mix_rate ( rate ) ;
2022-03-15 16:09:39 +01:00
sample - > set_loop_mode ( loop_mode ) ;
2017-02-03 02:51:26 +01:00
sample - > set_loop_begin ( loop_begin ) ;
sample - > set_loop_end ( loop_end ) ;
sample - > set_stereo ( format_channels = = 2 ) ;
2022-06-03 01:33:42 +02:00
ResourceSaver : : save ( sample , p_save_path + " .sample " ) ;
2017-02-03 02:51:26 +01:00
return OK ;
}
ResourceImporterWAV : : ResourceImporterWAV ( ) {
}