AudioStreams personnalisés

Introduction

AudioStream est la classe de base de tout objet émettant de l'audio. AudioStreamPlayer se lie à un AudioStream pour émettre des données PCM dans un AudioServer qui gère les pilotes audio.

Toutes les ressources audios nécessites deux classes basées sur : AudioStream et AudioStreamPlayback. Comme un conteneur de données, AudioStream contient les ressources et s'expose à GDScript. AudioStream fait référence à son propre AudioStreamPlayback interne personnalisé qui traduit AudioStream en données PCM.

Ce guide suppose que le lecteur sait comment créer des modules C++. Sinon, référez-vous à ce guide Modules personnalisées en C++.

Pourquoi ?

  • Liaison de bibliothèques externes (comme Wwise, FMOD, etc).
  • Ajout de files d'attente audios personnalisées
  • Ajout de la prise en charge de plus de formats audios

Créer un AudioStream

Un AudioStream consiste en trois composants : le conteneur de données, le nom du flux et un générateur de classe amie AudioStreamPlayback. Les données audio peuvent être chargées de plusieurs façons, par exemple avec un compteur interne pour un générateur de tonalité, un tampon interne/externe ou une référence de fichier.

Certains AudioStreams doivent être sans état comme des objets chargés depuis ResourceLoader. ResourceLoader charge une fois et référence les mêmes objets sans se soucier combien de fois load est appelé sur une ressource spécifique. Par conséquent, l'état de lecture doit être contenu dans 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);
}

Créer un AudioStreamPlayback

AudioStreamPlayer utilise le rappel mix pour obtenir les données PCM. Le rappel doit correspondre au taux d'échantillonnage et remplir le tampon.

Comme AudioStreamPlayback est contrôlé par le thread audio, i/o et l'allocation dynamique de la mémoire sont interdites.

/*  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;
}

Ré-échantillonnage

L'AudioServer de Godot utilise actuellement une fréquence d'échantillonnage de 44100 Hz. Lorsque d'autres taux d'échantillonnage sont nécessaires, comme 48000, il faut soit en fournir un, soit utiliser AudioStreamPlaybackResampled. Godot fournit une interpolation cubique pour le rééchantillonnage audio.

Au lieu de surcharger mix, AudioStreamPlaybackResampled utilise mix_internal pour interroger les AudioFrames et get_stream_sampling_rate pour interroger le taux de mixage actuel.

#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;
}