1
0
Fork 0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-11-05 18:59:41 +01:00
VVVVVV/desktop_version/src/Music.cpp
Dav999 133adba10e Fix remaining audio speedup/slowdown bug
Fixes #1057.

Based on Ethan's hunch, I simply removed the format comparison that
decides whether to halt and restart, or reuse the voice. Voices are
now always restarted when playing a new track.

This also simplifies the code somewhat: `MusicTrack::musicVoiceFormat`
was now no longer used, and an `if (!IsHalted())` was no longer
necessary because `Halt()` already does that. So those are now removed
as well.
2023-11-28 20:16:38 -05:00

1318 lines
35 KiB
C++

#define MUSIC_DEFINITION
#include "Music.h"
#include <SDL.h>
#include <FAudio.h>
#include <physfsrwops.h>
#include "Alloc.h"
#include "BinaryBlob.h"
#include "FileSystemUtils.h"
#include "Game.h"
#include "Graphics.h"
#include "Map.h"
#include "Script.h"
#include "Unused.h"
#include "UtilityClass.h"
#include "Vlogging.h"
#include <vector>
/* stb_vorbis */
#define malloc SDL_malloc
#define realloc SDL_realloc
#define free VVV_free
#ifdef memset /* Thanks, Apple! */
#undef memset
#endif
#define memset SDL_memset
#ifdef memcpy /* Thanks, Apple! */
#undef memcpy
#endif
#define memcpy SDL_memcpy
#define memcmp SDL_memcmp
#define pow SDL_pow
#define log(x) SDL_log(x)
#define sin(x) SDL_sin(x)
#define cos(x) SDL_cos(x)
#define floor SDL_floor
#define abs(x) SDL_abs(x)
#define ldexp(v, e) SDL_scalbn((v), (e))
#define exp(x) SDL_exp(x)
#define qsort SDL_qsort
#define assert SDL_assert
#define FILE SDL_RWops
#ifdef SEEK_SET
#undef SEEK_SET
#endif
#ifdef SEEK_CUR
#undef SEEK_CUR
#endif
#ifdef SEEK_END
#undef SEEK_END
#endif
#ifdef EOF
#undef EOF
#endif
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#define EOF -1
#define fopen(path, mode) SDL_RWFromFile(path, mode)
#define fopen_s(io, path, mode) (!(*io = fopen(path, mode)))
#define fclose(io) SDL_RWclose(io)
#define fread(dst, size, count, io) SDL_RWread(io, dst, size, count)
#define fseek(io, offset, whence) SDL_RWseek(io, offset, whence)
#define ftell(io) SDL_RWtell(io)
#define FAudio_alloca(x) SDL_stack_alloc(uint8_t, x)
#define FAudio_dealloca(x) SDL_stack_free(x)
#define STB_VORBIS_NO_PUSHDATA_API 1
#define STB_VORBIS_NO_INTEGER_CONVERSION 1
#include <stb_vorbis.h>
/* End stb_vorbis include */
#define VVV_MAX_VOLUME 128
#define VVV_MAX_CHANNELS 8
class SoundTrack;
class MusicTrack;
static std::vector<SoundTrack> soundTracks;
static std::vector<MusicTrack> musicTracks;
static FAudio* faudioctx = NULL;
static FAudioMasteringVoice* masteringvoice = NULL;
class SoundTrack
{
public:
SoundTrack(const char* fileName)
{
unsigned char* mem;
size_t length;
voice_index = -1;
FILESYSTEM_loadAssetToMemory(fileName, &mem, &length);
if (mem == NULL)
{
vlog_error("Unable to load sound file %s", fileName);
SDL_assert(0 && "Sound file missing!");
return;
}
SDL_zerop(this);
if (length >= 4 && SDL_memcmp(mem, "OggS", 4) == 0)
{
LoadOGG(fileName, mem, length);
callbacks.OnBufferStart = &SoundTrack::refillReserve;
callbacks.OnBufferEnd = &SoundTrack::swapBuffers;
}
else
{
LoadWAV(fileName, mem, length);
}
}
void LoadWAV(const char* fileName, unsigned char* mem, const size_t length)
{
SDL_AudioSpec spec;
SDL_RWops *fileIn;
fileIn = SDL_RWFromConstMem(mem, length);
if (SDL_LoadWAV_RW(fileIn, 1, &spec, &wav_buffer, &wav_length) == NULL)
{
vlog_error("Unable to load WAV file %s", fileName);
goto end;
}
format.nChannels = spec.channels;
format.nSamplesPerSec = spec.freq;
format.wFormatTag = FAUDIO_FORMAT_PCM;
format.wBitsPerSample = SDL_AUDIO_BITSIZE(spec.format);
format.nBlockAlign = format.nChannels * format.wBitsPerSample;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
valid = true;
end:
VVV_free(mem);
}
void LoadOGG(const char* fileName, unsigned char* mem, const size_t length)
{
int err;
stb_vorbis_info vorbis_info;
vorbis = stb_vorbis_open_memory(mem, length, &err, NULL);
if (vorbis == NULL)
{
vlog_error("Unable to create Vorbis handle for %s, error %d", fileName, err);
VVV_free(mem);
return;
}
vorbis_info = stb_vorbis_get_info(vorbis);
format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = sizeof(float) * 8;
format.nChannels = vorbis_info.channels;
format.nSamplesPerSec = vorbis_info.sample_rate;
format.nBlockAlign = format.nChannels * format.wBitsPerSample;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
channels = format.nChannels;
size = format.nAvgBytesPerSec / 20;
decoded_buf_playing = (Uint8*) SDL_malloc(size);
decoded_buf_reserve = (Uint8*) SDL_malloc(size);
ogg_file = mem;
valid = true;
}
void Dispose(void)
{
VVV_free(wav_buffer);
VVV_free(decoded_buf_playing);
VVV_free(decoded_buf_reserve);
VVV_freefunc(stb_vorbis_close, vorbis);
VVV_free(ogg_file);
}
void Play(void)
{
if (!valid)
{
return;
}
for (int i = 0; i < VVV_MAX_CHANNELS; i++)
{
FAudioVoiceState voicestate;
FAudioSourceVoice_GetState(voices[i], &voicestate, 0);
if (voicestate.BuffersQueued == 0)
{
if (SDL_memcmp(&voice_formats[i], &format, sizeof(format)) != 0)
{
VVV_freefunc(FAudioVoice_DestroyVoice, voices[i]);
if (vorbis != NULL)
{
FAudio_CreateSourceVoice(faudioctx, &voices[i], &format, 0, 2.0f, &callbacks, NULL, NULL);
}
else
{
FAudio_CreateSourceVoice(faudioctx, &voices[i], &format, 0, 2.0f, NULL, NULL, NULL);
}
voice_formats[i] = format;
}
FAudioBuffer faudio_buffer = {
FAUDIO_END_OF_STREAM, /* Flags */
wav_length * 8, /* AudioBytes */
wav_buffer, /* AudioData */
0, /* playbegin */
0, /* playlength */
0, /* LoopBegin */
0, /* LoopLength */
0, /* LoopCount */
NULL
};
if (vorbis != NULL)
{
stb_vorbis_seek_start(vorbis);
faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(
vorbis,
channels,
(float*) decoded_buf_playing,
size / sizeof(float)
);
faudio_buffer.AudioBytes = size;
faudio_buffer.pAudioData = decoded_buf_playing;
faudio_buffer.pContext = this;
}
if (FAudioSourceVoice_SubmitSourceBuffer(voices[i], &faudio_buffer, NULL))
{
vlog_error("Unable to queue sound buffer");
voice_index = -1;
return;
}
FAudioVoice_SetVolume(voices[i], volume, FAUDIO_COMMIT_NOW);
if (FAudioSourceVoice_Start(voices[i], 0, FAUDIO_COMMIT_NOW))
{
vlog_error("Unable to start voice processing");
voice_index = -1;
}
voice_index = i;
return;
}
}
}
static void Init(int audio_rate)
{
if (voices == NULL)
{
voices = (FAudioSourceVoice**) SDL_malloc(sizeof(FAudioSourceVoice*) * VVV_MAX_CHANNELS);
for (int i = 0; i < VVV_MAX_CHANNELS; i++)
{
FAudioWaveFormatEx format;
format.nChannels = 1; /* Assume 1 for SoundTracks. Will be recreated if mismatched during play */
format.nSamplesPerSec = audio_rate;
format.wFormatTag = FAUDIO_FORMAT_PCM;
format.wBitsPerSample = 16;
format.nBlockAlign = format.nChannels * format.wBitsPerSample;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
voice_formats[i] = format;
if (FAudio_CreateSourceVoice(faudioctx, &voices[i], &format, 0, 2.0f, NULL, NULL, NULL))
{
vlog_error("Unable to create source voice no. %i", i);
return;
}
}
}
}
static void Pause(void)
{
for (size_t i = 0; i < VVV_MAX_CHANNELS; i++)
{
FAudioSourceVoice_Stop(voices[i], 0, FAUDIO_COMMIT_NOW);
}
}
static void Resume(void)
{
for (size_t i = 0; i < VVV_MAX_CHANNELS; i++)
{
FAudioSourceVoice_Start(voices[i], 0, FAUDIO_COMMIT_NOW);
}
}
static void Destroy(void)
{
if (voices != NULL)
{
for (int i = 0; i < VVV_MAX_CHANNELS; i++)
{
VVV_freefunc(FAudioVoice_DestroyVoice, voices[i]);
}
VVV_free(voices);
}
}
static void SetVolume(int soundVolume)
{
volume = (float) soundVolume / VVV_MAX_VOLUME;
for (size_t i = 0; i < VVV_MAX_CHANNELS; i++)
{
FAudioVoice_SetVolume(voices[i], volume, FAUDIO_COMMIT_NOW);
}
}
static void refillReserve(FAudioVoiceCallback* callback, void* ctx)
{
bool inbounds;
SoundTrack* t = (SoundTrack*) ctx;
FAudioBuffer faudio_buffer;
SDL_zero(faudio_buffer);
UNUSED(callback);
faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float));
faudio_buffer.AudioBytes = t->size;
faudio_buffer.pAudioData = t->decoded_buf_reserve;
faudio_buffer.pContext = t;
if (faudio_buffer.PlayLength == 0)
{
return;
}
inbounds = t->voice_index >= 0 && t->voice_index < VVV_MAX_CHANNELS;
if (!inbounds)
{
return;
}
FAudioSourceVoice_SubmitSourceBuffer(voices[t->voice_index], &faudio_buffer, NULL);
}
static void swapBuffers(FAudioVoiceCallback* callback, void* ctx)
{
SoundTrack* t = (SoundTrack*) ctx;
Uint8* tmp = t->decoded_buf_playing;
UNUSED(callback);
t->decoded_buf_playing = t->decoded_buf_reserve;
t->decoded_buf_reserve = tmp;
}
Uint8 *wav_buffer;
Uint32 wav_length;
FAudioWaveFormatEx format;
int voice_index;
unsigned char* ogg_file;
stb_vorbis* vorbis;
int channels;
Uint32 size;
Uint8* decoded_buf_playing;
Uint8* decoded_buf_reserve;
FAudioVoiceCallback callbacks;
bool valid;
static FAudioSourceVoice** voices;
static FAudioWaveFormatEx voice_formats[VVV_MAX_CHANNELS];
static float volume;
};
FAudioSourceVoice** SoundTrack::voices = NULL;
FAudioWaveFormatEx SoundTrack::voice_formats[VVV_MAX_CHANNELS];
float SoundTrack::volume = 0.0f;
class MusicTrack
{
public:
MusicTrack(SDL_RWops *rw)
{
SDL_zerop(this);
read_buf = (Uint8*) SDL_malloc(rw->size(rw));
SDL_RWread(rw, read_buf, rw->size(rw), 1);
int err;
stb_vorbis_info vorbis_info;
stb_vorbis_comment vorbis_comment;
vorbis = stb_vorbis_open_memory(read_buf, rw->size(rw), &err, NULL);
if (vorbis == NULL)
{
vlog_error("Unable to create Vorbis handle, error %d", err);
VVV_free(read_buf);
goto end;
}
vorbis_info = stb_vorbis_get_info(vorbis);
format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = sizeof(float) * 8;
format.nChannels = vorbis_info.channels;
format.nSamplesPerSec = vorbis_info.sample_rate;
format.nBlockAlign = format.nChannels * format.wBitsPerSample;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
channels = format.nChannels;
size = format.nAvgBytesPerSec / 20;
decoded_buf_playing = (Uint8*) SDL_malloc(size);
decoded_buf_reserve = (Uint8*) SDL_malloc(size);
loopbegin = 0;
looplength = 0;
vorbis_comment = stb_vorbis_get_comment(vorbis);
parseComments(this, vorbis_comment.comment_list, vorbis_comment.comment_list_length);
valid = true;
end:
SDL_RWclose(rw);
}
void Dispose(void)
{
VVV_freefunc(stb_vorbis_close, vorbis);
VVV_free(read_buf);
VVV_free(decoded_buf_playing);
VVV_free(decoded_buf_reserve);
if (!IsHalted())
{
VVV_freefunc(FAudioVoice_DestroyVoice, musicVoice);
}
}
bool Play(bool loop)
{
if (!valid)
{
return false;
}
shouldloop = loop;
sample_pos = 0;
stb_vorbis_seek_start(vorbis);
Halt();
SDL_zero(callbacks);
callbacks.OnBufferStart = &MusicTrack::refillReserve;
callbacks.OnBufferEnd = &MusicTrack::swapBuffers;
FAudio_CreateSourceVoice(faudioctx, &musicVoice, &format, 0, 2.0f, &callbacks, NULL, NULL);
FAudioBuffer faudio_buffer;
SDL_zero(faudio_buffer);
if (looplength == 0)
{
faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(vorbis, channels, (float*) decoded_buf_playing, size / sizeof(float));
}
else
{
int samples_read = stb_vorbis_get_samples_float_interleaved(vorbis, channels, (float*) decoded_buf_playing, size / sizeof(float));
faudio_buffer.PlayLength = SDL_min(samples_read, (loopbegin + looplength) - sample_pos);
}
faudio_buffer.AudioBytes = size;
faudio_buffer.pAudioData = decoded_buf_playing;
faudio_buffer.pContext = this;
sample_pos += faudio_buffer.PlayLength;
if (FAudioSourceVoice_SubmitSourceBuffer(musicVoice, &faudio_buffer, NULL))
{
vlog_error("Unable to queue sound buffer");
return false;
}
Resume();
return true;
}
static void Halt(void)
{
if (!IsHalted())
{
FAudioSourceVoice_FlushSourceBuffers(musicVoice);
VVV_freefunc(FAudioVoice_DestroyVoice, musicVoice);
paused = true;
}
}
static bool IsHalted(void)
{
return musicVoice == NULL;
}
static void Pause(void)
{
if (!IsHalted())
{
FAudioSourceVoice_Stop(musicVoice, 0, FAUDIO_COMMIT_NOW);
paused = true;
}
}
static bool IsPaused(void)
{
return paused || IsHalted();
}
static void Resume(void)
{
if (!IsHalted())
{
FAudioSourceVoice_Start(musicVoice, 0, FAUDIO_COMMIT_NOW);
paused = false;
}
}
static void SetVolume(int musicVolume)
{
float adj_vol = (float) musicVolume / VVV_MAX_VOLUME;
if (!IsHalted())
{
FAudioVoice_SetVolume(musicVoice, adj_vol, FAUDIO_COMMIT_NOW);
}
}
stb_vorbis* vorbis;
int channels;
Uint32 size;
int loopbegin;
int looplength;
int sample_pos; //stb_vorbis offset not yet functional on pulldata API. TODO Replace when fixed
FAudioVoiceCallback callbacks;
FAudioWaveFormatEx format;
Uint8* decoded_buf_playing;
Uint8* decoded_buf_reserve;
Uint8* read_buf;
bool shouldloop;
bool valid;
static bool paused;
static FAudioSourceVoice* musicVoice;
static void refillReserve(FAudioVoiceCallback* callback, void* ctx)
{
MusicTrack* t = (MusicTrack*) ctx;
FAudioBuffer faudio_buffer;
SDL_zero(faudio_buffer);
UNUSED(callback);
if (t->looplength == 0)
{
faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float));
}
else
{
int samples_read = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float));
faudio_buffer.PlayLength = SDL_min(samples_read, (t->loopbegin + t->looplength) - t->sample_pos);
}
faudio_buffer.AudioBytes = t->size;
faudio_buffer.pAudioData = t->decoded_buf_reserve;
faudio_buffer.pContext = t;
if (faudio_buffer.PlayLength == 0)
{
if (t->shouldloop)
{
stb_vorbis_seek(t->vorbis, t->loopbegin);
t->sample_pos = t->loopbegin;
if (t->looplength != 0)
{
int samples_read = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float));
faudio_buffer.PlayLength = SDL_min(samples_read, (t->loopbegin + t->looplength) - t->sample_pos);
}
else
{
faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float));
}
if (faudio_buffer.PlayLength == 0)
{
return;
}
}
else
{
return;
}
}
t->sample_pos += faudio_buffer.PlayLength;
FAudioSourceVoice_SubmitSourceBuffer(musicVoice, &faudio_buffer, NULL);
}
static void swapBuffers(FAudioVoiceCallback* callback, void* ctx)
{
MusicTrack* t = (MusicTrack*) ctx;
Uint8* tmp = t->decoded_buf_playing;
UNUSED(callback);
t->decoded_buf_playing = t->decoded_buf_reserve;
t->decoded_buf_reserve = tmp;
}
/* Lifted from SDL_mixer, we used it in 2.3 and previous */
static void parseComments(
MusicTrack* t, char** comments, const int comment_list_length
) {
int loopend = 0;
for (int i = 0; i < comment_list_length; i++)
{
char* param = SDL_strdup(comments[i]);
if (param == NULL)
{
vlog_error(
"Could not allocate memory to parse '%s'. Ignoring comments.",
comments[i]
);
break;
}
char* argument = param;
char* value = SDL_strchr(param, '=');
if (value == NULL)
{
value = param + SDL_strlen(param);
}
else
{
*(value++) = '\0';
}
/* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
* string if it is present at position 4. */
char buf[5];
SDL_strlcpy(buf, argument, sizeof(buf));
if (SDL_strcasecmp(buf, "LOOP") == 0
&& ((argument[4] == '_') || (argument[4] == '-')))
{
SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
}
if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
{
t->loopbegin = _Mix_ParseTime(value, t->format.nSamplesPerSec);
}
else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0)
{
t->looplength = SDL_strtoll(value, NULL, 10);
}
else if (SDL_strcasecmp(argument, "LOOPEND") == 0)
{
loopend = _Mix_ParseTime(value, t->format.nSamplesPerSec);
}
if (t->loopbegin < 0 || t->looplength < 0 || loopend < 0)
{
vlog_warn(
"A track loop comment had a negative value. "
"Ignoring all comments for the track."
);
t->loopbegin = 0;
t->looplength = 0;
loopend = 0;
VVV_free(param);
break;
}
VVV_free(param);
}
if (loopend != 0)
{
t->looplength = loopend - t->loopbegin;
}
}
static int _Mix_ParseTime(char* time, const long samplerate_hz)
{
char* num_start = time;
char* p;
Sint64 result = 0;
int val;
/* Time is directly expressed as a sample position */
if (SDL_strchr(time, ':') == NULL)
{
return SDL_strtoll(time, NULL, 10);
}
for (p = time; *p != '\0'; ++p)
{
if (*p == '.' || *p == ':')
{
const char c = *p;
*p = '\0';
val = SDL_atoi(num_start);
if (val < 0)
{
return -1;
}
result = result * 60 + val;
num_start = p + 1;
*p = c;
}
if (*p == '.')
{
const double val_f = SDL_atof(p);
if (val_f < 0)
{
return -1;
}
return result * samplerate_hz + (Sint64) (val_f * samplerate_hz);
}
}
val = SDL_atoi(num_start);
if (val < 0)
{
return -1;
}
return (result * 60 + val) * samplerate_hz;
}
};
bool MusicTrack::paused = false;
FAudioSourceVoice* MusicTrack::musicVoice = NULL;
musicclass::musicclass(void)
{
safeToProcessMusic= false;
m_doFadeInVol = false;
m_doFadeOutVol = false;
musicVolume = 0;
user_music_volume = USER_VOLUME_MAX;
user_sound_volume = USER_VOLUME_MAX;
currentsong = -1;
haltedsong = -1;
nicechange = -1;
nicefade = false;
quick_fade = true;
usingmmmmmm = false;
}
void musicclass::init(void)
{
if (FAudioCreate(&faudioctx, 0, FAUDIO_DEFAULT_PROCESSOR))
{
vlog_error("Unable to initialize FAudio");
return;
}
if (FAudio_CreateMasteringVoice(faudioctx, &masteringvoice, 2, 44100, 0, 0, NULL))
{
vlog_error("Unable to create mastering voice");
return;
}
SoundTrack::Init(44100);
soundTracks.push_back(SoundTrack( "sounds/jump.wav" ));
soundTracks.push_back(SoundTrack( "sounds/jump2.wav" ));
soundTracks.push_back(SoundTrack( "sounds/hurt.wav" ));
soundTracks.push_back(SoundTrack( "sounds/souleyeminijingle.wav" ));
soundTracks.push_back(SoundTrack( "sounds/coin.wav" ));
soundTracks.push_back(SoundTrack( "sounds/save.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crumble.wav" ));
soundTracks.push_back(SoundTrack( "sounds/vanish.wav" ));
soundTracks.push_back(SoundTrack( "sounds/blip.wav" ));
soundTracks.push_back(SoundTrack( "sounds/preteleport.wav" ));
soundTracks.push_back(SoundTrack( "sounds/teleport.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crew1.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crew2.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crew3.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crew4.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crew5.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crew6.wav" ));
soundTracks.push_back(SoundTrack( "sounds/terminal.wav" ));
soundTracks.push_back(SoundTrack( "sounds/gamesaved.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crashing.wav" ));
soundTracks.push_back(SoundTrack( "sounds/blip2.wav" ));
soundTracks.push_back(SoundTrack( "sounds/countdown.wav" ));
soundTracks.push_back(SoundTrack( "sounds/go.wav" ));
soundTracks.push_back(SoundTrack( "sounds/crash.wav" ));
soundTracks.push_back(SoundTrack( "sounds/combine.wav" ));
soundTracks.push_back(SoundTrack( "sounds/newrecord.wav" ));
soundTracks.push_back(SoundTrack( "sounds/trophy.wav" ));
soundTracks.push_back(SoundTrack( "sounds/rescue.wav" ));
#ifdef VVV_COMPILEMUSIC
binaryBlob musicWriteBlob;
#define FOREACH_TRACK(blob, track_name) blob.AddFileToBinaryBlob("data/" track_name);
TRACK_NAMES(musicWriteBlob)
#undef FOREACH_TRACK
musicWriteBlob.writeBinaryBlob("data/BinaryMusic.vvv");
musicWriteBlob.clear();
#endif
num_mmmmmm_tracks = 0;
num_pppppp_tracks = 0;
if (!mmmmmm_blob.unPackBinary("mmmmmm.vvv"))
{
if (pppppp_blob.unPackBinary("vvvvvvmusic.vvv"))
{
vlog_info("Loading music from PPPPPP blob...");
mmmmmm = false;
usingmmmmmm=false;
int index;
SDL_RWops* rw;
#define TRACK_LOAD_BLOB(blob, track_name) \
index = blob.getIndex("data/" track_name); \
if (index >= 0 && index < blob.max_headers) \
{ \
rw = SDL_RWFromConstMem(blob.getAddress(index), blob.getSize(index)); \
if (rw == NULL) \
{ \
vlog_error("Unable to read music file header: %s", SDL_GetError()); \
} \
else \
{ \
musicTracks.push_back(MusicTrack(rw)); \
} \
}
#define FOREACH_TRACK(blob, track_name) TRACK_LOAD_BLOB(blob, track_name)
TRACK_NAMES(pppppp_blob)
#undef FOREACH_TRACK
}
else
{
vlog_info("Loading music from loose files...");
SDL_RWops* rw;
#define FOREACH_TRACK(_, track_name) \
rw = PHYSFSRWOPS_openRead(track_name); \
if (rw == NULL) \
{ \
vlog_error("Unable to read loose music file: %s", SDL_GetError()); \
} \
else \
{ \
musicTracks.push_back(MusicTrack(rw)); \
}
TRACK_NAMES(_)
#undef FOREACH_TRACK
}
}
else
{
vlog_info("Loading PPPPPP and MMMMMM blobs...");
mmmmmm = true;
int index;
SDL_RWops* rw;
#define FOREACH_TRACK(blob, track_name) TRACK_LOAD_BLOB(blob, track_name)
TRACK_NAMES(mmmmmm_blob)
num_mmmmmm_tracks += musicTracks.size();
size_t index_ = 0;
while (mmmmmm_blob.nextExtra(&index_))
{
rw = SDL_RWFromConstMem(mmmmmm_blob.getAddress(index_), mmmmmm_blob.getSize(index_));
musicTracks.push_back(MusicTrack( rw ));
num_mmmmmm_tracks++;
index_++;
}
bool ohCrap = pppppp_blob.unPackBinary("vvvvvvmusic.vvv");
SDL_assert(ohCrap && "Music not found!");
TRACK_NAMES(pppppp_blob)
#undef FOREACH_TRACK
#undef TRACK_LOAD_BLOB
}
num_pppppp_tracks += musicTracks.size() - num_mmmmmm_tracks;
SDL_RWops* rw;
size_t index_ = 0;
while (pppppp_blob.nextExtra(&index_))
{
rw = SDL_RWFromConstMem(pppppp_blob.getAddress(index_), pppppp_blob.getSize(index_));
musicTracks.push_back(MusicTrack( rw ));
num_pppppp_tracks++;
index_++;
}
}
void musicclass::destroy(void)
{
for (size_t i = 0; i < soundTracks.size(); ++i)
{
soundTracks[i].Dispose();
}
soundTracks.clear();
SoundTrack::Destroy();
for (size_t i = 0; i < musicTracks.size(); ++i)
{
musicTracks[i].Dispose();
}
musicTracks.clear();
pppppp_blob.clear();
mmmmmm_blob.clear();
VVV_freefunc(FAudioVoice_DestroyVoice, masteringvoice);
VVV_freefunc(FAudio_Release, faudioctx);
}
void musicclass::play(int t)
{
if (mmmmmm && usingmmmmmm)
{
// Don't conjoin this if-statement with the above one...
if (num_mmmmmm_tracks > 0)
{
t %= num_mmmmmm_tracks;
}
}
else if (num_pppppp_tracks > 0)
{
t %= num_pppppp_tracks;
}
if (mmmmmm && !usingmmmmmm)
{
t += num_mmmmmm_tracks;
}
safeToProcessMusic = true;
if (currentsong == t && !m_doFadeOutVol)
{
return;
}
currentsong = t;
haltedsong = -1;
if (t == -1)
{
return;
}
if (!INBOUNDS_VEC(t, musicTracks))
{
vlog_error("play() out-of-bounds!");
currentsong = -1;
return;
}
if (currentsong == Music_PATHCOMPLETE ||
currentsong == Music_PLENARY ||
(!map.custommode && (currentsong == Music_PATHCOMPLETE + num_mmmmmm_tracks
|| currentsong == Music_PLENARY + num_mmmmmm_tracks)))
{
// No fade in or repeat
if (musicTracks[t].Play(false))
{
m_doFadeInVol = false;
m_doFadeOutVol = false;
musicVolume = VVV_MAX_VOLUME;
MusicTrack::SetVolume(musicVolume);
}
}
else
{
if (m_doFadeOutVol)
{
// We're already fading out
nicechange = t;
nicefade = true;
currentsong = -1;
if (quick_fade)
{
fadeMusicVolumeOut(500); // fade out quicker
}
else
{
quick_fade = true;
}
}
else if (musicTracks[t].Play(true))
{
m_doFadeInVol = false;
m_doFadeOutVol = false;
fadeMusicVolumeIn(3000);
}
}
}
void musicclass::resume(void)
{
if (currentsong == -1)
{
currentsong = haltedsong;
haltedsong = -1;
}
MusicTrack::Resume();
}
void musicclass::resumefade(const int fadein_ms)
{
resume();
fadeMusicVolumeIn(fadein_ms);
}
void musicclass::fadein(void)
{
resumefade(3000); // 3000 ms fadein
}
void musicclass::pause(void)
{
MusicTrack::Pause();
}
void musicclass::haltdasmusik(void)
{
haltdasmusik(false);
}
void musicclass::haltdasmusik(const bool from_fade)
{
/* Just pauses music. This is intended. */
pause();
haltedsong = currentsong;
currentsong = -1;
m_doFadeInVol = false;
m_doFadeOutVol = false;
if (!from_fade)
{
nicefade = false;
nicechange = -1;
}
}
void musicclass::silencedasmusik(void)
{
musicVolume = 0;
m_doFadeInVol = false;
m_doFadeOutVol = false;
}
struct FadeState
{
int start_volume;
int end_volume;
int duration_ms;
int step_ms;
};
static struct FadeState fade;
enum FadeCode
{
Fade_continue,
Fade_finished
};
static enum FadeCode processmusicfade(struct FadeState* state, int* volume)
{
int range;
int new_volume;
if (state->duration_ms == 0 /* Fast path. */
|| state->start_volume == state->end_volume /* Fast path. */
|| state->step_ms >= state->duration_ms /* We're finished. */)
{
*volume = state->end_volume;
state->step_ms = 0;
return Fade_finished;
}
range = state->end_volume - state->start_volume;
new_volume = range * state->step_ms / state->duration_ms;
new_volume += state->start_volume;
*volume = new_volume;
state->step_ms += game.get_timestep();
return Fade_continue;
}
void musicclass::fadeMusicVolumeIn(int ms)
{
if (halted())
{
return;
}
m_doFadeInVol = true;
m_doFadeOutVol = false;
/* Ensure it starts at 0 */
musicVolume = 0;
/* Fix 1-frame glitch */
MusicTrack::SetVolume(0);
fade.step_ms = 0;
fade.duration_ms = ms;
fade.start_volume = 0;
fade.end_volume = VVV_MAX_VOLUME;
}
void musicclass::fadeMusicVolumeOut(const int fadeout_ms)
{
if (halted())
{
return;
}
m_doFadeInVol = false;
m_doFadeOutVol = true;
fade.step_ms = 0;
/* Duration is proportional to current volume. */
fade.duration_ms = fadeout_ms * musicVolume / VVV_MAX_VOLUME;
fade.start_volume = musicVolume;
fade.end_volume = 0;
}
void musicclass::fadeout(const bool quick_fade_ /*= true*/)
{
fadeMusicVolumeOut(quick_fade_ ? 500 : 2000);
quick_fade = quick_fade_;
}
void musicclass::processmusicfadein(void)
{
enum FadeCode fade_code = processmusicfade(&fade, &musicVolume);
if (fade_code == Fade_finished)
{
m_doFadeInVol = false;
}
}
void musicclass::processmusicfadeout(void)
{
enum FadeCode fade_code = processmusicfade(&fade, &musicVolume);
if (fade_code == Fade_finished)
{
musicVolume = 0;
m_doFadeOutVol = false;
haltdasmusik(true);
}
}
void musicclass::processmusic(void)
{
if(!safeToProcessMusic)
{
return;
}
if(m_doFadeInVol)
{
processmusicfadein();
}
if (m_doFadeOutVol)
{
processmusicfadeout();
}
/* This needs to come after processing fades */
if (nicefade && halted())
{
play(nicechange);
nicechange = -1;
nicefade = false;
}
}
void musicclass::niceplay(int t)
{
/* important: do nothing if the correct song is playing! */
if ((!mmmmmm && currentsong != t)
|| (mmmmmm && usingmmmmmm && currentsong != t)
|| (mmmmmm && !usingmmmmmm && currentsong != t + num_mmmmmm_tracks))
{
if (currentsong != -1)
{
fadeout(false);
}
nicefade = true;
}
nicechange = t;
}
static const int areamap[] = {
4, 3, 3, 3, 3, 3, 3, 3, 4,-2, 4, 4, 4,12,12,12,12,12,12,12,
4, 3, 3, 3, 3, 3, 3, 4, 4,-2, 4, 4, 4, 4,12,12,12,12,12,12,
4, 4, 4, 4, 3, 4, 4, 4, 4,-2, 4, 4, 4, 4,12,12,12,12,12,12,
4, 4, 4, 4, 3, 4, 4, 4, 4,-2, 4, 4, 1, 1, 1, 1,12,12,12,12,
4, 4, 3, 3, 3, 4, 4, 4, 4,-2,-2,-2, 1, 1, 1, 1, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 1, 1, 1, 1, 1, 1,11,11,-1, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 1, 1, 1, 1, 1, 1, 1,11,11,11,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 4, 4, 4, 1, 1, 1, 1, 1, 1, 3,
4, 4, 4, 4, 4, 4, 4, 4,-2,-2, 4, 4, 4, 1, 1, 1, 1, 1, 1, 4,
4, 4,-1,-1,-1, 4, 4, 4, 4,-2, 4, 4, 4, 1, 1, 1, 1, 1, 1, 4,
4, 4,-1,-1,-1, 4, 4, 4, 4,-2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 4, 1, 1, 1, 1, 1, 1, 4, 1, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 4, 1, 1, 1, 1, 1, 1, 4, 1, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4,-2, 4,-1,-3, 4, 4, 4, 4, 4, 1, 4,
4, 4, 4, 4, 4, 3, 3, 3, 4,-2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 3, 3, 3, 3, 3, 3, 4,-2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 3, 3, 3, 3, 3, 3, 3, 4,-2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 4, 4, 3, 4,-2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 4, 4, 3, 4,-2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
};
SDL_COMPILE_TIME_ASSERT(areamap, SDL_arraysize(areamap) == 20 * 20);
void musicclass::changemusicarea(int x, int y)
{
int room;
int track;
if (script.running)
{
return;
}
room = musicroom(x, y);
if (!INBOUNDS_ARR(room, areamap))
{
SDL_assert(0 && "Music map index out-of-bounds!");
return;
}
track = areamap[room];
switch (track)
{
case -1:
/* Don't change music. */
return;
case -2:
/* Special case: Tower music, changes with Flip Mode. */
if (graphics.setflipmode)
{
track = 9; /* ecroF evitisoP */
}
else
{
track = 2; /* Positive Force */
}
break;
case -3:
/* Special case: start of Space Station 2. */
if (game.intimetrial)
{
track = 1; /* Pushing Onwards */
}
else
{
track = 4; /* Passion for Exploring */
}
break;
}
niceplay(track);
}
void musicclass::playef(int t)
{
if (!INBOUNDS_VEC(t, soundTracks))
{
return;
}
soundTracks[t].Play();
}
void musicclass::pauseef(void)
{
SoundTrack::Pause();
}
void musicclass::resumeef(void)
{
SoundTrack::Resume();
}
bool musicclass::halted(void)
{
return MusicTrack::IsPaused();
}
void musicclass::updatemutestate(void)
{
if (game.muted)
{
MusicTrack::SetVolume(0);
SoundTrack::SetVolume(0);
}
else
{
SoundTrack::SetVolume(VVV_MAX_VOLUME * user_sound_volume / USER_VOLUME_MAX);
if (game.musicmuted)
{
MusicTrack::SetVolume(0);
}
else
{
MusicTrack::SetVolume(musicVolume * user_music_volume / USER_VOLUME_MAX);
}
}
}