diff --git a/examples/Makefile b/examples/Makefile index 218ed7219..2731ee503 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -165,7 +165,7 @@ ifeq ($(PLATFORM),PLATFORM_RPI) endif ifeq ($(PLATFORM),PLATFORM_WEB) # HTML5 emscripten compiler - # WARNING: To compile to HTML5, code must be redesigned + # WARNING: To compile to HTML5, code must be redesigned # to use emscripten.h and emscripten_set_main_loop() CC = emcc endif @@ -303,12 +303,12 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP) # Libraries for Debian GNU/Linux desktop compiling # NOTE: Required packages: libegl1-mesa-dev LDLIBS = -lraylib -lGL -lm -lpthread -ldl -lrt - + # On X11 requires also below libraries LDLIBS += -lX11 # NOTE: It seems additional libraries are not required any more, latest GLFW just dlopen them #LDLIBS += -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor - + # On Wayland windowing system, additional libraries requires ifeq ($(USE_WAYLAND_DISPLAY),TRUE) LDLIBS += -lwayland-client -lwayland-cursor -lwayland-egl -lxkbcommon @@ -435,10 +435,12 @@ EXAMPLES = \ shaders/shaders_texture_waves \ shaders/shaders_julia_set \ shaders/shaders_eratosthenes \ + shaders/shaders_basic_lighting \ audio/audio_module_playing \ audio/audio_music_stream \ audio/audio_raw_stream \ audio/audio_sound_loading \ + audio/audio_multichannel_sound \ physac/physics_demo \ physac/physics_friction \ physac/physics_movement \ diff --git a/examples/audio/audio_multichannel_sound.c b/examples/audio/audio_multichannel_sound.c new file mode 100644 index 000000000..42c71704e --- /dev/null +++ b/examples/audio/audio_multichannel_sound.c @@ -0,0 +1,116 @@ + +/* +* This example was coded to demonstrate multi channel audio changes added to raylib +* +* This example has been created using raylib (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* This example Copyright (c) 2018 Chris Camacho (codifies) http://bedroomcoders.co.uk/captcha/ +* +* THIS EXAMPLE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +* +* This example may be freely redistributed. +*/ + +#include "raylib.h" +#include // sprintf + +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] abuse!"); + + InitAudioDevice(); // Initialize audio device + + Sound fxWav = LoadSound("resources/sound.wav"); // Load WAV audio file + Sound fxOgg = LoadSound("resources/tanatana.ogg"); // Load OGG audio file + + + //InitPlayBufferPool(); + + + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + int frame = 0; + // Main game loop + + // old system still works alongside + SetSoundVolume(fxWav, 0.2); // effects all mutltiplay replay (really annoying sound!!!) + PlaySound(fxOgg); + + bool inhibitWav = false; + bool inhibitOgg = false; + int maxFrame = 60; + int numberPlaying = 0; + + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + frame++; + + if (IsKeyDown(KEY_ENTER)) { inhibitWav=true; } else { inhibitWav=false; } + if (IsKeyDown(KEY_SPACE)) { inhibitOgg=true; } else { inhibitOgg=false; } + + // deliberatly hammer the play pool to see what dropping old + // pool entries sounds like.... + if (frame % 5==0) { + if (!inhibitWav) PlaySoundEx(fxWav); + } + if (frame==maxFrame) { + if (!inhibitOgg) PlaySoundEx(fxOgg); + frame=0; + maxFrame = GetRandomValue(6,12); + } + + numberPlaying = ConcurrentPlayChannels(); + + //---------------------------------------------------------------------------------- + + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + char msg[1024]; + DrawText("multichannel sound abuse!", 200, 180, 20, LIGHTGRAY); + DrawText("Space to inhibit new ogg triggering", 200, 200, 20, LIGHTGRAY); + DrawText("Enter to inhibit new wav triggering", 200, 220, 20, LIGHTGRAY); + + sprintf(msg,"concurrently playing %i", numberPlaying); + DrawText(msg, 200, 280, 20, LIGHTGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + + + // you MUST stop the buffer pool before unloading + // any Sounds it might have used... + StopPlayBufferPool(); + + UnloadSound(fxWav); // Unload sound data + UnloadSound(fxOgg); // Unload sound data + + CloseAudioDevice(); // Close audio device + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/src/Makefile b/src/Makefile index ef8b438d0..4af01d916 100644 --- a/src/Makefile +++ b/src/Makefile @@ -161,7 +161,7 @@ ifeq ($(PLATFORM),PLATFORM_ANDROID) # Starting at 2019 using ARM64 is mandatory for published apps ANDROID_ARCH ?= ARM ANDROID_API_VERSION = 21 - + # Android required path variables # NOTE: Android NDK is just required to generate the standalone toolchain, # in case is not already provided @@ -169,7 +169,7 @@ ifeq ($(PLATFORM),PLATFORM_ANDROID) # Android standalone toolchain path ANDROID_TOOLCHAIN = C:/android_toolchain_$(ANDROID_ARCH)_API$(ANDROID_API_VERSION) - + ifeq ($(ANDROID_ARCH),ARM) ANDROID_ARCH_NAME = armeabi-v7a endif @@ -258,6 +258,10 @@ endif # -fno-strict-aliasing jar_xm.h does shady stuff (breaks strict aliasing) CFLAGS += -Wall -std=c99 -D_DEFAULT_SOURCE -Wno-missing-braces -Werror=pointer-arith -fno-strict-aliasing +ifeq ($(PLATFORM_OS),LINUX) + CFLAGS += -fPIC +endif + ifeq ($(RAYLIB_BUILD_MODE),DEBUG) CFLAGS += -g endif @@ -528,14 +532,14 @@ models.o : models.c raylib.h rlgl.h raymath.h # Compile audio module raudio.o : raudio.c raylib.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) -D$(PLATFORM) - + # Compile raygui module # NOTE: raygui header should be distributed with raylib.h raygui.o : raygui.c raygui.h @echo #define RAYGUI_IMPLEMENTATION > raygui.c @echo #include "$(RAYLIB_MODULE_RAYGUI_PATH)/raygui.h" > raygui.c $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) -D$(PLATFORM) -DRAYGUI_IMPLEMENTATION - + # Compile physac module # NOTE: physac header should be distributed with raylib.h physac.o : physac.c physac.h diff --git a/src/raudio.c b/src/raudio.c index 6ac0110cd..2008c2bf1 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -178,10 +178,11 @@ typedef enum { } TraceLogType; #endif + + //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -// ... //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -230,7 +231,7 @@ struct rAudioBuffer { unsigned int bufferSizeInFrames; rAudioBuffer *next; rAudioBuffer *prev; - unsigned char buffer[1]; + unsigned char *buffer; }; // HACK: To avoid CoreAudio (macOS) symbol collision @@ -268,6 +269,24 @@ void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch); void TrackAudioBuffer(AudioBuffer *audioBuffer); void UntrackAudioBuffer(AudioBuffer *audioBuffer); + +//---------------------------------------------------------------------------------- +// multi channel playback globals +//---------------------------------------------------------------------------------- + +// number of channels in the pool +#define PLAY_POOL_SIZE 16 + +// the buffer pool +AudioBuffer* PlayBufferPool[PLAY_POOL_SIZE]; + +// these are used to determine the oldest playing channel +unsigned long PlayPoolAge = 0; +unsigned long PlayPoolAges[PLAY_POOL_SIZE] = {0}; + +//---------------------------------------------------------------------------------- + + // Log callback function static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message) { @@ -462,6 +481,15 @@ static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 fr } } +// initialise the multichannel buffer pool +static void InitPlayBufferPool() +{ + // dummy buffers + for (int i=0; i %d", device.sampleRate, device.playback.internalSampleRate); TraceLog(LOG_INFO, "Audio buffer size: %d", device.playback.internalBufferSizeInFrames); + InitPlayBufferPool(); + TraceLog(LOG_INFO, "Audio multichannel pool size: %i", PLAY_POOL_SIZE); + isAudioInitialized = MA_TRUE; } +// internal +static void FreePlayBufferPool() { + for (int i = 0; i < PLAY_POOL_SIZE; i++) { + // NB important free only the buffer struct not the attached data...! + RL_FREE(PlayBufferPool[i]); + } +} + + // Close the audio device for all contexts void CloseAudioDevice(void) { @@ -542,6 +582,8 @@ void CloseAudioDevice(void) ma_device_uninit(&device); ma_context_uninit(&context); + FreePlayBufferPool(); + TraceLog(LOG_INFO, "Audio device closed successfully"); } @@ -567,7 +609,8 @@ void SetMasterVolume(float volume) // Create a new audio buffer. Initially filled with silence AudioBuffer *CreateAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 bufferSizeInFrames, AudioBufferUsage usage) { - AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*ma_get_bytes_per_sample(format)), 1); + AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(sizeof(*audioBuffer), 1); + audioBuffer->buffer = RL_CALLOC((bufferSizeInFrames*channels*ma_get_bytes_per_sample(format)), 1); if (audioBuffer == NULL) { TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to allocate memory for audio buffer"); @@ -623,6 +666,7 @@ void DeleteAudioBuffer(AudioBuffer *audioBuffer) } UntrackAudioBuffer(audioBuffer); + RL_FREE(audioBuffer->buffer); RL_FREE(audioBuffer); } @@ -966,6 +1010,79 @@ void PlaySound(Sound sound) PlayAudioBuffer((AudioBuffer *)sound.audioBuffer); } +// play a sound in the multichannel buffer pool +void PlaySoundEx(Sound s) +{ + int found = -1; + unsigned long oldAge = 0; + int oldIndex = -1; + + // find the first non playing pool entry + for (int i=0; i oldAge) { + oldAge = PlayPoolAges[i]; + oldIndex = i; + } + if (!IsAudioBufferPlaying(PlayBufferPool[i])) { + found = i; + break; + } + } + + // if no none playing pool members can be found choose the oldest + if (found == -1) { + TraceLog(LOG_WARNING,"pool age %i ended a sound early no room in buffer pool",PlayPoolAge); + if (oldIndex == -1) { + // shouldn't be able to get here... but just in case something odd happens! + TraceLog(LOG_ERROR,"sound buffer pool couldn't determine oldest buffer not playing sound"); + return; + } + found = oldIndex; + // just in case... + StopAudioBuffer(PlayBufferPool[found]); + } + + // experimentally mutex lock doesn't seem to be needed this makes sense + // as PlayBufferPool[found] isn't playing and the only stuff we're copying + // shouldn't be changing... + + PlayPoolAges[found] = PlayPoolAge; + PlayPoolAge++; + PlayBufferPool[found]->volume = ((AudioBuffer*)s.audioBuffer)->volume; + PlayBufferPool[found]->pitch = ((AudioBuffer*)s.audioBuffer)->pitch; + PlayBufferPool[found]->looping = ((AudioBuffer*)s.audioBuffer)->looping; + PlayBufferPool[found]->usage = ((AudioBuffer*)s.audioBuffer)->usage; + PlayBufferPool[found]->isSubBufferProcessed[0] = false; + PlayBufferPool[found]->isSubBufferProcessed[1] = false; + PlayBufferPool[found]->bufferSizeInFrames = ((AudioBuffer*)s.audioBuffer)->bufferSizeInFrames; + PlayBufferPool[found]->buffer = ((AudioBuffer*)s.audioBuffer)->buffer; + + PlayAudioBuffer(PlayBufferPool[found]); + +} + +// MUST be called before UnLoadSound is used on any sound played with PlaySoundEx +void StopPlayBufferPool() +{ + for (int i = 0; i < PLAY_POOL_SIZE; i++) { + StopAudioBuffer(PlayBufferPool[i]); + } +} + +// number of sounds playing in the multichannel buffer pool +int ConcurrentPlayChannels() +{ + int n = 0; + for (int i=0; i