2014-09-16 22:51:31 +02:00
/**********************************************************************************************
2013-11-18 23:38:44 +01:00
*
2019-03-12 11:54:45 +01:00
* raudio - A simple and easy - to - use audio library based on miniaudio
2013-11-18 23:38:44 +01:00
*
2017-03-20 20:34:44 +01:00
* FEATURES :
* - Manage audio device ( init / close )
2020-02-03 18:31:30 +01:00
* - Manage raw audio context
* - Manage mixing channels
2017-03-20 20:34:44 +01:00
* - Load and unload audio files
* - Format wave data ( sample rate , size , channels )
* - Play / Stop / Pause / Resume loaded audio
2017-02-16 00:50:02 +01:00
*
* CONFIGURATION :
2018-04-07 22:29:53 +02:00
*
2019-01-10 16:32:40 +01:00
* # define RAUDIO_STANDALONE
2017-03-19 12:52:58 +01:00
* Define to use the module as standalone library ( independently of raylib ) .
2017-02-16 00:50:02 +01:00
* Required types and functions are defined in the same module .
*
2017-03-26 22:49:01 +02:00
* # define SUPPORT_FILEFORMAT_WAV
2017-02-16 00:50:02 +01:00
* # define SUPPORT_FILEFORMAT_OGG
* # define SUPPORT_FILEFORMAT_XM
* # define SUPPORT_FILEFORMAT_MOD
* # define SUPPORT_FILEFORMAT_FLAC
2018-05-04 16:59:48 +02:00
* # define SUPPORT_FILEFORMAT_MP3
2018-04-07 22:29:53 +02:00
* Selected desired fileformats to be supported for loading . Some of those formats are
2017-02-16 00:50:02 +01:00
* supported by default , to remove support , just comment unrequired # define in this module
*
* DEPENDENCIES :
2019-03-12 11:54:45 +01:00
* miniaudio . h - Audio device management lib ( https : //github.com/dr-soft/miniaudio)
2019-02-12 12:18:01 +01:00
* stb_vorbis . h - Ogg audio files loading ( http : //www.nothings.org/stb_vorbis/)
* dr_mp3 . h - MP3 audio file loading ( https : //github.com/mackron/dr_libs)
* dr_flac . h - FLAC audio file loading ( https : //github.com/mackron/dr_libs)
* jar_xm . h - XM module file loading
* jar_mod . h - MOD audio file loading
2017-12-20 12:34:18 +01:00
*
2017-02-16 00:50:02 +01:00
* CONTRIBUTORS :
2017-12-20 12:34:18 +01:00
* David Reid ( github : @ mackron ) ( Nov . 2017 ) :
2019-03-12 11:54:45 +01:00
* - Complete port to miniaudio library
2017-12-20 12:34:18 +01:00
*
* Joshua Reisenauer ( github : @ kd7tck ) ( 2015 )
2017-03-19 12:52:58 +01:00
* - XM audio module support ( jar_xm )
* - MOD audio module support ( jar_mod )
* - Mixing channels support
* - Raw audio context support
2017-02-16 00:50:02 +01:00
*
2016-07-15 18:16:34 +02:00
*
2017-02-16 00:50:02 +01:00
* LICENSE : zlib / libpng
2016-11-16 18:46:13 +01:00
*
2020-01-05 20:01:54 +01:00
* Copyright ( c ) 2013 - 2020 Ramon Santamaria ( @ raysan5 )
2014-09-03 16:51:28 +02:00
*
* This software is provided " as-is " , without any express or implied warranty . In no event
2013-11-23 13:30:54 +01:00
* will the authors be held liable for any damages arising from the use of this software .
2013-11-18 23:38:44 +01:00
*
2014-09-03 16:51:28 +02:00
* Permission is granted to anyone to use this software for any purpose , including commercial
2013-11-23 13:30:54 +01:00
* applications , and to alter it and redistribute it freely , subject to the following restrictions :
2013-11-18 23:38:44 +01:00
*
2014-09-03 16:51:28 +02:00
* 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
2013-11-23 13:30:54 +01:00
* in the product documentation would be appreciated but is not required .
2013-11-18 23:38:44 +01:00
*
2013-11-23 13:30:54 +01:00
* 2. Altered source versions must be plainly marked as such , and must not be misrepresented
* as being the original software .
2013-11-18 23:38:44 +01:00
*
2013-11-23 13:30:54 +01:00
* 3. This notice may not be removed or altered from any source distribution .
2013-11-18 23:38:44 +01:00
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-01-10 16:32:40 +01:00
# if defined(RAUDIO_STANDALONE)
# include "raudio.h"
2016-11-18 13:39:57 +01:00
# include <stdarg.h> // Required for: va_list, va_start(), vfprintf(), va_end()
2015-07-29 21:41:19 +02:00
# else
2018-05-17 00:04:58 +02:00
# include "raylib.h" // Declares module functions
2019-07-23 22:21:01 +02:00
2018-12-18 00:20:08 +01:00
// Check if config flags have been externally provided on compilation line
# if !defined(EXTERNAL_CONFIG_FLAGS)
# include "config.h" // Defines module configuration flags
# endif
2017-04-21 00:08:00 +02:00
# include "utils.h" // Required for: fopen() Android mapping
2015-07-29 21:41:19 +02:00
# endif
2013-11-18 23:38:44 +01:00
2020-02-04 22:43:31 +10:00
# if defined(_WIN32)
// @raysan5: To avoid conflicting windows.h symbols with raylib, so flags are defined
// WARNING: Those flags avoid inclusion of some Win32 headers that could be required
// by user at some point and won't be included...
//-------------------------------------------------------------------------------------
// If defined, the following flags inhibit definition of the indicated items.
# define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_
# define NOVIRTUALKEYCODES // VK_*
# define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_*
# define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_*
# define NOSYSMETRICS // SM_*
# define NOMENUS // MF_*
# define NOICONS // IDI_*
# define NOKEYSTATES // MK_*
# define NOSYSCOMMANDS // SC_*
# define NORASTEROPS // Binary and Tertiary raster ops
# define NOSHOWWINDOW // SW_*
# define OEMRESOURCE // OEM Resource values
# define NOATOM // Atom Manager routines
# define NOCLIPBOARD // Clipboard routines
# define NOCOLOR // Screen colors
# define NOCTLMGR // Control and Dialog routines
# define NODRAWTEXT // DrawText() and DT_*
# define NOGDI // All GDI defines and routines
# define NOKERNEL // All KERNEL defines and routines
# define NOUSER // All USER defines and routines
//#define NONLS // All NLS defines and routines
# define NOMB // MB_* and MessageBox()
# define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines
# define NOMETAFILE // typedef METAFILEPICT
# define NOMINMAX // Macros min(a,b) and max(a,b)
# define NOMSG // typedef MSG and associated routines
# define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_*
# define NOSCROLL // SB_* and scrolling routines
# define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc.
# define NOSOUND // Sound driver routines
# define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines
# define NOWH // SetWindowsHook and WH_*
# define NOWINOFFSETS // GWL_*, GCL_*, associated routines
# define NOCOMM // COMM driver routines
# define NOKANJI // Kanji support stuff.
# define NOHELP // Help engine interface.
# define NOPROFILER // Profiler interface.
# define NODEFERWINDOWPOS // DeferWindowPos routines
# define NOMCX // Modem Configuration Extensions
// Type required before windows.h inclusion
typedef struct tagMSG * LPMSG ;
# include <windows.h>
// Type required by some unused function...
typedef struct tagBITMAPINFOHEADER {
DWORD biSize ;
LONG biWidth ;
LONG biHeight ;
WORD biPlanes ;
WORD biBitCount ;
DWORD biCompression ;
DWORD biSizeImage ;
LONG biXPelsPerMeter ;
LONG biYPelsPerMeter ;
DWORD biClrUsed ;
DWORD biClrImportant ;
} BITMAPINFOHEADER , * PBITMAPINFOHEADER ;
# include <objbase.h>
# include <mmreg.h>
# include <mmsystem.h>
// @raysan5: Some required types defined for MSVC/TinyC compiler
# if defined(_MSC_VER) || defined(__TINYC__)
# include "propidl.h"
# endif
# endif
2019-03-12 11:54:45 +01:00
# define MA_NO_JACK
# define MINIAUDIO_IMPLEMENTATION
# include "external/miniaudio.h" // miniaudio library
2019-02-12 12:18:01 +01:00
# undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro
2013-11-18 23:38:44 +01:00
2016-06-02 17:12:31 +02:00
# include <stdlib.h> // Required for: malloc(), free()
# include <stdio.h> // Required for: FILE, fopen(), fclose(), fread()
2013-11-18 23:38:44 +01:00
2020-02-04 16:55:24 +01:00
# if defined(RAUDIO_STANDALONE)
# include <string.h> // Required for: strcmp() [Used in IsFileExtension()]
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2018-12-21 00:17:44 +01:00
# define STB_VORBIS_IMPLEMENTATION
2017-03-26 22:49:01 +02:00
# include "external/stb_vorbis.h" // OGG loading functions
# endif
2016-04-25 18:40:19 -07:00
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_XM)
# define JAR_XM_IMPLEMENTATION
# include "external/jar_xm.h" // XM loading functions
# endif
2013-11-18 23:38:44 +01:00
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_MOD)
# define JAR_MOD_IMPLEMENTATION
# include "external/jar_mod.h" // MOD loading functions
# endif
2016-06-01 20:09:00 -07:00
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
# define DR_FLAC_IMPLEMENTATION
# define DR_FLAC_NO_WIN32_IO
# include "external/dr_flac.h" // FLAC loading functions
# endif
2016-10-10 18:22:55 +02:00
2018-05-04 16:59:48 +02:00
# if defined(SUPPORT_FILEFORMAT_MP3)
# define DR_MP3_IMPLEMENTATION
2018-11-23 11:58:45 +01:00
# include "external/dr_mp3.h" // MP3 loading functions
2018-05-04 16:59:48 +02:00
# endif
2018-10-16 10:53:01 +02:00
# if defined(_MSC_VER)
2016-07-29 13:17:50 +02:00
# undef bool
# endif
2013-11-18 23:38:44 +01:00
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
2016-12-25 01:58:56 +01:00
// NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number
2019-07-23 22:21:01 +02:00
// After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a
// standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough
2016-08-01 21:37:45 +02:00
// In case of music-stalls, just increase this number
2020-02-03 18:31:30 +01:00
# if !defined(AUDIO_BUFFER_SIZE)
# define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb)
# endif
# define DEVICE_FORMAT ma_format_f32
# define DEVICE_CHANNELS 2
# define DEVICE_SAMPLE_RATE 44100
# define MAX_AUDIO_BUFFER_POOL_CHANNELS 16
2013-11-18 23:38:44 +01:00
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
2014-04-19 16:36:49 +02:00
2019-07-23 22:21:01 +02:00
// Music context type
// NOTE: Depends on data structure provided by the library
// in charge of reading the different file types
2018-11-06 15:10:50 +01:00
typedef enum {
2019-07-23 22:21:01 +02:00
MUSIC_AUDIO_WAV = 0 ,
MUSIC_AUDIO_OGG ,
2018-11-06 15:10:50 +01:00
MUSIC_AUDIO_FLAC ,
MUSIC_AUDIO_MP3 ,
MUSIC_MODULE_XM ,
MUSIC_MODULE_MOD
2017-12-20 00:34:31 +01:00
} MusicContextType ;
2016-08-01 12:49:17 +02:00
2019-01-10 16:32:40 +01:00
# if defined(RAUDIO_STANDALONE)
2018-11-06 15:10:50 +01:00
typedef enum {
2019-01-20 22:22:21 +01:00
LOG_ALL ,
LOG_TRACE ,
2018-11-06 15:10:50 +01:00
LOG_DEBUG ,
2019-01-20 22:22:21 +01:00
LOG_INFO ,
2019-01-21 10:02:53 +01:00
LOG_WARNING ,
2019-01-20 22:22:21 +01:00
LOG_ERROR ,
LOG_FATAL ,
LOG_NONE
2017-12-20 00:34:31 +01:00
} TraceLogType ;
2015-07-31 12:31:39 +02:00
# endif
2020-02-03 19:26:28 +01:00
// NOTE: Different logic is used when feeding data to the playback device
2020-02-03 18:31:30 +01:00
// depending on whether or not data is streamed (Music vs Sound)
2020-02-03 19:26:28 +01:00
typedef enum {
AUDIO_BUFFER_USAGE_STATIC = 0 ,
2020-02-03 18:31:30 +01:00
AUDIO_BUFFER_USAGE_STREAM
} AudioBufferUsage ;
// Audio buffer structure
struct rAudioBuffer {
2020-02-04 22:43:31 +10:00
ma_data_converter converter ; // Audio data converter
2020-02-03 18:31:30 +01:00
float volume ; // Audio buffer volume
float pitch ; // Audio buffer pitch
bool playing ; // Audio buffer state: AUDIO_PLAYING
bool paused ; // Audio buffer state: AUDIO_PAUSED
bool looping ; // Audio buffer looping, always true for AudioStreams
int usage ; // Audio buffer usage mode: STATIC or STREAM
bool isSubBufferProcessed [ 2 ] ; // SubBuffer processed (virtual double buffer)
unsigned int sizeInFrames ; // Total buffer size in frames
unsigned int frameCursorPos ; // Frame cursor position
2020-02-04 22:43:31 +10:00
unsigned int totalFramesProcessed ; // Total frames processed in this buffer (required for play timing)
2020-02-03 18:31:30 +01:00
unsigned char * data ; // Data buffer, on music stream keeps filling
rAudioBuffer * next ; // Next audio buffer on the list
rAudioBuffer * prev ; // Previous audio buffer on the list
} ;
# define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision
// Audio data context
typedef struct AudioData {
struct {
ma_context context ; // miniaudio context data
ma_device device ; // miniaudio device
ma_mutex lock ; // miniaudio mutex lock
bool isReady ; // Check if audio device is ready
} System ;
struct {
AudioBuffer * first ; // Pointer to first AudioBuffer in the list
AudioBuffer * last ; // Pointer to last AudioBuffer in the list
} Buffer ;
struct {
AudioBuffer * pool [ MAX_AUDIO_BUFFER_POOL_CHANNELS ] ; // Multichannel AudioBuffer pointers pool
unsigned int poolCounter ; // AudioBuffer pointers pool counter
unsigned int channels [ MAX_AUDIO_BUFFER_POOL_CHANNELS ] ; // AudioBuffer pool channels
} MultiChannel ;
} AudioData ;
2013-11-18 23:38:44 +01:00
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
2020-02-03 18:31:30 +01:00
static AudioData AUDIO = { 0 } ; // Global CORE context
2016-06-02 17:12:31 +02:00
2013-11-18 23:38:44 +01:00
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
2020-02-03 18:31:30 +01:00
static void OnLog ( ma_context * pContext , ma_device * pDevice , ma_uint32 logLevel , const char * message ) ;
static void OnSendAudioDataToDevice ( ma_device * pDevice , void * pFramesOut , const void * pFramesInput , ma_uint32 frameCount ) ;
static void MixAudioFrames ( float * framesOut , const float * framesIn , ma_uint32 frameCount , float localVolume ) ;
static void InitAudioBufferPool ( void ) ; // Initialise the multichannel buffer pool
static void CloseAudioBufferPool ( void ) ; // Close the audio buffers pool
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_WAV)
2018-10-29 16:18:06 +01:00
static Wave LoadWAV ( const char * fileName ) ; // Load WAV file
static int SaveWAV ( Wave wave , const char * fileName ) ; // Save wave data as WAV file
2017-03-26 22:49:01 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_OGG)
2018-11-23 11:58:45 +01:00
static Wave LoadOGG ( const char * fileName ) ; // Load OGG file
2017-03-26 22:49:01 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_FLAC)
2018-11-23 11:58:45 +01:00
static Wave LoadFLAC ( const char * fileName ) ; // Load FLAC file
2017-03-26 22:49:01 +02:00
# endif
2018-09-19 15:57:46 +02:00
# if defined(SUPPORT_FILEFORMAT_MP3)
2018-11-23 11:58:45 +01:00
static Wave LoadMP3 ( const char * fileName ) ; // Load MP3 file
2018-09-19 15:57:46 +02:00
# endif
2014-04-09 20:25:26 +02:00
2019-01-10 16:32:40 +01:00
# if defined(RAUDIO_STANDALONE)
2020-02-03 18:31:30 +01:00
bool IsFileExtension ( const char * fileName , const char * ext ) ; // Check file extension
2020-02-03 19:13:24 +01:00
void TRACELOG ( int msgType , const char * text , . . . ) ; // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG)
2015-07-31 12:31:39 +02:00
# endif
2013-11-18 23:38:44 +01:00
//----------------------------------------------------------------------------------
2018-02-11 01:12:16 +01:00
// AudioBuffer management functions declaration
// NOTE: Those functions are not exposed by raylib... for the moment
2020-02-03 18:31:30 +01:00
//----------------------------------------------------------------------------------
AudioBuffer * InitAudioBuffer ( ma_format format , ma_uint32 channels , ma_uint32 sampleRate , ma_uint32 sizeInFrames , int usage ) ;
2019-07-24 22:37:24 +02:00
void CloseAudioBuffer ( AudioBuffer * buffer ) ;
bool IsAudioBufferPlaying ( AudioBuffer * buffer ) ;
void PlayAudioBuffer ( AudioBuffer * buffer ) ;
void StopAudioBuffer ( AudioBuffer * buffer ) ;
void PauseAudioBuffer ( AudioBuffer * buffer ) ;
void ResumeAudioBuffer ( AudioBuffer * buffer ) ;
void SetAudioBufferVolume ( AudioBuffer * buffer , float volume ) ;
void SetAudioBufferPitch ( AudioBuffer * buffer , float pitch ) ;
void TrackAudioBuffer ( AudioBuffer * buffer ) ;
void UntrackAudioBuffer ( AudioBuffer * buffer ) ;
2017-11-15 22:04:23 +10:00
2018-02-11 01:12:16 +01:00
//----------------------------------------------------------------------------------
// Module Functions Definition - Audio Device initialization and Closing
//----------------------------------------------------------------------------------
2016-08-01 12:49:17 +02:00
// Initialize audio device
2014-09-03 17:06:10 +02:00
void InitAudioDevice ( void )
2013-11-18 23:38:44 +01:00
{
2020-02-03 18:31:30 +01:00
// TODO: Load AUDIO context memory dynamically?
2020-02-03 19:26:28 +01:00
2019-07-23 22:21:01 +02:00
// Init audio context
2020-02-03 18:31:30 +01:00
ma_context_config ctxConfig = ma_context_config_init ( ) ;
ctxConfig . logCallback = OnLog ;
2019-10-17 17:18:03 +02:00
2020-02-03 18:31:30 +01:00
ma_result result = ma_context_init ( NULL , 0 , & ctxConfig , & AUDIO . System . context ) ;
2019-03-12 11:54:45 +01:00
if ( result ! = MA_SUCCESS )
2017-11-12 14:17:05 +10:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " Failed to initialize audio context " ) ;
2017-11-12 14:17:05 +10:00
return ;
}
2019-07-23 22:21:01 +02:00
// Init audio device
// NOTE: Using the default device. Format is floating point because it simplifies mixing.
2019-03-12 11:54:45 +01:00
ma_device_config config = ma_device_config_init ( ma_device_type_playback ) ;
2020-02-03 18:31:30 +01:00
config . playback . pDeviceID = NULL ; // NULL for the default playback AUDIO.System.device.
2019-03-12 11:54:45 +01:00
config . playback . format = DEVICE_FORMAT ;
config . playback . channels = DEVICE_CHANNELS ;
2020-02-03 18:31:30 +01:00
config . capture . pDeviceID = NULL ; // NULL for the default capture AUDIO.System.device.
2019-03-12 11:54:45 +01:00
config . capture . format = ma_format_s16 ;
config . capture . channels = 1 ;
config . sampleRate = DEVICE_SAMPLE_RATE ;
config . dataCallback = OnSendAudioDataToDevice ;
config . pUserData = NULL ;
2020-02-03 18:31:30 +01:00
result = ma_device_init ( & AUDIO . System . context , & config , & AUDIO . System . device ) ;
2019-03-12 11:54:45 +01:00
if ( result ! = MA_SUCCESS )
2017-11-12 14:17:05 +10:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " Failed to initialize audio playback AUDIO.System.device " ) ;
2020-02-03 18:31:30 +01:00
ma_context_uninit ( & AUDIO . System . context ) ;
2017-11-12 14:17:05 +10:00
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.
2020-02-03 18:31:30 +01:00
result = ma_device_start ( & AUDIO . System . device ) ;
2019-03-12 11:54:45 +01:00
if ( result ! = MA_SUCCESS )
2017-11-12 14:17:05 +10:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " Failed to start audio playback AUDIO.System.device " ) ;
2020-02-03 18:31:30 +01:00
ma_device_uninit ( & AUDIO . System . device ) ;
ma_context_uninit ( & AUDIO . System . context ) ;
2017-11-12 14:17:05 +10:00
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.
2020-02-03 18:31:30 +01:00
if ( ma_mutex_init ( & AUDIO . System . context , & AUDIO . System . lock ) ! = MA_SUCCESS )
2017-11-12 14:17:05 +10:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " Failed to create mutex for audio mixing " ) ;
2020-02-03 18:31:30 +01:00
ma_device_uninit ( & AUDIO . System . device ) ;
ma_context_uninit ( & AUDIO . System . context ) ;
2017-11-12 14:17:05 +10:00
return ;
}
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Audio device initialized successfully " ) ;
TRACELOG ( LOG_INFO , " Audio backend: miniaudio / %s " , ma_get_backend_name ( AUDIO . System . context . backend ) ) ;
TRACELOG ( LOG_INFO , " Audio format: %s -> %s " , ma_get_format_name ( AUDIO . System . device . playback . format ) , ma_get_format_name ( AUDIO . System . device . playback . internalFormat ) ) ;
TRACELOG ( LOG_INFO , " Audio channels: %d -> %d " , AUDIO . System . device . playback . channels , AUDIO . System . device . playback . internalChannels ) ;
TRACELOG ( LOG_INFO , " Audio sample rate: %d -> %d " , AUDIO . System . device . sampleRate , AUDIO . System . device . playback . internalSampleRate ) ;
2020-02-10 12:42:40 +01:00
TRACELOG ( LOG_INFO , " Audio buffer size: %d " , AUDIO . System . device . playback . internalPeriodSizeInFrames ) ;
2019-04-04 13:50:52 +02:00
2019-06-29 11:26:08 +02:00
InitAudioBufferPool ( ) ;
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Audio multichannel pool size: %i " , MAX_AUDIO_BUFFER_POOL_CHANNELS ) ;
2019-06-29 09:49:42 +01:00
2020-02-03 18:31:30 +01:00
AUDIO . System . isReady = true ;
2013-11-18 23:38:44 +01:00
}
2016-05-14 00:25:40 -07:00
// Close the audio device for all contexts
2014-09-03 17:06:10 +02:00
void CloseAudioDevice ( void )
2013-11-18 23:38:44 +01:00
{
2020-02-03 18:31:30 +01:00
if ( AUDIO . System . isReady )
2019-07-23 22:21:01 +02:00
{
2020-02-03 18:31:30 +01:00
ma_mutex_uninit ( & AUDIO . System . lock ) ;
ma_device_uninit ( & AUDIO . System . device ) ;
ma_context_uninit ( & AUDIO . System . context ) ;
2017-11-12 14:17:05 +10:00
2019-07-23 22:21:01 +02:00
CloseAudioBufferPool ( ) ;
2019-06-29 09:49:42 +01:00
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Audio AUDIO.System.device closed successfully " ) ;
2019-07-23 22:21:01 +02:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_WARNING , " Could not close audio AUDIO.System.device because it is not currently initialized " ) ;
2013-11-18 23:38:44 +01:00
}
2016-07-15 18:16:34 +02:00
// Check if device has been initialized successfully
2016-04-30 16:05:43 -07:00
bool IsAudioDeviceReady ( void )
2016-04-29 23:00:12 -07:00
{
2020-02-03 18:31:30 +01:00
return AUDIO . System . isReady ;
2016-04-29 23:00:12 -07:00
}
2017-02-06 00:44:54 +01:00
// Set master volume (listener)
void SetMasterVolume ( float volume )
{
2020-02-04 22:43:31 +10:00
ma_device_set_master_volume ( & AUDIO . System . device , volume ) ;
2017-02-06 00:44:54 +01:00
}
2017-11-18 08:42:14 +10:00
//----------------------------------------------------------------------------------
2018-02-11 01:12:16 +01:00
// Module Functions Definition - Audio Buffer management
2017-11-18 08:42:14 +10:00
//----------------------------------------------------------------------------------
2018-10-18 11:38:42 +02:00
2019-09-03 23:08:02 +02:00
// Initialize a new audio buffer (filled with silence)
2020-02-03 18:31:30 +01:00
AudioBuffer * InitAudioBuffer ( ma_format format , ma_uint32 channels , ma_uint32 sampleRate , ma_uint32 sizeInFrames , int usage )
2017-11-18 08:42:14 +10:00
{
2019-08-27 10:56:49 +02:00
AudioBuffer * audioBuffer = ( AudioBuffer * ) RL_CALLOC ( 1 , sizeof ( AudioBuffer ) ) ;
2019-10-17 17:18:03 +02:00
2017-11-18 08:42:14 +10:00
if ( audioBuffer = = NULL )
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " InitAudioBuffer() : Failed to allocate memory for audio buffer " ) ;
2017-11-18 08:42:14 +10:00
return NULL ;
}
2020-02-03 19:26:28 +01:00
2020-02-03 18:31:30 +01:00
audioBuffer - > data = RL_CALLOC ( sizeInFrames * channels * ma_get_bytes_per_sample ( format ) , 1 ) ;
2017-11-18 08:42:14 +10:00
2019-07-23 22:21:01 +02:00
// Audio data runs through a format converter
2020-02-04 22:43:31 +10:00
ma_data_converter_config converterConfig = ma_data_converter_config_init ( format , DEVICE_FORMAT , channels , DEVICE_CHANNELS , sampleRate , DEVICE_SAMPLE_RATE ) ;
converterConfig . resampling . allowDynamicSampleRate = true ; // Required for pitch shifting
ma_result result = ma_data_converter_init ( & converterConfig , & audioBuffer - > converter ) ;
2019-04-04 13:50:52 +02:00
2019-03-12 11:54:45 +01:00
if ( result ! = MA_SUCCESS )
2017-12-20 11:37:43 +01:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " InitAudioBuffer() : Failed to create data conversion pipeline " ) ;
2019-04-23 14:55:35 +02:00
RL_FREE ( audioBuffer ) ;
2017-11-18 08:42:14 +10:00
return NULL ;
}
2019-07-23 22:21:01 +02:00
// Init audio buffer values
2019-03-20 10:57:41 +01:00
audioBuffer - > volume = 1.0f ;
audioBuffer - > pitch = 1.0f ;
audioBuffer - > playing = false ;
audioBuffer - > paused = false ;
audioBuffer - > looping = false ;
2017-11-18 08:42:14 +10:00
audioBuffer - > usage = usage ;
audioBuffer - > frameCursorPos = 0 ;
2020-02-03 18:31:30 +01:00
audioBuffer - > sizeInFrames = sizeInFrames ;
2017-11-18 08:42:14 +10:00
2019-07-23 22:21:01 +02:00
// Buffers should be marked as processed by default so that a call to
// UpdateAudioStream() immediately after initialization works correctly
2017-11-18 08:42:14 +10:00
audioBuffer - > isSubBufferProcessed [ 0 ] = true ;
audioBuffer - > isSubBufferProcessed [ 1 ] = true ;
2019-07-23 22:21:01 +02:00
// Track audio buffer to linked list next position
2017-11-18 08:42:14 +10:00
TrackAudioBuffer ( audioBuffer ) ;
return audioBuffer ;
}
2018-02-11 01:12:16 +01:00
// Delete an audio buffer
2019-07-24 22:37:24 +02:00
void CloseAudioBuffer ( AudioBuffer * buffer )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL )
2017-11-18 08:42:14 +10:00
{
2020-02-04 22:43:31 +10:00
ma_data_converter_uninit ( & buffer - > converter ) ;
2019-07-24 22:37:24 +02:00
UntrackAudioBuffer ( buffer ) ;
2020-02-03 18:31:30 +01:00
RL_FREE ( buffer - > data ) ;
2019-07-24 22:37:24 +02:00
RL_FREE ( buffer ) ;
2017-11-18 08:42:14 +10:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " CloseAudioBuffer() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Check if an audio buffer is playing
2019-07-24 22:37:24 +02:00
bool IsAudioBufferPlaying ( AudioBuffer * buffer )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
bool result = false ;
2019-10-17 17:18:03 +02:00
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL ) result = ( buffer - > playing & & ! buffer - > paused ) ;
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_WARNING , " IsAudioBufferPlaying() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
2019-07-24 22:37:24 +02:00
return result ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Play an audio buffer
// NOTE: Buffer is restarted to the start.
// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained.
2019-07-24 22:37:24 +02:00
void PlayAudioBuffer ( AudioBuffer * buffer )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
buffer - > playing = true ;
buffer - > paused = false ;
buffer - > frameCursorPos = 0 ;
2017-11-18 08:42:14 +10:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " PlayAudioBuffer() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Stop an audio buffer
2019-07-24 22:37:24 +02:00
void StopAudioBuffer ( AudioBuffer * buffer )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( IsAudioBufferPlaying ( buffer ) )
{
buffer - > playing = false ;
buffer - > paused = false ;
buffer - > frameCursorPos = 0 ;
2019-09-03 23:08:02 +02:00
buffer - > totalFramesProcessed = 0 ;
2019-07-24 22:37:24 +02:00
buffer - > isSubBufferProcessed [ 0 ] = true ;
buffer - > isSubBufferProcessed [ 1 ] = true ;
}
2017-11-18 08:42:14 +10:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " StopAudioBuffer() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Pause an audio buffer
2019-07-24 22:37:24 +02:00
void PauseAudioBuffer ( AudioBuffer * buffer )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL ) buffer - > paused = true ;
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " PauseAudioBuffer() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Resume an audio buffer
2019-07-24 22:37:24 +02:00
void ResumeAudioBuffer ( AudioBuffer * buffer )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL ) buffer - > paused = false ;
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " ResumeAudioBuffer() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Set volume for an audio buffer
2019-07-24 22:37:24 +02:00
void SetAudioBufferVolume ( AudioBuffer * buffer , float volume )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL ) buffer - > volume = volume ;
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_WARNING , " SetAudioBufferVolume() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Set pitch for an audio buffer
2019-07-24 22:37:24 +02:00
void SetAudioBufferPitch ( AudioBuffer * buffer , float pitch )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
if ( buffer ! = NULL )
2017-11-18 08:42:14 +10:00
{
2019-07-24 22:37:24 +02:00
float pitchMul = pitch / buffer - > pitch ;
2017-11-18 08:42:14 +10:00
2019-10-17 17:18:03 +02:00
// Pitching is just an adjustment of the sample rate.
// Note that this changes the duration of the sound:
2019-09-03 23:08:02 +02:00
// - higher pitches will make the sound faster
// - lower pitches make it slower
2020-02-04 22:43:31 +10:00
ma_uint32 newOutputSampleRate = ( ma_uint32 ) ( ( float ) buffer - > converter . config . sampleRateOut / pitchMul ) ;
buffer - > pitch * = ( float ) buffer - > converter . config . sampleRateOut / newOutputSampleRate ;
2017-11-18 08:42:14 +10:00
2020-02-04 22:43:31 +10:00
ma_data_converter_set_rate ( & buffer - > converter , buffer - > converter . config . sampleRateIn , newOutputSampleRate ) ;
2019-07-24 22:37:24 +02:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_WARNING , " SetAudioBufferPitch() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
}
2018-02-11 01:12:16 +01:00
// Track audio buffer to linked list next position
2019-07-24 22:37:24 +02:00
void TrackAudioBuffer ( AudioBuffer * buffer )
2018-02-11 01:12:16 +01:00
{
2020-02-03 18:31:30 +01:00
ma_mutex_lock ( & AUDIO . System . lock ) ;
2018-02-11 01:12:16 +01:00
{
2020-02-03 18:31:30 +01:00
if ( AUDIO . Buffer . first = = NULL ) AUDIO . Buffer . first = buffer ;
2018-11-06 15:10:50 +01:00
else
2018-02-11 01:12:16 +01:00
{
2020-02-03 18:31:30 +01:00
AUDIO . Buffer . last - > next = buffer ;
buffer - > prev = AUDIO . Buffer . last ;
2018-02-11 01:12:16 +01:00
}
2020-02-03 18:31:30 +01:00
AUDIO . Buffer . last = buffer ;
2018-02-11 01:12:16 +01:00
}
2020-02-03 18:31:30 +01:00
ma_mutex_unlock ( & AUDIO . System . lock ) ;
2018-02-11 01:12:16 +01:00
}
// Untrack audio buffer from linked list
2019-07-24 22:37:24 +02:00
void UntrackAudioBuffer ( AudioBuffer * buffer )
2018-02-11 01:12:16 +01:00
{
2020-02-03 18:31:30 +01:00
ma_mutex_lock ( & AUDIO . System . lock ) ;
2018-02-11 01:12:16 +01:00
{
2020-02-03 18:31:30 +01:00
if ( buffer - > prev = = NULL ) AUDIO . Buffer . first = buffer - > next ;
2019-07-24 22:37:24 +02:00
else buffer - > prev - > next = buffer - > next ;
2018-02-11 01:12:16 +01:00
2020-02-03 18:31:30 +01:00
if ( buffer - > next = = NULL ) AUDIO . Buffer . last = buffer - > prev ;
2019-07-24 22:37:24 +02:00
else buffer - > next - > prev = buffer - > prev ;
2018-02-11 01:12:16 +01:00
2019-07-24 22:37:24 +02:00
buffer - > prev = NULL ;
buffer - > next = NULL ;
2018-02-11 01:12:16 +01:00
}
2020-02-03 18:31:30 +01:00
ma_mutex_unlock ( & AUDIO . System . lock ) ;
2018-02-11 01:12:16 +01:00
}
2017-11-18 08:42:14 +10:00
2014-04-19 16:36:49 +02:00
//----------------------------------------------------------------------------------
// Module Functions Definition - Sounds loading and playing (.WAV)
//----------------------------------------------------------------------------------
2016-12-25 01:58:56 +01:00
// Load wave data from file
2016-09-08 00:20:06 +02:00
Wave LoadWave ( const char * fileName )
2013-11-18 23:38:44 +01:00
{
2016-01-23 13:22:13 +01:00
Wave wave = { 0 } ;
2014-09-16 22:51:31 +02:00
2019-07-24 22:37:24 +02:00
if ( false ) { }
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_WAV)
2019-07-24 22:37:24 +02:00
else if ( IsFileExtension ( fileName , " .wav " ) ) wave = LoadWAV ( fileName ) ;
2019-02-14 12:32:23 +02:00
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2017-03-29 00:35:42 +02:00
else if ( IsFileExtension ( fileName , " .ogg " ) ) wave = LoadOGG ( fileName ) ;
2017-03-26 22:49:01 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_FLAC)
2017-03-29 00:35:42 +02:00
else if ( IsFileExtension ( fileName , " .flac " ) ) wave = LoadFLAC ( fileName ) ;
2018-09-19 15:57:46 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_MP3)
else if ( IsFileExtension ( fileName , " .mp3 " ) ) wave = LoadMP3 ( fileName ) ;
2017-03-26 22:49:01 +02:00
# endif
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_WARNING , " [%s] Audio fileformat not supported, it can't be loaded " , fileName ) ;
2014-04-19 16:36:49 +02:00
2016-09-08 00:20:06 +02:00
return wave ;
}
2016-08-16 11:09:55 +02:00
2016-12-25 01:58:56 +01:00
// Load sound from file
2016-09-08 00:20:06 +02:00
// NOTE: The entire file is loaded to memory to be played (no-streaming)
Sound LoadSound ( const char * fileName )
{
Wave wave = LoadWave ( fileName ) ;
2017-01-28 23:02:30 +01:00
2016-09-08 00:20:06 +02:00
Sound sound = LoadSoundFromWave ( wave ) ;
2017-01-28 23:02:30 +01:00
2016-09-08 00:20:06 +02:00
UnloadWave ( wave ) ; // Sound is loaded, we can unload wave
2014-09-03 16:51:28 +02:00
2013-11-23 13:30:54 +01:00
return sound ;
2013-11-18 23:38:44 +01:00
}
2014-12-15 01:08:30 +01:00
// Load sound from wave data
2016-08-01 12:49:17 +02:00
// NOTE: Wave data must be unallocated manually
2014-12-15 01:08:30 +01:00
Sound LoadSoundFromWave ( Wave wave )
{
2016-01-23 13:22:13 +01:00
Sound sound = { 0 } ;
2014-12-15 01:08:30 +01:00
if ( wave . data ! = NULL )
{
2019-10-17 17:18:03 +02:00
// When using miniaudio we need to do our own mixing.
2019-07-23 22:21:01 +02:00
// To simplify this we need convert the format of each sound to be consistent with
2020-02-03 18:31:30 +01:00
// the format used to open the playback AUDIO.System.device. We can do this two ways:
2018-11-06 15:10:50 +01:00
//
2017-11-12 14:17:05 +10:00
// 1) Convert the whole sound in one go at load time (here).
// 2) Convert the audio data in chunks at mixing time.
//
2019-07-23 22:21:01 +02:00
// First option has been selected, format conversion is done on the loading stage.
// The downside is that it uses more memory if the original sound is u8 or s16.
2019-03-12 11:54:45 +01:00
ma_format formatIn = ( ( wave . sampleSize = = 8 ) ? ma_format_u8 : ( ( wave . sampleSize = = 16 ) ? ma_format_s16 : ma_format_f32 ) ) ;
ma_uint32 frameCountIn = wave . sampleCount / wave . channels ;
2017-11-12 14:17:05 +10:00
2020-02-04 22:43:31 +10:00
ma_uint32 frameCount = ( ma_uint32 ) ma_convert_frames ( NULL , 0 , DEVICE_FORMAT , DEVICE_CHANNELS , DEVICE_SAMPLE_RATE , NULL , frameCountIn , formatIn , wave . channels , wave . sampleRate ) ;
2020-02-03 19:13:24 +01:00
if ( frameCount = = 0 ) TRACELOG ( LOG_WARNING , " LoadSoundFromWave() : Failed to get frame count for format conversion " ) ;
2017-11-12 14:17:05 +10:00
2019-07-23 22:21:01 +02:00
AudioBuffer * audioBuffer = InitAudioBuffer ( DEVICE_FORMAT , DEVICE_CHANNELS , DEVICE_SAMPLE_RATE , frameCount , AUDIO_BUFFER_USAGE_STATIC ) ;
2020-02-03 19:13:24 +01:00
if ( audioBuffer = = NULL ) TRACELOG ( LOG_WARNING , " LoadSoundFromWave() : Failed to create audio buffer " ) ;
2017-11-15 22:04:23 +10:00
2020-02-04 22:43:31 +10:00
frameCount = ( ma_uint32 ) ma_convert_frames ( audioBuffer - > data , frameCount , DEVICE_FORMAT , DEVICE_CHANNELS , DEVICE_SAMPLE_RATE , wave . data , frameCountIn , formatIn , wave . channels , wave . sampleRate ) ;
2020-02-03 19:13:24 +01:00
if ( frameCount = = 0 ) TRACELOG ( LOG_WARNING , " LoadSoundFromWave() : Format conversion failed " ) ;
2017-11-12 14:17:05 +10:00
2019-07-24 14:48:45 +02:00
sound . sampleCount = frameCount * DEVICE_CHANNELS ;
sound . stream . sampleRate = DEVICE_SAMPLE_RATE ;
sound . stream . sampleSize = 32 ;
sound . stream . channels = DEVICE_CHANNELS ;
2019-07-23 22:21:01 +02:00
sound . stream . buffer = audioBuffer ;
2014-12-15 01:08:30 +01:00
}
return sound ;
}
2016-12-25 01:58:56 +01:00
// Unload wave data
2016-09-08 00:20:06 +02:00
void UnloadWave ( Wave wave )
{
2019-04-23 14:55:35 +02:00
if ( wave . data ! = NULL ) RL_FREE ( wave . data ) ;
2016-09-08 00:20:06 +02:00
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Unloaded wave data from RAM " ) ;
2016-09-08 00:20:06 +02:00
}
2013-11-18 23:38:44 +01:00
// Unload sound
void UnloadSound ( Sound sound )
{
2019-07-24 22:37:24 +02:00
CloseAudioBuffer ( sound . stream . buffer ) ;
2016-07-29 21:35:57 +02:00
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Unloaded sound data from RAM " ) ;
2013-11-18 23:38:44 +01:00
}
2016-08-29 11:17:58 +02:00
// Update sound buffer with new data
2017-02-09 22:19:48 +01:00
void UpdateSound ( Sound sound , const void * data , int samplesCount )
2016-08-29 11:17:58 +02:00
{
2019-07-24 22:37:24 +02:00
AudioBuffer * audioBuffer = sound . stream . buffer ;
2018-11-06 15:10:50 +01:00
2019-09-03 23:08:02 +02:00
if ( audioBuffer ! = NULL )
2017-11-12 14:17:05 +10:00
{
2019-09-03 23:08:02 +02:00
StopAudioBuffer ( audioBuffer ) ;
2017-11-12 14:17:05 +10:00
2019-09-03 23:08:02 +02:00
// TODO: May want to lock/unlock this since this data buffer is read at mixing time
2020-02-04 22:43:31 +10:00
memcpy ( audioBuffer - > data , data , samplesCount * ma_get_bytes_per_frame ( audioBuffer - > converter . config . formatIn , audioBuffer - > converter . config . channelsIn ) ) ;
2019-09-03 23:08:02 +02:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " UpdateSound() : Invalid sound - no audio buffer " ) ;
2016-08-29 11:17:58 +02:00
}
2018-09-17 16:56:02 +02:00
// Export wave data to file
void ExportWave ( Wave wave , const char * fileName )
{
bool success = false ;
2018-11-06 15:10:50 +01:00
2019-07-24 22:37:24 +02:00
if ( false ) { }
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_WAV)
2019-07-24 22:37:24 +02:00
else if ( IsFileExtension ( fileName , " .wav " ) ) success = SaveWAV ( wave , fileName ) ;
2019-02-14 12:32:23 +02:00
# endif
2018-11-06 15:10:50 +01:00
else if ( IsFileExtension ( fileName , " .raw " ) )
2018-09-17 16:56:02 +02:00
{
2018-10-29 16:18:06 +01:00
// 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 ) ;
2018-09-17 16:56:02 +02:00
}
2018-11-06 15:10:50 +01:00
2020-02-03 19:13:24 +01:00
if ( success ) TRACELOG ( LOG_INFO , " Wave exported successfully: %s " , fileName ) ;
else TRACELOG ( LOG_WARNING , " Wave could not be exported. " ) ;
2018-09-17 16:56:02 +02:00
}
2018-10-29 16:18:06 +01:00
// Export wave sample data to code (.h)
void ExportWaveAsCode ( Wave wave , const char * fileName )
{
# define BYTES_TEXT_PER_LINE 20
2018-11-06 15:10:50 +01:00
2018-10-29 16:18:06 +01:00
char varFileName [ 256 ] = { 0 } ;
int dataSize = wave . sampleCount * wave . channels * wave . sampleSize / 8 ;
2018-11-06 15:10:50 +01:00
2018-10-29 16:18:06 +01:00
FILE * txtFile = fopen ( fileName , " wt " ) ;
2019-10-17 17:18:03 +02:00
2019-09-30 17:32:06 +02:00
if ( txtFile ! = NULL )
{
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 " ) ;
2018-10-29 16:18:06 +01:00
2019-02-12 12:18:01 +01:00
# if !defined(RAUDIO_STANDALONE)
2019-09-30 17:32:06 +02:00
// 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 ; }
2019-02-12 12:18:01 +01:00
# else
2019-09-30 17:32:06 +02:00
strcpy ( varFileName , fileName ) ;
2019-02-12 12:18:01 +01:00
# endif
2018-11-06 15:10:50 +01:00
2019-09-30 17:32:06 +02:00
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 ) ;
2018-10-29 16:18:06 +01:00
2019-09-30 17:32:06 +02:00
// 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 ] ) ;
2018-10-29 16:18:06 +01:00
2019-09-30 17:32:06 +02:00
fclose ( txtFile ) ;
}
2018-10-29 16:18:06 +01:00
}
2013-11-18 23:38:44 +01:00
// Play a sound
void PlaySound ( Sound sound )
{
2019-07-24 22:37:24 +02:00
PlayAudioBuffer ( sound . stream . buffer ) ;
2013-11-18 23:38:44 +01:00
}
2019-06-29 11:26:08 +02:00
// Play a sound in the multichannel buffer pool
void PlaySoundMulti ( Sound sound )
2019-06-29 09:49:42 +01:00
{
2019-06-29 11:26:08 +02:00
int index = - 1 ;
2019-09-03 23:08:02 +02:00
unsigned int oldAge = 0 ;
2019-06-29 09:49:42 +01:00
int oldIndex = - 1 ;
// find the first non playing pool entry
2019-06-29 11:26:08 +02:00
for ( int i = 0 ; i < MAX_AUDIO_BUFFER_POOL_CHANNELS ; i + + )
{
2020-02-03 18:31:30 +01:00
if ( AUDIO . MultiChannel . channels [ i ] > oldAge )
2019-06-29 11:26:08 +02:00
{
2020-02-03 18:31:30 +01:00
oldAge = AUDIO . MultiChannel . channels [ i ] ;
2019-06-29 09:49:42 +01:00
oldIndex = i ;
}
2019-10-17 17:18:03 +02:00
2020-02-03 18:31:30 +01:00
if ( ! IsAudioBufferPlaying ( AUDIO . MultiChannel . pool [ i ] ) )
2019-06-29 11:26:08 +02:00
{
index = i ;
2019-06-29 09:49:42 +01:00
break ;
}
}
2019-06-29 11:26:08 +02:00
// If no none playing pool members can be index choose the oldest
if ( index = = - 1 )
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " pool age %i ended a sound early no room in buffer pool " , AUDIO . MultiChannel . poolCounter ) ;
2019-10-17 17:18:03 +02:00
2019-06-29 11:26:08 +02:00
if ( oldIndex = = - 1 )
{
// Shouldn't be able to get here... but just in case something odd happens!
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " sound buffer pool couldn't determine oldest buffer not playing sound " ) ;
2019-10-17 17:18:03 +02:00
2019-06-29 09:49:42 +01:00
return ;
}
2019-10-17 17:18:03 +02:00
2019-06-29 11:26:08 +02:00
index = oldIndex ;
2019-10-17 17:18:03 +02:00
2019-06-29 11:26:08 +02:00
// Just in case...
2020-02-03 18:31:30 +01:00
StopAudioBuffer ( AUDIO . MultiChannel . pool [ index ] ) ;
2019-06-29 09:49:42 +01:00
}
2019-06-29 11:26:08 +02:00
// Experimentally mutex lock doesn't seem to be needed this makes sense
2020-02-03 18:31:30 +01:00
// as AUDIO.MultiChannel.pool[index] isn't playing and the only stuff we're copying
2019-06-29 09:49:42 +01:00
// shouldn't be changing...
2020-02-03 18:31:30 +01:00
AUDIO . MultiChannel . channels [ index ] = AUDIO . MultiChannel . poolCounter ;
AUDIO . MultiChannel . poolCounter + + ;
2019-10-17 17:18:03 +02:00
2020-02-03 18:31:30 +01:00
AUDIO . MultiChannel . pool [ index ] - > volume = sound . stream . buffer - > volume ;
AUDIO . MultiChannel . pool [ index ] - > pitch = sound . stream . buffer - > pitch ;
AUDIO . MultiChannel . pool [ index ] - > looping = sound . stream . buffer - > looping ;
AUDIO . MultiChannel . pool [ index ] - > usage = sound . stream . buffer - > usage ;
AUDIO . MultiChannel . pool [ index ] - > isSubBufferProcessed [ 0 ] = false ;
AUDIO . MultiChannel . pool [ index ] - > isSubBufferProcessed [ 1 ] = false ;
AUDIO . MultiChannel . pool [ index ] - > sizeInFrames = sound . stream . buffer - > sizeInFrames ;
AUDIO . MultiChannel . pool [ index ] - > data = sound . stream . buffer - > data ;
2019-06-29 09:49:42 +01:00
2020-02-03 18:31:30 +01:00
PlayAudioBuffer ( AUDIO . MultiChannel . pool [ index ] ) ;
2019-06-29 09:49:42 +01:00
}
2019-06-29 11:26:08 +02:00
// Stop any sound played with PlaySoundMulti()
void StopSoundMulti ( void )
2019-06-29 09:49:42 +01:00
{
2020-02-03 18:31:30 +01:00
for ( int i = 0 ; i < MAX_AUDIO_BUFFER_POOL_CHANNELS ; i + + ) StopAudioBuffer ( AUDIO . MultiChannel . pool [ i ] ) ;
2019-06-29 09:49:42 +01:00
}
2019-06-29 11:26:08 +02:00
// Get number of sounds playing in the multichannel buffer pool
int GetSoundsPlaying ( void )
2019-06-29 09:49:42 +01:00
{
2019-06-29 11:26:08 +02:00
int counter = 0 ;
2019-10-17 17:18:03 +02:00
2019-06-29 11:26:08 +02:00
for ( int i = 0 ; i < MAX_AUDIO_BUFFER_POOL_CHANNELS ; i + + )
{
2020-02-03 18:31:30 +01:00
if ( IsAudioBufferPlaying ( AUDIO . MultiChannel . pool [ i ] ) ) counter + + ;
2019-06-29 09:49:42 +01:00
}
2019-10-17 17:18:03 +02:00
2019-06-29 11:26:08 +02:00
return counter ;
2019-06-29 09:49:42 +01:00
}
2013-11-18 23:38:44 +01:00
// Pause a sound
void PauseSound ( Sound sound )
{
2019-07-24 22:37:24 +02:00
PauseAudioBuffer ( sound . stream . buffer ) ;
2013-11-18 23:38:44 +01:00
}
2016-08-01 12:49:17 +02:00
// Resume a paused sound
void ResumeSound ( Sound sound )
{
2019-07-24 22:37:24 +02:00
ResumeAudioBuffer ( sound . stream . buffer ) ;
2016-08-01 12:49:17 +02:00
}
2013-11-18 23:38:44 +01:00
// Stop reproducing a sound
void StopSound ( Sound sound )
{
2019-07-24 22:37:24 +02:00
StopAudioBuffer ( sound . stream . buffer ) ;
2013-11-18 23:38:44 +01:00
}
2014-01-23 12:36:18 +01:00
// Check if a sound is playing
2016-05-03 18:04:21 +02:00
bool IsSoundPlaying ( Sound sound )
2014-01-23 12:36:18 +01:00
{
2019-07-24 22:37:24 +02:00
return IsAudioBufferPlaying ( sound . stream . buffer ) ;
2014-01-23 12:36:18 +01:00
}
2014-04-19 16:36:49 +02:00
// Set volume for a sound
void SetSoundVolume ( Sound sound , float volume )
{
2019-07-24 22:37:24 +02:00
SetAudioBufferVolume ( sound . stream . buffer , volume ) ;
2014-04-19 16:36:49 +02:00
}
// Set pitch for a sound
void SetSoundPitch ( Sound sound , float pitch )
{
2019-07-24 22:37:24 +02:00
SetAudioBufferPitch ( sound . stream . buffer , pitch ) ;
2014-04-19 16:36:49 +02:00
}
2016-09-08 00:20:06 +02:00
// Convert wave data to desired format
void WaveFormat ( Wave * wave , int sampleRate , int sampleSize , int channels )
{
2019-03-12 11:54:45 +01:00
ma_format formatIn = ( ( wave - > sampleSize = = 8 ) ? ma_format_u8 : ( ( wave - > sampleSize = = 16 ) ? ma_format_s16 : ma_format_f32 ) ) ;
ma_format formatOut = ( ( sampleSize = = 8 ) ? ma_format_u8 : ( ( sampleSize = = 16 ) ? ma_format_s16 : ma_format_f32 ) ) ;
2017-11-12 14:17:05 +10:00
2019-03-12 11:54:45 +01:00
ma_uint32 frameCountIn = wave - > sampleCount ; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so.
2017-11-12 14:17:05 +10:00
2020-02-04 22:43:31 +10:00
ma_uint32 frameCount = ( ma_uint32 ) ma_convert_frames ( NULL , 0 , formatOut , channels , sampleRate , NULL , frameCountIn , formatIn , wave - > channels , wave - > sampleRate ) ;
2018-11-06 15:10:50 +01:00
if ( frameCount = = 0 )
2017-12-20 11:37:43 +01:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " WaveFormat() : Failed to get frame count for format conversion. " ) ;
2017-11-12 14:17:05 +10:00
return ;
}
2019-04-23 14:55:35 +02:00
void * data = RL_MALLOC ( frameCount * channels * ( sampleSize / 8 ) ) ;
2017-11-12 14:17:05 +10:00
2020-02-04 22:43:31 +10:00
frameCount = ( ma_uint32 ) ma_convert_frames ( data , frameCount , formatOut , channels , sampleRate , wave - > data , frameCountIn , formatIn , wave - > channels , wave - > sampleRate ) ;
2018-11-06 15:10:50 +01:00
if ( frameCount = = 0 )
2017-12-20 11:37:43 +01:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " WaveFormat() : Format conversion failed. " ) ;
2017-11-12 14:17:05 +10:00
return ;
}
wave - > sampleCount = frameCount ;
wave - > sampleSize = sampleSize ;
wave - > sampleRate = sampleRate ;
wave - > channels = channels ;
2019-04-23 14:55:35 +02:00
RL_FREE ( wave - > data ) ;
2017-11-12 14:17:05 +10:00
wave - > data = data ;
2016-09-08 00:20:06 +02:00
}
// Copy a wave to a new wave
Wave WaveCopy ( Wave wave )
{
2016-12-25 01:58:56 +01:00
Wave newWave = { 0 } ;
2016-09-08 00:20:06 +02:00
2019-04-23 14:55:35 +02:00
newWave . data = RL_MALLOC ( wave . sampleCount * wave . sampleSize / 8 * wave . channels ) ;
2016-09-08 00:20:06 +02:00
if ( newWave . data ! = NULL )
{
// NOTE: Size must be provided in bytes
2016-09-08 01:03:05 +02:00
memcpy ( newWave . data , wave . data , wave . sampleCount * wave . channels * wave . sampleSize / 8 ) ;
2016-09-08 00:20:06 +02:00
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 )
{
2017-01-28 23:02:30 +01:00
if ( ( initSample > = 0 ) & & ( initSample < finalSample ) & &
2018-05-21 20:46:22 +10:00
( finalSample > 0 ) & & ( ( unsigned int ) finalSample < wave - > sampleCount ) )
2016-09-08 01:03:05 +02:00
{
2016-12-25 01:58:56 +01:00
int sampleCount = finalSample - initSample ;
2017-01-28 23:02:30 +01:00
2019-04-23 14:55:35 +02:00
void * data = RL_MALLOC ( sampleCount * wave - > sampleSize / 8 * wave - > channels ) ;
2017-01-28 23:02:30 +01:00
2018-10-18 16:00:11 +02:00
memcpy ( data , ( unsigned char * ) wave - > data + ( initSample * wave - > channels * wave - > sampleSize / 8 ) , sampleCount * wave - > channels * wave - > sampleSize / 8 ) ;
2017-01-28 23:02:30 +01:00
2019-04-23 14:55:35 +02:00
RL_FREE ( wave - > data ) ;
2016-12-25 01:58:56 +01:00
wave - > data = data ;
2016-09-08 01:03:05 +02:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_WARNING , " Wave crop range out of bounds " ) ;
2016-09-08 00:20:06 +02:00
}
// Get samples data from wave as a floats array
2016-09-09 01:34:30 +02:00
// NOTE: Returned sample values are normalized to range [-1..1]
2016-09-08 00:20:06 +02:00
float * GetWaveData ( Wave wave )
{
2019-04-23 14:55:35 +02:00
float * samples = ( float * ) RL_MALLOC ( wave . sampleCount * wave . channels * sizeof ( float ) ) ;
2017-01-28 23:02:30 +01:00
2018-05-21 20:46:22 +10:00
for ( unsigned int i = 0 ; i < wave . sampleCount ; i + + )
2016-09-08 00:20:06 +02:00
{
2018-05-21 20:46:22 +10:00
for ( unsigned int j = 0 ; j < wave . channels ; j + + )
2016-12-25 01:58:56 +01:00
{
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 ] ;
}
2016-09-08 00:20:06 +02:00
}
2017-01-28 23:02:30 +01:00
2016-09-08 00:20:06 +02:00
return samples ;
}
2014-04-19 16:36:49 +02:00
//----------------------------------------------------------------------------------
// Module Functions Definition - Music loading and stream playing (.OGG)
//----------------------------------------------------------------------------------
2016-08-01 12:49:17 +02:00
// Load music stream from file
2016-09-08 00:20:06 +02:00
Music LoadMusicStream ( const char * fileName )
2016-07-29 21:35:57 +02:00
{
2019-07-26 10:26:39 +02:00
Music music = { 0 } ;
2019-07-24 22:37:24 +02:00
bool musicLoaded = false ;
2016-07-29 21:35:57 +02:00
2019-07-24 22:37:24 +02:00
if ( false ) { }
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2019-07-24 22:37:24 +02:00
else if ( IsFileExtension ( fileName , " .ogg " ) )
2016-07-29 21:35:57 +02:00
{
2016-08-01 12:49:17 +02:00
// Open ogg audio stream
2019-07-26 10:26:39 +02:00
music . ctxData = stb_vorbis_open_filename ( fileName , NULL , NULL ) ;
2016-07-29 21:35:57 +02:00
2019-07-26 10:26:39 +02:00
if ( music . ctxData ! = NULL )
2014-04-19 16:36:49 +02:00
{
2019-07-26 10:26:39 +02:00
music . ctxType = MUSIC_AUDIO_OGG ;
stb_vorbis_info info = stb_vorbis_get_info ( ( stb_vorbis * ) music . ctxData ) ; // Get Ogg file info
2016-07-29 21:35:57 +02:00
2016-12-25 01:58:56 +01:00
// OGG bit rate defaults to 16 bit, it's enough for compressed format
2019-07-26 10:26:39 +02:00
music . stream = InitAudioStream ( info . sample_rate , 16 , info . channels ) ;
music . sampleCount = ( unsigned int ) stb_vorbis_stream_length_in_samples ( ( stb_vorbis * ) music . ctxData ) * info . channels ;
music . loopCount = 0 ; // Infinite loop by default
2019-07-24 22:37:24 +02:00
musicLoaded = true ;
2014-04-19 16:36:49 +02:00
}
}
2019-02-14 12:32:23 +02:00
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
2017-03-29 00:35:42 +02:00
else if ( IsFileExtension ( fileName , " .flac " ) )
2016-10-10 18:22:55 +02:00
{
2019-07-26 10:26:39 +02:00
music . ctxData = drflac_open_file ( fileName ) ;
2017-01-28 23:02:30 +01:00
2019-07-26 10:26:39 +02:00
if ( music . ctxData ! = NULL )
2016-10-10 18:22:55 +02:00
{
2019-07-26 10:26:39 +02:00
music . ctxType = MUSIC_AUDIO_FLAC ;
drflac * ctxFlac = ( drflac * ) music . ctxData ;
2019-07-24 22:37:24 +02:00
2019-07-26 10:26:39 +02:00
music . stream = InitAudioStream ( ctxFlac - > sampleRate , ctxFlac - > bitsPerSample , ctxFlac - > channels ) ;
music . sampleCount = ( unsigned int ) ctxFlac - > totalSampleCount ;
music . loopCount = 0 ; // Infinite loop by default
2019-07-24 22:37:24 +02:00
musicLoaded = true ;
2016-10-10 18:22:55 +02:00
}
}
2017-03-26 22:49:01 +02:00
# endif
2018-05-17 00:04:58 +02:00
# if defined(SUPPORT_FILEFORMAT_MP3)
else if ( IsFileExtension ( fileName , " .mp3 " ) )
{
2019-07-23 22:21:01 +02:00
drmp3 * ctxMp3 = RL_MALLOC ( sizeof ( drmp3 ) ) ;
2019-07-26 10:26:39 +02:00
music . ctxData = ctxMp3 ;
2019-10-17 17:18:03 +02:00
2019-07-23 22:21:01 +02:00
int result = drmp3_init_file ( ctxMp3 , fileName , NULL ) ;
2018-05-17 00:04:58 +02:00
2019-07-24 22:37:24 +02:00
if ( result > 0 )
2018-05-17 00:04:58 +02:00
{
2019-07-26 10:26:39 +02:00
music . ctxType = MUSIC_AUDIO_MP3 ;
2018-11-06 15:10:50 +01:00
2019-07-26 10:26:39 +02:00
music . stream = InitAudioStream ( ctxMp3 - > sampleRate , 32 , ctxMp3 - > channels ) ;
2020-02-04 22:43:31 +10:00
music . sampleCount = ( unsigned int ) drmp3_get_pcm_frame_count ( ctxMp3 ) * ctxMp3 - > channels ;
2019-07-26 10:26:39 +02:00
music . loopCount = 0 ; // Infinite loop by default
2019-07-24 22:37:24 +02:00
musicLoaded = true ;
2018-05-17 00:04:58 +02:00
}
}
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_XM)
2017-03-29 00:35:42 +02:00
else if ( IsFileExtension ( fileName , " .xm " ) )
2016-04-24 18:18:18 -07:00
{
2019-07-23 22:21:01 +02:00
jar_xm_context_t * ctxXm = NULL ;
int result = jar_xm_create_context_from_file ( & ctxXm , 48000 , fileName ) ;
2016-08-16 11:09:55 +02:00
2020-02-03 18:31:30 +01:00
if ( result = = 0 ) // XM AUDIO.System.context created successfully
2016-04-24 18:18:18 -07:00
{
2019-07-26 10:26:39 +02:00
music . ctxType = MUSIC_MODULE_XM ;
2019-07-24 22:37:24 +02:00
jar_xm_set_max_loop_count ( ctxXm , 0 ) ; // Set infinite number of loops
2016-08-01 12:49:17 +02:00
// NOTE: Only stereo is supported for XM
2019-07-26 10:26:39 +02:00
music . stream = InitAudioStream ( 48000 , 16 , 2 ) ;
2020-01-26 18:29:13 +01:00
music . sampleCount = ( unsigned int ) jar_xm_get_remaining_samples ( ctxXm ) * 2 ;
2019-07-26 10:26:39 +02:00
music . loopCount = 0 ; // Infinite loop by default
2019-12-21 04:02:54 -08:00
jar_xm_reset ( ctxXm ) ; // make sure we start at the beginning of the song
2019-07-24 22:37:24 +02:00
musicLoaded = true ;
2019-10-17 17:18:03 +02:00
2019-07-26 10:26:39 +02:00
music . ctxData = ctxXm ;
2016-05-11 20:15:37 -07:00
}
}
2017-03-26 22:49:01 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_MOD)
2017-03-29 00:35:42 +02:00
else if ( IsFileExtension ( fileName , " .mod " ) )
2016-06-01 20:09:00 -07:00
{
2019-07-23 22:21:01 +02:00
jar_mod_context_t * ctxMod = RL_MALLOC ( sizeof ( jar_mod_context_t ) ) ;
2019-07-26 10:26:39 +02:00
music . ctxData = ctxMod ;
2019-10-17 17:18:03 +02:00
2019-07-23 22:21:01 +02:00
jar_mod_init ( ctxMod ) ;
2019-07-24 22:37:24 +02:00
int result = jar_mod_load_file ( ctxMod , fileName ) ;
2016-07-29 21:35:57 +02:00
2019-07-24 22:37:24 +02:00
if ( result > 0 )
2016-06-01 20:09:00 -07:00
{
2019-07-26 10:26:39 +02:00
music . ctxType = MUSIC_MODULE_MOD ;
2019-07-24 22:37:24 +02:00
2018-12-25 15:18:35 +01:00
// NOTE: Only stereo is supported for MOD
2019-07-26 10:26:39 +02:00
music . stream = InitAudioStream ( 48000 , 16 , 2 ) ;
2020-01-26 18:29:13 +01:00
music . sampleCount = ( unsigned int ) jar_mod_max_samples ( ctxMod ) * 2 ;
2019-07-26 10:26:39 +02:00
music . loopCount = 0 ; // Infinite loop by default
2019-07-24 22:37:24 +02:00
musicLoaded = true ;
2016-06-01 20:09:00 -07:00
}
}
2017-03-26 22:49:01 +02:00
# endif
2018-11-06 15:10:50 +01:00
2018-07-28 18:07:06 +02:00
if ( ! musicLoaded )
{
2019-07-24 22:37:24 +02:00
if ( false ) { }
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_AUDIO_OGG ) stb_vorbis_close ( ( stb_vorbis * ) music . ctxData ) ;
2019-02-14 12:32:23 +02:00
# endif
2018-07-28 18:07:06 +02:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_AUDIO_FLAC ) drflac_free ( ( drflac * ) music . ctxData ) ;
2018-07-28 18:07:06 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_MP3)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_AUDIO_MP3 ) { drmp3_uninit ( ( drmp3 * ) music . ctxData ) ; RL_FREE ( music . ctxData ) ; }
2018-07-28 18:07:06 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_XM)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_MODULE_XM ) jar_xm_free_context ( ( jar_xm_context_t * ) music . ctxData ) ;
2018-07-28 18:07:06 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_MOD)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_MODULE_MOD ) { jar_mod_unload ( ( jar_mod_context_t * ) music . ctxData ) ; RL_FREE ( music . ctxData ) ; }
2018-07-28 18:07:06 +02:00
# endif
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " [%s] Music file could not be opened " , fileName ) ;
2018-07-28 18:07:06 +02:00
}
2019-09-03 23:08:02 +02:00
else
{
// Show some music stream info
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " [%s] Music file successfully loaded: " , fileName ) ;
TRACELOG ( LOG_INFO , " Total samples: %i " , music . sampleCount ) ;
TRACELOG ( LOG_INFO , " Sample rate: %i Hz " , music . stream . sampleRate ) ;
TRACELOG ( LOG_INFO , " Sample size: %i bits " , music . stream . sampleSize ) ;
TRACELOG ( LOG_INFO , " Channels: %i (%s) " , music . stream . channels , ( music . stream . channels = = 1 ) ? " Mono " : ( music . stream . channels = = 2 ) ? " Stereo " : " Multi " ) ;
2019-09-03 23:08:02 +02:00
}
2016-07-29 21:35:57 +02:00
2016-08-01 12:49:17 +02:00
return music ;
2014-04-19 16:36:49 +02:00
}
2016-08-01 12:49:17 +02:00
// Unload music stream
void UnloadMusicStream ( Music music )
2016-07-29 21:35:57 +02:00
{
2019-07-26 10:26:39 +02:00
CloseAudioStream ( music . stream ) ;
2016-08-16 11:09:55 +02:00
2019-07-24 22:37:24 +02:00
if ( false ) { }
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_AUDIO_OGG ) stb_vorbis_close ( ( stb_vorbis * ) music . ctxData ) ;
2019-02-14 12:32:23 +02:00
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_AUDIO_FLAC ) drflac_free ( ( drflac * ) music . ctxData ) ;
2017-03-26 22:49:01 +02:00
# endif
2018-05-17 00:04:58 +02:00
# if defined(SUPPORT_FILEFORMAT_MP3)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_AUDIO_MP3 ) { drmp3_uninit ( ( drmp3 * ) music . ctxData ) ; RL_FREE ( music . ctxData ) ; }
2018-05-17 00:04:58 +02:00
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_XM)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_MODULE_XM ) jar_xm_free_context ( ( jar_xm_context_t * ) music . ctxData ) ;
2017-03-26 22:49:01 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_MOD)
2019-07-26 10:26:39 +02:00
else if ( music . ctxType = = MUSIC_MODULE_MOD ) { jar_mod_unload ( ( jar_mod_context_t * ) music . ctxData ) ; RL_FREE ( music . ctxData ) ; }
2017-03-26 22:49:01 +02:00
# endif
2016-08-01 12:49:17 +02:00
}
2016-07-29 21:35:57 +02:00
2016-08-01 12:49:17 +02:00
// Start music playing (open stream)
void PlayMusicStream ( Music music )
{
2019-07-26 10:26:39 +02:00
AudioBuffer * audioBuffer = music . stream . buffer ;
2019-01-02 14:05:20 +01:00
2019-09-03 23:08:02 +02:00
if ( audioBuffer ! = NULL )
2019-07-26 10:26:39 +02:00
{
2019-09-03 23:08:02 +02:00
// For music streams, we need to make sure we maintain the frame cursor position
// This is a hack for this section of code in UpdateMusicStream()
2019-10-17 17:18:03 +02:00
// NOTE: In case window is minimized, music stream is stopped, just make sure to
2019-09-03 23:08:02 +02:00
// play again on window restore: if (IsMusicPlaying(music)) PlayMusicStream(music);
ma_uint32 frameCursorPos = audioBuffer - > frameCursorPos ;
PlayAudioStream ( music . stream ) ; // WARNING: This resets the cursor position.
audioBuffer - > frameCursorPos = frameCursorPos ;
2019-07-26 10:26:39 +02:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " PlayMusicStream() : No audio buffer " ) ;
2017-11-18 08:42:14 +10:00
2016-07-29 21:35:57 +02:00
}
2016-08-01 12:49:17 +02:00
// Pause music playing
void PauseMusicStream ( Music music )
2014-04-19 16:36:49 +02:00
{
2019-07-26 10:26:39 +02:00
PauseAudioStream ( music . stream ) ;
2016-08-01 12:49:17 +02:00
}
2016-07-29 21:35:57 +02:00
2016-08-01 12:49:17 +02:00
// Resume music playing
void ResumeMusicStream ( Music music )
{
2019-07-26 10:26:39 +02:00
ResumeAudioStream ( music . stream ) ;
2016-08-01 12:49:17 +02:00
}
2016-07-29 21:35:57 +02:00
2016-08-01 12:49:17 +02:00
// Stop music playing (close stream)
void StopMusicStream ( Music music )
{
2019-07-26 10:26:39 +02:00
StopAudioStream ( music . stream ) ;
2018-11-06 15:10:50 +01:00
2019-07-26 10:26:39 +02:00
switch ( music . ctxType )
2016-09-15 11:53:16 +02:00
{
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2019-07-26 10:26:39 +02:00
case MUSIC_AUDIO_OGG : stb_vorbis_seek_start ( ( stb_vorbis * ) music . ctxData ) ; break ;
2019-02-14 12:32:23 +02:00
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
2019-09-03 23:24:09 +02:00
case MUSIC_AUDIO_FLAC : drflac_seek_to_pcm_frame ( ( drflac * ) music . ctxData , 0 ) ; break ;
2017-03-26 22:49:01 +02:00
# endif
2018-09-19 15:57:46 +02:00
# if defined(SUPPORT_FILEFORMAT_MP3)
2019-07-26 10:26:39 +02:00
case MUSIC_AUDIO_MP3 : drmp3_seek_to_pcm_frame ( ( drmp3 * ) music . ctxData , 0 ) ; break ;
2018-09-19 15:57:46 +02:00
# endif
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_XM)
2019-07-26 10:26:39 +02:00
case MUSIC_MODULE_XM : jar_xm_reset ( ( jar_xm_context_t * ) music . ctxData ) ; break ;
2017-03-26 22:49:01 +02:00
# endif
# if defined(SUPPORT_FILEFORMAT_MOD)
2019-07-26 10:26:39 +02:00
case MUSIC_MODULE_MOD : jar_mod_seek_start ( ( jar_mod_context_t * ) music . ctxData ) ; break ;
2017-03-26 22:49:01 +02:00
# endif
2016-09-15 11:53:16 +02:00
default : break ;
}
2016-05-11 00:37:10 -07:00
}
2014-09-03 16:51:28 +02:00
2016-07-15 18:16:34 +02:00
// Update (re-fill) music buffers if data already processed
2016-08-01 12:49:17 +02:00
void UpdateMusicStream ( Music music )
2016-07-15 18:16:34 +02:00
{
2017-11-12 21:55:24 +10:00
bool streamEnding = false ;
2020-02-03 18:31:30 +01:00
unsigned int subBufferSizeInFrames = music . stream . buffer - > sizeInFrames / 2 ;
2017-11-24 21:54:00 +10:00
2017-11-12 21:55:24 +10:00
// NOTE: Using dynamic allocation because it could require more than 16KB
2019-07-26 10:26:39 +02:00
void * pcm = RL_CALLOC ( subBufferSizeInFrames * music . stream . channels * music . stream . sampleSize / 8 , 1 ) ;
2017-11-12 21:55:24 +10:00
2019-09-03 23:08:02 +02:00
int samplesCount = 0 ; // Total size of data streamed in L+R samples for xm floats, individual L or R for ogg shorts
2019-10-17 17:18:03 +02:00
2019-09-03 23:08:02 +02:00
// TODO: Get the sampleLeft using totalFramesProcessed... but first, get total frames processed correctly...
//ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels;
int sampleLeft = music . sampleCount - ( music . stream . buffer - > totalFramesProcessed * music . stream . channels ) ;
2017-11-12 21:55:24 +10:00
2019-08-13 17:41:31 +02:00
while ( IsAudioStreamProcessed ( music . stream ) )
2017-11-12 21:55:24 +10:00
{
2019-09-03 23:08:02 +02:00
if ( ( sampleLeft / music . stream . channels ) > = subBufferSizeInFrames ) samplesCount = subBufferSizeInFrames * music . stream . channels ;
else samplesCount = sampleLeft ;
2017-11-12 21:55:24 +10:00
2019-07-26 10:26:39 +02:00
switch ( music . ctxType )
2017-11-12 21:55:24 +10:00
{
2019-02-14 12:32:23 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2017-11-12 21:55:24 +10:00
case MUSIC_AUDIO_OGG :
{
// NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!)
2019-07-26 10:26:39 +02:00
stb_vorbis_get_samples_short_interleaved ( ( stb_vorbis * ) music . ctxData , music . stream . channels , ( short * ) pcm , samplesCount ) ;
2017-11-12 21:55:24 +10:00
} break ;
2019-02-14 12:32:23 +02:00
# endif
2017-11-12 21:55:24 +10:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
case MUSIC_AUDIO_FLAC :
{
2019-07-23 22:21:01 +02:00
// NOTE: Returns the number of samples to process (not required)
2019-09-03 23:24:09 +02:00
drflac_read_pcm_frames_s16 ( ( drflac * ) music . ctxData , samplesCount , ( short * ) pcm ) ;
2017-11-12 21:55:24 +10:00
} break ;
# endif
2018-05-17 00:04:58 +02:00
# if defined(SUPPORT_FILEFORMAT_MP3)
2018-11-06 15:10:50 +01:00
case MUSIC_AUDIO_MP3 :
2018-05-17 00:04:58 +02:00
{
2018-10-31 17:04:24 +01:00
// NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed
2019-07-26 10:26:39 +02:00
drmp3_read_pcm_frames_f32 ( ( drmp3 * ) music . ctxData , samplesCount / music . stream . channels , ( float * ) pcm ) ;
2018-05-17 00:04:58 +02:00
} break ;
# endif
2017-11-12 21:55:24 +10:00
# if defined(SUPPORT_FILEFORMAT_XM)
2018-11-06 15:10:50 +01:00
case MUSIC_MODULE_XM :
2018-10-31 17:04:24 +01:00
{
2018-12-25 15:18:35 +01:00
// NOTE: Internally this function considers 2 channels generation, so samplesCount/2
2019-07-26 10:26:39 +02:00
jar_xm_generate_samples_16bit ( ( jar_xm_context_t * ) music . ctxData , ( short * ) pcm , samplesCount / 2 ) ;
2018-10-31 17:04:24 +01:00
} break ;
2017-11-12 21:55:24 +10:00
# endif
# if defined(SUPPORT_FILEFORMAT_MOD)
2019-02-21 18:45:19 +01:00
case MUSIC_MODULE_MOD :
2018-12-25 15:18:35 +01:00
{
// NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2
2019-07-26 10:26:39 +02:00
jar_mod_fillbuffer ( ( jar_mod_context_t * ) music . ctxData , ( short * ) pcm , samplesCount / 2 , 0 ) ;
2018-12-25 15:18:35 +01:00
} break ;
2017-11-12 21:55:24 +10:00
# endif
default : break ;
}
2019-07-26 10:26:39 +02:00
UpdateAudioStream ( music . stream , pcm , samplesCount ) ;
2019-10-17 17:18:03 +02:00
2019-07-26 10:26:39 +02:00
if ( ( music . ctxType = = MUSIC_MODULE_XM ) | | ( music . ctxType = = MUSIC_MODULE_MOD ) )
2018-12-25 15:18:35 +01:00
{
2019-09-03 23:08:02 +02:00
if ( samplesCount > 1 ) sampleLeft - = samplesCount / 2 ;
else sampleLeft - = samplesCount ;
2018-12-25 15:18:35 +01:00
}
2019-09-03 23:08:02 +02:00
else sampleLeft - = samplesCount ;
2017-11-12 21:55:24 +10:00
2019-09-03 23:08:02 +02:00
if ( sampleLeft < = 0 )
2017-11-12 21:55:24 +10:00
{
streamEnding = true ;
break ;
}
}
// Free allocated pcm data
2019-04-23 14:55:35 +02:00
RL_FREE ( pcm ) ;
2017-11-12 21:55:24 +10:00
// Reset audio stream for looping
if ( streamEnding )
{
StopMusicStream ( music ) ; // Stop music (and reset)
2018-11-06 15:10:50 +01:00
2017-11-12 21:55:24 +10:00
// Decrease loopCount to stop when required
2019-07-26 10:26:39 +02:00
if ( music . loopCount > 1 )
2017-11-12 21:55:24 +10:00
{
2019-09-03 23:08:02 +02:00
music . loopCount - - ; // Decrease loop count
2017-11-12 21:55:24 +10:00
PlayMusicStream ( music ) ; // Play again
}
2019-09-03 23:08:02 +02:00
else if ( music . loopCount = = 0 ) PlayMusicStream ( music ) ;
2017-11-12 21:55:24 +10:00
}
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 ) ;
}
2014-04-19 16:36:49 +02:00
}
2016-05-11 18:14:59 -07:00
// Check if any music is playing
2016-08-01 12:49:17 +02:00
bool IsMusicPlaying ( Music music )
2014-04-09 20:25:26 +02:00
{
2019-07-26 10:26:39 +02:00
return IsAudioStreamPlaying ( music . stream ) ;
2014-04-09 20:25:26 +02:00
}
2014-04-19 16:36:49 +02:00
// Set volume for music
2016-08-01 12:49:17 +02:00
void SetMusicVolume ( Music music , float volume )
2014-01-23 12:36:18 +01:00
{
2019-07-26 10:26:39 +02:00
SetAudioStreamVolume ( music . stream , volume ) ;
2016-05-11 18:14:59 -07:00
}
2016-06-02 17:12:31 +02:00
// Set pitch for music
2016-08-01 12:49:17 +02:00
void SetMusicPitch ( Music music , float pitch )
2016-05-11 18:14:59 -07:00
{
2019-07-26 10:26:39 +02:00
SetAudioStreamPitch ( music . stream , pitch ) ;
2014-01-23 12:36:18 +01:00
}
2017-01-24 00:32:16 +01:00
2017-02-06 01:03:58 +01:00
// Set music loop count (loop repeats)
2019-08-08 23:08:54 +02:00
// NOTE: If set to 0, means infinite loop
2017-11-12 21:55:24 +10:00
void SetMusicLoopCount ( Music music , int count )
2017-02-06 01:03:58 +01:00
{
2019-07-26 10:26:39 +02:00
music . loopCount = count ;
2017-02-06 01:03:58 +01:00
}
2016-06-01 20:09:00 -07:00
// Get music time length (in seconds)
2016-08-01 12:49:17 +02:00
float GetMusicTimeLength ( Music music )
2014-01-23 12:36:18 +01:00
{
2019-01-02 14:05:20 +01:00
float totalSeconds = 0.0f ;
2019-02-21 18:45:19 +01:00
2019-07-26 10:26:39 +02:00
totalSeconds = ( float ) music . sampleCount / ( music . stream . sampleRate * music . stream . channels ) ;
2016-08-16 11:09:55 +02:00
2014-04-19 16:36:49 +02:00
return totalSeconds ;
}
// Get current music time played (in seconds)
2016-08-01 12:49:17 +02:00
float GetMusicTimePlayed ( Music music )
2014-04-19 16:36:49 +02:00
{
2016-05-21 18:08:09 +02:00
float secondsPlayed = 0.0f ;
2016-07-29 21:35:57 +02:00
2019-09-03 23:08:02 +02:00
//ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels;
unsigned int samplesPlayed = music . stream . buffer - > totalFramesProcessed * music . stream . channels ;
2019-07-26 10:26:39 +02:00
secondsPlayed = ( float ) samplesPlayed / ( music . stream . sampleRate * music . stream . channels ) ;
2016-08-01 12:49:17 +02:00
return secondsPlayed ;
}
// Init audio stream (to stream audio pcm data)
2016-08-02 17:32:24 +02:00
AudioStream InitAudioStream ( unsigned int sampleRate , unsigned int sampleSize , unsigned int channels )
2016-08-01 12:49:17 +02:00
{
AudioStream stream = { 0 } ;
2016-08-16 11:09:55 +02:00
2016-08-01 12:49:17 +02:00
stream . sampleRate = sampleRate ;
stream . sampleSize = sampleSize ;
2019-09-03 23:08:02 +02:00
stream . channels = channels ;
2016-08-01 12:49:17 +02:00
2019-03-12 11:54:45 +01:00
ma_format formatIn = ( ( stream . sampleSize = = 8 ) ? ma_format_u8 : ( ( stream . sampleSize = = 16 ) ? ma_format_s16 : ma_format_f32 ) ) ;
2017-11-12 20:59:16 +10:00
2019-07-23 22:21:01 +02:00
// The size of a streaming buffer must be at least double the size of a period
2020-02-10 12:42:40 +01:00
unsigned int periodSize = AUDIO . System . device . playback . internalPeriodSizeInFrames / AUDIO . System . device . playback . internalPeriods ;
2017-11-24 21:54:00 +10:00
unsigned int subBufferSize = AUDIO_BUFFER_SIZE ;
2019-10-17 17:18:03 +02:00
2017-12-20 11:37:43 +01:00
if ( subBufferSize < periodSize ) subBufferSize = periodSize ;
2017-11-24 21:54:00 +10:00
2019-09-03 23:08:02 +02:00
stream . buffer = InitAudioBuffer ( formatIn , stream . channels , stream . sampleRate , subBufferSize * 2 , AUDIO_BUFFER_USAGE_STREAM ) ;
2019-10-17 17:18:03 +02:00
2019-09-03 23:08:02 +02:00
if ( stream . buffer ! = NULL )
2017-11-12 20:59:16 +10:00
{
2019-09-03 23:08:02 +02:00
stream . buffer - > looping = true ; // Always loop for streaming buffers
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Audio stream loaded successfully (%i Hz, %i bit, %s) " , stream . sampleRate , stream . sampleSize , ( stream . channels = = 1 ) ? " Mono " : " Stereo " ) ;
2017-11-12 20:59:16 +10:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " InitAudioStream() : Failed to create audio buffer " ) ;
2016-08-01 12:49:17 +02:00
return stream ;
2014-04-19 16:36:49 +02:00
}
2016-08-01 12:49:17 +02:00
// Close audio stream and free memory
2016-08-02 17:32:24 +02:00
void CloseAudioStream ( AudioStream stream )
2016-08-01 12:49:17 +02:00
{
2019-07-23 22:21:01 +02:00
CloseAudioBuffer ( stream . buffer ) ;
2018-11-06 15:10:50 +01:00
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " Unloaded audio stream data " ) ;
2016-08-01 12:49:17 +02:00
}
2016-08-02 17:32:24 +02:00
// Update audio stream buffers with data
2017-05-10 19:34:57 +02:00
// NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue
2019-08-13 17:41:31 +02:00
// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioStreamProcessed()
2017-02-09 22:19:48 +01:00
void UpdateAudioStream ( AudioStream stream , const void * data , int samplesCount )
2016-08-02 17:32:24 +02:00
{
2019-07-23 22:21:01 +02:00
AudioBuffer * audioBuffer = stream . buffer ;
2019-10-17 17:18:03 +02:00
2019-09-03 23:08:02 +02:00
if ( audioBuffer ! = NULL )
2017-11-12 20:59:16 +10:00
{
2019-09-03 23:08:02 +02:00
if ( audioBuffer - > isSubBufferProcessed [ 0 ] | | audioBuffer - > isSubBufferProcessed [ 1 ] )
{
ma_uint32 subBufferToUpdate = 0 ;
2017-11-12 20:59:16 +10:00
2019-09-03 23:08:02 +02:00
if ( audioBuffer - > isSubBufferProcessed [ 0 ] & & audioBuffer - > isSubBufferProcessed [ 1 ] )
{
2019-10-17 17:18:03 +02:00
// Both buffers are available for updating.
2019-09-03 23:08:02 +02:00
// 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 ;
}
2018-11-06 15:10:50 +01:00
2020-02-03 18:31:30 +01:00
ma_uint32 subBufferSizeInFrames = audioBuffer - > sizeInFrames / 2 ;
unsigned char * subBuffer = audioBuffer - > data + ( ( subBufferSizeInFrames * stream . channels * ( stream . sampleSize / 8 ) ) * subBufferToUpdate ) ;
2017-11-12 20:59:16 +10:00
2019-09-03 23:08:02 +02:00
// TODO: Get total frames processed on this buffer... DOES NOT WORK.
audioBuffer - > totalFramesProcessed + = subBufferSizeInFrames ;
2017-11-12 20:59:16 +10:00
2019-10-17 17:18:03 +02:00
// Does this API expect a whole buffer to be updated in one go?
2019-09-03 23:08:02 +02:00
// Assuming so, but if not will need to change this logic.
if ( subBufferSizeInFrames > = ( ma_uint32 ) samplesCount / stream . channels )
{
ma_uint32 framesToWrite = subBufferSizeInFrames ;
2018-11-06 15:10:50 +01:00
2019-09-03 23:08:02 +02:00
if ( framesToWrite > ( ( ma_uint32 ) samplesCount / stream . channels ) ) framesToWrite = ( ma_uint32 ) samplesCount / stream . channels ;
2017-11-12 20:59:16 +10:00
2019-09-03 23:08:02 +02:00
ma_uint32 bytesToWrite = framesToWrite * stream . channels * ( stream . sampleSize / 8 ) ;
memcpy ( subBuffer , data , bytesToWrite ) ;
2017-11-12 20:59:16 +10:00
2019-09-03 23:08:02 +02:00
// Any leftover frames should be filled with zeros.
ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite ;
2018-11-06 15:10:50 +01:00
2019-09-03 23:08:02 +02:00
if ( leftoverFrameCount > 0 ) memset ( subBuffer + bytesToWrite , 0 , leftoverFrameCount * stream . channels * ( stream . sampleSize / 8 ) ) ;
2017-11-12 20:59:16 +10:00
2019-09-03 23:08:02 +02:00
audioBuffer - > isSubBufferProcessed [ subBufferToUpdate ] = false ;
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " UpdateAudioStream() : Attempting to write too many frames to buffer " ) ;
2017-11-12 20:59:16 +10:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " UpdateAudioStream() : Audio buffer not available for updating " ) ;
2017-11-12 20:59:16 +10:00
}
2020-02-03 19:13:24 +01:00
else TRACELOG ( LOG_ERROR , " UpdateAudioStream() : No audio buffer " ) ;
2016-08-02 17:32:24 +02:00
}
// Check if any audio stream buffers requires refill
2019-08-13 17:41:31 +02:00
bool IsAudioStreamProcessed ( AudioStream stream )
2016-08-02 17:32:24 +02:00
{
2019-07-23 22:21:01 +02:00
if ( stream . buffer = = NULL )
2017-11-12 20:59:16 +10:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , " IsAudioStreamProcessed() : No audio buffer " ) ;
2017-11-12 20:59:16 +10:00
return false ;
}
2019-07-23 22:21:01 +02:00
return ( stream . buffer - > isSubBufferProcessed [ 0 ] | | stream . buffer - > isSubBufferProcessed [ 1 ] ) ;
2016-08-01 12:49:17 +02:00
}
2014-04-19 16:36:49 +02:00
2016-08-02 17:32:24 +02:00
// Play audio stream
void PlayAudioStream ( AudioStream stream )
2014-04-19 16:36:49 +02:00
{
2019-07-23 22:21:01 +02:00
PlayAudioBuffer ( stream . buffer ) ;
2016-08-02 17:32:24 +02:00
}
2016-07-29 21:35:57 +02:00
2016-08-02 17:32:24 +02:00
// Play audio stream
void PauseAudioStream ( AudioStream stream )
{
2019-07-23 22:21:01 +02:00
PauseAudioBuffer ( stream . buffer ) ;
2016-08-02 17:32:24 +02:00
}
2016-07-29 21:35:57 +02:00
2016-08-02 17:32:24 +02:00
// Resume audio stream playing
void ResumeAudioStream ( AudioStream stream )
{
2019-07-23 22:21:01 +02:00
ResumeAudioBuffer ( stream . buffer ) ;
2014-04-19 16:36:49 +02:00
}
2017-11-12 21:55:24 +10:00
// Check if audio stream is playing.
bool IsAudioStreamPlaying ( AudioStream stream )
{
2019-07-23 22:21:01 +02:00
return IsAudioBufferPlaying ( stream . buffer ) ;
2017-11-12 21:55:24 +10:00
}
2016-08-02 17:32:24 +02:00
// Stop audio stream
void StopAudioStream ( AudioStream stream )
{
2019-07-23 22:21:01 +02:00
StopAudioBuffer ( stream . buffer ) ;
2016-08-02 17:32:24 +02:00
}
2017-11-14 21:15:50 +10:00
void SetAudioStreamVolume ( AudioStream stream , float volume )
{
2019-07-23 22:21:01 +02:00
SetAudioBufferVolume ( stream . buffer , volume ) ;
2017-11-14 21:15:50 +10:00
}
void SetAudioStreamPitch ( AudioStream stream , float pitch )
{
2019-07-23 22:21:01 +02:00
SetAudioBufferPitch ( stream . buffer , pitch ) ;
2017-11-14 21:15:50 +10:00
}
2016-08-02 17:32:24 +02:00
//----------------------------------------------------------------------------------
// Module specific Functions Definition
//----------------------------------------------------------------------------------
2020-02-03 18:31:30 +01:00
// Log callback function
static void OnLog ( ma_context * pContext , ma_device * pDevice , ma_uint32 logLevel , const char * message )
{
( void ) pContext ;
( void ) pDevice ;
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_ERROR , message ) ; // All log messages from miniaudio are errors
2020-02-03 18:31:30 +01:00
}
2020-02-04 22:43:31 +10:00
// Reads audio data from an AudioBuffer object in internal format.
static ma_uint32 ReadAudioBufferFramesInInternalFormat ( AudioBuffer * audioBuffer , void * framesOut , ma_uint32 frameCount )
{
ma_uint32 subBufferSizeInFrames = ( audioBuffer - > sizeInFrames > 1 ) ? audioBuffer - > sizeInFrames / 2 : audioBuffer - > sizeInFrames ;
ma_uint32 currentSubBufferIndex = audioBuffer - > frameCursorPos / subBufferSizeInFrames ;
if ( currentSubBufferIndex > 1 )
{
TRACELOGD ( " 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 ] ;
ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame ( audioBuffer - > converter . config . formatIn , audioBuffer - > converter . config . channelsIn ) ;
// Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0
ma_uint32 framesRead = 0 ;
while ( 1 )
{
// 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 ;
}
ma_uint32 totalFramesRemaining = ( frameCount - framesRead ) ;
if ( totalFramesRemaining = = 0 ) break ;
ma_uint32 framesRemainingInOutputBuffer ;
if ( audioBuffer - > usage = = AUDIO_BUFFER_USAGE_STATIC )
{
framesRemainingInOutputBuffer = audioBuffer - > sizeInFrames - audioBuffer - > frameCursorPos ;
}
else
{
ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex ;
framesRemainingInOutputBuffer = subBufferSizeInFrames - ( audioBuffer - > frameCursorPos - firstFrameIndexOfThisSubBuffer ) ;
}
ma_uint32 framesToRead = totalFramesRemaining ;
if ( framesToRead > framesRemainingInOutputBuffer ) framesToRead = framesRemainingInOutputBuffer ;
memcpy ( ( unsigned char * ) framesOut + ( framesRead * frameSizeInBytes ) , audioBuffer - > data + ( audioBuffer - > frameCursorPos * frameSizeInBytes ) , framesToRead * frameSizeInBytes ) ;
audioBuffer - > frameCursorPos = ( audioBuffer - > frameCursorPos + framesToRead ) % audioBuffer - > sizeInFrames ;
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
ma_uint32 totalFramesRemaining = ( frameCount - framesRead ) ;
if ( totalFramesRemaining > 0 )
{
memset ( ( unsigned char * ) framesOut + ( 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 ;
}
// Reads audio data from an AudioBuffer object in device format. Returned data will be in a format appropriate for mixing.
static ma_uint32 ReadAudioBufferFramesInMixingFormat ( AudioBuffer * audioBuffer , float * framesOut , ma_uint32 frameCount )
{
// What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which
// should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important
// detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output
// frames. This can be achieved with ma_data_converter_get_required_input_frame_count().
ma_uint8 inputBuffer [ 4096 ] ;
ma_uint32 inputBufferFrameCap = sizeof ( inputBuffer ) / ma_get_bytes_per_frame ( audioBuffer - > converter . config . formatIn , audioBuffer - > converter . config . channelsIn ) ;
ma_uint32 totalOutputFramesProcessed = 0 ;
while ( totalOutputFramesProcessed < frameCount )
{
ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed ;
ma_uint64 inputFramesToProcessThisIteration = ma_data_converter_get_required_input_frame_count ( & audioBuffer - > converter , outputFramesToProcessThisIteration ) ;
if ( inputFramesToProcessThisIteration > inputBufferFrameCap )
{
inputFramesToProcessThisIteration = inputBufferFrameCap ;
}
float * runningFramesOut = framesOut + ( totalOutputFramesProcessed * audioBuffer - > converter . config . channelsOut ) ;
/* At this point we can convert the data to our mixing format. */
ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat ( audioBuffer , inputBuffer , ( ma_uint32 ) inputFramesToProcessThisIteration ) ; /* Safe cast. */
ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration ;
ma_data_converter_process_pcm_frames ( & audioBuffer - > converter , inputBuffer , & inputFramesProcessedThisIteration , runningFramesOut , & outputFramesProcessedThisIteration ) ;
totalOutputFramesProcessed + = ( ma_uint32 ) outputFramesProcessedThisIteration ; /* Safe cast. */
if ( inputFramesProcessedThisIteration < inputFramesToProcessThisIteration )
{
break ; /* Ran out of input data. */
}
/* This should never be hit, but will add it here for safety. Ensures we get out of the loop when no input nor output frames are processed. */
if ( inputFramesProcessedThisIteration = = 0 & & outputFramesProcessedThisIteration = = 0 )
{
break ;
}
}
return totalOutputFramesProcessed ;
}
2020-02-03 18:31:30 +01:00
// Sending audio data to device callback function
// NOTE: All the mixing takes place here
static void OnSendAudioDataToDevice ( ma_device * pDevice , void * pFramesOut , const void * pFramesInput , ma_uint32 frameCount )
{
( void ) pDevice ;
// Mixing is basically just an accumulation, we need to initialize the output buffer to 0
memset ( pFramesOut , 0 , frameCount * pDevice - > playback . channels * ma_get_bytes_per_sample ( pDevice - > playback . 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
ma_mutex_lock ( & AUDIO . System . lock ) ;
{
for ( AudioBuffer * audioBuffer = AUDIO . Buffer . first ; audioBuffer ! = NULL ; audioBuffer = audioBuffer - > next )
{
// Ignore stopped or paused sounds
if ( ! audioBuffer - > playing | | audioBuffer - > paused ) continue ;
ma_uint32 framesRead = 0 ;
while ( 1 )
{
if ( framesRead > frameCount )
{
2020-02-03 19:13:24 +01:00
TRACELOGD ( " Mixed too many frames from audio buffer " ) ;
2020-02-03 18:31:30 +01:00
break ;
}
if ( framesRead = = frameCount ) break ;
// Just read as much data as we can from the stream
ma_uint32 framesToRead = ( frameCount - framesRead ) ;
while ( framesToRead > 0 )
{
float tempBuffer [ 1024 ] ; // 512 frames for stereo
ma_uint32 framesToReadRightNow = framesToRead ;
if ( framesToReadRightNow > sizeof ( tempBuffer ) / sizeof ( tempBuffer [ 0 ] ) / DEVICE_CHANNELS )
{
framesToReadRightNow = sizeof ( tempBuffer ) / sizeof ( tempBuffer [ 0 ] ) / DEVICE_CHANNELS ;
}
2020-02-04 22:43:31 +10:00
ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat ( audioBuffer , tempBuffer , framesToReadRightNow ) ;
2020-02-03 18:31:30 +01:00
if ( framesJustRead > 0 )
{
float * framesOut = ( float * ) pFramesOut + ( framesRead * AUDIO . System . device . playback . channels ) ;
float * framesIn = tempBuffer ;
MixAudioFrames ( framesOut , framesIn , framesJustRead , audioBuffer - > volume ) ;
framesToRead - = framesJustRead ;
framesRead + = framesJustRead ;
}
2020-02-03 19:26:28 +01:00
2020-02-03 18:31:30 +01:00
if ( ! audioBuffer - > playing )
{
framesRead = frameCount ;
break ;
}
// 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 ;
}
}
}
ma_mutex_unlock ( & AUDIO . System . lock ) ;
}
// 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 , ma_uint32 frameCount , float localVolume )
{
for ( ma_uint32 iFrame = 0 ; iFrame < frameCount ; + + iFrame )
{
for ( ma_uint32 iChannel = 0 ; iChannel < AUDIO . System . device . playback . channels ; + + iChannel )
{
float * frameOut = framesOut + ( iFrame * AUDIO . System . device . playback . channels ) ;
const float * frameIn = framesIn + ( iFrame * AUDIO . System . device . playback . channels ) ;
2020-02-04 22:43:31 +10:00
frameOut [ iChannel ] + = ( frameIn [ iChannel ] * localVolume ) ;
2020-02-03 18:31:30 +01:00
}
}
}
// Initialise the multichannel buffer pool
static void InitAudioBufferPool ( void )
{
// Dummy buffers
for ( int i = 0 ; i < MAX_AUDIO_BUFFER_POOL_CHANNELS ; i + + )
{
AUDIO . MultiChannel . pool [ i ] = InitAudioBuffer ( DEVICE_FORMAT , DEVICE_CHANNELS , DEVICE_SAMPLE_RATE , 0 , AUDIO_BUFFER_USAGE_STATIC ) ;
}
}
// Close the audio buffers pool
static void CloseAudioBufferPool ( void )
{
2020-02-03 19:26:28 +01:00
for ( int i = 0 ; i < MAX_AUDIO_BUFFER_POOL_CHANNELS ; i + + )
2020-02-03 18:31:30 +01:00
{
RL_FREE ( AUDIO . MultiChannel . pool [ i ] - > data ) ;
RL_FREE ( AUDIO . MultiChannel . pool [ i ] ) ;
}
}
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_WAV)
2013-11-18 23:38:44 +01:00
// Load WAV file into Wave structure
2014-04-19 16:36:49 +02:00
static Wave LoadWAV ( const char * fileName )
2013-11-18 23:38:44 +01:00
{
2013-12-01 12:34:31 +01:00
// Basic WAV headers structs
typedef struct {
char chunkID [ 4 ] ;
2014-11-09 08:06:58 +01:00
int chunkSize ;
2013-12-01 12:34:31 +01:00
char format [ 4 ] ;
2016-12-27 17:37:35 +01:00
} WAVRiffHeader ;
2013-12-01 12:34:31 +01:00
typedef struct {
char subChunkID [ 4 ] ;
2014-11-09 08:06:58 +01:00
int subChunkSize ;
2013-12-01 12:34:31 +01:00
short audioFormat ;
short numChannels ;
2014-11-09 08:06:58 +01:00
int sampleRate ;
int byteRate ;
2013-12-01 12:34:31 +01:00
short blockAlign ;
short bitsPerSample ;
2016-12-27 17:37:35 +01:00
} WAVFormat ;
2013-12-01 12:34:31 +01:00
typedef struct {
char subChunkID [ 4 ] ;
2014-11-09 08:06:58 +01:00
int subChunkSize ;
2016-12-27 17:37:35 +01:00
} WAVData ;
2014-09-03 16:51:28 +02:00
2020-02-09 13:23:12 +01:00
WAVRiffHeader wavRiffHeader = { 0 } ;
WAVFormat wavFormat = { 0 } ;
WAVData wavData = { 0 } ;
2014-09-03 16:51:28 +02:00
2016-01-23 13:22:13 +01:00
Wave wave = { 0 } ;
2020-01-28 16:40:12 +01:00
FILE * wavFile = NULL ;
2014-09-03 16:51:28 +02:00
2013-11-18 23:38:44 +01:00
wavFile = fopen ( fileName , " rb " ) ;
2014-09-03 16:51:28 +02:00
2014-12-31 18:03:32 +01:00
if ( wavFile = = NULL )
2013-11-23 13:30:54 +01:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " [%s] WAV file could not be opened " , fileName ) ;
2015-07-31 12:31:39 +02:00
wave . data = NULL ;
2013-11-23 13:30:54 +01:00
}
2014-04-09 20:25:26 +02:00
else
{
// Read in the first chunk into the struct
2016-12-27 17:37:35 +01:00
fread ( & wavRiffHeader , sizeof ( WAVRiffHeader ) , 1 , wavFile ) ;
2014-09-03 16:51:28 +02:00
2014-04-09 20:25:26 +02:00
// Check for RIFF and WAVE tags
2020-02-04 16:55:24 +01:00
if ( ( wavRiffHeader . chunkID [ 0 ] ! = ' R ' ) | |
( wavRiffHeader . chunkID [ 1 ] ! = ' I ' ) | |
( wavRiffHeader . chunkID [ 2 ] ! = ' F ' ) | |
( wavRiffHeader . chunkID [ 3 ] ! = ' F ' ) | |
( wavRiffHeader . format [ 0 ] ! = ' W ' ) | |
( wavRiffHeader . format [ 1 ] ! = ' A ' ) | |
( wavRiffHeader . format [ 2 ] ! = ' V ' ) | |
( wavRiffHeader . format [ 3 ] ! = ' E ' ) )
2014-04-09 20:25:26 +02:00
{
2020-02-09 13:23:12 +01:00
TRACELOG ( LOG_WARNING , " [%s] Invalid RIFF or WAVE Header " , fileName ) ;
2014-04-09 20:25:26 +02:00
}
else
{
// Read in the 2nd chunk for the wave info
2016-12-27 17:37:35 +01:00
fread ( & wavFormat , sizeof ( WAVFormat ) , 1 , wavFile ) ;
2014-09-03 16:51:28 +02:00
2014-04-09 20:25:26 +02:00
// Check for fmt tag
2016-12-25 01:58:56 +01:00
if ( ( wavFormat . subChunkID [ 0 ] ! = ' f ' ) | | ( wavFormat . subChunkID [ 1 ] ! = ' m ' ) | |
( wavFormat . subChunkID [ 2 ] ! = ' t ' ) | | ( wavFormat . subChunkID [ 3 ] ! = ' ' ) )
2014-04-09 20:25:26 +02:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " [%s] Invalid Wave format " , fileName ) ;
2014-04-09 20:25:26 +02:00
}
else
{
// Check for extra parameters;
2016-12-25 01:58:56 +01:00
if ( wavFormat . subChunkSize > 16 ) fseek ( wavFile , sizeof ( short ) , SEEK_CUR ) ;
2014-09-03 16:51:28 +02:00
2014-04-09 20:25:26 +02:00
// Read in the the last byte of data before the sound file
2016-12-27 17:37:35 +01:00
fread ( & wavData , sizeof ( WAVData ) , 1 , wavFile ) ;
2014-09-03 16:51:28 +02:00
2014-04-09 20:25:26 +02:00
// Check for data tag
2016-12-25 01:58:56 +01:00
if ( ( wavData . subChunkID [ 0 ] ! = ' d ' ) | | ( wavData . subChunkID [ 1 ] ! = ' a ' ) | |
( wavData . subChunkID [ 2 ] ! = ' t ' ) | | ( wavData . subChunkID [ 3 ] ! = ' a ' ) )
2014-04-09 20:25:26 +02:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " [%s] Invalid data header " , fileName ) ;
2014-04-09 20:25:26 +02:00
}
else
{
// Allocate memory for data
2019-04-23 14:55:35 +02:00
wave . data = RL_MALLOC ( wavData . subChunkSize ) ;
2014-09-03 16:51:28 +02:00
2014-04-09 20:25:26 +02:00
// Read in the sound data into the soundData variable
2017-02-11 23:17:56 +01:00
fread ( wave . data , wavData . subChunkSize , 1 , wavFile ) ;
2014-09-03 16:51:28 +02:00
2016-12-20 00:33:45 +01:00
// Store wave parameters
2016-12-25 01:58:56 +01:00
wave . sampleRate = wavFormat . sampleRate ;
wave . sampleSize = wavFormat . bitsPerSample ;
wave . channels = wavFormat . numChannels ;
2017-01-28 23:02:30 +01:00
2017-01-19 13:18:04 +01:00
// NOTE: Only support 8 bit, 16 bit and 32 bit sample sizes
if ( ( wave . sampleSize ! = 8 ) & & ( wave . sampleSize ! = 16 ) & & ( wave . sampleSize ! = 32 ) )
2016-12-25 01:58:56 +01:00
{
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " [%s] WAV sample size (%ibit) not supported, converted to 16bit " , fileName , wave . sampleSize ) ;
2017-01-19 13:18:04 +01:00
WaveFormat ( & wave , wave . sampleRate , 16 , wave . channels ) ;
2016-12-25 01:58:56 +01:00
}
2017-01-19 13:18:04 +01:00
2016-12-25 01:58:56 +01:00
// NOTE: Only support up to 2 channels (mono, stereo)
2017-01-28 23:02:30 +01:00
if ( wave . channels > 2 )
2016-12-25 01:58:56 +01:00
{
WaveFormat ( & wave , wave . sampleRate , wave . sampleSize , 2 ) ;
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_WARNING , " [%s] WAV channels number (%i) not supported, converted to 2 channels " , fileName , wave . channels ) ;
2016-12-25 01:58:56 +01:00
}
2017-01-28 23:02:30 +01:00
2016-12-20 00:33:45 +01:00
// NOTE: subChunkSize comes in bytes, we need to translate it to number of samples
2016-12-25 01:58:56 +01:00
wave . sampleCount = ( wavData . subChunkSize / ( wave . sampleSize / 8 ) ) / wave . channels ;
2014-09-03 16:51:28 +02:00
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " [%s] WAV file loaded successfully (%i Hz, %i bit, %s) " , fileName , wave . sampleRate , wave . sampleSize , ( wave . channels = = 1 ) ? " Mono " : " Stereo " ) ;
2014-04-09 20:25:26 +02:00
}
}
}
2013-11-23 13:30:54 +01:00
2014-04-09 20:25:26 +02:00
fclose ( wavFile ) ;
}
2014-09-03 16:51:28 +02:00
2013-11-23 13:30:54 +01:00
return wave ;
2013-12-01 12:34:31 +01:00
}
2018-10-29 16:18:06 +01:00
// 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 ;
2018-11-06 15:10:50 +01:00
2018-10-29 16:18:06 +01:00
// 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 " ) ;
2018-11-06 15:10:50 +01:00
2020-02-03 19:13:24 +01:00
if ( wavFile = = NULL ) TRACELOG ( LOG_WARNING , " [%s] WAV audio file could not be created " , fileName ) ;
2018-10-29 16:18:06 +01:00
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 ;
2018-11-06 15:10:50 +01:00
2019-10-03 17:06:08 +02:00
fwrite ( & riffHeader , sizeof ( RiffHeader ) , 1 , wavFile ) ;
fwrite ( & waveFormat , sizeof ( WaveFormat ) , 1 , wavFile ) ;
fwrite ( & waveData , sizeof ( WaveData ) , 1 , wavFile ) ;
2018-10-29 16:18:06 +01:00
success = fwrite ( wave . data , dataSize , 1 , wavFile ) ;
fclose ( wavFile ) ;
}
2018-11-06 15:10:50 +01:00
2018-10-29 16:18:06 +01:00
// If all data has been written correctly to file, success = 1
return success ;
}
2017-03-26 22:49:01 +02:00
# endif
2013-11-18 23:38:44 +01:00
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_OGG)
2014-04-19 16:36:49 +02:00
// Load OGG file into Wave structure
2014-09-16 22:51:31 +02:00
// NOTE: Using stb_vorbis library
2016-09-08 00:20:06 +02:00
static Wave LoadOGG ( const char * fileName )
2013-11-18 23:38:44 +01:00
{
2017-05-03 14:16:53 +02:00
Wave wave = { 0 } ;
2014-09-03 16:51:28 +02:00
2014-04-19 16:36:49 +02:00
stb_vorbis * oggFile = stb_vorbis_open_filename ( fileName , NULL , NULL ) ;
2014-09-03 16:51:28 +02:00
2020-02-03 19:13:24 +01:00
if ( oggFile = = NULL ) TRACELOG ( LOG_WARNING , " [%s] OGG file could not be opened " , fileName ) ;
2015-07-31 12:31:39 +02:00
else
{
stb_vorbis_info info = stb_vorbis_get_info ( oggFile ) ;
2018-11-06 15:10:50 +01:00
2015-07-31 12:31:39 +02:00
wave . sampleRate = info . sample_rate ;
2016-08-15 16:35:11 +02:00
wave . sampleSize = 16 ; // 16 bit per sample (short)
2015-07-31 12:31:39 +02:00
wave . channels = info . channels ;
2018-11-23 11:58:45 +01:00
wave . sampleCount = ( unsigned int ) stb_vorbis_stream_length_in_samples ( oggFile ) * info . channels ; // Independent by channel
2017-01-28 23:02:30 +01:00
2015-07-31 12:31:39 +02:00
float totalSeconds = stb_vorbis_stream_length_in_seconds ( oggFile ) ;
2020-02-03 19:13:24 +01:00
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 ) ;
2014-09-03 16:51:28 +02:00
2019-04-23 14:55:35 +02:00
wave . data = ( short * ) RL_MALLOC ( wave . sampleCount * wave . channels * sizeof ( short ) ) ;
2014-09-03 16:51:28 +02:00
2017-01-18 17:04:20 +01:00
// NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!)
2020-02-04 16:55:24 +01:00
//int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)wave.data, wave.sampleCount*wave.channels);
2020-02-09 13:23:12 +01:00
//TRACELOGD("[%s] Samples obtained: %i", fileName, numSamplesOgg);
2014-04-19 16:36:49 +02:00
2020-02-03 19:13:24 +01:00
TRACELOG ( LOG_INFO , " [%s] OGG file loaded successfully (%i Hz, %i bit, %s) " , fileName , wave . sampleRate , wave . sampleSize , ( wave . channels = = 1 ) ? " Mono " : " Stereo " ) ;
2015-07-31 12:31:39 +02:00
stb_vorbis_close ( oggFile ) ;
}
2014-09-03 16:51:28 +02:00
2014-04-19 16:36:49 +02:00
return wave ;
2014-04-09 20:25:26 +02:00
}
2017-03-26 22:49:01 +02:00
# endif
2013-11-18 23:38:44 +01:00
2017-03-26 22:49:01 +02:00
# if defined(SUPPORT_FILEFORMAT_FLAC)
2016-10-10 18:22:55 +02:00
// 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
2020-02-10 10:56:48 +01:00
unsigned long long int totalSampleCount ;
2019-09-03 23:24:09 +02:00
wave . data = drflac_open_file_and_read_pcm_frames_s16 ( fileName , & wave . channels , & wave . sampleRate , & totalSampleCount ) ;
2017-01-28 23:02:30 +01:00
2018-11-23 11:58:45 +01:00
wave . sampleCount = ( unsigned int ) totalSampleCount ;
2016-12-26 10:52:57 +01:00
wave . sampleSize = 16 ;
2017-01-28 23:02:30 +01:00
2016-12-25 01:58:56 +01:00
// NOTE: Only support up to 2 channels (mono, stereo)
2020-02-03 19:13:24 +01:00
if ( wave . channels > 2 ) TRACELOG ( LOG_WARNING , " [%s] FLAC channels number (%i) not supported " , fileName , wave . channels ) ;
2016-12-26 10:52:57 +01:00
2020-02-03 19:13:24 +01:00
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 " ) ;
2018-09-19 15:57:46 +02:00
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 )
{
2018-10-17 19:39:16 +02:00
Wave wave = { 0 } ;
2018-09-19 15:57:46 +02:00
// Decode an entire MP3 file in one go
2020-02-10 10:56:48 +01:00
unsigned long long int totalFrameCount = 0 ;
2018-10-17 19:39:16 +02:00
drmp3_config config = { 0 } ;
2018-10-31 17:04:24 +01:00
wave . data = drmp3_open_file_and_read_f32 ( fileName , & config , & totalFrameCount ) ;
2018-11-06 15:10:50 +01:00
2018-10-17 19:39:16 +02:00
wave . channels = config . outputChannels ;
wave . sampleRate = config . outputSampleRate ;
2018-10-31 17:04:24 +01:00
wave . sampleCount = ( int ) totalFrameCount * wave . channels ;
2018-10-17 19:39:16 +02:00
wave . sampleSize = 32 ;
2018-09-19 15:57:46 +02:00
// NOTE: Only support up to 2 channels (mono, stereo)
2020-02-03 19:13:24 +01:00
if ( wave . channels > 2 ) TRACELOG ( LOG_WARNING , " [%s] MP3 channels number (%i) not supported " , fileName , wave . channels ) ;
2018-09-19 15:57:46 +02:00
2020-02-03 19:13:24 +01:00
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 " ) ;
2017-01-28 23:02:30 +01:00
2016-10-10 18:22:55 +02:00
return wave ;
}
2017-03-26 22:49:01 +02:00
# endif
2016-10-10 18:22:55 +02:00
2015-07-31 12:31:39 +02:00
// Some required functions for audio standalone module version
2019-01-10 16:32:40 +01:00
# if defined(RAUDIO_STANDALONE)
2017-03-29 00:35:42 +02:00
// Check file extension
bool IsFileExtension ( const char * fileName , const char * ext )
2015-07-31 12:31:39 +02:00
{
2017-03-29 00:35:42 +02:00
bool result = false ;
const char * fileExt ;
2018-11-06 15:10:50 +01:00
2017-03-29 00:35:42 +02:00
if ( ( fileExt = strrchr ( fileName , ' . ' ) ) ! = NULL )
{
if ( strcmp ( fileExt , ext ) = = 0 ) result = true ;
}
return result ;
2015-07-31 12:31:39 +02:00
}
2017-07-02 12:35:13 +02:00
// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG)
2020-02-03 19:13:24 +01:00
void TRACELOG ( int msgType , const char * text , . . . )
2015-07-31 12:31:39 +02:00
{
va_list args ;
2017-07-02 12:35:13 +02:00
va_start ( args , text ) ;
2015-07-31 12:31:39 +02:00
2016-08-31 10:27:29 +02:00
switch ( msgType )
2015-07-31 12:31:39 +02:00
{
2017-07-02 12:35:13 +02:00
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 ;
2015-07-31 12:31:39 +02:00
default : break ;
}
2017-07-02 12:35:13 +02:00
vfprintf ( stdout , text , args ) ;
fprintf ( stdout , " \n " ) ;
2015-07-31 12:31:39 +02:00
2017-07-02 12:35:13 +02:00
va_end ( args ) ;
2015-07-31 12:31:39 +02:00
2017-07-02 12:35:13 +02:00
if ( msgType = = LOG_ERROR ) exit ( 1 ) ;
2015-07-31 12:31:39 +02:00
}
2017-02-11 23:34:41 +01:00
# endif
2019-02-12 15:53:34 +01:00
# undef AudioBuffer