AudioStreams Personalizados

Introducción

AudioStream es la clase base de todos los objetos que emiten audio. AudioStreamPlayer se vincula a un AudioStream para emitir datos PCM en un AudioServer que gestiona los controladores de audio.

Todos los recursos de audio requieren dos clases basadas en audio: AudioStream y AudioStreamPlayback. Como container de datos, AudioStream contiene el recurso y se expone a GDScript. AudioStream hace referencia a su propio AudioStreamPlayback interno personalizado que traduce AudioStream en datos PCM.

Esta guía asume que el lector sabe como crear modulos en C++, Si no, consulte la guia Módulos personalizados en C++.

Referencias:

¿Para qué?

  • Vinculando librerias externas (como Wwise, FMOD, etc).

  • Agregando colas de audio personalizados

  • Agregando soporte para mas formatos de audio

Creando un AudioStream

Un AudioStream consta de tres componentes: container de datos, nombre del stream y un generador de clases amigables AudioStreamPlayback. Los datos de audio pueden cargarse de varias maneras, por ejemplo, con un contador interno para un generador de tonos, una memoria intermedia interna/externa o una referencia de archivo.

Algunos AudioStreams necesitan ser sin estado, como los objetos cargados desde ResourceLoader. ResourceLoader carga una vez y hace referencia al mismo objeto sin importar cuántas veces se llame a load en un recurso específico. Por lo tanto, el estado de reproducción debe estar autocontenido en AudioStreamPlayback.

/* audiostream_mytone.h */

#include "core/reference.h"
#include "core/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 *pcm_buf, int size);
        virtual float get_length() const { return 0; } // if supported, otherwise return 0
        AudioStreamMyTone();

protected:
        static void _bind_methods();
};
/* 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);
}

Referencias:

Creando un AudioStreamPlayback

El AudioStreamPlayer utiliza el callback mix para obtener datos PCM. El callback debe coincidir con la frecuencia de muestreo (sample rate) y llenar el búfer de audio.

Dado que AudioStreamPlayback está controlado por el hilo de audio, las operaciones de entrada/salida y la asignación dinámica de memoria están prohibidas.

/*  audiostreamplayer_mytone.h */

#include "core/reference.h"
#include "core/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();
        ~AudioStreamPlaybackMyTone();
};
/* audiostreamplayer_mytone.cpp */

#include "audiostreamplayer_mytone.h"

#include "core/math/math_funcs.h"
#include "core/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();
}
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;
}

Remuestreo

El AudioServer de Godot actualmente utiliza una frecuencia de muestreo (sample rate) de 44100 Hz. Cuando se necesiten otras frecuencias de muestreo, como 48000 Hz, es necesario proporcionar una o utilizar AudioStreamPlaybackResampled. Godot ofrece interpolación cúbica para el remuestreo de audio.

En lugar de sobrecargar mix, AudioStreamPlaybackResampled utiliza _mix_internal para obtener AudioFrames y get_stream_sampling_rate para obtener la frecuencia de mezcla actual.

#include "core/reference.h"
#include "core/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();
        ~AudioStreamPlaybackResampledMyTone();
};
#include "mytone_audiostream_resampled.h"

#include "core/math/math_funcs.h"
#include "core/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();
}
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;
}

Referencias: