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.

This guide assumes the reader knows how to create C++ modules. If not, refer to this guide 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

Crear 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.instantiate();
    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:

Crear 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.

mytone_audiostream_resampled.h
#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();
};
mytone_audiostream_resampled.cpp
#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: