From 8d13d962f26a4c6fb597e7481c60f59034345583 Mon Sep 17 00:00:00 2001 From: cheeseburger Date: Sun, 28 Jan 2018 23:14:01 -0800 Subject: [PATCH 1/2] Added an inital guide on how to create an audiostream. change creating to create --- development/cpp/custom_audiostreams.rst | 345 ++++++++++++++++++++++++ development/cpp/index.rst | 1 + 2 files changed, 346 insertions(+) create mode 100644 development/cpp/custom_audiostreams.rst diff --git a/development/cpp/custom_audiostreams.rst b/development/cpp/custom_audiostreams.rst new file mode 100644 index 000000000..3b486ca00 --- /dev/null +++ b/development/cpp/custom_audiostreams.rst @@ -0,0 +1,345 @@ +.. _custom_audiostreams: + +Custom AudioStreams +=================== + +Introduction +------------ + +AudioStream is the base class of all audio emitting objects. +AudioStreamPlayer binds onto an AudioStream to emit pcm data +into an AudioServer which manages audio drivers. + +All audio resources requires two audio based classes; AudioStream +and AudioStreamPlayback. As a data container, AudioStream contains +the resource and exposes itself to GDScript. AudioStream references +its own internal custom AudioStreamPlayback which translate +AudioStream into PCM data. + +This doc assumes the reader knows how to create C++ Modules. If not, refer to this guide +:ref:`doc_custom_modules_in_c++`. + +References: +~~~~~~~~~~~ + +- `servers/audio/audio_stream.h `__ +- `scene/audio/audioplayer.cpp `__ + + + +What for? +--------- + +- Binding external libraries (like Wwise, FMOD, etc). +- Adding custom audio queues +- Adding support for more audio formats + +Create an AudioStream +--------------------- + + +An AudioStream consists of three components; data container, stream name, +and an AudioStreamPlayback friend class generator. Audio data can be +loaded a number of ways such as an internal counter for a tone generator, +internal/external buffer, or a file reference. + + +Some AudioStreams need to be stateless such as objects loaded from +ResourceLoader. ResourceLoader loads once and references the same +object regardless how many times ``load`` is called on a specific resource. +Therefore, playback state must be self contained in AudioStreamPlayback. + + +.. code:: cpp + + /* audiostream_mytone.h */ + + #include "reference.h" + #include "resource.h" + #include "servers/audio/audio_stream.h + + class AudioStreamMyTone : public AudioStream { + GDCLASS(AudioStreamMyTone, AudioStream) + private: + friend class AudioStreamPlaybackMyTone; + uint64_t pos; + int mix_rate; + bool stereo; + int hz; + public: + void reset(); + void set_position(uint64_t pos); + virtual Ref instance_playback(); + virtual String get_stream_name() const; + void gen_tone(int16_t *, int frames); + AudioStreamMyTone(); + + protected: + static void _bind_methods(); + }; + + +.. code:: cpp + + /* audiostream_mytone.cpp */ + AudioStreamMyTone::AudioStreamMyTone() + : mix_rate(44100), stereo(false), hz(639) { + } + + Ref AudioStreamMyTone::instance_playback(){ + Ref talking_tree; + talking_tree.instance(); + talking_tree->base = Ref(this); + return talking_tree; + } + + String AudioStreamMyTone::get_stream_name() const { + return "MyTone"; + } + void AudioStreamMyTone::reset() { + set_position(0); + } + void AudioStreamMyTone::set_position(uint64_t p) { + pos = p; + } + void AudioStreamMyTone::gen_tone(int16_t * pcm_buf, int size){ + for( int i = 0; i < size; i++){ + pcm_buf[i] = 32767.0 * sin(2.0*Math_PI*double(pos+i)/(double(mix_rate)/double(hz))); + } + pos += size; + } + void AudioStreamMyTone::_bind_methods(){ + ClassDB::bind_method(D_METHOD("reset"), &AudioStreamMyTone::reset); + ClassDB::bind_method(D_METHOD("get_stream_name"), &AudioStreamMyTone::get_stream_name); + + } + +References: +~~~~~~~~~~~ + +- `servers/audio/audio_stream.h `__ + + +Create an AudioStreamPlayback +----------------------------- + +AudioStreamPlayer uses ``mix`` callback to obtain pcm data. The callback must match sample rate and fill the buffer. + + +.. code:: cpp + + /* audiostreamplayer_mytone.h */ + #include "reference.h" + #include "resource.h" + #include "servers/audio/audio_stream.h" + + + class AudioStreamPlaybackMyTone : public AudioStreamPlayback { + GDCLASS(AudioStreamPlaybackMyTone, AudioStreamPlayback) + friend class AudioStreamMyTone; + private: + enum{ + PCM_BUFFER_SIZE = 4096 + }; + enum { + MIX_FRAC_BITS = 13, + MIX_FRAC_LEN = (1 << MIX_FRAC_BITS), + MIX_FRAC_MASK = MIX_FRAC_LEN - 1, + }; + void * pcm_buffer; + Ref base; + bool active; + 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_playback_position() const; + virtual void seek(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 + AudioStreamPlaybackMyTone(); + + }; + + + +.. code:: cpp + + /* audiostreamplayer_mytone.cpp */ + #include "audiostreamplayer_mytone.h" + #include "math/math_funcs.h" + #include "print_string.h" + + AudioStreamPlaybackMyTone::AudioStreamPlaybackMyTone() + : active(false){ + AudioServer::get_singleton()->lock(); + pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE); + zeromem(pcm_buffer, PCM_BUFFER_SIZE); + AudioServer::get_singleton()->unlock(); + } + void AudioStreamPlaybackMyTone::stop(){ + active = false; + base->reset(); + } + + void AudioStreamPlaybackMyTone::start(float p_from_pos){ + seek(p_from_pos); + active = true; + } + void AudioStreamPlaybackMyTone::seek(float p_time){ + float max = get_length(); + if (p_time < 0) { + p_time = 0; + } + base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS); + } + void AudioStreamPlaybackMyTone::mix(AudioFrame *p_buffer, float p_rate, int p_frames){ + ERR_FAIL_COND(!active); + if (!active) { + return; + } + zeromem(pcm_buffer, PCM_BUFFER_SIZE); + int16_t * buf = (int16_t * )pcm_buffer; + base->gen_tone(buf, p_frames); + + for(int i = 0; i < p_frames; i++){ + float sample = float(buf[i])/32767.0; + p_buffer[i] = AudioFrame(sample, sample); + } + } + int AudioStreamPlaybackMyTone::get_loop_count() const { + return 0; + } + float AudioStreamPlaybackMyTone::get_playback_position() const { + return 0.0; + } + float AudioStreamPlaybackMyTone::get_length() const { + return 0.0; + } + bool AudioStreamPlaybackMyTone::is_playing() const { + return active; + } + + +Resampling +~~~~~~~~~~ + + +Godot’s AudioServer currently uses 44100 Hz sample rate. When other sample rates are +needed such as 48000, either provide one or use AudioStreamPlaybackResampled. +Godot provides cubic interpolation for audio resampling. + +Instead of overloading ``mix``, AudioStreamPlaybackResampled uses ``_mix_internal`` to +query AudioFrames and ``get_stream_sampling_rate`` to query current mix rate. + +.. code:: cpp + + #include "reference.h" + #include "resource.h" + + #include "servers/audio/audio_stream.h" + + class AudioStreamMyToneResampled; + + class AudioStreamPlaybackResampledMyTone : public AudioStreamPlaybackResampled { + GDCLASS(AudioStreamPlaybackResampledMyTone, AudioStreamPlaybackResampled) + friend class AudioStreamMyToneResampled; + private: + enum{ + PCM_BUFFER_SIZE = 4096 + }; + enum { + MIX_FRAC_BITS = 13, + MIX_FRAC_LEN = (1 << MIX_FRAC_BITS), + MIX_FRAC_MASK = MIX_FRAC_LEN - 1, + }; + void * pcm_buffer; + Ref base; + bool active; + protected: + virtual void _mix_internal(AudioFrame *p_buffer, int p_frames); + 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_playback_position() const; + virtual void seek(float p_time); + virtual float get_length() const; //if supported, otherwise return 0 + virtual float get_stream_sampling_rate(); + AudioStreamPlaybackResampledMyTone(); + + }; + + + +.. code:: cpp + + #include "mytone_audiostream_resampled.h" + #include "math/math_funcs.h" + #include "print_string.h" + + AudioStreamPlaybackResampledMyTone::AudioStreamPlaybackResampledMyTone() + : active(false){ + AudioServer::get_singleton()->lock(); + pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE); + zeromem(pcm_buffer, PCM_BUFFER_SIZE); + AudioServer::get_singleton()->unlock(); + } + void AudioStreamPlaybackResampledMyTone::stop(){ + active = false; + base->reset(); + } + + void AudioStreamPlaybackResampledMyTone::start(float p_from_pos){ + seek(p_from_pos); + active = true; + } + void AudioStreamPlaybackResampledMyTone::seek(float p_time){ + float max = get_length(); + if (p_time < 0) { + p_time = 0; + } + base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS); + } + void AudioStreamPlaybackResampledMyTone::_mix_internal(AudioFrame *p_buffer, int p_frames){ + ERR_FAIL_COND(!active); + if (!active) { + return; + } + zeromem(pcm_buffer, PCM_BUFFER_SIZE); + int16_t * buf = (int16_t * )pcm_buffer; + base->gen_tone(buf, p_frames); + + for(int i = 0; i < p_frames; i++){ + float sample = float(buf[i])/32767.0; + p_buffer[i] = AudioFrame(sample, sample); + } + } + float AudioStreamPlaybackResampledMyTone::get_stream_sampling_rate(){ + return float(base->mix_rate); + } + int AudioStreamPlaybackResampledMyTone::get_loop_count() const { + return 0; + } + float AudioStreamPlaybackResampledMyTone::get_playback_position() const { + return 0.0; + } + float AudioStreamPlaybackResampledMyTone::get_length() const { + return 0.0; + } + bool AudioStreamPlaybackResampledMyTone::is_playing() const { + return active; + } + + + + +References: +~~~~~~~~~~~ +- `core/math/audio_frame.h `__ +- `servers/audio/audio_stream.h `__ +- `scene/audio/audioplayer.cpp `__ + + diff --git a/development/cpp/index.rst b/development/cpp/index.rst index 256105926..5999e44de 100644 --- a/development/cpp/index.rst +++ b/development/cpp/index.rst @@ -12,4 +12,5 @@ Engine development object_class inheritance_class_tree custom_modules_in_cpp + custom_audiostreams creating_android_modules From 8e5be124df6e3498b366b1d7ee20c0addc652537 Mon Sep 17 00:00:00 2001 From: cheeseburger Date: Wed, 7 Feb 2018 07:25:11 -0800 Subject: [PATCH 2/2] added mhilbrunner suggested changes fix audiostream change ; to : for list of items change doc to this guide --- development/cpp/custom_audiostreams.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/development/cpp/custom_audiostreams.rst b/development/cpp/custom_audiostreams.rst index 3b486ca00..3b770f86f 100644 --- a/development/cpp/custom_audiostreams.rst +++ b/development/cpp/custom_audiostreams.rst @@ -7,16 +7,16 @@ Introduction ------------ AudioStream is the base class of all audio emitting objects. -AudioStreamPlayer binds onto an AudioStream to emit pcm data +AudioStreamPlayer binds onto an AudioStream to emit PCM data into an AudioServer which manages audio drivers. -All audio resources requires two audio based classes; AudioStream +All audio resources require two audio based classes: AudioStream and AudioStreamPlayback. As a data container, AudioStream contains the resource and exposes itself to GDScript. AudioStream references -its own internal custom AudioStreamPlayback which translate +its own internal custom AudioStreamPlayback which translates AudioStream into PCM data. -This doc assumes the reader knows how to create C++ Modules. If not, refer to this guide +This guide assumes the reader knows how to create C++ modules. If not, refer to this guide :ref:`doc_custom_modules_in_c++`. References: @@ -38,9 +38,9 @@ Create an AudioStream --------------------- -An AudioStream consists of three components; data container, stream name, +An AudioStream consists of three components: data container, stream name, and an AudioStreamPlayback friend class generator. Audio data can be -loaded a number of ways such as an internal counter for a tone generator, +loaded in a number of ways such as with an internal counter for a tone generator, internal/external buffer, or a file reference. @@ -72,6 +72,7 @@ Therefore, playback state must be self contained in AudioStreamPlayback. virtual Ref instance_playback(); virtual String get_stream_name() const; void gen_tone(int16_t *, int frames); + virtual float get_length() const { return 0; } //if supported, otherwise return 0 AudioStreamMyTone(); protected: @@ -123,8 +124,9 @@ References: Create an AudioStreamPlayback ----------------------------- -AudioStreamPlayer uses ``mix`` callback to obtain pcm data. The callback must match sample rate and fill the buffer. +AudioStreamPlayer uses ``mix`` callback to obtain PCM data. The callback must match sample rate and fill the buffer. +Since AudioStreamPlayback is controlled by the audio thread, i/o and dynamic memory allocation are forbidden. .. code:: cpp