diff options
-rw-r--r-- | scene/register_scene_types.cpp | 2 | ||||
-rw-r--r-- | scene/resources/audio_stream_sample.cpp | 557 | ||||
-rw-r--r-- | scene/resources/audio_stream_sample.h | 128 | ||||
-rw-r--r-- | tools/editor/editor_node.cpp | 5 | ||||
-rw-r--r-- | tools/editor/import/resource_import_wav.cpp | 619 | ||||
-rw-r--r-- | tools/editor/import/resource_import_wav.h | 30 |
6 files changed, 1341 insertions, 0 deletions
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 0ad140f7c3..c0eaca24a3 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -85,6 +85,7 @@ #include "scene/gui/graph_node.h" #include "scene/gui/graph_edit.h" #include "scene/gui/tool_button.h" +#include "scene/resources/audio_stream_sample.h" #include "scene/resources/video_stream.h" #include "scene/2d/particles_2d.h" #include "scene/2d/path_2d.h" @@ -596,6 +597,7 @@ void register_scene_types() { ClassDB::register_class<AudioPlayer>(); ClassDB::register_virtual_class<VideoStream>(); + ClassDB::register_class<AudioStreamSample>(); OS::get_singleton()->yield(); //may take time to init diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp new file mode 100644 index 0000000000..21339cb90b --- /dev/null +++ b/scene/resources/audio_stream_sample.cpp @@ -0,0 +1,557 @@ +#include "audio_stream_sample.h" + +void AudioStreamPlaybackSample::start(float p_from_pos) { + + for(int i=0;i<2;i++) { + ima_adpcm[i].step_index=0; + ima_adpcm[i].predictor=0; + ima_adpcm[i].loop_step_index=0; + ima_adpcm[i].loop_predictor=0; + ima_adpcm[i].last_nibble=-1; + ima_adpcm[i].loop_pos=0x7FFFFFFF; + ima_adpcm[i].window_ofs=0; + ima_adpcm[i].ptr=(const uint8_t*)base->data; + ima_adpcm[i].ptr+=AudioStreamSample::DATA_PAD; + } + + seek_pos(p_from_pos); + sign=1; + active=true; +} + +void AudioStreamPlaybackSample::stop() { + + active=false; +} + +bool AudioStreamPlaybackSample::is_playing() const { + + return active; +} + +int AudioStreamPlaybackSample::get_loop_count() const { + + return 0; +} + +float AudioStreamPlaybackSample::get_pos() const { + + return float(offset>>MIX_FRAC_BITS)/base->mix_rate; +} +void AudioStreamPlaybackSample::seek_pos(float p_time) { + + if (base->format==AudioStreamSample::FORMAT_IMA_ADPCM) + return; //no seeking in ima-adpcm + + float max=get_length(); + if (p_time<0) { + p_time=0; + } else if (p_time>=max) { + p_time=max-0.001; + } + + offset = uint64_t(p_time * base->mix_rate)<<MIX_FRAC_BITS; +} + + +template<class Depth,bool is_stereo,bool is_ima_adpcm> +void AudioStreamPlaybackSample::do_resample(const Depth* p_src, AudioFrame *p_dst,int64_t &offset,int32_t &increment,uint32_t amount,IMA_ADPCM_State *ima_adpcm) { + + // this function will be compiled branchless by any decent compiler + + int32_t final,final_r,next,next_r; + while (amount--) { + + int64_t pos=offset >> MIX_FRAC_BITS; + if (is_stereo && !is_ima_adpcm) + pos<<=1; + + if (is_ima_adpcm) { + + int64_t sample_pos = pos + ima_adpcm[0].window_ofs; + + while(sample_pos>ima_adpcm[0].last_nibble) { + + + static const int16_t _ima_adpcm_step_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + static const int8_t _ima_adpcm_index_table[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + for(int i=0;i<(is_stereo?2:1);i++) { + + + int16_t nibble,diff,step; + + ima_adpcm[i].last_nibble++; + const uint8_t *src_ptr=ima_adpcm[i].ptr; + + + uint8_t nbb = src_ptr[ (ima_adpcm[i].last_nibble>>1) * (is_stereo?2:1) + i ]; + nibble = (ima_adpcm[i].last_nibble&1)?(nbb>>4):(nbb&0xF); + step=_ima_adpcm_step_table[ima_adpcm[i].step_index]; + + + ima_adpcm[i].step_index += _ima_adpcm_index_table[nibble]; + if (ima_adpcm[i].step_index<0) + ima_adpcm[i].step_index=0; + if (ima_adpcm[i].step_index>88) + ima_adpcm[i].step_index=88; + + diff = step >> 3 ; + if (nibble & 1) + diff += step >> 2 ; + if (nibble & 2) + diff += step >> 1 ; + if (nibble & 4) + diff += step ; + if (nibble & 8) + diff = -diff ; + + ima_adpcm[i].predictor+=diff; + if (ima_adpcm[i].predictor<-0x8000) + ima_adpcm[i].predictor=-0x8000; + else if (ima_adpcm[i].predictor>0x7FFF) + ima_adpcm[i].predictor=0x7FFF; + + + /* store loop if there */ + if (ima_adpcm[i].last_nibble==ima_adpcm[i].loop_pos) { + + ima_adpcm[i].loop_step_index = ima_adpcm[i].step_index; + ima_adpcm[i].loop_predictor = ima_adpcm[i].predictor; + } + + //printf("%i - %i - pred %i\n",int(ima_adpcm[i].last_nibble),int(nibble),int(ima_adpcm[i].predictor)); + + } + + } + + final=ima_adpcm[0].predictor; + if (is_stereo) { + final_r=ima_adpcm[1].predictor; + } + + } else { + final=p_src[pos]; + if (is_stereo) + final_r=p_src[pos+1]; + + if (sizeof(Depth)==1) { /* conditions will not exist anymore when compiled! */ + final<<=8; + if (is_stereo) + final_r<<=8; + } + + if (is_stereo) { + + next=p_src[pos+2]; + next_r=p_src[pos+3]; + } else { + next=p_src[pos+1]; + } + + if (sizeof(Depth)==1) { + next<<=8; + if (is_stereo) + next_r<<=8; + } + + int32_t frac=int64_t(offset&MIX_FRAC_MASK); + + final=final+((next-final)*frac >> MIX_FRAC_BITS); + if (is_stereo) + final_r=final_r+((next_r-final_r)*frac >> MIX_FRAC_BITS); + + } + + + if (!is_stereo) { + final_r=final; //copy to right channel if stereo + } + + p_dst->l=final/32767.0; + p_dst->r=final_r/32767.0; + p_dst++; + + offset+=increment; + } +} + +void AudioStreamPlaybackSample::mix(AudioFrame* p_buffer,float p_rate_scale,int p_frames) { + + if (!base->data || !active) { + for(int i=0;i<p_frames;i++) { + p_buffer[i]=AudioFrame(0,0); + } + return; + } + + int len = base->data_bytes; + switch(base->format) { + case AudioStreamSample::FORMAT_8_BITS: len/=1; break; + case AudioStreamSample::FORMAT_16_BITS: len/=2; break; + case AudioStreamSample::FORMAT_IMA_ADPCM: len*=2; break; + } + + if (base->stereo) { + len/=2; + } + + /* some 64-bit fixed point precaches */ + + int64_t loop_begin_fp=((int64_t)len<< MIX_FRAC_BITS); + int64_t loop_end_fp=((int64_t)base->loop_end << MIX_FRAC_BITS); + int64_t length_fp=((int64_t)len << MIX_FRAC_BITS); + int64_t begin_limit=(base->loop_mode!=AudioStreamSample::LOOP_DISABLED)?loop_begin_fp:0; + int64_t end_limit=(base->loop_mode!=AudioStreamSample::LOOP_DISABLED)?loop_end_fp:length_fp; + bool is_stereo=base->stereo; + + int32_t todo=p_frames; + + float base_rate = AudioServer::get_singleton()->get_mix_rate(); + float srate = base->mix_rate; + srate*=p_rate_scale; + float fincrement = srate / base_rate; + int32_t increment = int32_t(fincrement * MIX_FRAC_LEN); + increment*=sign; + + + //looping + + AudioStreamSample::LoopMode loop_format=base->loop_mode; + AudioStreamSample::Format format = base->format; + + + /* audio data */ + + uint8_t *dataptr=(uint8_t*)base->data; + const void *data=dataptr+AudioStreamSample::DATA_PAD; + AudioFrame *dst_buff=p_buffer; + + + if (format==AudioStreamSample::FORMAT_IMA_ADPCM) { + + if (loop_format!=AudioStreamSample::LOOP_DISABLED) { + ima_adpcm[0].loop_pos=loop_begin_fp>>MIX_FRAC_BITS; + ima_adpcm[1].loop_pos=loop_begin_fp>>MIX_FRAC_BITS; + loop_format=AudioStreamSample::LOOP_FORWARD; + } + } + + while (todo>0) { + + int64_t limit=0; + int32_t target=0,aux=0; + + /** LOOP CHECKING **/ + + if ( increment < 0 ) { + /* going backwards */ + + if ( loop_format!=AudioStreamSample::LOOP_DISABLED && offset < loop_begin_fp ) { + /* loopstart reached */ + if ( loop_format==AudioStreamSample::LOOP_PING_PONG ) { + /* bounce ping pong */ + offset= loop_begin_fp + ( loop_begin_fp-offset ); + increment=-increment; + sign*=-1; + } else { + /* go to loop-end */ + offset=loop_end_fp-(loop_begin_fp-offset); + } + } else { + /* check for sample not reaching begining */ + if(offset < 0) { + + active=false; + break; + } + } + } else { + /* going forward */ + if( loop_format!=AudioStreamSample::LOOP_DISABLED && offset >= loop_end_fp ) { + /* loopend reached */ + + if ( loop_format==AudioStreamSample::LOOP_PING_PONG ) { + /* bounce ping pong */ + offset=loop_end_fp-(offset-loop_end_fp); + increment=-increment; + sign*=-1; + } else { + /* go to loop-begin */ + + if (format==AudioStreamSample::FORMAT_IMA_ADPCM) { + for(int i=0;i<2;i++) { + ima_adpcm[i].step_index=ima_adpcm[i].loop_step_index; + ima_adpcm[i].predictor=ima_adpcm[i].loop_predictor; + ima_adpcm[i].last_nibble=loop_begin_fp>>MIX_FRAC_BITS; + } + offset=loop_begin_fp; + } else { + offset=loop_begin_fp+(offset-loop_end_fp); + } + + } + } else { + /* no loop, check for end of sample */ + if(offset >= length_fp) { + + active=false; + break; + } + } + } + + /** MIXCOUNT COMPUTING **/ + + /* next possible limit (looppoints or sample begin/end */ + limit=(increment < 0) ?begin_limit:end_limit; + + /* compute what is shorter, the todo or the limit? */ + aux=(limit-offset)/increment+1; + target=(aux<todo)?aux:todo; /* mix target is the shorter buffer */ + + /* check just in case */ + if ( target<=0 ) { + active=false; + break; + } + + todo-=target; + + switch(base->format) { + case AudioStreamSample::FORMAT_8_BITS: { + + if (is_stereo) + do_resample<int8_t,true,false>((int8_t*)data,dst_buff,offset,increment,target,ima_adpcm); + else + do_resample<int8_t,false,false>((int8_t*)data,dst_buff,offset,increment,target,ima_adpcm); + } break; + case AudioStreamSample::FORMAT_16_BITS: { + if (is_stereo) + do_resample<int16_t,true,false>((int16_t*)data,dst_buff,offset,increment,target,ima_adpcm); + else + do_resample<int16_t,false,false>((int16_t*)data,dst_buff,offset,increment,target,ima_adpcm); + + } break; + case AudioStreamSample::FORMAT_IMA_ADPCM: { + if (is_stereo) + do_resample<int8_t,true,true>((int8_t*)data,dst_buff,offset,increment,target,ima_adpcm); + else + do_resample<int8_t,false,true>((int8_t*)data,dst_buff,offset,increment,target,ima_adpcm); + + } break; + } + + dst_buff+=target; + + } + + +} + +float AudioStreamPlaybackSample::get_length() const { + + int len = base->data_bytes; + switch(base->format) { + case AudioStreamSample::FORMAT_8_BITS: len/=1; break; + case AudioStreamSample::FORMAT_16_BITS: len/=2; break; + case AudioStreamSample::FORMAT_IMA_ADPCM: len*=2; break; + } + + if (base->stereo) { + len/=2; + } + + + return float(len)/base->mix_rate; +} + + +AudioStreamPlaybackSample::AudioStreamPlaybackSample() { + + active=false; + offset=0; + sign=1; +} + + +///////////////////// + + +void AudioStreamSample::set_format(Format p_format) { + + format=p_format; +} + +AudioStreamSample::Format AudioStreamSample::get_format() const{ + + return format; +} + +void AudioStreamSample::set_loop_mode(LoopMode p_loop_mode){ + + loop_mode=p_loop_mode; +} +AudioStreamSample::LoopMode AudioStreamSample::get_loop_mode() const{ + + return loop_mode; +} + +void AudioStreamSample::set_loop_begin(int p_frame){ + + loop_begin=p_frame; +} +int AudioStreamSample::get_loop_begin() const{ + + return loop_begin; +} + +void AudioStreamSample::set_loop_end(int p_frame){ + + loop_end=p_frame; +} +int AudioStreamSample::get_loop_end() const{ + + return loop_end; +} + + +void AudioStreamSample::set_mix_rate(int p_hz){ + + mix_rate=p_hz; +} +int AudioStreamSample::get_mix_rate() const{ + + return mix_rate; +} +void AudioStreamSample::set_stereo(bool p_enable){ + + stereo=p_enable; +} +bool AudioStreamSample::is_stereo() const{ + + return stereo; +} + +void AudioStreamSample::set_data(const PoolVector<uint8_t>& p_data) { + + AudioServer::get_singleton()->lock(); + if (data) { + AudioServer::get_singleton()->audio_data_free(data); + data=NULL; + data_bytes=0; + } + + int datalen = p_data.size(); + if (datalen) { + + PoolVector<uint8_t>::Read r = p_data.read(); + int alloc_len = datalen+DATA_PAD*2; + data = AudioServer::get_singleton()->audio_data_alloc(alloc_len); //alloc with some padding for interpolation + zeromem(data,alloc_len); + uint8_t *dataptr=(uint8_t*)data; + copymem(dataptr+DATA_PAD,r.ptr(),datalen); + data_bytes=datalen; + } + + AudioServer::get_singleton()->unlock(); + +} +PoolVector<uint8_t> AudioStreamSample::get_data() const{ + + PoolVector<uint8_t> pv; + + if (data) { + pv.resize(data_bytes); + { + + PoolVector<uint8_t>::Write w =pv.write(); + copymem(w.ptr(),data,data_bytes); + } + } + + return pv; +} + + +Ref<AudioStreamPlayback> AudioStreamSample::instance_playback() { + + Ref<AudioStreamPlaybackSample> sample; + sample.instance(); + sample->base=Ref<AudioStreamSample>(this); + return sample; +} + +String AudioStreamSample::get_stream_name() const { + + return ""; +} + +void AudioStreamSample::_bind_methods() { + + ClassDB::bind_method(_MD("set_format","format"),&AudioStreamSample::set_format); + ClassDB::bind_method(_MD("get_format"),&AudioStreamSample::get_format); + + ClassDB::bind_method(_MD("set_loop_mode","loop_mode"),&AudioStreamSample::set_loop_mode); + ClassDB::bind_method(_MD("get_loop_mode"),&AudioStreamSample::get_loop_mode); + + ClassDB::bind_method(_MD("set_loop_begin","loop_begin"),&AudioStreamSample::set_loop_begin); + ClassDB::bind_method(_MD("get_loop_begin"),&AudioStreamSample::get_loop_begin); + + ClassDB::bind_method(_MD("set_loop_end","loop_end"),&AudioStreamSample::set_loop_end); + ClassDB::bind_method(_MD("get_loop_end"),&AudioStreamSample::get_loop_end); + + ClassDB::bind_method(_MD("set_mix_rate","mix_rate"),&AudioStreamSample::set_mix_rate); + ClassDB::bind_method(_MD("get_mix_rate"),&AudioStreamSample::get_mix_rate); + + ClassDB::bind_method(_MD("set_stereo","stereo"),&AudioStreamSample::set_stereo); + ClassDB::bind_method(_MD("is_stereo"),&AudioStreamSample::is_stereo); + + ClassDB::bind_method(_MD("set_data","data"),&AudioStreamSample::set_data); + ClassDB::bind_method(_MD("get_data"),&AudioStreamSample::get_data); + + ADD_PROPERTY(PropertyInfo(Variant::INT,"format",PROPERTY_HINT_ENUM,"8-Bit,16-Bit,IMA-ADPCM"),_SCS("set_format"),_SCS("get_format")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"loop_mode",PROPERTY_HINT_ENUM,"Disabled,Forward,Ping-Pong"),_SCS("set_loop_mode"),_SCS("get_loop_mode")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"loop_begin"),_SCS("set_loop_begin"),_SCS("get_loop_begin")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"loop_end"),_SCS("set_loop_end"),_SCS("get_loop_end")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"mix_rate"),_SCS("set_mix_rate"),_SCS("get_mix_rate")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL,"stereo"),_SCS("set_stereo"),_SCS("is_stereo")); + ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY,"data",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("set_data"),_SCS("get_data")); + +} + +AudioStreamSample::AudioStreamSample() +{ + format=FORMAT_8_BITS; + loop_mode=LOOP_DISABLED; + stereo=false; + loop_begin=0; + loop_end=0; + mix_rate=44100; + data=NULL; + data_bytes=0; +} +AudioStreamSample::~AudioStreamSample() { + + + if (data) { + AudioServer::get_singleton()->audio_data_free(data); + data=NULL; + data_bytes=0; + } +} diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h new file mode 100644 index 0000000000..8c1e74608b --- /dev/null +++ b/scene/resources/audio_stream_sample.h @@ -0,0 +1,128 @@ +#ifndef AUDIOSTREAMSAMPLE_H +#define AUDIOSTREAMSAMPLE_H + +#include "servers/audio/audio_stream.h" + + +class AudioStreamSample; + +class AudioStreamPlaybackSample : public AudioStreamPlayback { + + GDCLASS( AudioStreamPlaybackSample, AudioStreamPlayback ) + enum { + MIX_FRAC_BITS=13, + MIX_FRAC_LEN=(1<<MIX_FRAC_BITS), + MIX_FRAC_MASK=MIX_FRAC_LEN-1, + }; + + struct IMA_ADPCM_State { + + int16_t step_index; + int32_t predictor; + /* values at loop point */ + int16_t loop_step_index; + int32_t loop_predictor; + int32_t last_nibble; + int32_t loop_pos; + int32_t window_ofs; + const uint8_t *ptr; + } ima_adpcm[2]; + + int64_t offset; + int sign; + bool active; +friend class AudioStreamSample; + Ref<AudioStreamSample> base; + + template<class Depth,bool is_stereo,bool is_ima_adpcm> + void do_resample(const Depth* p_src, AudioFrame *p_dst,int64_t &offset,int32_t &increment,uint32_t amount,IMA_ADPCM_State *ima_adpcm); +public: + + virtual void start(float p_from_pos=0.0); + virtual void stop(); + virtual bool is_playing() const; + + virtual int get_loop_count() const; //times it looped + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + virtual void mix(AudioFrame* p_buffer,float p_rate_scale,int p_frames); + + virtual float get_length() const; //if supported, otherwise return 0 + + + AudioStreamPlaybackSample(); +}; + +class AudioStreamSample : public AudioStream { + GDCLASS(AudioStreamSample,AudioStream) + RES_BASE_EXTENSION("smp") + +public: + + enum Format { + FORMAT_8_BITS, + FORMAT_16_BITS, + FORMAT_IMA_ADPCM + }; + + enum LoopMode { + LOOP_DISABLED, + LOOP_FORWARD, + LOOP_PING_PONG + }; + + +private: +friend class AudioStreamPlaybackSample; + + enum { + DATA_PAD=16 //padding for interpolation + }; + + Format format; + LoopMode loop_mode; + bool stereo; + int loop_begin; + int loop_end; + int mix_rate; + void *data; + uint32_t data_bytes; +protected: + + static void _bind_methods(); +public: + void set_format(Format p_format); + Format get_format() const; + + void set_loop_mode(LoopMode p_loop_mode); + LoopMode get_loop_mode() const; + + void set_loop_begin(int p_frame); + int get_loop_begin() const; + + void set_loop_end(int p_frame); + int get_loop_end() const; + + void set_mix_rate(int p_hz); + int get_mix_rate() const; + + void set_stereo(bool p_enable); + bool is_stereo() const; + + void set_data(const PoolVector<uint8_t>& p_data); + PoolVector<uint8_t> get_data() const; + + + virtual Ref<AudioStreamPlayback> instance_playback(); + virtual String get_stream_name() const; + + AudioStreamSample(); + ~AudioStreamSample(); +}; + +VARIANT_ENUM_CAST(AudioStreamSample::Format) +VARIANT_ENUM_CAST(AudioStreamSample::LoopMode) + +#endif // AUDIOSTREAMSample_H diff --git a/tools/editor/editor_node.cpp b/tools/editor/editor_node.cpp index a51507e96f..1c57d374b2 100644 --- a/tools/editor/editor_node.cpp +++ b/tools/editor/editor_node.cpp @@ -101,6 +101,7 @@ #include "plugins/gi_probe_editor_plugin.h" #include "import/resource_import_texture.h" #include "import/resource_importer_csv_translation.h" +#include "import/resource_import_wav.h" // end #include "editor_settings.h" #include "io_plugins/editor_texture_import_plugin.h" @@ -5126,6 +5127,10 @@ EditorNode::EditorNode() { import_csv_translation.instance(); ResourceFormatImporter::get_singleton()->add_importer(import_csv_translation); + Ref<ResourceImporterWAV> import_wav; + import_wav.instance(); + ResourceFormatImporter::get_singleton()->add_importer(import_wav); + } _pvrtc_register_compressors(); diff --git a/tools/editor/import/resource_import_wav.cpp b/tools/editor/import/resource_import_wav.cpp new file mode 100644 index 0000000000..c277bd3b6c --- /dev/null +++ b/tools/editor/import/resource_import_wav.cpp @@ -0,0 +1,619 @@ +#include "resource_import_wav.h" + +#include "scene/resources/audio_stream_sample.h" +#include "os/file_access.h" +#include "io/marshalls.h" +#include "io/resource_saver.h" + +String ResourceImporterWAV::get_importer_name() const { + + return "wav"; +} + +String ResourceImporterWAV::get_visible_name() const{ + + return "Microsoft WAV"; +} +void ResourceImporterWAV::get_recognized_extensions(List<String> *p_extensions) const{ + + p_extensions->push_back("wav"); +} +String ResourceImporterWAV::get_save_extension() const { + return "smp"; +} + +String ResourceImporterWAV::get_resource_type() const{ + + return "AudioStreamSample"; +} + +bool ResourceImporterWAV::get_option_visibility(const String& p_option,const Map<StringName,Variant>& p_options) const { + + return true; +} + +int ResourceImporterWAV::get_preset_count() const { + return 0; +} +String ResourceImporterWAV::get_preset_name(int p_idx) const { + + return String(); +} + + +void ResourceImporterWAV::get_import_options(List<ImportOption> *r_options,int p_preset) const { + + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL,"force/8_bit"),false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL,"force/mono"),false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL,"force/max_rate"),false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::REAL,"force/max_rate_hz",PROPERTY_HINT_EXP_RANGE,"11025,192000,1"),44100)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL,"edit/trim"),true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL,"edit/normalize"),true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL,"edit/loop"),false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT,"compress/mode",PROPERTY_HINT_ENUM,"Disabled,RAM (Ima-ADPCM)"),0)); + +} + + +Error ResourceImporterWAV::import(const String& p_source_file, const String& p_save_path, const Map<StringName,Variant>& p_options, List<String>* r_platform_variants, List<String> *r_gen_files) { + + /* STEP 1, READ WAVE FILE */ + + Error err; + FileAccess *file=FileAccess::open(p_source_file, FileAccess::READ,&err); + + ERR_FAIL_COND_V( err!=OK, ERR_CANT_OPEN ); + + /* 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( ERR_FILE_UNRECOGNIZED ); + } + + + /* 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( ERR_FILE_UNRECOGNIZED ); + } + + int format_bits=0; + int format_channels=0; + + AudioStreamSample::LoopMode loop=AudioStreamSample::LOOP_DISABLED; + bool format_found=false; + bool data_found=false; + int format_freq=0; + int loop_begin=0; + int loop_end=0; + int frames; + + 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(); + 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 */ + + uint16_t compression_code=file->get_16(); + + + if (compression_code!=1) { + 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; + + } + + 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)); +*/ + + int len=frames; + if (format_channels==2) + len*=2; + if (format_bits>8) + len*=2; + + + data.resize(frames*format_channels); + + for (int i=0;i<frames;i++) { + + + for (int c=0;c<format_channels;c++) { + + + if (format_bits==8) { + // 8 bit samples are UNSIGNED + + uint8_t s = file->get_8(); + s-=128; + int8_t *sp=(int8_t*)&s; + + data[i*format_channels+c]=float(*sp)/128.0; + + } else { + //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); + int32_t ss=s; + + + data[i*format_channels+c]=(ss>>16)/32768.0; + } + } + + } + + + + if (file->eof_reached()) { + file->close(); + memdelete(file); + ERR_EXPLAIN("Premature end of file."); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + } + + 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()?AudioStreamSample::LOOP_PING_PONG:AudioStreamSample::LOOP_FORWARD; + loop_begin=file->get_32(); + loop_end=file->get_32(); + + } + file->seek( file_pos+chunksize ); + } + + file->close(); + memdelete(file); + + // STEP 2, APPLY CONVERSIONS + + + bool is16=format_bits!=8; + int rate=format_freq; + + print_line("Input Sample: "); + print_line("\tframes: "+itos(frames)); + print_line("\tformat_channels: "+itos(format_channels)); + print_line("\t16bits: "+itos(is16)); + print_line("\trate: "+itos(rate)); + print_line("\tloop: "+itos(loop)); + print_line("\tloop begin: "+itos(loop_begin)); + print_line("\tloop end: "+itos(loop_end)); + + + //apply frequency limit + + bool limit_rate = p_options["force/max_rate"]; + int limit_rate_hz = p_options["force/max_rate_hz"]; + if (limit_rate && rate > limit_rate_hz) { + //resampleeee!!! + int new_data_frames = frames * limit_rate_hz / rate; + Vector<float> new_data; + new_data.resize( new_data_frames * format_channels ); + for(int c=0;c<format_channels;c++) { + + for(int i=0;i<new_data_frames;i++) { + + //simple cubic interpolation should be enough. + float pos = float(i) * frames / new_data_frames; + float mu = pos-Math::floor(pos); + int ipos = int(Math::floor(pos)); + + 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]; + + float mu2 = mu*mu; + float a0 = y3 - y2 - y0 + y1; + float a1 = y0 - y1 - a0; + float a2 = y2 - y0; + float a3 = y1; + + float res=(a0*mu*mu2+a1*mu2+a2*mu+a3); + + new_data[i*format_channels+c]=res; + } + } + + if (loop) { + + loop_begin=loop_begin*new_data_frames/frames; + loop_end=loop_end*new_data_frames/frames; + } + 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]); + if (amp>max) + max=amp; + } + + if (max>0) { + + float mult=1.0/max; + for(int i=0;i<data.size();i++) { + + data[i]*=mult; + } + + } + } + + bool trim = p_options["edit/trim"]; + + if (trim && !loop) { + + int first=0; + int last=(frames*format_channels)-1; + bool found=false; + float limit = Math::db2linear((float)-30); + for(int i=0;i<data.size();i++) { + float amp = Math::abs(data[i]); + + if (!found && amp > limit) { + first=i; + found=true; + } + + if (found && amp > limit) { + last=i; + } + } + + first/=format_channels; + last/=format_channels; + + if (first<last) { + + Vector<float> new_data; + new_data.resize((last-first+1)*format_channels); + for(int i=first*format_channels;i<=last*format_channels;i++) { + new_data[i-first*format_channels]=data[i]; + } + + data=new_data; + frames=data.size()/format_channels; + } + + } + + bool make_loop = p_options["edit/loop"]; + + if (make_loop && !loop) { + + loop=AudioStreamSample::LOOP_FORWARD; + loop_begin=0; + loop_end=frames; + } + + 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++) { + new_data[i]=(data[i*2+0]+data[i*2+1])/2.0; + } + + data=new_data; + format_channels=1; + } + + bool force_8_bit = p_options["force/8_bit"]; + if (force_8_bit) { + + is16=false; + } + + + PoolVector<uint8_t> dst_data; + AudioStreamSample::Format dst_format; + + if ( compression == 1) { + + dst_format=AudioStreamSample::FORMAT_IMA_ADPCM; + if (format_channels==1) { + _compress_ima_adpcm(data,dst_data); + } 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++) { + left[i]=data[i*2+0]; + right[i]=data[i*2+1]; + } + + PoolVector<uint8_t> bleft; + PoolVector<uint8_t> bright; + + _compress_ima_adpcm(left,bleft); + _compress_ima_adpcm(right,bright); + + int dl = bleft.size(); + dst_data.resize( dl *2 ); + + PoolVector<uint8_t>::Write w=dst_data.write(); + PoolVector<uint8_t>::Read rl=bleft.read(); + PoolVector<uint8_t>::Read rr=bright.read(); + + for(int i=0;i<dl;i++) { + w[i*2+0]=rl[i]; + w[i*2+1]=rr[i]; + } + } + + //print_line("compressing ima-adpcm, resulting buffersize is "+itos(dst_data.size())+" from "+itos(data.size())); + + } else { + + dst_format=is16?AudioStreamSample::FORMAT_16_BITS:AudioStreamSample::FORMAT_8_BITS; + dst_data.resize( data.size() * (is16?2:1)); + { + PoolVector<uint8_t>::Write w = dst_data.write(); + + int ds=data.size(); + for(int i=0;i<ds;i++) { + + if (is16) { + 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; + } + } + } + } + + + Ref<AudioStreamSample> sample; + sample.instance(); + sample->set_data(dst_data); + sample->set_format(dst_format); + sample->set_mix_rate(rate); + sample->set_loop_mode(loop); + sample->set_loop_begin(loop_begin); + sample->set_loop_end(loop_end); + sample->set_stereo(format_channels==2); + + ResourceSaver::save(p_save_path+".smp",sample); + + + return OK; + +} + +void ResourceImporterWAV::_compress_ima_adpcm(const Vector<float>& p_data,PoolVector<uint8_t>& dst_data) { + + + /*p_sample_data->data = (void*)malloc(len); + xm_s8 *dataptr=(xm_s8*)p_sample_data->data;*/ + + static const int16_t _ima_adpcm_step_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + static const int8_t _ima_adpcm_index_table[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + + int datalen = p_data.size(); + int datamax=datalen; + if (datalen&1) + datalen++; + + dst_data.resize(datalen/2+4); + PoolVector<uint8_t>::Write w = dst_data.write(); + + + int i,step_idx=0,prev=0; + uint8_t *out = w.ptr(); + //int16_t xm_prev=0; + const float *in=p_data.ptr(); + + + /* initial value is zero */ + *(out++) =0; + *(out++) =0; + /* Table index initial value */ + *(out++) =0; + /* unused */ + *(out++) =0; + + for (i=0;i<datalen;i++) { + int step,diff,vpdiff,mask; + uint8_t nibble; + int16_t xm_sample; + + if (i>=datamax) + xm_sample=0; + else { + + + xm_sample=CLAMP(in[i]*32767.0,-32768,32767); + /* + if (xm_sample==32767 || xm_sample==-32768) + printf("clippy!\n",xm_sample); + */ + } + + //xm_sample=xm_sample+xm_prev; + //xm_prev=xm_sample; + + diff = (int)xm_sample - prev ; + + nibble=0 ; + step = _ima_adpcm_step_table[ step_idx ]; + vpdiff = step >> 3 ; + if (diff < 0) { + nibble=8; + diff=-diff ; + } + mask = 4 ; + while (mask) { + + if (diff >= step) { + + nibble |= mask; + diff -= step; + vpdiff += step; + } + + step >>= 1 ; + mask >>= 1 ; + }; + + if (nibble&8) + prev-=vpdiff ; + else + prev+=vpdiff ; + + if (prev > 32767) { + //printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip up %i\n",i,xm_sample,prev,diff,vpdiff,prev); + prev=32767; + } else if (prev < -32768) { + //printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip down %i\n",i,xm_sample,prev,diff,vpdiff,prev); + prev = -32768 ; + } + + step_idx += _ima_adpcm_index_table[nibble]; + if (step_idx< 0) + step_idx= 0 ; + else if (step_idx> 88) + step_idx= 88 ; + + + if (i&1) { + *out|=nibble<<4; + out++; + } else { + *out=nibble; + } + /*dataptr[i]=prev>>8;*/ + } + + + + +} + +ResourceImporterWAV::ResourceImporterWAV() +{ + +} diff --git a/tools/editor/import/resource_import_wav.h b/tools/editor/import/resource_import_wav.h new file mode 100644 index 0000000000..9f1bd57da7 --- /dev/null +++ b/tools/editor/import/resource_import_wav.h @@ -0,0 +1,30 @@ +#ifndef RESOURCEIMPORTWAV_H +#define RESOURCEIMPORTWAV_H + + +#include "io/resource_import.h" + +class ResourceImporterWAV : public ResourceImporter { + GDCLASS(ResourceImporterWAV,ResourceImporter) +public: + virtual String get_importer_name() const; + virtual String get_visible_name() const; + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual String get_save_extension() const; + virtual String get_resource_type() const; + + + virtual int get_preset_count() const; + virtual String get_preset_name(int p_idx) const; + + virtual void get_import_options(List<ImportOption> *r_options,int p_preset=0) const; + virtual bool get_option_visibility(const String& p_option,const Map<StringName,Variant>& p_options) const; + + void _compress_ima_adpcm(const Vector<float>& p_data,PoolVector<uint8_t>& dst_data); + + virtual Error import(const String& p_source_file,const String& p_save_path,const Map<StringName,Variant>& p_options,List<String>* r_platform_variants,List<String>* r_gen_files=NULL); + + ResourceImporterWAV(); +}; + +#endif // RESOURCEIMPORTWAV_H |