twn_audio: rework interface
This commit is contained in:
		| @@ -106,10 +106,9 @@ Scene *ingame_scene(State *state) { | |||||||
|  |  | ||||||
|     new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; |     new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; | ||||||
|  |  | ||||||
|     audio_play_ex("music/mod65.xm", "soundtrack", (PlayAudioArgs){ |     m_audio(m_set("music/mod65.xm"), | ||||||
|         .repeat = true, |             m_opt(channel, "soundtrack"), | ||||||
|         .volume = 1.0f |             m_opt(repeat, true)); | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     input_set_mouse_captured(&ctx.input, true); |     input_set_mouse_captured(&ctx.input, true); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,31 +0,0 @@ | |||||||
| ORIGIN = { x = 320, y = 180 } |  | ||||||
| RADIUS = 48 |  | ||||||
| offset = { x = 0, y = 0 } |  | ||||||
| angle = 0 |  | ||||||
|  |  | ||||||
| function game_tick() |  | ||||||
|     rectangle { |  | ||||||
|         rect = { x = 0, y = 0, w = 640, h = 360 }, |  | ||||||
|         color = { r = 127, g = 0, b = 127, a = 255 }, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     sprite { |  | ||||||
|         path = "/assets/title.png", |  | ||||||
|         rect = { |  | ||||||
|             x = 320 - (320 / 2), |  | ||||||
|             y = 180 - (128 / 2), |  | ||||||
|             w = 320, |  | ||||||
|             h = 128, |  | ||||||
|         }, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     text { |  | ||||||
|         string = "IT KEEPS HAPPENING", |  | ||||||
|         position = offset, |  | ||||||
|         font = "/fonts/kenney-pixel.ttf", |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     offset.x = ORIGIN.x + (math.cos(angle) * RADIUS) |  | ||||||
|     offset.y = ORIGIN.y + (math.sin(angle) * RADIUS) |  | ||||||
|     angle = angle + 0.1 |  | ||||||
| end |  | ||||||
| @@ -2,37 +2,45 @@ | |||||||
| #define TWN_AUDIO_H | #define TWN_AUDIO_H | ||||||
|  |  | ||||||
| #include "twn_engine_api.h" | #include "twn_engine_api.h" | ||||||
|  | #include "twn_option.h" | ||||||
|  |  | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  |  | ||||||
|  | /* plays audio file at specified channel or at scratch channel if NULL is passed, without ability to refer to it later */ | ||||||
|  | /* path path must contain valid file extension to infer which file format it is */ | ||||||
|  | /* supported formats: .ogg, .xm */ | ||||||
|  | TWN_API void audio_play(const char *path, | ||||||
|  |                         const char *channel,    /* optional */ | ||||||
|  |                         bool  repeat,           /* default: false */ | ||||||
|  |                         float volume,           /* default: 1.0f, range: 0.0f to 1.0f */ | ||||||
|  |                         float panning);         /* default: 0.0f, range: -1.0 to 1.0f */ | ||||||
|  |  | ||||||
|  | typedef enum { | ||||||
|  |     AUDIO_PARAM_REPEAT, | ||||||
|  |     AUDIO_PARAM_VOLUME, | ||||||
|  |     AUDIO_PARAM_PANNING, | ||||||
|  | } AudioParam; | ||||||
|  |  | ||||||
|  | TWN_API void audio_set(const char *channel, AudioParam param, float value); | ||||||
|  |  | ||||||
|  | /* TODO */ | ||||||
|  | // TWN_API bool audio_ended(const char *channel); | ||||||
|  |  | ||||||
|  | #ifndef TWN_NOT_C | ||||||
|  |  | ||||||
| typedef struct PlayAudioArgs { | typedef struct PlayAudioArgs { | ||||||
|     /* default: false */ |     char *path; | ||||||
|     bool repeat; |  | ||||||
|     /* crossfade between already playing audio on a given channel, if any */ |     m_option_list( | ||||||
|     /* default: false */ |         char *, channel, | ||||||
|     bool crossfade; |         bool,   repeat, | ||||||
|     /* range: 0.0f to 1.0f */ |         float,  volume, | ||||||
|     /* default: 1.0f */ |         float,  panning ) | ||||||
|     float volume; |  | ||||||
|     /* range: -1.0 to 1.0f */ |  | ||||||
|     /* default: 0.0f */ |  | ||||||
|     float panning; |  | ||||||
| } PlayAudioArgs; | } PlayAudioArgs; | ||||||
|  |  | ||||||
|  | TWN_API void audio_play_args(PlayAudioArgs args); | ||||||
| /* plays audio file at specified channel or anywhere if NULL is passed */ | #define m_audio(...) (audio_play_args((PlayAudioArgs){__VA_ARGS__})) | ||||||
| /* path must contain valid file extension to infer which file format it is */ |  | ||||||
| /* supported formats: .ogg, .xm */ | #endif | ||||||
| /* preserves args that are already specified on the channel */ |  | ||||||
| TWN_API void audio_play(const char *path, const char *channel); |  | ||||||
|  |  | ||||||
| TWN_API void audio_play_ex(const char *path, const char *channel, PlayAudioArgs args); |  | ||||||
|  |  | ||||||
| /* could be used for modifying args */ |  | ||||||
| /* warn: is only valid if no other calls to audio are made */ |  | ||||||
| TWN_API PlayAudioArgs *audio_get_args(const char *channel); |  | ||||||
|  |  | ||||||
| TWN_API PlayAudioArgs audio_get_default_args(void); |  | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -54,15 +54,6 @@ static int64_t get_audio_data(const char *path, unsigned char **data) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void audio_play(const char *path, const char *channel) { |  | ||||||
|     const AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel); |  | ||||||
|     if (!pair) |  | ||||||
|         audio_play_ex(path, channel, audio_get_default_args()); |  | ||||||
|     else |  | ||||||
|         audio_play_ex(path, channel, pair->value.args); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| static AudioFileType infer_audio_file_type(const char *path) { | static AudioFileType infer_audio_file_type(const char *path) { | ||||||
|     size_t path_len = SDL_strlen(path); |     size_t path_len = SDL_strlen(path); | ||||||
|  |  | ||||||
| @@ -163,18 +154,25 @@ static void repeat_audio(AudioChannel *channel) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void audio_play_ex(const char *path, const char *channel, PlayAudioArgs args) { | void audio_play(const char *path, | ||||||
|  |                 const char *channel, | ||||||
|  |                 bool  repeat, | ||||||
|  |                 float volume, | ||||||
|  |                 float panning) | ||||||
|  | { | ||||||
|     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 file_type = infer_audio_file_type(path); | ||||||
|         AudioChannel new_channel = { |         AudioChannel new_channel = { | ||||||
|             .args = args, |  | ||||||
|             .file_type = file_type, |             .file_type = file_type, | ||||||
|             .context = init_audio_context(path, file_type), |             .context = init_audio_context(path, file_type), | ||||||
|             .path = path, |             .path = path, | ||||||
|             .name = channel, |             .name = channel, | ||||||
|  |             .repeat = repeat, | ||||||
|  |             .volume = volume, | ||||||
|  |             .panning = panning, | ||||||
|         }; |         }; | ||||||
|         shput(ctx.audio_channels, channel, new_channel); |         shput(ctx.audio_channels, channel, new_channel); | ||||||
|         pair = shgetp_null(ctx.audio_channels, channel); |         pair = shgetp_null(ctx.audio_channels, channel); | ||||||
| @@ -188,22 +186,40 @@ void audio_play_ex(const char *path, const char *channel, PlayAudioArgs args) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| PlayAudioArgs *audio_get_args(const char *channel) { | TWN_API void audio_set(const char *channel, AudioParam param, float value) { | ||||||
|     AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel); |     AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel); | ||||||
|     if (!pair) |     if (!pair) { | ||||||
|         return NULL; |         log_warn("No channel by the name of %s to set a parameter for", channel); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return &pair->value.args; |     switch (param) { | ||||||
| } |     case AUDIO_PARAM_REPEAT: | ||||||
|  |         pair->value.repeat = (bool)value; | ||||||
|  |         break; | ||||||
| PlayAudioArgs audio_get_default_args(void) { |     case AUDIO_PARAM_VOLUME: | ||||||
|     return (PlayAudioArgs){ |         if (value > 1.0f) { | ||||||
|         .repeat = false, |             log_warn("Out of range volume for channel %s set", channel); | ||||||
|         .crossfade = false, |             value = 1.0f; | ||||||
|         .volume = 1.0f, |         } | ||||||
|         .panning = 0.0f, |         if (value < 0.0f) { | ||||||
|     }; |             log_warn("Out of range volume for channel %s set", channel); | ||||||
|  |             value = 0.0f; | ||||||
|  |         } | ||||||
|  |         pair->value.volume = value; | ||||||
|  |         break; | ||||||
|  |     case AUDIO_PARAM_PANNING: | ||||||
|  |         if (value > 1.0f) { | ||||||
|  |             log_warn("Out of range panning for channel %s set", channel); | ||||||
|  |             value = 1.0f; | ||||||
|  |         } | ||||||
|  |         if (value < -1.0f) { | ||||||
|  |             log_warn("Out of range panning for channel %s set", channel); | ||||||
|  |             value = -1.0f; | ||||||
|  |         } | ||||||
|  |         pair->value.panning = value; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -216,15 +232,15 @@ static void audio_mixin_streams(const AudioChannel *channel, | |||||||
|     float *const sa = (float *)a; |     float *const sa = (float *)a; | ||||||
|     float *const sb = (float *)b; |     float *const sb = (float *)b; | ||||||
|  |  | ||||||
|     const float left_panning = fminf(fabsf(channel->args.panning - 1.0f), 1.0f); |     const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f); | ||||||
|     const float right_panning = fminf(fabsf(channel->args.panning + 1.0f), 1.0f); |     const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f); | ||||||
|  |  | ||||||
|     for (size_t s = 0; s < frames; s += 2) { |     for (size_t s = 0; s < frames; s += 2) { | ||||||
|         /* left channel */ |         /* left channel */ | ||||||
|         sa[s] += (float)(sb[s] * channel->args.volume * left_panning); |         sa[s] += (float)(sb[s] * channel->volume * left_panning); | ||||||
|  |  | ||||||
|         /* right channel */ |         /* right channel */ | ||||||
|         sa[s + 1] += (float)(sb[s + 1] * channel->args.volume * right_panning); |         sa[s + 1] += (float)(sb[s + 1] * channel->volume * right_panning); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -253,7 +269,7 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel, | |||||||
|  |  | ||||||
|             /* handle end of file */ |             /* handle end of file */ | ||||||
|             if (samples_per_channel == 0) { |             if (samples_per_channel == 0) { | ||||||
|                 if (channel->args.repeat) { |                 if (channel->repeat) { | ||||||
|                     /* 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; | ||||||
| @@ -284,7 +300,7 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel, | |||||||
|  |  | ||||||
|             /* handle end of file */ |             /* handle end of file */ | ||||||
|             if (samples_per_channel == 0) { |             if (samples_per_channel == 0) { | ||||||
|                 if (channel->args.repeat) { |                 if (channel->repeat) { | ||||||
|                     /* 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; | ||||||
| @@ -313,10 +329,10 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel, | |||||||
|  |  | ||||||
|  |  | ||||||
| static void sanity_check_channel(const AudioChannel *channel) { | static void sanity_check_channel(const AudioChannel *channel) { | ||||||
|     if (channel->args.volume < 0.0f || channel->args.volume > 1.0f) |     if (channel->volume < 0.0f || channel->volume > 1.0f) | ||||||
|         log_warn("Volume argument is out of range for channel (%s)", channel->name); |         log_warn("Volume argument is out of range for channel (%s)", channel->name); | ||||||
|  |  | ||||||
|     if (channel->args.panning < -1.0f || channel->args.panning > 1.0f) |     if (channel->panning < -1.0f || channel->panning > 1.0f) | ||||||
|         log_warn("Panning argument is out of range for channel (%s)", channel->name); |         log_warn("Panning argument is out of range for channel (%s)", channel->name); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -332,3 +348,11 @@ void audio_callback(void *userdata, uint8_t *stream, int len) { | |||||||
|         audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len); |         audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | TWN_API void audio_play_args(PlayAudioArgs args) { | ||||||
|  |     const char *channel = m_or(args, channel, NULL); | ||||||
|  |     const bool repeat = m_or(args, repeat, false); | ||||||
|  |     const float volume = m_or(args, volume, 1.0f); | ||||||
|  |     const float panning = m_or(args, panning, 0.0f); | ||||||
|  |     audio_play(args.path, channel, repeat, volume, panning); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -36,11 +36,13 @@ union AudioContext { | |||||||
|  |  | ||||||
|  |  | ||||||
| typedef struct AudioChannel { | typedef struct AudioChannel { | ||||||
|     PlayAudioArgs args; |  | ||||||
|     AudioFileType file_type; |     AudioFileType file_type; | ||||||
|     union AudioContext context; /* interpreted by `file_type` value */ |     union AudioContext context; /* interpreted by `file_type` value */ | ||||||
|     const char *path; |     const char *path; | ||||||
|     const char *name; |     const char *name; | ||||||
|  |     bool repeat; | ||||||
|  |     float volume; | ||||||
|  |     float panning; | ||||||
| } AudioChannel; | } AudioChannel; | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user