.xm playback
This commit is contained in:
parent
febe3310aa
commit
0eb851e7bf
@ -15,6 +15,7 @@ set(PHYSFS_BUILD_SHARED FALSE)
|
|||||||
set(PHYSFS_DISABLE_INSTALL TRUE)
|
set(PHYSFS_DISABLE_INSTALL TRUE)
|
||||||
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall")
|
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall")
|
||||||
add_subdirectory(third-party/physfs)
|
add_subdirectory(third-party/physfs)
|
||||||
|
add_subdirectory(third-party/libxm)
|
||||||
|
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
@ -113,6 +114,7 @@ target_include_directories(${PROJECT_NAME}
|
|||||||
PRIVATE
|
PRIVATE
|
||||||
third-party/physfs/src
|
third-party/physfs/src
|
||||||
third-party/physfs/extras
|
third-party/physfs/extras
|
||||||
|
third-party/libxm/include
|
||||||
)
|
)
|
||||||
|
|
||||||
# header-only libraries should be marked as "system includes"
|
# header-only libraries should be marked as "system includes"
|
||||||
@ -136,6 +138,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC
|
|||||||
SDL2_image::SDL2_image
|
SDL2_image::SDL2_image
|
||||||
SDL2_ttf::SDL2_ttf
|
SDL2_ttf::SDL2_ttf
|
||||||
physfs-static
|
physfs-static
|
||||||
|
xms
|
||||||
)
|
)
|
||||||
|
|
||||||
# copy dlls for baby windows
|
# copy dlls for baby windows
|
||||||
|
129
src/audio.c
129
src/audio.c
@ -1,5 +1,6 @@
|
|||||||
#include "private/audio.h"
|
#include "private/audio.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
#include "config.h"
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ static const char *audio_exts[audio_file_type_count] = {
|
|||||||
|
|
||||||
|
|
||||||
/* TODO: count frames without use, free the memory when threshold is met */
|
/* TODO: count frames without use, free the memory when threshold is met */
|
||||||
|
/* TODO: count repeated usages for sound effect cases with rendering to ram? */
|
||||||
/* stores path to data hash, useful for sound effects */
|
/* stores path to data hash, useful for sound effects */
|
||||||
static struct audio_file_cache {
|
static struct audio_file_cache {
|
||||||
char *key;
|
char *key;
|
||||||
@ -110,6 +112,29 @@ static union audio_context init_audio_context(const char *path, t_audio_file_typ
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case audio_file_type_xm: {
|
||||||
|
unsigned char *data;
|
||||||
|
int64_t len = get_audio_data(path, &data);
|
||||||
|
if (len == -1) {
|
||||||
|
CRY("Audio error", "Error reading file");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
xm_context_t *handle;
|
||||||
|
int response = xm_create_context_safe(&handle,
|
||||||
|
(const char *)data,
|
||||||
|
(size_t)len,
|
||||||
|
AUDIO_FREQUENCY);
|
||||||
|
if (response != 0) {
|
||||||
|
CRY("Audio error", "Error loading xm module");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (union audio_context) {
|
||||||
|
.xm = { .handle = handle }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
CRY("Audio error", "Unhandled audio format (in init)");
|
CRY("Audio error", "Unhandled audio format (in init)");
|
||||||
return (union audio_context){0};
|
return (union audio_context){0};
|
||||||
@ -120,9 +145,22 @@ static union audio_context init_audio_context(const char *path, t_audio_file_typ
|
|||||||
|
|
||||||
|
|
||||||
static void repeat_audio(struct audio_channel *channel) {
|
static void repeat_audio(struct audio_channel *channel) {
|
||||||
(void)channel;
|
switch (channel->file_type) {
|
||||||
|
case audio_file_type_ogg: {
|
||||||
|
stb_vorbis_seek_start(channel->context.vorbis.handle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO */
|
case audio_file_type_xm: {
|
||||||
|
/* TODO: test */
|
||||||
|
xm_seek(channel->context.xm.handle, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
CRY("Audio error", "Unhandled audio format (in repeat)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -160,20 +198,50 @@ t_play_audio_args get_default_audio_args(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void audio_sample_and_mixin_channel(struct audio_channel *channel,
|
/* this assumes int16_t based streams */
|
||||||
|
static void audio_mixin_streams(const struct audio_channel *channel,
|
||||||
|
uint8_t *restrict a,
|
||||||
|
uint8_t *restrict b,
|
||||||
|
size_t frames)
|
||||||
|
{
|
||||||
|
int16_t *const sa = (int16_t *)a;
|
||||||
|
int16_t *const sb = (int16_t *)b;
|
||||||
|
|
||||||
|
const float left_panning = fminf(fabsf(channel->args.panning - 1.0f), 1.0f);
|
||||||
|
const float right_panning = fminf(fabsf(channel->args.panning + 1.0f), 1.0f);
|
||||||
|
|
||||||
|
#if AUDIO_N_CHANNELS == 2
|
||||||
|
|
||||||
|
for (size_t s = 0; s < frames; s += 2) {
|
||||||
|
/* left channel */
|
||||||
|
sa[s] += (int16_t)(sb[s] * channel->args.volume * left_panning);
|
||||||
|
|
||||||
|
/* right channel */
|
||||||
|
sa[s + 1] += (int16_t)(sb[s + 1] * channel->args.volume * right_panning);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "Unimplemented channel count"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* remember: sample is data for all channels where frame is a part of it */
|
||||||
|
static void audio_sample_and_mixin_channel(const struct audio_channel *channel,
|
||||||
uint8_t *stream,
|
uint8_t *stream,
|
||||||
int len) {
|
int len)
|
||||||
int16_t *const sstream = (int16_t *)stream;
|
{
|
||||||
static uint8_t buffer[16384];
|
static uint8_t buffer[16384];
|
||||||
const int buffer_frames = sizeof (buffer) / sizeof (int16_t);
|
const int int16_buffer_frames = sizeof (buffer) / sizeof (int16_t);
|
||||||
|
const int float_buffer_frames = sizeof (buffer) / sizeof (float);
|
||||||
|
const int stream_frames = len / sizeof (int16_t);
|
||||||
|
|
||||||
switch (channel->file_type) {
|
switch (channel->file_type) {
|
||||||
case audio_file_type_ogg: {
|
case audio_file_type_ogg: {
|
||||||
/* feed stream for needed conversions */
|
/* feed stream for needed conversions */
|
||||||
for (int i = 0; i < (len / 2);) {
|
for (int i = 0; i < stream_frames; ) {
|
||||||
const int n_frames = ((len / 2) - i) > buffer_frames ? buffer_frames : (len / 2) - i;
|
const int n_frames = (stream_frames - i) > int16_buffer_frames ?
|
||||||
|
int16_buffer_frames : stream_frames - i;
|
||||||
|
|
||||||
/* TODO: handle end of file */
|
|
||||||
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
|
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
|
||||||
channel->context.vorbis.handle,
|
channel->context.vorbis.handle,
|
||||||
channel->context.vorbis.channel_count,
|
channel->context.vorbis.channel_count,
|
||||||
@ -191,24 +259,8 @@ static void audio_sample_and_mixin_channel(struct audio_channel *channel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* panning and mixing */
|
/* panning and mixing */
|
||||||
|
audio_mixin_streams(channel, &stream[i * sizeof(int16_t)], buffer,
|
||||||
const float left_panning = fminf(fabsf(channel->args.panning - 1.0f), 1.0f);
|
samples_per_channel * ctx.audio_stream_channel_count);
|
||||||
const float right_panning = fminf(fabsf(channel->args.panning + 1.0f), 1.0f);
|
|
||||||
|
|
||||||
#if AUDIO_N_CHANNELS == 2
|
|
||||||
|
|
||||||
for (int s = 0; s < samples_per_channel * ctx.audio_stream_channel_count; s += 2) {
|
|
||||||
/* left channel */
|
|
||||||
sstream[i + s] += (int16_t)(((int16_t *)buffer)[s] *
|
|
||||||
channel->args.volume * left_panning);
|
|
||||||
|
|
||||||
/* right channel */
|
|
||||||
sstream[i + s + 1] += (int16_t)(((int16_t *)buffer)[s + 1] *
|
|
||||||
channel->args.volume * right_panning);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#error "Unimplemented channel count"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
i += samples_per_channel * ctx.audio_stream_channel_count;
|
i += samples_per_channel * ctx.audio_stream_channel_count;
|
||||||
}
|
}
|
||||||
@ -216,6 +268,29 @@ static void audio_sample_and_mixin_channel(struct audio_channel *channel,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case audio_file_type_xm: {
|
||||||
|
for (int i = 0; i < stream_frames; ) {
|
||||||
|
const int n_frames = (stream_frames - i) > float_buffer_frames ?
|
||||||
|
float_buffer_frames : stream_frames - i;
|
||||||
|
|
||||||
|
/* TODO: make it report generated frame count and stop right before looping */
|
||||||
|
xm_generate_samples(channel->context.xm.handle, (float *)buffer, n_frames / 2);
|
||||||
|
|
||||||
|
/* convert floats to int16_t */
|
||||||
|
for (int p = 0; p < n_frames; ++p) {
|
||||||
|
const int16_t value = (int16_t)(((float *)buffer)[p] * 32768.0f);
|
||||||
|
((int16_t *)buffer)[p] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* panning and mixing */
|
||||||
|
audio_mixin_streams(channel, &stream[i * sizeof(int16_t)], buffer, n_frames);
|
||||||
|
|
||||||
|
i += n_frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
CRY("Audio error", "Unhandled audio format (in sampling)");
|
CRY("Audio error", "Unhandled audio format (in sampling)");
|
||||||
break;
|
break;
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#define STB_VORBIS_HEADER_ONLY
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
#include <stb_vorbis.c>
|
#include <stb_vorbis.c>
|
||||||
|
#include <xm.h>
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -27,6 +28,10 @@ union audio_context {
|
|||||||
int frequency;
|
int frequency;
|
||||||
uint8_t channel_count;
|
uint8_t channel_count;
|
||||||
} vorbis;
|
} vorbis;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
xm_context_t *handle;
|
||||||
|
} xm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user