// Copyright (c) 2014 Alex Diener. All rights reserved. #include "watertowerclassic/AudioManager.h" #include "jsonio/JSONParser.h" #include "pcmaudio/PCMAudio.h" #include "shell/ShellThreads.h" #define OV_EXCLUDE_STATIC_CALLBACKS #include "vorbisaudioio/VorbisAudioStream.h" #include #include #include #include "portaudio.h" #define MUSIC_BUFFER_COUNT 3 #define MUSIC_BUFFER_SIZE_SECONDS 1 #define AUDIO_CHANNEL_COUNT 96 #define FRAMES_PER_BUFFER_WINXP 2048 #define FRAMES_PER_BUFFER_WIN7 768 #if defined(WIN32) #include #define SIZE_T_FORMAT "%Iu" #else #define SIZE_T_FORMAT "%zu" #endif struct soundPlayState { bool active; PCMAudio * sound; unsigned long sampleIndex; }; static GameSession * gameSession; static PaStream * stream; static struct JSONNode * soundList; static PCMAudio ** sounds; static struct soundPlayState playingSounds[AUDIO_CHANNEL_COUNT]; static VorbisAudioStream * musicStream; static int16_t * musicSamples; static unsigned long musicFrameIndex; static unsigned long nextMusicChunkIndex; static bool musicPlaying, musicPaused; static int callback(const void * input, void * output, unsigned long frameCount, const PaStreamCallbackTimeInfo * timeInfo, PaStreamCallbackFlags flags, void * userData) { AudioManager_mixSamples(output, frameCount); return paContinue; } void AudioManager_globalInit(GameSession * inGameSession) { struct JSONParseError parseError; unsigned int soundIndex; PaError error; gameSession = inGameSession; soundList = JSONParser_loadFile("sounds.json", &parseError); if (soundList == NULL) { fprintf(stderr, "Error: Couldn't parse sounds.json: %s (" SIZE_T_FORMAT ")\n", parseError.description, parseError.charIndex); } else if (soundList->type != JSON_TYPE_OBJECT) { fprintf(stderr, "Error: sounds.json malformed (expected JSON_TYPE_OBJECT, but got %d)\n", soundList->type); } else { sounds = calloc(sizeof(PCMAudio *), soundList->value.count); for (soundIndex = 0; soundIndex < soundList->value.count; soundIndex++) { if (soundList->subitems[soundIndex].type != JSON_TYPE_STRING) { fprintf(stderr, "Error: sounds.json malformed (expected subitem %u to be JSON_TYPE_STRING, but got %d)\n", soundIndex, soundList->subitems[soundIndex].type); continue; } sounds[soundIndex] = gameSession->resourceManager->referenceResource(gameSession->resourceManager, "vorbis", soundList->subitems[soundIndex].value.string); if (sounds[soundIndex] == NULL) { fprintf(stderr, "Error: Couldn't load \"%s\" (sounds.json subitem %u)\n", soundList->subitems[soundIndex].value.string, soundIndex); continue; } } } musicSamples = malloc(sizeof(int16_t) * 44100 * 2 * MUSIC_BUFFER_SIZE_SECONDS * MUSIC_BUFFER_COUNT); Pa_Initialize(); #ifdef WIN32 { OSVERSIONINFO versionInfo; PaStreamParameters streamParams; unsigned long framesPerBuffer; versionInfo.dwMajorVersion = 0; versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&versionInfo); if (versionInfo.dwMajorVersion < 6) { framesPerBuffer = FRAMES_PER_BUFFER_WINXP; } else { framesPerBuffer = FRAMES_PER_BUFFER_WIN7; } streamParams.device = Pa_GetDefaultOutputDevice(); streamParams.channelCount = 2; streamParams.sampleFormat = paInt16; streamParams.suggestedLatency = 0.0f; streamParams.hostApiSpecificStreamInfo = NULL; error = Pa_OpenStream(&stream, NULL, &streamParams, 44100, framesPerBuffer, paNoFlag, callback, NULL); } #else error = Pa_OpenDefaultStream(&stream, 0, 2, paInt16, 44100, paFramesPerBufferUnspecified, callback, NULL); #endif if (error != paNoError) { fprintf(stderr, "Pa_OpenDefaultStream returned %d\n", error); stream = NULL; } else { error = Pa_StartStream(stream); if (error != paNoError) { fprintf(stderr, "Pa_StartStream returned %d\n", error); } } } void AudioManager_globalShutdown() { if (stream != NULL) { Pa_StopStream(stream); Pa_CloseStream(stream); Pa_Terminate(); stream = NULL; } } void AudioManager_setSpeakerOutput(bool outputToSpeakers) { if (outputToSpeakers) { Pa_StartStream(stream); } else { Pa_StopStream(stream); } } void AudioManager_mixSamples(int16_t * outSamples, unsigned long frameCount) { int16_t * inSamples; int32_t sample; size_t channelIndex; unsigned long frameIndex, sampleIndex, sampleMax; for (frameIndex = 0; frameIndex < frameCount; frameIndex++) { outSamples[frameIndex * 2 + 0] = 0; outSamples[frameIndex * 2 + 1] = 0; } for (channelIndex = 0; channelIndex < AUDIO_CHANNEL_COUNT; channelIndex++) { if (!playingSounds[channelIndex].active) { continue; } inSamples = playingSounds[channelIndex].sound->samples; sampleMax = playingSounds[channelIndex].sound->sampleCount; sampleIndex = playingSounds[channelIndex].sampleIndex; for (frameIndex = 0; frameIndex < frameCount && sampleIndex < sampleMax; frameIndex++) { sample = outSamples[frameIndex * 2 + 0] + inSamples[sampleIndex]; outSamples[frameIndex * 2 + 0] = sample < INT16_MIN ? INT16_MIN : sample > INT16_MAX ? INT16_MAX : sample; sample = outSamples[frameIndex * 2 + 1] + inSamples[sampleIndex]; outSamples[frameIndex * 2 + 1] = sample < INT16_MIN ? INT16_MIN : sample > INT16_MAX ? INT16_MAX : sample; sampleIndex++; } playingSounds[channelIndex].sampleIndex = sampleIndex; if (playingSounds[channelIndex].sampleIndex >= sampleMax) { playingSounds[channelIndex].active = false; } } if (musicPlaying && !musicPaused) { for (frameIndex = 0; frameIndex < frameCount; frameIndex++) { sample = outSamples[frameIndex * 2 + 0] + musicSamples[musicFrameIndex * 2 + 0]; outSamples[frameIndex * 2 + 0] = sample < INT16_MIN ? INT16_MIN : sample > INT16_MAX ? INT16_MAX : sample; sample = outSamples[frameIndex * 2 + 1] + musicSamples[musicFrameIndex * 2 + 1]; outSamples[frameIndex * 2 + 1] = sample < INT16_MIN ? INT16_MIN : sample > INT16_MAX ? INT16_MAX : sample; musicFrameIndex = (musicFrameIndex + 1) % (44100 * MUSIC_BUFFER_SIZE_SECONDS * MUSIC_BUFFER_COUNT); } } } void AudioManager_play(const char * soundName) { size_t channelIndex; if (soundList != NULL && valueGetBoolean(Preferences_get(gameSession->preferences, "play_sound"))) { size_t subitemIndex; subitemIndex = JSONNode_subitemIndexForKey(soundList, soundName, strlen(soundName)); if (subitemIndex == JSON_SUBITEM_NOT_FOUND) { fprintf(stderr, "Warning: Request to play sound \"%s\", which wasn't specified in sounds.json\n", soundName); } else if (sounds != NULL && sounds[subitemIndex] != NULL) { for (channelIndex = 0; channelIndex < AUDIO_CHANNEL_COUNT; channelIndex++) { if (!playingSounds[channelIndex].active) { playingSounds[channelIndex].sound = sounds[subitemIndex]; playingSounds[channelIndex].sampleIndex = 0; playingSounds[channelIndex].active = true; break; } } } } } static void bufferNextMusicChunk() { size_t bytesRead; bytesRead = musicStream->read(musicStream, MUSIC_BUFFER_SIZE_SECONDS * musicStream->bytesPerSample * musicStream->channelCount * musicStream->sampleRate, musicSamples + nextMusicChunkIndex * musicStream->channelCount * musicStream->sampleRate * MUSIC_BUFFER_SIZE_SECONDS, true); if (bytesRead < MUSIC_BUFFER_SIZE_SECONDS * musicStream->bytesPerSample * musicStream->channelCount * musicStream->sampleRate) { fprintf(stderr, "Error: Couldn't read %d bytes from music stream (got " SIZE_T_FORMAT "); stopping music\n", MUSIC_BUFFER_SIZE_SECONDS * musicStream->bytesPerSample * musicStream->channelCount * musicStream->sampleRate, bytesRead); AudioManager_stopMusic(); return; } } void AudioManager_startMusic(const char * musicName) { if (!valueGetBoolean(Preferences_get(gameSession->preferences, "play_music"))) { return; } if (musicStream == NULL) { musicStream = VorbisAudioStream_createWithFile(musicName); if (musicStream == NULL) { fprintf(stderr, "Error: Couldn't load %s as ogg vorbis audio stream\n", musicName); return; } } else { musicStream->seek(musicStream, 0, SEEK_SET); } for (nextMusicChunkIndex = 0; nextMusicChunkIndex < MUSIC_BUFFER_COUNT; nextMusicChunkIndex++) { bufferNextMusicChunk(); } nextMusicChunkIndex = 0; musicFrameIndex = 0; musicPlaying = true; } void AudioManager_stopMusic() { musicPlaying = false; musicPaused = false; } bool AudioManager_isMusicPlaying() { return musicPlaying; } void AudioManager_pauseMusic() { musicPaused = true; } void AudioManager_resumeMusic() { musicPaused = false; } void AudioManager_singleThreadStreamMusic() { if (!musicPlaying || musicPaused) { return; } if ((nextMusicChunkIndex == MUSIC_BUFFER_COUNT - 1 && musicFrameIndex <= 44100 * MUSIC_BUFFER_SIZE_SECONDS) || (nextMusicChunkIndex < MUSIC_BUFFER_COUNT - 1 && musicFrameIndex > 44100 * MUSIC_BUFFER_SIZE_SECONDS * (nextMusicChunkIndex + 1))) { bufferNextMusicChunk(); nextMusicChunkIndex = (nextMusicChunkIndex + 1) % MUSIC_BUFFER_COUNT; } }