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 --- projects/Geany/raylib_compile_sources.bat | 4 ++-- projects/Notepad++/npes_saved_mingw.txt | Bin 7730 -> 7620 bytes projects/Notepad++/npes_saved_tcc.txt | Bin 7656 -> 7546 bytes projects/VS2015.UWP/raylib/raylib.vcxproj | 1 - projects/VS2015/raylib/raylib.vcxproj | 1 - projects/VS2017/raylib/raylib.vcxproj | 1 - 6 files changed, 2 insertions(+), 5 deletions(-) (limited to 'projects') diff --git a/projects/Geany/raylib_compile_sources.bat b/projects/Geany/raylib_compile_sources.bat index 105857ef..8a5c1e7b 100644 --- a/projects/Geany/raylib_compile_sources.bat +++ b/projects/Geany/raylib_compile_sources.bat @@ -24,13 +24,13 @@ 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 external/stb_vorbis.c -Wall -I. 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 stb_vorbis.o utils.o +ar rcs libraylib.a core.o rglfw.o shapes.o textures.o text.o models.o audio.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 7ed0662d..6857fa13 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 d3c4fb0e..6b8d6f2e 100644 Binary files a/projects/Notepad++/npes_saved_tcc.txt and b/projects/Notepad++/npes_saved_tcc.txt differ diff --git a/projects/VS2015.UWP/raylib/raylib.vcxproj b/projects/VS2015.UWP/raylib/raylib.vcxproj index d7fc2176..ed8eb80b 100644 --- a/projects/VS2015.UWP/raylib/raylib.vcxproj +++ b/projects/VS2015.UWP/raylib/raylib.vcxproj @@ -153,7 +153,6 @@ - diff --git a/projects/VS2015/raylib/raylib.vcxproj b/projects/VS2015/raylib/raylib.vcxproj index 3925c3e0..5d592146 100644 --- a/projects/VS2015/raylib/raylib.vcxproj +++ b/projects/VS2015/raylib/raylib.vcxproj @@ -235,7 +235,6 @@ - diff --git a/projects/VS2017/raylib/raylib.vcxproj b/projects/VS2017/raylib/raylib.vcxproj index 1e8f9491..df745e16 100644 --- a/projects/VS2017/raylib/raylib.vcxproj +++ b/projects/VS2017/raylib/raylib.vcxproj @@ -162,7 +162,6 @@ - -- cgit v1.2.3 From fa250e5d178610234d36b0ce742d0142865da313 Mon Sep 17 00:00:00 2001 From: Jens Pitkanen Date: Tue, 8 Jan 2019 18:31:50 +0200 Subject: Add project/scripts --- projects/scripts/README.md | 68 +++++++++++ projects/scripts/core_basic_window.c | 62 ++++++++++ projects/scripts/linux-build.sh | 158 ++++++++++++++++++++++++++ projects/scripts/osx-build.sh | 154 +++++++++++++++++++++++++ projects/scripts/windows-build.bat | 211 +++++++++++++++++++++++++++++++++++ 5 files changed, 653 insertions(+) create mode 100644 projects/scripts/README.md create mode 100644 projects/scripts/core_basic_window.c create mode 100755 projects/scripts/linux-build.sh create mode 100755 projects/scripts/osx-build.sh create mode 100644 projects/scripts/windows-build.bat (limited to 'projects') diff --git a/projects/scripts/README.md b/projects/scripts/README.md new file mode 100644 index 00000000..cdfa29bf --- /dev/null +++ b/projects/scripts/README.md @@ -0,0 +1,68 @@ +Here are dependency-less build scripts for raylib projects. + +## Dependencies +The scripts, as mentioned above, do not have dependencies. There's one +exception to this however, and that is Windows, because Windows +doesn't have a built-in C compiler. On Windows, you'll need to install +[Visual Studio][visual-studio] or the [build tools][vs-tools]. If you +didn't install them in the default location, write your changes around +line 101 of [`windows-build.bat`](windows-build.bat). + +## Script customization +First of all, the scripts have a few variables at the very top, which +are supposed to be configured for each project separately: +- `GAME_NAME` variable is used for the executable name. +- `SOURCES` is a list of .c source files, divided by spaces, which are + going to be compiled and linked with raylib to create the final + executable. You can use wildcards, so if you have all your .c files + in a directory called `src`, you can just set `SOURCES` to + `../../src/*.c`. Note: the paths should be either absolute, or + relative to `builds/platform`, hence `../../`. +- `RAYLIB_SRC` should point to the raylib/src directory. In this case, + it's `../../src`, but as with the `SOURCES`, if the path is + relative, it should be relative to `temp/debug`, so it's actually + `../../../../src`. + +## Compilation flags +- `-Os` (`/O1` with MSVC, `-O2` with clang\*) is used for release + builds, to save space. Since it's a good practice to make your games + run on the slowest possible systems, only a few games would benefit + from additional runtime performance on almost all systems. Other + flags: `-flto` (`/GL` and `/LTCG` for MSVC) in release builds, `-O0 + -g` (`/Od /Zi` for MSVC) in debug builds. +- `-Wall -Wextra -Wpedantic` (`/Wall` for MSVC) are used for warnings. + +\* Clang 7.0.1 seems to have problems compiling with `-flto` and `-Os` +enabled at the same time, so `-Os` is replaced with `-O2` for clang. + +## Command line arguments +The build scripts accept some flags, which can be given either one at +a time (`-d -c -r`) or in bunches (`-dcr`). Here's a description of +all of the flags. +- `-h` Describes all the flags, and a few example commands +- `-d` Faster builds that have debug symbols, and enable warnings +- `-u` Run upx\* on the executable after compilation (before -r) +- `-r` Run the executable after compilation +- `-c` Remove the temp/(debug|release) directory, ie. full recompile +- `-q` Suppress this script's informational prints +- `-qq` Suppress all prints, complete silence +- `-v` cl.exe normally prints out a lot of superficial information, as + well as the MSVC build environment activation scripts, but these are + mostly suppressed by default. If you do want to see everything, use + this flag. + +\* This is mostly here to make building simple "shipping" versions + easier, and it's a very small bit in the build scripts. The option + requires that you have upx installed and on your path, of course. + +#### Examples +| What the command does | Command | +|-------------------------------------------------------------|---------------------------| +| Build a release build, on Windows | `windows-build.bat` | +| Build a release build, full recompile, on Linux | `./linux-build.sh -c` | +| Build a debug build and run, on macOS | `./osx-build.sh -d -r` | +| Build in debug, run, don't print at all, on Linux with `sh` | `sh linux-build.sh -drqq` | + + +[visual-studio]: https://visualstudio.microsoft.com/downloads/#visual-studio-community-2017 +[vs-tools]: https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017 diff --git a/projects/scripts/core_basic_window.c b/projects/scripts/core_basic_window.c new file mode 100644 index 00000000..b30f05de --- /dev/null +++ b/projects/scripts/core_basic_window.c @@ -0,0 +1,62 @@ +/******************************************************************************************* +* +* raylib [core] example - Basic window +* +* Welcome to raylib! +* +* To test examples, just press F6 and execute raylib_compile_execute script +* Note that compiled executable is placed in the same folder as .c file +* +* You can find all basic examples on C:\raylib\raylib\examples folder or +* raylib official webpage: www.raylib.com +* +* Enjoy using raylib. :) +* +* This example has been created using raylib 1.0 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2013-2016 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - basic window"); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/projects/scripts/linux-build.sh b/projects/scripts/linux-build.sh new file mode 100755 index 00000000..851ea1a5 --- /dev/null +++ b/projects/scripts/linux-build.sh @@ -0,0 +1,158 @@ +#!/bin/sh +# Change your executable name here +GAME_NAME="game" + +# Set your sources here (relative to the ./builds/linux directory) +# Example with two source folders: +# SOURCES="../../src/*.c ../../src/submodule/*.c" +SOURCES="../../core_basic_window.c" + +# Set your raylib/src location here, relative to the ./temp/x directory +RAYLIB_SRC="../../../../src" + +# About this build script: it does many things, but in essence, it's +# very simple. It has 3 compiler invocations: building raylib (which +# is not done always, see logic by searching "Build raylib"), building +# src/*.c files, and linking together those two. Each invocation is +# wrapped in an if statement to make the -qq flag work, it's pretty +# verbose, sorry. + +# Get arguments +while getopts ":hdurcq" opt; do + case $opt in + h) + echo "Usage: ./linux-build.sh [-hdurcqq]" + echo " -h Show this information" + echo " -d Faster builds that have debug symbols, and enable warnings" + echo " -u Run upx* on the executable after compilation (before -r)" + echo " -r Run the executable after compilation" + echo " -c Remove the temp/(debug|release) directory, ie. full recompile" + echo " -q Suppress this script's informational prints" + echo " -qq Suppress all prints, complete silence (> /dev/null 2>&1)" + echo "" + echo "* This is mostly here to make building simple \"shipping\" versions" + echo " easier, and it's a very small bit in the build scripts. The option" + echo " requires that you have upx installed and on your path, of course." + echo "" + echo "Examples:" + echo " Build a release build: ./linux-build.sh" + echo " Build a release build, full recompile: ./linux-build.sh -c" + echo " Build a debug build and run: ./linux-build.sh -d -r" + echo " Build in debug, run, don't print at all: ./linux-build.sh -drqq" + exit 0 + ;; + d) + BUILD_DEBUG="1" + ;; + u) + UPX_IT="1" + ;; + r) + RUN_AFTER_BUILD="1" + ;; + c) + BUILD_ALL="1" + ;; + q) + if [ -n "$QUIET" ]; then + REALLY_QUIET="1" + else + QUIET="1" + fi + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# Set CC if it's not set already +if [ -z "$CC" ]; then + CC=cc +fi + +# Flags +OUTPUT_DIR="builds/linux" +COMPILATION_FLAGS="-std=c99 -Os -flto" +if [ "$CC" = "clang" ]; then + # Clang 7.0.1 fails to compile with -Os, possibly the same bug as this: + # https://www.mail-archive.com/llvm-bugs@lists.llvm.org/msg25771.html + COMPILATION_FLAGS="-std=c99 -O2 -flto" + [ -z "$QUIET" ] && echo "COMPILE-WARNING: \$CC is clang, using -O2 instead of -Os." +fi +FINAL_COMPILE_FLAGS="-s" +WARNING_FLAGS="-Wall -Wextra -Wpedantic" +LINK_FLAGS="-lm -ldl -lpthread -lX11 -lxcb -lGL -lGLX -lXext -lGLdispatch -lXau -lXdmcp" +# Debug changes to flags +if [ -n "$BUILD_DEBUG" ]; then + OUTPUT_DIR="builds-debug/linux" + COMPILATION_FLAGS="-std=c99 -O0 -g" + FINAL_COMPILE_FLAGS="" +fi + +# Display what we're doing +if [ -n "$BUILD_DEBUG" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Compiling in debug mode. ($COMPILATION_FLAGS $WARNING_FLAGS)" +else + [ -z "$QUIET" ] && echo "COMPILE-INFO: Compiling in release mode. ($COMPILATION_FLAGS $FINAL_COMPILE_FLAGS)" +fi + +# Create the raylib cache directory +ROOT_DIR=$(pwd) +TEMP_DIR="temp/release" +if [ -n "$BUILD_DEBUG" ]; then + TEMP_DIR="temp/debug" +fi +# If there's a -c flag, remove the cache +if [ -d "$TEMP_DIR" ] && [ -n "$BUILD_ALL" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Found cached raylib, rebuilding." + rm -r "$TEMP_DIR" +fi +# If temp directory doesn't exist, build raylib +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_INCLUDE_FLAGS="-I$RAYLIB_SRC -I$RAYLIB_SRC/external/glfw/include" + + if [ -n "$REALLY_QUIET" ]; then + $CC -c $RAYLIB_DEFINES $RAYLIB_INCLUDE_FLAGS $COMPILATION_FLAGS $RAYLIB_C_FILES > /dev/null 2>&1 + else + $CC -c $RAYLIB_DEFINES $RAYLIB_INCLUDE_FLAGS $COMPILATION_FLAGS $RAYLIB_C_FILES + fi + [ -z "$QUIET" ] && echo "COMPILE-INFO: Raylib compiled into object files in: $TEMP_DIR/" + cd $ROOT_DIR +fi + +# Build the actual game +mkdir -p $OUTPUT_DIR +cd $OUTPUT_DIR +[ -z "$QUIET" ] && echo "COMPILE-INFO: Compiling game code." +if [ -n "$REALLY_QUIET" ]; then + $CC -c -o main.o -I$RAYLIB_SRC $COMPILATION_FLAGS $WARNING_FLAGS $SOURCES > /dev/null 2>&1 + $CC -o $GAME_NAME $ROOT_DIR/$TEMP_DIR/*.o main.o $LINK_FLAGS > /dev/null 2>&1 +else + $CC -c -o main.o -I$RAYLIB_SRC $COMPILATION_FLAGS $WARNING_FLAGS $SOURCES + $CC -o $GAME_NAME $ROOT_DIR/$TEMP_DIR/*.o main.o $LINK_FLAGS +fi +rm main.o +[ -z "$QUIET" ] && echo "COMPILE-INFO: Game compiled into an executable in: $OUTPUT_DIR/" + +if [ -n "$UPX_IT" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Packing $GAME_NAME with upx." + upx $GAME_NAME > /dev/null 2>&1 +fi + +if [ -n "$RUN_AFTER_BUILD" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Running." + if [ -n "$REALLY_QUIET" ]; then + ./$GAME_NAME > /dev/null 2>&1 + else + ./$GAME_NAME + fi +fi +cd $ROOT_DIR + +[ -z "$QUIET" ] && echo "COMPILE-INFO: All done." diff --git a/projects/scripts/osx-build.sh b/projects/scripts/osx-build.sh new file mode 100755 index 00000000..36558f63 --- /dev/null +++ b/projects/scripts/osx-build.sh @@ -0,0 +1,154 @@ +#!/bin/sh +# Change your executable name here +GAME_NAME="game" + +# Set your sources here (relative to the ./builds/osx directory) +# Example with two source folders: +# SOURCES="../../src/*.c ../../src/submodule/*.c" +SOURCES="../../core_basic_window.c" + +# Set your raylib/src location here, relative to the ./temp/x directory +RAYLIB_SRC="../../../../src" + +# About this build script: it does many things, but in essence, it's +# very simple. It has 3 compiler invocations: building raylib (which +# is not done always, see logic by searching "Build raylib"), building +# src/*.c files, and linking together those two. Each invocation is +# wrapped in an if statement to make the -qq flag work, it's pretty +# verbose, sorry. + +# Get arguments +while getopts ":hdurcq" opt; do + case $opt in + h) + echo "Usage: ./osx-build.sh [-hdurcqq]" + echo " -h Show this information" + echo " -d Faster builds that have debug symbols, and enable warnings" + echo " -u Run upx* on the executable after compilation (before -r)" + echo " -r Run the executable after compilation" + echo " -c Remove the temp/(debug|release) directory, ie. full recompile" + echo " -q Suppress this script's informational prints" + echo " -qq Suppress all prints, complete silence (> /dev/null 2>&1)" + echo "" + echo "* This is mostly here to make building simple \"shipping\" versions" + echo " easier, and it's a very small bit in the build scripts. The option" + echo " requires that you have upx installed and on your path, of course." + echo "" + echo "Examples:" + echo " Build a release build: ./osx-build.sh" + echo " Build a release build, full recompile: ./osx-build.sh -c" + echo " Build a debug build and run: ./osx-build.sh -d -r" + echo " Build in debug, run, don't print at all: ./osx-build.sh -drqq" + exit 0 + ;; + d) + BUILD_DEBUG="1" + ;; + u) + UPX_IT="1" + ;; + r) + RUN_AFTER_BUILD="1" + ;; + c) + BUILD_ALL="1" + ;; + q) + if [ -n "$QUIET" ]; then + REALLY_QUIET="1" + else + QUIET="1" + fi + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# Set CC if it's not set already +if [ -z "$CC" ]; then + CC=cc +fi + +# Flags +OUTPUT_DIR="builds/osx" +COMPILATION_FLAGS="-std=c99 -O2 -flto" +FINAL_COMPILE_FLAGS="-s" +WARNING_FLAGS="-Wall -Wextra -Wpedantic" +LINK_FLAGS="-framework OpenGL -framework OpenAL -framework IOKit -framework CoreVideo -framework Cocoa" +# Debug changes to flags +if [ -n "$BUILD_DEBUG" ]; then + OUTPUT_DIR="builds-debug/osx" + COMPILATION_FLAGS="-std=c99 -O0 -g" + FINAL_COMPILE_FLAGS="" +fi + +# Display what we're doing +if [ -n "$BUILD_DEBUG" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Compiling in debug mode. ($COMPILATION_FLAGS $WARNING_FLAGS)" +else + [ -z "$QUIET" ] && echo "COMPILE-INFO: Compiling in release mode. ($COMPILATION_FLAGS $FINAL_COMPILE_FLAGS)" +fi + +# Create the raylib cache directory +ROOT_DIR=$(pwd) +TEMP_DIR="temp/release" +if [ -n "$BUILD_DEBUG" ]; then + TEMP_DIR="temp/debug" +fi +# If there's a -c flag, remove the cache +if [ -d "$TEMP_DIR" ] && [ -n "$BUILD_ALL" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Found cached raylib, rebuilding." + rm -r "$TEMP_DIR" +fi +# If temp directory doesn't exist, build raylib +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_INCLUDE_FLAGS="-I$RAYLIB_SRC -I$RAYLIB_SRC/external/glfw/include" + + if [ -n "$REALLY_QUIET" ]; then + $CC -c $RAYLIB_DEFINES $RAYLIB_INCLUDE_FLAGS $COMPILATION_FLAGS -x objective-c $RAYLIB_SRC/rglfw.c > /dev/null 2>&1 + $CC -c $RAYLIB_DEFINES $RAYLIB_INCLUDE_FLAGS $COMPILATION_FLAGS $RAYLIB_C_FILES > /dev/null 2>&1 + else + $CC -c $RAYLIB_DEFINES $RAYLIB_INCLUDE_FLAGS $COMPILATION_FLAGS -x objective-c $RAYLIB_SRC/rglfw.c + $CC -c $RAYLIB_DEFINES $RAYLIB_INCLUDE_FLAGS $COMPILATION_FLAGS $RAYLIB_C_FILES + fi + [ -z "$QUIET" ] && echo "COMPILE-INFO: Raylib compiled into object files in: $TEMP_DIR/" + cd $ROOT_DIR +fi + +# Build the actual game +mkdir -p $OUTPUT_DIR +cd $OUTPUT_DIR +[ -z "$QUIET" ] && echo "COMPILE-INFO: Compiling game code." +if [ -n "$REALLY_QUIET" ]; then + $CC -c -o main.o -I$RAYLIB_SRC $COMPILATION_FLAGS $WARNING_FLAGS $SOURCES > /dev/null 2>&1 + $CC -o $GAME_NAME $ROOT_DIR/$TEMP_DIR/*.o main.o $LINK_FLAGS > /dev/null 2>&1 +else + $CC -c -o main.o -I$RAYLIB_SRC $COMPILATION_FLAGS $WARNING_FLAGS $SOURCES + $CC -o $GAME_NAME $ROOT_DIR/$TEMP_DIR/*.o main.o $LINK_FLAGS +fi +rm main.o +[ -z "$QUIET" ] && echo "COMPILE-INFO: Game compiled into an executable in: $OUTPUT_DIR/" + +if [ -n "$UPX_IT" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Packing $GAME_NAME with upx." + upx $GAME_NAME > /dev/null 2>&1 +fi + +if [ -n "$RUN_AFTER_BUILD" ]; then + [ -z "$QUIET" ] && echo "COMPILE-INFO: Running." + if [ -n "$REALLY_QUIET" ]; then + ./$GAME_NAME > /dev/null 2>&1 + else + ./$GAME_NAME + fi +fi +cd $ROOT_DIR + +[ -z "$QUIET" ] && echo "COMPILE-INFO: All done." diff --git a/projects/scripts/windows-build.bat b/projects/scripts/windows-build.bat new file mode 100644 index 00000000..c4be4c89 --- /dev/null +++ b/projects/scripts/windows-build.bat @@ -0,0 +1,211 @@ +@echo off +REM Change your executable name here +set GAME_NAME=game.exe + +REM Set your sources here (relative to the builds\windows directory) +REM Example with two source folders: +REM set SOURCES=..\..\src\*.c ..\..\src\submodule\*.c +set SOURCES=..\..\core_basic_window.c + +REM Set your raylib/src location here, relative to the ./temp/x directory +set RAYLIB_SRC=..\..\..\..\src + +REM About this build script: it does many things, but in essence, it's +REM very simple. It has 3 compiler invocations: building raylib (which +REM is not done always, see logic by searching "Build raylib"), building +REM src/*.c files, and linking together those two. Each invocation is +REM wrapped in an if statement to make the -qq flag work, it's pretty +REM verbose, sorry. + +REM To skip to the actual building part of the script, search for ":BUILD" + +REM For the ! variable notation +setlocal EnableDelayedExpansion +REM For shifting, which the command line argument parsing needs +setlocal EnableExtensions + + +:ARG_LOOP +set ARG=%1 +if "!ARG!" == "" ( goto PREPARE ) +IF NOT "x!ARG!" == "x!ARG:h=!" ( + goto HELP +) +IF NOT "x!ARG!" == "x!ARG:d=!" ( + set BUILD_DEBUG=1 +) +IF NOT "x!ARG!" == "x!ARG:u=!" ( + set UPX_IT=1 +) +IF NOT "x!ARG!" == "x!ARG:r=!" ( + set RUN_AFTER_BUILD=1 +) +IF NOT "x!ARG!" == "x!ARG:c=!" ( + set BUILD_ALL=1 +) +IF NOT "x!ARG!" == "x!ARG:qq=!" ( + set QUIET=1 + set REALLY_QUIET=1 +) ELSE IF NOT "x!ARG!" == "x!ARG:q=!" ( + IF DEFINED QUIET ( + set REALLY_QUIET=1 + ) ELSE ( + set QUIET=1 + ) +) +IF NOT "x!ARG!" == "x!ARG:v=!" ( + set VERBOSE=1 +) +IF NOT "%1" == "" ( + shift /1 + goto ARG_LOOP +) + + +:HELP +echo Usage: windows-build.bat [-hdurcqqv] +echo -h Show this information +echo -d Faster builds that have debug symbols, and enable warnings +echo -u Run upx* on the executable after compilation (before -r) +echo -r Run the executable after compilation +echo -c Remove the temp\{debug,release} directory, ie. full recompile +echo -q Suppress this script's informational prints +echo -qq Suppress all prints, complete silence +echo -v cl.exe normally prints out a lot of superficial information, as +echo well as the MSVC build environment activation scripts, but these are +echo mostly suppressed by default. If you do want to see everything, use +echo this flag. +echo. +echo * This is mostly here to make building simple "shipping" versions +echo easier, and it's a very small bit in the build scripts. The option +echo requires that you have upx installed and on your path, of course. +echo. +echo Examples: +echo Build a release build: windows-build.bat +echo Build a release build, full recompile: windows-build.bat -c +echo Build a debug build and run: windows-build.bat -d -r +echo Build in debug, run, don't print at all: windows-build.bat -drqq +exit /B + + +:PREPARE +REM Activate the msvc build environment +IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" ( + set VC_INIT="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" +) ELSE IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" ( + set VC_INIT="C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" +) ELSE IF EXIST "C:\Program Files (x86)\Microsoft Visual C++ Build Tools\vcbuildtools.bat" ( + set VC_INIT="C:\Program Files (x86)\Microsoft Visual C++ Build Tools\vcbuildtools.bat" +) ELSE ( + REM Initialize your vc environment here if the defaults don't work + REM set VC_INIT="C:\your\path\here\vcvarsall.bat" + REM And then remove/comment out the following two lines + echo "Couldn't find vcvarsall.bat or vcbuildtools.bat, please set it manually." + exit /B +) +IF DEFINED VERBOSE ( + call !VC_INIT! x86 +) ELSE ( + call !VC_INIT! x86 > NUL 2>&1 +) + + +:BUILD +REM Flags +set OUTPUT_FLAG=/Fe: "!GAME_NAME!" +set COMPILATION_FLAGS=/O1 /GL +set WARNING_FLAGS= +set SUBSYSTEM_FLAGS=/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup +set LINK_FLAGS=/link /LTCG kernel32.lib user32.lib shell32.lib winmm.lib gdi32.lib opengl32.lib +set OUTPUT_DIR=builds\windows +REM Debug changes to flags +IF DEFINED BUILD_DEBUG ( + set OUTPUT_FLAG=/Fe: "!GAME_NAME!" + set COMPILATION_FLAGS=/Od /Zi + set WARNING_FLAGS=/Wall + set SUBSYSTEM_FLAGS= + set LINK_FLAGS=/link kernel32.lib user32.lib shell32.lib winmm.lib gdi32.lib opengl32.lib + set OUTPUT_DIR=builds-debug\windows +) +IF NOT DEFINED VERBOSE ( + set VERBOSITY_FLAG=/nologo +) + +REM Display what we're doing +IF DEFINED BUILD_DEBUG ( + IF NOT DEFINED QUIET echo COMPILE-INFO: Compiling in debug mode, flags: !COMPILATION_FLAGS! !WARNING_FLAGS! +) ELSE ( + IF NOT DEFINED QUIET echo COMPILE-INFO: Compiling in release mode, flags: !COMPILATION_FLAGS! /link /LTCG +) + +REM Create the temp directory for raylib +set "ROOT_DIR=%CD%" +set "TEMP_DIR=temp\release" +IF DEFINED BUILD_DEBUG ( + set "TEMP_DIR=temp\debug" +) + +IF DEFINED BUILD_ALL ( + IF EXIST !TEMP_DIR!\ ( + IF NOT DEFINED QUIET echo COMPILE-INFO: Found cached raylib, rebuilding. + del /Q !TEMP_DIR! + rmdir !TEMP_DIR! + ) +) + +REM Build raylib if it hasn't been cached in TEMP_DIR +IF NOT EXIST !TEMP_DIR!\ ( + mkdir !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_INCLUDE_FLAGS=/I"!RAYLIB_SRC!" /I"!RAYLIB_SRC!\external\glfw\include" + + IF DEFINED REALLY_QUIET ( + cl.exe /w /c !RAYLIB_DEFINES! !RAYLIB_INCLUDE_FLAGS! !COMPILATION_FLAGS! !RAYLIB_C_FILES! > NUL 2>&1 + ) ELSE ( + cl.exe /w /c !VERBOSITY_FLAG! !RAYLIB_DEFINES! !RAYLIB_INCLUDE_FLAGS! !COMPILATION_FLAGS! !RAYLIB_C_FILES! + ) + IF NOT DEFINED QUIET echo COMPILE-INFO: Raylib compiled into object files in: !TEMP_DIR!\ + + REM Out of the temp directory + cd !ROOT_DIR! +) + +REM Move to the build directory +IF NOT EXIST !OUTPUT_DIR! mkdir !OUTPUT_DIR! +cd !OUTPUT_DIR! + +REM Build the actual game +IF NOT DEFINED QUIET echo COMPILE-INFO: Compiling game code. +IF DEFINED REALLY_QUIET ( + cl.exe !VERBOSITY_FLAG! /Fo"main.obj" !COMPILATION_FLAGS! !WARNING_FLAGS! /c /I"!RAYLIB_SRC!" !SOURCES! > NUL 2>&1 + cl.exe !VERBOSITY_FLAG! !OUTPUT_FLAG! "!ROOT_DIR!\!TEMP_DIR!\*.obj" main.obj !LINK_FLAGS! !SUBSYSTEM_FLAGS! > NUL 2>&1 +) ELSE ( + cl.exe !VERBOSITY_FLAG! /Fo"main.obj" !COMPILATION_FLAGS! !WARNING_FLAGS! /c /I"!RAYLIB_SRC!" !SOURCES! + cl.exe !VERBOSITY_FLAG! !OUTPUT_FLAG! "!ROOT_DIR!\!TEMP_DIR!\*.obj" main.obj !LINK_FLAGS! !SUBSYSTEM_FLAGS! +) +del main.obj +IF NOT DEFINED QUIET echo COMPILE-INFO: Game compiled into an executable in: !OUTPUT_DIR!\ + +REM Run upx +IF DEFINED UPX_IT ( + IF NOT DEFINED QUIET echo COMPILE-INFO: Packing !GAME_NAME! with upx. + upx !GAME_NAME! > NUL 2>&1 +) + +REM Finally, run the produced executable +IF DEFINED RUN_AFTER_BUILD ( + IF NOT DEFINED QUIET echo COMPILE-INFO: Running. + IF DEFINED REALLY_QUIET ( + !GAME_NAME! > NUL 2>&1 + ) ELSE ( + !GAME_NAME! + ) +) + +REM Back to development directory +cd !ROOT_DIR! + +IF NOT DEFINED QUIET echo COMPILE-INFO: All done. -- cgit v1.2.3 From 73597332b6fb1fb934c703c32f287bde1f5b3292 Mon Sep 17 00:00:00 2001 From: Marco Lizza Date: Wed, 9 Jan 2019 16:18:00 +0100 Subject: Adding uniform array support for shaders. --- .../Notepad++/raylib_npp_parser/raylib_to_parse.h | 2 ++ src/raylib.h | 2 ++ src/rlgl.h | 42 ++++++++++++++-------- 3 files changed, 32 insertions(+), 14 deletions(-) (limited to 'projects') diff --git a/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h b/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h index 5dfb5dfe..04a6d0d3 100644 --- a/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h +++ b/projects/Notepad++/raylib_npp_parser/raylib_to_parse.h @@ -376,6 +376,8 @@ RLAPI Texture2D GetTextureDefault(void); // Get 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 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/raylib.h b/src/raylib.h index 430e66db..6c47ae37 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1213,6 +1213,8 @@ RLAPI Texture2D GetTextureDefault(void); // Get 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 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 e1b9a98b..1eac7438 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -478,6 +478,8 @@ Texture2D GetTextureDefault(void); // Get defau 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 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) @@ -2897,35 +2899,47 @@ int GetShaderLocation(Shader shader, const char *uniformName) return location; } -// Set shader uniform value (float) +// 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) +{ + SetShaderValueArrayi(shader, uniformLoc, value, size, 1); +} + +// Set shader uniform value (array of float/vec2/vec3/vec4) +void SetShaderValueArray(Shader shader, int uniformLoc, const float *value, int size, int count) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glUseProgram(shader.id); - if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float - else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2 - else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3 - else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4 - else TraceLog(LOG_WARNING, "Shader value float array size not supported"); + 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)"); //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set #endif } -// Set shader uniform value (int) -void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size) +// 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, 1, value); // Shader uniform type: int - else if (size == 2) glUniform2iv(uniformLoc, 1, value); // Shader uniform type: ivec2 - else if (size == 3) glUniform3iv(uniformLoc, 1, value); // Shader uniform type: ivec3 - else if (size == 4) glUniform4iv(uniformLoc, 1, value); // Shader uniform type: ivec4 - else TraceLog(LOG_WARNING, "Shader value int array size not supported"); + 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); + //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set #endif } -- cgit v1.2.3 From 7c4a0f963dfc6f089337158ea6b78b84f4022368 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 10 Jan 2019 11:24:34 +0100 Subject: Reviewed path --- projects/VSCode/.vscode/tasks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'projects') diff --git a/projects/VSCode/.vscode/tasks.json b/projects/VSCode/.vscode/tasks.json index 93e4d396..0d8be4cf 100644 --- a/projects/VSCode/.vscode/tasks.json +++ b/projects/VSCode/.vscode/tasks.json @@ -19,7 +19,7 @@ }, "osx": { "args": [ - "RAYLIB_PATH=/Users/murray/work/ray/raylib" + "RAYLIB_PATH=/raylib" ], }, "group": { @@ -42,7 +42,7 @@ }, "osx": { "args": [ - "RAYLIB_PATH=/Users/murray/work/ray/raylib", + "RAYLIB_PATH=/raylib", ], }, "group": "build" -- 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 'projects') 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