Custom AudioStreams
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 translates AudioStream into PCM data.
This guide assumes the reader knows how to create C++ modules. If not, refer to this guide .
Adding custom audio queues
Adding support for more audio formats
An AudioStream consists of three components: data container, stream name, and an AudioStreamPlayback friend class generator. Audio data can be loaded in a number of ways such as with 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 is called on a specific resource. Therefore, playback state must be self-contained in AudioStreamPlayback.
/* audiostream_mytone.cpp */
#include "audiostream_mytone.h"
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);
}
Since AudioStreamPlayback is controlled by the audio thread, i/o and dynamic memory allocation are forbidden.
/* audiostreamplayer_mytone.cpp */
#include "audiostreamplayer_mytone.h"
#include "core/math/math_funcs.h"
#include "core/print_string.h"
AudioStreamPlaybackMyTone::AudioStreamPlaybackMyTone()
: active(false) {
pcm_buffer = AudioServer::get_singleton()->audio_data_alloc(PCM_BUFFER_SIZE);
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
AudioServer::get_singleton()->unlock();
}
AudioStreamPlaybackMyTone::~AudioStreamPlaybackMyTone() {
if(pcm_buffer) {
AudioServer::get_singleton()->audio_data_free(pcm_buffer);
pcm_buffer = NULL;
}
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;
}
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.
#include "mytone_audiostream_resampled.h"
#include "core/math/math_funcs.h"
#include "core/print_string.h"
AudioStreamPlaybackResampledMyTone::AudioStreamPlaybackResampledMyTone()
AudioServer::get_singleton()->lock();
zeromem(pcm_buffer, PCM_BUFFER_SIZE);
AudioServer::get_singleton()->unlock();
}
AudioStreamPlaybackResampledMyTone::~AudioStreamPlaybackResampledMyTone() {
if (pcm_buffer) {
AudioServer::get_singleton()->audio_data_free(pcm_buffer);
pcm_buffer = NULL;
}
}
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;
}