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 <stb_ds.h>
 | 
			
		||||
#include <physfs.h>
 | 
			
		||||
#include <physfsrwops.h>
 | 
			
		||||
 | 
			
		||||
#define STB_VORBIS_NO_PUSHDATA_API
 | 
			
		||||
#define STB_VORBIS_HEADER_ONLY
 | 
			
		||||
@@ -17,6 +18,7 @@
 | 
			
		||||
 | 
			
		||||
static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
 | 
			
		||||
    ".ogg", /* AUDIO_FILE_TYPE_OGG */
 | 
			
		||||
    ".wav", /* AUDIO_FILE_TYPE_WAV */
 | 
			
		||||
    ".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? */
 | 
			
		||||
/*       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) {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
    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: {
 | 
			
		||||
        unsigned char *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) {
 | 
			
		||||
    switch (channel->file_type) {
 | 
			
		||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
			
		||||
@@ -145,6 +179,11 @@ static void repeat_audio(AudioChannel *channel) {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case AUDIO_FILE_TYPE_WAV: {
 | 
			
		||||
        channel->context.wav.position = 0;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case AUDIO_FILE_TYPE_XM: {
 | 
			
		||||
        xm_restart(channel->context.xm.handle);
 | 
			
		||||
        break;
 | 
			
		||||
@@ -165,11 +204,12 @@ void audio_play(const char *path,
 | 
			
		||||
                float volume,
 | 
			
		||||
                float panning)
 | 
			
		||||
{
 | 
			
		||||
    if (channel) {
 | 
			
		||||
        AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
			
		||||
 | 
			
		||||
        /* create a channel if it doesn't exist */
 | 
			
		||||
        if (!pair) {
 | 
			
		||||
        AudioFileType file_type = infer_audio_file_type(path);
 | 
			
		||||
            AudioFileType const file_type = infer_audio_file_type(path);
 | 
			
		||||
            AudioChannel new_channel = {
 | 
			
		||||
                .file_type = 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 */
 | 
			
		||||
        if (strcmp(pair->value.path, path) == 0)
 | 
			
		||||
            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 */
 | 
			
		||||
static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
			
		||||
static void audio_sample_and_mixin_channel(AudioChannel *channel,
 | 
			
		||||
                                           uint8_t *stream,
 | 
			
		||||
                                           int len)
 | 
			
		||||
{
 | 
			
		||||
    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));
 | 
			
		||||
 | 
			
		||||
    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 */
 | 
			
		||||
                    stb_vorbis_seek_start(channel->context.vorbis.handle);
 | 
			
		||||
                    continue;
 | 
			
		||||
                } else
 | 
			
		||||
                } else {
 | 
			
		||||
                    /* leave silence */
 | 
			
		||||
                    channel->finished = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* panning and mixing */
 | 
			
		||||
            audio_mixin_streams(channel,
 | 
			
		||||
@@ -286,6 +346,66 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
			
		||||
        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: {
 | 
			
		||||
        for (int i = 0; i < stream_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 */
 | 
			
		||||
                    xm_restart(channel->context.xm.handle);
 | 
			
		||||
                    continue;
 | 
			
		||||
                } else
 | 
			
		||||
                } else {
 | 
			
		||||
                    channel->finished = true;
 | 
			
		||||
                    /* leave silence */
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* panning and mixing */
 | 
			
		||||
            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);
 | 
			
		||||
        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) {
 | 
			
		||||
    const char *channel = m_or(args, channel, NULL);
 | 
			
		||||
    const bool repeat = m_or(args, repeat, false);
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,12 @@
 | 
			
		||||
 | 
			
		||||
#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 {
 | 
			
		||||
    AUDIO_FILE_TYPE_OGG,
 | 
			
		||||
    AUDIO_FILE_TYPE_WAV,
 | 
			
		||||
    AUDIO_FILE_TYPE_XM,
 | 
			
		||||
    AUDIO_FILE_TYPE_COUNT,
 | 
			
		||||
    AUDIO_FILE_TYPE_UNKNOWN,
 | 
			
		||||
@@ -30,6 +33,12 @@ union AudioContext {
 | 
			
		||||
        uint8_t channel_count;
 | 
			
		||||
    } vorbis;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
        void *samples;
 | 
			
		||||
        SDL_AudioSpec spec;
 | 
			
		||||
        size_t position;
 | 
			
		||||
    } wav;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
        xm_context_t *handle;
 | 
			
		||||
    } xm;
 | 
			
		||||
@@ -44,6 +53,7 @@ typedef struct AudioChannel {
 | 
			
		||||
    bool repeat;
 | 
			
		||||
    float volume;
 | 
			
		||||
    float panning;
 | 
			
		||||
    bool finished;
 | 
			
		||||
} AudioChannel;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ typedef struct EngineContext {
 | 
			
		||||
 | 
			
		||||
    /* audio */
 | 
			
		||||
    AudioChannelItem *audio_channels;
 | 
			
		||||
    AudioChannel *unnamed_audio_channels;
 | 
			
		||||
    SDL_AudioDeviceID audio_device;
 | 
			
		||||
    int audio_stream_frequency;
 | 
			
		||||
    SDL_AudioFormat audio_stream_format;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user