twn_audio.c: .wav support and scratch channels
This commit is contained in:
		
							
								
								
									
										147
									
								
								src/twn_audio.c
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								src/twn_audio.c
									
									
									
									
									
								
							@@ -7,6 +7,7 @@
 | 
				
			|||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
#include <physfs.h>
 | 
					#include <physfs.h>
 | 
				
			||||||
 | 
					#include <physfsrwops.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define STB_VORBIS_NO_PUSHDATA_API
 | 
					#define STB_VORBIS_NO_PUSHDATA_API
 | 
				
			||||||
#define STB_VORBIS_HEADER_ONLY
 | 
					#define STB_VORBIS_HEADER_ONLY
 | 
				
			||||||
@@ -17,6 +18,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
 | 
					static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
 | 
				
			||||||
    ".ogg", /* AUDIO_FILE_TYPE_OGG */
 | 
					    ".ogg", /* AUDIO_FILE_TYPE_OGG */
 | 
				
			||||||
 | 
					    ".wav", /* AUDIO_FILE_TYPE_WAV */
 | 
				
			||||||
    ".xm",  /* AUDIO_FILE_TYPE_XM */
 | 
					    ".xm",  /* AUDIO_FILE_TYPE_XM */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,6 +75,7 @@ static AudioFileType infer_audio_file_type(const char *path) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/* TODO: error propagation and clearing of resources on partial success? */
 | 
					/* TODO: error propagation and clearing of resources on partial success? */
 | 
				
			||||||
/*       or should we expect things to simply fail? */
 | 
					/*       or should we expect things to simply fail? */
 | 
				
			||||||
 | 
					/* TODO: reuse often used decoded/decompressed data */
 | 
				
			||||||
static union AudioContext init_audio_context(const char *path, AudioFileType type) {
 | 
					static union AudioContext init_audio_context(const char *path, AudioFileType type) {
 | 
				
			||||||
    switch (type) {
 | 
					    switch (type) {
 | 
				
			||||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
					    case AUDIO_FILE_TYPE_OGG: {
 | 
				
			||||||
@@ -102,6 +105,25 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: transform to destination format immediately? */
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        SDL_AudioSpec spec;
 | 
				
			||||||
 | 
					        uint8_t *data;
 | 
				
			||||||
 | 
					        uint32_t len;
 | 
				
			||||||
 | 
					        if (!SDL_LoadWAV_RW(PHYSFSRWOPS_openRead(path), 1, &spec, &data, &len)) {
 | 
				
			||||||
 | 
					            CRY_SDL("Cannot load .wav file:");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (union AudioContext) {
 | 
				
			||||||
 | 
					            .wav = {
 | 
				
			||||||
 | 
					                .position = 0,
 | 
				
			||||||
 | 
					                .samples = data,
 | 
				
			||||||
 | 
					                .spec = spec
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case AUDIO_FILE_TYPE_XM: {
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
        unsigned char *data;
 | 
					        unsigned char *data;
 | 
				
			||||||
        int64_t len = get_audio_data(path, &data);
 | 
					        int64_t len = get_audio_data(path, &data);
 | 
				
			||||||
@@ -138,6 +160,18 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void free_audio_channel(AudioChannel channel) {
 | 
				
			||||||
 | 
					    switch (channel.file_type) {
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        SDL_free(channel.context.wav.samples);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void repeat_audio(AudioChannel *channel) {
 | 
					static void repeat_audio(AudioChannel *channel) {
 | 
				
			||||||
    switch (channel->file_type) {
 | 
					    switch (channel->file_type) {
 | 
				
			||||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
					    case AUDIO_FILE_TYPE_OGG: {
 | 
				
			||||||
@@ -145,6 +179,11 @@ static void repeat_audio(AudioChannel *channel) {
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        channel->context.wav.position = 0;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case AUDIO_FILE_TYPE_XM: {
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
        xm_restart(channel->context.xm.handle);
 | 
					        xm_restart(channel->context.xm.handle);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
@@ -165,11 +204,12 @@ void audio_play(const char *path,
 | 
				
			|||||||
                float volume,
 | 
					                float volume,
 | 
				
			||||||
                float panning)
 | 
					                float panning)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    if (channel) {
 | 
				
			||||||
        AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
					        AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* create a channel if it doesn't exist */
 | 
					        /* create a channel if it doesn't exist */
 | 
				
			||||||
        if (!pair) {
 | 
					        if (!pair) {
 | 
				
			||||||
        AudioFileType file_type = infer_audio_file_type(path);
 | 
					            AudioFileType const file_type = infer_audio_file_type(path);
 | 
				
			||||||
            AudioChannel new_channel = {
 | 
					            AudioChannel new_channel = {
 | 
				
			||||||
                .file_type = file_type,
 | 
					                .file_type = file_type,
 | 
				
			||||||
                .context = init_audio_context(path, file_type),
 | 
					                .context = init_audio_context(path, file_type),
 | 
				
			||||||
@@ -188,6 +228,24 @@ void audio_play(const char *path,
 | 
				
			|||||||
        /* works for both restarts and new audio */
 | 
					        /* works for both restarts and new audio */
 | 
				
			||||||
        if (strcmp(pair->value.path, path) == 0)
 | 
					        if (strcmp(pair->value.path, path) == 0)
 | 
				
			||||||
            repeat_audio(&pair->value);
 | 
					            repeat_audio(&pair->value);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        /* audio without channel plays without repeat and ability to change parameters over its course, nor stop it */
 | 
				
			||||||
 | 
					        AudioFileType const file_type = infer_audio_file_type(path);
 | 
				
			||||||
 | 
					        AudioChannel new_channel = {
 | 
				
			||||||
 | 
					            .file_type = file_type,
 | 
				
			||||||
 | 
					            .context = init_audio_context(path, file_type),
 | 
				
			||||||
 | 
					            .path = path,
 | 
				
			||||||
 | 
					            .name = channel,
 | 
				
			||||||
 | 
					            .repeat = false,
 | 
				
			||||||
 | 
					            .volume = volume,
 | 
				
			||||||
 | 
					            .panning = panning,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (repeat)
 | 
				
			||||||
 | 
					            log_warn("Cannot repeat audio played on unnamed scratch channel (for %s)", path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        arrpush(ctx.unnamed_audio_channels, new_channel);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -243,12 +301,12 @@ static void audio_mixin_streams(const AudioChannel *channel,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* remember: sample is data for all channels where frame is a part of it */
 | 
					/* remember: sample is data for all channels where frame is a part of it */
 | 
				
			||||||
static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
					static void audio_sample_and_mixin_channel(AudioChannel *channel,
 | 
				
			||||||
                                           uint8_t *stream,
 | 
					                                           uint8_t *stream,
 | 
				
			||||||
                                           int len)
 | 
					                                           int len)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    static uint8_t buffer[16384];
 | 
					    static uint8_t buffer[16384];
 | 
				
			||||||
    const int float_buffer_frames = sizeof (buffer) / sizeof (float);
 | 
					    const int float_buffer_frames = sizeof (buffer) / sizeof (float) / 2;
 | 
				
			||||||
    const int stream_frames = len / (int)(sizeof (float));
 | 
					    const int stream_frames = len / (int)(sizeof (float));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (channel->file_type) {
 | 
					    switch (channel->file_type) {
 | 
				
			||||||
@@ -270,10 +328,12 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
				
			|||||||
                    /* seek to start and try sampling some more */
 | 
					                    /* seek to start and try sampling some more */
 | 
				
			||||||
                    stb_vorbis_seek_start(channel->context.vorbis.handle);
 | 
					                    stb_vorbis_seek_start(channel->context.vorbis.handle);
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                } else
 | 
					                } else {
 | 
				
			||||||
                    /* leave silence */
 | 
					                    /* leave silence */
 | 
				
			||||||
 | 
					                    channel->finished = true;
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* panning and mixing */
 | 
					            /* panning and mixing */
 | 
				
			||||||
            audio_mixin_streams(channel,
 | 
					            audio_mixin_streams(channel,
 | 
				
			||||||
@@ -286,6 +346,66 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        /* feed stream for needed conversions */
 | 
				
			||||||
 | 
					        for (int i = 0; i < stream_frames; ) {
 | 
				
			||||||
 | 
					            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
				
			||||||
 | 
					                float_buffer_frames : stream_frames - i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int const limit = MIN(n_frames, channel->context.wav.spec.samples);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            switch (channel->context.wav.spec.format) {
 | 
				
			||||||
 | 
					            case AUDIO_U16: {
 | 
				
			||||||
 | 
					                if (channel->context.wav.spec.channels == 1) {
 | 
				
			||||||
 | 
					                    for (int x = 0; x < limit; ++x) {
 | 
				
			||||||
 | 
					                        ((float *)buffer)[x * 2 + 0] = (float)((uint16_t *)channel->context.wav.samples)[x] / (float)UINT16_MAX;
 | 
				
			||||||
 | 
					                        ((float *)buffer)[x * 2 + 1] = (float)((uint16_t *)channel->context.wav.samples)[x] / (float)UINT16_MAX;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case AUDIO_S16: {
 | 
				
			||||||
 | 
					                if (channel->context.wav.spec.channels == 1) {
 | 
				
			||||||
 | 
					                    for (int x = 0; x < limit; ++x) {
 | 
				
			||||||
 | 
					                        if ((float)((int16_t *)channel->context.wav.samples)[x] < 0) {
 | 
				
			||||||
 | 
					                            ((float *)buffer)[x * 2 + 0] = (float)((int16_t *)channel->context.wav.samples)[x] / (float)INT16_MIN;
 | 
				
			||||||
 | 
					                            ((float *)buffer)[x * 2 + 1] = (float)((int16_t *)channel->context.wav.samples)[x] / (float)INT16_MIN;
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            ((float *)buffer)[x * 2 + 0] = (float)((int16_t *)channel->context.wav.samples)[x] / (float)INT16_MAX;
 | 
				
			||||||
 | 
					                            ((float *)buffer)[x * 2 + 1] = (float)((int16_t *)channel->context.wav.samples)[x] / (float)INT16_MAX;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                log_warn("Unsupported .wav PCM format (%x), producing silence", channel->context.wav.spec.format);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* panning and mixing */
 | 
				
			||||||
 | 
					            audio_mixin_streams(channel, &stream[i * sizeof(float)], buffer, limit * 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            channel->context.wav.position += limit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (channel->context.wav.position == channel->context.wav.spec.samples) {
 | 
				
			||||||
 | 
					                if (channel->repeat)
 | 
				
			||||||
 | 
					                    channel->context.wav.position = 0;
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    /* leave silence */
 | 
				
			||||||
 | 
					                    channel->finished = true;
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            i += limit * 2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case AUDIO_FILE_TYPE_XM: {
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
        for (int i = 0; i < stream_frames; ) {
 | 
					        for (int i = 0; i < stream_frames; ) {
 | 
				
			||||||
            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
					            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
				
			||||||
@@ -301,10 +421,12 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
				
			|||||||
                    /* seek to start and try sampling some more */
 | 
					                    /* seek to start and try sampling some more */
 | 
				
			||||||
                    xm_restart(channel->context.xm.handle);
 | 
					                    xm_restart(channel->context.xm.handle);
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                } else
 | 
					                } else {
 | 
				
			||||||
 | 
					                    channel->finished = true;
 | 
				
			||||||
                    /* leave silence */
 | 
					                    /* leave silence */
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* panning and mixing */
 | 
					            /* panning and mixing */
 | 
				
			||||||
            audio_mixin_streams(channel,
 | 
					            audio_mixin_streams(channel,
 | 
				
			||||||
@@ -346,8 +468,23 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
 | 
				
			|||||||
        sanity_check_channel(&ctx.audio_channels[i].value);
 | 
					        sanity_check_channel(&ctx.audio_channels[i].value);
 | 
				
			||||||
        audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
 | 
					        audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < arrlen(ctx.unnamed_audio_channels); ++i) {
 | 
				
			||||||
 | 
					        sanity_check_channel(&ctx.unnamed_audio_channels[i]);
 | 
				
			||||||
 | 
					        audio_sample_and_mixin_channel(&ctx.unnamed_audio_channels[i], stream, len);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* ditch finished unnamed */
 | 
				
			||||||
 | 
					    int i = 0;
 | 
				
			||||||
 | 
					    while (i < arrlen(ctx.unnamed_audio_channels)) {
 | 
				
			||||||
 | 
					        if (ctx.unnamed_audio_channels[i].finished) {
 | 
				
			||||||
 | 
					            free_audio_channel(ctx.unnamed_audio_channels[i]);
 | 
				
			||||||
 | 
					            arrdelswap(ctx.unnamed_audio_channels, i);
 | 
				
			||||||
 | 
					        } else i++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API void audio_play_args(PlayAudioArgs args) {
 | 
					TWN_API void audio_play_args(PlayAudioArgs args) {
 | 
				
			||||||
    const char *channel = m_or(args, channel, NULL);
 | 
					    const char *channel = m_or(args, channel, NULL);
 | 
				
			||||||
    const bool repeat = m_or(args, repeat, false);
 | 
					    const bool repeat = m_or(args, repeat, false);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,9 +13,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define AUDIO_FREQUENCY 48000
 | 
					#define AUDIO_FREQUENCY 48000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: specify which PCM formats are usable with WAV */
 | 
				
			||||||
 | 
					/* TODO: specify limitations of libxm */
 | 
				
			||||||
 | 
					/* TODO: specify limitations of stb_vorbis */
 | 
				
			||||||
typedef enum AudioFileType {
 | 
					typedef enum AudioFileType {
 | 
				
			||||||
    AUDIO_FILE_TYPE_OGG,
 | 
					    AUDIO_FILE_TYPE_OGG,
 | 
				
			||||||
 | 
					    AUDIO_FILE_TYPE_WAV,
 | 
				
			||||||
    AUDIO_FILE_TYPE_XM,
 | 
					    AUDIO_FILE_TYPE_XM,
 | 
				
			||||||
    AUDIO_FILE_TYPE_COUNT,
 | 
					    AUDIO_FILE_TYPE_COUNT,
 | 
				
			||||||
    AUDIO_FILE_TYPE_UNKNOWN,
 | 
					    AUDIO_FILE_TYPE_UNKNOWN,
 | 
				
			||||||
@@ -30,6 +33,12 @@ union AudioContext {
 | 
				
			|||||||
        uint8_t channel_count;
 | 
					        uint8_t channel_count;
 | 
				
			||||||
    } vorbis;
 | 
					    } vorbis;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct {
 | 
				
			||||||
 | 
					        void *samples;
 | 
				
			||||||
 | 
					        SDL_AudioSpec spec;
 | 
				
			||||||
 | 
					        size_t position;
 | 
				
			||||||
 | 
					    } wav;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct {
 | 
					    struct {
 | 
				
			||||||
        xm_context_t *handle;
 | 
					        xm_context_t *handle;
 | 
				
			||||||
    } xm;
 | 
					    } xm;
 | 
				
			||||||
@@ -44,6 +53,7 @@ typedef struct AudioChannel {
 | 
				
			|||||||
    bool repeat;
 | 
					    bool repeat;
 | 
				
			||||||
    float volume;
 | 
					    float volume;
 | 
				
			||||||
    float panning;
 | 
					    float panning;
 | 
				
			||||||
 | 
					    bool finished;
 | 
				
			||||||
} AudioChannel;
 | 
					} AudioChannel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,7 @@ typedef struct EngineContext {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* audio */
 | 
					    /* audio */
 | 
				
			||||||
    AudioChannelItem *audio_channels;
 | 
					    AudioChannelItem *audio_channels;
 | 
				
			||||||
 | 
					    AudioChannel *unnamed_audio_channels;
 | 
				
			||||||
    SDL_AudioDeviceID audio_device;
 | 
					    SDL_AudioDeviceID audio_device;
 | 
				
			||||||
    int audio_stream_frequency;
 | 
					    int audio_stream_frequency;
 | 
				
			||||||
    SDL_AudioFormat audio_stream_format;
 | 
					    SDL_AudioFormat audio_stream_format;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user