From 4ec4dc691f5a41ef85d8ee07295e897e368bc8c7 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 21 Dec 2018 00:17:44 +0100 Subject: Use stb_vorbis.h as header only --- examples/others/audio_standalone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/others/audio_standalone.c b/examples/others/audio_standalone.c index d6e75066..f08fbf82 100644 --- a/examples/others/audio_standalone.c +++ b/examples/others/audio_standalone.c @@ -6,7 +6,7 @@ * * DEPENDENCIES: * mini_al.h - Audio device management lib (http://kcat.strangesoft.net/openal.html) -* stb_vorbis.c - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) * jar_xm.h - XM module file loading * jar_mod.h - MOD audio file loading * dr_flac.h - FLAC audio file loading -- cgit v1.2.3 From 96207a8a026a629fcc3026efab96cf18e1302618 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 25 Dec 2018 15:17:42 +0100 Subject: REVIEWED: LoadFontEx() Changed parameters order for consistency with LoadFontData() and other functions when an array is passed by parameter and array size is the following parameter. --- examples/textures/textures_image_text.c | 2 +- games/transmission/screens/screen_ending.c | 2 +- games/transmission/screens/screen_gameplay.c | 2 +- src/raylib.h | 2 +- src/text.c | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'examples') diff --git a/examples/textures/textures_image_text.c b/examples/textures/textures_image_text.c index fb99e827..ce91fbf2 100644 --- a/examples/textures/textures_image_text.c +++ b/examples/textures/textures_image_text.c @@ -21,7 +21,7 @@ int main() InitWindow(screenWidth, screenHeight, "raylib [texture] example - image text drawing"); // TTF Font loading with custom generation parameters - Font font = LoadFontEx("resources/KAISG.ttf", 64, 95, 0); + Font font = LoadFontEx("resources/KAISG.ttf", 64, 0, 0); Image parrots = LoadImage("resources/parrots.png"); // Load image in CPU memory (RAM) diff --git a/games/transmission/screens/screen_ending.c b/games/transmission/screens/screen_ending.c index 620fc30b..bb355b8b 100644 --- a/games/transmission/screens/screen_ending.c +++ b/games/transmission/screens/screen_ending.c @@ -123,7 +123,7 @@ void InitEndingScreen(void) // Generate newspaper with title and subtitle Image imNewspaper = LoadImage("resources/textures/ending_newspaper.png"); - fontNews = LoadFontEx("resources/fonts/Lora-Bold.ttf", 32, 250, 0); + fontNews = LoadFontEx("resources/fonts/Lora-Bold.ttf", 32, 0, 250); ImageDrawTextEx(&imNewspaper, (Vector2){ 50, 220 }, fontNews, headline, fontNews.baseSize, 0, DARKGRAY); texNewspaper = LoadTextureFromImage(imNewspaper); diff --git a/games/transmission/screens/screen_gameplay.c b/games/transmission/screens/screen_gameplay.c index aeec98ca..814db824 100644 --- a/games/transmission/screens/screen_gameplay.c +++ b/games/transmission/screens/screen_gameplay.c @@ -126,7 +126,7 @@ void InitGameplayScreen(void) framesCounter = 0; finishScreen = 0; - fontMessage = LoadFontEx("resources/fonts/traveling_typewriter.ttf", 30, 250, 0); + fontMessage = LoadFontEx("resources/fonts/traveling_typewriter.ttf", 30, 0, 250); texBackground = LoadTexture("resources/textures/message_background.png"); texVignette = LoadTexture("resources/textures/message_vignette.png"); diff --git a/src/raylib.h b/src/raylib.h index 023d35c7..d4b5bd20 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1095,7 +1095,7 @@ RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle // Font loading/unloading functions RLAPI Font GetFontDefault(void); // Get the default Font RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load font from file with extended parameters +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount); // Load font from file with extended parameters RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) RLAPI CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use RLAPI Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info diff --git a/src/text.c b/src/text.c index 80b75f10..770c8ddb 100644 --- a/src/text.c +++ b/src/text.c @@ -275,7 +275,7 @@ Font LoadFont(const char *fileName) Font font = { 0 }; #if defined(SUPPORT_FILEFORMAT_TTF) - if (IsFileExtension(fileName, ".ttf") || IsFileExtension(fileName, ".otf")) font = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, DEFAULT_TTF_NUMCHARS, NULL); + if (IsFileExtension(fileName, ".ttf") || IsFileExtension(fileName, ".otf")) font = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, NULL, DEFAULT_TTF_NUMCHARS); else #endif #if defined(SUPPORT_FILEFORMAT_FNT) @@ -301,7 +301,7 @@ Font LoadFont(const char *fileName) // Load Font from TTF font file with generation parameters // NOTE: You can pass an array with desired characters, those characters should be available in the font // if array is NULL, default char set is selected 32..126 -Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars) +Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount) { Font font = { 0 }; -- cgit v1.2.3 From 35a6e9a07476c06e239b2cf1879bdb97fdc04b09 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 25 Dec 2018 15:18:35 +0100 Subject: Corrected issue with MOD playing Despite issue is corrected, now module loop doesn't work... --- examples/audio/audio_module_playing.c | 6 +++--- src/audio.c | 20 +++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) (limited to 'examples') diff --git a/examples/audio/audio_module_playing.c b/examples/audio/audio_module_playing.c index 671a119f..54bfa3d2 100644 --- a/examples/audio/audio_module_playing.c +++ b/examples/audio/audio_module_playing.c @@ -30,11 +30,11 @@ int main() int screenWidth = 800; int screenHeight = 450; - SetConfigFlags(FLAG_MSAA_4X_HINT); // NOTE: Try to enable MSAA 4X + SetConfigFlags(FLAG_MSAA_4X_HINT); // NOTE: Try to enable MSAA 4X InitWindow(screenWidth, screenHeight, "raylib [audio] example - module playing (streaming)"); - InitAudioDevice(); // Initialize audio device + InitAudioDevice(); // Initialize audio device Color colors[14] = { ORANGE, RED, GOLD, LIME, BLUE, VIOLET, BROWN, LIGHTGRAY, PINK, YELLOW, GREEN, SKYBLUE, PURPLE, BEIGE }; @@ -52,7 +52,7 @@ int main() circles[i].color = colors[GetRandomValue(0, 13)]; } - Music xm = LoadMusicStream("resources/mini1111.xm"); + Music xm = LoadMusicStream("resources/chiptun1.mod"); PlayMusicStream(xm); diff --git a/src/audio.c b/src/audio.c index 41aa1b61..b4eec113 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1176,14 +1176,15 @@ Music LoadMusicStream(const char *fileName) if (jar_mod_load_file(&music->ctxMod, fileName)) { + // NOTE: Only stereo is supported for MOD music->stream = InitAudioStream(48000, 16, 2); music->totalSamples = (unsigned int)jar_mod_max_samples(&music->ctxMod); music->samplesLeft = music->totalSamples; music->ctxType = MUSIC_MODULE_MOD; music->loopCount = -1; // Infinite loop by default - TraceLog(LOG_DEBUG, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); - TraceLog(LOG_DEBUG, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + TraceLog(LOG_INFO, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); + TraceLog(LOG_INFO, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); } else musicLoaded = false; } @@ -1345,18 +1346,27 @@ void UpdateMusicStream(Music music) #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: { - // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 --> WEIRD + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 jar_xm_generate_samples_16bit(music->ctxXm, (short *)pcm, samplesCount/2); } break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) - case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break; + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 + jar_mod_fillbuffer(&music->ctxMod, (short *)pcm, samplesCount/2, 0); + } break; #endif default: break; } + UpdateAudioStream(music->stream, pcm, samplesCount); - music->samplesLeft -= samplesCount; + if ((music->ctxType == MUSIC_MODULE_XM) || (music->ctxType == MUSIC_MODULE_MOD)) + { + music->samplesLeft -= samplesCount/2; + } + else music->samplesLeft -= samplesCount; if (music->samplesLeft <= 0) { -- cgit v1.2.3 From 7b8965eb38adcbc325533acc831e3331c3efba9c Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 25 Dec 2018 15:19:25 +0100 Subject: Support float texture data on OpenGL ES 2.0 --- examples/models/models_skybox.c | 2 +- examples/models/resources/shaders/cubemap.fs | 4 ++-- examples/models/resources/shaders/cubemap.vs | 4 ++-- examples/models/resources/shaders/skybox.fs | 4 ++-- examples/models/resources/shaders/skybox.vs | 4 ++-- games/transmission/transmission.c | 2 +- src/rlgl.h | 17 +++++++++++++---- 7 files changed, 23 insertions(+), 14 deletions(-) (limited to 'examples') diff --git a/examples/models/models_skybox.c b/examples/models/models_skybox.c index 6f6002b8..a6b233e3 100644 --- a/examples/models/models_skybox.c +++ b/examples/models/models_skybox.c @@ -21,7 +21,7 @@ int main() InitWindow(screenWidth, screenHeight, "raylib [models] example - skybox loading and drawing"); // Define the camera to look into our 3d world - Camera camera = {{ 1.0f, 1.0f, 1.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, 45.0f, 0 }; + Camera camera = {{ 1.0f, 1.0f, 1.0f }, { 4.0f, 1.0f, 4.0f }, { 0.0f, 1.0f, 0.0f }, 45.0f, 0 }; // Load skybox model Mesh cube = GenMeshCube(1.0f, 1.0f, 1.0f); diff --git a/examples/models/resources/shaders/cubemap.fs b/examples/models/resources/shaders/cubemap.fs index 09ae62f5..e8e28536 100644 --- a/examples/models/resources/shaders/cubemap.fs +++ b/examples/models/resources/shaders/cubemap.fs @@ -9,7 +9,7 @@ #version 330 // Input vertex attributes (from vertex shader) -in vec3 fragPos; +in vec3 fragPosition; // Input uniform values uniform sampler2D equirectangularMap; @@ -28,7 +28,7 @@ vec2 SampleSphericalMap(vec3 v) void main() { // Normalize local position - vec2 uv = SampleSphericalMap(normalize(fragPos)); + vec2 uv = SampleSphericalMap(normalize(fragPosition)); // Fetch color from texture map vec3 color = texture(equirectangularMap, uv).rgb; diff --git a/examples/models/resources/shaders/cubemap.vs b/examples/models/resources/shaders/cubemap.vs index 6e0bf4e1..5721eaa2 100644 --- a/examples/models/resources/shaders/cubemap.vs +++ b/examples/models/resources/shaders/cubemap.vs @@ -16,12 +16,12 @@ uniform mat4 projection; uniform mat4 view; // Output vertex attributes (to fragment shader) -out vec3 fragPos; +out vec3 fragPosition; void main() { // Calculate fragment position based on model transformations - fragPos = vertexPosition; + fragPosition = vertexPosition; // Calculate final vertex position gl_Position = projection*view*vec4(vertexPosition, 1.0); diff --git a/examples/models/resources/shaders/skybox.fs b/examples/models/resources/shaders/skybox.fs index 0bd2f320..053a2517 100644 --- a/examples/models/resources/shaders/skybox.fs +++ b/examples/models/resources/shaders/skybox.fs @@ -9,7 +9,7 @@ #version 330 // Input vertex attributes (from vertex shader) -in vec3 fragPos; +in vec3 fragPosition; // Input uniform values uniform samplerCube environmentMap; @@ -20,7 +20,7 @@ out vec4 finalColor; void main() { // Fetch color from texture map - vec3 color = texture(environmentMap, fragPos).rgb; + vec3 color = texture(environmentMap, fragPosition).rgb; // Apply gamma correction color = color/(color + vec3(1.0)); diff --git a/examples/models/resources/shaders/skybox.vs b/examples/models/resources/shaders/skybox.vs index f40d615c..dcbe6c3d 100644 --- a/examples/models/resources/shaders/skybox.vs +++ b/examples/models/resources/shaders/skybox.vs @@ -16,12 +16,12 @@ uniform mat4 projection; uniform mat4 view; // Output vertex attributes (to fragment shader) -out vec3 fragPos; +out vec3 fragPosition; void main() { // Calculate fragment position based on model transformations - fragPos = vertexPosition; + fragPosition = vertexPosition; // Remove translation from the view matrix mat4 rotView = mat4(mat3(view)); diff --git a/games/transmission/transmission.c b/games/transmission/transmission.c index d4298e34..91ca28c1 100644 --- a/games/transmission/transmission.c +++ b/games/transmission/transmission.c @@ -70,7 +70,7 @@ int main(void) SetMusicVolume(music, 1.0f); PlayMusicStream(music); - fontMission = LoadFontEx("resources/fonts/traveling_typewriter.ttf", 64, 250, 0); + fontMission = LoadFontEx("resources/fonts/traveling_typewriter.ttf", 64, 0, 250); texButton = LoadTexture("resources/textures/title_ribbon.png"); // UI BUTTON diff --git a/src/rlgl.h b/src/rlgl.h index 5c14f9be..8bee37a3 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2968,7 +2968,7 @@ Matrix GetMatrixModelview() Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) { Texture2D cubemap = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader // Other locations should be setup externally in shader before calling the function @@ -2978,22 +2978,31 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Flag not supported on OpenGL ES 2.0 #endif - // Setup framebuffer unsigned int fbo, rbo; glGenFramebuffers(1, &fbo); glGenRenderbuffers(1, &rbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); +#if defined(GRAPHICS_API_OPENGL_33) glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); +#elif defined(GRAPHICS_API_OPENGL_ES2) + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size, size); +#endif glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); // Set up cubemap to render and attach to framebuffer // NOTE: faces are stored with 16 bit floating point values glGenTextures(1, &cubemap.id); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - for (unsigned int i = 0; i < 6; i++) - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + for (unsigned int i = 0; i < 6; i++) + { +#if defined(GRAPHICS_API_OPENGL_33) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL); +#elif defined(GRAPHICS_API_OPENGL_ES2) + if (texFloatSupported) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); +#endif + } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #if defined(GRAPHICS_API_OPENGL_33) -- cgit v1.2.3 From 01338b0a14f053d281970a32595bc531e8c70885 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Wed, 26 Dec 2018 13:26:34 +0100 Subject: WARNING: BREAKING CHANGE Added a bunch of useful text management functions. Consequently, some already available functions like `FormatText()` and `SubText()` has been renamed for consistency. Created temporal fallbacks for old names. raylib version bumped to 2.3. --- examples/shapes/shapes_logo_raylib_anim.c | 4 +- examples/text/text_writing_anim.c | 4 +- games/transmission/screens/screen_gameplay.c | 6 +- games/transmission/screens/screen_mission.c | 2 +- games/transmission/screens/screen_title.c | 4 +- src/config.h | 2 +- src/core.c | 8 +- src/raylib.h | 26 ++- src/text.c | 263 ++++++++++++++++++++++++--- 9 files changed, 273 insertions(+), 46 deletions(-) (limited to 'examples') diff --git a/examples/shapes/shapes_logo_raylib_anim.c b/examples/shapes/shapes_logo_raylib_anim.c index c6d3796e..9be1d963 100644 --- a/examples/shapes/shapes_logo_raylib_anim.c +++ b/examples/shapes/shapes_logo_raylib_anim.c @@ -2,7 +2,7 @@ * * raylib [shapes] example - raylib logo animation * -* This example has been created using raylib 1.4 (www.raylib.com) +* This example has been created using raylib 2.3 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * * Copyright (c) 2014 Ramon Santamaria (@raysan5) @@ -140,7 +140,7 @@ int main() DrawRectangle(screenWidth/2 - 112, screenHeight/2 - 112, 224, 224, Fade(RAYWHITE, alpha)); - DrawText(SubText("raylib", 0, lettersCount), screenWidth/2 - 44, screenHeight/2 + 48, 50, Fade(BLACK, alpha)); + DrawText(TextSubtext("raylib", 0, lettersCount), screenWidth/2 - 44, screenHeight/2 + 48, 50, Fade(BLACK, alpha)); } else if (state == 4) { diff --git a/examples/text/text_writing_anim.c b/examples/text/text_writing_anim.c index 5563b561..b2aba697 100644 --- a/examples/text/text_writing_anim.c +++ b/examples/text/text_writing_anim.c @@ -2,7 +2,7 @@ * * raylib [text] example - Text Writing Animation * -* This example has been created using raylib 1.4 (www.raylib.com) +* This example has been created using raylib 2.3 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * * Copyright (c) 2016 Ramon Santamaria (@raysan5) @@ -44,7 +44,7 @@ int main() ClearBackground(RAYWHITE); - DrawText(SubText(message, 0, framesCounter/10), 210, 160, 20, MAROON); + DrawText(TextSubtext(message, 0, framesCounter/10), 210, 160, 20, MAROON); DrawText("PRESS [ENTER] to RESTART!", 240, 260, 20, LIGHTGRAY); DrawText("PRESS [SPACE] to SPEED UP!", 239, 300, 20, LIGHTGRAY); diff --git a/games/transmission/screens/screen_gameplay.c b/games/transmission/screens/screen_gameplay.c index 814db824..ee70632a 100644 --- a/games/transmission/screens/screen_gameplay.c +++ b/games/transmission/screens/screen_gameplay.c @@ -219,10 +219,10 @@ void InitGameplayScreen(void) { foundWord = false; - messageWords[currentWord - 1].rec.width = (int)MeasureTextEx(fontMessage, SubText(missions[currentMission].msg, wordInitPosX, (i - wordInitPosX)), 30, 0).x; + messageWords[currentWord - 1].rec.width = (int)MeasureTextEx(fontMessage, TextSubtext(missions[currentMission].msg, wordInitPosX, (i - wordInitPosX)), 30, 0).x; messageWords[currentWord - 1].rec.height = fontMessage.baseSize; - strncpy(messageWords[currentWord - 1].text, SubText(missions[currentMission].msg, wordInitPosX, (i - wordInitPosX)), i - wordInitPosX); + strncpy(messageWords[currentWord - 1].text, TextSubtext(missions[currentMission].msg, wordInitPosX, (i - wordInitPosX)), i - wordInitPosX); } if (c == '@') // One word to change @@ -230,7 +230,7 @@ void InitGameplayScreen(void) foundWord = true; missions[currentMission].msg[i] = ' '; - offsetX = (int)MeasureTextEx(fontMessage, SubText(missions[currentMission].msg, wordInitPosY, (i + 1) - wordInitPosY), 30, 0).x; + offsetX = (int)MeasureTextEx(fontMessage, TextSubtext(missions[currentMission].msg, wordInitPosY, (i + 1) - wordInitPosY), 30, 0).x; messageWords[currentWord].rec.x = offsetX; messageWords[currentWord].rec.y = offsetY; diff --git a/games/transmission/screens/screen_mission.c b/games/transmission/screens/screen_mission.c index d62b7205..1cd2563b 100644 --- a/games/transmission/screens/screen_mission.c +++ b/games/transmission/screens/screen_mission.c @@ -205,7 +205,7 @@ void DrawMissionScreen(void) DrawTexturePro(texBackline, sourceRecBackLine, destRecBackLine, (Vector2){0,0},0, Fade(WHITE, fadeBackLine)); if (writeNumber) DrawTextEx(fontMission, FormatText("Filtración #%02i ", currentMission + 1), numberPosition, missionSize + 10, 0, numberColor); - DrawTextEx(fontMission, SubText(missions[currentMission].brief, 0, missionLenght), missionPosition, missionSize, 0, missionColor); + DrawTextEx(fontMission, TextSubtext(missions[currentMission].brief, 0, missionLenght), missionPosition, missionSize, 0, missionColor); if (writeKeyword && blinkKeyWord) DrawTextEx(fontMission, FormatText("Keyword: %s", missions[currentMission].key), keywordPosition, missionSize + 10, 0, keywordColor); if (showButton) diff --git a/games/transmission/screens/screen_title.c b/games/transmission/screens/screen_title.c index 22efadb1..2a30a6ba 100644 --- a/games/transmission/screens/screen_title.c +++ b/games/transmission/screens/screen_title.c @@ -137,8 +137,8 @@ void UpdateTitleScreen(void) void DrawTitleScreen(void) { DrawTexture(texBackground, 0,0, WHITE); - DrawTextEx(fontTitle, SubText(textTitle, 0, transmissionLenght), transmissionPosition, titleSize, 0, titleColor); - DrawTextEx(fontTitle, SubText(textTitle, 12, missionLenght), missionPositon, titleSize, 0, titleColor); + DrawTextEx(fontTitle, TextSubtext(textTitle, 0, transmissionLenght), transmissionPosition, titleSize, 0, titleColor); + DrawTextEx(fontTitle, TextSubtext(textTitle, 12, missionLenght), missionPositon, titleSize, 0, titleColor); DrawButton("start"); } diff --git a/src/config.h b/src/config.h index baafb934..6d16207c 100644 --- a/src/config.h +++ b/src/config.h @@ -25,7 +25,7 @@ * **********************************************************************************************/ -#define RAYLIB_VERSION "2.2-dev" +#define RAYLIB_VERSION "2.3-dev" // Edit to control what features Makefile'd raylib is compiled with #if defined(RAYLIB_CMAKE) diff --git a/src/core.c b/src/core.c index 1b975214..6c2713e1 100644 --- a/src/core.c +++ b/src/core.c @@ -3176,17 +3176,17 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i // NOTE: delay represents the time between frames in the gif, if we capture a gif frame every // 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10. - GifBegin(FormatText("screenrec%03i.gif", screenshotCounter), screenWidth, screenHeight, (int)(GetFrameTime()*10.0f), 8, false); + GifBegin(TextFormat("screenrec%03i.gif", screenshotCounter), screenWidth, screenHeight, (int)(GetFrameTime()*10.0f), 8, false); screenshotCounter++; - TraceLog(LOG_INFO, "Begin animated GIF recording: %s", FormatText("screenrec%03i.gif", screenshotCounter)); + TraceLog(LOG_INFO, "Begin animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); } } else #endif // SUPPORT_GIF_RECORDING #if defined(SUPPORT_SCREEN_CAPTURE) { - TakeScreenshot(FormatText("screenshot%03i.png", screenshotCounter)); + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); screenshotCounter++; } #endif // SUPPORT_SCREEN_CAPTURE @@ -4454,7 +4454,7 @@ static void LogoAnimation(void) DrawRectangle(screenWidth/2 - 112, screenHeight/2 - 112, 224, 224, Fade(RAYWHITE, alpha)); - DrawText(SubText("raylib", 0, lettersCount), screenWidth/2 - 44, screenHeight/2 + 48, 50, Fade(BLACK, alpha)); + DrawText(TextSubtext("raylib", 0, lettersCount), screenWidth/2 - 44, screenHeight/2 + 48, 50, Fade(BLACK, alpha)); } EndDrawing(); diff --git a/src/raylib.h b/src/raylib.h index baac5a8c..3cad19fb 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -135,6 +135,11 @@ #define MAGENTA CLITERAL{ 255, 0, 255, 255 } // Magenta #define RAYWHITE CLITERAL{ 245, 245, 245, 255 } // My own White (raylib logo) +// Temporal hack to avoid breaking old codebases using +// deprecated raylib implementation of these functions +#define FormatText TextFormat +#define SubText TextSubText + //---------------------------------------------------------------------------------- // Structures Definition //---------------------------------------------------------------------------------- @@ -1111,11 +1116,22 @@ RLAPI int MeasureText(const char *text, int fontSize); RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on font -// Text string edition functions -RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' -RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string -RLAPI char **SplitText(char *text, char delimiter, int *strCount); // Split text string into multiple strings (memory should be freed manually!) -RLAPI bool IsEqualText(const char *text1, const char *text2); // Check if two text string are equal +// Text strings management functions +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI const char *TextReplace(char *text, const char *replace, const char *by); // Replace text string (memory should be freed!) +RLAPI const char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (memory should be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings (memory should be freed!) +RLAPI void TextSplitEx(const char *text, char delimiter, int *count, const char **ptrs, int *lengths); // Get pointers to substrings separated by delimiter +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) diff --git a/src/text.c b/src/text.c index 770c8ddb..ebf2599a 100644 --- a/src/text.c +++ b/src/text.c @@ -47,6 +47,7 @@ #include // Required for: strlen() #include // Required for: va_list, va_start(), vfprintf(), va_end() #include // Required for: FILE, fopen(), fclose(), fscanf(), feof(), rewind(), fgets() +#include // Required for: toupper(), tolower() #include "utils.h" // Required for: fopen() Android mapping @@ -62,8 +63,7 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_FORMATTEXT_LENGTH 512 -#define MAX_SUBTEXT_LENGTH 512 +#define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers of some Text*() functions //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -700,7 +700,7 @@ void DrawFPS(int posX, int posY) } // NOTE: We have rounding errors every frame, so it oscillates a lot - DrawText(FormatText("%2i FPS", fps), posX, posY, 20, LIME); + DrawText(TextFormat("%2i FPS", fps), posX, posY, 20, LIME); } // Draw text (using default font) @@ -863,10 +863,33 @@ int GetGlyphIndex(Font font, int character) #endif } +// Text strings management functions +//---------------------------------------------------------------------------------- +// Check if two text string are equal +// REQUIRES: strcmp() +bool TextIsEqual(const char *text1, const char *text2) +{ + bool result = false; + + if (strcmp(text1, text2) == 0) result = true; + + return result; +} + +// Get text length in bytes, check for \0 character +unsigned int TextLength(const char *text) +{ + unsigned int length = 0; + + while (*text++) length++; + + return length; +} + // Formatting of text with variables to 'embed' -const char *FormatText(const char *text, ...) +const char *TextFormat(const char *text, ...) { - static char buffer[MAX_FORMATTEXT_LENGTH]; + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; va_list args; va_start(args, text); @@ -877,9 +900,11 @@ const char *FormatText(const char *text, ...) } // Get a piece of a text string -const char *SubText(const char *text, int position, int length) +// REQUIRES: strlen() +const char *TextSubtext(const char *text, int position, int length) { - static char buffer[MAX_SUBTEXT_LENGTH] = { 0 }; + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + int textLength = strlen(text); if (position >= textLength) @@ -901,52 +926,238 @@ const char *SubText(const char *text, int position, int length) return buffer; } +// Replace text string +// REQUIRES: strlen(), strstr(), strncpy(), strcpy() +// WARNING: Internally allocated memory must be freed by the user (if return != NULL) +const char *TextReplace(char *text, const char *replace, const char *by) +{ + char *result; + + char *insertPoint; // Next insert point + char *temp; // Temp pointer + int replaceLen; // Replace string length of (the string to remove) + int byLen; // Replacement length (the string to replace replace by) + int lastReplacePos; // Distance between replace and end of last replace + int count; // Number of replacements + + // Sanity checks and initialization + if (!text || !replace) return NULL; + + replaceLen = strlen(replace); + if (replaceLen == 0) return NULL; // Empty replace causes infinite loop during count + + if (!by) by = ""; // Replace by nothing if not provided + byLen = strlen(by); + + // Count the number of replacements needed + insertPoint = text; + for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen; + + // Allocate returning string and point temp to it + temp = result = malloc(strlen(text) + (byLen - replaceLen)*count + 1); + + if (!result) return NULL; // Memory could not be allocated + + // First time through the loop, all the variable are set correctly from here on, + // temp points to the end of the result string + // insertPoint points to the next occurrence of replace in text + // text points to the remainder of text after "end of replace" + while (count--) + { + insertPoint = strstr(text, replace); + lastReplacePos = insertPoint - text; + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, by) + byLen; + text += lastReplacePos + replaceLen; // Move to next "end of replace" + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + strcpy(temp, text); + + return result; +} + +// Insert text in a specific position, moves all text forward +// REQUIRES: strlen(), strcpy(), strtok() +// WARNING: Allocated memory should be manually freed +const char *TextInsert(const char *text, const char *insert, int position) +{ + int textLen = strlen(text); + int insertLen = strlen(insert); + + char *result = (char *)malloc(textLen + insertLen + 1); + + for (int i = 0; i < position; i++) result[i] = text[i]; + for (int i = position; i < insertLen + position; i++) result[i] = insert[i]; + for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i]; + + result[textLen + insertLen] = '\0'; // Make sure text string is valid! + + return result; +} + +// Join text strings with delimiter +// REQUIRES: strcat() +const char *TextJoin(const char **textList, int count, const char *delimiter) +{ + static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(text, 0, MAX_TEXT_BUFFER_LENGTH); + + int delimiterLen = strlen(delimiter); + + for (int i = 0; i < count; i++) + { + strcat(text, textList[i]); + if ((delimiterLen > 0) && (i < (count - 1))) strcat(text, delimiter); + } + + return text; +} + // Split string into multiple strings -// NOTE: Files count is returned by parameters pointer -// NOTE: Allocated memory should be manually freed -char **SplitText(char *text, char delimiter, int *strCount) +// REQUIRES: strlen(), strcpy(), strtok() +// WARNING: Allocated memory should be manually freed +char **TextSplit(const char *text, char delimiter, int *count) { #define MAX_SUBSTRING_LENGTH 128 + + // TODO: Allocate memory properly for every substring size - char **strings = NULL; + char **result = NULL; + int len = strlen(text); - char *strDup = (char *)malloc(len + 1); - strcpy(strDup, text); + char *textcopy = (char *)malloc(len + 1); + strcpy(textcopy, text); int counter = 1; - // Count how many substrings we have on string + // Count how many substrings we have on text and init memory for each of them for (int i = 0; i < len; i++) if (text[i] == delimiter) counter++; // Memory allocation for substrings - strings = (char **)malloc(sizeof(char *)*counter); - for (int i = 0; i < counter; i++) strings[i] = (char *)malloc(sizeof(char)*MAX_SUBSTRING_LENGTH); + result = (char **)malloc(sizeof(char *)*counter); + for (int i = 0; i < counter; i++) result[i] = (char *)malloc(sizeof(char)*MAX_SUBSTRING_LENGTH); char *substrPtr = NULL; char delimiters[1] = { delimiter }; // Only caring for one delimiter - substrPtr = strtok(strDup, delimiters); + substrPtr = strtok(textcopy, delimiters); for (int i = 0; (i < counter) && (substrPtr != NULL); i++) { - strcpy(strings[i], substrPtr); + strcpy(result[i], substrPtr); substrPtr = strtok(NULL, delimiters); } - *strCount = counter; - free(strDup); + *count = counter; + free(textcopy); - return strings; + return result; } -// Check if two text string are equal -bool IsEqualText(const char *text1, const char *text2) +// Get pointers to substrings separated by delimiter +void TextSplitEx(const char *text, char delimiter, int *count, const char **ptrs, int *lengths) { - bool result = false; + int elementsCount = 0; + int charsCount = 0; - if (strcmp(text1, text2) == 0) result = true; + ptrs[0] = text; - return result; + for (int i = 0; text[i] != '\0'; i++) + { + charsCount++; + + if (text[i] == delimiter) + { + lengths[elementsCount] = charsCount - 1; + charsCount = 0; + elementsCount++; + + ptrs[elementsCount] = &text[i + 1]; + } + } + + lengths[elementsCount] = charsCount; + elementsCount++; + + *count = elementsCount; +} + +// Append text at specific position and move cursor! +// REQUIRES: strcpy() +void TextAppend(char *text, const char *append, int *position) +{ + strcpy(text + *position, append); + *position += strlen(append); +} + +// Find first text occurrence within a string +// REQUIRES: strstr() +int TextFindIndex(const char *text, const char *find) +{ + int position = -1; + + char *ptr = strstr(text, find); + + if (ptr != NULL) position = ptr - text; + + return position; +} + +// Get upper case version of provided string +// REQUIRES: toupper() +const char *TextToUpper(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') buffer[i] = (char)toupper(text[i]); + else { buffer[i] = '\0'; break; } + } + + return buffer; } +// Get lower case version of provided string +// REQUIRES: tolower() +const char *TextToLower(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') buffer[i] = (char)tolower(text[i]); + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get Pascal case notation version of provided string +// REQUIRES: toupper() +const char *TextToPascal(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + buffer[0] = (char)toupper(text[0]); + + for (int i = 1, j = 1; i < MAX_TEXT_BUFFER_LENGTH; i++, j++) + { + if (text[j] != '\0') + { + if (text[j] != '_') buffer[i] = text[j]; + else + { + j++; + buffer[i] = (char)toupper(text[j]); + } + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} +//---------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- -- cgit v1.2.3 From 0619571149f1fde5500dec4b64a94541ef0981f2 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 29 Dec 2018 14:44:28 +0100 Subject: ADDED: DrawTextRec() and example --- examples/text/text_draw_inside_rectangle.c | 120 +++++++++++++++++++++++++ examples/text/text_draw_inside_rectangle.png | Bin 0 -> 17844 bytes src/raylib.h | 3 +- src/text.c | 125 +++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 examples/text/text_draw_inside_rectangle.c create mode 100644 examples/text/text_draw_inside_rectangle.png (limited to 'examples') diff --git a/examples/text/text_draw_inside_rectangle.c b/examples/text/text_draw_inside_rectangle.c new file mode 100644 index 00000000..e60fa5e5 --- /dev/null +++ b/examples/text/text_draw_inside_rectangle.c @@ -0,0 +1,120 @@ +/******************************************************************************************* +* +* raylib [text] example - Draw text inside a rectangle +* +* This example has been created using raylib 2.3 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2018 Vlad Adrian (@demizdor) +* +********************************************************************************************/ + +#include "raylib.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [text] example - draw text inside a rectangle"); + + char text[] = "Text cannot escape\tthis container\t...word wrap also works when active so here's\ + a long text for testing.\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\ + tempor incididunt ut labore et dolore magna aliqua. Nec ullamcorper sit amet risus nullam eget felis eget."; + + bool resizing = false; + bool wordWrap = true; + + Rectangle container = { 25, 25, screenWidth - 50, screenHeight - 250}; + Rectangle resizer = { container.x + container.width - 17, container.y + container.height - 17, 14, 14 }; + + // Minimum width and heigh for the container rectangle + const int minWidth = 60; + const int minHeight = 60; + const int maxWidth = screenWidth - 50; + const int maxHeight = screenHeight - 160; + + Vector2 lastMouse = { 0, 0 }; // Stores last mouse coordinates + + Color borderColor = MAROON; // Container border color + + Font font = GetFontDefault(); // Get default system font + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed(KEY_SPACE)) wordWrap = !wordWrap; + + Vector2 mouse = GetMousePosition(); + + // Check if the mouse is inside the container and toggle border color + if (CheckCollisionPointRec(mouse, container)) borderColor = Fade(MAROON, 0.4f); + else if (!resizing) borderColor = MAROON; + + // Container resizing logic + if (resizing) + { + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) resizing = false; + + int width = container.width + (mouse.x - lastMouse.x); + container.width = (width > minWidth)? ((width < maxWidth)? width : maxWidth) : minWidth; + + int height = container.height + (mouse.y - lastMouse.y); + container.height = (height > minHeight)? ((height < maxHeight)? height : maxHeight) : minHeight; + } + else + { + // Check if we're resizing + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(mouse, resizer)) resizing = true; + } + + // Move resizer rectangle properly + resizer.x = container.x + container.width - 17; + resizer.y = container.y + container.height - 17; + + lastMouse = mouse; // Update mouse + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawRectangleLinesEx(container, 3, borderColor); // Draw container border + + // Draw text in container (add some padding) + DrawTextRec(font, text, + (Rectangle){ container.x + 4, container.y + 4, container.width - 4, container.height - 4 }, + 20.0f, 2.0f, wordWrap, GRAY); + + DrawRectangleRec(resizer, borderColor); // Draw the resize box + + // Draw info + DrawText("Word Wrap: ", 313, screenHeight-115, 20, BLACK); + if (wordWrap) DrawText("ON", 447, screenHeight - 115, 20, RED); + else DrawText("OFF", 447, screenHeight - 115, 20, BLACK); + DrawText("Press [SPACE] to toggle word wrap", 218, screenHeight - 91, 20, GRAY); + + DrawRectangle(0, screenHeight - 54, screenWidth, 54, GRAY); + DrawText("Click hold & drag the to resize the container", 155, screenHeight - 38, 20, RAYWHITE); + DrawRectangleRec((Rectangle){ 382, screenHeight - 34, 12, 12 }, MAROON); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/text/text_draw_inside_rectangle.png b/examples/text/text_draw_inside_rectangle.png new file mode 100644 index 00000000..f46b1096 Binary files /dev/null and b/examples/text/text_draw_inside_rectangle.png differ diff --git a/src/raylib.h b/src/raylib.h index 8535f8d5..0e9e0155 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1110,7 +1110,8 @@ RLAPI void UnloadFont(Font font); // Text drawing functions RLAPI void DrawFPS(int posX, int posY); // Shows current FPS RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint); // Draw text using font inside rectangle limits // Text misc. functions RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font diff --git a/src/text.c b/src/text.c index ebf2599a..0b343af3 100644 --- a/src/text.c +++ b/src/text.c @@ -779,6 +779,131 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f } } +// Draw text using font inside rectangle limits +void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) +{ + int length = strlen(text); + int textOffsetX = 0; // Offset between characters + int textOffsetY = 0; // Required for line break! + float scaleFactor = 0.0f; + + unsigned char letter = 0; // Current character + int index = 0; // Index position in sprite font + + scaleFactor = fontSize/font.baseSize; + + enum { MEASURE_WORD = 0, DRAW_WORD = 1 }; + int state = wordWrap ? MEASURE_WORD : DRAW_WORD; + int lastTextOffsetX = 0; + int wordStart = 0; + + bool firstWord = true; + + for (int i = 0; i < length; i++) + { + int glyphWidth = 0; + letter = (unsigned char)text[i]; + + if (letter != '\n') + { + if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! + { + // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) + letter = (unsigned char)text[i + 1]; + index = GetGlyphIndex(font, (int)letter); + i++; + } + else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! + { + // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) + letter = (unsigned char)text[i + 1]; + index = GetGlyphIndex(font, (int)letter + 64); + i++; + } + else index = GetGlyphIndex(font, (unsigned char)text[i]); + + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.chars[index].rec.width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + } + + // NOTE: When word wrap is active first we measure a `word`(measure until a ' ','\n','\t' is found) + // then set all the variables back to what they were before the measurement, change the state to + // draw that word then change the state again and repeat until the end of the string...when the word + // doesn't fit inside the rect we simple increase `textOffsetY` to draw it on the next line + if (state == MEASURE_WORD) + { + // Measuring state + if ((letter == ' ') || (letter == '\n') || (letter == '\t') || ((i + 1) == length)) + { + int t = textOffsetX + glyphWidth; + + if (textOffsetX+1>=rec.width) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + lastTextOffsetX = t - lastTextOffsetX; + textOffsetX = 0; + } + else + { + textOffsetX = lastTextOffsetX; + lastTextOffsetX = t; + } + + glyphWidth = 0; + state = !state; // Change state + t = i; + i = firstWord?-1:wordStart; + wordStart = t; + } + } + else + { + // Drawing state + int t = textOffsetX + glyphWidth; + + if (letter == '\n') + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + lastTextOffsetX = t - lastTextOffsetX; + if (lastTextOffsetX < 0) lastTextOffsetX = 0; + textOffsetX = 0; + } + else if ((letter != ' ') && (letter != '\t')) + { + if ((t + 1) >= rec.width) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + + if ((textOffsetY + (int)((font.baseSize + font.baseSize/2)*scaleFactor)) > rec.height) break; + + DrawTexturePro(font.texture, font.chars[index].rec, + (Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor, + rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor, + font.chars[index].rec.width*scaleFactor, + font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, tint); + } + + if (wordWrap) + { + if ((letter == ' ') || (letter == '\n') || (letter == '\t')) + { + // After drawing a word change the state + firstWord = false; + i = wordStart; + textOffsetX = lastTextOffsetX; + glyphWidth = 0; + state = !state; + } + } + } + + textOffsetX += glyphWidth; + } +} + // Measure string width for default font int MeasureText(const char *text, int fontSize) { -- cgit v1.2.3 From d5735720b0cd8893320774e074694b48eb7b7d01 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 1 Jan 2019 20:53:30 +0100 Subject: Update Makefile for Emscripten --- examples/Makefile | 18 +++++++++--------- src/Makefile | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'examples') diff --git a/examples/Makefile b/examples/Makefile index 53e8555d..976cfbc1 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -118,8 +118,8 @@ endif ifeq ($(PLATFORM),PLATFORM_WEB) # Emscripten required variables EMSDK_PATH = C:/emsdk - EMSCRIPTEN_VERSION = 1.38.20 - CLANG_VERSION = e1.38.20_64bit + EMSCRIPTEN_VERSION = 1.38.21 + CLANG_VERSION = e1.38.21_64bit PYTHON_VERSION = 2.7.13.1_64bit\python-2.7.13.amd64 NODE_VERSION = 8.9.1_64bit export PATH = $(EMSDK_PATH);$(EMSDK_PATH)\clang\$(CLANG_VERSION);$(EMSDK_PATH)\node\$(NODE_VERSION)\bin;$(EMSDK_PATH)\python\$(PYTHON_VERSION);$(EMSDK_PATH)\emscripten\$(EMSCRIPTEN_VERSION);C:\raylib\MinGW\bin:$$(PATH) @@ -130,23 +130,23 @@ endif # RAYLIB_RELEASE_PATH points to provided binaries or your freshly built version. ifeq ($(PLATFORM),PLATFORM_DESKTOP) ifeq ($(PLATFORM_OS),WINDOWS) - RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/win32/mingw32 + RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/src endif ifeq ($(PLATFORM_OS),LINUX) - RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/linux + RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/src endif ifeq ($(PLATFORM_OS),OSX) - RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/osx + RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/src endif ifeq ($(PLATFORM_OS),BSD) - RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/bsd + RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/src endif endif ifeq ($(PLATFORM),PLATFORM_RPI) - RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/rpi + RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/src endif ifeq ($(PLATFORM),PLATFORM_WEB) - RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/release/libs/html5 + RAYLIB_RELEASE_PATH = $(RAYLIB_PATH)/src endif # EXAMPLE_RUNTIME_PATH embeds a custom runtime location of libraylib.so or other desired libraries @@ -249,7 +249,7 @@ ifeq ($(PLATFORM),PLATFORM_WEB) # -s EMTERPRETIFY_ASYNC=1 # support synchronous loops by emterpreter # --profiling # include information for code profiling # --preload-file resources # specify a resources folder for data compilation - CFLAGS += -Os -s USE_GLFW=3 -s USE_SDL=2 -s ASSERTIONS=1 -s WASM=1 -s EMTERPRETIFY=1 -s EMTERPRETIFY_ASYNC=1 + CFLAGS += -Os -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s EMTERPRETIFY=1 -s EMTERPRETIFY_ASYNC=1 # NOTE: Simple raylib examples are compiled to be interpreter by emterpreter, that way, # we can compile same code for ALL platforms with no change required, but, working on bigger diff --git a/src/Makefile b/src/Makefile index e3e9d31b..2c2bfb27 100644 --- a/src/Makefile +++ b/src/Makefile @@ -149,8 +149,8 @@ endif ifeq ($(PLATFORM),PLATFORM_WEB) # Emscripten required variables EMSDK_PATH = C:/emsdk - EMSCRIPTEN_VERSION = 1.38.20 - CLANG_VERSION = e1.38.20_64bit + EMSCRIPTEN_VERSION ?= 1.38.21 + CLANG_VERSION = e$(EMSCRIPTEN_VERSION)_64bit PYTHON_VERSION = 2.7.13.1_64bit\python-2.7.13.amd64 NODE_VERSION = 8.9.1_64bit export PATH = $(EMSDK_PATH);$(EMSDK_PATH)\clang\$(CLANG_VERSION);$(EMSDK_PATH)\node\$(NODE_VERSION)\bin;$(EMSDK_PATH)\python\$(PYTHON_VERSION);$(EMSDK_PATH)\emscripten\$(EMSCRIPTEN_VERSION);C:\raylib\MinGW\bin:$$(PATH) @@ -300,7 +300,7 @@ ifeq ($(PLATFORM),PLATFORM_WEB) # -s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing # -s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) # -s USE_PTHREADS=1 # multithreading support - CFLAGS += -s USE_GLFW=3 -s USE_SDL=2 -s ASSERTIONS=1 --profiling + CFLAGS += -s USE_GLFW=3 -s ASSERTIONS=1 --profiling endif ifeq ($(PLATFORM),PLATFORM_ANDROID) # Compiler flags for arquitecture -- cgit v1.2.3 From 490e930665c7a2853f4a678aa397a7ed3551d8df Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 5 Jan 2019 17:25:35 +0100 Subject: Reviewed example --- examples/textures/textures_raw_data.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'examples') diff --git a/examples/textures/textures_raw_data.c b/examples/textures/textures_raw_data.c index b038792b..481bd66a 100644 --- a/examples/textures/textures_raw_data.c +++ b/examples/textures/textures_raw_data.c @@ -32,8 +32,8 @@ int main() UnloadImage(fudesumiRaw); // Unload CPU (RAM) image data // Generate a checked texture by code (1024x1024 pixels) - int width = 1024; - int height = 1024; + int width = 960; + int height = 480; // Dynamic memory allocation to store pixels data (Color type) Color *pixels = (Color *)malloc(width*height*sizeof(Color)); @@ -42,8 +42,8 @@ int main() { for (int x = 0; x < width; x++) { - if (((x/32+y/32)/1)%2 == 0) pixels[y*height + x] = ORANGE; - else pixels[y*height + x] = GOLD; + if (((x/32+y/32)/1)%2 == 0) pixels[y*width + x] = ORANGE; + else pixels[y*width + x] = GOLD; } } @@ -73,9 +73,9 @@ int main() DrawTexture(checked, screenWidth/2 - checked.width/2, screenHeight/2 - checked.height/2, Fade(WHITE, 0.5f)); DrawTexture(fudesumi, 430, -30, WHITE); - DrawText("CHECKED TEXTURE ", 84, 100, 30, BROWN); - DrawText("GENERATED by CODE", 72, 164, 30, BROWN); - DrawText("and RAW IMAGE LOADING", 46, 226, 30, BROWN); + DrawText("CHECKED TEXTURE ", 84, 85, 30, BROWN); + DrawText("GENERATED by CODE", 72, 148, 30, BROWN); + DrawText("and RAW IMAGE LOADING", 46, 210, 30, BROWN); DrawText("(c) Fudesumi sprite by Eiden Marsal", 310, screenHeight - 20, 10, BROWN); -- cgit v1.2.3 From 5c614f69755623e346105d17c71697005bd2900c Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 5 Jan 2019 19:24:42 +0100 Subject: Some code tweaks --- examples/models/models_material_pbr.c | 2 +- src/raylib.h | 2 +- src/rlgl.h | 33 +++++++++++++++++++++++++-------- 3 files changed, 27 insertions(+), 10 deletions(-) (limited to 'examples') diff --git a/examples/models/models_material_pbr.c b/examples/models/models_material_pbr.c index a4a10d60..6885f753 100644 --- a/examples/models/models_material_pbr.c +++ b/examples/models/models_material_pbr.c @@ -156,7 +156,7 @@ static Material LoadMaterialPBR(Color albedo, float metalness, float roughness) Texture2D cubemap = GenTextureCubemap(shdrCubemap, texHDR, CUBEMAP_SIZE); mat.maps[MAP_IRRADIANCE].texture = GenTextureIrradiance(shdrIrradiance, cubemap, IRRADIANCE_SIZE); mat.maps[MAP_PREFILTER].texture = GenTexturePrefilter(shdrPrefilter, cubemap, PREFILTERED_SIZE); - mat.maps[MAP_BRDF].texture = GenTextureBRDF(shdrBRDF, cubemap, BRDF_SIZE); + mat.maps[MAP_BRDF].texture = GenTextureBRDF(shdrBRDF, BRDF_SIZE); UnloadTexture(cubemap); UnloadTexture(texHDR); diff --git a/src/raylib.h b/src/raylib.h index e422e6bc..6d4b08d0 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1241,7 +1241,7 @@ RLAPI Matrix GetMatrixModelview(); // Get RLAPI Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size); // Generate cubemap texture from HDR texture RLAPI Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size); // Generate irradiance texture using cubemap data RLAPI Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size); // Generate prefilter texture using cubemap data -RLAPI Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size); // Generate BRDF texture using cubemap data +RLAPI Texture2D GenTextureBRDF(Shader shader, int size); // Generate BRDF texture using cubemap data // Shading begin/end functions RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing diff --git a/src/rlgl.h b/src/rlgl.h index 7776879a..2b575614 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -480,7 +480,7 @@ Matrix GetMatrixModelview(); // Get inter Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size); // Generate cubemap texture from HDR texture Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size); // Generate irradiance texture using cubemap data Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size); // Generate prefilter texture using cubemap data -Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size); // Generate BRDF texture using cubemap data +Texture2D GenTextureBRDF(Shader shader, int size); // Generate BRDF texture using cubemap data // Shading begin/end functions void BeginShaderMode(Shader shader); // Begin custom shader drawing @@ -849,7 +849,7 @@ static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays; #if defined(SUPPORT_VR_SIMULATOR) // VR global variables -static VrStereoConfig vrConfig; // VR stereo configuration for simulator +static VrStereoConfig vrConfig = { 0 }; // VR stereo configuration for simulator static bool vrSimulatorReady = false; // VR simulator ready flag static bool vrStereoRender = false; // VR stereo rendering enabled/disabled flag // NOTE: This flag is useful to render data over stereo image (i.e. FPS) @@ -2993,7 +2993,7 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); // Set up cubemap to render and attach to framebuffer - // NOTE: faces are stored with 16 bit floating point values + // NOTE: Faces are stored as 32 bit floating point values glGenTextures(1, &cubemap.id); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); for (unsigned int i = 0; i < 6; i++) @@ -3012,7 +3012,7 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // Create projection (transposed) and different views for each face + // Create projection and different views for each face Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); Matrix fboViews[6] = { MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), @@ -3050,6 +3050,10 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) cubemap.width = size; cubemap.height = size; + cubemap.mipmaps = 1; + cubemap.format = UNCOMPRESSED_R32G32B32; + + // TODO: Texture2D is a GL_TEXTURE_CUBE_MAP, not a GL_TEXTURE_2D! Only cubemap.id makes some sense... #endif return cubemap; } @@ -3216,15 +3220,20 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) } // Generate BRDF texture using cubemap data -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) +// NOTE: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 +Texture2D GenTextureBRDF(Shader shader, int size) { Texture2D brdf = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Generate BRDF convolution texture glGenTextures(1, &brdf.id); glBindTexture(GL_TEXTURE_2D, brdf.id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, size, size, 0, GL_RG, GL_FLOAT, 0); +#if defined(GRAPHICS_API_OPENGL_33) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, size, size, 0, GL_RG, GL_FLOAT, NULL); +#elif defined(GRAPHICS_API_OPENGL_ES2) + if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, size, size, 0, GL_RG, GL_FLOAT, NULL); +#endif + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -3236,7 +3245,11 @@ Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) glGenRenderbuffers(1, &rbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); +#if defined(GRAPHICS_API_OPENGL_33) glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); +#elif defined(GRAPHICS_API_OPENGL_ES2) + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size, size); +#endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdf.id, 0); glViewport(0, 0, size, size); @@ -3246,6 +3259,10 @@ Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) // Unbind framebuffer and textures glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Unload framebuffer but keep color texture + glDeleteRenderbuffers(1, &rbo); + glDeleteFramebuffers(1, &fbo); // Reset viewport dimensions to default glViewport(0, 0, screenWidth, screenHeight); -- cgit v1.2.3 From f4fe7f4d4c6d25e4bcc1ea4b48af07ef49b82720 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 6 Jan 2019 15:49:29 +0100 Subject: Review BRDF texture generation Actually, that function should be redesigned... --- examples/models/resources/shaders/brdf.fs | 73 ++++++++++++++----------------- src/rlgl.h | 7 ++- 2 files changed, 38 insertions(+), 42 deletions(-) (limited to 'examples') diff --git a/examples/models/resources/shaders/brdf.fs b/examples/models/resources/shaders/brdf.fs index 59ae384a..3e8777d2 100644 --- a/examples/models/resources/shaders/brdf.fs +++ b/examples/models/resources/shaders/brdf.fs @@ -1,12 +1,15 @@ /******************************************************************************************* * -* rPBR [shader] - Bidirectional reflectance distribution function fragment shader +* BRDF LUT Generation - Bidirectional reflectance distribution function fragment shader +* +* REF: https://github.com/HectorMF/BRDFGenerator * * Copyright (c) 2017 Victor Fisac * **********************************************************************************************/ #version 330 + #define MAX_SAMPLES 1024u // Input vertex attributes (from vertex shader) @@ -18,43 +21,30 @@ const float PI = 3.14159265359; // Output fragment color out vec4 finalColor; -float DistributionGGX(vec3 N, vec3 H, float roughness); -float RadicalInverse_VdC(uint bits); vec2 Hammersley(uint i, uint N); -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); +float RadicalInverseVdC(uint bits); float GeometrySchlickGGX(float NdotV, float roughness); float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness); +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); vec2 IntegrateBRDF(float NdotV, float roughness); -float DistributionGGX(vec3 N, vec3 H, float roughness) -{ - float a = roughness*roughness; - float a2 = a*a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH*NdotH; - - float nom = a2; - float denom = (NdotH2*(a2 - 1.0) + 1.0); - denom = PI*denom*denom; - - return nom/denom; -} - -float RadicalInverse_VdC(uint bits) +float RadicalInverseVdC(uint bits) { - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } +// Compute Hammersley coordinates vec2 Hammersley(uint i, uint N) { - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); + return vec2(float(i)/float(N), RadicalInverseVdC(i)); } +// Integrate number of importance samples for (roughness and NoV) vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { float a = roughness*roughness; @@ -85,6 +75,7 @@ float GeometrySchlickGGX(float NdotV, float roughness) return nom/denom; } +// Compute the geometry term for the BRDF given roughness squared, NoV, NoL float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { float NdotV = max(dot(N, V), 0.0); @@ -97,29 +88,31 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) vec2 IntegrateBRDF(float NdotV, float roughness) { - vec3 V = vec3(sqrt(1.0 - NdotV*NdotV), 0.0, NdotV); float A = 0.0; - float B = 0.0; + float B = 0.0; + vec3 V = vec3(sqrt(1.0 - NdotV*NdotV), 0.0, NdotV); vec3 N = vec3(0.0, 0.0, 1.0); - for(uint i = 0u; i < MAX_SAMPLES; i++) + for (int i = 0; i < MAX_SAMPLES; i++) { // Generate a sample vector that's biased towards the preferred alignment direction (importance sampling) - vec2 Xi = Hammersley(i, MAX_SAMPLES); - vec3 H = ImportanceSampleGGX(Xi, N, roughness); - vec3 L = normalize(2.0*dot(V, H)*H - V); - float NdotL = max(L.z, 0.0); - float NdotH = max(H.z, 0.0); - float VdotH = max(dot(V, H), 0.0); + + vec2 Xi = Hammersley(i, MAX_SAMPLES); // Compute a Hammersely coordinate + vec3 H = ImportanceSampleGGX(Xi, N, roughness); // Integrate number of importance samples for (roughness and NoV) + vec3 L = normalize(2.0*dot(V, H)*H - V); // Compute reflection vector L + + float NdotL = max(L.z, 0.0); // Compute normal dot light + float NdotH = max(H.z, 0.0); // Compute normal dot half + float VdotH = max(dot(V, H), 0.0); // Compute view dot half if (NdotL > 0.0) { - float G = GeometrySmith(N, V, L, roughness); - float G_Vis = (G*VdotH)/(NdotH*NdotV); - float Fc = pow(1.0 - VdotH, 5.0); + float G = GeometrySmith(N, V, L, roughness); // Compute the geometry term for the BRDF given roughness squared, NoV, NoL + float GVis = (G*VdotH)/(NdotH*NdotV); // Compute the visibility term given G, VoH, NoH, NoV, NoL + float Fc = pow(1.0 - VdotH, 5.0); // Compute the fresnel term given VoH - A += (1.0 - Fc)*G_Vis; - B += Fc*G_Vis; + A += (1.0 - Fc)*GVis; // Sum the result given fresnel, geometry, visibility + B += Fc*GVis; } } diff --git a/src/rlgl.h b/src/rlgl.h index 2b575614..8c7526fb 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3221,6 +3221,7 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) // Generate BRDF texture using cubemap data // NOTE: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 +// TODO: Review implementation: https://github.com/HectorMF/BRDFGenerator Texture2D GenTextureBRDF(Shader shader, int size) { Texture2D brdf = { 0 }; @@ -3229,9 +3230,9 @@ Texture2D GenTextureBRDF(Shader shader, int size) glGenTextures(1, &brdf.id); glBindTexture(GL_TEXTURE_2D, brdf.id); #if defined(GRAPHICS_API_OPENGL_33) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, size, size, 0, GL_RG, GL_FLOAT, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL); #elif defined(GRAPHICS_API_OPENGL_ES2) - if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, size, size, 0, GL_RG, GL_FLOAT, NULL); + if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); #endif glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -3269,6 +3270,8 @@ Texture2D GenTextureBRDF(Shader shader, int size) brdf.width = size; brdf.height = size; + brdf.mipmaps = 1; + brdf.format = UNCOMPRESSED_R32G32B32; #endif return brdf; } -- cgit v1.2.3 From adf0c6486480d601f7e20272ac43abe19233997d Mon Sep 17 00:00:00 2001 From: Marco Lizza Date: Wed, 9 Jan 2019 16:08:10 +0100 Subject: Fixing typo in examples makefile, preventing build. --- examples/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/Makefile b/examples/Makefile index 53e8555d..deeadb75 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -407,7 +407,7 @@ EXAMPLES = \ models/models_box_collisions \ models/models_billboard \ models/models_obj_loading \ - models/models_obj_viewing \ + models/models_obj_viewer \ models/models_heightmap \ models/models_cubicmap \ models/models_mesh_picking \ -- cgit v1.2.3 From 0fe56b1674c99aa75c0dd6f45d77cbff80cf218c Mon Sep 17 00:00:00 2001 From: Marco Lizza Date: Wed, 9 Jan 2019 16:20:56 +0100 Subject: Adding basic palette-switching example using uniform arrays. --- examples/CMakeLists.txt | 1 + examples/Makefile | 1 + .../resources/shaders/glsl100/palette-switch.fs | 29 ++++ .../resources/shaders/glsl120/palette-switch.fs | 27 ++++ .../resources/shaders/glsl330/palette-switch.fs | 30 ++++ examples/shaders/shaders_palette_switch.c | 160 +++++++++++++++++++++ 6 files changed, 248 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl100/palette-switch.fs create mode 100644 examples/shaders/resources/shaders/glsl120/palette-switch.fs create mode 100644 examples/shaders/resources/shaders/glsl330/palette-switch.fs create mode 100644 examples/shaders/shaders_palette_switch.c (limited to 'examples') diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1c0c935c..1d4fdd79 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -52,6 +52,7 @@ if(${PLATFORM} MATCHES "Android") list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shaders_model_shader.c) list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shaders_postprocessing.c) list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shaders_raymarching.c) + list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/shaders/shaders_palette_switch.c) elseif(${PLATFORM} MATCHES "Web") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s EMTERPRETIFY=1 -s EMTERPRETIFY_ASYNC=1") diff --git a/examples/Makefile b/examples/Makefile index deeadb75..b652482f 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -420,6 +420,7 @@ EXAMPLES = \ shaders/shaders_custom_uniform \ shaders/shaders_postprocessing \ shaders/shaders_raymarching \ + shaders/shaders_palette_switch \ audio/audio_sound_loading \ audio/audio_music_stream \ audio/audio_module_playing \ diff --git a/examples/shaders/resources/shaders/glsl100/palette-switch.fs b/examples/shaders/resources/shaders/glsl100/palette-switch.fs new file mode 100644 index 00000000..65a7bd29 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/palette-switch.fs @@ -0,0 +1,29 @@ +#version 100 + +precision mediump float; + +const int colors = 8; + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform ivec3 palette[colors]; + +void main() +{ + // Texel color fetching from texture sampler + vec4 texelColor = texture(texture0, fragTexCoord) * fragColor; + + // Convert the (normalized) texel color RED component (GB would work, too) + // to the palette index by scaling up from [0, 1] to [0, 255]. + int index = int(texelColor.r * 255.0); + ivec3 color = palette[index]; + + // Calculate final fragment color. Note that the palette color components + // are defined in the range [0, 255] and need to be normalized to [0, 1] + // for OpenGL to work. + gl_FragColor = vec4(color / 255.0, texelColor.a); +} diff --git a/examples/shaders/resources/shaders/glsl120/palette-switch.fs b/examples/shaders/resources/shaders/glsl120/palette-switch.fs new file mode 100644 index 00000000..b4384502 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/palette-switch.fs @@ -0,0 +1,27 @@ +#version 120 + +const int colors = 8; + +// Input fragment attributes (from fragment shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform ivec3 palette[colors]; + +void main() +{ + // Texel color fetching from texture sampler + vec4 texelColor = texture(texture0, fragTexCoord) * fragColor; + + // Convert the (normalized) texel color RED component (GB would work, too) + // to the palette index by scaling up from [0, 1] to [0, 255]. + int index = int(texelColor.r * 255.0); + ivec3 color = palette[index]; + + // Calculate final fragment color. Note that the palette color components + // are defined in the range [0, 255] and need to be normalized to [0, 1] + // for OpenGL to work. + gl_FragColor = vec4(color / 255.0, texelColor.a); +} diff --git a/examples/shaders/resources/shaders/glsl330/palette-switch.fs b/examples/shaders/resources/shaders/glsl330/palette-switch.fs new file mode 100644 index 00000000..178c42c5 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/palette-switch.fs @@ -0,0 +1,30 @@ +#version 330 + +const int colors = 8; + +// Input fragment attributes (from fragment shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform ivec3 palette[colors]; + +// Output fragment color +out vec4 finalColor; + +void main() +{ + // Texel color fetching from texture sampler + vec4 texelColor = texture(texture0, fragTexCoord) * fragColor; + + // Convert the (normalized) texel color RED component (GB would work, too) + // to the palette index by scaling up from [0, 1] to [0, 255]. + int index = int(texelColor.r * 255.0); + ivec3 color = palette[index]; + + // Calculate final fragment color. Note that the palette color components + // are defined in the range [0, 255] and need to be normalized to [0, 1] + // for OpenGL to work. + finalColor = vec4(color / 255.0, texelColor.a); +} diff --git a/examples/shaders/shaders_palette_switch.c b/examples/shaders/shaders_palette_switch.c new file mode 100644 index 00000000..03151b66 --- /dev/null +++ b/examples/shaders/shaders_palette_switch.c @@ -0,0 +1,160 @@ +/******************************************************************************************* +* +* raylib [shaders] example - Apply a postprocessing shader to a scene +* +* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, +* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version. +* +* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3), to test this example +* on OpenGL ES 2.0 platforms (Android, Raspberry Pi, HTML5), use #version 100 shaders +* raylib comes with shaders ready for both versions, check raylib/shaders install folder +* +* This example has been created using raylib 2.x (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2015 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +#define MAX_PALETTES 3 +#define COLORS_PER_PALETTE 8 +#define VALUES_PER_COLOR 3 + +static const int palettes[MAX_PALETTES][COLORS_PER_PALETTE * VALUES_PER_COLOR] = { + { + 0, 0, 0, + 255, 0, 0, + 0, 255, 0, + 0, 0, 255, + 0, 255, 255, + 255, 0, 255, + 255, 255, 0, + 255, 255, 255, + }, + { + 4, 12, 6, + 17, 35, 24, + 30, 58, 41, + 48, 93, 66, + 77, 128, 97, + 137, 162, 87, + 190, 220, 127, + 238, 255, 204, + }, + { + 21, 25, 26, + 138, 76, 88, + 217, 98, 117, + 230, 184, 193, + 69, 107, 115, + 75, 151, 166, + 165, 189, 194, + 255, 245, 247, + } +}; + +static const char *paletteText[] = { + "3-BIT RGB", + "AMMO-8 (GameBoy-like)", + "RKBV (2-strip film)" +}; + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - palette-switch shader"); + + // Load shader to be used on some parts drawing + // NOTE 1: Using GLSL 330 shader version, on OpenGL ES 2.0 use GLSL 100 shader version + // NOTE 2: Defining 0 (NULL) for vertex shader forces usage of internal default vertex shader + Shader shader = LoadShader(0, FormatText("resources/shaders/glsl%i/palette-switch.fs", GLSL_VERSION)); + + // Get variable (uniform) location on the shader to connect with the program + // NOTE: If uniform variable could not be found in the shader, function returns -1 + int paletteLoc = GetShaderLocation(shader, "palette"); + + // Initial index not set, will be automatically bounded below. + int currentPalette = -1; + + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + int paletteIndex = currentPalette; + if (IsKeyPressed(KEY_RIGHT)) paletteIndex++; + else if (IsKeyPressed(KEY_LEFT)) paletteIndex--; + + if (paletteIndex >= MAX_PALETTES) paletteIndex = 0; + else if (paletteIndex < 0) paletteIndex = MAX_PALETTES - 1; + + // Send new value to the shader to be used on drawing. + // Note that we are sending RGB triplets w/o the alpha channel *only* if the current + // palette index has changed (in order to save performances). + if (currentPalette != paletteIndex) { + currentPalette = paletteIndex; + SetShaderValueArrayi(shader, paletteLoc, palettes[currentPalette], VALUES_PER_COLOR, COLORS_PER_PALETTE); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginShaderMode(shader); + + // Draw horizontal screen-wide rectangles with increasing "palette index". + // The used palette index is encoded in the RGB components of the pixel. + int linesPerRectangle = screenHeight / COLORS_PER_PALETTE; + int leftover = screenHeight % COLORS_PER_PALETTE; + int y = 0; + + for (int i = 0; i < COLORS_PER_PALETTE; ++i) { + int height = linesPerRectangle; + if (leftover > 0) { + height += 1; + leftover -= 1; + } + + DrawRectangle(0, y, screenWidth, height, CLITERAL{ i, i, i, 255 }); + + y += height; + } + + EndShaderMode(); + + DrawText("CURRENT PALETTE:", 10, 15, 20, RAYWHITE); + DrawText(paletteText[currentPalette], 240, 15, 20, RED); + DrawText("< >", 540, 10, 30, DARKBLUE); + + DrawFPS(700, 15); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shader); // Unload shader + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} -- cgit v1.2.3 From 0c5bee4c9addc20b8b3c443820764854c63f76d7 Mon Sep 17 00:00:00 2001 From: Marco Lizza Date: Wed, 9 Jan 2019 16:33:09 +0100 Subject: Limiting FPS to 60 for uniformity with other examples. --- examples/shaders/shaders_palette_switch.c | 1 + 1 file changed, 1 insertion(+) (limited to 'examples') diff --git a/examples/shaders/shaders_palette_switch.c b/examples/shaders/shaders_palette_switch.c index 03151b66..ad09ca73 100644 --- a/examples/shaders/shaders_palette_switch.c +++ b/examples/shaders/shaders_palette_switch.c @@ -88,6 +88,7 @@ int main() // Initial index not set, will be automatically bounded below. int currentPalette = -1; + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop -- cgit v1.2.3 From 55f8dbc755c2e31d2112e71439fef0d31e8090a6 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 10 Jan 2019 11:25:26 +0100 Subject: WARNING: Redesigned SetShaderValue() --- examples/models/models_material_pbr.c | 20 ++--- examples/models/models_skybox.c | 4 +- examples/models/rlights.h | 10 +-- examples/others/standard_lighting.c | 25 +++--- .../resources/shaders/glsl330/palette-switch.fs | 2 +- examples/shaders/shaders_custom_uniform.c | 2 +- examples/shaders/shaders_palette_switch.c | 24 +++--- examples/shaders/shaders_raymarching.c | 12 +-- src/raylib.h | 23 +++++- src/rlgl.h | 96 ++++++++++++---------- 10 files changed, 120 insertions(+), 98 deletions(-) (limited to 'examples') diff --git a/examples/models/models_material_pbr.c b/examples/models/models_material_pbr.c index 6885f753..f93c7a68 100644 --- a/examples/models/models_material_pbr.c +++ b/examples/models/models_material_pbr.c @@ -64,7 +64,7 @@ int main() // Send to material PBR shader camera view position float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z }; - SetShaderValue(model.material.shader, model.material.shader.locs[LOC_VECTOR_VIEW], cameraPos, 3); + SetShaderValue(model.material.shader, model.material.shader.locs[LOC_VECTOR_VIEW], cameraPos, UNIFORM_VEC3); //---------------------------------------------------------------------------------- // Draw @@ -148,9 +148,9 @@ static Material LoadMaterialPBR(Color albedo, float metalness, float roughness) Shader shdrBRDF = LoadShader(PATH_BRDF_VS, PATH_BRDF_FS); // Setup required shader locations - SetShaderValuei(shdrCubemap, GetShaderLocation(shdrCubemap, "equirectangularMap"), (int[1]){ 0 }, 1); - SetShaderValuei(shdrIrradiance, GetShaderLocation(shdrIrradiance, "environmentMap"), (int[1]){ 0 }, 1); - SetShaderValuei(shdrPrefilter, GetShaderLocation(shdrPrefilter, "environmentMap"), (int[1]){ 0 }, 1); + SetShaderValue(shdrCubemap, GetShaderLocation(shdrCubemap, "equirectangularMap"), (int[1]){ 0 }, UNIFORM_INT); + SetShaderValue(shdrIrradiance, GetShaderLocation(shdrIrradiance, "environmentMap"), (int[1]){ 0 }, UNIFORM_INT); + SetShaderValue(shdrPrefilter, GetShaderLocation(shdrPrefilter, "environmentMap"), (int[1]){ 0 }, UNIFORM_INT); Texture2D texHDR = LoadTexture("resources/dresden_square.hdr"); Texture2D cubemap = GenTextureCubemap(shdrCubemap, texHDR, CUBEMAP_SIZE); @@ -174,14 +174,14 @@ static Material LoadMaterialPBR(Color albedo, float metalness, float roughness) SetTextureFilter(mat.maps[MAP_OCCLUSION].texture, FILTER_BILINEAR); // Enable sample usage in shader for assigned textures - SetShaderValuei(mat.shader, GetShaderLocation(mat.shader, "albedo.useSampler"), (int[1]){ 1 }, 1); - SetShaderValuei(mat.shader, GetShaderLocation(mat.shader, "normals.useSampler"), (int[1]){ 1 }, 1); - SetShaderValuei(mat.shader, GetShaderLocation(mat.shader, "metalness.useSampler"), (int[1]){ 1 }, 1); - SetShaderValuei(mat.shader, GetShaderLocation(mat.shader, "roughness.useSampler"), (int[1]){ 1 }, 1); - SetShaderValuei(mat.shader, GetShaderLocation(mat.shader, "occlusion.useSampler"), (int[1]){ 1 }, 1); + SetShaderValue(mat.shader, GetShaderLocation(mat.shader, "albedo.useSampler"), (int[1]){ 1 }, UNIFORM_INT); + SetShaderValue(mat.shader, GetShaderLocation(mat.shader, "normals.useSampler"), (int[1]){ 1 }, UNIFORM_INT); + SetShaderValue(mat.shader, GetShaderLocation(mat.shader, "metalness.useSampler"), (int[1]){ 1 }, UNIFORM_INT); + SetShaderValue(mat.shader, GetShaderLocation(mat.shader, "roughness.useSampler"), (int[1]){ 1 }, UNIFORM_INT); + SetShaderValue(mat.shader, GetShaderLocation(mat.shader, "occlusion.useSampler"), (int[1]){ 1 }, UNIFORM_INT); int renderModeLoc = GetShaderLocation(mat.shader, "renderMode"); - SetShaderValuei(mat.shader, renderModeLoc, (int[1]){ 0 }, 1); + SetShaderValue(mat.shader, renderModeLoc, (int[1]){ 0 }, UNIFORM_INT); // Set up material properties color mat.maps[MAP_ALBEDO].color = albedo; diff --git a/examples/models/models_skybox.c b/examples/models/models_skybox.c index a6b233e3..c7f76ecf 100644 --- a/examples/models/models_skybox.c +++ b/examples/models/models_skybox.c @@ -30,11 +30,11 @@ int main() // Load skybox shader and set required locations // NOTE: Some locations are automatically set at shader loading skybox.material.shader = LoadShader("resources/shaders/skybox.vs", "resources/shaders/skybox.fs"); - SetShaderValuei(skybox.material.shader, GetShaderLocation(skybox.material.shader, "environmentMap"), (int[1]){ MAP_CUBEMAP }, 1); + SetShaderValue(skybox.material.shader, GetShaderLocation(skybox.material.shader, "environmentMap"), (int[1]){ MAP_CUBEMAP }, UNIFORM_INT); // Load cubemap shader and setup required shader locations Shader shdrCubemap = LoadShader("resources/shaders/cubemap.vs", "resources/shaders/cubemap.fs"); - SetShaderValuei(shdrCubemap, GetShaderLocation(shdrCubemap, "equirectangularMap"), (int[1]){ 0 }, 1); + SetShaderValue(shdrCubemap, GetShaderLocation(shdrCubemap, "equirectangularMap"), (int[1]){ 0 }, UNIFORM_INT); // Load HDR panorama (sphere) texture Texture2D texHDR = LoadTexture("resources/dresden_square.hdr"); diff --git a/examples/models/rlights.h b/examples/models/rlights.h index 0da3e2cb..19504473 100644 --- a/examples/models/rlights.h +++ b/examples/models/rlights.h @@ -158,20 +158,20 @@ Light CreateLight(int type, Vector3 pos, Vector3 targ, Color color, Shader shade void UpdateLightValues(Shader shader, Light light) { // Send to shader light enabled state and type - SetShaderValuei(shader, light.enabledLoc, (int[1]){ light.enabled }, 1); - SetShaderValuei(shader, light.typeLoc, (int[1]){ light.type }, 1); + SetShaderValue(shader, light.enabledLoc, &light.enabled, UNIFORM_INT); + SetShaderValue(shader, light.typeLoc, &light.type, UNIFORM_INT); // Send to shader light position values float position[3] = { light.position.x, light.position.y, light.position.z }; - SetShaderValue(shader, light.posLoc, position, 3); + SetShaderValue(shader, light.posLoc, position, UNIFORM_VEC3); // Send to shader light target position values float target[3] = { light.target.x, light.target.y, light.target.z }; - SetShaderValue(shader, light.targetLoc, target, 3); + SetShaderValue(shader, light.targetLoc, target, UNIFORM_VEC3); // Send to shader light color values float diff[4] = { (float)light.color.r/(float)255, (float)light.color.g/(float)255, (float)light.color.b/(float)255, (float)light.color.a/(float)255 }; - SetShaderValue(shader, light.colorLoc, diff, 4); + SetShaderValue(shader, light.colorLoc, diff, UNIFORM_VEC4); } #endif // RLIGHTS_IMPLEMENTATION \ No newline at end of file diff --git a/examples/others/standard_lighting.c b/examples/others/standard_lighting.c index 72890436..7034aa35 100644 --- a/examples/others/standard_lighting.c +++ b/examples/others/standard_lighting.c @@ -350,9 +350,6 @@ static void GetShaderLightsLocations(Shader shader) // Set shader uniform values for lights // NOTE: It would be far easier with shader UBOs but are not supported on OpenGL ES 2.0 -// TODO: Replace glUniform1i(), glUniform1f(), glUniform3f(), glUniform4f(): -//SetShaderValue(Shader shader, int uniformLoc, float *value, int size) -//SetShaderValuei(Shader shader, int uniformLoc, int *value, int size) static void SetShaderLightsValues(Shader shader) { int tempInt[8] = { 0 }; @@ -363,20 +360,20 @@ static void SetShaderLightsValues(Shader shader) if (i < lightsCount) { tempInt[0] = lights[i]->enabled; - SetShaderValuei(shader, lightsLocs[i][0], tempInt, 1); //glUniform1i(lightsLocs[i][0], lights[i]->enabled); + SetShaderValue(shader, lightsLocs[i][0], tempInt, UNIFORM_INT); //glUniform1i(lightsLocs[i][0], lights[i]->enabled); tempInt[0] = lights[i]->type; - SetShaderValuei(shader, lightsLocs[i][1], tempInt, 1); //glUniform1i(lightsLocs[i][1], lights[i]->type); + SetShaderValue(shader, lightsLocs[i][1], tempInt, UNIFORM_INT); //glUniform1i(lightsLocs[i][1], lights[i]->type); tempFloat[0] = (float)lights[i]->diffuse.r/255.0f; tempFloat[1] = (float)lights[i]->diffuse.g/255.0f; tempFloat[2] = (float)lights[i]->diffuse.b/255.0f; tempFloat[3] = (float)lights[i]->diffuse.a/255.0f; - SetShaderValue(shader, lightsLocs[i][5], tempFloat, 4); + SetShaderValue(shader, lightsLocs[i][5], tempFloat, UNIFORM_VEC4); //glUniform4f(lightsLocs[i][5], (float)lights[i]->diffuse.r/255, (float)lights[i]->diffuse.g/255, (float)lights[i]->diffuse.b/255, (float)lights[i]->diffuse.a/255); tempFloat[0] = lights[i]->intensity; - SetShaderValue(shader, lightsLocs[i][6], tempFloat, 1); + SetShaderValue(shader, lightsLocs[i][6], tempFloat, UNIFORM_FLOAT); switch (lights[i]->type) { @@ -385,10 +382,10 @@ static void SetShaderLightsValues(Shader shader) tempFloat[0] = lights[i]->position.x; tempFloat[1] = lights[i]->position.y; tempFloat[2] = lights[i]->position.z; - SetShaderValue(shader, lightsLocs[i][2], tempFloat, 3); + SetShaderValue(shader, lightsLocs[i][2], tempFloat, UNIFORM_VEC3); tempFloat[0] = lights[i]->radius; - SetShaderValue(shader, lightsLocs[i][4], tempFloat, 1); + SetShaderValue(shader, lightsLocs[i][4], tempFloat, UNIFORM_FLOAT); //glUniform3f(lightsLocs[i][2], lights[i]->position.x, lights[i]->position.y, lights[i]->position.z); //glUniform1f(lightsLocs[i][4], lights[i]->radius); @@ -401,7 +398,7 @@ static void SetShaderLightsValues(Shader shader) tempFloat[0] = direction.x; tempFloat[1] = direction.y; tempFloat[2] = direction.z; - SetShaderValue(shader, lightsLocs[i][3], tempFloat, 3); + SetShaderValue(shader, lightsLocs[i][3], tempFloat, UNIFORM_VEC3); //glUniform3f(lightsLocs[i][3], direction.x, direction.y, direction.z); } break; @@ -410,7 +407,7 @@ static void SetShaderLightsValues(Shader shader) tempFloat[0] = lights[i]->position.x; tempFloat[1] = lights[i]->position.y; tempFloat[2] = lights[i]->position.z; - SetShaderValue(shader, lightsLocs[i][2], tempFloat, 3); + SetShaderValue(shader, lightsLocs[i][2], tempFloat, UNIFORM_VEC3); //glUniform3f(lightsLocs[i][2], lights[i]->position.x, lights[i]->position.y, lights[i]->position.z); @@ -420,11 +417,11 @@ static void SetShaderLightsValues(Shader shader) tempFloat[0] = direction.x; tempFloat[1] = direction.y; tempFloat[2] = direction.z; - SetShaderValue(shader, lightsLocs[i][3], tempFloat, 3); + SetShaderValue(shader, lightsLocs[i][3], tempFloat, UNIFORM_VEC3); //glUniform3f(lightsLocs[i][3], direction.x, direction.y, direction.z); tempFloat[0] = lights[i]->coneAngle; - SetShaderValue(shader, lightsLocs[i][7], tempFloat, 1); + SetShaderValue(shader, lightsLocs[i][7], tempFloat, UNIFORM_FLOAT); //glUniform1f(lightsLocs[i][7], lights[i]->coneAngle); } break; default: break; @@ -433,7 +430,7 @@ static void SetShaderLightsValues(Shader shader) else { tempInt[0] = 0; - SetShaderValuei(shader, lightsLocs[i][0], tempInt, 1); //glUniform1i(lightsLocs[i][0], 0); // Light disabled + SetShaderValue(shader, lightsLocs[i][0], tempInt, UNIFORM_INT); //glUniform1i(lightsLocs[i][0], 0); // Light disabled } } } diff --git a/examples/shaders/resources/shaders/glsl330/palette-switch.fs b/examples/shaders/resources/shaders/glsl330/palette-switch.fs index 178c42c5..61b532ed 100644 --- a/examples/shaders/resources/shaders/glsl330/palette-switch.fs +++ b/examples/shaders/resources/shaders/glsl330/palette-switch.fs @@ -16,7 +16,7 @@ out vec4 finalColor; void main() { // Texel color fetching from texture sampler - vec4 texelColor = texture(texture0, fragTexCoord) * fragColor; + vec4 texelColor = texture(texture0, fragTexCoord)*fragColor; // Convert the (normalized) texel color RED component (GB would work, too) // to the palette index by scaling up from [0, 1] to [0, 255]. diff --git a/examples/shaders/shaders_custom_uniform.c b/examples/shaders/shaders_custom_uniform.c index de76a376..fbfd82d0 100644 --- a/examples/shaders/shaders_custom_uniform.c +++ b/examples/shaders/shaders_custom_uniform.c @@ -79,7 +79,7 @@ int main() swirlCenter[1] = screenHeight - mousePosition.y; // Send new value to the shader to be used on drawing - SetShaderValue(shader, swirlCenterLoc, swirlCenter, 2); + SetShaderValue(shader, swirlCenterLoc, swirlCenter, UNIFORM_VEC2); UpdateCamera(&camera); // Update camera //---------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_palette_switch.c b/examples/shaders/shaders_palette_switch.c index ad09ca73..d0b56190 100644 --- a/examples/shaders/shaders_palette_switch.c +++ b/examples/shaders/shaders_palette_switch.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [shaders] example - Apply a postprocessing shader to a scene +* raylib [shaders] example - Color palette switch * * NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, * OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version. @@ -9,10 +9,10 @@ * on OpenGL ES 2.0 platforms (Android, Raspberry Pi, HTML5), use #version 100 shaders * raylib comes with shaders ready for both versions, check raylib/shaders install folder * -* This example has been created using raylib 2.x (www.raylib.com) +* This example has been created using raylib 2.3 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * -* Copyright (c) 2015 Ramon Santamaria (@raysan5) +* Copyright (c) 2019 Ramon Santamaria (@raysan5) * ********************************************************************************************/ @@ -28,7 +28,7 @@ #define COLORS_PER_PALETTE 8 #define VALUES_PER_COLOR 3 -static const int palettes[MAX_PALETTES][COLORS_PER_PALETTE * VALUES_PER_COLOR] = { +static const int palettes[MAX_PALETTES][COLORS_PER_PALETTE*VALUES_PER_COLOR] = { { 0, 0, 0, 255, 0, 0, @@ -74,7 +74,7 @@ int main() int screenWidth = 800; int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [shaders] example - palette-switch shader"); + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - color palette switch"); // Load shader to be used on some parts drawing // NOTE 1: Using GLSL 330 shader version, on OpenGL ES 2.0 use GLSL 100 shader version @@ -106,9 +106,10 @@ int main() // Send new value to the shader to be used on drawing. // Note that we are sending RGB triplets w/o the alpha channel *only* if the current // palette index has changed (in order to save performances). - if (currentPalette != paletteIndex) { + if (currentPalette != paletteIndex) + { currentPalette = paletteIndex; - SetShaderValueArrayi(shader, paletteLoc, palettes[currentPalette], VALUES_PER_COLOR, COLORS_PER_PALETTE); + SetShaderValueV(shader, paletteLoc, palettes[currentPalette], UNIFORM_IVEC3, COLORS_PER_PALETTE); } //---------------------------------------------------------------------------------- @@ -126,14 +127,17 @@ int main() int leftover = screenHeight % COLORS_PER_PALETTE; int y = 0; - for (int i = 0; i < COLORS_PER_PALETTE; ++i) { + for (int i = 0; i < COLORS_PER_PALETTE; ++i) + { int height = linesPerRectangle; - if (leftover > 0) { + + if (leftover > 0) + { height += 1; leftover -= 1; } - DrawRectangle(0, y, screenWidth, height, CLITERAL{ i, i, i, 255 }); + DrawRectangle(0, y, screenWidth, height, (Color){ i, i, i, 255 }); y += height; } diff --git a/examples/shaders/shaders_raymarching.c b/examples/shaders/shaders_raymarching.c index d1f9d5f8..79868a2a 100644 --- a/examples/shaders/shaders_raymarching.c +++ b/examples/shaders/shaders_raymarching.c @@ -48,7 +48,7 @@ int main() int resolutionLoc = GetShaderLocation(shader, "resolution"); float resolution[2] = { screenWidth, screenHeight }; - SetShaderValue(shader, resolutionLoc, resolution, 2); + SetShaderValue(shader, resolutionLoc, resolution, UNIFORM_VEC2); float runTime = 0.0f; @@ -70,11 +70,11 @@ int main() runTime += deltaTime; // Set shader required uniform values - SetShaderValue(shader, viewEyeLoc, cameraPos, 3); - SetShaderValue(shader, viewCenterLoc, cameraTarget, 3); - SetShaderValue(shader, viewUpLoc, cameraUp, 3); - SetShaderValue(shader, deltaTimeLoc, &deltaTime, 1); - SetShaderValue(shader, runTimeLoc, &runTime, 1); + SetShaderValue(shader, viewEyeLoc, cameraPos, UNIFORM_VEC3); + SetShaderValue(shader, viewCenterLoc, cameraTarget, UNIFORM_VEC3); + SetShaderValue(shader, viewUpLoc, cameraUp, UNIFORM_VEC3); + SetShaderValue(shader, deltaTimeLoc, &deltaTime, UNIFORM_FLOAT); + SetShaderValue(shader, runTimeLoc, &runTime, UNIFORM_FLOAT); //---------------------------------------------------------------------------------- // Draw diff --git a/src/raylib.h b/src/raylib.h index 275a3b63..5e18ae00 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -676,6 +676,23 @@ typedef enum { #define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO #define LOC_MAP_SPECULAR LOC_MAP_METALNESS +// Shader uniform data types +typedef enum { + UNIFORM_BOOL = 0, + UNIFORM_INT, + UNIFORM_UNIT, + UNIFORM_FLOAT, + UNIFORM_IVEC2, + UNIFORM_IVEC3, + UNIFORM_IVEC4, + UNIFORM_UVEC2, + UNIFORM_UVEC3, + UNIFORM_UVEC4, + UNIFORM_VEC2, + UNIFORM_VEC3, + UNIFORM_VEC4, +} ShaderUniformDataType; + // Material map type typedef enum { MAP_ALBEDO = 0, // MAP_DIFFUSE @@ -1229,10 +1246,8 @@ RLAPI Texture2D GetTextureDefault(void); // Get // Shader configuration functions RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size); // Set shader uniform value (float) -RLAPI void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size); // Set shader uniform value (int) -RLAPI void SetShaderValueArray(Shader shader, int uniformLoc, const float *value, int size, int count); // Set shader uniform value (array of float/vec2/vec3/vec4) -RLAPI void SetShaderValueArrayi(Shader shader, int uniformLoc, const int *value, int size, int count); // Set shader uniform value (array of int/ivec2/ivec3/ivec4) +RLAPI void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count); // Set shader uniform value vector RLAPI void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) RLAPI void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) RLAPI void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) diff --git a/src/rlgl.h b/src/rlgl.h index 07f18f0f..e40553bc 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -332,6 +332,23 @@ typedef unsigned char byte; LOC_MAP_PREFILTER, LOC_MAP_BRDF } ShaderLocationIndex; + + // Shader uniform data types + typedef enum { + UNIFORM_BOOL = 0, + UNIFORM_INT, + UNIFORM_UNIT, + UNIFORM_FLOAT, + UNIFORM_IVEC2, + UNIFORM_IVEC3, + UNIFORM_IVEC4, + UNIFORM_UVEC2, + UNIFORM_UVEC3, + UNIFORM_UVEC4, + UNIFORM_VEC2, + UNIFORM_VEC3, + UNIFORM_VEC4, + } ShaderUniformDataType; #define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO #define LOC_MAP_SPECULAR LOC_MAP_METALNESS @@ -468,10 +485,8 @@ Texture2D GetTextureDefault(void); // Get defau // Shader configuration functions int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size); // Set shader uniform value (float) -void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size); // Set shader uniform value (int) -void SetShaderValueArray(Shader shader, int uniformLoc, const float *value, int size, int count); // Set shader uniform value (array of float/vec2/vec3/vec4) -void SetShaderValueArrayi(Shader shader, int uniformLoc, const int *value, int size, int count); // Set shader uniform value (array of int/ivec2/ivec3/ivec4) +void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType); // Set shader uniform value +void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count); // Set shader uniform value vector void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) @@ -2893,49 +2908,40 @@ int GetShaderLocation(Shader shader, const char *uniformName) return location; } -// Set shader uniform value (float/vec2/vec3/vec4) -void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size) -{ - SetShaderValueArray(shader, uniformLoc, value, size, 1); -} - -// Set shader uniform value (int/ivec2/ivec3/ivec4) -void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size) +// Set shader uniform value +void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType) { - SetShaderValueArrayi(shader, uniformLoc, value, size, 1); + SetShaderValueV(shader, uniformLoc, value, uniformType, 1); } -// Set shader uniform value (array of float/vec2/vec3/vec4) -void SetShaderValueArray(Shader shader, int uniformLoc, const float *value, int size, int count) +// Set shader uniform value vector +void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glUseProgram(shader.id); - if (size == 1) glUniform1fv(uniformLoc, count, value); // Shader uniform type: float[] - else if (size == 2) glUniform2fv(uniformLoc, count, value); // Shader uniform type: vec2[] - else if (size == 3) glUniform3fv(uniformLoc, count, value); // Shader uniform type: vec3[] - else if (size == 4) glUniform4fv(uniformLoc, count, value); // Shader uniform type: vec4[] - else TraceLog(LOG_WARNING, "Wrong size for shader's uniform value (1 to 4 supported)"); - + switch (uniformType) + { + case UNIFORM_BOOL: glUniform1iv(uniformLoc, count, (int *)value); break; + case UNIFORM_INT: glUniform1iv(uniformLoc, count, (int *)value); break; + case UNIFORM_UNIT: glUniform1uiv(uniformLoc, count, (unsigned int *)value); break; + case UNIFORM_FLOAT: glUniform1fv(uniformLoc, count, (float *)value); break; + case UNIFORM_IVEC2: glUniform2iv(uniformLoc, count, (int *)value); break; + case UNIFORM_IVEC3: glUniform3iv(uniformLoc, count, (int *)value); break; + case UNIFORM_IVEC4: glUniform4iv(uniformLoc, count, (int *)value); break; + case UNIFORM_UVEC2: glUniform2uiv(uniformLoc, count, (unsigned int *)value); break; + case UNIFORM_UVEC3: glUniform3uiv(uniformLoc, count, (unsigned int *)value); break; + case UNIFORM_UVEC4: glUniform4uiv(uniformLoc, count, (unsigned int *)value); break; + case UNIFORM_VEC2: glUniform2fv(uniformLoc, count, (float *)value); break; + case UNIFORM_VEC3: glUniform3fv(uniformLoc, count, (float *)value); break; + case UNIFORM_VEC4: glUniform4fv(uniformLoc, count, (float *)value); break; + default: TraceLog(LOG_WARNING, "Shader uniform could not be set data type not recognized"); + } + //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set #endif } -// Set shader uniform value (array of int/ivec2/ivec3/ivec4) -void SetShaderValueArrayi(Shader shader, int uniformLoc, const int *value, int size, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - if (size == 1) glUniform1iv(uniformLoc, count, value); // Shader uniform type: int[] - else if (size == 2) glUniform2iv(uniformLoc, count, value); // Shader uniform type: ivec2[] - else if (size == 3) glUniform3iv(uniformLoc, count, value); // Shader uniform type: ivec3[] - else if (size == 4) glUniform4iv(uniformLoc, count, value); // Shader uniform type: ivec4[] - else TraceLog(LOG_WARNING, "Wrong size for shader's uniform value (1 to 4 supported)"); - - //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set -#endif -} // Set shader uniform value (matrix 4x4) void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) @@ -4286,15 +4292,15 @@ static void SetStereoConfig(VrDeviceInfo hmd) #if defined(SUPPORT_DISTORTION_SHADER) // Update distortion shader with lens and distortion-scale parameters - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftLensCenter"), leftLensCenter, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightLensCenter"), rightLensCenter, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftScreenCenter"), leftScreenCenter, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightScreenCenter"), rightScreenCenter, 2); - - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scale"), scale, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scaleIn"), scaleIn, 2); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "hmdWarpParam"), hmd.lensDistortionValues, 4); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, 4); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftLensCenter"), leftLensCenter, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightLensCenter"), rightLensCenter, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftScreenCenter"), leftScreenCenter, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightScreenCenter"), rightScreenCenter, UNIFORM_VEC2); + + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scale"), scale, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scaleIn"), scaleIn, UNIFORM_VEC2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "hmdWarpParam"), hmd.lensDistortionValues, UNIFORM_VEC4); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, UNIFORM_VEC4); #endif // Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance) -- cgit v1.2.3 From 93471b0a7c75fc675f27fc74dfda281eb8310a7a Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 10 Jan 2019 16:32:40 +0100 Subject: WARNING: Renamed module: audio -> raudio Planning to promote raudio module as a simple and easy-to-use front-end for the amazing mini_al library, so the name change. Name comes from raylib-audio but in spanish it also remembers to word "raudo", meaning "very fast", an analogy that fits perfectly to the usefulness and performance of the library! Consequently, raylib version has been bumped to 2.4-dev. --- HISTORY.md | 2 +- README.md | 2 +- examples/others/audio_standalone.c | 141 --- examples/others/raudio_standalone.c | 141 +++ projects/Geany/raylib_compile_sources.bat | 4 +- projects/Notepad++/npes_saved_mingw.txt | Bin 7620 -> 7622 bytes projects/Notepad++/npes_saved_tcc.txt | Bin 7546 -> 7548 bytes projects/scripts/linux-build.sh | 2 +- projects/scripts/osx-build.sh | 2 +- projects/scripts/windows-build.bat | 2 +- src/CMakeLists.txt | 4 +- src/CMakeOptions.txt | 2 +- src/Makefile | 4 +- src/audio.c | 1958 ----------------------------- src/audio.h | 182 --- src/config.h | 2 +- src/config.h.in | 2 +- src/libraylib.a | Bin 0 -> 1287474 bytes src/raudio.c | 1958 +++++++++++++++++++++++++++++ src/raudio.h | 182 +++ 20 files changed, 2295 insertions(+), 2295 deletions(-) delete mode 100644 examples/others/audio_standalone.c create mode 100644 examples/others/raudio_standalone.c delete mode 100644 src/audio.c delete mode 100644 src/audio.h create mode 100644 src/libraylib.a create mode 100644 src/raudio.c create mode 100644 src/raudio.h (limited to 'examples') diff --git a/HISTORY.md b/HISTORY.md index ee416694..f9f33ef3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -149,7 +149,7 @@ On November 2016, only 4 months after raylib 1.5, arrives raylib 1.6. This new v Complete [raylib Lua binding](https://github.com/raysan5/raylib-lua). All raylib functions plus the +60 code examples have been ported to Lua, now Lua users can enjoy coding videogames in Lua while using all the internal power of raylib. This addition also open the doors to Lua scripting support for a future raylib-based engine, being able to move game logic (Init, Update, Draw, De-Init) to Lua scripts while keep using raylib functionality. -Completely redesigned [audio module](https://github.com/raysan5/raylib/blob/master/src/audio.c). Based on the new direction taken in raylib 1.5, it has been further improved and more functionality added (+20 new functions) to allow raw audio processing and streaming. [FLAC file format support](https://github.com/raysan5/raylib/blob/master/src/external/dr_flac.h) has also been added. In the same line, [OpenAL Soft](https://github.com/kcat/openal-soft) backend is now provided as a static library in Windows to allow static linking and get ride of OpenAL32.dll. Now raylib Windows games are completey self-contained, no external libraries required any more! +Completely redesigned [audio module](https://github.com/raysan5/raylib/blob/master/src/raudio.c). Based on the new direction taken in raylib 1.5, it has been further improved and more functionality added (+20 new functions) to allow raw audio processing and streaming. [FLAC file format support](https://github.com/raysan5/raylib/blob/master/src/external/dr_flac.h) has also been added. In the same line, [OpenAL Soft](https://github.com/kcat/openal-soft) backend is now provided as a static library in Windows to allow static linking and get ride of OpenAL32.dll. Now raylib Windows games are completey self-contained, no external libraries required any more! [Physac](https://github.com/victorfisac/Physac) module has been moved to its own repository and it has been improved A LOT, actually, library has been completely rewritten from scratch by [@victorfisac](https://github.com/victorfisac), multiple samples have been added together with countless new features to match current standard 2D physic libraries. Results are amazing! diff --git a/README.md b/README.md index 2967ffb3..b11665c8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ features raylib uses on its [core](https://github.com/raysan5/raylib/blob/master/src/core.c) module the outstanding [GLFW3](http://www.glfw.org/) library, embedded inside raylib in the form of [rglfw](https://github.com/raysan5/raylib/blob/master/src/rglfw.c) module, avoiding that way external dependencies. -raylib uses on its [audio](https://github.com/raysan5/raylib/blob/master/src/audio.c) module, the amazing [mini_al](https://github.com/dr-soft/mini_al) audio library, single-file header-only and supporting multiple platforms and multiple audio backends. +raylib uses on its [audio](https://github.com/raysan5/raylib/blob/master/src/raudio.c) module, the amazing [mini_al](https://github.com/dr-soft/mini_al) audio library, single-file header-only and supporting multiple platforms and multiple audio backends. raylib uses internally multiple single-file header-only libraries to support multiple fileformats loading and saving, all those libraries are embedded with raylib and available in [src/external](https://github.com/raysan5/raylib/tree/master/src/external) directory. diff --git a/examples/others/audio_standalone.c b/examples/others/audio_standalone.c deleted file mode 100644 index f08fbf82..00000000 --- a/examples/others/audio_standalone.c +++ /dev/null @@ -1,141 +0,0 @@ -/******************************************************************************************* -* -* raylib [audio] example - Using audio module as standalone module -* -* NOTE: This example does not require any graphic device, it can run directly on console. -* -* DEPENDENCIES: -* mini_al.h - Audio device management lib (http://kcat.strangesoft.net/openal.html) -* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) -* jar_xm.h - XM module file loading -* jar_mod.h - MOD audio file loading -* dr_flac.h - FLAC audio file loading -* -* COMPILATION: -* gcc -c ..\..\src\external\mini_al.c -Wall -I. -* gcc -o audio_standalone.exe audio_standalone.c ..\..\src\audio.c ..\..\src\external\stb_vorbis.c mini_al.o / -* -I..\..\src -I..\..\src\external -L. -Wall -std=c99 / -* -DAUDIO_STANDALONE -DSUPPORT_FILEFORMAT_WAV -DSUPPORT_FILEFORMAT_OGG -* -* LICENSE: zlib/libpng -* -* This example is licensed under an unmodified zlib/libpng license, which is an OSI-certified, -* BSD-like license that allows static linking with closed source software: -* -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -********************************************************************************************/ - -#include "audio.h" // Audio library - -#include // Required for: printf() - -#if defined(_WIN32) - #include // Windows only, no stardard library -#else - -// Provide kbhit() function in non-Windows platforms -#include -#include -#include -#include - -// Check if a key has been pressed -static int kbhit(void) -{ - struct termios oldt, newt; - int ch; - int oldf; - - tcgetattr(STDIN_FILENO, &oldt); - newt = oldt; - newt.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &newt); - oldf = fcntl(STDIN_FILENO, F_GETFL, 0); - fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); - - ch = getchar(); - - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); - fcntl(STDIN_FILENO, F_SETFL, oldf); - - if (ch != EOF) - { - ungetc(ch, stdin); - return 1; - } - - return 0; -} - -// Get pressed character -static char getch() { return getchar(); } - -#endif - -#define KEY_ESCAPE 27 - -int main() -{ - // Initialization - //-------------------------------------------------------------------------------------- - static unsigned char key; - - InitAudioDevice(); - - Sound fxWav = LoadSound("resources/audio/weird.wav"); // Load WAV audio file - Sound fxOgg = LoadSound("resources/audio/tanatana.ogg"); // Load OGG audio file - - Music music = LoadMusicStream("resources/audio/guitar_noodling.ogg"); - PlayMusicStream(music); - - printf("\nPress s or d to play sounds...\n"); - //-------------------------------------------------------------------------------------- - - // Main loop - while (key != KEY_ESCAPE) - { - if (kbhit()) key = getch(); - - if (key == 's') - { - PlaySound(fxWav); - key = 0; - } - - if (key == 'd') - { - PlaySound(fxOgg); - key = 0; - } - - UpdateMusicStream(music); - } - - // De-Initialization - //-------------------------------------------------------------------------------------- - UnloadSound(fxWav); // Unload sound data - UnloadSound(fxOgg); // Unload sound data - - UnloadMusicStream(music); // Unload music stream data - - CloseAudioDevice(); - //-------------------------------------------------------------------------------------- - - return 0; -} diff --git a/examples/others/raudio_standalone.c b/examples/others/raudio_standalone.c new file mode 100644 index 00000000..930bde85 --- /dev/null +++ b/examples/others/raudio_standalone.c @@ -0,0 +1,141 @@ +/******************************************************************************************* +* +* raylib [audio] example - Using raudio module as standalone module +* +* NOTE: This example does not require any graphic device, it can run directly on console. +* +* DEPENDENCIES: +* mini_al.h - Audio device management lib (http://kcat.strangesoft.net/openal.html) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* dr_flac.h - FLAC audio file loading +* +* COMPILATION: +* gcc -c ..\..\src\external\mini_al.c -Wall -I. +* gcc -o audio_standalone.exe audio_standalone.c ..\..\src\raudio.c mini_al.o / +* -I..\..\src -I..\..\src\external -L. -Wall -std=c99 / +* -DAUDIO_STANDALONE -DSUPPORT_FILEFORMAT_WAV -DSUPPORT_FILEFORMAT_OGG +* +* LICENSE: zlib/libpng +* +* This example is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2014-2019 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +********************************************************************************************/ + +#include "raudio.h" // raylib audio library + +#include // Required for: printf() + +#if defined(_WIN32) + #include // Windows only, no stardard library +#else + +// Provide kbhit() function in non-Windows platforms +#include +#include +#include +#include + +// Check if a key has been pressed +static int kbhit(void) +{ + struct termios oldt, newt; + int ch; + int oldf; + + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + + ch = getchar(); + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, oldf); + + if (ch != EOF) + { + ungetc(ch, stdin); + return 1; + } + + return 0; +} + +// Get pressed character +static char getch() { return getchar(); } + +#endif + +#define KEY_ESCAPE 27 + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + static unsigned char key; + + InitAudioDevice(); + + Sound fxWav = LoadSound("resources/audio/weird.wav"); // Load WAV audio file + Sound fxOgg = LoadSound("resources/audio/tanatana.ogg"); // Load OGG audio file + + Music music = LoadMusicStream("resources/audio/guitar_noodling.ogg"); + PlayMusicStream(music); + + printf("\nPress s or d to play sounds...\n"); + //-------------------------------------------------------------------------------------- + + // Main loop + while (key != KEY_ESCAPE) + { + if (kbhit()) key = getch(); + + if (key == 's') + { + PlaySound(fxWav); + key = 0; + } + + if (key == 'd') + { + PlaySound(fxOgg); + key = 0; + } + + UpdateMusicStream(music); + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadSound(fxWav); // Unload sound data + UnloadSound(fxOgg); // Unload sound data + + UnloadMusicStream(music); // Unload music stream data + + CloseAudioDevice(); + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/projects/Geany/raylib_compile_sources.bat b/projects/Geany/raylib_compile_sources.bat index 8a5c1e7b..708873c2 100644 --- a/projects/Geany/raylib_compile_sources.bat +++ b/projects/Geany/raylib_compile_sources.bat @@ -23,14 +23,14 @@ gcc -O2 -c shapes.c -std=c99 -Wall -DPLATFORM_DESKTOP gcc -O2 -c textures.c -std=c99 -Wall -DPLATFORM_DESKTOP gcc -O2 -c text.c -std=c99 -Wall -DPLATFORM_DESKTOP gcc -O2 -c models.c -std=c99 -Wall -DPLATFORM_DESKTOP -gcc -O2 -c audio.c -std=c99 -Wall -DPLATFORM_DESKTOP +gcc -O2 -c raudio.c -std=c99 -Wall -DPLATFORM_DESKTOP gcc -O2 -c external/mini_al.c -Wall -I. gcc -O2 -c utils.c -std=c99 -Wall -DPLATFORM_DESKTOP :: . :: . > Generate raylib library :: ------------------------------ -ar rcs libraylib.a core.o rglfw.o shapes.o textures.o text.o models.o audio.o mini_al.o utils.o +ar rcs libraylib.a core.o rglfw.o shapes.o textures.o text.o models.o raudio.o mini_al.o utils.o :: . :: > Installing raylib library :: ----------------------------- diff --git a/projects/Notepad++/npes_saved_mingw.txt b/projects/Notepad++/npes_saved_mingw.txt index 6857fa13..6d10a1b0 100644 Binary files a/projects/Notepad++/npes_saved_mingw.txt and b/projects/Notepad++/npes_saved_mingw.txt differ diff --git a/projects/Notepad++/npes_saved_tcc.txt b/projects/Notepad++/npes_saved_tcc.txt index 6b8d6f2e..32d72c2f 100644 Binary files a/projects/Notepad++/npes_saved_tcc.txt and b/projects/Notepad++/npes_saved_tcc.txt differ diff --git a/projects/scripts/linux-build.sh b/projects/scripts/linux-build.sh index 851ea1a5..abbe0e47 100755 --- a/projects/scripts/linux-build.sh +++ b/projects/scripts/linux-build.sh @@ -114,7 +114,7 @@ if [ ! -d "$TEMP_DIR" ]; then mkdir -p $TEMP_DIR cd $TEMP_DIR RAYLIB_DEFINES="-D_DEFAULT_SOURCE -DPLATFORM_DESKTOP -DGRAPHICS_API_OPENGL_33" - RAYLIB_C_FILES="$RAYLIB_SRC/core.c $RAYLIB_SRC/shapes.c $RAYLIB_SRC/textures.c $RAYLIB_SRC/text.c $RAYLIB_SRC/models.c $RAYLIB_SRC/utils.c $RAYLIB_SRC/audio.c $RAYLIB_SRC/rglfw.c $RAYLIB_SRC/external/mini_al.c" + RAYLIB_C_FILES="$RAYLIB_SRC/core.c $RAYLIB_SRC/shapes.c $RAYLIB_SRC/textures.c $RAYLIB_SRC/text.c $RAYLIB_SRC/models.c $RAYLIB_SRC/utils.c $RAYLIB_SRC/raudio.c $RAYLIB_SRC/rglfw.c $RAYLIB_SRC/external/mini_al.c" RAYLIB_INCLUDE_FLAGS="-I$RAYLIB_SRC -I$RAYLIB_SRC/external/glfw/include" if [ -n "$REALLY_QUIET" ]; then diff --git a/projects/scripts/osx-build.sh b/projects/scripts/osx-build.sh index 36558f63..fd4f77b7 100755 --- a/projects/scripts/osx-build.sh +++ b/projects/scripts/osx-build.sh @@ -108,7 +108,7 @@ if [ ! -d "$TEMP_DIR" ]; then mkdir -p $TEMP_DIR cd $TEMP_DIR RAYLIB_DEFINES="-D_DEFAULT_SOURCE -DPLATFORM_DESKTOP -DGRAPHICS_API_OPENGL_33" - RAYLIB_C_FILES="$RAYLIB_SRC/core.c $RAYLIB_SRC/shapes.c $RAYLIB_SRC/textures.c $RAYLIB_SRC/text.c $RAYLIB_SRC/models.c $RAYLIB_SRC/utils.c $RAYLIB_SRC/audio.c $RAYLIB_SRC/external/mini_al.c" + RAYLIB_C_FILES="$RAYLIB_SRC/core.c $RAYLIB_SRC/shapes.c $RAYLIB_SRC/textures.c $RAYLIB_SRC/text.c $RAYLIB_SRC/models.c $RAYLIB_SRC/utils.c $RAYLIB_SRC/raudio.c $RAYLIB_SRC/external/mini_al.c" RAYLIB_INCLUDE_FLAGS="-I$RAYLIB_SRC -I$RAYLIB_SRC/external/glfw/include" if [ -n "$REALLY_QUIET" ]; then diff --git a/projects/scripts/windows-build.bat b/projects/scripts/windows-build.bat index c4be4c89..e56141bc 100644 --- a/projects/scripts/windows-build.bat +++ b/projects/scripts/windows-build.bat @@ -159,7 +159,7 @@ IF NOT EXIST !TEMP_DIR!\ ( cd !TEMP_DIR! REM Raylib's src folder set "RAYLIB_DEFINES=/D_DEFAULT_SOURCE /DPLATFORM_DESKTOP /DGRAPHICS_API_OPENGL_33" - set RAYLIB_C_FILES="!RAYLIB_SRC!\core.c" "!RAYLIB_SRC!\shapes.c" "!RAYLIB_SRC!\textures.c" "!RAYLIB_SRC!\text.c" "!RAYLIB_SRC!\models.c" "!RAYLIB_SRC!\utils.c" "!RAYLIB_SRC!\audio.c" "!RAYLIB_SRC!\rglfw.c" "!RAYLIB_SRC!\external\mini_al.c" + set RAYLIB_C_FILES="!RAYLIB_SRC!\core.c" "!RAYLIB_SRC!\shapes.c" "!RAYLIB_SRC!\textures.c" "!RAYLIB_SRC!\text.c" "!RAYLIB_SRC!\models.c" "!RAYLIB_SRC!\utils.c" "!RAYLIB_SRC!\raudio.c" "!RAYLIB_SRC!\rglfw.c" "!RAYLIB_SRC!\external\mini_al.c" set RAYLIB_INCLUDE_FLAGS=/I"!RAYLIB_SRC!" /I"!RAYLIB_SRC!\external\glfw\include" IF DEFINED REALLY_QUIET ( diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a91f13a..504930d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,7 +51,7 @@ if(USE_AUDIO) else() MESSAGE(STATUS "Audio Backend: None (-DUSE_AUDIO=OFF)") set(INCLUDE_AUDIO_MODULE 0) - list(REMOVE_ITEM raylib_sources ${CMAKE_CURRENT_SOURCE_DIR}/audio.c) + list(REMOVE_ITEM raylib_sources ${CMAKE_CURRENT_SOURCE_DIR}/raudio.c) set(sources ${raylib_sources}) endif() @@ -250,7 +250,7 @@ file(COPY "raylib.h" DESTINATION ".") file(COPY "rlgl.h" DESTINATION ".") file(COPY "physac.h" DESTINATION ".") file(COPY "raymath.h" DESTINATION ".") -file(COPY "audio.h" DESTINATION ".") +file(COPY "raudio.h" DESTINATION ".") # Print the flags for the user message(STATUS "Compiling with the flags:") diff --git a/src/CMakeOptions.txt b/src/CMakeOptions.txt index ee63c5b4..b69c443f 100644 --- a/src/CMakeOptions.txt +++ b/src/CMakeOptions.txt @@ -61,7 +61,7 @@ option(SUPPORT_FILEFORMAT_OBJ "Support loading OBJ file format" ON) option(SUPPORT_FILEFORMAT_MTL "Support loading MTL file format" ON) option(SUPPORT_MESH_GENERATION "Support procedural mesh generation functions, uses external par_shapes.h library. NOTE: Some generated meshes DO NOT include generated texture coordinates" ON) -# audio.c +# raudio.c option(SUPPORT_FILEFORMAT_WAV "Support loading WAV for sound" ON) option(SUPPORT_FILEFORMAT_OGG "Support loading OGG for sound" ON) option(SUPPORT_FILEFORMAT_XM "Support loading XM for sound" ON) diff --git a/src/Makefile b/src/Makefile index 2c2bfb27..b99ff711 100644 --- a/src/Makefile +++ b/src/Makefile @@ -403,7 +403,7 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP) endif ifeq ($(INCLUDE_AUDIO_MODULE),TRUE) - OBJS += audio.o + OBJS += raudio.o OBJS += mini_al.o endif @@ -529,7 +529,7 @@ models.o : models.c raylib.h rlgl.h raymath.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) -D$(PLATFORM) -D$(GRAPHICS) # Compile audio module -audio.o : audio.c raylib.h +raudio.o : raudio.c raylib.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) -D$(PLATFORM) # Compile mini_al audio library diff --git a/src/audio.c b/src/audio.c deleted file mode 100644 index a76481b4..00000000 --- a/src/audio.c +++ /dev/null @@ -1,1958 +0,0 @@ -/********************************************************************************************** -* -* raylib.audio - Basic funtionality to work with audio -* -* FEATURES: -* - Manage audio device (init/close) -* - Load and unload audio files -* - Format wave data (sample rate, size, channels) -* - Play/Stop/Pause/Resume loaded audio -* - Manage mixing channels -* - Manage raw audio context -* -* CONFIGURATION: -* -* #define AUDIO_STANDALONE -* Define to use the module as standalone library (independently of raylib). -* Required types and functions are defined in the same module. -* -* #define SUPPORT_FILEFORMAT_WAV -* #define SUPPORT_FILEFORMAT_OGG -* #define SUPPORT_FILEFORMAT_XM -* #define SUPPORT_FILEFORMAT_MOD -* #define SUPPORT_FILEFORMAT_FLAC -* #define SUPPORT_FILEFORMAT_MP3 -* Selected desired fileformats to be supported for loading. Some of those formats are -* supported by default, to remove support, just comment unrequired #define in this module -* -* LIMITATIONS (only OpenAL Soft): -* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) -* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) -* -* DEPENDENCIES: -* mini_al - Audio device/context management (https://github.com/dr-soft/mini_al) -* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) -* jar_xm - XM module file loading -* jar_mod - MOD audio file loading -* dr_flac - FLAC audio file loading -* -* *OpenAL Soft - Audio device management, still used on HTML5 and OSX platforms -* -* CONTRIBUTORS: -* David Reid (github: @mackron) (Nov. 2017): -* - Complete port to mini_al library -* -* Joshua Reisenauer (github: @kd7tck) (2015) -* - XM audio module support (jar_xm) -* - MOD audio module support (jar_mod) -* - Mixing channels support -* - Raw audio context support -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#if defined(AUDIO_STANDALONE) - #include "audio.h" - #include // Required for: va_list, va_start(), vfprintf(), va_end() -#else - #include "raylib.h" // Declares module functions -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif - #include "utils.h" // Required for: fopen() Android mapping -#endif - -#include "external/mini_al.h" // mini_al audio library - // NOTE: Cannot be implement here because it conflicts with - // Win32 APIs: Rectangle, CloseWindow(), ShowCursor(), PlaySoundA() - -#include // Required for: malloc(), free() -#include // Required for: strcmp(), strncmp() -#include // Required for: FILE, fopen(), fclose(), fread() - -#if defined(SUPPORT_FILEFORMAT_OGG) - #define STB_VORBIS_IMPLEMENTATION - #include "external/stb_vorbis.h" // OGG loading functions -#endif - -#if defined(SUPPORT_FILEFORMAT_XM) - #define JAR_XM_IMPLEMENTATION - #include "external/jar_xm.h" // XM loading functions -#endif - -#if defined(SUPPORT_FILEFORMAT_MOD) - #define JAR_MOD_IMPLEMENTATION - #include "external/jar_mod.h" // MOD loading functions -#endif - -#if defined(SUPPORT_FILEFORMAT_FLAC) - #define DR_FLAC_IMPLEMENTATION - #define DR_FLAC_NO_WIN32_IO - #include "external/dr_flac.h" // FLAC loading functions -#endif - -#if defined(SUPPORT_FILEFORMAT_MP3) - #define DR_MP3_IMPLEMENTATION - #include "external/dr_mp3.h" // MP3 loading functions -#endif - -#if defined(_MSC_VER) - #undef bool -#endif - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#define MAX_STREAM_BUFFERS 2 // Number of buffers for each audio stream - -// NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number -// After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds -// and double-buffering system, I concluded that a 4096 samples buffer should be enough -// In case of music-stalls, just increase this number -#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb) - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- - -typedef enum { - MUSIC_AUDIO_OGG = 0, - MUSIC_AUDIO_FLAC, - MUSIC_AUDIO_MP3, - MUSIC_MODULE_XM, - MUSIC_MODULE_MOD -} MusicContextType; - -// Music type (file streaming from memory) -typedef struct MusicData { - MusicContextType ctxType; // Type of music context -#if defined(SUPPORT_FILEFORMAT_OGG) - stb_vorbis *ctxOgg; // OGG audio context -#endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - drflac *ctxFlac; // FLAC audio context -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) - drmp3 ctxMp3; // MP3 audio context -#endif -#if defined(SUPPORT_FILEFORMAT_XM) - jar_xm_context_t *ctxXm; // XM chiptune context -#endif -#if defined(SUPPORT_FILEFORMAT_MOD) - jar_mod_context_t ctxMod; // MOD chiptune context -#endif - - AudioStream stream; // Audio stream (double buffering) - - int loopCount; // Loops count (times music repeats), -1 means infinite loop - unsigned int totalSamples; // Total number of samples - unsigned int samplesLeft; // Number of samples left to end -} MusicData; - -#if defined(AUDIO_STANDALONE) -typedef enum { - LOG_INFO = 0, - LOG_ERROR, - LOG_WARNING, - LOG_DEBUG, - LOG_OTHER -} TraceLogType; -#endif - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -// ... - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -#if defined(SUPPORT_FILEFORMAT_WAV) -static Wave LoadWAV(const char *fileName); // Load WAV file -static int SaveWAV(Wave wave, const char *fileName); // Save wave data as WAV file -#endif -#if defined(SUPPORT_FILEFORMAT_OGG) -static Wave LoadOGG(const char *fileName); // Load OGG file -#endif -#if defined(SUPPORT_FILEFORMAT_FLAC) -static Wave LoadFLAC(const char *fileName); // Load FLAC file -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) -static Wave LoadMP3(const char *fileName); // Load MP3 file -#endif - -#if defined(AUDIO_STANDALONE) -bool IsFileExtension(const char *fileName, const char *ext); // Check file extension -void TraceLog(int msgType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -#endif - -//---------------------------------------------------------------------------------- -// mini_al AudioBuffer Functionality -//---------------------------------------------------------------------------------- -#define DEVICE_FORMAT mal_format_f32 -#define DEVICE_CHANNELS 2 -#define DEVICE_SAMPLE_RATE 44100 - -typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; - -// Audio buffer structure -// NOTE: Slightly different logic is used when feeding data to the playback device depending on whether or not data is streamed -typedef struct AudioBuffer AudioBuffer; -struct AudioBuffer { - mal_dsp dsp; // Required for format conversion - float volume; - float pitch; - bool playing; - bool paused; - bool looping; // Always true for AudioStreams - int usage; // AudioBufferUsage type - bool isSubBufferProcessed[2]; - unsigned int frameCursorPos; - unsigned int bufferSizeInFrames; - AudioBuffer *next; - AudioBuffer *prev; - unsigned char buffer[1]; -}; - -// mini_al global variables -static mal_context context; -static mal_device device; -static mal_mutex audioLock; -static bool isAudioInitialized = MAL_FALSE; -static float masterVolume = 1.0f; - -// Audio buffers are tracked in a linked list -static AudioBuffer *firstAudioBuffer = NULL; -static AudioBuffer *lastAudioBuffer = NULL; - -// mini_al functions declaration -static void OnLog(mal_context *pContext, mal_device *pDevice, const char *message); -static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameCount, void *pFramesOut); -static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, void *pFramesOut, void *pUserData); -static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume); - -// AudioBuffer management functions declaration -// NOTE: Those functions are not exposed by raylib... for the moment -AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage); -void DeleteAudioBuffer(AudioBuffer *audioBuffer); -bool IsAudioBufferPlaying(AudioBuffer *audioBuffer); -void PlayAudioBuffer(AudioBuffer *audioBuffer); -void StopAudioBuffer(AudioBuffer *audioBuffer); -void PauseAudioBuffer(AudioBuffer *audioBuffer); -void ResumeAudioBuffer(AudioBuffer *audioBuffer); -void SetAudioBufferVolume(AudioBuffer *audioBuffer, float volume); -void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch); -void TrackAudioBuffer(AudioBuffer *audioBuffer); -void UntrackAudioBuffer(AudioBuffer *audioBuffer); - - -// Log callback function -static void OnLog(mal_context *pContext, mal_device *pDevice, const char *message) -{ - (void)pContext; - (void)pDevice; - - TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors -} - -// Sending audio data to device callback function -static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameCount, void *pFramesOut) -{ - // This is where all of the mixing takes place. - (void)pDevice; - - // Mixing is basically just an accumulation. We need to initialize the output buffer to 0. - memset(pFramesOut, 0, frameCount*pDevice->channels*mal_get_bytes_per_sample(pDevice->format)); - - // Using a mutex here for thread-safety which makes things not real-time. This is unlikely to be necessary for this project, but may - // want to consider how you might want to avoid this. - mal_mutex_lock(&audioLock); - { - for (AudioBuffer *audioBuffer = firstAudioBuffer; audioBuffer != NULL; audioBuffer = audioBuffer->next) - { - // Ignore stopped or paused sounds. - if (!audioBuffer->playing || audioBuffer->paused) continue; - - mal_uint32 framesRead = 0; - for (;;) - { - if (framesRead > frameCount) - { - TraceLog(LOG_DEBUG, "Mixed too many frames from audio buffer"); - break; - } - - if (framesRead == frameCount) break; - - // Just read as much data as we can from the stream. - mal_uint32 framesToRead = (frameCount - framesRead); - while (framesToRead > 0) - { - float tempBuffer[1024]; // 512 frames for stereo. - - mal_uint32 framesToReadRightNow = framesToRead; - if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS) - { - framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS; - } - - mal_uint32 framesJustRead = (mal_uint32)mal_dsp_read(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, audioBuffer->dsp.pUserData); - if (framesJustRead > 0) - { - float *framesOut = (float *)pFramesOut + (framesRead*device.channels); - float *framesIn = tempBuffer; - MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); - - framesToRead -= framesJustRead; - framesRead += framesJustRead; - } - - // If we weren't able to read all the frames we requested, break. - if (framesJustRead < framesToReadRightNow) - { - if (!audioBuffer->looping) - { - StopAudioBuffer(audioBuffer); - break; - } - else - { - // Should never get here, but just for safety, - // move the cursor position back to the start and continue the loop. - audioBuffer->frameCursorPos = 0; - continue; - } - } - } - - // If for some reason we weren't able to read every frame we'll need to break from the loop. - // Not doing this could theoretically put us into an infinite loop. - if (framesToRead > 0) break; - } - } - } - - mal_mutex_unlock(&audioLock); - - return frameCount; // We always output the same number of frames that were originally requested. -} - -// DSP read from audio buffer callback function -static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, void *pFramesOut, void *pUserData) -{ - AudioBuffer *audioBuffer = (AudioBuffer *)pUserData; - - mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; - mal_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; - - if (currentSubBufferIndex > 1) - { - TraceLog(LOG_DEBUG, "Frame cursor position moved too far forward in audio stream"); - return 0; - } - - // Another thread can update the processed state of buffers so we just take a copy here to try and avoid potential synchronization problems. - bool isSubBufferProcessed[2]; - isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; - isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; - - mal_uint32 frameSizeInBytes = mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)*audioBuffer->dsp.formatConverterIn.config.channels; - - // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. - mal_uint32 framesRead = 0; - for (;;) - { - // We break from this loop differently depending on the buffer's usage. For static buffers, we simply fill as much data as we can. For - // streaming buffers we only fill the halves of the buffer that are processed. Unprocessed halves must keep their audio data in-tact. - if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) - { - if (framesRead >= frameCount) break; - } - else - { - if (isSubBufferProcessed[currentSubBufferIndex]) break; - } - - mal_uint32 totalFramesRemaining = (frameCount - framesRead); - if (totalFramesRemaining == 0) break; - - mal_uint32 framesRemainingInOutputBuffer; - if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) - { - framesRemainingInOutputBuffer = audioBuffer->bufferSizeInFrames - audioBuffer->frameCursorPos; - } - else - { - mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; - framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); - } - - mal_uint32 framesToRead = totalFramesRemaining; - if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; - - memcpy((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), audioBuffer->buffer + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); - audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead) % audioBuffer->bufferSizeInFrames; - framesRead += framesToRead; - - // If we've read to the end of the buffer, mark it as processed. - if (framesToRead == framesRemainingInOutputBuffer) - { - audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; - isSubBufferProcessed[currentSubBufferIndex] = true; - - currentSubBufferIndex = (currentSubBufferIndex + 1)%2; - - // We need to break from this loop if we're not looping. - if (!audioBuffer->looping) - { - StopAudioBuffer(audioBuffer); - break; - } - } - } - - // Zero-fill excess. - mal_uint32 totalFramesRemaining = (frameCount - framesRead); - if (totalFramesRemaining > 0) - { - memset((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); - - // For static buffers we can fill the remaining frames with silence for safety, but we don't want - // to report those frames as "read". The reason for this is that the caller uses the return value - // to know whether or not a non-looping sound has finished playback. - if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; - } - - return framesRead; -} - -// This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation. -// NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. -static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume) -{ - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) - { - for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) - { - float *frameOut = framesOut + (iFrame*device.channels); - const float *frameIn = framesIn + (iFrame*device.channels); - - frameOut[iChannel] += frameIn[iChannel]*masterVolume*localVolume; - } - } -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Audio Device initialization and Closing -//---------------------------------------------------------------------------------- -// Initialize audio device -void InitAudioDevice(void) -{ - // Context. - mal_context_config contextConfig = mal_context_config_init(OnLog); - mal_result result = mal_context_init(NULL, 0, &contextConfig, &context); - if (result != MAL_SUCCESS) - { - TraceLog(LOG_ERROR, "Failed to initialize audio context"); - return; - } - - // Device. Using the default device. Format is floating point because it simplifies mixing. - mal_device_config deviceConfig = mal_device_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, OnSendAudioDataToDevice); - - result = mal_device_init(&context, mal_device_type_playback, NULL, &deviceConfig, NULL, &device); - if (result != MAL_SUCCESS) - { - TraceLog(LOG_ERROR, "Failed to initialize audio playback device"); - mal_context_uninit(&context); - return; - } - - // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running - // while there's at least one sound being played. - result = mal_device_start(&device); - if (result != MAL_SUCCESS) - { - TraceLog(LOG_ERROR, "Failed to start audio playback device"); - mal_device_uninit(&device); - mal_context_uninit(&context); - return; - } - - // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may - // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. - if (mal_mutex_init(&context, &audioLock) != MAL_SUCCESS) - { - TraceLog(LOG_ERROR, "Failed to create mutex for audio mixing"); - mal_device_uninit(&device); - mal_context_uninit(&context); - return; - } - - TraceLog(LOG_INFO, "Audio device initialized successfully: %s", device.name); - TraceLog(LOG_INFO, "Audio backend: mini_al / %s", mal_get_backend_name(context.backend)); - TraceLog(LOG_INFO, "Audio format: %s -> %s", mal_get_format_name(device.format), mal_get_format_name(device.internalFormat)); - TraceLog(LOG_INFO, "Audio channels: %d -> %d", device.channels, device.internalChannels); - TraceLog(LOG_INFO, "Audio sample rate: %d -> %d", device.sampleRate, device.internalSampleRate); - TraceLog(LOG_INFO, "Audio buffer size: %d", device.bufferSizeInFrames); - - isAudioInitialized = MAL_TRUE; -} - -// Close the audio device for all contexts -void CloseAudioDevice(void) -{ - if (!isAudioInitialized) - { - TraceLog(LOG_WARNING, "Could not close audio device because it is not currently initialized"); - return; - } - - mal_mutex_uninit(&audioLock); - mal_device_uninit(&device); - mal_context_uninit(&context); - - TraceLog(LOG_INFO, "Audio device closed successfully"); -} - -// Check if device has been initialized successfully -bool IsAudioDeviceReady(void) -{ - return isAudioInitialized; -} - -// Set master volume (listener) -void SetMasterVolume(float volume) -{ - if (volume < 0.0f) volume = 0.0f; - else if (volume > 1.0f) volume = 1.0f; - - masterVolume = volume; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Audio Buffer management -//---------------------------------------------------------------------------------- - -// Create a new audio buffer. Initially filled with silence -AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage) -{ - AudioBuffer *audioBuffer = (AudioBuffer *)calloc(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*mal_get_bytes_per_sample(format)), 1); - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to allocate memory for audio buffer"); - return NULL; - } - - // We run audio data through a format converter. - mal_dsp_config dspConfig; - memset(&dspConfig, 0, sizeof(dspConfig)); - dspConfig.formatIn = format; - dspConfig.formatOut = DEVICE_FORMAT; - dspConfig.channelsIn = channels; - dspConfig.channelsOut = DEVICE_CHANNELS; - dspConfig.sampleRateIn = sampleRate; - dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE; - dspConfig.onRead = OnAudioBufferDSPRead; - dspConfig.pUserData = audioBuffer; - dspConfig.allowDynamicSampleRate = MAL_TRUE; // <-- Required for pitch shifting. - mal_result resultMAL = mal_dsp_init(&dspConfig, &audioBuffer->dsp); - if (resultMAL != MAL_SUCCESS) - { - TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to create data conversion pipeline"); - free(audioBuffer); - return NULL; - } - - audioBuffer->volume = 1; - audioBuffer->pitch = 1; - audioBuffer->playing = 0; - audioBuffer->paused = 0; - audioBuffer->looping = 0; - audioBuffer->usage = usage; - audioBuffer->bufferSizeInFrames = bufferSizeInFrames; - audioBuffer->frameCursorPos = 0; - - // Buffers should be marked as processed by default so that a call to UpdateAudioStream() immediately after initialization works correctly. - audioBuffer->isSubBufferProcessed[0] = true; - audioBuffer->isSubBufferProcessed[1] = true; - - TrackAudioBuffer(audioBuffer); - - return audioBuffer; -} - -// Delete an audio buffer -void DeleteAudioBuffer(AudioBuffer *audioBuffer) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "DeleteAudioBuffer() : No audio buffer"); - return; - } - - UntrackAudioBuffer(audioBuffer); - free(audioBuffer); -} - -// Check if an audio buffer is playing -bool IsAudioBufferPlaying(AudioBuffer *audioBuffer) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "IsAudioBufferPlaying() : No audio buffer"); - return false; - } - - return audioBuffer->playing && !audioBuffer->paused; -} - -// Play an audio buffer -// NOTE: Buffer is restarted to the start. -// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. -void PlayAudioBuffer(AudioBuffer *audioBuffer) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); - return; - } - - audioBuffer->playing = true; - audioBuffer->paused = false; - audioBuffer->frameCursorPos = 0; -} - -// Stop an audio buffer -void StopAudioBuffer(AudioBuffer *audioBuffer) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "StopAudioBuffer() : No audio buffer"); - return; - } - - // Don't do anything if the audio buffer is already stopped. - if (!IsAudioBufferPlaying(audioBuffer)) return; - - audioBuffer->playing = false; - audioBuffer->paused = false; - audioBuffer->frameCursorPos = 0; - audioBuffer->isSubBufferProcessed[0] = true; - audioBuffer->isSubBufferProcessed[1] = true; -} - -// Pause an audio buffer -void PauseAudioBuffer(AudioBuffer *audioBuffer) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "PauseAudioBuffer() : No audio buffer"); - return; - } - - audioBuffer->paused = true; -} - -// Resume an audio buffer -void ResumeAudioBuffer(AudioBuffer *audioBuffer) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "ResumeAudioBuffer() : No audio buffer"); - return; - } - - audioBuffer->paused = false; -} - -// Set volume for an audio buffer -void SetAudioBufferVolume(AudioBuffer *audioBuffer, float volume) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "SetAudioBufferVolume() : No audio buffer"); - return; - } - - audioBuffer->volume = volume; -} - -// Set pitch for an audio buffer -void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch) -{ - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "SetAudioBufferPitch() : No audio buffer"); - return; - } - - audioBuffer->pitch = pitch; - - // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches - // will make the sound faster; lower pitches make it slower. - mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.src.config.sampleRateOut / (float)audioBuffer->dsp.src.config.sampleRateIn) / pitch) * audioBuffer->dsp.src.config.sampleRateIn); - mal_dsp_set_output_sample_rate(&audioBuffer->dsp, newOutputSampleRate); -} - -// Track audio buffer to linked list next position -void TrackAudioBuffer(AudioBuffer *audioBuffer) -{ - mal_mutex_lock(&audioLock); - - { - if (firstAudioBuffer == NULL) firstAudioBuffer = audioBuffer; - else - { - lastAudioBuffer->next = audioBuffer; - audioBuffer->prev = lastAudioBuffer; - } - - lastAudioBuffer = audioBuffer; - } - - mal_mutex_unlock(&audioLock); -} - -// Untrack audio buffer from linked list -void UntrackAudioBuffer(AudioBuffer *audioBuffer) -{ - mal_mutex_lock(&audioLock); - - { - if (audioBuffer->prev == NULL) firstAudioBuffer = audioBuffer->next; - else audioBuffer->prev->next = audioBuffer->next; - - if (audioBuffer->next == NULL) lastAudioBuffer = audioBuffer->prev; - else audioBuffer->next->prev = audioBuffer->prev; - - audioBuffer->prev = NULL; - audioBuffer->next = NULL; - } - - mal_mutex_unlock(&audioLock); -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Sounds loading and playing (.WAV) -//---------------------------------------------------------------------------------- - -// Load wave data from file -Wave LoadWave(const char *fileName) -{ - Wave wave = { 0 }; - - if (IsFileExtension(fileName, ".wav")) wave = LoadWAV(fileName); -#if defined(SUPPORT_FILEFORMAT_OGG) - else if (IsFileExtension(fileName, ".ogg")) wave = LoadOGG(fileName); -#endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (IsFileExtension(fileName, ".flac")) wave = LoadFLAC(fileName); -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) - else if (IsFileExtension(fileName, ".mp3")) wave = LoadMP3(fileName); -#endif - else TraceLog(LOG_WARNING, "[%s] Audio fileformat not supported, it can't be loaded", fileName); - - return wave; -} - -// Load wave data from raw array data -Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels) -{ - Wave wave; - - wave.data = data; - wave.sampleCount = sampleCount; - wave.sampleRate = sampleRate; - wave.sampleSize = sampleSize; - wave.channels = channels; - - // NOTE: Copy wave data to work with, user is responsible of input data to free - Wave cwave = WaveCopy(wave); - - WaveFormat(&cwave, sampleRate, sampleSize, channels); - - return cwave; -} - -// Load sound from file -// NOTE: The entire file is loaded to memory to be played (no-streaming) -Sound LoadSound(const char *fileName) -{ - Wave wave = LoadWave(fileName); - - Sound sound = LoadSoundFromWave(wave); - - UnloadWave(wave); // Sound is loaded, we can unload wave - - return sound; -} - -// Load sound from wave data -// NOTE: Wave data must be unallocated manually -Sound LoadSoundFromWave(Wave wave) -{ - Sound sound = { 0 }; - - if (wave.data != NULL) - { - // When using mini_al we need to do our own mixing. To simplify this we need convert the format of each sound to be consistent with - // the format used to open the playback device. We can do this two ways: - // - // 1) Convert the whole sound in one go at load time (here). - // 2) Convert the audio data in chunks at mixing time. - // - // I have decided on the first option because it offloads work required for the format conversion to the to the loading stage. - // The downside to this is that it uses more memory if the original sound is u8 or s16. - mal_format formatIn = ((wave.sampleSize == 8) ? mal_format_u8 : ((wave.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - mal_uint32 frameCountIn = wave.sampleCount/wave.channels; - - mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); - if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to get frame count for format conversion"); - - AudioBuffer* audioBuffer = CreateAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); - if (audioBuffer == NULL) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to create audio buffer"); - - frameCount = (mal_uint32)mal_convert_frames(audioBuffer->buffer, audioBuffer->dsp.formatConverterIn.config.formatIn, audioBuffer->dsp.formatConverterIn.config.channels, audioBuffer->dsp.src.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); - if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Format conversion failed"); - - sound.audioBuffer = audioBuffer; - } - - return sound; -} - -// Unload wave data -void UnloadWave(Wave wave) -{ - if (wave.data != NULL) free(wave.data); - - TraceLog(LOG_INFO, "Unloaded wave data from RAM"); -} - -// Unload sound -void UnloadSound(Sound sound) -{ - DeleteAudioBuffer((AudioBuffer *)sound.audioBuffer); - - TraceLog(LOG_INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); -} - -// Update sound buffer with new data -// NOTE: data must match sound.format -void UpdateSound(Sound sound, const void *data, int samplesCount) -{ - AudioBuffer *audioBuffer = (AudioBuffer *)sound.audioBuffer; - - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound - no audio buffer"); - return; - } - - StopAudioBuffer(audioBuffer); - - // TODO: May want to lock/unlock this since this data buffer is read at mixing time. - memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.formatConverterIn.config.channels*mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)); -} - -// Export wave data to file -void ExportWave(Wave wave, const char *fileName) -{ - bool success = false; - - if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); - else if (IsFileExtension(fileName, ".raw")) - { - // Export raw sample data (without header) - // NOTE: It's up to the user to track wave parameters - FILE *rawFile = fopen(fileName, "wb"); - success = fwrite(wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8, 1, rawFile); - fclose(rawFile); - } - - if (success) TraceLog(LOG_INFO, "Wave exported successfully: %s", fileName); - else TraceLog(LOG_WARNING, "Wave could not be exported."); -} - -// Export wave sample data to code (.h) -void ExportWaveAsCode(Wave wave, const char *fileName) -{ - #define BYTES_TEXT_PER_LINE 20 - - char varFileName[256] = { 0 }; - int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; - - FILE *txtFile = fopen(fileName, "wt"); - - fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); - fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "//////////////////////////////////////////////////////////////////////////////////\n\n"); - - // Get file name from path and convert variable name to uppercase - strcpy(varFileName, GetFileNameWithoutExt(fileName)); - for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } - - fprintf(txtFile, "// Wave data information\n"); - fprintf(txtFile, "#define %s_SAMPLE_COUNT %i\n", varFileName, wave.sampleCount); - fprintf(txtFile, "#define %s_SAMPLE_RATE %i\n", varFileName, wave.sampleRate); - fprintf(txtFile, "#define %s_SAMPLE_SIZE %i\n", varFileName, wave.sampleSize); - fprintf(txtFile, "#define %s_CHANNELS %i\n\n", varFileName, wave.channels); - - // Write byte data as hexadecimal text - fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); - for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); - fprintf(txtFile, "0x%x };\n", ((unsigned char *)wave.data)[dataSize - 1]); - - fclose(txtFile); -} - -// Play a sound -void PlaySound(Sound sound) -{ - PlayAudioBuffer((AudioBuffer *)sound.audioBuffer); -} - -// Pause a sound -void PauseSound(Sound sound) -{ - PauseAudioBuffer((AudioBuffer *)sound.audioBuffer); -} - -// Resume a paused sound -void ResumeSound(Sound sound) -{ - ResumeAudioBuffer((AudioBuffer *)sound.audioBuffer); -} - -// Stop reproducing a sound -void StopSound(Sound sound) -{ - StopAudioBuffer((AudioBuffer *)sound.audioBuffer); -} - -// Check if a sound is playing -bool IsSoundPlaying(Sound sound) -{ - return IsAudioBufferPlaying((AudioBuffer *)sound.audioBuffer); -} - -// Set volume for a sound -void SetSoundVolume(Sound sound, float volume) -{ - SetAudioBufferVolume((AudioBuffer *)sound.audioBuffer, volume); -} - -// Set pitch for a sound -void SetSoundPitch(Sound sound, float pitch) -{ - SetAudioBufferPitch((AudioBuffer *)sound.audioBuffer, pitch); -} - -// Convert wave data to desired format -void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) -{ - mal_format formatIn = ((wave->sampleSize == 8) ? mal_format_u8 : ((wave->sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - mal_format formatOut = (( sampleSize == 8) ? mal_format_u8 : (( sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - - mal_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. - - mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, formatOut, channels, sampleRate, NULL, formatIn, wave->channels, wave->sampleRate, frameCountIn); - if (frameCount == 0) - { - TraceLog(LOG_ERROR, "WaveFormat() : Failed to get frame count for format conversion."); - return; - } - - void *data = malloc(frameCount*channels*(sampleSize/8)); - - frameCount = (mal_uint32)mal_convert_frames(data, formatOut, channels, sampleRate, wave->data, formatIn, wave->channels, wave->sampleRate, frameCountIn); - if (frameCount == 0) - { - TraceLog(LOG_ERROR, "WaveFormat() : Format conversion failed."); - return; - } - - wave->sampleCount = frameCount; - wave->sampleSize = sampleSize; - wave->sampleRate = sampleRate; - wave->channels = channels; - free(wave->data); - wave->data = data; -} - -// Copy a wave to a new wave -Wave WaveCopy(Wave wave) -{ - Wave newWave = { 0 }; - - newWave.data = malloc(wave.sampleCount*wave.sampleSize/8*wave.channels); - - if (newWave.data != NULL) - { - // NOTE: Size must be provided in bytes - memcpy(newWave.data, wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8); - - newWave.sampleCount = wave.sampleCount; - newWave.sampleRate = wave.sampleRate; - newWave.sampleSize = wave.sampleSize; - newWave.channels = wave.channels; - } - - return newWave; -} - -// Crop a wave to defined samples range -// NOTE: Security check in case of out-of-range -void WaveCrop(Wave *wave, int initSample, int finalSample) -{ - if ((initSample >= 0) && (initSample < finalSample) && - (finalSample > 0) && ((unsigned int)finalSample < wave->sampleCount)) - { - int sampleCount = finalSample - initSample; - - void *data = malloc(sampleCount*wave->sampleSize/8*wave->channels); - - memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); - - free(wave->data); - wave->data = data; - } - else TraceLog(LOG_WARNING, "Wave crop range out of bounds"); -} - -// Get samples data from wave as a floats array -// NOTE: Returned sample values are normalized to range [-1..1] -float *GetWaveData(Wave wave) -{ - float *samples = (float *)malloc(wave.sampleCount*wave.channels*sizeof(float)); - - for (unsigned int i = 0; i < wave.sampleCount; i++) - { - for (unsigned int j = 0; j < wave.channels; j++) - { - if (wave.sampleSize == 8) samples[wave.channels*i + j] = (float)(((unsigned char *)wave.data)[wave.channels*i + j] - 127)/256.0f; - else if (wave.sampleSize == 16) samples[wave.channels*i + j] = (float)((short *)wave.data)[wave.channels*i + j]/32767.0f; - else if (wave.sampleSize == 32) samples[wave.channels*i + j] = ((float *)wave.data)[wave.channels*i + j]; - } - } - - return samples; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Music loading and stream playing (.OGG) -//---------------------------------------------------------------------------------- - -// Load music stream from file -Music LoadMusicStream(const char *fileName) -{ - Music music = (MusicData *)malloc(sizeof(MusicData)); - bool musicLoaded = true; - - if (IsFileExtension(fileName, ".ogg")) - { - // Open ogg audio stream - music->ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); - - if (music->ctxOgg == NULL) musicLoaded = false; - else - { - stb_vorbis_info info = stb_vorbis_get_info(music->ctxOgg); // Get Ogg file info - - // OGG bit rate defaults to 16 bit, it's enough for compressed format - music->stream = InitAudioStream(info.sample_rate, 16, info.channels); - music->totalSamples = (unsigned int)stb_vorbis_stream_length_in_samples(music->ctxOgg)*info.channels; - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_AUDIO_OGG; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_DEBUG, "[%s] OGG total samples: %i", fileName, music->totalSamples); - TraceLog(LOG_DEBUG, "[%s] OGG sample rate: %i", fileName, info.sample_rate); - TraceLog(LOG_DEBUG, "[%s] OGG channels: %i", fileName, info.channels); - TraceLog(LOG_DEBUG, "[%s] OGG memory required: %i", fileName, info.temp_memory_required); - } - } -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (IsFileExtension(fileName, ".flac")) - { - music->ctxFlac = drflac_open_file(fileName); - - if (music->ctxFlac == NULL) musicLoaded = false; - else - { - music->stream = InitAudioStream(music->ctxFlac->sampleRate, music->ctxFlac->bitsPerSample, music->ctxFlac->channels); - music->totalSamples = (unsigned int)music->ctxFlac->totalSampleCount; - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_AUDIO_FLAC; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_DEBUG, "[%s] FLAC total samples: %i", fileName, music->totalSamples); - TraceLog(LOG_DEBUG, "[%s] FLAC sample rate: %i", fileName, music->ctxFlac->sampleRate); - TraceLog(LOG_DEBUG, "[%s] FLAC bits per sample: %i", fileName, music->ctxFlac->bitsPerSample); - TraceLog(LOG_DEBUG, "[%s] FLAC channels: %i", fileName, music->ctxFlac->channels); - } - } -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) - else if (IsFileExtension(fileName, ".mp3")) - { - int result = drmp3_init_file(&music->ctxMp3, fileName, NULL); - - if (!result) musicLoaded = false; - else - { - TraceLog(LOG_INFO, "[%s] MP3 sample rate: %i", fileName, music->ctxMp3.sampleRate); - TraceLog(LOG_INFO, "[%s] MP3 bits per sample: %i", fileName, 32); - TraceLog(LOG_INFO, "[%s] MP3 channels: %i", fileName, music->ctxMp3.channels); - - music->stream = InitAudioStream(music->ctxMp3.sampleRate, 32, music->ctxMp3.channels); - - // TODO: There is not an easy way to compute the total number of samples available - // in an MP3, frames size could be variable... we tried with a 60 seconds music... but crashes... - music->totalSamples = drmp3_get_pcm_frame_count(&music->ctxMp3)*music->ctxMp3.channels; - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_AUDIO_MP3; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_INFO, "[%s] MP3 total samples: %i", fileName, music->totalSamples); - } - } -#endif -#if defined(SUPPORT_FILEFORMAT_XM) - else if (IsFileExtension(fileName, ".xm")) - { - int result = jar_xm_create_context_from_file(&music->ctxXm, 48000, fileName); - - if (!result) // XM context created successfully - { - jar_xm_set_max_loop_count(music->ctxXm, 0); // Set infinite number of loops - - // NOTE: Only stereo is supported for XM - music->stream = InitAudioStream(48000, 16, 2); - music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_MODULE_XM; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_INFO, "[%s] XM number of samples: %i", fileName, music->totalSamples); - TraceLog(LOG_INFO, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); - } - else musicLoaded = false; - } -#endif -#if defined(SUPPORT_FILEFORMAT_MOD) - else if (IsFileExtension(fileName, ".mod")) - { - jar_mod_init(&music->ctxMod); - - if (jar_mod_load_file(&music->ctxMod, fileName)) - { - // NOTE: Only stereo is supported for MOD - music->stream = InitAudioStream(48000, 16, 2); - music->totalSamples = (unsigned int)jar_mod_max_samples(&music->ctxMod); - music->samplesLeft = music->totalSamples; - music->ctxType = MUSIC_MODULE_MOD; - music->loopCount = -1; // Infinite loop by default - - TraceLog(LOG_INFO, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); - TraceLog(LOG_INFO, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); - } - else musicLoaded = false; - } -#endif - else musicLoaded = false; - - if (!musicLoaded) - { - if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); - #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac); - #endif - #if defined(SUPPORT_FILEFORMAT_MP3) - else if (music->ctxType == MUSIC_AUDIO_MP3) drmp3_uninit(&music->ctxMp3); - #endif - #if defined(SUPPORT_FILEFORMAT_XM) - else if (music->ctxType == MUSIC_MODULE_XM) jar_xm_free_context(music->ctxXm); - #endif - #if defined(SUPPORT_FILEFORMAT_MOD) - else if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_unload(&music->ctxMod); - #endif - - free(music); - music = NULL; - - TraceLog(LOG_WARNING, "[%s] Music file could not be opened", fileName); - } - - return music; -} - -// Unload music stream -void UnloadMusicStream(Music music) -{ - if (music == NULL) return; - - CloseAudioStream(music->stream); - - if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac); -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) - else if (music->ctxType == MUSIC_AUDIO_MP3) drmp3_uninit(&music->ctxMp3); -#endif -#if defined(SUPPORT_FILEFORMAT_XM) - else if (music->ctxType == MUSIC_MODULE_XM) jar_xm_free_context(music->ctxXm); -#endif -#if defined(SUPPORT_FILEFORMAT_MOD) - else if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_unload(&music->ctxMod); -#endif - - free(music); -} - -// Start music playing (open stream) -void PlayMusicStream(Music music) -{ - if (music != NULL) - { - AudioBuffer *audioBuffer = (AudioBuffer *)music->stream.audioBuffer; - - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "PlayMusicStream() : No audio buffer"); - return; - } - - // For music streams, we need to make sure we maintain the frame cursor position. This is hack for this section of code in UpdateMusicStream() - // // NOTE: In case window is minimized, music stream is stopped, - // // just make sure to play again on window restore - // if (IsMusicPlaying(music)) PlayMusicStream(music); - mal_uint32 frameCursorPos = audioBuffer->frameCursorPos; - - PlayAudioStream(music->stream); // <-- This resets the cursor position. - - audioBuffer->frameCursorPos = frameCursorPos; - } -} - -// Pause music playing -void PauseMusicStream(Music music) -{ - if (music != NULL) PauseAudioStream(music->stream); -} - -// Resume music playing -void ResumeMusicStream(Music music) -{ - if (music != NULL) ResumeAudioStream(music->stream); -} - -// Stop music playing (close stream) -// TODO: To clear a buffer, make sure they have been already processed! -void StopMusicStream(Music music) -{ - if (music == NULL) return; - - StopAudioStream(music->stream); - - // Restart music context - switch (music->ctxType) - { - case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break; -#if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: /* TODO: Restart FLAC context */ break; -#endif -#if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame(&music->ctxMp3, 0); break; -#endif -#if defined(SUPPORT_FILEFORMAT_XM) - case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break; -#endif -#if defined(SUPPORT_FILEFORMAT_MOD) - case MUSIC_MODULE_MOD: jar_mod_seek_start(&music->ctxMod); break; -#endif - default: break; - } - - music->samplesLeft = music->totalSamples; -} - -// Update (re-fill) music buffers if data already processed -// TODO: Make sure buffers are ready for update... check music state -void UpdateMusicStream(Music music) -{ - if (music == NULL) return; - - bool streamEnding = false; - - unsigned int subBufferSizeInFrames = ((AudioBuffer *)music->stream.audioBuffer)->bufferSizeInFrames/2; - - // NOTE: Using dynamic allocation because it could require more than 16KB - void *pcm = calloc(subBufferSizeInFrames*music->stream.channels*music->stream.sampleSize/8, 1); - - int samplesCount = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts - - while (IsAudioBufferProcessed(music->stream)) - { - if ((music->samplesLeft/music->stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music->stream.channels; - else samplesCount = music->samplesLeft; - - // TODO: Really don't like ctxType thingy... - switch (music->ctxType) - { - case MUSIC_AUDIO_OGG: - { - // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount); - - } break; - #if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: - { - // NOTE: Returns the number of samples to process - unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount, (short *)pcm); - - } break; - #endif - #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: - { - // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed - drmp3_read_pcm_frames_f32(&music->ctxMp3, samplesCount/music->stream.channels, (float *)pcm); - - } break; - #endif - #if defined(SUPPORT_FILEFORMAT_XM) - case MUSIC_MODULE_XM: - { - // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 - jar_xm_generate_samples_16bit(music->ctxXm, (short *)pcm, samplesCount/2); - } break; - #endif - #if defined(SUPPORT_FILEFORMAT_MOD) - case MUSIC_MODULE_MOD: - { - // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 - jar_mod_fillbuffer(&music->ctxMod, (short *)pcm, samplesCount/2, 0); - } break; - #endif - default: break; - } - - - UpdateAudioStream(music->stream, pcm, samplesCount); - if ((music->ctxType == MUSIC_MODULE_XM) || (music->ctxType == MUSIC_MODULE_MOD)) - { - if (samplesCount > 1) music->samplesLeft -= samplesCount/2; - else music->samplesLeft -= samplesCount; - } - else music->samplesLeft -= samplesCount; - - if (music->samplesLeft <= 0) - { - streamEnding = true; - break; - } - } - - // Free allocated pcm data - free(pcm); - - // Reset audio stream for looping - if (streamEnding) - { - StopMusicStream(music); // Stop music (and reset) - - // Decrease loopCount to stop when required - if (music->loopCount > 0) - { - music->loopCount--; // Decrease loop count - PlayMusicStream(music); // Play again - } - else - { - if (music->loopCount == -1) PlayMusicStream(music); - } - } - else - { - // NOTE: In case window is minimized, music stream is stopped, - // just make sure to play again on window restore - if (IsMusicPlaying(music)) PlayMusicStream(music); - } -} - -// Check if any music is playing -bool IsMusicPlaying(Music music) -{ - if (music == NULL) return false; - else return IsAudioStreamPlaying(music->stream); -} - -// Set volume for music -void SetMusicVolume(Music music, float volume) -{ - if (music != NULL) SetAudioStreamVolume(music->stream, volume); -} - -// Set pitch for music -void SetMusicPitch(Music music, float pitch) -{ - if (music != NULL) SetAudioStreamPitch(music->stream, pitch); -} - -// Set music loop count (loop repeats) -// NOTE: If set to -1, means infinite loop -void SetMusicLoopCount(Music music, int count) -{ - if (music != NULL) music->loopCount = count; -} - -// Get music time length (in seconds) -float GetMusicTimeLength(Music music) -{ - float totalSeconds = 0.0f; - - if (music != NULL) totalSeconds = (float)music->totalSamples/(music->stream.sampleRate*music->stream.channels); - - return totalSeconds; -} - -// Get current music time played (in seconds) -float GetMusicTimePlayed(Music music) -{ - float secondsPlayed = 0.0f; - - if (music != NULL) - { - unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; - secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); - } - - return secondsPlayed; -} - -// Init audio stream (to stream audio pcm data) -AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) -{ - AudioStream stream = { 0 }; - - stream.sampleRate = sampleRate; - stream.sampleSize = sampleSize; - - // Only mono and stereo channels are supported, more channels require AL_EXT_MCFORMATS extension - if ((channels > 0) && (channels < 3)) stream.channels = channels; - else - { - TraceLog(LOG_WARNING, "Init audio stream: Number of channels not supported: %i", channels); - stream.channels = 1; // Fallback to mono channel - } - - mal_format formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - - // The size of a streaming buffer must be at least double the size of a period. - unsigned int periodSize = device.bufferSizeInFrames/device.periods; - unsigned int subBufferSize = AUDIO_BUFFER_SIZE; - if (subBufferSize < periodSize) subBufferSize = periodSize; - - AudioBuffer *audioBuffer = CreateAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "InitAudioStream() : Failed to create audio buffer"); - return stream; - } - - audioBuffer->looping = true; // Always loop for streaming buffers. - stream.audioBuffer = audioBuffer; - - TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo"); - - return stream; -} - -// Close audio stream and free memory -void CloseAudioStream(AudioStream stream) -{ - DeleteAudioBuffer((AudioBuffer *)stream.audioBuffer); - - TraceLog(LOG_INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); -} - -// Update audio stream buffers with data -// NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue -// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioBufferProcessed() -void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) -{ - AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "UpdateAudioStream() : No audio buffer"); - return; - } - - if (audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]) - { - mal_uint32 subBufferToUpdate; - - if (audioBuffer->isSubBufferProcessed[0] && audioBuffer->isSubBufferProcessed[1]) - { - // Both buffers are available for updating. Update the first one and make sure the cursor is moved back to the front. - subBufferToUpdate = 0; - audioBuffer->frameCursorPos = 0; - } - else - { - // Just update whichever sub-buffer is processed. - subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0]) ? 0 : 1; - } - - mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; - unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); - - // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic. - if (subBufferSizeInFrames >= (mal_uint32)samplesCount/stream.channels) - { - mal_uint32 framesToWrite = subBufferSizeInFrames; - - if (framesToWrite > ((mal_uint32)samplesCount/stream.channels)) framesToWrite = (mal_uint32)samplesCount/stream.channels; - - mal_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); - memcpy(subBuffer, data, bytesToWrite); - - // Any leftover frames should be filled with zeros. - mal_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; - - if (leftoverFrameCount > 0) - { - memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); - } - - audioBuffer->isSubBufferProcessed[subBufferToUpdate] = false; - } - else - { - TraceLog(LOG_ERROR, "UpdateAudioStream() : Attempting to write too many frames to buffer"); - return; - } - } - else - { - TraceLog(LOG_ERROR, "Audio buffer not available for updating"); - return; - } -} - -// Check if any audio stream buffers requires refill -bool IsAudioBufferProcessed(AudioStream stream) -{ - AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; - if (audioBuffer == NULL) - { - TraceLog(LOG_ERROR, "IsAudioBufferProcessed() : No audio buffer"); - return false; - } - - return audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]; -} - -// Play audio stream -void PlayAudioStream(AudioStream stream) -{ - PlayAudioBuffer((AudioBuffer *)stream.audioBuffer); -} - -// Play audio stream -void PauseAudioStream(AudioStream stream) -{ - PauseAudioBuffer((AudioBuffer *)stream.audioBuffer); -} - -// Resume audio stream playing -void ResumeAudioStream(AudioStream stream) -{ - ResumeAudioBuffer((AudioBuffer *)stream.audioBuffer); -} - -// Check if audio stream is playing. -bool IsAudioStreamPlaying(AudioStream stream) -{ - return IsAudioBufferPlaying((AudioBuffer *)stream.audioBuffer); -} - -// Stop audio stream -void StopAudioStream(AudioStream stream) -{ - StopAudioBuffer((AudioBuffer *)stream.audioBuffer); -} - -void SetAudioStreamVolume(AudioStream stream, float volume) -{ - SetAudioBufferVolume((AudioBuffer *)stream.audioBuffer, volume); -} - -void SetAudioStreamPitch(AudioStream stream, float pitch) -{ - SetAudioBufferPitch((AudioBuffer *)stream.audioBuffer, pitch); -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -#if defined(SUPPORT_FILEFORMAT_WAV) -// Load WAV file into Wave structure -static Wave LoadWAV(const char *fileName) -{ - // Basic WAV headers structs - typedef struct { - char chunkID[4]; - int chunkSize; - char format[4]; - } WAVRiffHeader; - - typedef struct { - char subChunkID[4]; - int subChunkSize; - short audioFormat; - short numChannels; - int sampleRate; - int byteRate; - short blockAlign; - short bitsPerSample; - } WAVFormat; - - typedef struct { - char subChunkID[4]; - int subChunkSize; - } WAVData; - - WAVRiffHeader wavRiffHeader; - WAVFormat wavFormat; - WAVData wavData; - - Wave wave = { 0 }; - FILE *wavFile; - - wavFile = fopen(fileName, "rb"); - - if (wavFile == NULL) - { - TraceLog(LOG_WARNING, "[%s] WAV file could not be opened", fileName); - wave.data = NULL; - } - else - { - // Read in the first chunk into the struct - fread(&wavRiffHeader, sizeof(WAVRiffHeader), 1, wavFile); - - // Check for RIFF and WAVE tags - if (strncmp(wavRiffHeader.chunkID, "RIFF", 4) || - strncmp(wavRiffHeader.format, "WAVE", 4)) - { - TraceLog(LOG_WARNING, "[%s] Invalid RIFF or WAVE Header", fileName); - } - else - { - // Read in the 2nd chunk for the wave info - fread(&wavFormat, sizeof(WAVFormat), 1, wavFile); - - // Check for fmt tag - if ((wavFormat.subChunkID[0] != 'f') || (wavFormat.subChunkID[1] != 'm') || - (wavFormat.subChunkID[2] != 't') || (wavFormat.subChunkID[3] != ' ')) - { - TraceLog(LOG_WARNING, "[%s] Invalid Wave format", fileName); - } - else - { - // Check for extra parameters; - if (wavFormat.subChunkSize > 16) fseek(wavFile, sizeof(short), SEEK_CUR); - - // Read in the the last byte of data before the sound file - fread(&wavData, sizeof(WAVData), 1, wavFile); - - // Check for data tag - if ((wavData.subChunkID[0] != 'd') || (wavData.subChunkID[1] != 'a') || - (wavData.subChunkID[2] != 't') || (wavData.subChunkID[3] != 'a')) - { - TraceLog(LOG_WARNING, "[%s] Invalid data header", fileName); - } - else - { - // Allocate memory for data - wave.data = malloc(wavData.subChunkSize); - - // Read in the sound data into the soundData variable - fread(wave.data, wavData.subChunkSize, 1, wavFile); - - // Store wave parameters - wave.sampleRate = wavFormat.sampleRate; - wave.sampleSize = wavFormat.bitsPerSample; - wave.channels = wavFormat.numChannels; - - // NOTE: Only support 8 bit, 16 bit and 32 bit sample sizes - if ((wave.sampleSize != 8) && (wave.sampleSize != 16) && (wave.sampleSize != 32)) - { - TraceLog(LOG_WARNING, "[%s] WAV sample size (%ibit) not supported, converted to 16bit", fileName, wave.sampleSize); - WaveFormat(&wave, wave.sampleRate, 16, wave.channels); - } - - // NOTE: Only support up to 2 channels (mono, stereo) - if (wave.channels > 2) - { - WaveFormat(&wave, wave.sampleRate, wave.sampleSize, 2); - TraceLog(LOG_WARNING, "[%s] WAV channels number (%i) not supported, converted to 2 channels", fileName, wave.channels); - } - - // NOTE: subChunkSize comes in bytes, we need to translate it to number of samples - wave.sampleCount = (wavData.subChunkSize/(wave.sampleSize/8))/wave.channels; - - TraceLog(LOG_INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); - } - } - } - - fclose(wavFile); - } - - return wave; -} - -// Save wave data as WAV file -static int SaveWAV(Wave wave, const char *fileName) -{ - int success = 0; - int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; - - // Basic WAV headers structs - typedef struct { - char chunkID[4]; - int chunkSize; - char format[4]; - } RiffHeader; - - typedef struct { - char subChunkID[4]; - int subChunkSize; - short audioFormat; - short numChannels; - int sampleRate; - int byteRate; - short blockAlign; - short bitsPerSample; - } WaveFormat; - - typedef struct { - char subChunkID[4]; - int subChunkSize; - } WaveData; - - FILE *wavFile = fopen(fileName, "wb"); - - if (wavFile == NULL) TraceLog(LOG_WARNING, "[%s] WAV audio file could not be created", fileName); - else - { - RiffHeader riffHeader; - WaveFormat waveFormat; - WaveData waveData; - - // Fill structs with data - riffHeader.chunkID[0] = 'R'; - riffHeader.chunkID[1] = 'I'; - riffHeader.chunkID[2] = 'F'; - riffHeader.chunkID[3] = 'F'; - riffHeader.chunkSize = 44 - 4 + wave.sampleCount*wave.sampleSize/8; - riffHeader.format[0] = 'W'; - riffHeader.format[1] = 'A'; - riffHeader.format[2] = 'V'; - riffHeader.format[3] = 'E'; - - waveFormat.subChunkID[0] = 'f'; - waveFormat.subChunkID[1] = 'm'; - waveFormat.subChunkID[2] = 't'; - waveFormat.subChunkID[3] = ' '; - waveFormat.subChunkSize = 16; - waveFormat.audioFormat = 1; - waveFormat.numChannels = wave.channels; - waveFormat.sampleRate = wave.sampleRate; - waveFormat.byteRate = wave.sampleRate*wave.sampleSize/8; - waveFormat.blockAlign = wave.sampleSize/8; - waveFormat.bitsPerSample = wave.sampleSize; - - waveData.subChunkID[0] = 'd'; - waveData.subChunkID[1] = 'a'; - waveData.subChunkID[2] = 't'; - waveData.subChunkID[3] = 'a'; - waveData.subChunkSize = dataSize; - - success = fwrite(&riffHeader, sizeof(RiffHeader), 1, wavFile); - success = fwrite(&waveFormat, sizeof(WaveFormat), 1, wavFile); - success = fwrite(&waveData, sizeof(WaveData), 1, wavFile); - - success = fwrite(wave.data, dataSize, 1, wavFile); - - fclose(wavFile); - } - - // If all data has been written correctly to file, success = 1 - return success; -} -#endif - -#if defined(SUPPORT_FILEFORMAT_OGG) -// Load OGG file into Wave structure -// NOTE: Using stb_vorbis library -static Wave LoadOGG(const char *fileName) -{ - Wave wave = { 0 }; - - stb_vorbis *oggFile = stb_vorbis_open_filename(fileName, NULL, NULL); - - if (oggFile == NULL) TraceLog(LOG_WARNING, "[%s] OGG file could not be opened", fileName); - else - { - stb_vorbis_info info = stb_vorbis_get_info(oggFile); - - wave.sampleRate = info.sample_rate; - wave.sampleSize = 16; // 16 bit per sample (short) - wave.channels = info.channels; - wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggFile)*info.channels; // Independent by channel - - float totalSeconds = stb_vorbis_stream_length_in_seconds(oggFile); - if (totalSeconds > 10) TraceLog(LOG_WARNING, "[%s] Ogg audio length is larger than 10 seconds (%f), that's a big file in memory, consider music streaming", fileName, totalSeconds); - - wave.data = (short *)malloc(wave.sampleCount*wave.channels*sizeof(short)); - - // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)wave.data, wave.sampleCount*wave.channels); - - TraceLog(LOG_DEBUG, "[%s] Samples obtained: %i", fileName, numSamplesOgg); - - TraceLog(LOG_INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); - - stb_vorbis_close(oggFile); - } - - return wave; -} -#endif - -#if defined(SUPPORT_FILEFORMAT_FLAC) -// Load FLAC file into Wave structure -// NOTE: Using dr_flac library -static Wave LoadFLAC(const char *fileName) -{ - Wave wave; - - // Decode an entire FLAC file in one go - uint64_t totalSampleCount; - wave.data = drflac_open_and_decode_file_s16(fileName, &wave.channels, &wave.sampleRate, &totalSampleCount); - - wave.sampleCount = (unsigned int)totalSampleCount; - wave.sampleSize = 16; - - // NOTE: Only support up to 2 channels (mono, stereo) - if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] FLAC channels number (%i) not supported", fileName, wave.channels); - - if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] FLAC data could not be loaded", fileName); - else TraceLog(LOG_INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); - - return wave; -} -#endif - -#if defined(SUPPORT_FILEFORMAT_MP3) -// Load MP3 file into Wave structure -// NOTE: Using dr_mp3 library -static Wave LoadMP3(const char *fileName) -{ - Wave wave = { 0 }; - - // Decode an entire MP3 file in one go - uint64_t totalFrameCount = 0; - drmp3_config config = { 0 }; - wave.data = drmp3_open_file_and_read_f32(fileName, &config, &totalFrameCount); - - wave.channels = config.outputChannels; - wave.sampleRate = config.outputSampleRate; - wave.sampleCount = (int)totalFrameCount*wave.channels; - wave.sampleSize = 32; - - // NOTE: Only support up to 2 channels (mono, stereo) - if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] MP3 channels number (%i) not supported", fileName, wave.channels); - - if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] MP3 data could not be loaded", fileName); - else TraceLog(LOG_INFO, "[%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); - - return wave; -} -#endif - -// Some required functions for audio standalone module version -#if defined(AUDIO_STANDALONE) -// Check file extension -bool IsFileExtension(const char *fileName, const char *ext) -{ - bool result = false; - const char *fileExt; - - if ((fileExt = strrchr(fileName, '.')) != NULL) - { - if (strcmp(fileExt, ext) == 0) result = true; - } - - return result; -} - -// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -void TraceLog(int msgType, const char *text, ...) -{ - va_list args; - va_start(args, text); - - switch (msgType) - { - case LOG_INFO: fprintf(stdout, "INFO: "); break; - case LOG_ERROR: fprintf(stdout, "ERROR: "); break; - case LOG_WARNING: fprintf(stdout, "WARNING: "); break; - case LOG_DEBUG: fprintf(stdout, "DEBUG: "); break; - default: break; - } - - vfprintf(stdout, text, args); - fprintf(stdout, "\n"); - - va_end(args); - - if (msgType == LOG_ERROR) exit(1); -} -#endif diff --git a/src/audio.h b/src/audio.h deleted file mode 100644 index 01c93741..00000000 --- a/src/audio.h +++ /dev/null @@ -1,182 +0,0 @@ -/********************************************************************************************** -* -* raylib.audio - Basic funtionality to work with audio -* -* FEATURES: -* - Manage audio device (init/close) -* - Load and unload audio files -* - Format wave data (sample rate, size, channels) -* - Play/Stop/Pause/Resume loaded audio -* - Manage mixing channels -* - Manage raw audio context -* -* LIMITATIONS (only OpenAL Soft): -* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) -* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) -* -* DEPENDENCIES: -* mini_al - Audio device/context management (https://github.com/dr-soft/mini_al) -* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) -* jar_xm - XM module file loading -* jar_mod - MOD audio file loading -* dr_flac - FLAC audio file loading -* -* *OpenAL Soft - Audio device management, still used on HTML5 and OSX platforms -* -* CONTRIBUTORS: -* David Reid (github: @mackron) (Nov. 2017): -* - Complete port to mini_al library -* -* Joshua Reisenauer (github: @kd7tck) (2015) -* - XM audio module support (jar_xm) -* - MOD audio module support (jar_mod) -* - Mixing channels support -* - Raw audio context support -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef AUDIO_H -#define AUDIO_H - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -// NOTE: Below types are required for CAMERA_STANDALONE usage -//---------------------------------------------------------------------------------- -#ifndef __cplusplus -// Boolean type - #if !defined(_STDBOOL_H) - typedef enum { false, true } bool; - #define _STDBOOL_H - #endif -#endif - -// Wave type, defines audio wave data -typedef struct Wave { - unsigned int sampleCount; // Number of samples - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo) - void *data; // Buffer data pointer -} Wave; - -// Sound source type -typedef struct Sound { - void *audioBuffer; // Pointer to internal data used by the audio system - - unsigned int source; // Audio source id - unsigned int buffer; // Audio buffer id - int format; // Audio format specifier -} Sound; - -// Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed -typedef struct MusicData *Music; - -// Audio stream type -// NOTE: Useful to create custom audio streams not bound to a specific file -typedef struct AudioStream { - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo) - - void *audioBuffer; // Pointer to internal data used by the audio system. - - int format; // Audio format specifier - unsigned int source; // Audio source id - unsigned int buffers[2]; // Audio buffers (double buffering) -} AudioStream; - -#ifdef __cplusplus -extern "C" { // Prevents name mangling of functions -#endif - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- -void InitAudioDevice(void); // Initialize audio device and context -void CloseAudioDevice(void); // Close the audio device and context -bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully -void SetMasterVolume(float volume); // Set master volume (listener) - -Wave LoadWave(const char *fileName); // Load wave data from file -Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels); // Load wave data from raw array data -Sound LoadSound(const char *fileName); // Load sound from file -Sound LoadSoundFromWave(Wave wave); // Load sound from wave data -void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data -void UnloadWave(Wave wave); // Unload wave data -void UnloadSound(Sound sound); // Unload sound -void PlaySound(Sound sound); // Play a sound -void PauseSound(Sound sound); // Pause a sound -void ResumeSound(Sound sound); // Resume a paused sound -void StopSound(Sound sound); // Stop playing a sound -bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing -void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) -void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format -Wave WaveCopy(Wave wave); // Copy a wave to a new wave -void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range -float *GetWaveData(Wave wave); // Get samples data from wave as a floats array -Music LoadMusicStream(const char *fileName); // Load music stream from file -void UnloadMusicStream(Music music); // Unload music stream -void PlayMusicStream(Music music); // Start music playing -void UpdateMusicStream(Music music); // Updates buffers for music streaming -void StopMusicStream(Music music); // Stop music playing -void PauseMusicStream(Music music); // Pause music playing -void ResumeMusicStream(Music music); // Resume playing paused music -bool IsMusicPlaying(Music music); // Check if music is playing -void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) -void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -void SetMusicLoopCount(Music music, int count); // Set music loop count (loop repeats) -float GetMusicTimeLength(Music music); // Get music time length (in seconds) -float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) - -// AudioStream management functions -AudioStream InitAudioStream(unsigned int sampleRate, - unsigned int sampleSize, - unsigned int channels); // Init audio stream (to stream raw audio pcm data) -void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data -void CloseAudioStream(AudioStream stream); // Close audio stream and free memory -bool IsAudioBufferProcessed(AudioStream stream); // Check if any audio stream buffers requires refill -void PlayAudioStream(AudioStream stream); // Play audio stream -void PauseAudioStream(AudioStream stream); // Pause audio stream -void ResumeAudioStream(AudioStream stream); // Resume audio stream -bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing -void StopAudioStream(AudioStream stream); // Stop audio stream -void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) -void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) - -#ifdef __cplusplus -} -#endif - -#endif // AUDIO_H \ No newline at end of file diff --git a/src/config.h b/src/config.h index 6d16207c..d45ff707 100644 --- a/src/config.h +++ b/src/config.h @@ -25,7 +25,7 @@ * **********************************************************************************************/ -#define RAYLIB_VERSION "2.3-dev" +#define RAYLIB_VERSION "2.4-dev" // Edit to control what features Makefile'd raylib is compiled with #if defined(RAYLIB_CMAKE) diff --git a/src/config.h.in b/src/config.h.in index 928405cd..b1c524f7 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -65,7 +65,7 @@ * NOTE: Some generated meshes DO NOT include generated texture coordinates */ #cmakedefine SUPPORT_MESH_GENERATION 1 -// audio.c +// raudio.c /* Desired fileformats to be supported for loading. */ #cmakedefine SUPPORT_FILEFORMAT_WAV 1 #cmakedefine SUPPORT_FILEFORMAT_OGG 1 diff --git a/src/libraylib.a b/src/libraylib.a new file mode 100644 index 00000000..5aa7099d Binary files /dev/null and b/src/libraylib.a differ diff --git a/src/raudio.c b/src/raudio.c new file mode 100644 index 00000000..3510acd5 --- /dev/null +++ b/src/raudio.c @@ -0,0 +1,1958 @@ +/********************************************************************************************** +* +* raylib.audio - Basic funtionality to work with audio +* +* FEATURES: +* - Manage audio device (init/close) +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* - Manage mixing channels +* - Manage raw audio context +* +* CONFIGURATION: +* +* #define RAUDIO_STANDALONE +* Define to use the module as standalone library (independently of raylib). +* Required types and functions are defined in the same module. +* +* #define SUPPORT_FILEFORMAT_WAV +* #define SUPPORT_FILEFORMAT_OGG +* #define SUPPORT_FILEFORMAT_XM +* #define SUPPORT_FILEFORMAT_MOD +* #define SUPPORT_FILEFORMAT_FLAC +* #define SUPPORT_FILEFORMAT_MP3 +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* LIMITATIONS (only OpenAL Soft): +* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) +* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) +* +* DEPENDENCIES: +* mini_al - Audio device/context management (https://github.com/dr-soft/mini_al) +* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) +* jar_xm - XM module file loading +* jar_mod - MOD audio file loading +* dr_flac - FLAC audio file loading +* +* *OpenAL Soft - Audio device management, still used on HTML5 and OSX platforms +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to mini_al library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(RAUDIO_STANDALONE) + #include "raudio.h" + #include // Required for: va_list, va_start(), vfprintf(), va_end() +#else + #include "raylib.h" // Declares module functions +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include "utils.h" // Required for: fopen() Android mapping +#endif + +#include "external/mini_al.h" // mini_al audio library + // NOTE: Cannot be implement here because it conflicts with + // Win32 APIs: Rectangle, CloseWindow(), ShowCursor(), PlaySoundA() + +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strncmp() +#include // Required for: FILE, fopen(), fclose(), fread() + +#if defined(SUPPORT_FILEFORMAT_OGG) + #define STB_VORBIS_IMPLEMENTATION + #include "external/stb_vorbis.h" // OGG loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_XM) + #define JAR_XM_IMPLEMENTATION + #include "external/jar_xm.h" // XM loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MOD) + #define JAR_MOD_IMPLEMENTATION + #include "external/jar_mod.h" // MOD loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) + #define DR_FLAC_IMPLEMENTATION + #define DR_FLAC_NO_WIN32_IO + #include "external/dr_flac.h" // FLAC loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) + #define DR_MP3_IMPLEMENTATION + #include "external/dr_mp3.h" // MP3 loading functions +#endif + +#if defined(_MSC_VER) + #undef bool +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define MAX_STREAM_BUFFERS 2 // Number of buffers for each audio stream + +// NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number +// After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds +// and double-buffering system, I concluded that a 4096 samples buffer should be enough +// In case of music-stalls, just increase this number +#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +typedef enum { + MUSIC_AUDIO_OGG = 0, + MUSIC_AUDIO_FLAC, + MUSIC_AUDIO_MP3, + MUSIC_MODULE_XM, + MUSIC_MODULE_MOD +} MusicContextType; + +// Music type (file streaming from memory) +typedef struct MusicData { + MusicContextType ctxType; // Type of music context +#if defined(SUPPORT_FILEFORMAT_OGG) + stb_vorbis *ctxOgg; // OGG audio context +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + drflac *ctxFlac; // FLAC audio context +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + drmp3 ctxMp3; // MP3 audio context +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + jar_xm_context_t *ctxXm; // XM chiptune context +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + jar_mod_context_t ctxMod; // MOD chiptune context +#endif + + AudioStream stream; // Audio stream (double buffering) + + int loopCount; // Loops count (times music repeats), -1 means infinite loop + unsigned int totalSamples; // Total number of samples + unsigned int samplesLeft; // Number of samples left to end +} MusicData; + +#if defined(RAUDIO_STANDALONE) +typedef enum { + LOG_INFO = 0, + LOG_ERROR, + LOG_WARNING, + LOG_DEBUG, + LOG_OTHER +} TraceLogType; +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_WAV) +static Wave LoadWAV(const char *fileName); // Load WAV file +static int SaveWAV(Wave wave, const char *fileName); // Save wave data as WAV file +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) +static Wave LoadOGG(const char *fileName); // Load OGG file +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) +static Wave LoadFLAC(const char *fileName); // Load FLAC file +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) +static Wave LoadMP3(const char *fileName); // Load MP3 file +#endif + +#if defined(RAUDIO_STANDALONE) +bool IsFileExtension(const char *fileName, const char *ext); // Check file extension +void TraceLog(int msgType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +#endif + +//---------------------------------------------------------------------------------- +// mini_al AudioBuffer Functionality +//---------------------------------------------------------------------------------- +#define DEVICE_FORMAT mal_format_f32 +#define DEVICE_CHANNELS 2 +#define DEVICE_SAMPLE_RATE 44100 + +typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; + +// Audio buffer structure +// NOTE: Slightly different logic is used when feeding data to the playback device depending on whether or not data is streamed +typedef struct AudioBuffer AudioBuffer; +struct AudioBuffer { + mal_dsp dsp; // Required for format conversion + float volume; + float pitch; + bool playing; + bool paused; + bool looping; // Always true for AudioStreams + int usage; // AudioBufferUsage type + bool isSubBufferProcessed[2]; + unsigned int frameCursorPos; + unsigned int bufferSizeInFrames; + AudioBuffer *next; + AudioBuffer *prev; + unsigned char buffer[1]; +}; + +// mini_al global variables +static mal_context context; +static mal_device device; +static mal_mutex audioLock; +static bool isAudioInitialized = MAL_FALSE; +static float masterVolume = 1.0f; + +// Audio buffers are tracked in a linked list +static AudioBuffer *firstAudioBuffer = NULL; +static AudioBuffer *lastAudioBuffer = NULL; + +// mini_al functions declaration +static void OnLog(mal_context *pContext, mal_device *pDevice, const char *message); +static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameCount, void *pFramesOut); +static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, void *pFramesOut, void *pUserData); +static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume); + +// AudioBuffer management functions declaration +// NOTE: Those functions are not exposed by raylib... for the moment +AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage); +void DeleteAudioBuffer(AudioBuffer *audioBuffer); +bool IsAudioBufferPlaying(AudioBuffer *audioBuffer); +void PlayAudioBuffer(AudioBuffer *audioBuffer); +void StopAudioBuffer(AudioBuffer *audioBuffer); +void PauseAudioBuffer(AudioBuffer *audioBuffer); +void ResumeAudioBuffer(AudioBuffer *audioBuffer); +void SetAudioBufferVolume(AudioBuffer *audioBuffer, float volume); +void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch); +void TrackAudioBuffer(AudioBuffer *audioBuffer); +void UntrackAudioBuffer(AudioBuffer *audioBuffer); + + +// Log callback function +static void OnLog(mal_context *pContext, mal_device *pDevice, const char *message) +{ + (void)pContext; + (void)pDevice; + + TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors +} + +// Sending audio data to device callback function +static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameCount, void *pFramesOut) +{ + // This is where all of the mixing takes place. + (void)pDevice; + + // Mixing is basically just an accumulation. We need to initialize the output buffer to 0. + memset(pFramesOut, 0, frameCount*pDevice->channels*mal_get_bytes_per_sample(pDevice->format)); + + // Using a mutex here for thread-safety which makes things not real-time. This is unlikely to be necessary for this project, but may + // want to consider how you might want to avoid this. + mal_mutex_lock(&audioLock); + { + for (AudioBuffer *audioBuffer = firstAudioBuffer; audioBuffer != NULL; audioBuffer = audioBuffer->next) + { + // Ignore stopped or paused sounds. + if (!audioBuffer->playing || audioBuffer->paused) continue; + + mal_uint32 framesRead = 0; + for (;;) + { + if (framesRead > frameCount) + { + TraceLog(LOG_DEBUG, "Mixed too many frames from audio buffer"); + break; + } + + if (framesRead == frameCount) break; + + // Just read as much data as we can from the stream. + mal_uint32 framesToRead = (frameCount - framesRead); + while (framesToRead > 0) + { + float tempBuffer[1024]; // 512 frames for stereo. + + mal_uint32 framesToReadRightNow = framesToRead; + if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS) + { + framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS; + } + + mal_uint32 framesJustRead = (mal_uint32)mal_dsp_read(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, audioBuffer->dsp.pUserData); + if (framesJustRead > 0) + { + float *framesOut = (float *)pFramesOut + (framesRead*device.channels); + float *framesIn = tempBuffer; + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); + + framesToRead -= framesJustRead; + framesRead += framesJustRead; + } + + // If we weren't able to read all the frames we requested, break. + if (framesJustRead < framesToReadRightNow) + { + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + else + { + // Should never get here, but just for safety, + // move the cursor position back to the start and continue the loop. + audioBuffer->frameCursorPos = 0; + continue; + } + } + } + + // If for some reason we weren't able to read every frame we'll need to break from the loop. + // Not doing this could theoretically put us into an infinite loop. + if (framesToRead > 0) break; + } + } + } + + mal_mutex_unlock(&audioLock); + + return frameCount; // We always output the same number of frames that were originally requested. +} + +// DSP read from audio buffer callback function +static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, void *pFramesOut, void *pUserData) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)pUserData; + + mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; + mal_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; + + if (currentSubBufferIndex > 1) + { + TraceLog(LOG_DEBUG, "Frame cursor position moved too far forward in audio stream"); + return 0; + } + + // Another thread can update the processed state of buffers so we just take a copy here to try and avoid potential synchronization problems. + bool isSubBufferProcessed[2]; + isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; + + mal_uint32 frameSizeInBytes = mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)*audioBuffer->dsp.formatConverterIn.config.channels; + + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. + mal_uint32 framesRead = 0; + for (;;) + { + // We break from this loop differently depending on the buffer's usage. For static buffers, we simply fill as much data as we can. For + // streaming buffers we only fill the halves of the buffer that are processed. Unprocessed halves must keep their audio data in-tact. + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + if (framesRead >= frameCount) break; + } + else + { + if (isSubBufferProcessed[currentSubBufferIndex]) break; + } + + mal_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) break; + + mal_uint32 framesRemainingInOutputBuffer; + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + framesRemainingInOutputBuffer = audioBuffer->bufferSizeInFrames - audioBuffer->frameCursorPos; + } + else + { + mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; + framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); + } + + mal_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; + + memcpy((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), audioBuffer->buffer + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead) % audioBuffer->bufferSizeInFrames; + framesRead += framesToRead; + + // If we've read to the end of the buffer, mark it as processed. + if (framesToRead == framesRemainingInOutputBuffer) + { + audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; + isSubBufferProcessed[currentSubBufferIndex] = true; + + currentSubBufferIndex = (currentSubBufferIndex + 1)%2; + + // We need to break from this loop if we're not looping. + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + } + } + + // Zero-fill excess. + mal_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) + { + memset((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + + // For static buffers we can fill the remaining frames with silence for safety, but we don't want + // to report those frames as "read". The reason for this is that the caller uses the return value + // to know whether or not a non-looping sound has finished playback. + if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; + } + + return framesRead; +} + +// This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation. +// NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. +static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume) +{ + for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) + { + for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) + { + float *frameOut = framesOut + (iFrame*device.channels); + const float *frameIn = framesIn + (iFrame*device.channels); + + frameOut[iChannel] += frameIn[iChannel]*masterVolume*localVolume; + } + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Device initialization and Closing +//---------------------------------------------------------------------------------- +// Initialize audio device +void InitAudioDevice(void) +{ + // Context. + mal_context_config contextConfig = mal_context_config_init(OnLog); + mal_result result = mal_context_init(NULL, 0, &contextConfig, &context); + if (result != MAL_SUCCESS) + { + TraceLog(LOG_ERROR, "Failed to initialize audio context"); + return; + } + + // Device. Using the default device. Format is floating point because it simplifies mixing. + mal_device_config deviceConfig = mal_device_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, OnSendAudioDataToDevice); + + result = mal_device_init(&context, mal_device_type_playback, NULL, &deviceConfig, NULL, &device); + if (result != MAL_SUCCESS) + { + TraceLog(LOG_ERROR, "Failed to initialize audio playback device"); + mal_context_uninit(&context); + return; + } + + // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running + // while there's at least one sound being played. + result = mal_device_start(&device); + if (result != MAL_SUCCESS) + { + TraceLog(LOG_ERROR, "Failed to start audio playback device"); + mal_device_uninit(&device); + mal_context_uninit(&context); + return; + } + + // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may + // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. + if (mal_mutex_init(&context, &audioLock) != MAL_SUCCESS) + { + TraceLog(LOG_ERROR, "Failed to create mutex for audio mixing"); + mal_device_uninit(&device); + mal_context_uninit(&context); + return; + } + + TraceLog(LOG_INFO, "Audio device initialized successfully: %s", device.name); + TraceLog(LOG_INFO, "Audio backend: mini_al / %s", mal_get_backend_name(context.backend)); + TraceLog(LOG_INFO, "Audio format: %s -> %s", mal_get_format_name(device.format), mal_get_format_name(device.internalFormat)); + TraceLog(LOG_INFO, "Audio channels: %d -> %d", device.channels, device.internalChannels); + TraceLog(LOG_INFO, "Audio sample rate: %d -> %d", device.sampleRate, device.internalSampleRate); + TraceLog(LOG_INFO, "Audio buffer size: %d", device.bufferSizeInFrames); + + isAudioInitialized = MAL_TRUE; +} + +// Close the audio device for all contexts +void CloseAudioDevice(void) +{ + if (!isAudioInitialized) + { + TraceLog(LOG_WARNING, "Could not close audio device because it is not currently initialized"); + return; + } + + mal_mutex_uninit(&audioLock); + mal_device_uninit(&device); + mal_context_uninit(&context); + + TraceLog(LOG_INFO, "Audio device closed successfully"); +} + +// Check if device has been initialized successfully +bool IsAudioDeviceReady(void) +{ + return isAudioInitialized; +} + +// Set master volume (listener) +void SetMasterVolume(float volume) +{ + if (volume < 0.0f) volume = 0.0f; + else if (volume > 1.0f) volume = 1.0f; + + masterVolume = volume; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Buffer management +//---------------------------------------------------------------------------------- + +// Create a new audio buffer. Initially filled with silence +AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)calloc(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*mal_get_bytes_per_sample(format)), 1); + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to allocate memory for audio buffer"); + return NULL; + } + + // We run audio data through a format converter. + mal_dsp_config dspConfig; + memset(&dspConfig, 0, sizeof(dspConfig)); + dspConfig.formatIn = format; + dspConfig.formatOut = DEVICE_FORMAT; + dspConfig.channelsIn = channels; + dspConfig.channelsOut = DEVICE_CHANNELS; + dspConfig.sampleRateIn = sampleRate; + dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE; + dspConfig.onRead = OnAudioBufferDSPRead; + dspConfig.pUserData = audioBuffer; + dspConfig.allowDynamicSampleRate = MAL_TRUE; // <-- Required for pitch shifting. + mal_result resultMAL = mal_dsp_init(&dspConfig, &audioBuffer->dsp); + if (resultMAL != MAL_SUCCESS) + { + TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to create data conversion pipeline"); + free(audioBuffer); + return NULL; + } + + audioBuffer->volume = 1; + audioBuffer->pitch = 1; + audioBuffer->playing = 0; + audioBuffer->paused = 0; + audioBuffer->looping = 0; + audioBuffer->usage = usage; + audioBuffer->bufferSizeInFrames = bufferSizeInFrames; + audioBuffer->frameCursorPos = 0; + + // Buffers should be marked as processed by default so that a call to UpdateAudioStream() immediately after initialization works correctly. + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; + + TrackAudioBuffer(audioBuffer); + + return audioBuffer; +} + +// Delete an audio buffer +void DeleteAudioBuffer(AudioBuffer *audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "DeleteAudioBuffer() : No audio buffer"); + return; + } + + UntrackAudioBuffer(audioBuffer); + free(audioBuffer); +} + +// Check if an audio buffer is playing +bool IsAudioBufferPlaying(AudioBuffer *audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "IsAudioBufferPlaying() : No audio buffer"); + return false; + } + + return audioBuffer->playing && !audioBuffer->paused; +} + +// Play an audio buffer +// NOTE: Buffer is restarted to the start. +// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. +void PlayAudioBuffer(AudioBuffer *audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->playing = true; + audioBuffer->paused = false; + audioBuffer->frameCursorPos = 0; +} + +// Stop an audio buffer +void StopAudioBuffer(AudioBuffer *audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "StopAudioBuffer() : No audio buffer"); + return; + } + + // Don't do anything if the audio buffer is already stopped. + if (!IsAudioBufferPlaying(audioBuffer)) return; + + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->frameCursorPos = 0; + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; +} + +// Pause an audio buffer +void PauseAudioBuffer(AudioBuffer *audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PauseAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->paused = true; +} + +// Resume an audio buffer +void ResumeAudioBuffer(AudioBuffer *audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "ResumeAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->paused = false; +} + +// Set volume for an audio buffer +void SetAudioBufferVolume(AudioBuffer *audioBuffer, float volume) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "SetAudioBufferVolume() : No audio buffer"); + return; + } + + audioBuffer->volume = volume; +} + +// Set pitch for an audio buffer +void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "SetAudioBufferPitch() : No audio buffer"); + return; + } + + audioBuffer->pitch = pitch; + + // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches + // will make the sound faster; lower pitches make it slower. + mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.src.config.sampleRateOut / (float)audioBuffer->dsp.src.config.sampleRateIn) / pitch) * audioBuffer->dsp.src.config.sampleRateIn); + mal_dsp_set_output_sample_rate(&audioBuffer->dsp, newOutputSampleRate); +} + +// Track audio buffer to linked list next position +void TrackAudioBuffer(AudioBuffer *audioBuffer) +{ + mal_mutex_lock(&audioLock); + + { + if (firstAudioBuffer == NULL) firstAudioBuffer = audioBuffer; + else + { + lastAudioBuffer->next = audioBuffer; + audioBuffer->prev = lastAudioBuffer; + } + + lastAudioBuffer = audioBuffer; + } + + mal_mutex_unlock(&audioLock); +} + +// Untrack audio buffer from linked list +void UntrackAudioBuffer(AudioBuffer *audioBuffer) +{ + mal_mutex_lock(&audioLock); + + { + if (audioBuffer->prev == NULL) firstAudioBuffer = audioBuffer->next; + else audioBuffer->prev->next = audioBuffer->next; + + if (audioBuffer->next == NULL) lastAudioBuffer = audioBuffer->prev; + else audioBuffer->next->prev = audioBuffer->prev; + + audioBuffer->prev = NULL; + audioBuffer->next = NULL; + } + + mal_mutex_unlock(&audioLock); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Sounds loading and playing (.WAV) +//---------------------------------------------------------------------------------- + +// Load wave data from file +Wave LoadWave(const char *fileName) +{ + Wave wave = { 0 }; + + if (IsFileExtension(fileName, ".wav")) wave = LoadWAV(fileName); +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (IsFileExtension(fileName, ".ogg")) wave = LoadOGG(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (IsFileExtension(fileName, ".flac")) wave = LoadFLAC(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (IsFileExtension(fileName, ".mp3")) wave = LoadMP3(fileName); +#endif + else TraceLog(LOG_WARNING, "[%s] Audio fileformat not supported, it can't be loaded", fileName); + + return wave; +} + +// Load wave data from raw array data +Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels) +{ + Wave wave; + + wave.data = data; + wave.sampleCount = sampleCount; + wave.sampleRate = sampleRate; + wave.sampleSize = sampleSize; + wave.channels = channels; + + // NOTE: Copy wave data to work with, user is responsible of input data to free + Wave cwave = WaveCopy(wave); + + WaveFormat(&cwave, sampleRate, sampleSize, channels); + + return cwave; +} + +// Load sound from file +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) +{ + Wave wave = LoadWave(fileName); + + Sound sound = LoadSoundFromWave(wave); + + UnloadWave(wave); // Sound is loaded, we can unload wave + + return sound; +} + +// Load sound from wave data +// NOTE: Wave data must be unallocated manually +Sound LoadSoundFromWave(Wave wave) +{ + Sound sound = { 0 }; + + if (wave.data != NULL) + { + // When using mini_al we need to do our own mixing. To simplify this we need convert the format of each sound to be consistent with + // the format used to open the playback device. We can do this two ways: + // + // 1) Convert the whole sound in one go at load time (here). + // 2) Convert the audio data in chunks at mixing time. + // + // I have decided on the first option because it offloads work required for the format conversion to the to the loading stage. + // The downside to this is that it uses more memory if the original sound is u8 or s16. + mal_format formatIn = ((wave.sampleSize == 8) ? mal_format_u8 : ((wave.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + mal_uint32 frameCountIn = wave.sampleCount/wave.channels; + + mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); + if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to get frame count for format conversion"); + + AudioBuffer* audioBuffer = CreateAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to create audio buffer"); + + frameCount = (mal_uint32)mal_convert_frames(audioBuffer->buffer, audioBuffer->dsp.formatConverterIn.config.formatIn, audioBuffer->dsp.formatConverterIn.config.channels, audioBuffer->dsp.src.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); + if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Format conversion failed"); + + sound.audioBuffer = audioBuffer; + } + + return sound; +} + +// Unload wave data +void UnloadWave(Wave wave) +{ + if (wave.data != NULL) free(wave.data); + + TraceLog(LOG_INFO, "Unloaded wave data from RAM"); +} + +// Unload sound +void UnloadSound(Sound sound) +{ + DeleteAudioBuffer((AudioBuffer *)sound.audioBuffer); + + TraceLog(LOG_INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); +} + +// Update sound buffer with new data +// NOTE: data must match sound.format +void UpdateSound(Sound sound, const void *data, int samplesCount) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)sound.audioBuffer; + + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound - no audio buffer"); + return; + } + + StopAudioBuffer(audioBuffer); + + // TODO: May want to lock/unlock this since this data buffer is read at mixing time. + memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.formatConverterIn.config.channels*mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)); +} + +// Export wave data to file +void ExportWave(Wave wave, const char *fileName) +{ + bool success = false; + + if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw sample data (without header) + // NOTE: It's up to the user to track wave parameters + FILE *rawFile = fopen(fileName, "wb"); + success = fwrite(wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8, 1, rawFile); + fclose(rawFile); + } + + if (success) TraceLog(LOG_INFO, "Wave exported successfully: %s", fileName); + else TraceLog(LOG_WARNING, "Wave could not be exported."); +} + +// Export wave sample data to code (.h) +void ExportWaveAsCode(Wave wave, const char *fileName) +{ + #define BYTES_TEXT_PER_LINE 20 + + char varFileName[256] = { 0 }; + int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; + + FILE *txtFile = fopen(fileName, "wt"); + + fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } + + fprintf(txtFile, "// Wave data information\n"); + fprintf(txtFile, "#define %s_SAMPLE_COUNT %i\n", varFileName, wave.sampleCount); + fprintf(txtFile, "#define %s_SAMPLE_RATE %i\n", varFileName, wave.sampleRate); + fprintf(txtFile, "#define %s_SAMPLE_SIZE %i\n", varFileName, wave.sampleSize); + fprintf(txtFile, "#define %s_CHANNELS %i\n\n", varFileName, wave.channels); + + // Write byte data as hexadecimal text + fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); + fprintf(txtFile, "0x%x };\n", ((unsigned char *)wave.data)[dataSize - 1]); + + fclose(txtFile); +} + +// Play a sound +void PlaySound(Sound sound) +{ + PlayAudioBuffer((AudioBuffer *)sound.audioBuffer); +} + +// Pause a sound +void PauseSound(Sound sound) +{ + PauseAudioBuffer((AudioBuffer *)sound.audioBuffer); +} + +// Resume a paused sound +void ResumeSound(Sound sound) +{ + ResumeAudioBuffer((AudioBuffer *)sound.audioBuffer); +} + +// Stop reproducing a sound +void StopSound(Sound sound) +{ + StopAudioBuffer((AudioBuffer *)sound.audioBuffer); +} + +// Check if a sound is playing +bool IsSoundPlaying(Sound sound) +{ + return IsAudioBufferPlaying((AudioBuffer *)sound.audioBuffer); +} + +// Set volume for a sound +void SetSoundVolume(Sound sound, float volume) +{ + SetAudioBufferVolume((AudioBuffer *)sound.audioBuffer, volume); +} + +// Set pitch for a sound +void SetSoundPitch(Sound sound, float pitch) +{ + SetAudioBufferPitch((AudioBuffer *)sound.audioBuffer, pitch); +} + +// Convert wave data to desired format +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) +{ + mal_format formatIn = ((wave->sampleSize == 8) ? mal_format_u8 : ((wave->sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + mal_format formatOut = (( sampleSize == 8) ? mal_format_u8 : (( sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + + mal_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. + + mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, formatOut, channels, sampleRate, NULL, formatIn, wave->channels, wave->sampleRate, frameCountIn); + if (frameCount == 0) + { + TraceLog(LOG_ERROR, "WaveFormat() : Failed to get frame count for format conversion."); + return; + } + + void *data = malloc(frameCount*channels*(sampleSize/8)); + + frameCount = (mal_uint32)mal_convert_frames(data, formatOut, channels, sampleRate, wave->data, formatIn, wave->channels, wave->sampleRate, frameCountIn); + if (frameCount == 0) + { + TraceLog(LOG_ERROR, "WaveFormat() : Format conversion failed."); + return; + } + + wave->sampleCount = frameCount; + wave->sampleSize = sampleSize; + wave->sampleRate = sampleRate; + wave->channels = channels; + free(wave->data); + wave->data = data; +} + +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave = { 0 }; + + newWave.data = malloc(wave.sampleCount*wave.sampleSize/8*wave.channels); + + if (newWave.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8); + + newWave.sampleCount = wave.sampleCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; + } + + return newWave; +} + +// Crop a wave to defined samples range +// NOTE: Security check in case of out-of-range +void WaveCrop(Wave *wave, int initSample, int finalSample) +{ + if ((initSample >= 0) && (initSample < finalSample) && + (finalSample > 0) && ((unsigned int)finalSample < wave->sampleCount)) + { + int sampleCount = finalSample - initSample; + + void *data = malloc(sampleCount*wave->sampleSize/8*wave->channels); + + memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); + + free(wave->data); + wave->data = data; + } + else TraceLog(LOG_WARNING, "Wave crop range out of bounds"); +} + +// Get samples data from wave as a floats array +// NOTE: Returned sample values are normalized to range [-1..1] +float *GetWaveData(Wave wave) +{ + float *samples = (float *)malloc(wave.sampleCount*wave.channels*sizeof(float)); + + for (unsigned int i = 0; i < wave.sampleCount; i++) + { + for (unsigned int j = 0; j < wave.channels; j++) + { + if (wave.sampleSize == 8) samples[wave.channels*i + j] = (float)(((unsigned char *)wave.data)[wave.channels*i + j] - 127)/256.0f; + else if (wave.sampleSize == 16) samples[wave.channels*i + j] = (float)((short *)wave.data)[wave.channels*i + j]/32767.0f; + else if (wave.sampleSize == 32) samples[wave.channels*i + j] = ((float *)wave.data)[wave.channels*i + j]; + } + } + + return samples; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing (.OGG) +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) +{ + Music music = (MusicData *)malloc(sizeof(MusicData)); + bool musicLoaded = true; + + if (IsFileExtension(fileName, ".ogg")) + { + // Open ogg audio stream + music->ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (music->ctxOgg == NULL) musicLoaded = false; + else + { + stb_vorbis_info info = stb_vorbis_get_info(music->ctxOgg); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music->stream = InitAudioStream(info.sample_rate, 16, info.channels); + music->totalSamples = (unsigned int)stb_vorbis_stream_length_in_samples(music->ctxOgg)*info.channels; + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_AUDIO_OGG; + music->loopCount = -1; // Infinite loop by default + + TraceLog(LOG_DEBUG, "[%s] OGG total samples: %i", fileName, music->totalSamples); + TraceLog(LOG_DEBUG, "[%s] OGG sample rate: %i", fileName, info.sample_rate); + TraceLog(LOG_DEBUG, "[%s] OGG channels: %i", fileName, info.channels); + TraceLog(LOG_DEBUG, "[%s] OGG memory required: %i", fileName, info.temp_memory_required); + } + } +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (IsFileExtension(fileName, ".flac")) + { + music->ctxFlac = drflac_open_file(fileName); + + if (music->ctxFlac == NULL) musicLoaded = false; + else + { + music->stream = InitAudioStream(music->ctxFlac->sampleRate, music->ctxFlac->bitsPerSample, music->ctxFlac->channels); + music->totalSamples = (unsigned int)music->ctxFlac->totalSampleCount; + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_AUDIO_FLAC; + music->loopCount = -1; // Infinite loop by default + + TraceLog(LOG_DEBUG, "[%s] FLAC total samples: %i", fileName, music->totalSamples); + TraceLog(LOG_DEBUG, "[%s] FLAC sample rate: %i", fileName, music->ctxFlac->sampleRate); + TraceLog(LOG_DEBUG, "[%s] FLAC bits per sample: %i", fileName, music->ctxFlac->bitsPerSample); + TraceLog(LOG_DEBUG, "[%s] FLAC channels: %i", fileName, music->ctxFlac->channels); + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (IsFileExtension(fileName, ".mp3")) + { + int result = drmp3_init_file(&music->ctxMp3, fileName, NULL); + + if (!result) musicLoaded = false; + else + { + TraceLog(LOG_INFO, "[%s] MP3 sample rate: %i", fileName, music->ctxMp3.sampleRate); + TraceLog(LOG_INFO, "[%s] MP3 bits per sample: %i", fileName, 32); + TraceLog(LOG_INFO, "[%s] MP3 channels: %i", fileName, music->ctxMp3.channels); + + music->stream = InitAudioStream(music->ctxMp3.sampleRate, 32, music->ctxMp3.channels); + + // TODO: There is not an easy way to compute the total number of samples available + // in an MP3, frames size could be variable... we tried with a 60 seconds music... but crashes... + music->totalSamples = drmp3_get_pcm_frame_count(&music->ctxMp3)*music->ctxMp3.channels; + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_AUDIO_MP3; + music->loopCount = -1; // Infinite loop by default + + TraceLog(LOG_INFO, "[%s] MP3 total samples: %i", fileName, music->totalSamples); + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (IsFileExtension(fileName, ".xm")) + { + int result = jar_xm_create_context_from_file(&music->ctxXm, 48000, fileName); + + if (!result) // XM context created successfully + { + jar_xm_set_max_loop_count(music->ctxXm, 0); // Set infinite number of loops + + // NOTE: Only stereo is supported for XM + music->stream = InitAudioStream(48000, 16, 2); + music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_MODULE_XM; + music->loopCount = -1; // Infinite loop by default + + TraceLog(LOG_INFO, "[%s] XM number of samples: %i", fileName, music->totalSamples); + TraceLog(LOG_INFO, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + } + else musicLoaded = false; + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (IsFileExtension(fileName, ".mod")) + { + jar_mod_init(&music->ctxMod); + + if (jar_mod_load_file(&music->ctxMod, fileName)) + { + // NOTE: Only stereo is supported for MOD + music->stream = InitAudioStream(48000, 16, 2); + music->totalSamples = (unsigned int)jar_mod_max_samples(&music->ctxMod); + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_MODULE_MOD; + music->loopCount = -1; // Infinite loop by default + + TraceLog(LOG_INFO, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); + TraceLog(LOG_INFO, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + } + else musicLoaded = false; + } +#endif + else musicLoaded = false; + + if (!musicLoaded) + { + if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music->ctxType == MUSIC_AUDIO_MP3) drmp3_uninit(&music->ctxMp3); + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music->ctxType == MUSIC_MODULE_XM) jar_xm_free_context(music->ctxXm); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_unload(&music->ctxMod); + #endif + + free(music); + music = NULL; + + TraceLog(LOG_WARNING, "[%s] Music file could not be opened", fileName); + } + + return music; +} + +// Unload music stream +void UnloadMusicStream(Music music) +{ + if (music == NULL) return; + + CloseAudioStream(music->stream); + + if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (music->ctxType == MUSIC_AUDIO_MP3) drmp3_uninit(&music->ctxMp3); +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (music->ctxType == MUSIC_MODULE_XM) jar_xm_free_context(music->ctxXm); +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_unload(&music->ctxMod); +#endif + + free(music); +} + +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + if (music != NULL) + { + AudioBuffer *audioBuffer = (AudioBuffer *)music->stream.audioBuffer; + + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayMusicStream() : No audio buffer"); + return; + } + + // For music streams, we need to make sure we maintain the frame cursor position. This is hack for this section of code in UpdateMusicStream() + // // NOTE: In case window is minimized, music stream is stopped, + // // just make sure to play again on window restore + // if (IsMusicPlaying(music)) PlayMusicStream(music); + mal_uint32 frameCursorPos = audioBuffer->frameCursorPos; + + PlayAudioStream(music->stream); // <-- This resets the cursor position. + + audioBuffer->frameCursorPos = frameCursorPos; + } +} + +// Pause music playing +void PauseMusicStream(Music music) +{ + if (music != NULL) PauseAudioStream(music->stream); +} + +// Resume music playing +void ResumeMusicStream(Music music) +{ + if (music != NULL) ResumeAudioStream(music->stream); +} + +// Stop music playing (close stream) +// TODO: To clear a buffer, make sure they have been already processed! +void StopMusicStream(Music music) +{ + if (music == NULL) return; + + StopAudioStream(music->stream); + + // Restart music context + switch (music->ctxType) + { + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break; +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: /* TODO: Restart FLAC context */ break; +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame(&music->ctxMp3, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break; +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: jar_mod_seek_start(&music->ctxMod); break; +#endif + default: break; + } + + music->samplesLeft = music->totalSamples; +} + +// Update (re-fill) music buffers if data already processed +// TODO: Make sure buffers are ready for update... check music state +void UpdateMusicStream(Music music) +{ + if (music == NULL) return; + + bool streamEnding = false; + + unsigned int subBufferSizeInFrames = ((AudioBuffer *)music->stream.audioBuffer)->bufferSizeInFrames/2; + + // NOTE: Using dynamic allocation because it could require more than 16KB + void *pcm = calloc(subBufferSizeInFrames*music->stream.channels*music->stream.sampleSize/8, 1); + + int samplesCount = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts + + while (IsAudioBufferProcessed(music->stream)) + { + if ((music->samplesLeft/music->stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music->stream.channels; + else samplesCount = music->samplesLeft; + + // TODO: Really don't like ctxType thingy... + switch (music->ctxType) + { + case MUSIC_AUDIO_OGG: + { + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount); + + } break; + #if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: + { + // NOTE: Returns the number of samples to process + unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount, (short *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: + { + // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed + drmp3_read_pcm_frames_f32(&music->ctxMp3, samplesCount/music->stream.channels, (float *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: + { + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_16bit(music->ctxXm, (short *)pcm, samplesCount/2); + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 + jar_mod_fillbuffer(&music->ctxMod, (short *)pcm, samplesCount/2, 0); + } break; + #endif + default: break; + } + + + UpdateAudioStream(music->stream, pcm, samplesCount); + if ((music->ctxType == MUSIC_MODULE_XM) || (music->ctxType == MUSIC_MODULE_MOD)) + { + if (samplesCount > 1) music->samplesLeft -= samplesCount/2; + else music->samplesLeft -= samplesCount; + } + else music->samplesLeft -= samplesCount; + + if (music->samplesLeft <= 0) + { + streamEnding = true; + break; + } + } + + // Free allocated pcm data + free(pcm); + + // Reset audio stream for looping + if (streamEnding) + { + StopMusicStream(music); // Stop music (and reset) + + // Decrease loopCount to stop when required + if (music->loopCount > 0) + { + music->loopCount--; // Decrease loop count + PlayMusicStream(music); // Play again + } + else + { + if (music->loopCount == -1) PlayMusicStream(music); + } + } + else + { + // NOTE: In case window is minimized, music stream is stopped, + // just make sure to play again on window restore + if (IsMusicPlaying(music)) PlayMusicStream(music); + } +} + +// Check if any music is playing +bool IsMusicPlaying(Music music) +{ + if (music == NULL) return false; + else return IsAudioStreamPlaying(music->stream); +} + +// Set volume for music +void SetMusicVolume(Music music, float volume) +{ + if (music != NULL) SetAudioStreamVolume(music->stream, volume); +} + +// Set pitch for music +void SetMusicPitch(Music music, float pitch) +{ + if (music != NULL) SetAudioStreamPitch(music->stream, pitch); +} + +// Set music loop count (loop repeats) +// NOTE: If set to -1, means infinite loop +void SetMusicLoopCount(Music music, int count) +{ + if (music != NULL) music->loopCount = count; +} + +// Get music time length (in seconds) +float GetMusicTimeLength(Music music) +{ + float totalSeconds = 0.0f; + + if (music != NULL) totalSeconds = (float)music->totalSamples/(music->stream.sampleRate*music->stream.channels); + + return totalSeconds; +} + +// Get current music time played (in seconds) +float GetMusicTimePlayed(Music music) +{ + float secondsPlayed = 0.0f; + + if (music != NULL) + { + unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; + secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); + } + + return secondsPlayed; +} + +// Init audio stream (to stream audio pcm data) +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +{ + AudioStream stream = { 0 }; + + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; + + // Only mono and stereo channels are supported, more channels require AL_EXT_MCFORMATS extension + if ((channels > 0) && (channels < 3)) stream.channels = channels; + else + { + TraceLog(LOG_WARNING, "Init audio stream: Number of channels not supported: %i", channels); + stream.channels = 1; // Fallback to mono channel + } + + mal_format formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + + // The size of a streaming buffer must be at least double the size of a period. + unsigned int periodSize = device.bufferSizeInFrames/device.periods; + unsigned int subBufferSize = AUDIO_BUFFER_SIZE; + if (subBufferSize < periodSize) subBufferSize = periodSize; + + AudioBuffer *audioBuffer = CreateAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "InitAudioStream() : Failed to create audio buffer"); + return stream; + } + + audioBuffer->looping = true; // Always loop for streaming buffers. + stream.audioBuffer = audioBuffer; + + TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo"); + + return stream; +} + +// Close audio stream and free memory +void CloseAudioStream(AudioStream stream) +{ + DeleteAudioBuffer((AudioBuffer *)stream.audioBuffer); + + TraceLog(LOG_INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); +} + +// Update audio stream buffers with data +// NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue +// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioBufferProcessed() +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "UpdateAudioStream() : No audio buffer"); + return; + } + + if (audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]) + { + mal_uint32 subBufferToUpdate; + + if (audioBuffer->isSubBufferProcessed[0] && audioBuffer->isSubBufferProcessed[1]) + { + // Both buffers are available for updating. Update the first one and make sure the cursor is moved back to the front. + subBufferToUpdate = 0; + audioBuffer->frameCursorPos = 0; + } + else + { + // Just update whichever sub-buffer is processed. + subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0]) ? 0 : 1; + } + + mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; + unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); + + // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic. + if (subBufferSizeInFrames >= (mal_uint32)samplesCount/stream.channels) + { + mal_uint32 framesToWrite = subBufferSizeInFrames; + + if (framesToWrite > ((mal_uint32)samplesCount/stream.channels)) framesToWrite = (mal_uint32)samplesCount/stream.channels; + + mal_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros. + mal_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + + if (leftoverFrameCount > 0) + { + memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + } + + audioBuffer->isSubBufferProcessed[subBufferToUpdate] = false; + } + else + { + TraceLog(LOG_ERROR, "UpdateAudioStream() : Attempting to write too many frames to buffer"); + return; + } + } + else + { + TraceLog(LOG_ERROR, "Audio buffer not available for updating"); + return; + } +} + +// Check if any audio stream buffers requires refill +bool IsAudioBufferProcessed(AudioStream stream) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "IsAudioBufferProcessed() : No audio buffer"); + return false; + } + + return audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]; +} + +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + PlayAudioBuffer((AudioBuffer *)stream.audioBuffer); +} + +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + PauseAudioBuffer((AudioBuffer *)stream.audioBuffer); +} + +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ResumeAudioBuffer((AudioBuffer *)stream.audioBuffer); +} + +// Check if audio stream is playing. +bool IsAudioStreamPlaying(AudioStream stream) +{ + return IsAudioBufferPlaying((AudioBuffer *)stream.audioBuffer); +} + +// Stop audio stream +void StopAudioStream(AudioStream stream) +{ + StopAudioBuffer((AudioBuffer *)stream.audioBuffer); +} + +void SetAudioStreamVolume(AudioStream stream, float volume) +{ + SetAudioBufferVolume((AudioBuffer *)stream.audioBuffer, volume); +} + +void SetAudioStreamPitch(AudioStream stream, float pitch) +{ + SetAudioBufferPitch((AudioBuffer *)stream.audioBuffer, pitch); +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +#if defined(SUPPORT_FILEFORMAT_WAV) +// Load WAV file into Wave structure +static Wave LoadWAV(const char *fileName) +{ + // Basic WAV headers structs + typedef struct { + char chunkID[4]; + int chunkSize; + char format[4]; + } WAVRiffHeader; + + typedef struct { + char subChunkID[4]; + int subChunkSize; + short audioFormat; + short numChannels; + int sampleRate; + int byteRate; + short blockAlign; + short bitsPerSample; + } WAVFormat; + + typedef struct { + char subChunkID[4]; + int subChunkSize; + } WAVData; + + WAVRiffHeader wavRiffHeader; + WAVFormat wavFormat; + WAVData wavData; + + Wave wave = { 0 }; + FILE *wavFile; + + wavFile = fopen(fileName, "rb"); + + if (wavFile == NULL) + { + TraceLog(LOG_WARNING, "[%s] WAV file could not be opened", fileName); + wave.data = NULL; + } + else + { + // Read in the first chunk into the struct + fread(&wavRiffHeader, sizeof(WAVRiffHeader), 1, wavFile); + + // Check for RIFF and WAVE tags + if (strncmp(wavRiffHeader.chunkID, "RIFF", 4) || + strncmp(wavRiffHeader.format, "WAVE", 4)) + { + TraceLog(LOG_WARNING, "[%s] Invalid RIFF or WAVE Header", fileName); + } + else + { + // Read in the 2nd chunk for the wave info + fread(&wavFormat, sizeof(WAVFormat), 1, wavFile); + + // Check for fmt tag + if ((wavFormat.subChunkID[0] != 'f') || (wavFormat.subChunkID[1] != 'm') || + (wavFormat.subChunkID[2] != 't') || (wavFormat.subChunkID[3] != ' ')) + { + TraceLog(LOG_WARNING, "[%s] Invalid Wave format", fileName); + } + else + { + // Check for extra parameters; + if (wavFormat.subChunkSize > 16) fseek(wavFile, sizeof(short), SEEK_CUR); + + // Read in the the last byte of data before the sound file + fread(&wavData, sizeof(WAVData), 1, wavFile); + + // Check for data tag + if ((wavData.subChunkID[0] != 'd') || (wavData.subChunkID[1] != 'a') || + (wavData.subChunkID[2] != 't') || (wavData.subChunkID[3] != 'a')) + { + TraceLog(LOG_WARNING, "[%s] Invalid data header", fileName); + } + else + { + // Allocate memory for data + wave.data = malloc(wavData.subChunkSize); + + // Read in the sound data into the soundData variable + fread(wave.data, wavData.subChunkSize, 1, wavFile); + + // Store wave parameters + wave.sampleRate = wavFormat.sampleRate; + wave.sampleSize = wavFormat.bitsPerSample; + wave.channels = wavFormat.numChannels; + + // NOTE: Only support 8 bit, 16 bit and 32 bit sample sizes + if ((wave.sampleSize != 8) && (wave.sampleSize != 16) && (wave.sampleSize != 32)) + { + TraceLog(LOG_WARNING, "[%s] WAV sample size (%ibit) not supported, converted to 16bit", fileName, wave.sampleSize); + WaveFormat(&wave, wave.sampleRate, 16, wave.channels); + } + + // NOTE: Only support up to 2 channels (mono, stereo) + if (wave.channels > 2) + { + WaveFormat(&wave, wave.sampleRate, wave.sampleSize, 2); + TraceLog(LOG_WARNING, "[%s] WAV channels number (%i) not supported, converted to 2 channels", fileName, wave.channels); + } + + // NOTE: subChunkSize comes in bytes, we need to translate it to number of samples + wave.sampleCount = (wavData.subChunkSize/(wave.sampleSize/8))/wave.channels; + + TraceLog(LOG_INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + } + } + } + + fclose(wavFile); + } + + return wave; +} + +// Save wave data as WAV file +static int SaveWAV(Wave wave, const char *fileName) +{ + int success = 0; + int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; + + // Basic WAV headers structs + typedef struct { + char chunkID[4]; + int chunkSize; + char format[4]; + } RiffHeader; + + typedef struct { + char subChunkID[4]; + int subChunkSize; + short audioFormat; + short numChannels; + int sampleRate; + int byteRate; + short blockAlign; + short bitsPerSample; + } WaveFormat; + + typedef struct { + char subChunkID[4]; + int subChunkSize; + } WaveData; + + FILE *wavFile = fopen(fileName, "wb"); + + if (wavFile == NULL) TraceLog(LOG_WARNING, "[%s] WAV audio file could not be created", fileName); + else + { + RiffHeader riffHeader; + WaveFormat waveFormat; + WaveData waveData; + + // Fill structs with data + riffHeader.chunkID[0] = 'R'; + riffHeader.chunkID[1] = 'I'; + riffHeader.chunkID[2] = 'F'; + riffHeader.chunkID[3] = 'F'; + riffHeader.chunkSize = 44 - 4 + wave.sampleCount*wave.sampleSize/8; + riffHeader.format[0] = 'W'; + riffHeader.format[1] = 'A'; + riffHeader.format[2] = 'V'; + riffHeader.format[3] = 'E'; + + waveFormat.subChunkID[0] = 'f'; + waveFormat.subChunkID[1] = 'm'; + waveFormat.subChunkID[2] = 't'; + waveFormat.subChunkID[3] = ' '; + waveFormat.subChunkSize = 16; + waveFormat.audioFormat = 1; + waveFormat.numChannels = wave.channels; + waveFormat.sampleRate = wave.sampleRate; + waveFormat.byteRate = wave.sampleRate*wave.sampleSize/8; + waveFormat.blockAlign = wave.sampleSize/8; + waveFormat.bitsPerSample = wave.sampleSize; + + waveData.subChunkID[0] = 'd'; + waveData.subChunkID[1] = 'a'; + waveData.subChunkID[2] = 't'; + waveData.subChunkID[3] = 'a'; + waveData.subChunkSize = dataSize; + + success = fwrite(&riffHeader, sizeof(RiffHeader), 1, wavFile); + success = fwrite(&waveFormat, sizeof(WaveFormat), 1, wavFile); + success = fwrite(&waveData, sizeof(WaveData), 1, wavFile); + + success = fwrite(wave.data, dataSize, 1, wavFile); + + fclose(wavFile); + } + + // If all data has been written correctly to file, success = 1 + return success; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_OGG) +// Load OGG file into Wave structure +// NOTE: Using stb_vorbis library +static Wave LoadOGG(const char *fileName) +{ + Wave wave = { 0 }; + + stb_vorbis *oggFile = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (oggFile == NULL) TraceLog(LOG_WARNING, "[%s] OGG file could not be opened", fileName); + else + { + stb_vorbis_info info = stb_vorbis_get_info(oggFile); + + wave.sampleRate = info.sample_rate; + wave.sampleSize = 16; // 16 bit per sample (short) + wave.channels = info.channels; + wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggFile)*info.channels; // Independent by channel + + float totalSeconds = stb_vorbis_stream_length_in_seconds(oggFile); + if (totalSeconds > 10) TraceLog(LOG_WARNING, "[%s] Ogg audio length is larger than 10 seconds (%f), that's a big file in memory, consider music streaming", fileName, totalSeconds); + + wave.data = (short *)malloc(wave.sampleCount*wave.channels*sizeof(short)); + + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)wave.data, wave.sampleCount*wave.channels); + + TraceLog(LOG_DEBUG, "[%s] Samples obtained: %i", fileName, numSamplesOgg); + + TraceLog(LOG_INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + + stb_vorbis_close(oggFile); + } + + return wave; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) +// Load FLAC file into Wave structure +// NOTE: Using dr_flac library +static Wave LoadFLAC(const char *fileName) +{ + Wave wave; + + // Decode an entire FLAC file in one go + uint64_t totalSampleCount; + wave.data = drflac_open_and_decode_file_s16(fileName, &wave.channels, &wave.sampleRate, &totalSampleCount); + + wave.sampleCount = (unsigned int)totalSampleCount; + wave.sampleSize = 16; + + // NOTE: Only support up to 2 channels (mono, stereo) + if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] FLAC channels number (%i) not supported", fileName, wave.channels); + + if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] FLAC data could not be loaded", fileName); + else TraceLog(LOG_INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + + return wave; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) +// Load MP3 file into Wave structure +// NOTE: Using dr_mp3 library +static Wave LoadMP3(const char *fileName) +{ + Wave wave = { 0 }; + + // Decode an entire MP3 file in one go + uint64_t totalFrameCount = 0; + drmp3_config config = { 0 }; + wave.data = drmp3_open_file_and_read_f32(fileName, &config, &totalFrameCount); + + wave.channels = config.outputChannels; + wave.sampleRate = config.outputSampleRate; + wave.sampleCount = (int)totalFrameCount*wave.channels; + wave.sampleSize = 32; + + // NOTE: Only support up to 2 channels (mono, stereo) + if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] MP3 channels number (%i) not supported", fileName, wave.channels); + + if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] MP3 data could not be loaded", fileName); + else TraceLog(LOG_INFO, "[%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); + + return wave; +} +#endif + +// Some required functions for audio standalone module version +#if defined(RAUDIO_STANDALONE) +// Check file extension +bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt; + + if ((fileExt = strrchr(fileName, '.')) != NULL) + { + if (strcmp(fileExt, ext) == 0) result = true; + } + + return result; +} + +// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +void TraceLog(int msgType, const char *text, ...) +{ + va_list args; + va_start(args, text); + + switch (msgType) + { + case LOG_INFO: fprintf(stdout, "INFO: "); break; + case LOG_ERROR: fprintf(stdout, "ERROR: "); break; + case LOG_WARNING: fprintf(stdout, "WARNING: "); break; + case LOG_DEBUG: fprintf(stdout, "DEBUG: "); break; + default: break; + } + + vfprintf(stdout, text, args); + fprintf(stdout, "\n"); + + va_end(args); + + if (msgType == LOG_ERROR) exit(1); +} +#endif diff --git a/src/raudio.h b/src/raudio.h new file mode 100644 index 00000000..01c93741 --- /dev/null +++ b/src/raudio.h @@ -0,0 +1,182 @@ +/********************************************************************************************** +* +* raylib.audio - Basic funtionality to work with audio +* +* FEATURES: +* - Manage audio device (init/close) +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* - Manage mixing channels +* - Manage raw audio context +* +* LIMITATIONS (only OpenAL Soft): +* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) +* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) +* +* DEPENDENCIES: +* mini_al - Audio device/context management (https://github.com/dr-soft/mini_al) +* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) +* jar_xm - XM module file loading +* jar_mod - MOD audio file loading +* dr_flac - FLAC audio file loading +* +* *OpenAL Soft - Audio device management, still used on HTML5 and OSX platforms +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to mini_al library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef AUDIO_H +#define AUDIO_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for CAMERA_STANDALONE usage +//---------------------------------------------------------------------------------- +#ifndef __cplusplus +// Boolean type + #if !defined(_STDBOOL_H) + typedef enum { false, true } bool; + #define _STDBOOL_H + #endif +#endif + +// Wave type, defines audio wave data +typedef struct Wave { + unsigned int sampleCount; // Number of samples + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + void *data; // Buffer data pointer +} Wave; + +// Sound source type +typedef struct Sound { + void *audioBuffer; // Pointer to internal data used by the audio system + + unsigned int source; // Audio source id + unsigned int buffer; // Audio buffer id + int format; // Audio format specifier +} Sound; + +// Music type (file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct MusicData *Music; + +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + + void *audioBuffer; // Pointer to internal data used by the audio system. + + int format; // Audio format specifier + unsigned int source; // Audio source id + unsigned int buffers[2]; // Audio buffers (double buffering) +} AudioStream; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +void InitAudioDevice(void); // Initialize audio device and context +void CloseAudioDevice(void); // Close the audio device and context +bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +void SetMasterVolume(float volume); // Set master volume (listener) + +Wave LoadWave(const char *fileName); // Load wave data from file +Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels); // Load wave data from raw array data +Sound LoadSound(const char *fileName); // Load sound from file +Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data +void UnloadWave(Wave wave); // Unload wave data +void UnloadSound(Sound sound); // Unload sound +void PlaySound(Sound sound); // Play a sound +void PauseSound(Sound sound); // Pause a sound +void ResumeSound(Sound sound); // Resume a paused sound +void StopSound(Sound sound); // Stop playing a sound +bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +Wave WaveCopy(Wave wave); // Copy a wave to a new wave +void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +float *GetWaveData(Wave wave); // Get samples data from wave as a floats array +Music LoadMusicStream(const char *fileName); // Load music stream from file +void UnloadMusicStream(Music music); // Unload music stream +void PlayMusicStream(Music music); // Start music playing +void UpdateMusicStream(Music music); // Updates buffers for music streaming +void StopMusicStream(Music music); // Stop music playing +void PauseMusicStream(Music music); // Pause music playing +void ResumeMusicStream(Music music); // Resume playing paused music +bool IsMusicPlaying(Music music); // Check if music is playing +void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +void SetMusicLoopCount(Music music, int count); // Set music loop count (loop repeats) +float GetMusicTimeLength(Music music); // Get music time length (in seconds) +float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +AudioStream InitAudioStream(unsigned int sampleRate, + unsigned int sampleSize, + unsigned int channels); // Init audio stream (to stream raw audio pcm data) +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data +void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +bool IsAudioBufferProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +void PlayAudioStream(AudioStream stream); // Play audio stream +void PauseAudioStream(AudioStream stream); // Pause audio stream +void ResumeAudioStream(AudioStream stream); // Resume audio stream +bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +void StopAudioStream(AudioStream stream); // Stop audio stream +void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) + +#ifdef __cplusplus +} +#endif + +#endif // AUDIO_H \ No newline at end of file -- cgit v1.2.3