Skip to content

Commit cee560f

Browse files
authored
Merge pull request godotengine#1047 from hungrymonkey/add_audiostream
Created an inital guide on how to make audiostreams
2 parents b7eb065 + 8e5be12 commit cee560f

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed
+347
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
.. _custom_audiostreams:
2+
3+
Custom AudioStreams
4+
===================
5+
6+
Introduction
7+
------------
8+
9+
AudioStream is the base class of all audio emitting objects.
10+
AudioStreamPlayer binds onto an AudioStream to emit PCM data
11+
into an AudioServer which manages audio drivers.
12+
13+
All audio resources require two audio based classes: AudioStream
14+
and AudioStreamPlayback. As a data container, AudioStream contains
15+
the resource and exposes itself to GDScript. AudioStream references
16+
its own internal custom AudioStreamPlayback which translates
17+
AudioStream into PCM data.
18+
19+
This guide assumes the reader knows how to create C++ modules. If not, refer to this guide
20+
:ref:`doc_custom_modules_in_c++`.
21+
22+
References:
23+
~~~~~~~~~~~
24+
25+
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
26+
- `scene/audio/audioplayer.cpp <https://github.com/godotengine/godot/blob/master/scene/audio/audio_player.cpp>`__
27+
28+
29+
30+
What for?
31+
---------
32+
33+
- Binding external libraries (like Wwise, FMOD, etc).
34+
- Adding custom audio queues
35+
- Adding support for more audio formats
36+
37+
Create an AudioStream
38+
---------------------
39+
40+
41+
An AudioStream consists of three components: data container, stream name,
42+
and an AudioStreamPlayback friend class generator. Audio data can be
43+
loaded in a number of ways such as with an internal counter for a tone generator,
44+
internal/external buffer, or a file reference.
45+
46+
47+
Some AudioStreams need to be stateless such as objects loaded from
48+
ResourceLoader. ResourceLoader loads once and references the same
49+
object regardless how many times ``load`` is called on a specific resource.
50+
Therefore, playback state must be self contained in AudioStreamPlayback.
51+
52+
53+
.. code:: cpp
54+
55+
/* audiostream_mytone.h */
56+
57+
#include "reference.h"
58+
#include "resource.h"
59+
#include "servers/audio/audio_stream.h
60+
61+
class AudioStreamMyTone : public AudioStream {
62+
GDCLASS(AudioStreamMyTone, AudioStream)
63+
private:
64+
friend class AudioStreamPlaybackMyTone;
65+
uint64_t pos;
66+
int mix_rate;
67+
bool stereo;
68+
int hz;
69+
public:
70+
void reset();
71+
void set_position(uint64_t pos);
72+
virtual Ref<AudioStreamPlayback> instance_playback();
73+
virtual String get_stream_name() const;
74+
void gen_tone(int16_t *, int frames);
75+
virtual float get_length() const { return 0; } //if supported, otherwise return 0
76+
AudioStreamMyTone();
77+
78+
protected:
79+
static void _bind_methods();
80+
};
81+
82+
83+
.. code:: cpp
84+
85+
/* audiostream_mytone.cpp */
86+
AudioStreamMyTone::AudioStreamMyTone()
87+
: mix_rate(44100), stereo(false), hz(639) {
88+
}
89+
90+
Ref<AudioStreamPlayback> AudioStreamMyTone::instance_playback(){
91+
Ref<AudioStreamPlaybackMyTone> talking_tree;
92+
talking_tree.instance();
93+
talking_tree->base = Ref<AudioStreamMyTone>(this);
94+
return talking_tree;
95+
}
96+
97+
String AudioStreamMyTone::get_stream_name() const {
98+
return "MyTone";
99+
}
100+
void AudioStreamMyTone::reset() {
101+
set_position(0);
102+
}
103+
void AudioStreamMyTone::set_position(uint64_t p) {
104+
pos = p;
105+
}
106+
void AudioStreamMyTone::gen_tone(int16_t * pcm_buf, int size){
107+
for( int i = 0; i < size; i++){
108+
pcm_buf[i] = 32767.0 * sin(2.0*Math_PI*double(pos+i)/(double(mix_rate)/double(hz)));
109+
}
110+
pos += size;
111+
}
112+
void AudioStreamMyTone::_bind_methods(){
113+
ClassDB::bind_method(D_METHOD("reset"), &AudioStreamMyTone::reset);
114+
ClassDB::bind_method(D_METHOD("get_stream_name"), &AudioStreamMyTone::get_stream_name);
115+
116+
}
117+
118+
References:
119+
~~~~~~~~~~~
120+
121+
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
122+
123+
124+
Create an AudioStreamPlayback
125+
-----------------------------
126+
127+
AudioStreamPlayer uses ``mix`` callback to obtain PCM data. The callback must match sample rate and fill the buffer.
128+
129+
Since AudioStreamPlayback is controlled by the audio thread, i/o and dynamic memory allocation are forbidden.
130+
131+
.. code:: cpp
132+
133+
/* audiostreamplayer_mytone.h */
134+
#include "reference.h"
135+
#include "resource.h"
136+
#include "servers/audio/audio_stream.h"
137+
138+
139+
class AudioStreamPlaybackMyTone : public AudioStreamPlayback {
140+
GDCLASS(AudioStreamPlaybackMyTone, AudioStreamPlayback)
141+
friend class AudioStreamMyTone;
142+
private:
143+
enum{
144+
PCM_BUFFER_SIZE = 4096
145+
};
146+
enum {
147+
MIX_FRAC_BITS = 13,
148+
MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
149+
MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
150+
};
151+
void * pcm_buffer;
152+
Ref<AudioStreamMyTone> base;
153+
bool active;
154+
public:
155+
virtual void start(float p_from_pos = 0.0);
156+
virtual void stop();
157+
virtual bool is_playing() const;
158+
virtual int get_loop_count() const; //times it looped
159+
virtual float get_playback_position() const;
160+
virtual void seek(float p_time);
161+
virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
162+
virtual float get_length() const; //if supported, otherwise return 0
163+
AudioStreamPlaybackMyTone();
164+
165+
};
166+
167+
168+
169+
.. code:: cpp
170+
171+
/* audiostreamplayer_mytone.cpp */
172+
#include "audiostreamplayer_mytone.h"
173+
#include "math/math_funcs.h"
174+
#include "print_string.h"
175+
176+
AudioStreamPlaybackMyTone::AudioStreamPlaybackMyTone()
177+
: active(false){
178+
AudioServer::get_singleton()->lock();
179+
pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
180+
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
181+
AudioServer::get_singleton()->unlock();
182+
}
183+
void AudioStreamPlaybackMyTone::stop(){
184+
active = false;
185+
base->reset();
186+
}
187+
188+
void AudioStreamPlaybackMyTone::start(float p_from_pos){
189+
seek(p_from_pos);
190+
active = true;
191+
}
192+
void AudioStreamPlaybackMyTone::seek(float p_time){
193+
float max = get_length();
194+
if (p_time < 0) {
195+
p_time = 0;
196+
}
197+
base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS);
198+
}
199+
void AudioStreamPlaybackMyTone::mix(AudioFrame *p_buffer, float p_rate, int p_frames){
200+
ERR_FAIL_COND(!active);
201+
if (!active) {
202+
return;
203+
}
204+
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
205+
int16_t * buf = (int16_t * )pcm_buffer;
206+
base->gen_tone(buf, p_frames);
207+
208+
for(int i = 0; i < p_frames; i++){
209+
float sample = float(buf[i])/32767.0;
210+
p_buffer[i] = AudioFrame(sample, sample);
211+
}
212+
}
213+
int AudioStreamPlaybackMyTone::get_loop_count() const {
214+
return 0;
215+
}
216+
float AudioStreamPlaybackMyTone::get_playback_position() const {
217+
return 0.0;
218+
}
219+
float AudioStreamPlaybackMyTone::get_length() const {
220+
return 0.0;
221+
}
222+
bool AudioStreamPlaybackMyTone::is_playing() const {
223+
return active;
224+
}
225+
226+
227+
Resampling
228+
~~~~~~~~~~
229+
230+
231+
Godot’s AudioServer currently uses 44100 Hz sample rate. When other sample rates are
232+
needed such as 48000, either provide one or use AudioStreamPlaybackResampled.
233+
Godot provides cubic interpolation for audio resampling.
234+
235+
Instead of overloading ``mix``, AudioStreamPlaybackResampled uses ``_mix_internal`` to
236+
query AudioFrames and ``get_stream_sampling_rate`` to query current mix rate.
237+
238+
.. code:: cpp
239+
240+
#include "reference.h"
241+
#include "resource.h"
242+
243+
#include "servers/audio/audio_stream.h"
244+
245+
class AudioStreamMyToneResampled;
246+
247+
class AudioStreamPlaybackResampledMyTone : public AudioStreamPlaybackResampled {
248+
GDCLASS(AudioStreamPlaybackResampledMyTone, AudioStreamPlaybackResampled)
249+
friend class AudioStreamMyToneResampled;
250+
private:
251+
enum{
252+
PCM_BUFFER_SIZE = 4096
253+
};
254+
enum {
255+
MIX_FRAC_BITS = 13,
256+
MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
257+
MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
258+
};
259+
void * pcm_buffer;
260+
Ref<AudioStreamMyToneResampled> base;
261+
bool active;
262+
protected:
263+
virtual void _mix_internal(AudioFrame *p_buffer, int p_frames);
264+
public:
265+
virtual void start(float p_from_pos = 0.0);
266+
virtual void stop();
267+
virtual bool is_playing() const;
268+
virtual int get_loop_count() const; //times it looped
269+
virtual float get_playback_position() const;
270+
virtual void seek(float p_time);
271+
virtual float get_length() const; //if supported, otherwise return 0
272+
virtual float get_stream_sampling_rate();
273+
AudioStreamPlaybackResampledMyTone();
274+
275+
};
276+
277+
278+
279+
.. code:: cpp
280+
281+
#include "mytone_audiostream_resampled.h"
282+
#include "math/math_funcs.h"
283+
#include "print_string.h"
284+
285+
AudioStreamPlaybackResampledMyTone::AudioStreamPlaybackResampledMyTone()
286+
: active(false){
287+
AudioServer::get_singleton()->lock();
288+
pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
289+
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
290+
AudioServer::get_singleton()->unlock();
291+
}
292+
void AudioStreamPlaybackResampledMyTone::stop(){
293+
active = false;
294+
base->reset();
295+
}
296+
297+
void AudioStreamPlaybackResampledMyTone::start(float p_from_pos){
298+
seek(p_from_pos);
299+
active = true;
300+
}
301+
void AudioStreamPlaybackResampledMyTone::seek(float p_time){
302+
float max = get_length();
303+
if (p_time < 0) {
304+
p_time = 0;
305+
}
306+
base->set_position(uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS);
307+
}
308+
void AudioStreamPlaybackResampledMyTone::_mix_internal(AudioFrame *p_buffer, int p_frames){
309+
ERR_FAIL_COND(!active);
310+
if (!active) {
311+
return;
312+
}
313+
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
314+
int16_t * buf = (int16_t * )pcm_buffer;
315+
base->gen_tone(buf, p_frames);
316+
317+
for(int i = 0; i < p_frames; i++){
318+
float sample = float(buf[i])/32767.0;
319+
p_buffer[i] = AudioFrame(sample, sample);
320+
}
321+
}
322+
float AudioStreamPlaybackResampledMyTone::get_stream_sampling_rate(){
323+
return float(base->mix_rate);
324+
}
325+
int AudioStreamPlaybackResampledMyTone::get_loop_count() const {
326+
return 0;
327+
}
328+
float AudioStreamPlaybackResampledMyTone::get_playback_position() const {
329+
return 0.0;
330+
}
331+
float AudioStreamPlaybackResampledMyTone::get_length() const {
332+
return 0.0;
333+
}
334+
bool AudioStreamPlaybackResampledMyTone::is_playing() const {
335+
return active;
336+
}
337+
338+
339+
340+
341+
References:
342+
~~~~~~~~~~~
343+
- `core/math/audio_frame.h <https://github.com/godotengine/godot/blob/master/core/math/audio_frame.h>`__
344+
- `servers/audio/audio_stream.h <https://github.com/godotengine/godot/blob/master/servers/audio/audio_stream.h>`__
345+
- `scene/audio/audioplayer.cpp <https://github.com/godotengine/godot/blob/master/scene/audio/audio_player.cpp>`__
346+
347+

development/cpp/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ Engine development
1212
object_class
1313
inheritance_class_tree
1414
custom_modules_in_cpp
15+
custom_audiostreams
1516
creating_android_modules

0 commit comments

Comments
 (0)