mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-04 14:11:02 +03:00
Added an inital guide on how to create an audiostream.
change creating to create
This commit is contained in:
345
development/cpp/custom_audiostreams.rst
Normal file
345
development/cpp/custom_audiostreams.rst
Normal file
@@ -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 <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
|
||||
- `scene/audio/audioplayer.cpp <https://github.com/godotengine/godot/blob/master/scene/audio/audio_player.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<AudioStreamPlayback> 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<AudioStreamPlayback> AudioStreamMyTone::instance_playback(){
|
||||
Ref<AudioStreamPlaybackMyTone> talking_tree;
|
||||
talking_tree.instance();
|
||||
talking_tree->base = Ref<AudioStreamMyTone>(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 <https://github.com/godotengine/godot/blob/master/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<AudioStreamMyTone> 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<AudioStreamMyToneResampled> 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 <https://github.com/godotengine/godot/blob/master/core/math/audio_frame.h>`__
|
||||
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
|
||||
- `scene/audio/audioplayer.cpp <https://github.com/godotengine/godot/blob/master/scene/audio/audio_player.cpp>`__
|
||||
|
||||
|
||||
@@ -12,4 +12,5 @@ Engine development
|
||||
object_class
|
||||
inheritance_class_tree
|
||||
custom_modules_in_cpp
|
||||
custom_audiostreams
|
||||
creating_android_modules
|
||||
|
||||
Reference in New Issue
Block a user