diff options
author | Nikolas <nikolas@boutalas.com> | 2024-10-27 12:52:55 +0200 |
---|---|---|
committer | Nikolas <nikolas@boutalas.com> | 2024-10-27 12:52:55 +0200 |
commit | 43394c8a8908442982e3a7e25975c31b3c952923 (patch) | |
tree | 2facd563e29f48fe3b0653ac5c113998940b4d5e /graphics/audio.cpp |
Diffstat (limited to 'graphics/audio.cpp')
-rw-r--r-- | graphics/audio.cpp | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/graphics/audio.cpp b/graphics/audio.cpp new file mode 100644 index 0000000..1d74a9e --- /dev/null +++ b/graphics/audio.cpp @@ -0,0 +1,459 @@ +/* + * Simple-SDL2-Audio + * + * Copyright 2016 Jake Besworth + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <SDL2/SDL.h> + +#include "audio.h" + +/* + * Native WAVE format + * + * On some GNU/Linux you can identify a files properties using: + * mplayer -identify music.wav + * + * On some GNU/Linux to convert any music to this or another specified format use: + * ffmpeg -i in.mp3 -acodec pcm_s16le -ac 2 -ar 48000 out.wav + */ +/* SDL_AudioFormat of files, such as s16 little endian */ +#define AUDIO_FORMAT AUDIO_S16LSB + +/* Frequency of the file */ +#define AUDIO_FREQUENCY 48000 + +/* 1 mono, 2 stereo, 4 quad, 6 (5.1) */ +#define AUDIO_CHANNELS 2 + +/* Specifies a unit of audio data to be used at a time. Must be a power of 2 */ +#define AUDIO_SAMPLES 4096 + +/* Max number of sounds that can be in the audio queue at anytime, stops too much mixing */ +#define AUDIO_MAX_SOUNDS 25 + +/* Flags OR'd together, which specify how SDL should behave when a device cannot offer a specific feature + * If flag is set, SDL will change the format in the actual audio file structure (as opposed to gDevice->want) + * + * Note: If you're having issues with Emscripten / EMCC play around with these flags + * + * 0 Allow no changes + * SDL_AUDIO_ALLOW_FREQUENCY_CHANGE Allow frequency changes (e.g. AUDIO_FREQUENCY is 48k, but allow files to play at 44.1k + * SDL_AUDIO_ALLOW_FORMAT_CHANGE Allow Format change (e.g. AUDIO_FORMAT may be S32LSB, but allow wave files of S16LSB to play) + * SDL_AUDIO_ALLOW_CHANNELS_CHANGE Allow any number of channels (e.g. AUDIO_CHANNELS being 2, allow actual 1) + * SDL_AUDIO_ALLOW_ANY_CHANGE Allow all changes above + */ +#define SDL_AUDIO_ALLOW_CHANGES SDL_AUDIO_ALLOW_ANY_CHANGE + +/* + * Definition for the game global sound device + * + */ +typedef struct privateAudioDevice +{ + SDL_AudioDeviceID device; + SDL_AudioSpec want; + uint8_t audioEnabled; +} PrivateAudioDevice; + +/* File scope variables to persist data */ +static PrivateAudioDevice * gDevice; +static uint32_t gSoundCount; + +/* + * Add a music to the queue, addAudio wrapper for music due to fade + * + * @param new New Audio to add + * + */ +static void addMusic(Audio * root, Audio * news); + +/* + * Wrapper function for playMusic, playSound, playMusicFromMemory, playSoundFromMemory + * + * @param filename Provide a filename to load WAV from, or NULL if using FromMemory + * @param audio Provide an Audio object if copying from memory, or NULL if using a filename + * @param sound 1 if looping (music), 0 otherwise (sound) + * @param volume See playSound for explanation + * + */ +static inline void playAudio(const char * filename, Audio * audio, uint8_t loop, int volume); + +/* + * Add a sound to the end of the queue + * + * @param root Root of queue + * @param new New Audio to add + * + */ +static void addAudio(Audio * root, Audio * news); + +/* + * Audio callback function for OpenAudioDevice + * + * @param userdata Points to linked list of sounds to play, first being a placeholder + * @param stream Stream to mix sound into + * @param len Length of sound to play + * + */ +static inline void audioCallback(void * userdata, uint8_t * stream, int len); + +void playSound(const char * filename, int volume) +{ + playAudio(filename, NULL, 0, volume); +} + +void playMusic(const char * filename, int volume) +{ + playAudio(filename, NULL, 1, volume); +} + +void playSoundFromMemory(Audio * audio, int volume) +{ + playAudio(NULL, audio, 0, volume); +} + +void playMusicFromMemory(Audio * audio, int volume) +{ + playAudio(NULL, audio, 1, volume); +} + +void initAudio(void) +{ + Audio * global; + gDevice = (PrivateAudioDevice*)calloc(1, sizeof(PrivateAudioDevice)); + gSoundCount = 0; + + if(gDevice == NULL) + { + fprintf(stderr, "[%s: %d]Fatal Error: Memory c-allocation error\n", __FILE__, __LINE__); + return; + } + + gDevice->audioEnabled = 0; + + if(!(SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO)) + { + fprintf(stderr, "[%s: %d]Error: SDL_INIT_AUDIO not initialized\n", __FILE__, __LINE__); + return; + } + + SDL_memset(&(gDevice->want), 0, sizeof(gDevice->want)); + + (gDevice->want).freq = AUDIO_FREQUENCY; + (gDevice->want).format = AUDIO_FORMAT; + (gDevice->want).channels = AUDIO_CHANNELS; + (gDevice->want).samples = AUDIO_SAMPLES; + (gDevice->want).callback = audioCallback; + (gDevice->want).userdata = calloc(1, sizeof(Audio)); + + global = (Audio*)(gDevice->want).userdata; + + if(global == NULL) + { + fprintf(stderr, "[%s: %d]Error: Memory allocation error\n", __FILE__, __LINE__); + return; + } + + global->buffer = NULL; + global->next = NULL; + + /* want.userdata = new; */ + if((gDevice->device = SDL_OpenAudioDevice(NULL, 0, &(gDevice->want), NULL, SDL_AUDIO_ALLOW_CHANGES)) == 0) + { + fprintf(stderr, "[%s: %d]Warning: failed to open audio device: %s\n", __FILE__, __LINE__, SDL_GetError()); + } + else + { + /* Set audio device enabled global flag */ + gDevice->audioEnabled = 1; + + /* Unpause active audio stream */ + unpauseAudio(); + } +} + +void endAudio(void) +{ + if(gDevice->audioEnabled) + { + pauseAudio(); + + freeAudio((Audio *) (gDevice->want).userdata); + + /* Close down audio */ + SDL_CloseAudioDevice(gDevice->device); + } + + free(gDevice); +} + +void pauseAudio(void) +{ + if(gDevice->audioEnabled) + { + SDL_PauseAudioDevice(gDevice->device, 1); + } +} + +void unpauseAudio(void) +{ + if(gDevice->audioEnabled) + { + SDL_PauseAudioDevice(gDevice->device, 0); + } +} + +void freeAudio(Audio * audio) +{ + Audio * temp; + + while(audio != NULL) + { + if(audio->free == 1) + { + SDL_FreeWAV(audio->bufferTrue); + } + + temp = audio; + audio = audio->next; + + free(temp); + } +} + +Audio * createAudio(const char * filename, uint8_t loop, int volume) +{ + Audio * news = (Audio*)calloc(1, sizeof(Audio)); + + if(news == NULL) + { + fprintf(stderr, "[%s: %d]Error: Memory allocation error\n", __FILE__, __LINE__); + return NULL; + } + + if(filename == NULL) + { + fprintf(stderr, "[%s: %d]Warning: filename NULL: %s\n", __FILE__, __LINE__, filename); + return NULL; + } + + news->next = NULL; + news->loop = loop; + news->fade = 0; + news->free = 1; + news->volume = volume; + + if(SDL_LoadWAV(filename, &(news->audio), &(news->bufferTrue), &(news->lengthTrue)) == NULL) + { + fprintf(stderr, "[%s: %d]Warning: failed to open wave file: %s error: %s\n", __FILE__, __LINE__, filename, SDL_GetError()); + free(news); + return NULL; + } + + news->buffer = news->bufferTrue; + news->length = news->lengthTrue; + (news->audio).callback = NULL; + (news->audio).userdata = NULL; + + return news; +} + +static inline void playAudio(const char * filename, Audio * audio, uint8_t loop, int volume) +{ + Audio * news; + + /* Check if audio is enabled */ + if(!gDevice->audioEnabled) + { + return; + } + + /* If sound, check if under max number of sounds allowed, else don't play */ + if(loop == 0) + { + if(gSoundCount >= AUDIO_MAX_SOUNDS) + { + return; + } + else + { + gSoundCount++; + } + } + + /* Load from filename or from Memory */ + if(filename != NULL) + { + /* Create new music sound with loop */ + news = createAudio(filename, loop, volume); + } + else if(audio != NULL) + { + news = (Audio*)malloc(sizeof(Audio)); + + if(news == NULL) + { + fprintf(stderr, "[%s: %d]Fatal Error: Memory allocation error\n", __FILE__, __LINE__); + return; + } + + memcpy(news, audio, sizeof(Audio)); + + news->volume = volume; + news->loop = loop; + news->free = 0; + } + else + { + fprintf(stderr, "[%s: %d]Warning: filename and Audio parameters NULL\n", __FILE__, __LINE__); + return; + } + + /* Lock callback function */ + SDL_LockAudioDevice(gDevice->device); + + if(loop == 1) + { + addMusic((Audio *) (gDevice->want).userdata, news); + } + else + { + addAudio((Audio *) (gDevice->want).userdata, news); + } + + SDL_UnlockAudioDevice(gDevice->device); + +} + +static void addMusic(Audio * root, Audio * news) +{ + uint8_t musicFound = 0; + Audio * rootNext = root->next; + + /* Find any existing musics, 0, 1 or 2 and fade them out */ + while(rootNext != NULL) + { + /* Phase out any current music */ + if(rootNext->loop == 1 && rootNext->fade == 0) + { + if(musicFound) + { + rootNext->length = 0; + rootNext->volume = 0; + } + + rootNext->fade = 1; + } + /* Set flag to remove any queued up music in favour of new music */ + else if(rootNext->loop == 1 && rootNext->fade == 1) + { + musicFound = 1; + } + + rootNext = rootNext->next; + } + + addAudio(root, news); +} + +static inline void audioCallback(void * userdata, uint8_t * stream, int len) +{ + Audio * audio = (Audio *) userdata; + Audio * previous = audio; + int tempLength; + uint8_t music = 0; + + /* Silence the main buffer */ + SDL_memset(stream, 0, len); + + /* First one is place holder */ + audio = audio->next; + + while(audio != NULL) + { + if(audio->length > 0) + { + if(audio->fade == 1 && audio->loop == 1) + { + music = 1; + + if(audio->volume > 0) + { + audio->volume--; + } + else + { + audio->length = 0; + } + } + + if(music && audio->loop == 1 && audio->fade == 0) + { + tempLength = 0; + } + else + { + tempLength = ((uint32_t) len > audio->length) ? audio->length : (uint32_t) len; + } + + SDL_MixAudioFormat(stream, audio->buffer, AUDIO_FORMAT, tempLength, audio->volume); + + audio->buffer += tempLength; + audio->length -= tempLength; + + previous = audio; + audio = audio->next; + } + else if(audio->loop == 1 && audio->fade == 0) + { + audio->buffer = audio->bufferTrue; + audio->length = audio->lengthTrue; + } + else + { + previous->next = audio->next; + + if(audio->loop == 0) + { + gSoundCount--; + } + + audio->next = NULL; + freeAudio(audio); + + audio = previous->next; + } + } +} + +static void addAudio(Audio * root, Audio * news) +{ + if(root == NULL) + { + return; + } + + while(root->next != NULL) + { + root = root->next; + } + + root->next = news; +} |