mirror of
https://github.com/LostArtefacts/TR2X.git
synced 2024-11-23 05:50:01 +00:00
music: add music directory backend
This adds support to play both CDAudio single MP3 as well as loading individual tracks directly from a music directory.
This commit is contained in:
parent
0c827cb3b0
commit
475ca7c255
@ -90,7 +90,9 @@ dll_sources = [
|
||||
'src/game/math.c',
|
||||
'src/game/math_misc.c',
|
||||
'src/game/matrix.c',
|
||||
'src/game/music.c',
|
||||
'src/game/music/music_main.c',
|
||||
'src/game/music/music_backend_files.c',
|
||||
'src/game/music/music_backend_cdaudio.c',
|
||||
'src/game/objects/creatures/bird.c',
|
||||
'src/game/output.c',
|
||||
'src/game/overlay.c',
|
||||
|
283
src/game/music.c
283
src/game/music.c
@ -1,283 +0,0 @@
|
||||
#include "game/music.h"
|
||||
|
||||
#include "global/const.h"
|
||||
#include "global/funcs.h"
|
||||
#include "global/types.h"
|
||||
#include "global/vars.h"
|
||||
|
||||
#include <libtrx/engine/audio.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/log.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define MAX_CD_TRACKS 60
|
||||
|
||||
static MUSIC_TRACK_ID m_TrackCurrent = MX_INACTIVE;
|
||||
static MUSIC_TRACK_ID m_TrackLooped = MX_INACTIVE;
|
||||
|
||||
typedef struct {
|
||||
uint64_t from;
|
||||
uint64_t to;
|
||||
bool active;
|
||||
} CDAUDIO_TRACK;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
char *audio_type;
|
||||
} CDAUDIO_SPEC;
|
||||
|
||||
static const int32_t m_CDAudioSpecCount = 2;
|
||||
static CDAUDIO_SPEC m_CDAudioSpecs[] = {
|
||||
{
|
||||
.path = "audio/cdaudio.wav",
|
||||
.audio_type = "waveaudio",
|
||||
},
|
||||
{
|
||||
.path = "audio/cdaudio.mp3",
|
||||
.audio_type = "mpegvideo",
|
||||
},
|
||||
};
|
||||
|
||||
static const CDAUDIO_SPEC *m_ChosenSpec = NULL;
|
||||
static CDAUDIO_TRACK m_Tracks[MAX_CD_TRACKS];
|
||||
|
||||
static bool m_Initialized = false;
|
||||
static float m_MusicVolume = 0.0f;
|
||||
static int m_AudioStreamID = -1;
|
||||
|
||||
static const CDAUDIO_SPEC *Music_FindSpec(void);
|
||||
static bool Music_ParseCDAudio(void);
|
||||
static void Music_StreamFinished(int stream_id, void *user_data);
|
||||
|
||||
static void Music_StreamFinished(const int stream_id, void *const user_data)
|
||||
{
|
||||
// When a stream finishes, play the remembered background BGM.
|
||||
if (stream_id == m_AudioStreamID) {
|
||||
m_AudioStreamID = -1;
|
||||
if (m_TrackLooped >= 0) {
|
||||
Music_Play(m_TrackLooped, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const CDAUDIO_SPEC *Music_FindSpec(void)
|
||||
{
|
||||
const CDAUDIO_SPEC *spec = NULL;
|
||||
|
||||
for (int32_t i = 0; i < m_CDAudioSpecCount; i++) {
|
||||
MYFILE *const fp = File_Open(m_CDAudioSpecs[i].path, FILE_OPEN_READ);
|
||||
if (fp != NULL) {
|
||||
spec = &m_CDAudioSpecs[i];
|
||||
File_Close(fp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spec == NULL) {
|
||||
LOG_WARNING("Cannot find any CDAudio data files");
|
||||
return NULL;
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
static bool Music_ParseCDAudio(void)
|
||||
{
|
||||
char *track_content = NULL;
|
||||
size_t track_content_size;
|
||||
if (!File_Load("audio/cdaudio.dat", &track_content, &track_content_size)) {
|
||||
LOG_WARNING("Cannot find CDAudio control file");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(m_Tracks, 0, sizeof(m_Tracks));
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset < track_content_size) {
|
||||
while (track_content[offset] == '\n' || track_content[offset] == '\r') {
|
||||
if (++offset >= track_content_size) {
|
||||
goto parse_end;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t track_num;
|
||||
uint64_t from;
|
||||
uint64_t to;
|
||||
int32_t result = sscanf(
|
||||
&track_content[offset], "%" PRIu64 " %" PRIu64 " %" PRIu64,
|
||||
&track_num, &from, &to);
|
||||
|
||||
if (result == 3 && track_num > 0 && track_num <= MAX_CD_TRACKS) {
|
||||
int32_t track_idx = track_num - 1;
|
||||
m_Tracks[track_idx].active = true;
|
||||
m_Tracks[track_idx].from = from;
|
||||
m_Tracks[track_idx].to = to;
|
||||
}
|
||||
|
||||
while (track_content[offset] != '\n' && track_content[offset] != '\r') {
|
||||
if (++offset >= track_content_size) {
|
||||
goto parse_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parse_end:
|
||||
free(track_content);
|
||||
|
||||
// reindex wrong track boundaries
|
||||
for (int32_t i = 0; i < MAX_CD_TRACKS; i++) {
|
||||
if (!m_Tracks[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < MAX_CD_TRACKS - 1 && m_Tracks[i].from >= m_Tracks[i].to) {
|
||||
for (int32_t j = i + 1; j < MAX_CD_TRACKS; j++) {
|
||||
if (m_Tracks[j].active) {
|
||||
m_Tracks[i].to = m_Tracks[j].from;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Tracks[i].from >= m_Tracks[i].to && i > 0) {
|
||||
for (int32_t j = i - 1; j >= 0; j--) {
|
||||
if (m_Tracks[j].active) {
|
||||
m_Tracks[i].from = m_Tracks[j].to;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __cdecl Music_Init(void)
|
||||
{
|
||||
// TODO: remove this guard once Music_Init can be called in a proper place
|
||||
if (m_Initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Audio_Init()) {
|
||||
LOG_ERROR("Failed to initialize libtrx sound system");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ChosenSpec = Music_FindSpec();
|
||||
if (m_ChosenSpec == NULL) {
|
||||
LOG_ERROR("Failed to find CDAudio data");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Music_ParseCDAudio()) {
|
||||
LOG_ERROR("Failed to parse CDAudio data");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Initialized = true;
|
||||
m_TrackCurrent = MX_INACTIVE;
|
||||
m_TrackLooped = MX_INACTIVE;
|
||||
Music_SetVolume(25 * g_OptionMusicVolume + 5);
|
||||
return true;
|
||||
}
|
||||
|
||||
void __cdecl Music_Shutdown(void)
|
||||
{
|
||||
if (m_AudioStreamID < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are only interested in calling Music_StreamFinished if a stream
|
||||
// finished by itself. In cases where we end the streams early by hand,
|
||||
// we clear the finish callback in order to avoid resuming the BGM playback
|
||||
// just after we stop it.
|
||||
Audio_Stream_SetFinishCallback(m_AudioStreamID, NULL, NULL);
|
||||
Audio_Stream_Close(m_AudioStreamID);
|
||||
}
|
||||
|
||||
void __cdecl Music_Play(int16_t track_id, bool is_looped)
|
||||
{
|
||||
if (track_id == m_TrackCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: this should be called in shell instead, once per game launch
|
||||
Music_Init();
|
||||
|
||||
Audio_Stream_Close(m_AudioStreamID);
|
||||
if (g_OptionMusicVolume == 0) {
|
||||
LOG_DEBUG("Not playing track %d because the game is silent", track_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t track_idx = Music_GetRealTrack(track_id) - 1;
|
||||
if (track_idx < 0 || track_idx >= MAX_CD_TRACKS) {
|
||||
LOG_ERROR("Invalid track: %d", track_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const CDAUDIO_TRACK *track = &m_Tracks[track_idx];
|
||||
if (!track->active) {
|
||||
LOG_ERROR("Invalid track: %d", track_id);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(
|
||||
"Playing track %d (real: %d), looped: %d", track_id, track_idx + 1,
|
||||
is_looped);
|
||||
|
||||
m_AudioStreamID = Audio_Stream_CreateFromFile(m_ChosenSpec->path);
|
||||
if (m_AudioStreamID < 0) {
|
||||
LOG_ERROR("Failed to create music stream for track %d", track_id);
|
||||
return;
|
||||
}
|
||||
|
||||
g_CD_TrackID = track_id;
|
||||
m_TrackCurrent = track_id;
|
||||
if (is_looped) {
|
||||
m_TrackLooped = track_id;
|
||||
}
|
||||
|
||||
Audio_Stream_SetVolume(m_AudioStreamID, m_MusicVolume);
|
||||
Audio_Stream_SetFinishCallback(m_AudioStreamID, Music_StreamFinished, NULL);
|
||||
Audio_Stream_SetStartTimestamp(m_AudioStreamID, track->from / 1000.0);
|
||||
Audio_Stream_SetStopTimestamp(m_AudioStreamID, track->to / 1000.0);
|
||||
Audio_Stream_SeekTimestamp(m_AudioStreamID, track->from / 1000.0);
|
||||
Audio_Stream_SetIsLooped(m_AudioStreamID, is_looped);
|
||||
}
|
||||
|
||||
void __cdecl Music_Stop(void)
|
||||
{
|
||||
if (m_AudioStreamID < 0) {
|
||||
return;
|
||||
}
|
||||
m_TrackCurrent = MX_INACTIVE;
|
||||
m_TrackLooped = MX_INACTIVE;
|
||||
Audio_Stream_Close(m_AudioStreamID);
|
||||
}
|
||||
|
||||
bool __cdecl Music_PlaySynced(int16_t track_id)
|
||||
{
|
||||
Music_Play(track_id, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t __cdecl Music_GetFrames(void)
|
||||
{
|
||||
if (m_AudioStreamID < 0) {
|
||||
return 0;
|
||||
}
|
||||
return Audio_Stream_GetTimestamp(m_AudioStreamID) * FRAMES_PER_SECOND
|
||||
* TICKS_PER_FRAME / 1000.0;
|
||||
}
|
||||
|
||||
void __cdecl Music_SetVolume(int32_t volume)
|
||||
{
|
||||
m_MusicVolume = volume ? volume / 255.0f : 0.0f;
|
||||
if (m_AudioStreamID >= 0) {
|
||||
Audio_Stream_SetVolume(m_AudioStreamID, m_MusicVolume);
|
||||
}
|
||||
}
|
11
src/game/music/music_backend.h
Normal file
11
src/game/music/music_backend.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct MUSIC_BACKEND {
|
||||
bool (*init)(struct MUSIC_BACKEND *backend);
|
||||
const char *(*describe)(const struct MUSIC_BACKEND *backend);
|
||||
int32_t (*play)(const struct MUSIC_BACKEND *backend, int32_t track_id);
|
||||
void *data;
|
||||
} MUSIC_BACKEND;
|
197
src/game/music/music_backend_cdaudio.c
Normal file
197
src/game/music/music_backend_cdaudio.c
Normal file
@ -0,0 +1,197 @@
|
||||
#include "game/music/music_backend_cdaudio.h"
|
||||
|
||||
#include <libtrx/engine/audio.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/log.h>
|
||||
#include <libtrx/memory.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define MAX_CD_TRACKS 60
|
||||
|
||||
typedef struct {
|
||||
uint64_t from;
|
||||
uint64_t to;
|
||||
bool active;
|
||||
} CDAUDIO_TRACK;
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
const char *description;
|
||||
CDAUDIO_TRACK *tracks;
|
||||
} BACKEND_DATA;
|
||||
|
||||
static bool Music_Backend_CDAudio_Parse(BACKEND_DATA *const data);
|
||||
static bool Music_Backend_CDAudio_Init(MUSIC_BACKEND *const backend);
|
||||
static const char *Music_Backend_CDAudio_Describe(
|
||||
const MUSIC_BACKEND *const backend);
|
||||
static int32_t Music_Backend_CDAudio_Play(
|
||||
const MUSIC_BACKEND *const backend, int32_t track_id);
|
||||
|
||||
static bool Music_Backend_CDAudio_Parse(BACKEND_DATA *const data)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
char *track_content = NULL;
|
||||
size_t track_content_size;
|
||||
if (!File_Load("audio/cdaudio.dat", &track_content, &track_content_size)) {
|
||||
LOG_WARNING("Cannot find CDAudio control file");
|
||||
return false;
|
||||
}
|
||||
|
||||
data->tracks = Memory_Alloc(sizeof(CDAUDIO_TRACK) * MAX_CD_TRACKS);
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset < track_content_size) {
|
||||
while (track_content[offset] == '\n' || track_content[offset] == '\r') {
|
||||
if (++offset >= track_content_size) {
|
||||
goto parse_end;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t track_num;
|
||||
uint64_t from;
|
||||
uint64_t to;
|
||||
int32_t result = sscanf(
|
||||
&track_content[offset], "%" PRIu64 " %" PRIu64 " %" PRIu64,
|
||||
&track_num, &from, &to);
|
||||
|
||||
if (result == 3 && track_num > 0 && track_num <= MAX_CD_TRACKS) {
|
||||
int32_t track_idx = track_num - 1;
|
||||
data->tracks[track_idx].active = true;
|
||||
data->tracks[track_idx].from = from;
|
||||
data->tracks[track_idx].to = to;
|
||||
}
|
||||
|
||||
while (track_content[offset] != '\n' && track_content[offset] != '\r') {
|
||||
if (++offset >= track_content_size) {
|
||||
goto parse_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parse_end:
|
||||
Memory_Free(track_content);
|
||||
|
||||
// reindex wrong track boundaries
|
||||
for (int32_t i = 0; i < MAX_CD_TRACKS; i++) {
|
||||
if (!data->tracks[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < MAX_CD_TRACKS - 1
|
||||
&& data->tracks[i].from >= data->tracks[i].to) {
|
||||
for (int32_t j = i + 1; j < MAX_CD_TRACKS; j++) {
|
||||
if (data->tracks[j].active) {
|
||||
data->tracks[i].to = data->tracks[j].from;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data->tracks[i].from >= data->tracks[i].to && i > 0) {
|
||||
for (int32_t j = i - 1; j >= 0; j--) {
|
||||
if (data->tracks[j].active) {
|
||||
data->tracks[i].from = data->tracks[j].to;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Music_Backend_CDAudio_Init(MUSIC_BACKEND *const backend)
|
||||
{
|
||||
assert(backend != NULL);
|
||||
BACKEND_DATA *data = backend->data;
|
||||
assert(data != NULL);
|
||||
|
||||
MYFILE *const fp = File_Open(data->path, FILE_OPEN_READ);
|
||||
if (fp == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Music_Backend_CDAudio_Parse(data)) {
|
||||
LOG_ERROR("Failed to parse CDAudio data");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *Music_Backend_CDAudio_Describe(
|
||||
const MUSIC_BACKEND *const backend)
|
||||
{
|
||||
assert(backend != NULL);
|
||||
const BACKEND_DATA *const data = backend->data;
|
||||
assert(data != NULL);
|
||||
return data->description;
|
||||
}
|
||||
|
||||
static int32_t Music_Backend_CDAudio_Play(
|
||||
const MUSIC_BACKEND *const backend, int32_t track_id)
|
||||
{
|
||||
assert(backend != NULL);
|
||||
const BACKEND_DATA *const data = backend->data;
|
||||
assert(data != NULL);
|
||||
|
||||
const int32_t track_idx = track_id - 1;
|
||||
const CDAUDIO_TRACK *track = &data->tracks[track_idx];
|
||||
if (track_idx < 0 || track_idx >= MAX_CD_TRACKS) {
|
||||
LOG_ERROR("Invalid track: %d", track_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!track->active) {
|
||||
LOG_ERROR("Invalid track: %d", track_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t audio_stream_id = Audio_Stream_CreateFromFile(data->path);
|
||||
Audio_Stream_SetStartTimestamp(audio_stream_id, track->from / 1000.0);
|
||||
Audio_Stream_SetStopTimestamp(audio_stream_id, track->to / 1000.0);
|
||||
Audio_Stream_SeekTimestamp(audio_stream_id, track->from / 1000.0);
|
||||
return audio_stream_id;
|
||||
}
|
||||
|
||||
MUSIC_BACKEND *Music_Backend_CDAudio_Factory(const char *path)
|
||||
{
|
||||
assert(path != NULL);
|
||||
|
||||
const char *description_fmt = "CDAudio (path: %s)";
|
||||
const size_t description_size = snprintf(NULL, 0, description_fmt, path);
|
||||
char *description = Memory_Alloc(description_size + 1);
|
||||
sprintf(description, description_fmt, path);
|
||||
|
||||
BACKEND_DATA *data = Memory_Alloc(sizeof(BACKEND_DATA));
|
||||
data->path = Memory_DupStr(path);
|
||||
data->description = description;
|
||||
|
||||
MUSIC_BACKEND *backend = Memory_Alloc(sizeof(MUSIC_BACKEND));
|
||||
backend->data = data;
|
||||
backend->init = Music_Backend_CDAudio_Init;
|
||||
backend->describe = Music_Backend_CDAudio_Describe;
|
||||
backend->play = Music_Backend_CDAudio_Play;
|
||||
return backend;
|
||||
}
|
||||
|
||||
void Music_Backend_CDAudio_Destroy(MUSIC_BACKEND *backend)
|
||||
{
|
||||
if (backend == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (backend->data != NULL) {
|
||||
BACKEND_DATA *const data = backend->data;
|
||||
Memory_FreePointer(&data->path);
|
||||
Memory_FreePointer(&data->description);
|
||||
Memory_FreePointer(&data->tracks);
|
||||
}
|
||||
Memory_FreePointer(&backend->data);
|
||||
Memory_FreePointer(&backend);
|
||||
}
|
6
src/game/music/music_backend_cdaudio.h
Normal file
6
src/game/music/music_backend_cdaudio.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/music/music_backend.h"
|
||||
|
||||
MUSIC_BACKEND *Music_Backend_CDAudio_Factory(const char *path);
|
||||
void Music_Backend_CDAudio_Destroy(MUSIC_BACKEND *backend);
|
107
src/game/music/music_backend_files.c
Normal file
107
src/game/music/music_backend_files.c
Normal file
@ -0,0 +1,107 @@
|
||||
|
||||
#include "game/music/music_backend_files.h"
|
||||
|
||||
#include <libtrx/engine/audio.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/log.h>
|
||||
#include <libtrx/memory.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct {
|
||||
const char *dir;
|
||||
const char *description;
|
||||
} BACKEND_DATA;
|
||||
|
||||
static const char *m_ExtensionsToTry[] = { ".flac", ".ogg", ".mp3", ".wav",
|
||||
NULL };
|
||||
|
||||
static char *Music_Backend_Files_GetTrackFileName(
|
||||
const char *base_dir, int32_t track);
|
||||
|
||||
static bool Music_Backend_Files_Init(MUSIC_BACKEND *const backend);
|
||||
static int32_t Music_Backend_Files_Play(
|
||||
const MUSIC_BACKEND *const backend, int32_t track_id);
|
||||
|
||||
static char *Music_Backend_Files_GetTrackFileName(
|
||||
const char *base_dir, int32_t track)
|
||||
{
|
||||
char file_path[64];
|
||||
sprintf(file_path, "%s/track%02d.flac", base_dir, track);
|
||||
char *result = File_GuessExtension(file_path, m_ExtensionsToTry);
|
||||
if (!File_Exists(file_path)) {
|
||||
Memory_FreePointer(&result);
|
||||
sprintf(file_path, "%s/%d.flac", base_dir, track);
|
||||
result = File_GuessExtension(file_path, m_ExtensionsToTry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool Music_Backend_Files_Init(MUSIC_BACKEND *const backend)
|
||||
{
|
||||
assert(backend != NULL);
|
||||
const BACKEND_DATA *data = backend->data;
|
||||
assert(data->dir != NULL);
|
||||
return File_DirExists(data->dir);
|
||||
}
|
||||
|
||||
static const char *Music_Backend_Files_Describe(
|
||||
const MUSIC_BACKEND *const backend)
|
||||
{
|
||||
assert(backend != NULL);
|
||||
const BACKEND_DATA *const data = backend->data;
|
||||
assert(data != NULL);
|
||||
return data->description;
|
||||
}
|
||||
|
||||
static int32_t Music_Backend_Files_Play(
|
||||
const MUSIC_BACKEND *const backend, int32_t track_id)
|
||||
{
|
||||
assert(backend != NULL);
|
||||
const BACKEND_DATA *const data = backend->data;
|
||||
assert(data != NULL);
|
||||
|
||||
char *file_path = Music_Backend_Files_GetTrackFileName(data->dir, track_id);
|
||||
if (file_path == NULL) {
|
||||
LOG_ERROR("Invalid track: %d", track_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Audio_Stream_CreateFromFile(file_path);
|
||||
}
|
||||
|
||||
MUSIC_BACKEND *Music_Backend_Files_Factory(const char *path)
|
||||
{
|
||||
assert(path != NULL);
|
||||
|
||||
const char *description_fmt = "Directory (directory: %s)";
|
||||
const size_t description_size = snprintf(NULL, 0, description_fmt, path);
|
||||
char *description = Memory_Alloc(description_size + 1);
|
||||
sprintf(description, description_fmt, path);
|
||||
|
||||
BACKEND_DATA *data = Memory_Alloc(sizeof(BACKEND_DATA));
|
||||
data->dir = Memory_DupStr(path);
|
||||
data->description = description;
|
||||
|
||||
MUSIC_BACKEND *backend = Memory_Alloc(sizeof(MUSIC_BACKEND));
|
||||
backend->data = data;
|
||||
backend->init = Music_Backend_Files_Init;
|
||||
backend->describe = Music_Backend_Files_Describe;
|
||||
backend->play = Music_Backend_Files_Play;
|
||||
return backend;
|
||||
}
|
||||
|
||||
void Music_Backend_Files_Destroy(MUSIC_BACKEND *backend)
|
||||
{
|
||||
if (backend == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (backend->data != NULL) {
|
||||
BACKEND_DATA *const data = backend->data;
|
||||
Memory_FreePointer(&data->dir);
|
||||
Memory_FreePointer(&data->description);
|
||||
}
|
||||
Memory_FreePointer(&backend->data);
|
||||
Memory_FreePointer(&backend);
|
||||
}
|
6
src/game/music/music_backend_files.h
Normal file
6
src/game/music/music_backend_files.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/music/music_backend.h"
|
||||
|
||||
MUSIC_BACKEND *Music_Backend_Files_Factory(const char *path);
|
||||
void Music_Backend_Files_Destroy(MUSIC_BACKEND *backend);
|
182
src/game/music/music_main.c
Normal file
182
src/game/music/music_main.c
Normal file
@ -0,0 +1,182 @@
|
||||
#include "game/music.h"
|
||||
|
||||
#include "game/music/music_backend.h"
|
||||
#include "game/music/music_backend_cdaudio.h"
|
||||
#include "game/music/music_backend_files.h"
|
||||
#include "global/const.h"
|
||||
#include "global/funcs.h"
|
||||
#include "global/types.h"
|
||||
#include "global/vars.h"
|
||||
|
||||
#include <libtrx/engine/audio.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/log.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static MUSIC_TRACK_ID m_TrackCurrent = MX_INACTIVE;
|
||||
static MUSIC_TRACK_ID m_TrackLooped = MX_INACTIVE;
|
||||
|
||||
static bool m_Initialized = false;
|
||||
static float m_MusicVolume = 0.0f;
|
||||
static int m_AudioStreamID = -1;
|
||||
static const MUSIC_BACKEND *m_Backend = NULL;
|
||||
|
||||
static const MUSIC_BACKEND *Music_FindBackend(void);
|
||||
static void Music_StreamFinished(int stream_id, void *user_data);
|
||||
|
||||
static const MUSIC_BACKEND *Music_FindBackend(void)
|
||||
{
|
||||
MUSIC_BACKEND *all_backends[] = {
|
||||
Music_Backend_Files_Factory("music"),
|
||||
Music_Backend_CDAudio_Factory("audio/cdaudio.wav"),
|
||||
Music_Backend_CDAudio_Factory("audio/cdaudio.mp3"),
|
||||
NULL,
|
||||
};
|
||||
|
||||
MUSIC_BACKEND **backend_ptr = all_backends;
|
||||
while (true) {
|
||||
MUSIC_BACKEND *backend = *backend_ptr;
|
||||
if (backend == NULL) {
|
||||
break;
|
||||
}
|
||||
if (backend->init(backend)) {
|
||||
return backend;
|
||||
}
|
||||
backend_ptr++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void Music_StreamFinished(const int stream_id, void *const user_data)
|
||||
{
|
||||
// When a stream finishes, play the remembered background BGM.
|
||||
if (stream_id == m_AudioStreamID) {
|
||||
m_AudioStreamID = -1;
|
||||
if (m_TrackLooped >= 0) {
|
||||
Music_Play(m_TrackLooped, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool __cdecl Music_Init(void)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// TODO: remove this guard once Music_Init can be called in a proper place
|
||||
if (m_Initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Audio_Init()) {
|
||||
LOG_ERROR("Failed to initialize libtrx sound system");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
m_Backend = Music_FindBackend();
|
||||
if (m_Backend == NULL) {
|
||||
LOG_ERROR("No music backend is available");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
LOG_ERROR("Chosen music backend: %s", m_Backend->describe(m_Backend));
|
||||
result = true;
|
||||
Music_SetVolume(25 * g_OptionMusicVolume + 5);
|
||||
|
||||
finish:
|
||||
m_TrackCurrent = MX_INACTIVE;
|
||||
m_TrackLooped = MX_INACTIVE;
|
||||
m_Initialized = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void __cdecl Music_Shutdown(void)
|
||||
{
|
||||
if (m_AudioStreamID < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are only interested in calling Music_StreamFinished if a stream
|
||||
// finished by itself. In cases where we end the streams early by hand,
|
||||
// we clear the finish callback in order to avoid resuming the BGM playback
|
||||
// just after we stop it.
|
||||
Audio_Stream_SetFinishCallback(m_AudioStreamID, NULL, NULL);
|
||||
Audio_Stream_Close(m_AudioStreamID);
|
||||
}
|
||||
|
||||
void __cdecl Music_Play(int16_t track_id, bool is_looped)
|
||||
{
|
||||
if (track_id == m_TrackCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: this should be called in shell instead, once per game launch
|
||||
Music_Init();
|
||||
|
||||
Audio_Stream_Close(m_AudioStreamID);
|
||||
if (g_OptionMusicVolume == 0) {
|
||||
LOG_DEBUG("Not playing track %d because the game is silent", track_id);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (m_Backend == NULL) {
|
||||
LOG_DEBUG(
|
||||
"Not playing track %d because no backend is available", track_id);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
const int32_t real_track_id = Music_GetRealTrack(track_id);
|
||||
LOG_DEBUG(
|
||||
"Playing track %d (real: %d), looped: %d", track_id, real_track_id,
|
||||
is_looped);
|
||||
|
||||
m_AudioStreamID = m_Backend->play(m_Backend, real_track_id);
|
||||
if (m_AudioStreamID < 0) {
|
||||
LOG_ERROR("Failed to create music stream for track %d", track_id);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Audio_Stream_SetIsLooped(m_AudioStreamID, is_looped);
|
||||
Audio_Stream_SetVolume(m_AudioStreamID, m_MusicVolume);
|
||||
Audio_Stream_SetFinishCallback(m_AudioStreamID, Music_StreamFinished, NULL);
|
||||
|
||||
finish:
|
||||
g_CD_TrackID = track_id;
|
||||
m_TrackCurrent = track_id;
|
||||
if (is_looped) {
|
||||
m_TrackLooped = track_id;
|
||||
}
|
||||
}
|
||||
|
||||
void __cdecl Music_Stop(void)
|
||||
{
|
||||
if (m_AudioStreamID < 0) {
|
||||
return;
|
||||
}
|
||||
m_TrackCurrent = MX_INACTIVE;
|
||||
m_TrackLooped = MX_INACTIVE;
|
||||
Audio_Stream_Close(m_AudioStreamID);
|
||||
}
|
||||
|
||||
bool __cdecl Music_PlaySynced(int16_t track_id)
|
||||
{
|
||||
Music_Play(track_id, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t __cdecl Music_GetFrames(void)
|
||||
{
|
||||
if (m_AudioStreamID < 0) {
|
||||
return 0;
|
||||
}
|
||||
return Audio_Stream_GetTimestamp(m_AudioStreamID) * FRAMES_PER_SECOND
|
||||
* TICKS_PER_FRAME / 1000.0;
|
||||
}
|
||||
|
||||
void __cdecl Music_SetVolume(int32_t volume)
|
||||
{
|
||||
m_MusicVolume = volume ? volume / 255.0f : 0.0f;
|
||||
if (m_AudioStreamID >= 0) {
|
||||
Audio_Stream_SetVolume(m_AudioStreamID, m_MusicVolume);
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 67f6f1110768381596724250bd420a1e4497dfa0
|
||||
Subproject commit 0f6d84dcd5e437a9b24989b9cf84fa163df33a6a
|
@ -10,7 +10,9 @@ run_script(
|
||||
TR2X_REPO_DIR / "build/windows",
|
||||
],
|
||||
system_include_dirs=[TR2X_REPO_DIR / "subprojects/libtrx/include"],
|
||||
own_include_map={},
|
||||
own_include_map={
|
||||
"game/music/music_main.c": "game/music.h",
|
||||
},
|
||||
fix_map={},
|
||||
forced_order=["<ddrawi.h>", "<d3dhal.h>"],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user