diff --git a/raylib/audio.c b/raylib/audio.c index abb536f..22da816 100644 --- a/raylib/audio.c +++ b/raylib/audio.c @@ -18,9 +18,6 @@ * Define to use the module as standalone library (independently of raylib). * Required types and functions are defined in the same module. * -* #define USE_OPENAL_BACKEND -* Use OpenAL Soft audio backend -* * #define SUPPORT_FILEFORMAT_WAV * #define SUPPORT_FILEFORMAT_OGG * #define SUPPORT_FILEFORMAT_XM @@ -84,25 +81,9 @@ #include "utils.h" // Required for: fopen() Android mapping #endif -#if !defined(USE_OPENAL_BACKEND) - #define USE_MINI_AL 1 // Set to 1 to use mini_al; 0 to use OpenAL. -#endif - -#include "external/mini_al.h" // Implemented in mini_al.c. Cannot implement this here because it conflicts with Win32 APIs such as CloseWindow(), etc. - -#if !defined(USE_MINI_AL) || (USE_MINI_AL == 0) - #if defined(__APPLE__) - #include "OpenAL/al.h" // OpenAL basic header - #include "OpenAL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work) - #else - #include "AL/al.h" // OpenAL basic header - #include "AL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work) - //#include "AL/alext.h" // OpenAL extensions header, required for AL_EXT_FLOAT32 and AL_EXT_MCFORMATS - #endif - - // OpenAL extension: AL_EXT_FLOAT32 - Support for 32bit float samples - // OpenAL extension: AL_EXT_MCFORMATS - Support for multi-channel formats (Quad, 5.1, 6.1, 7.1) -#endif +#include "external/mini_al.h" // mini_al audio library + // NOTE: Cannot be implement here because it conflicts with + // Win32 APIs: Rectangle, CloseWindow(), ShowCursor(), PlaySoundA() #include // Required for: malloc(), free() #include // Required for: strcmp(), strncmp() @@ -134,7 +115,7 @@ #include "external/dr_mp3.h" // MP3 loading functions #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) #undef bool #endif @@ -149,25 +130,16 @@ // In case of music-stalls, just increase this number #define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb) -// Support uncompressed PCM data in 32-bit float IEEE format -// NOTE: This definition is included in "AL/alext.h", but some OpenAL implementations -// could not provide the extensions header (Android), so its defined here -#if !defined(AL_EXT_float32) - #define AL_EXT_float32 1 - #define AL_FORMAT_MONO_FLOAT32 0x10010 - #define AL_FORMAT_STEREO_FLOAT32 0x10011 -#endif - //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef enum { - MUSIC_AUDIO_OGG = 0, - MUSIC_AUDIO_FLAC, - MUSIC_AUDIO_MP3, - MUSIC_MODULE_XM, - MUSIC_MODULE_MOD +typedef enum { + MUSIC_AUDIO_OGG = 0, + MUSIC_AUDIO_FLAC, + MUSIC_AUDIO_MP3, + MUSIC_MODULE_XM, + MUSIC_MODULE_MOD } MusicContextType; // Music type (file streaming from memory) @@ -197,12 +169,12 @@ typedef struct MusicData { } MusicData; #if defined(AUDIO_STANDALONE) -typedef enum { - LOG_INFO = 0, - LOG_ERROR, - LOG_WARNING, - LOG_DEBUG, - LOG_OTHER +typedef enum { + LOG_INFO = 0, + LOG_ERROR, + LOG_WARNING, + LOG_DEBUG, + LOG_OTHER } TraceLogType; #endif @@ -215,7 +187,8 @@ typedef enum { // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_WAV) -static Wave LoadWAV(const char *fileName); // Load WAV file +static Wave LoadWAV(const char *fileName); // Load WAV file +static int SaveWAV(Wave wave, const char *fileName); // Save wave data as WAV file #endif #if defined(SUPPORT_FILEFORMAT_OGG) static Wave LoadOGG(const char *fileName); // Load OGG file @@ -235,8 +208,6 @@ void TraceLog(int msgType, const char *text, ...); // Show trace lo //---------------------------------------------------------------------------------- // mini_al AudioBuffer Functionality //---------------------------------------------------------------------------------- -#if USE_MINI_AL - #define DEVICE_FORMAT mal_format_f32 #define DEVICE_CHANNELS 2 #define DEVICE_SAMPLE_RATE 44100 @@ -270,7 +241,7 @@ static bool isAudioInitialized = MAL_FALSE; static float masterVolume = 1.0f; // Audio buffers are tracked in a linked list -static AudioBuffer *firstAudioBuffer = NULL; +static AudioBuffer *firstAudioBuffer = NULL; static AudioBuffer *lastAudioBuffer = NULL; // mini_al functions declaration @@ -299,7 +270,7 @@ static void OnLog(mal_context *pContext, mal_device *pDevice, const char *messag { (void)pContext; (void)pDevice; - + TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors } @@ -322,30 +293,30 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC if (!audioBuffer->playing || audioBuffer->paused) continue; mal_uint32 framesRead = 0; - for (;;) + for (;;) { - if (framesRead > frameCount) + if (framesRead > frameCount) { TraceLog(LOG_DEBUG, "Mixed too many frames from audio buffer"); break; } - + if (framesRead == frameCount) break; // Just read as much data as we can from the stream. mal_uint32 framesToRead = (frameCount - framesRead); - while (framesToRead > 0) + while (framesToRead > 0) { float tempBuffer[1024]; // 512 frames for stereo. mal_uint32 framesToReadRightNow = framesToRead; - if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS) + if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS) { framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/DEVICE_CHANNELS; } mal_uint32 framesJustRead = (mal_uint32)mal_dsp_read(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, audioBuffer->dsp.pUserData); - if (framesJustRead > 0) + if (framesJustRead > 0) { float *framesOut = (float *)pFramesOut + (framesRead*device.channels); float *framesIn = tempBuffer; @@ -356,16 +327,16 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC } // If we weren't able to read all the frames we requested, break. - if (framesJustRead < framesToReadRightNow) + if (framesJustRead < framesToReadRightNow) { - if (!audioBuffer->looping) + if (!audioBuffer->looping) { StopAudioBuffer(audioBuffer); break; - } - else + } + else { - // Should never get here, but just for safety, + // Should never get here, but just for safety, // move the cursor position back to the start and continue the loop. audioBuffer->frameCursorPos = 0; continue; @@ -373,13 +344,13 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC } } - // If for some reason we weren't able to read every frame we'll need to break from the loop. + // If for some reason we weren't able to read every frame we'll need to break from the loop. // Not doing this could theoretically put us into an infinite loop. if (framesToRead > 0) break; } } } - + mal_mutex_unlock(&audioLock); return frameCount; // We always output the same number of frames that were originally requested. @@ -392,8 +363,8 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; mal_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; - - if (currentSubBufferIndex > 1) + + if (currentSubBufferIndex > 1) { TraceLog(LOG_DEBUG, "Frame cursor position moved too far forward in audio stream"); return 0; @@ -412,11 +383,11 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi { // 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 (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { if (framesRead >= frameCount) break; - } - else + } + else { if (isSubBufferProcessed[currentSubBufferIndex]) break; } @@ -425,11 +396,11 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi if (totalFramesRemaining == 0) break; mal_uint32 framesRemainingInOutputBuffer; - if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { framesRemainingInOutputBuffer = audioBuffer->bufferSizeInFrames - audioBuffer->frameCursorPos; - } - else + } + else { mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); @@ -443,7 +414,7 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi framesRead += framesToRead; // If we've read to the end of the buffer, mark it as processed. - if (framesToRead == framesRemainingInOutputBuffer) + if (framesToRead == framesRemainingInOutputBuffer) { audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; isSubBufferProcessed[currentSubBufferIndex] = true; @@ -451,7 +422,7 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi currentSubBufferIndex = (currentSubBufferIndex + 1)%2; // We need to break from this loop if we're not looping. - if (!audioBuffer->looping) + if (!audioBuffer->looping) { StopAudioBuffer(audioBuffer); break; @@ -461,9 +432,9 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi // Zero-fill excess. mal_uint32 totalFramesRemaining = (frameCount - framesRead); - if (totalFramesRemaining > 0) + if (totalFramesRemaining > 0) { - memset((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + memset((unsigned char *)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); // For static buffers we can fill the remaining frames with silence for safety, but we don't want // to report those frames as "read". The reason for this is that the caller uses the return value @@ -478,9 +449,9 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi // NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 frameCount, float localVolume) { - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) + for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { - for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) + for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) { float *frameOut = framesOut + (iFrame*device.channels); const float *frameIn = framesIn + (iFrame*device.channels); @@ -489,7 +460,6 @@ static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 f } } } -#endif //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Device initialization and Closing @@ -497,7 +467,6 @@ static void MixAudioFrames(float *framesOut, const float *framesIn, mal_uint32 f // Initialize audio device void InitAudioDevice(void) { -#if USE_MINI_AL // Context. mal_context_config contextConfig = mal_context_config_init(OnLog); mal_result result = mal_context_init(NULL, 0, &contextConfig, &context); @@ -547,46 +516,12 @@ void InitAudioDevice(void) TraceLog(LOG_INFO, "Audio buffer size: %d", device.bufferSizeInFrames); isAudioInitialized = MAL_TRUE; -#else - // Open and initialize a device with default settings - ALCdevice *device = alcOpenDevice(NULL); - - if (!device) TraceLog(LOG_ERROR, "Audio device could not be opened"); - else - { - ALCcontext *context = alcCreateContext(device, NULL); - - if ((context == NULL) || (alcMakeContextCurrent(context) == ALC_FALSE)) - { - if (context != NULL) alcDestroyContext(context); - - alcCloseDevice(device); - - TraceLog(LOG_ERROR, "Could not initialize audio context"); - } - else - { - TraceLog(LOG_INFO, "Audio device and context initialized successfully: %s", alcGetString(device, ALC_DEVICE_SPECIFIER)); - - // Listener definition (just for 2D) - alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); - alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f); - alListener3f(AL_ORIENTATION, 0.0f, 0.0f, -1.0f); - - alListenerf(AL_GAIN, 1.0f); - - if (alIsExtensionPresent("AL_EXT_float32")) TraceLog(LOG_INFO, "[EXTENSION] AL_EXT_float32 supported"); - else TraceLog(LOG_INFO, "[EXTENSION] AL_EXT_float32 not supported"); - } - } -#endif } // Close the audio device for all contexts void CloseAudioDevice(void) { -#if USE_MINI_AL - if (!isAudioInitialized) + if (!isAudioInitialized) { TraceLog(LOG_WARNING, "Could not close audio device because it is not currently initialized"); return; @@ -595,18 +530,6 @@ void CloseAudioDevice(void) mal_mutex_uninit(&audioLock); mal_device_uninit(&device); mal_context_uninit(&context); -#else - ALCdevice *device; - ALCcontext *context = alcGetCurrentContext(); - - if (context == NULL) TraceLog(LOG_WARNING, "Could not get current audio context for closing"); - - device = alcGetContextsDevice(context); - - alcMakeContextCurrent(NULL); - alcDestroyContext(context); - alcCloseDevice(device); -#endif TraceLog(LOG_INFO, "Audio device closed successfully"); } @@ -614,20 +537,7 @@ void CloseAudioDevice(void) // Check if device has been initialized successfully bool IsAudioDeviceReady(void) { -#if USE_MINI_AL return isAudioInitialized; -#else - ALCcontext *context = alcGetCurrentContext(); - - if (context == NULL) return false; - else - { - ALCdevice *device = alcGetContextsDevice(context); - - if (device == NULL) return false; - else return true; - } -#endif } // Set master volume (listener) @@ -635,18 +545,14 @@ void SetMasterVolume(float volume) { if (volume < 0.0f) volume = 0.0f; else if (volume > 1.0f) volume = 1.0f; - -#if USE_MINI_AL + masterVolume = volume; -#else - alListenerf(AL_GAIN, volume); -#endif } //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Buffer management //---------------------------------------------------------------------------------- -#if USE_MINI_AL + // Create a new audio buffer. Initially filled with silence AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage) { @@ -670,7 +576,7 @@ AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint3 dspConfig.pUserData = audioBuffer; dspConfig.allowDynamicSampleRate = MAL_TRUE; // <-- Required for pitch shifting. mal_result resultMAL = mal_dsp_init(&dspConfig, &audioBuffer->dsp); - if (resultMAL != MAL_SUCCESS) + if (resultMAL != MAL_SUCCESS) { TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to create data conversion pipeline"); free(audioBuffer); @@ -812,10 +718,10 @@ void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch) void TrackAudioBuffer(AudioBuffer *audioBuffer) { mal_mutex_lock(&audioLock); - + { if (firstAudioBuffer == NULL) firstAudioBuffer = audioBuffer; - else + else { lastAudioBuffer->next = audioBuffer; audioBuffer->prev = lastAudioBuffer; @@ -823,7 +729,7 @@ void TrackAudioBuffer(AudioBuffer *audioBuffer) lastAudioBuffer = audioBuffer; } - + mal_mutex_unlock(&audioLock); } @@ -831,7 +737,7 @@ void TrackAudioBuffer(AudioBuffer *audioBuffer) void UntrackAudioBuffer(AudioBuffer *audioBuffer) { mal_mutex_lock(&audioLock); - + { if (audioBuffer->prev == NULL) firstAudioBuffer = audioBuffer->next; else audioBuffer->prev->next = audioBuffer->next; @@ -842,10 +748,9 @@ void UntrackAudioBuffer(AudioBuffer *audioBuffer) audioBuffer->prev = NULL; audioBuffer->next = NULL; } - + mal_mutex_unlock(&audioLock); } -#endif //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) @@ -911,17 +816,16 @@ Sound LoadSoundFromWave(Wave wave) if (wave.data != NULL) { -#if USE_MINI_AL // When using mini_al we need to do our own mixing. To simplify this we need convert the format of each sound to be consistent with // the format used to open the playback device. We can do this two ways: - // + // // 1) Convert the whole sound in one go at load time (here). // 2) Convert the audio data in chunks at mixing time. // - // I have decided on the first option because it offloads work required for the format conversion to the to the loading stage. The - // downside to this is that it uses more memory if the original sound is u8 or s16. + // I have decided on the first option because it offloads work required for the format conversion to the to the loading stage. + // The downside to this is that it uses more memory if the original sound is u8 or s16. mal_format formatIn = ((wave.sampleSize == 8) ? mal_format_u8 : ((wave.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - mal_uint32 frameCountIn = wave.sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. + mal_uint32 frameCountIn = wave.sampleCount/wave.channels; mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to get frame count for format conversion"); @@ -933,61 +837,6 @@ Sound LoadSoundFromWave(Wave wave) if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Format conversion failed"); sound.audioBuffer = audioBuffer; -#else - ALenum format = 0; - - // The OpenAL format is worked out by looking at the number of channels and the sample size (bits per sample) - if (wave.channels == 1) - { - switch (wave.sampleSize) - { - case 8: format = AL_FORMAT_MONO8; break; - case 16: format = AL_FORMAT_MONO16; break; - case 32: format = AL_FORMAT_MONO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 - default: TraceLog(LOG_WARNING, "Wave sample size not supported: %i", wave.sampleSize); break; - } - } - else if (wave.channels == 2) - { - switch (wave.sampleSize) - { - case 8: format = AL_FORMAT_STEREO8; break; - case 16: format = AL_FORMAT_STEREO16; break; - case 32: format = AL_FORMAT_STEREO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 - default: TraceLog(LOG_WARNING, "Wave sample size not supported: %i", wave.sampleSize); break; - } - } - else TraceLog(LOG_WARNING, "Wave number of channels not supported: %i", wave.channels); - - // Create an audio source - ALuint source; - alGenSources(1, &source); // Generate pointer to audio source - - alSourcef(source, AL_PITCH, 1.0f); - alSourcef(source, AL_GAIN, 1.0f); - alSource3f(source, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - alSourcei(source, AL_LOOPING, AL_FALSE); - - // Convert loaded data to OpenAL buffer - //---------------------------------------- - ALuint buffer; - alGenBuffers(1, &buffer); // Generate pointer to buffer - - unsigned int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; // Size in bytes - - // Upload sound data to buffer - alBufferData(buffer, format, wave.data, dataSize, wave.sampleRate); - - // Attach sound buffer to source - alSourcei(source, AL_BUFFER, buffer); - - TraceLog(LOG_INFO, "[SND ID %i][BUFR ID %i] Sound data loaded successfully (%i Hz, %i bit, %s)", source, buffer, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); - - sound.source = source; - sound.buffer = buffer; - sound.format = format; -#endif } return sound; @@ -1004,14 +853,7 @@ void UnloadWave(Wave wave) // Unload sound void UnloadSound(Sound sound) { -#if USE_MINI_AL DeleteAudioBuffer((AudioBuffer *)sound.audioBuffer); -#else - alSourceStop(sound.source); - - alDeleteSources(1, &sound.source); - alDeleteBuffers(1, &sound.buffer); -#endif TraceLog(LOG_INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); } @@ -1020,8 +862,8 @@ void UnloadSound(Sound sound) // NOTE: data must match sound.format void UpdateSound(Sound sound, const void *data, int samplesCount) { -#if USE_MINI_AL AudioBuffer *audioBuffer = (AudioBuffer *)sound.audioBuffer; + if (audioBuffer == NULL) { TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound - no audio buffer"); @@ -1032,220 +874,118 @@ void UpdateSound(Sound sound, const void *data, int samplesCount) // TODO: May want to lock/unlock this since this data buffer is read at mixing time. memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.formatConverterIn.config.channels*mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)); -#else - ALint sampleRate, sampleSize, channels; - alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate); - alGetBufferi(sound.buffer, AL_BITS, &sampleSize); // It could also be retrieved from sound.format - alGetBufferi(sound.buffer, AL_CHANNELS, &channels); // It could also be retrieved from sound.format - - TraceLog(LOG_DEBUG, "UpdateSound() : AL_FREQUENCY: %i", sampleRate); - TraceLog(LOG_DEBUG, "UpdateSound() : AL_BITS: %i", sampleSize); - TraceLog(LOG_DEBUG, "UpdateSound() : AL_CHANNELS: %i", channels); - - unsigned int dataSize = samplesCount*channels*sampleSize/8; // Size of data in bytes - - alSourceStop(sound.source); // Stop sound - alSourcei(sound.source, AL_BUFFER, 0); // Unbind buffer from sound to update - //alDeleteBuffers(1, &sound.buffer); // Delete current buffer data - //alGenBuffers(1, &sound.buffer); // Generate new buffer - - // Upload new data to sound buffer - alBufferData(sound.buffer, sound.format, data, dataSize, sampleRate); - - // Attach sound buffer to source again - alSourcei(sound.source, AL_BUFFER, sound.buffer); -#endif } // Export wave data to file void ExportWave(Wave wave, const char *fileName) { bool success = false; - - if (IsFileExtension(fileName, ".wav")) + + if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); + else if (IsFileExtension(fileName, ".raw")) { - // 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; - - 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 = wave.sampleCount*wave.channels*wave.sampleSize/8; - - FILE *wavFile = fopen(fileName, "wb"); - - if (wavFile == NULL) return; - - fwrite(&riffHeader, 1, sizeof(RiffHeader), wavFile); - fwrite(&waveFormat, 1, sizeof(WaveFormat), wavFile); - fwrite(&waveData, 1, sizeof(WaveData), wavFile); - - fwrite(wave.data, 1, wave.sampleCount*wave.channels*wave.sampleSize/8, wavFile); - - fclose(wavFile); - - success = true; + // 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); } - else if (IsFileExtension(fileName, ".raw")) { } // TODO: Support additional file formats to export wave sample data if (success) TraceLog(LOG_INFO, "Wave exported successfully: %s", fileName); else TraceLog(LOG_WARNING, "Wave could not be exported."); } +// Export wave sample data to code (.h) +void ExportWaveAsCode(Wave wave, const char *fileName) +{ + #define BYTES_TEXT_PER_LINE 20 + + char varFileName[256] = { 0 }; + int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; + + FILE *txtFile = fopen(fileName, "wt"); + + fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } + + fprintf(txtFile, "// Wave data information\n"); + fprintf(txtFile, "#define %s_SAMPLE_COUNT %i\n", varFileName, wave.sampleCount); + fprintf(txtFile, "#define %s_SAMPLE_RATE %i\n", varFileName, wave.sampleRate); + fprintf(txtFile, "#define %s_SAMPLE_SIZE %i\n", varFileName, wave.sampleSize); + fprintf(txtFile, "#define %s_CHANNELS %i\n\n", varFileName, wave.channels); + + // Write byte data as hexadecimal text + fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); + fprintf(txtFile, "0x%x };\n", ((unsigned char *)wave.data)[dataSize - 1]); + + fclose(txtFile); +} + // Play a sound void PlaySound(Sound sound) { -#if USE_MINI_AL PlayAudioBuffer((AudioBuffer *)sound.audioBuffer); -#else - alSourcePlay(sound.source); // Play the sound -#endif - - //TraceLog(LOG_INFO, "Playing sound"); - - // Find the current position of the sound being played - // NOTE: Only work when the entire file is in a single buffer - //int byteOffset; - //alGetSourcei(sound.source, AL_BYTE_OFFSET, &byteOffset); - // - //int sampleRate; - //alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate); // AL_CHANNELS, AL_BITS (bps) - - //float seconds = (float)byteOffset/sampleRate; // Number of seconds since the beginning of the sound - //or - //float result; - //alGetSourcef(sound.source, AL_SEC_OFFSET, &result); // AL_SAMPLE_OFFSET } // Pause a sound void PauseSound(Sound sound) { -#if USE_MINI_AL PauseAudioBuffer((AudioBuffer *)sound.audioBuffer); -#else - alSourcePause(sound.source); -#endif } // Resume a paused sound void ResumeSound(Sound sound) { -#if USE_MINI_AL ResumeAudioBuffer((AudioBuffer *)sound.audioBuffer); -#else - ALenum state; - - alGetSourcei(sound.source, AL_SOURCE_STATE, &state); - - if (state == AL_PAUSED) alSourcePlay(sound.source); -#endif } // Stop reproducing a sound void StopSound(Sound sound) { -#if USE_MINI_AL StopAudioBuffer((AudioBuffer *)sound.audioBuffer); -#else - alSourceStop(sound.source); -#endif } // Check if a sound is playing bool IsSoundPlaying(Sound sound) { -#if USE_MINI_AL return IsAudioBufferPlaying((AudioBuffer *)sound.audioBuffer); -#else - bool playing = false; - ALint state; - - alGetSourcei(sound.source, AL_SOURCE_STATE, &state); - if (state == AL_PLAYING) playing = true; - - return playing; -#endif } // Set volume for a sound void SetSoundVolume(Sound sound, float volume) { -#if USE_MINI_AL SetAudioBufferVolume((AudioBuffer *)sound.audioBuffer, volume); -#else - alSourcef(sound.source, AL_GAIN, volume); -#endif } // Set pitch for a sound void SetSoundPitch(Sound sound, float pitch) { -#if USE_MINI_AL SetAudioBufferPitch((AudioBuffer *)sound.audioBuffer, pitch); -#else - alSourcef(sound.source, AL_PITCH, pitch); -#endif } // Convert wave data to desired format void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { -#if USE_MINI_AL mal_format formatIn = ((wave->sampleSize == 8) ? mal_format_u8 : ((wave->sampleSize == 16) ? mal_format_s16 : mal_format_f32)); mal_format formatOut = (( sampleSize == 8) ? mal_format_u8 : (( sampleSize == 16) ? mal_format_s16 : mal_format_f32)); mal_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, formatOut, channels, sampleRate, NULL, formatIn, wave->channels, wave->sampleRate, frameCountIn); - if (frameCount == 0) + if (frameCount == 0) { TraceLog(LOG_ERROR, "WaveFormat() : Failed to get frame count for format conversion."); return; @@ -1254,7 +994,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) void *data = malloc(frameCount*channels*(sampleSize/8)); frameCount = (mal_uint32)mal_convert_frames(data, formatOut, channels, sampleRate, wave->data, formatIn, wave->channels, wave->sampleRate, frameCountIn); - if (frameCount == 0) + if (frameCount == 0) { TraceLog(LOG_ERROR, "WaveFormat() : Format conversion failed."); return; @@ -1266,87 +1006,6 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) wave->channels = channels; free(wave->data); wave->data = data; - -#else - // Format sample rate - // NOTE: Only supported 22050 <--> 44100 - if (wave->sampleRate != sampleRate) - { - // TODO: Resample wave data (upsampling or downsampling) - // NOTE 1: To downsample, you have to drop samples or average them. - // NOTE 2: To upsample, you have to interpolate new samples. - - wave->sampleRate = sampleRate; - } - - // Format sample size - // NOTE: Only supported 8 bit <--> 16 bit <--> 32 bit - if (wave->sampleSize != sampleSize) - { - void *data = malloc(wave->sampleCount*wave->channels*sampleSize/8); - - for (int i = 0; i < wave->sampleCount; i++) - { - for (int j = 0; j < wave->channels; j++) - { - if (sampleSize == 8) - { - if (wave->sampleSize == 16) ((unsigned char *)data)[wave->channels*i + j] = (unsigned char)(((float)(((short *)wave->data)[wave->channels*i + j])/32767.0f)*256); - else if (wave->sampleSize == 32) ((unsigned char *)data)[wave->channels*i + j] = (unsigned char)(((float *)wave->data)[wave->channels*i + j]*127.0f + 127); - } - else if (sampleSize == 16) - { - if (wave->sampleSize == 8) ((short *)data)[wave->channels*i + j] = (short)(((float)(((unsigned char *)wave->data)[wave->channels*i + j] - 127)/256.0f)*32767); - else if (wave->sampleSize == 32) ((short *)data)[wave->channels*i + j] = (short)((((float *)wave->data)[wave->channels*i + j])*32767); - } - else if (sampleSize == 32) - { - if (wave->sampleSize == 8) ((float *)data)[wave->channels*i + j] = (float)(((unsigned char *)wave->data)[wave->channels*i + j] - 127)/256.0f; - else if (wave->sampleSize == 16) ((float *)data)[wave->channels*i + j] = (float)(((short *)wave->data)[wave->channels*i + j])/32767.0f; - } - } - } - - wave->sampleSize = sampleSize; - free(wave->data); - wave->data = data; - } - - // Format channels (interlaced mode) - // NOTE: Only supported mono <--> stereo - if (wave->channels != channels) - { - void *data = malloc(wave->sampleCount*wave->sampleSize/8*channels); - - if ((wave->channels == 1) && (channels == 2)) // mono ---> stereo (duplicate mono information) - { - for (int i = 0; i < wave->sampleCount; i++) - { - for (int j = 0; j < channels; j++) - { - if (wave->sampleSize == 8) ((unsigned char *)data)[channels*i + j] = ((unsigned char *)wave->data)[i]; - else if (wave->sampleSize == 16) ((short *)data)[channels*i + j] = ((short *)wave->data)[i]; - else if (wave->sampleSize == 32) ((float *)data)[channels*i + j] = ((float *)wave->data)[i]; - } - } - } - else if ((wave->channels == 2) && (channels == 1)) // stereo ---> mono (mix stereo channels) - { - for (int i = 0, j = 0; i < wave->sampleCount; i++, j += 2) - { - if (wave->sampleSize == 8) ((unsigned char *)data)[i] = (((unsigned char *)wave->data)[j] + ((unsigned char *)wave->data)[j + 1])/2; - else if (wave->sampleSize == 16) ((short *)data)[i] = (((short *)wave->data)[j] + ((short *)wave->data)[j + 1])/2; - else if (wave->sampleSize == 32) ((float *)data)[i] = (((float *)wave->data)[j] + ((float *)wave->data)[j + 1])/2.0f; - } - } - - // TODO: Add/remove additional interlaced channels - - wave->channels = channels; - free(wave->data); - wave->data = data; - } -#endif } // Copy a wave to a new wave @@ -1381,7 +1040,7 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) void *data = malloc(sampleCount*wave->sampleSize/8*wave->channels); - memcpy(data, (unsigned char*)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); + memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); free(wave->data); wave->data = data; @@ -1465,21 +1124,25 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_MP3) else if (IsFileExtension(fileName, ".mp3")) { - drmp3_init_file(&music->ctxMp3, fileName, NULL); + int result = drmp3_init_file(&music->ctxMp3, fileName, NULL); - if (music->ctxMp3.framesRemaining <= 0) musicLoaded = false; + if (!result) musicLoaded = false; else { - music->stream = InitAudioStream(music->ctxMp3.sampleRate, 16, music->ctxMp3.channels); - music->totalSamples = (unsigned int)music->ctxMp3.framesRemaining*music->ctxMp3.channels; + TraceLog(LOG_INFO, "[%s] MP3 sample rate: %i", fileName, music->ctxMp3.sampleRate); + TraceLog(LOG_INFO, "[%s] MP3 bits per sample: %i", fileName, 32); + TraceLog(LOG_INFO, "[%s] MP3 channels: %i", fileName, music->ctxMp3.channels); + + music->stream = InitAudioStream(music->ctxMp3.sampleRate, 32, music->ctxMp3.channels); + + // TODO: There is not an easy way to compute the total number of samples available + // in an MP3, frames size could be variable... we tried with a 60 seconds music... but crashes... + music->totalSamples = drmp3_get_pcm_frame_count(&music->ctxMp3)*music->ctxMp3.channels; music->samplesLeft = music->totalSamples; music->ctxType = MUSIC_AUDIO_MP3; music->loopCount = -1; // Infinite loop by default - TraceLog(LOG_DEBUG, "[%s] MP3 total samples: %i", fileName, music->totalSamples); - TraceLog(LOG_DEBUG, "[%s] MP3 sample rate: %i", fileName, music->ctxMp3.sampleRate); - //TraceLog(LOG_DEBUG, "[%s] MP3 bits per sample: %i", fileName, music->ctxMp3.bitsPerSample); - TraceLog(LOG_DEBUG, "[%s] MP3 channels: %i", fileName, music->ctxMp3.channels); + TraceLog(LOG_INFO, "[%s] MP3 total samples: %i", fileName, music->totalSamples); } } #endif @@ -1499,8 +1162,8 @@ Music LoadMusicStream(const char *fileName) music->ctxType = MUSIC_MODULE_XM; music->loopCount = -1; // Infinite loop by default - TraceLog(LOG_DEBUG, "[%s] XM number of samples: %i", fileName, music->totalSamples); - TraceLog(LOG_DEBUG, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + TraceLog(LOG_INFO, "[%s] XM number of samples: %i", fileName, music->totalSamples); + TraceLog(LOG_INFO, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); } else musicLoaded = false; } @@ -1525,7 +1188,7 @@ Music LoadMusicStream(const char *fileName) } #endif else musicLoaded = false; - + if (!musicLoaded) { if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); @@ -1576,8 +1239,8 @@ void UnloadMusicStream(Music music) // Start music playing (open stream) void PlayMusicStream(Music music) { -#if USE_MINI_AL AudioBuffer *audioBuffer = (AudioBuffer *)music->stream.audioBuffer; + if (audioBuffer == NULL) { TraceLog(LOG_ERROR, "PlayMusicStream() : No audio buffer"); @@ -1589,66 +1252,30 @@ void PlayMusicStream(Music music) // // just make sure to play again on window restore // if (IsMusicPlaying(music)) PlayMusicStream(music); mal_uint32 frameCursorPos = audioBuffer->frameCursorPos; - + PlayAudioStream(music->stream); // <-- This resets the cursor position. audioBuffer->frameCursorPos = frameCursorPos; -#else - alSourcePlay(music->stream.source); -#endif } // Pause music playing void PauseMusicStream(Music music) { -#if USE_MINI_AL PauseAudioStream(music->stream); -#else - alSourcePause(music->stream.source); -#endif } // Resume music playing void ResumeMusicStream(Music music) { -#if USE_MINI_AL ResumeAudioStream(music->stream); -#else - ALenum state; - alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); - - if (state == AL_PAUSED) - { - TraceLog(LOG_INFO, "[AUD ID %i] Resume music stream playing", music->stream.source); - alSourcePlay(music->stream.source); - } -#endif } // Stop music playing (close stream) // TODO: To clear a buffer, make sure they have been already processed! void StopMusicStream(Music music) { -#if USE_MINI_AL StopAudioStream(music->stream); -#else - alSourceStop(music->stream.source); - - /* - // Clear stream buffers - // WARNING: Queued buffers must have been processed before unqueueing and reloaded with data!!! - void *pcm = calloc(AUDIO_BUFFER_SIZE*music->stream.sampleSize/8*music->stream.channels, 1); - - for (int i = 0; i < MAX_STREAM_BUFFERS; i++) - { - //UpdateAudioStream(music->stream, pcm, AUDIO_BUFFER_SIZE); // Update one buffer at a time - alBufferData(music->stream.buffers[i], music->stream.format, pcm, AUDIO_BUFFER_SIZE*music->stream.sampleSize/8*music->stream.channels, music->stream.sampleRate); - } - free(pcm); - */ -#endif - // Restart music context switch (music->ctxType) { @@ -1657,7 +1284,7 @@ void StopMusicStream(Music music) case MUSIC_AUDIO_FLAC: /* TODO: Restart FLAC context */ break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: /* TODO: Restart MP3 context */ break; + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame(&music->ctxMp3, 0); break; #endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break; @@ -1675,19 +1302,18 @@ void StopMusicStream(Music music) // TODO: Make sure buffers are ready for update... check music state void UpdateMusicStream(Music music) { -#if USE_MINI_AL bool streamEnding = false; unsigned int subBufferSizeInFrames = ((AudioBuffer *)music->stream.audioBuffer)->bufferSizeInFrames/2; // NOTE: Using dynamic allocation because it could require more than 16KB - void *pcm = calloc(subBufferSizeInFrames*music->stream.sampleSize/8*music->stream.channels, 1); + void *pcm = calloc(subBufferSizeInFrames*music->stream.channels*music->stream.sampleSize/8, 1); int samplesCount = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts while (IsAudioBufferProcessed(music->stream)) { - if (music->samplesLeft >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames; + if ((music->samplesLeft/music->stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music->stream.channels; else samplesCount = music->samplesLeft; // TODO: Really don't like ctxType thingy... @@ -1696,27 +1322,31 @@ void UpdateMusicStream(Music music) case MUSIC_AUDIO_OGG: { // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount*music->stream.channels); + stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount); } break; #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: { // NOTE: Returns the number of samples to process - unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount*music->stream.channels, (short *)pcm); + unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount, (short *)pcm); } break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: + case MUSIC_AUDIO_MP3: { - // NOTE: Returns the number of samples to process - unsigned int numSamplesMp3 = (unsigned int)drmp3_read_f32(&music->ctxMp3, samplesCount*music->stream.channels, (float *)pcm); + // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed + drmp3_read_pcm_frames_f32(&music->ctxMp3, samplesCount/music->stream.channels, (float *)pcm); } break; #endif #if defined(SUPPORT_FILEFORMAT_XM) - case MUSIC_MODULE_XM: jar_xm_generate_samples_16bit(music->ctxXm, pcm, samplesCount); break; + case MUSIC_MODULE_XM: + { + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 --> WEIRD + jar_xm_generate_samples_16bit(music->ctxXm, (short *)pcm, samplesCount/2); + } break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break; @@ -1741,7 +1371,7 @@ void UpdateMusicStream(Music music) if (streamEnding) { StopMusicStream(music); // Stop music (and reset) - + // Decrease loopCount to stop when required if (music->loopCount > 0) { @@ -1759,139 +1389,24 @@ void UpdateMusicStream(Music music) // just make sure to play again on window restore if (IsMusicPlaying(music)) PlayMusicStream(music); } -#else - ALenum state; - ALint processed = 0; - - alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); // Get music stream state - alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); // Get processed buffers - - if (processed > 0) - { - bool streamEnding = false; - - // NOTE: Using dynamic allocation because it could require more than 16KB - void *pcm = calloc(AUDIO_BUFFER_SIZE*music->stream.sampleSize/8*music->stream.channels, 1); - - int numBuffersToProcess = processed; - int samplesCount = 0; // Total size of data steamed in L+R samples for xm floats, - // individual L or R for ogg shorts - - for (int i = 0; i < numBuffersToProcess; i++) - { - if (music->samplesLeft >= AUDIO_BUFFER_SIZE) samplesCount = AUDIO_BUFFER_SIZE; - else samplesCount = music->samplesLeft; - - // TODO: Really don't like ctxType thingy... - switch (music->ctxType) - { - case MUSIC_AUDIO_OGG: - { - // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount*music->stream.channels); - - } break; - #if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: - { - // NOTE: Returns the number of samples to process - unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount*music->stream.channels, (short *)pcm); - - } break; - #endif - #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: - { - // NOTE: Returns the number of samples to process - unsigned int numSamplesMp3 = (unsigned int)drmp3_read_f32(&music->ctxMp3, samplesCount*music->stream.channels, (float *)pcm); - } break; - #endif - #if defined(SUPPORT_FILEFORMAT_XM) - case MUSIC_MODULE_XM: jar_xm_generate_samples_16bit(music->ctxXm, pcm, samplesCount); break; - #endif - #if defined(SUPPORT_FILEFORMAT_MOD) - case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break; - #endif - default: break; - } - - UpdateAudioStream(music->stream, pcm, samplesCount); - music->samplesLeft -= samplesCount; - - if (music->samplesLeft <= 0) - { - streamEnding = true; - break; - } - } - - // Free allocated pcm data - free(pcm); - - // Reset audio stream for looping - if (streamEnding) - { - StopMusicStream(music); // Stop music (and reset) - - // Decrease loopCount to stop when required - if (music->loopCount > 0) - { - music->loopCount--; // Decrease loop count - PlayMusicStream(music); // Play again - } - else - { - if (music->loopCount == -1) - { - PlayMusicStream(music); - } - } - } - else - { - // NOTE: In case window is minimized, music stream is stopped, - // just make sure to play again on window restore - if (state != AL_PLAYING) PlayMusicStream(music); - } - } -#endif } // Check if any music is playing bool IsMusicPlaying(Music music) { -#if USE_MINI_AL return IsAudioStreamPlaying(music->stream); -#else - bool playing = false; - ALint state; - - alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); - - if (state == AL_PLAYING) playing = true; - - return playing; -#endif } // Set volume for music void SetMusicVolume(Music music, float volume) { -#if USE_MINI_AL SetAudioStreamVolume(music->stream, volume); -#else - alSourcef(music->stream.source, AL_GAIN, volume); -#endif } // Set pitch for music void SetMusicPitch(Music music, float pitch) { -#if USE_MINI_AL SetAudioStreamPitch(music->stream, pitch); -#else - alSourcef(music->stream.source, AL_PITCH, pitch); -#endif } // Set music loop count (loop repeats) @@ -1904,7 +1419,7 @@ void SetMusicLoopCount(Music music, int count) // Get music time length (in seconds) float GetMusicTimeLength(Music music) { - float totalSeconds = (float)music->totalSamples/music->stream.sampleRate; + float totalSeconds = (float)music->totalSamples/(music->stream.sampleRate*music->stream.channels); return totalSeconds; } @@ -1915,12 +1430,11 @@ float GetMusicTimePlayed(Music music) float secondsPlayed = 0.0f; unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; - secondsPlayed = (float)samplesPlayed/music->stream.sampleRate; + secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); return secondsPlayed; } - // Init audio stream (to stream audio pcm data) AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) { @@ -1937,12 +1451,10 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un stream.channels = 1; // Fallback to mono channel } - -#if USE_MINI_AL mal_format formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); // The size of a streaming buffer must be at least double the size of a period. - unsigned int periodSize = device.bufferSizeInFrames / device.periods; + unsigned int periodSize = device.bufferSizeInFrames/device.periods; unsigned int subBufferSize = AUDIO_BUFFER_SIZE; if (subBufferSize < periodSize) subBufferSize = periodSize; @@ -1953,54 +1465,8 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un return stream; } - audioBuffer->looping = true; // Always loop for streaming buffers. + audioBuffer->looping = true; // Always loop for streaming buffers. stream.audioBuffer = audioBuffer; -#else - // Setup OpenAL format - if (stream.channels == 1) - { - switch (sampleSize) - { - case 8: stream.format = AL_FORMAT_MONO8; break; - case 16: stream.format = AL_FORMAT_MONO16; break; - case 32: stream.format = AL_FORMAT_MONO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 - default: TraceLog(LOG_WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; - } - } - else if (stream.channels == 2) - { - switch (sampleSize) - { - case 8: stream.format = AL_FORMAT_STEREO8; break; - case 16: stream.format = AL_FORMAT_STEREO16; break; - case 32: stream.format = AL_FORMAT_STEREO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 - default: TraceLog(LOG_WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; - } - } - - // Create an audio source - alGenSources(1, &stream.source); - alSourcef(stream.source, AL_PITCH, 1.0f); - alSourcef(stream.source, AL_GAIN, 1.0f); - alSource3f(stream.source, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(stream.source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - // Create Buffers (double buffering) - alGenBuffers(MAX_STREAM_BUFFERS, stream.buffers); - - // Initialize buffer with zeros by default - // NOTE: Using dynamic allocation because it requires more than 16KB - void *pcm = calloc(AUDIO_BUFFER_SIZE*stream.sampleSize/8*stream.channels, 1); - - for (int i = 0; i < MAX_STREAM_BUFFERS; i++) - { - alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*stream.sampleSize/8*stream.channels, stream.sampleRate); - } - - free(pcm); - - alSourceQueueBuffers(stream.source, MAX_STREAM_BUFFERS, stream.buffers); -#endif TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo"); @@ -2010,29 +1476,8 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un // Close audio stream and free memory void CloseAudioStream(AudioStream stream) { -#if USE_MINI_AL DeleteAudioBuffer((AudioBuffer *)stream.audioBuffer); -#else - // Stop playing channel - alSourceStop(stream.source); - // Flush out all queued buffers - int queued = 0; - alGetSourcei(stream.source, AL_BUFFERS_QUEUED, &queued); - - ALuint buffer = 0; - - while (queued > 0) - { - alSourceUnqueueBuffers(stream.source, 1, &buffer); - queued--; - } - - // Delete source and buffers - alDeleteSources(1, &stream.source); - alDeleteBuffers(MAX_STREAM_BUFFERS, stream.buffers); -#endif - TraceLog(LOG_INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); } @@ -2041,7 +1486,6 @@ void CloseAudioStream(AudioStream stream) // NOTE 2: To unqueue a buffer it needs to be processed: IsAudioBufferProcessed() void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) { -#if USE_MINI_AL AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; if (audioBuffer == NULL) { @@ -2052,6 +1496,7 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) if (audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]) { mal_uint32 subBufferToUpdate; + if (audioBuffer->isSubBufferProcessed[0] && audioBuffer->isSubBufferProcessed[1]) { // Both buffers are available for updating. Update the first one and make sure the cursor is moved back to the front. @@ -2068,17 +1513,19 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic. - if (subBufferSizeInFrames >= (mal_uint32)samplesCount) + if (subBufferSizeInFrames >= (mal_uint32)samplesCount/stream.channels) { mal_uint32 framesToWrite = subBufferSizeInFrames; - if (framesToWrite > (mal_uint32)samplesCount) framesToWrite = (mal_uint32)samplesCount; + + if (framesToWrite > ((mal_uint32)samplesCount/stream.channels)) framesToWrite = (mal_uint32)samplesCount/stream.channels; mal_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); memcpy(subBuffer, data, bytesToWrite); // Any leftover frames should be filled with zeros. mal_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; - if (leftoverFrameCount > 0) + + if (leftoverFrameCount > 0) { memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); } @@ -2096,24 +1543,11 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) TraceLog(LOG_ERROR, "Audio buffer not available for updating"); return; } -#else - ALuint buffer = 0; - alSourceUnqueueBuffers(stream.source, 1, &buffer); - - // Check if any buffer was available for unqueue - if (alGetError() != AL_INVALID_VALUE) - { - alBufferData(buffer, stream.format, data, samplesCount*stream.sampleSize/8*stream.channels, stream.sampleRate); - alSourceQueueBuffers(stream.source, 1, &buffer); - } - else TraceLog(LOG_WARNING, "[AUD ID %i] Audio buffer not available for unqueuing", stream.source); -#endif } // Check if any audio stream buffers requires refill bool IsAudioBufferProcessed(AudioStream stream) { -#if USE_MINI_AL AudioBuffer *audioBuffer = (AudioBuffer *)stream.audioBuffer; if (audioBuffer == NULL) { @@ -2122,92 +1556,46 @@ bool IsAudioBufferProcessed(AudioStream stream) } return audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]; -#else - ALint processed = 0; - - // Determine if music stream is ready to be written - alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed); - - return (processed > 0); -#endif } // Play audio stream void PlayAudioStream(AudioStream stream) { -#if USE_MINI_AL PlayAudioBuffer((AudioBuffer *)stream.audioBuffer); -#else - alSourcePlay(stream.source); -#endif } // Play audio stream void PauseAudioStream(AudioStream stream) { -#if USE_MINI_AL PauseAudioBuffer((AudioBuffer *)stream.audioBuffer); -#else - alSourcePause(stream.source); -#endif } // Resume audio stream playing void ResumeAudioStream(AudioStream stream) { -#if USE_MINI_AL ResumeAudioBuffer((AudioBuffer *)stream.audioBuffer); -#else - ALenum state; - alGetSourcei(stream.source, AL_SOURCE_STATE, &state); - - if (state == AL_PAUSED) alSourcePlay(stream.source); -#endif } // Check if audio stream is playing. bool IsAudioStreamPlaying(AudioStream stream) { -#if USE_MINI_AL return IsAudioBufferPlaying((AudioBuffer *)stream.audioBuffer); -#else - bool playing = false; - ALint state; - - alGetSourcei(stream.source, AL_SOURCE_STATE, &state); - - if (state == AL_PLAYING) playing = true; - - return playing; -#endif } // Stop audio stream void StopAudioStream(AudioStream stream) { -#if USE_MINI_AL StopAudioBuffer((AudioBuffer *)stream.audioBuffer); -#else - alSourceStop(stream.source); -#endif } void SetAudioStreamVolume(AudioStream stream, float volume) { -#if USE_MINI_AL SetAudioBufferVolume((AudioBuffer *)stream.audioBuffer, volume); -#else - alSourcef(stream.source, AL_GAIN, volume); -#endif } void SetAudioStreamPitch(AudioStream stream, float pitch) { -#if USE_MINI_AL SetAudioBufferPitch((AudioBuffer *)stream.audioBuffer, pitch); -#else - alSourcef(stream.source, AL_PITCH, pitch); -#endif } //---------------------------------------------------------------------------------- @@ -2331,6 +1719,86 @@ static Wave LoadWAV(const char *fileName) return wave; } + +// Save wave data as WAV file +static int SaveWAV(Wave wave, const char *fileName) +{ + int success = 0; + int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; + + // Basic WAV headers structs + typedef struct { + char chunkID[4]; + int chunkSize; + char format[4]; + } RiffHeader; + + typedef struct { + char subChunkID[4]; + int subChunkSize; + short audioFormat; + short numChannels; + int sampleRate; + int byteRate; + short blockAlign; + short bitsPerSample; + } WaveFormat; + + typedef struct { + char subChunkID[4]; + int subChunkSize; + } WaveData; + + FILE *wavFile = fopen(fileName, "wb"); + + if (wavFile == NULL) TraceLog(LOG_WARNING, "[%s] WAV audio file could not be created", fileName); + else + { + RiffHeader riffHeader; + WaveFormat waveFormat; + WaveData waveData; + + // Fill structs with data + riffHeader.chunkID[0] = 'R'; + riffHeader.chunkID[1] = 'I'; + riffHeader.chunkID[2] = 'F'; + riffHeader.chunkID[3] = 'F'; + riffHeader.chunkSize = 44 - 4 + wave.sampleCount*wave.sampleSize/8; + riffHeader.format[0] = 'W'; + riffHeader.format[1] = 'A'; + riffHeader.format[2] = 'V'; + riffHeader.format[3] = 'E'; + + waveFormat.subChunkID[0] = 'f'; + waveFormat.subChunkID[1] = 'm'; + waveFormat.subChunkID[2] = 't'; + waveFormat.subChunkID[3] = ' '; + waveFormat.subChunkSize = 16; + waveFormat.audioFormat = 1; + waveFormat.numChannels = wave.channels; + waveFormat.sampleRate = wave.sampleRate; + waveFormat.byteRate = wave.sampleRate*wave.sampleSize/8; + waveFormat.blockAlign = wave.sampleSize/8; + waveFormat.bitsPerSample = wave.sampleSize; + + waveData.subChunkID[0] = 'd'; + waveData.subChunkID[1] = 'a'; + waveData.subChunkID[2] = 't'; + waveData.subChunkID[3] = 'a'; + waveData.subChunkSize = dataSize; + + success = fwrite(&riffHeader, sizeof(RiffHeader), 1, wavFile); + success = fwrite(&waveFormat, sizeof(WaveFormat), 1, wavFile); + success = fwrite(&waveData, sizeof(WaveData), 1, wavFile); + + success = fwrite(wave.data, dataSize, 1, wavFile); + + fclose(wavFile); + } + + // If all data has been written correctly to file, success = 1 + return success; +} #endif #if defined(SUPPORT_FILEFORMAT_OGG) @@ -2346,7 +1814,7 @@ static Wave LoadOGG(const char *fileName) else { stb_vorbis_info info = stb_vorbis_get_info(oggFile); - + wave.sampleRate = info.sample_rate; wave.sampleSize = 16; // 16 bit per sample (short) wave.channels = info.channels; @@ -2400,17 +1868,17 @@ static Wave LoadFLAC(const char *fileName) // NOTE: Using dr_mp3 library static Wave LoadMP3(const char *fileName) { - Wave wave; + Wave wave = { 0 }; // Decode an entire MP3 file in one go - uint64_t totalSampleCount; - drmp3_config *config; - wave.data = drmp3_open_and_decode_file_f32(fileName, config, &totalSampleCount); - - wave.channels = config->outputChannels; - wave.sampleRate = config->outputSampleRate; - wave.sampleCount = (int)totalSampleCount/wave.channels; - wave.sampleSize = 16; + uint64_t totalFrameCount = 0; + drmp3_config config = { 0 }; + wave.data = drmp3_open_file_and_read_f32(fileName, &config, &totalFrameCount); + + wave.channels = config.outputChannels; + wave.sampleRate = config.outputSampleRate; + wave.sampleCount = (int)totalFrameCount*wave.channels; + wave.sampleSize = 32; // NOTE: Only support up to 2 channels (mono, stereo) if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] MP3 channels number (%i) not supported", fileName, wave.channels); @@ -2429,7 +1897,7 @@ bool IsFileExtension(const char *fileName, const char *ext) { bool result = false; const char *fileExt; - + if ((fileExt = strrchr(fileName, '.')) != NULL) { if (strcmp(fileExt, ext) == 0) result = true; diff --git a/raylib/audio.go b/raylib/audio.go index dec2927..7315b6e 100644 --- a/raylib/audio.go +++ b/raylib/audio.go @@ -107,6 +107,14 @@ func UnloadSound(sound Sound) { C.UnloadSound(*csound) } +// ExportWave - Export wave data to file +func ExportWave(wave Wave, fileName string) { + cwave := wave.cptr() + cfileName := C.CString(fileName) + defer C.free(unsafe.Pointer(cfileName)) + C.ExportWave(*cwave, cfileName) +} + // PlaySound - Play a sound func PlaySound(sound Sound) { csound := sound.cptr() diff --git a/raylib/cgo_android.go b/raylib/cgo_android.go index a5657ef..369677f 100644 --- a/raylib/cgo_android.go +++ b/raylib/cgo_android.go @@ -6,6 +6,6 @@ package rl #include "external/android/native_app_glue/android_native_app_glue.c" #cgo android LDFLAGS: -llog -landroid -lEGL -lGLESv2 -lOpenSLES -lm -#cgo android CFLAGS: -DPLATFORM_ANDROID -DGRAPHICS_API_OPENGL_ES2 -DMAL_NO_SDL -Iexternal -Iexternal/android/native_app_glue +#cgo android CFLAGS: -DPLATFORM_ANDROID -DGRAPHICS_API_OPENGL_ES2 -DMAL_NO_SDL -Iexternal/android/native_app_glue */ import "C" diff --git a/raylib/cgo_darwin.go b/raylib/cgo_darwin.go index 5e1bb56..6f15fdb 100644 --- a/raylib/cgo_darwin.go +++ b/raylib/cgo_darwin.go @@ -21,7 +21,7 @@ package rl #include "external/glfw/src/osmesa_context.c" #cgo darwin LDFLAGS: -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo -framework CoreFoundation -#cgo darwin CFLAGS: -x objective-c -Iexternal -Iexternal/glfw/include -D_GLFW_COCOA -D_GLFW_USE_CHDIR -D_GLFW_USE_MENUBAR -D_GLFW_USE_RETINA -Wno-deprecated-declarations -DPLATFORM_DESKTOP -DMAL_NO_COREAUDIO +#cgo darwin CFLAGS: -x objective-c -Iexternal/glfw/include -D_GLFW_COCOA -D_GLFW_USE_CHDIR -D_GLFW_USE_MENUBAR -D_GLFW_USE_RETINA -Wno-deprecated-declarations -DPLATFORM_DESKTOP -DMAL_NO_COREAUDIO #cgo darwin,opengl11 CFLAGS: -DGRAPHICS_API_OPENGL_11 #cgo darwin,opengl21 CFLAGS: -DGRAPHICS_API_OPENGL_21 diff --git a/raylib/cgo_linux.go b/raylib/cgo_linux.go index de07cff..59a6d76 100644 --- a/raylib/cgo_linux.go +++ b/raylib/cgo_linux.go @@ -34,7 +34,7 @@ package rl #include "external/glfw/src/egl_context.c" #include "external/glfw/src/osmesa_context.c" -#cgo linux CFLAGS: -Iexternal -Iexternal/glfw/include -DPLATFORM_DESKTOP -Wno-stringop-overflow +#cgo linux CFLAGS: -Iexternal/glfw/include -DPLATFORM_DESKTOP -Wno-stringop-overflow #cgo linux,!wayland LDFLAGS: -lGL -lm -pthread -ldl -lrt -lX11 #cgo linux,wayland LDFLAGS: -lGL -lm -pthread -ldl -lrt -lwayland-client -lwayland-cursor -lwayland-egl -lxkbcommon diff --git a/raylib/cgo_linux_arm.go b/raylib/cgo_linux_arm.go index deb69a0..4ab31b6 100644 --- a/raylib/cgo_linux_arm.go +++ b/raylib/cgo_linux_arm.go @@ -4,6 +4,6 @@ package rl /* #cgo linux,arm LDFLAGS: -L/opt/vc/lib -L/opt/vc/lib64 -lbrcmGLESv2 -lbrcmEGL -lpthread -lrt -lm -lbcm_host -lvcos -lvchiq_arm -ldl -#cgo linux,arm CFLAGS: -DPLATFORM_RPI -DGRAPHICS_API_OPENGL_ES2 -Iexternal -I/opt/vc/include -I/opt/vc/include/interface/vcos -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads +#cgo linux,arm CFLAGS: -DPLATFORM_RPI -DGRAPHICS_API_OPENGL_ES2 -I/opt/vc/include -I/opt/vc/include/interface/vcos -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads */ import "C" diff --git a/raylib/config.h b/raylib/config.h index b4bc561..1425eeb 100644 --- a/raylib/config.h +++ b/raylib/config.h @@ -44,6 +44,8 @@ #define SUPPORT_MOUSE_GESTURES 1 // Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used //#define SUPPORT_BUSY_WAIT_LOOP 1 +// Wait for events passively (sleeping while no events) instead of polling them actively every frame +//#define SUPPORT_EVENTS_WAITING 1 // Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() #define SUPPORT_SCREEN_CAPTURE 1 // Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() diff --git a/raylib/core.c b/raylib/core.c index 4f6969c..67f4fb5 100644 --- a/raylib/core.c +++ b/raylib/core.c @@ -2,12 +2,12 @@ * * raylib.core - Basic functions to manage windows, OpenGL context and input on multiple platforms * -* PLATFORMS SUPPORTED: +* PLATFORMS SUPPORTED: * - PLATFORM_DESKTOP: Windows (Win32, Win64) * - PLATFORM_DESKTOP: Linux (X11 desktop mode) * - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) * - PLATFORM_DESKTOP: OSX/macOS -* - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64) +* - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64) * - PLATFORM_RPI: Raspberry Pi 0,1,2,3 (Raspbian) * - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox) * - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One @@ -23,7 +23,7 @@ * NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL * * #define PLATFORM_RPI -* Windowing and input system configured for Raspberry Pi i native mode (no X.org required, tested on Raspbian), +* Windowing and input system configured for Raspberry Pi i native mode (no X.org required, tested on Raspbian), * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ * * #define PLATFORM_WEB @@ -50,6 +50,9 @@ * #define SUPPORT_BUSY_WAIT_LOOP * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used * +* #define SUPPORT_EVENTS_WAITING +* Wait for events passively (sleeping while no events) instead of polling them actively every frame +* * #define SUPPORT_SCREEN_CAPTURE * Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() * @@ -123,52 +126,65 @@ #include // Required for: strrchr(), strcmp() //#include // Macros for reporting and retrieving error conditions through error codes #include // Required for: tolower() [Used in IsFileExtension()] -#include // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#include // Required for stat() [Used in GetLastWriteTime()] + +#if defined(PLATFORM_DESKTOP) && defined(_WIN32) && defined(_MSC_VER) + #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#else + #include // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#endif #if defined(_WIN32) #include // Required for: _getch(), _chdir() #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() #define CHDIR _chdir + #include // Required for _access() [Used in FileExists()] #else - #include "unistd.h" // Required for: getch(), chdir() (POSIX) + #include "unistd.h" // Required for: getch(), chdir() (POSIX), access() #define GETCWD getcwd #define CHDIR chdir #endif -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - #if defined(PLATFORM_WEB) - #define GLFW_INCLUDE_ES2 - #endif - //#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 +#if defined(PLATFORM_DESKTOP) + #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 + // NOTE: Already provided by rlgl implementation (on glad.h) #include // GLFW3 library: Windows, OpenGL context and Input management // NOTE: GLFW3 already includes gl.h (OpenGL) headers - + // Support retrieving native window handlers #if defined(_WIN32) #define GLFW_EXPOSE_NATIVE_WIN32 #include // WARNING: It requires customization to avoid windows.h inclusion! + + #if !defined(SUPPORT_BUSY_WAIT_LOOP) + // NOTE: Those functions require linking with winmm library + unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); + unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + #endif + #elif defined(__linux__) - //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type - //GLFW_EXPOSE_NATIVE_WAYLAND - //GLFW_EXPOSE_NATIVE_MIR - #endif + #include // Required for: timespec, nanosleep(), select() - POSIX + //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type + //#define GLFW_EXPOSE_NATIVE_WAYLAND + //#define GLFW_EXPOSE_NATIVE_MIR + #include // Required for: glfwGetX11Window() + #elif defined(__APPLE__) + #include // Required for: usleep() + #include // Required for: objc_msgsend(), sel_registerName() + + //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition + #define GLFW_EXPOSE_NATIVE_NSGL + #include // Required for: glfwGetCocoaWindow(), glfwGetNSGLContext() - #if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) - // NOTE: Those functions require linking with winmm library - unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); - unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #endif #endif -#if defined(__linux__) || defined(PLATFORM_WEB) - #include // Required for: timespec, nanosleep(), select() - POSIX -#elif defined(__APPLE__) - #include // Required for: usleep() - #include // Required for: objc_msgsend(), sel_registerName() - #define GLFW_EXPOSE_NATIVE_COCOA - #define GLFW_EXPOSE_NATIVE_NSGL - #include // Required for: glfwGetCocoaWindow(), glfwGetNSGLContext() +#if defined(__linux__) + #include // for NAME_MAX and PATH_MAX defines + #define MAX_FILEPATH_LENGTH PATH_MAX // Use Linux define (4096) +#else + #define MAX_FILEPATH_LENGTH 256 // Use common value #endif #if defined(PLATFORM_ANDROID) @@ -185,6 +201,7 @@ #include // POSIX standard function definitions - read(), close(), STDIN_FILENO #include // POSIX terminal control definitions - tcgetattr(), tcsetattr() #include // POSIX threads management (mouse input) + #include // POSIX directory browsing #include // UNIX System call for device-specific input/output operations - ioctl() #include // Linux: KDSKBMODE, K_MEDIUMRAM constants definition @@ -205,19 +222,24 @@ #endif #if defined(PLATFORM_WEB) - #include - #include + #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) + #include // GLFW3 library: Windows, OpenGL context and Input management + #include // Required for: timespec, nanosleep(), select() - POSIX + + #include // Emscripten library - LLVM to JavaScript compiler + #include // Emscripten HTML5 library #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #if defined(PLATFORM_RPI) + #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number + // Old device inputs system #define DEFAULT_KEYBOARD_DEV STDIN_FILENO // Standard input - #define DEFAULT_MOUSE_DEV "/dev/input/mouse0" // Mouse input - #define DEFAULT_TOUCH_DEV "/dev/input/event4" // Touch input virtual device (created by ts_uinput) #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) + #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events // New device input events (evdev) (must be detected) //#define DEFAULT_KEYBOARD_DEV "/dev/input/eventN" @@ -320,12 +342,23 @@ static int currentMouseWheelY = 0; // Registers current mouse wheel #endif #if defined(PLATFORM_RPI) -static int mouseStream = -1; // Mouse device file descriptor -static bool mouseReady = false; // Flag to know if mouse is ready -static pthread_t mouseThreadId; // Mouse reading thread id -static int touchStream = -1; // Touch device file descriptor -static bool touchReady = false; // Flag to know if touch interface is ready -static pthread_t touchThreadId; // Touch reading thread id +static char currentMouseStateEvdev[3] = { 0 }; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update) + +typedef struct { + pthread_t threadId; // Event reading thread id + int fd; // File descriptor to the device it is assigned to + int eventNum; // Number of 'event' device + Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) + int touchSlot; // Hold the touch slot number of the currently being sent multitouch block + bool isMouse; // True if device supports relative X Y movements + bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH + bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH + bool isKeyboard; // True if device has letter keycodes + bool isGamepad; // True if device has gamepad buttons +} InputEventWorker; + +static InputEventWorker eventWorkers[10]; // List of worker threads for every monitored "/dev/input/event" + #endif #if defined(PLATFORM_WEB) static bool toggleCursorLock = false; // Ask for cursor pointer lock on next click @@ -439,9 +472,8 @@ static void InitKeyboard(void); // Init raw keyboard sys static void ProcessKeyboard(void); // Process keyboard events static void RestoreKeyboard(void); // Restore keyboard system static void InitMouse(void); // Mouse initialization (including mouse thread) -static void *MouseThread(void *arg); // Mouse reading thread -static void InitTouch(void); // Touch device initialization (including touch thread) -static void *TouchThread(void *arg); // Touch device reading thread +static void EventThreadSpawn(char *device); // Indetifies a input device and spawns a thread to handle it if needed +static void *EventThread(void *arg); // Input device event reading thread static void InitGamepad(void); // Init raw gamepad input static void *GamepadThread(void *arg); // Mouse reading thread #endif @@ -460,7 +492,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread //---------------------------------------------------------------------------------- #if defined(PLATFORM_ANDROID) -// To allow easier porting to android, we allow the user to define a +// To allow easier porting to android, we allow the user to define a // main function which we call from android_main, defined by ourselves //extern int main(int argc, char *argv[]); extern void android_run(); @@ -472,7 +504,7 @@ void android_main(struct android_app *app) (void)android_run(); // TODO: Should we maybe report != 0 return codes somewhere? - //(void)main(1, (char*[]) { arg0, NULL }); + //(void)main(1, (char *[]) { arg0, NULL }); } // TODO: Add this to header (if apps really need it) @@ -560,17 +592,16 @@ void InitWindow(int width, int height, const char *title) #if defined(PLATFORM_RPI) // Init raw input system InitMouse(); // Mouse init - InitTouch(); // Touch init InitKeyboard(); // Keyboard init InitGamepad(); // Gamepad init #endif #if defined(PLATFORM_WEB) emscripten_set_fullscreenchange_callback(0, 0, 1, EmscriptenFullscreenChangeCallback); - + // Support keyboard events emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); - + // Support mouse events emscripten_set_click_callback("#canvas", NULL, 1, EmscriptenMouseCallback); @@ -609,7 +640,7 @@ void CloseWindow(void) gifRecording = false; } #endif - + #if defined(SUPPORT_DEFAULT_FONT) UnloadDefaultFont(); #endif @@ -655,8 +686,13 @@ void CloseWindow(void) windowShouldClose = true; // Added to force threads to exit when the close window is called - pthread_join(mouseThreadId, NULL); - pthread_join(touchThreadId, NULL); + for (int i = 0; i < sizeof(eventWorkers)/sizeof(InputEventWorker); ++i) + { + if (eventWorkers[i].threadId == 0) + { + pthread_join(eventWorkers[i].threadId, NULL); + } + } pthread_join(gamepadThreadId, NULL); #endif @@ -681,7 +717,7 @@ bool WindowShouldClose(void) emscripten_sleep(16); return false; #endif - + #if defined(PLATFORM_DESKTOP) if (windowReady) { @@ -729,18 +765,21 @@ void ToggleFullscreen(void) void SetWindowIcon(Image image) { #if defined(PLATFORM_DESKTOP) - ImageFormat(&image, UNCOMPRESSED_R8G8B8A8); + Image imicon = ImageCopy(image); + ImageFormat(&imicon, UNCOMPRESSED_R8G8B8A8); - GLFWimage icon[1]; - - icon[0].width = image.width; - icon[0].height = image.height; - icon[0].pixels = (unsigned char *)image.data; + GLFWimage icon[1] = { 0 }; - // NOTE: We only support one image icon + icon[0].width = imicon.width; + icon[0].height = imicon.height; + icon[0].pixels = (unsigned char *)imicon.data; + + // NOTE 1: We only support one image icon + // NOTE 2: The specified image data is copied before this function returns glfwSetWindowIcon(window, 1, icon); // TODO: Support multi-image icons --> image.mipmaps + UnloadImage(imicon); #endif } @@ -765,9 +804,9 @@ void SetWindowMonitor(int monitor) { #if defined(PLATFORM_DESKTOP) int monitorCount; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - - if ((monitor >= 0) && (monitor < monitorCount)) + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) { //glfwSetWindowMonitor(window, monitors[monitor], 0, 0, screenWidth, screenHeight, GLFW_DONT_CARE); TraceLog(LOG_INFO, "Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); @@ -818,8 +857,8 @@ void *GetWindowHandle(void) //unsigned long id = (unsigned long)glfwGetX11Window(window); return NULL; // TODO: Find a way to return value... cast to void *? #elif defined(__APPLE__) - // NOTE: Returned handle is: void *id - return glfwGetCocoaWindow(window); + // NOTE: Returned handle is: (objc_object *) + return NULL; // TODO: return (void *)glfwGetCocoaWindow(window); #else return NULL; #endif @@ -839,12 +878,12 @@ int GetMonitorCount(void) // Get primary monitor width int GetMonitorWidth(int monitor) -{ +{ #if defined(PLATFORM_DESKTOP) int monitorCount; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - - if ((monitor >= 0) && (monitor < monitorCount)) + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) { const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); return mode->width; @@ -856,12 +895,12 @@ int GetMonitorWidth(int monitor) // Get primary monitor width int GetMonitorHeight(int monitor) -{ +{ #if defined(PLATFORM_DESKTOP) int monitorCount; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - - if ((monitor >= 0) && (monitor < monitorCount)) + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) { const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); return mode->height; @@ -876,9 +915,9 @@ int GetMonitorPhysicalWidth(int monitor) { #if defined(PLATFORM_DESKTOP) int monitorCount; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - - if ((monitor >= 0) && (monitor < monitorCount)) + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) { int physicalWidth; glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL); @@ -894,9 +933,9 @@ int GetMonitorPhysicalHeight(int monitor) { #if defined(PLATFORM_DESKTOP) int monitorCount; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - - if ((monitor >= 0) && (monitor < monitorCount)) + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) { int physicalHeight; glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight); @@ -910,11 +949,11 @@ int GetMonitorPhysicalHeight(int monitor) // Get the human-readable, UTF-8 encoded name of the primary monitor const char *GetMonitorName(int monitor) { -#if defined(PLATFORM_DESKTOP) +#if defined(PLATFORM_DESKTOP) int monitorCount; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - - if ((monitor >= 0) && (monitor < monitorCount)) + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) { return glfwGetMonitorName(monitors[monitor]); } @@ -1004,7 +1043,7 @@ void EndDrawing(void) if (gifRecording) { gifFramesCounter++; - + // NOTE: We record one gif frame every 10 game frames if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0) { @@ -1012,20 +1051,20 @@ void EndDrawing(void) // NOTE: This process is very slow... :( unsigned char *screenData = rlReadScreenPixels(screenWidth, screenHeight); GifWriteFrame(screenData, screenWidth, screenHeight, 10, 8, false); - + free(screenData); // Free image data } - + if (((gifFramesCounter/15)%2) == 1) { DrawCircle(30, screenHeight - 20, 10, RED); DrawText("RECORDING", 50, screenHeight - 25, 10, MAROON); } - + rlglDraw(); // Draw RECORDING message } #endif - + SwapBuffers(); // Copy back buffer to front buffer PollInputEvents(); // Poll user events @@ -1033,7 +1072,7 @@ void EndDrawing(void) currentTime = GetTime(); drawTime = currentTime - previousTime; previousTime = currentTime; - + frameTime = updateTime + drawTime; // Wait for some milliseconds... @@ -1079,14 +1118,14 @@ void EndMode2D(void) void BeginMode3D(Camera3D camera) { rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) - + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection rlLoadIdentity(); // Reset current matrix (PROJECTION) float aspect = (float)screenWidth/(float)screenHeight; - if (camera.type == CAMERA_PERSPECTIVE) + if (camera.type == CAMERA_PERSPECTIVE) { // Setup perspective projection double top = 0.01*tan(camera.fovy*0.5*DEG2RAD); @@ -1135,7 +1174,7 @@ void BeginTextureMode(RenderTexture2D target) rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlEnableRenderTexture(target.id); // Enable render target - + rlClearScreenBuffers(); // Clear render texture buffers // Set viewport to framebuffer size @@ -1196,7 +1235,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) Matrix matProj = MatrixIdentity(); - if (camera.type == CAMERA_PERSPECTIVE) + if (camera.type == CAMERA_PERSPECTIVE) { // Calculate projection matrix from perspective matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), 0.01, 1000.0); @@ -1206,7 +1245,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) float aspect = (float)screenWidth/(float)screenHeight; double top = camera.fovy/2.0; double right = top*aspect; - + // Calculate projection matrix from orthographic matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); } @@ -1216,7 +1255,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) Vector3 farPoint = rlUnproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); // Unproject the mouse cursor in the near plane. - // We need this as the source position because orthographic projects, compared to perspect doesn't have a + // We need this as the source position because orthographic projects, compared to perspect doesn't have a // convergence point, meaning that the "eye" of the camera is more like a plane than a point. Vector3 cameraPlanePointerPos = rlUnproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); @@ -1248,7 +1287,7 @@ Vector2 GetWorldToScreen(Vector3 position, Camera camera) float aspect = (float)screenWidth/(float)screenHeight; double top = camera.fovy/2.0; double right = top*aspect; - + // Calculate projection matrix from orthographic matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); } @@ -1335,7 +1374,7 @@ Vector4 ColorNormalize(Color color) result.y = (float)color.g/255.0f; result.z = (float)color.b/255.0f; result.w = (float)color.a/255.0f; - + return result; } @@ -1355,27 +1394,27 @@ Vector3 ColorToHSV(Color color) hsv.z = max; // Value delta = max - min; - + if (delta < 0.00001f) { hsv.y = 0.0f; hsv.x = 0.0f; // Undefined, maybe NAN? return hsv; } - - if (max > 0.0f) + + if (max > 0.0f) { // NOTE: If max is 0, this divide would cause a crash hsv.y = (delta/max); // Saturation - } - else + } + else { // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined hsv.y = 0.0f; hsv.x = NAN; // Undefined return hsv; } - + // NOTE: Comparing float values could not work properly if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta else @@ -1383,7 +1422,7 @@ Vector3 ColorToHSV(Color color) if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan } - + hsv.x *= 60.0f; // Convert to degrees if (hsv.x < 0.0f) hsv.x += 360.0f; @@ -1459,6 +1498,21 @@ void TakeScreenshot(const char *fileName) #endif } +// Check if the file exists +bool FileExists(const char *fileName) +{ + bool result = false; + +#if defined(_WIN32) + if (_access(fileName, 0) != -1) +#else + if (access(fileName, F_OK) != -1) +#endif + result = true; + + return result; +} + // Check file extension bool IsFileExtension(const char *fileName, const char *ext) { @@ -1470,7 +1524,7 @@ bool IsFileExtension(const char *fileName, const char *ext) #if defined(_WIN32) result = true; int extLen = strlen(ext); - + if (strlen(fileExt) == extLen) { for (int i = 0; i < extLen; i++) @@ -1495,9 +1549,9 @@ bool IsFileExtension(const char *fileName, const char *ext) const char *GetExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); - + if (!dot || dot == fileName) return NULL; - + return (dot + 1); } @@ -1519,19 +1573,55 @@ const char *GetFileName(const char *filePath) return fileName + 1; } +// Get filename string without extension (memory should be freed) +const char *GetFileNameWithoutExt(const char *filePath) +{ + char *result, *lastDot, *lastSep; + + char nameDot = '.'; // Default filename to extension separator character + char pathSep = '/'; // Default filepath separator character + + // Error checks and allocate string + if (filePath == NULL) return NULL; + + // Try to allocate new string, same size as original + // NOTE: By default strlen() does not count the '\0' character + if ((result = malloc(strlen(filePath) + 1)) == NULL) return NULL; + + strcpy(result, filePath); // Make a copy of the string + + // NOTE: strrchr() returns a pointer to the last occurrence of character + lastDot = strrchr(result, nameDot); + lastSep = (pathSep == 0) ? NULL : strrchr(result, pathSep); + + if (lastDot != NULL) // Check if it has an extension separator... + { + if (lastSep != NULL) // ...and it's before the extenstion separator... + { + if (lastSep < lastDot) + { + *lastDot = '\0'; // ...then remove it + } + } + else *lastDot = '\0'; // Has extension separator with no path separator + } + + return result; // Return the modified string +} // Get directory for a given fileName (with path) const char *GetDirectoryPath(const char *fileName) { const char *lastSlash = NULL; - static char filePath[256]; // MAX_DIRECTORY_PATH_SIZE = 256 - memset(filePath, 0, 256); + static char filePath[MAX_FILEPATH_LENGTH]; + memset(filePath, 0, MAX_FILEPATH_LENGTH); lastSlash = strprbrk(fileName, "\\/"); if (!lastSlash) return NULL; + // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' strncpy(filePath, fileName, strlen(fileName) - (strlen(lastSlash) - 1)); - filePath[strlen(fileName) - strlen(lastSlash)] = '\0'; + filePath[strlen(fileName) - strlen(lastSlash)] = '\0'; // Add '\0' manually return filePath; } @@ -1539,50 +1629,49 @@ const char *GetDirectoryPath(const char *fileName) // Get current working directory const char *GetWorkingDirectory(void) { - static char currentDir[256]; // MAX_DIRECTORY_PATH_SIZE = 256 - memset(currentDir, 0, 256); - - GETCWD(currentDir, 256 - 1); - + static char currentDir[MAX_FILEPATH_LENGTH]; + memset(currentDir, 0, MAX_FILEPATH_LENGTH); + + GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); + return currentDir; } -// Get filenames in a directory path (max 256 files) +// Get filenames in a directory path (max 512 files) // NOTE: Files count is returned by parameters pointer char **GetDirectoryFiles(const char *dirPath, int *fileCount) { - #define MAX_FILEPATH_LENGTH 256 #define MAX_DIRECTORY_FILES 512 - + ClearDirectoryFiles(); // Memory allocation for MAX_DIRECTORY_FILES dirFilesPath = (char **)malloc(sizeof(char *)*MAX_DIRECTORY_FILES); - for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)malloc(sizeof(char)*MAX_FILEPATH_LENGTH); - + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)malloc(sizeof(char)*MAX_FILEPATH_LENGTH); + int counter = 0; struct dirent *ent; DIR *dir = opendir(dirPath); - + if (dir != NULL) // It's a directory { - // TODO: Reading could be done in two passes, + // TODO: Reading could be done in two passes, // first one to count files and second one to read names // That way we can allocate required memory, instead of a limited pool - + while ((ent = readdir(dir)) != NULL) { strcpy(dirFilesPath[counter], ent->d_name); counter++; } - + closedir(dir); } else TraceLog(LOG_WARNING, "Can not open directory...\n"); // Maybe it's a file... dirFilesCount = counter; *fileCount = dirFilesCount; - + return dirFilesPath; } @@ -1641,6 +1730,21 @@ void ClearDroppedFiles(void) #endif } +// Get file modification time (last write time) +RLAPI long GetFileModTime(const char *fileName) +{ + struct stat result = { 0 }; + + if (stat(fileName, &result) == 0) + { + time_t mod = result.st_mtime; + + return (long)mod; + } + + return 0; +} + // Save integer value to storage file (to defined position) // NOTE: Storage positions is directly related to file memory layout (4 bytes each integer) void StorageSaveValue(int position, int value) @@ -1670,11 +1774,11 @@ void StorageSaveValue(int position, int value) int fileSize = ftell(storageFile); // Size in bytes fseek(storageFile, 0, SEEK_SET); - if (fileSize < (position*4)) TraceLog(LOG_WARNING, "Storage position could not be found"); + if (fileSize < (position*sizeof(int))) TraceLog(LOG_WARNING, "Storage position could not be found"); else { - fseek(storageFile, (position*4), SEEK_SET); - fwrite(&value, 1, 4, storageFile); + fseek(storageFile, (position*sizeof(int)), SEEK_SET); + fwrite(&value, 1, sizeof(int), storageFile); } fclose(storageFile); @@ -1720,6 +1824,34 @@ int StorageLoadValue(int position) return value; } +// Open URL with default system browser (if available) +// NOTE: This function is onlyl safe to use if you control the URL given. +// A user could craft a malicious string performing another action. +// Only call this function yourself not with user input or make sure to check the string yourself. +// CHECK: https://github.com/raysan5/raylib/issues/686 +void OpenURL(const char *url) +{ + // Small security check trying to avoid (partially) malicious code... + // sorry for the inconvenience when you hit this point... + if (strchr(url, '\'') != NULL) + { + TraceLog(LOG_WARNING, "Provided URL does not seem to be valid."); + } else { + char *cmd = calloc(strlen(url) + 10, sizeof(char)); + +#if defined(_WIN32) + sprintf(cmd, "explorer '%s'", url); +#elif defined(__linux__) + sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser +#elif defined(__APPLE__) + sprintf(cmd, "open '%s'", url); +#endif + system(cmd); + + free(cmd); + } +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions //---------------------------------------------------------------------------------- @@ -2056,7 +2188,9 @@ Vector2 GetTouchPosition(int index) position.x = position.x*((float)renderWidth/(float)displayWidth) - renderOffsetX/2; position.y = position.y*((float)renderHeight/(float)displayHeight) - renderOffsetY/2; } -#else // PLATFORM_DESKTOP, PLATFORM_RPI +#elif defined(PLATFORM_RPI) + position = touchPosition[index]; +#else // PLATFORM_DESKTOP if (index == 0) position = GetMousePosition(); #endif @@ -2158,7 +2292,7 @@ static bool InitGraphicsDevice(int width, int height) glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! - // Other values: GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE + // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE #if defined(__APPLE__) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // OSX Requires fordward compatibility #else @@ -2256,10 +2390,10 @@ static bool InitGraphicsDevice(int width, int height) #endif glfwMakeContextCurrent(window); - + // Try to disable GPU V-Sync by default, set framerate using SetTargetFPS() // NOTE: V-Sync can be enabled by graphic driver configuration - glfwSwapInterval(0); + glfwSwapInterval(0); #if defined(PLATFORM_DESKTOP) // Load OpenGL 3.3 extensions @@ -2271,6 +2405,7 @@ static bool InitGraphicsDevice(int width, int height) // NOTE: V-Sync can be enabled by graphic driver configuration if (configFlags & FLAG_VSYNC_HINT) { + // WARNING: It seems to hits a critical render path in Intel HD Graphics glfwSwapInterval(1); TraceLog(LOG_INFO, "Trying to enable VSYNC"); } @@ -2343,14 +2478,14 @@ static bool InitGraphicsDevice(int width, int height) // EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER is an optimization that can have large performance benefits on mobile devices. // Its syntax is subject to change, though. Please update your Visual Studio templates if you experience compilation issues with it. EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, - - // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call - // the IDXGIDevice3::Trim method on behalf of the application when it gets suspended. + + // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call + // the IDXGIDevice3::Trim method on behalf of the application when it gets suspended. // Calling IDXGIDevice3::Trim when an application is suspended is a Windows Store application certification requirement. EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, EGL_NONE, }; - + const EGLint fl9_3DisplayAttributes[] = { // These can be used to request ANGLE's D3D11 renderer, with D3D11 Feature Level 9_3. @@ -2373,7 +2508,7 @@ static bool InitGraphicsDevice(int width, int height) EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, EGL_NONE, }; - + EGLConfig config = NULL; // eglGetPlatformDisplayEXT is an alternative to eglGetDisplay. It allows us to pass in display attributes, used to configure D3D11. @@ -2385,15 +2520,15 @@ static bool InitGraphicsDevice(int width, int height) } // - // To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying + // To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying // parameters passed to eglGetPlatformDisplayEXT: // 1) The first calls uses "defaultDisplayAttributes" as a parameter. This corresponds to D3D11 Feature Level 10_0+. - // 2) If eglInitialize fails for step 1 (e.g. because 10_0+ isn't supported by the default GPU), then we try again + // 2) If eglInitialize fails for step 1 (e.g. because 10_0+ isn't supported by the default GPU), then we try again // using "fl9_3DisplayAttributes". This corresponds to D3D11 Feature Level 9_3. - // 3) If eglInitialize fails for step 2 (e.g. because 9_3+ isn't supported by the default GPU), then we try again + // 3) If eglInitialize fails for step 2 (e.g. because 9_3+ isn't supported by the default GPU), then we try again // using "warpDisplayAttributes". This corresponds to D3D11 Feature Level 11_0 on WARP, a D3D11 software rasterizer. // - + // This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details. display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); if (display == EGL_NO_DISPLAY) @@ -2401,7 +2536,7 @@ static bool InitGraphicsDevice(int width, int height) TraceLog(LOG_WARNING, "Failed to initialize EGL display"); return false; } - + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { // This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile devices). @@ -2416,7 +2551,7 @@ static bool InitGraphicsDevice(int width, int height) { // This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU. display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes); - if (display == EGL_NO_DISPLAY) + if (display == EGL_NO_DISPLAY) { TraceLog(LOG_WARNING, "Failed to initialize EGL display"); return false; @@ -2444,7 +2579,7 @@ static bool InitGraphicsDevice(int width, int height) //PropertySet^ surfaceCreationProperties = ref new PropertySet(); //surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), window); // CoreWindow^ window - // You can configure the surface to render at a lower resolution and be scaled up to + // You can configure the surface to render at a lower resolution and be scaled up to // the full window size. The scaling is often free on mobile hardware. // // One way to configure the SwapChainPanel is to specify precisely which resolution it should render at. @@ -2456,20 +2591,20 @@ static bool InitGraphicsDevice(int width, int height) // float customResolutionScale = 0.5f; // surfaceCreationProperties->Insert(ref new String(EGLRenderResolutionScaleProperty), PropertyValue::CreateSingle(customResolutionScale)); - - // eglCreateWindowSurface() requires a EGLNativeWindowType parameter, + + // eglCreateWindowSurface() requires a EGLNativeWindowType parameter, // In Windows platform: typedef HWND EGLNativeWindowType; - - + + // Property: EGLNativeWindowTypeProperty // Type: IInspectable // Description: Set this property to specify the window type to use for creating a surface. // If this property is missing, surface creation will fail. // //const wchar_t EGLNativeWindowTypeProperty[] = L"EGLNativeWindowTypeProperty"; - + //https://stackoverflow.com/questions/46550182/how-to-create-eglsurface-using-c-winrt-and-angle - + //surface = eglCreateWindowSurface(display, config, reinterpret_cast(surfaceCreationProperties), surfaceAttributes); surface = eglCreateWindowSurface(display, config, uwpWindow, surfaceAttributes); if (surface == EGL_NO_SURFACE) @@ -2485,16 +2620,16 @@ static bool InitGraphicsDevice(int width, int height) return false; } - // Get EGL display window size + // Get EGL display window size eglQuerySurface(display, surface, EGL_WIDTH, &screenWidth); eglQuerySurface(display, surface, EGL_HEIGHT, &screenHeight); - + #else // PLATFORM_ANDROID, PLATFORM_RPI EGLint numConfigs; // Get an EGL display connection display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) + if (display == EGL_NO_DISPLAY) { TraceLog(LOG_WARNING, "Failed to initialize EGL display"); return false; @@ -2727,7 +2862,7 @@ static void SetupFramebuffer(int width, int height) static void InitTimer(void) { srand((unsigned int)time(NULL)); // Initialize random seed - + #if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) #endif @@ -2823,6 +2958,20 @@ static void PollInputEvents(void) gamepadAxisCount = 0; #endif +#if defined(PLATFORM_RPI) + // Register previous keys states + for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i]; + + // Register previous mouse states + previousMouseWheelY = currentMouseWheelY; + currentMouseWheelY = 0; + for (int i = 0; i < 3; i++) + { + previousMouseState[i] = currentMouseState[i]; + currentMouseState[i] = currentMouseStateEvdev[i]; + } +#endif + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // Mouse input polling double mouseX; @@ -2894,8 +3043,12 @@ static void PollInputEvents(void) } } +#if defined(SUPPORT_EVENTS_WAITING) + glfwWaitEvents(); +#else glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! #endif +#endif //defined(PLATFORM_DESKTOP) // Gamepad support using emscripten API // NOTE: GLFW3 joystick functionality not available in web @@ -2977,11 +3130,12 @@ static void SwapBuffers(void) #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) glfwSwapBuffers(window); #if __APPLE__ - // workaround for missing/erroneous initial rendering on macOS - if (windowNeedsUpdating) { + // Workaround for missing/erroneous initial rendering on macOS + if (windowNeedsUpdating) + { // Desugared version of Objective C: [glfwGetNSGLContext(window) update] - ((id (*)(id, SEL))objc_msgSend)(glfwGetNSGLContext(window), - sel_registerName("update")); + ((id (*)(id, SEL))objc_msgSend)(glfwGetNSGLContext(window), sel_registerName("update")); + windowNeedsUpdating--; } #endif @@ -3024,19 +3178,19 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i { GifEnd(); gifRecording = false; - + TraceLog(LOG_INFO, "End animated GIF recording"); } - else + else { gifRecording = true; gifFramesCounter = 0; - + // NOTE: delay represents the time between frames in the gif, if we capture a gif frame every // 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10. GifBegin(FormatText("screenrec%03i.gif", screenshotCounter), screenWidth, screenHeight, (int)(GetFrameTime()*10.0f), 8, false); screenshotCounter++; - + TraceLog(LOG_INFO, "Begin animated GIF recording: %s", FormatText("screenrec%03i.gif", screenshotCounter)); } } @@ -3053,7 +3207,7 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i else { currentKeyState[key] = action; - + // NOTE: lastKeyPressed already registered on CharCallback() //if (action == GLFW_PRESS) lastKeyPressed = key; } @@ -3123,12 +3277,12 @@ static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) // GLFW3 Char Key Callback, runs on key down (get unicode char value) static void CharCallback(GLFWwindow *window, unsigned int key) -{ +{ // NOTE: Registers any key down considering OS keyboard layout but // do not detects action events, those should be managed by user... // https://github.com/glfw/glfw/issues/668#issuecomment-166794907 // http://www.glfw.org/docs/latest/input_guide.html#input_char - + lastKeyPressed = key; } @@ -3164,7 +3318,7 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) } // GLFW3 WindowIconify Callback, runs when window is minimized/restored -static void WindowIconifyCallback(GLFWwindow* window, int iconified) +static void WindowIconifyCallback(GLFWwindow *window, int iconified) { if (iconified) windowMinimized = true; // The window was iconified else windowMinimized = false; // The window was restored @@ -3183,7 +3337,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths for (int i = 0; i < count; i++) { - dropFilesPath[i] = (char *)malloc(sizeof(char)*256); // Max path length set to 256 char + dropFilesPath[i] = (char *)malloc(sizeof(char)*MAX_FILEPATH_LENGTH); // Max path length using MAX_FILEPATH_LENGTH set to 256 char strcpy(dropFilesPath[i], paths[i]); } @@ -3229,7 +3383,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) { // Init graphics device (display device and OpenGL context) InitGraphicsDevice(screenWidth, screenHeight); - + // Init hi-res timer InitTimer(); @@ -3337,15 +3491,15 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) // Get second touch position touchPosition[1].x = AMotionEvent_getX(event, 1); touchPosition[1].y = AMotionEvent_getY(event, 1); - + // Useful functions for gamepad inputs: //AMotionEvent_getAction() //AMotionEvent_getAxisValue() //AMotionEvent_getButtonState() - + // Gamepad dpad button presses capturing // TODO: That's weird, key input (or button) - // shouldn't come as a TYPE_MOTION event... + // shouldn't come as a TYPE_MOTION event... int32_t keycode = AKeyEvent_getKeyCode(event); if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) { @@ -3421,7 +3575,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) // Normalize gestureEvent.position[x] for screenWidth and screenHeight gestureEvent.position[0].x /= (float)GetScreenWidth(); gestureEvent.position[0].y /= (float)GetScreenHeight(); - + gestureEvent.position[1].x /= (float)GetScreenWidth(); gestureEvent.position[1].y /= (float)GetScreenHeight(); @@ -3429,14 +3583,14 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) ProcessGestureEvent(gestureEvent); } #else - + // Support only simple touch position if (flags == AMOTION_EVENT_ACTION_DOWN) { // Get first touch position touchPosition[0].x = AMotionEvent_getX(event, 0); touchPosition[0].y = AMotionEvent_getY(event, 0); - + touchPosition[0].x /= (float)GetScreenWidth(); touchPosition[0].y /= (float)GetScreenHeight(); } @@ -3499,10 +3653,10 @@ static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent emscripten_get_pointerlock_status(&plce); //if (plce.isActive) TraceLog(LOG_WARNING, "Pointer lock exit did not work!"); } - + toggleCursorLock = false; } - + return 0; } @@ -3756,184 +3910,364 @@ static void RestoreKeyboard(void) // Mouse initialization (including mouse thread) static void InitMouse(void) { - // NOTE: We can use /dev/input/mice to read from all available mice - if ((mouseStream = open(DEFAULT_MOUSE_DEV, O_RDONLY|O_NONBLOCK)) < 0) + char Path[MAX_FILEPATH_LENGTH]; + DIR *directory; + struct dirent *entity; + + // Reset variables + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) { - TraceLog(LOG_WARNING, "Mouse device could not be opened, no mouse available"); + touchPosition[i].x = -1; + touchPosition[i].y = -1; + } + + // Open the linux directory of "/dev/input" + directory = opendir(DEFAULT_EVDEV_PATH); + if (directory) + { + while ((entity = readdir(directory)) != NULL) + { + if (strncmp("event", entity->d_name, strlen("event")) == 0) // Search for devices named "event*" + { + sprintf(Path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + EventThreadSpawn(Path); // Identify the device and spawn a thread for it + } + } + + closedir(directory); } else { - mouseReady = true; - - int error = pthread_create(&mouseThreadId, NULL, &MouseThread, NULL); - - if (error != 0) TraceLog(LOG_WARNING, "Error creating mouse input event thread"); - else TraceLog(LOG_INFO, "Mouse device initialized successfully"); + TraceLog(LOG_WARNING, "Unable to open linux event directory %s", DEFAULT_EVDEV_PATH); } } -// Mouse reading thread -// NOTE: We need a separate thread to avoid loosing mouse events, -// if too much time passes between reads, queue gets full and new events override older ones... -static void *MouseThread(void *arg) +static void EventThreadSpawn(char *device) { - const unsigned char XSIGN = (1 << 4); - const unsigned char YSIGN = (1 << 5); + #define BITS_PER_LONG (sizeof(long)*8) + #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) + #define OFF(x) ((x)%BITS_PER_LONG) + #define BIT(x) (1UL<> OFF(bit)) & 1) - typedef struct { - char buttons; - char dx, dy; - } MouseEvent; + struct input_absinfo absinfo; + unsigned long evBits[NBITS(EV_MAX)]; + unsigned long absBits[NBITS(ABS_MAX)]; + unsigned long relBits[NBITS(REL_MAX)]; + unsigned long keyBits[NBITS(KEY_MAX)]; + bool hasAbs = false; + bool hasRel = false; + bool hasAbsMulti = false; + int freeWorkerId = -1; + int fd = -1; - MouseEvent mouse; + InputEventWorker *worker; - int mouseRelX = 0; - int mouseRelY = 0; + /////////////////////////////////// Open the device and allocate worker ///////////////////////////////////////////// - while (!windowShouldClose) + // Find a free spot in the workers array + for (int i = 0; i < sizeof(eventWorkers)/sizeof(InputEventWorker); ++i) { - if (read(mouseStream, &mouse, sizeof(MouseEvent)) == (int)sizeof(MouseEvent)) + if (eventWorkers[i].threadId == 0) { - if ((mouse.buttons & 0x08) == 0) break; // This bit should always be set - - // Check Left button pressed - if ((mouse.buttons & 0x01) > 0) currentMouseState[0] = 1; - else currentMouseState[0] = 0; - - // Check Right button pressed - if ((mouse.buttons & 0x02) > 0) currentMouseState[1] = 1; - else currentMouseState[1] = 0; - - // Check Middle button pressed - if ((mouse.buttons & 0x04) > 0) currentMouseState[2] = 1; - else currentMouseState[2] = 0; - - mouseRelX = (int)mouse.dx; - mouseRelY = (int)mouse.dy; - - if ((mouse.buttons & XSIGN) > 0) mouseRelX = -1*(255 - mouseRelX); - if ((mouse.buttons & YSIGN) > 0) mouseRelY = -1*(255 - mouseRelY); - - // NOTE: Mouse movement is normalized to not be screen resolution dependant - // We suppose 2*255 (max relative movement) is equivalent to screenWidth (max pixels width) - // Result after normalization is multiplied by MOUSE_SENSITIVITY factor - - mousePosition.x += (float)mouseRelX*((float)screenWidth/(2*255))*MOUSE_SENSITIVITY; - mousePosition.y -= (float)mouseRelY*((float)screenHeight/(2*255))*MOUSE_SENSITIVITY; - - if (mousePosition.x < 0) mousePosition.x = 0; - if (mousePosition.y < 0) mousePosition.y = 0; - - if (mousePosition.x > screenWidth) mousePosition.x = screenWidth; - if (mousePosition.y > screenHeight) mousePosition.y = screenHeight; - } - //else read(mouseStream, &mouse, 1); // Try to sync up again - } - - return NULL; -} - -// Touch initialization (including touch thread) -static void InitTouch(void) -{ - if ((touchStream = open(DEFAULT_TOUCH_DEV, O_RDONLY|O_NONBLOCK)) < 0) - { - TraceLog(LOG_WARNING, "Touch device could not be opened, no touchscreen available"); - } - else - { - touchReady = true; - - int error = pthread_create(&touchThreadId, NULL, &TouchThread, NULL); - - if (error != 0) TraceLog(LOG_WARNING, "Error creating touch input event thread"); - else TraceLog(LOG_INFO, "Touch device initialized successfully"); - } -} - -// Touch reading thread. -// This reads from a Virtual Input Event /dev/input/event4 which is -// created by the ts_uinput daemon. This takes, filters and scales -// raw input from the Touchscreen (which appears in /dev/input/event3) -// based on the Calibration data referenced by tslib. -static void *TouchThread(void *arg) -{ - struct input_event ev; - GestureEvent gestureEvent; - - while (!windowShouldClose) - { - if (read(touchStream, &ev, sizeof(ev)) == (int)sizeof(ev)) - { - // if pressure > 0 then simulate left mouse button click - if (ev.type == EV_ABS && ev.code == 24 && ev.value == 0 && currentMouseState[0] == 1) - { - currentMouseState[0] = 0; - gestureEvent.touchAction = TOUCH_UP; - gestureEvent.pointCount = 1; - gestureEvent.pointerId[0] = 0; - gestureEvent.pointerId[1] = 1; - gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[0].x /= (float)GetScreenWidth(); - gestureEvent.position[0].y /= (float)GetScreenHeight(); - gestureEvent.position[1].x /= (float)GetScreenWidth(); - gestureEvent.position[1].y /= (float)GetScreenHeight(); - ProcessGestureEvent(gestureEvent); - } - if (ev.type == EV_ABS && ev.code == 24 && ev.value > 0 && currentMouseState[0] == 0) - { - currentMouseState[0] = 1; - gestureEvent.touchAction = TOUCH_DOWN; - gestureEvent.pointCount = 1; - gestureEvent.pointerId[0] = 0; - gestureEvent.pointerId[1] = 1; - gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[0].x /= (float)GetScreenWidth(); - gestureEvent.position[0].y /= (float)GetScreenHeight(); - gestureEvent.position[1].x /= (float)GetScreenWidth(); - gestureEvent.position[1].y /= (float)GetScreenHeight(); - ProcessGestureEvent(gestureEvent); - } - // x & y values supplied by event4 have been scaled & de-jittered using tslib calibration data - if (ev.type == EV_ABS && ev.code == 0) - { - mousePosition.x = ev.value; - if (mousePosition.x < 0) mousePosition.x = 0; - if (mousePosition.x > screenWidth) mousePosition.x = screenWidth; - gestureEvent.touchAction = TOUCH_MOVE; - gestureEvent.pointCount = 1; - gestureEvent.pointerId[0] = 0; - gestureEvent.pointerId[1] = 1; - gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[0].x /= (float)GetScreenWidth(); - gestureEvent.position[0].y /= (float)GetScreenHeight(); - gestureEvent.position[1].x /= (float)GetScreenWidth(); - gestureEvent.position[1].y /= (float)GetScreenHeight(); - ProcessGestureEvent(gestureEvent); - } - if (ev.type == EV_ABS && ev.code == 1) - { - mousePosition.y = ev.value; - if (mousePosition.y < 0) mousePosition.y = 0; - if (mousePosition.y > screenHeight) mousePosition.y = screenHeight; - gestureEvent.touchAction = TOUCH_MOVE; - gestureEvent.pointCount = 1; - gestureEvent.pointerId[0] = 0; - gestureEvent.pointerId[1] = 1; - gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; - gestureEvent.position[0].x /= (float)GetScreenWidth(); - gestureEvent.position[0].y /= (float)GetScreenHeight(); - gestureEvent.position[1].x /= (float)GetScreenWidth(); - gestureEvent.position[1].y /= (float)GetScreenHeight(); - ProcessGestureEvent(gestureEvent); - } - + freeWorkerId = i; + break; } } + + // Select the free worker from array + if (freeWorkerId >= 0) + { + worker = &(eventWorkers[freeWorkerId]); // Grab a pointer to the worker + memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker + } + else + { + TraceLog(LOG_WARNING, "Error creating input device thread for '%s': Out of worker slots", device); + return; + } + + // Open the device + fd = open(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) + { + TraceLog(LOG_WARNING, "Error creating input device thread for '%s': Can't open device (Err: %d)", device, worker->fd); + return; + } + worker->fd = fd; + + //Grab number on the end of the devices name "event" + int devNum = 0; + char *ptrDevName = strrchr(device, 't'); + worker->eventNum = -1; + + if (ptrDevName != NULL) + { + if (sscanf(ptrDevName, "t%d", &devNum) == 1) + worker->eventNum = devNum; + } + + // At this point we have a connection to the device, + // but we don't yet know what the device is (Could be + // many things, even as simple as a power button) + + /////////////////////////////////// Identify the device ///////////////////////////////////////////// + + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the avalable device properties + + // Check for absolute input devices + if (TEST_BIT(evBits, EV_ABS)) + { + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); + + // Check for absolute movement support (usualy touchscreens, but also joysticks) + if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) + { + hasAbs = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + + // Check for multiple absolute movement support (usualy multitouch touchscreens) + if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) + { + hasAbsMulti = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + } + + // Check for relative movement support (usualy mouse) + if (TEST_BIT(evBits, EV_REL)) + { + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); + + if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; + } + + // Check for button support to determine the device type(usualy on all input devices) + if (TEST_BIT(evBits, EV_KEY)) + { + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); + + if (hasAbs || hasAbsMulti) + { + if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen + if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet + if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device + } + + if (hasRel) + { + if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse + if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse + } + + if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + + if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard + } + + + /////////////////////////////////// Decide what to do with the device ///////////////////////////////////////////// + if (worker->isTouch || worker->isMouse) + { + // Looks like a interesting device + TraceLog(LOG_INFO, "Opening input device '%s' (%s%s%s%s%s)", device, + worker->isMouse ? "mouse " : "", + worker->isMultitouch ? "multitouch " : "", + worker->isTouch ? "touchscreen " : "", + worker->isGamepad ? "gamepad " : "", + worker->isKeyboard ? "keyboard " : ""); + + // Create a thread for this device + int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); + if (error != 0) + { + TraceLog(LOG_WARNING, "Error creating input device thread for '%s': Can't create thread (Err: %d)", device, error); + worker->threadId = 0; + close(fd); + } + +#if defined(USE_LAST_TOUCH_DEVICE) + // Find touchscreen with the highest index + int maxTouchNumber = -1; + + for (int i = 0; i < sizeof(eventWorkers)/sizeof(InputEventWorker); ++i) + { + if (eventWorkers[i].isTouch && (eventWorkers[i].eventNum > maxTouchNumber)) maxTouchNumber = eventWorkers[i].eventNum; + } + + // Find toucnscreens with lower indexes + for (int i = 0; i < sizeof(eventWorkers)/sizeof(InputEventWorker); ++i) + { + if (eventWorkers[i].isTouch && (eventWorkers[i].eventNum < maxTouchNumber)) + { + if (eventWorkers[i].threadId != 0) + { + TraceLog(LOG_WARNING, "Duplicate touchscreen found, killing toucnscreen on event%d", i); + pthread_cancel(eventWorkers[i].threadId); + close(eventWorkers[i].fd); + } + } + } +#endif + } + else close(fd); // We are not interested in this device +} + +static void *EventThread(void *arg) +{ + struct input_event event; + GestureEvent gestureEvent; + InputEventWorker *worker = (InputEventWorker *)arg; + bool GestureNeedsUpdate = false; + + while (!windowShouldClose) + { + if (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) + { + /////////////////////////////// Relative movement parsing //////////////////////////////////// + if (event.type == EV_REL) + { + if (event.code == REL_X) + { + mousePosition.x += event.value; + touchPosition[0].x = mousePosition.x; + gestureEvent.touchAction = TOUCH_MOVE; + GestureNeedsUpdate = true; + } + + if (event.code == REL_Y) + { + mousePosition.y += event.value; + touchPosition[0].y = mousePosition.y; + gestureEvent.touchAction = TOUCH_MOVE; + GestureNeedsUpdate = true; + } + + if (event.code == REL_WHEEL) + { + currentMouseWheelY += event.value; + } + } + + /////////////////////////////// Absolute movement parsing //////////////////////////////////// + if (event.type == EV_ABS) + { + // Basic movement + if (event.code == ABS_X) + { + mousePosition.x = (event.value - worker->absRange.x)*screenWidth/worker->absRange.width; // Scale acording to absRange + gestureEvent.touchAction = TOUCH_MOVE; + GestureNeedsUpdate = true; + } + + if (event.code == ABS_Y) + { + mousePosition.y = (event.value - worker->absRange.y)*screenHeight/worker->absRange.height; // Scale acording to absRange + gestureEvent.touchAction = TOUCH_MOVE; + GestureNeedsUpdate = true; + } + + // Multitouch movement + if (event.code == ABS_MT_SLOT) + { + worker->touchSlot = event.value; // Remeber the slot number for the folowing events + } + + if (event.code == ABS_MT_POSITION_X) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) + touchPosition[worker->touchSlot].x = (event.value - worker->absRange.x)*screenWidth/worker->absRange.width; // Scale acording to absRange + } + + if (event.code == ABS_MT_POSITION_Y) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) + touchPosition[worker->touchSlot].y = (event.value - worker->absRange.y)*screenHeight/worker->absRange.height; // Scale acording to absRange + } + + if (event.code == ABS_MT_TRACKING_ID) + { + if ( (event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS) ) + { + // Touch has ended for this point + touchPosition[worker->touchSlot].x = -1; + touchPosition[worker->touchSlot].y = -1; + } + } + } + + /////////////////////////////// Button parsing //////////////////////////////////// + if (event.type == EV_KEY) + { + if((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) + { + currentMouseStateEvdev[MOUSE_LEFT_BUTTON] = event.value; + if (event.value > 0) gestureEvent.touchAction = TOUCH_DOWN; + else gestureEvent.touchAction = TOUCH_UP; + GestureNeedsUpdate = true; + } + + if (event.code == BTN_RIGHT) currentMouseStateEvdev[MOUSE_RIGHT_BUTTON] = event.value; + + if (event.code == BTN_MIDDLE) currentMouseStateEvdev[MOUSE_MIDDLE_BUTTON] = event.value; + } + + /////////////////////////////// Screen confinement //////////////////////////////////// + if (mousePosition.x < 0) mousePosition.x = 0; + if (mousePosition.x > screenWidth/mouseScale) mousePosition.x = screenWidth/mouseScale; + + if (mousePosition.y < 0) mousePosition.y = 0; + if (mousePosition.y > screenHeight/mouseScale) mousePosition.y = screenHeight/mouseScale; + + /////////////////////////////// Gesture update //////////////////////////////////// + if (GestureNeedsUpdate) + { + gestureEvent.pointCount = 0; + if (touchPosition[0].x >= 0) gestureEvent.pointCount++; + if (touchPosition[1].x >= 0) gestureEvent.pointCount++; + if (touchPosition[2].x >= 0) gestureEvent.pointCount++; + if (touchPosition[3].x >= 0) gestureEvent.pointCount++; + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.pointerId[2] = 2; + gestureEvent.pointerId[3] = 3; + gestureEvent.position[0] = touchPosition[0]; + gestureEvent.position[1] = touchPosition[1]; + gestureEvent.position[2] = touchPosition[2]; + gestureEvent.position[3] = touchPosition[3]; + ProcessGestureEvent(gestureEvent); + } + } + else + { + usleep(5000); // Sleep for 5ms to avoid hogging CPU time + } + } + + close(worker->fd); + return NULL; } @@ -4017,6 +4351,10 @@ static void *GamepadThread(void *arg) } } } + else + { + usleep(1000); //Sleep for 1ms to avoid hogging CPU time + } } } diff --git a/raylib/external/dirent.h b/raylib/external/dirent.h new file mode 100644 index 0000000..a955253 --- /dev/null +++ b/raylib/external/dirent.h @@ -0,0 +1,198 @@ +#ifndef DIRENT_H +#define DIRENT_H + +/* + + Declaration of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003. + Rights: See end of file. + +*/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct DIR DIR; + +struct dirent +{ + char *d_name; +}; + +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); + +/* + + Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ + +#ifdef __cplusplus +} +#endif + +#endif // DIRENT_H + +/* + + Implementation of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003 and July 2012. + Rights: See end of file. + +*/ + +#include +#include /* _findfirst and _findnext set errno iff they return -1 */ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */ + +struct DIR +{ + handle_type handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* null-terminated char string */ +}; + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if(name && name[0]) + { + size_t base_length = strlen(name); + const char *all = /* search pattern must end with suitable wildcard */ + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if((dir = (DIR *) malloc(sizeof *dir)) != 0 && + (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) + { + strcat(strcpy(dir->name, name), all); + + if((dir->handle = + (handle_type) _findfirst(dir->name, &dir->info)) != -1) + { + dir->result.d_name = 0; + } + else /* rollback */ + { + free(dir->name); + free(dir); + dir = 0; + } + } + else /* rollback */ + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else + { + errno = EINVAL; + } + + return dir; +} + +int closedir(DIR *dir) +{ + int result = -1; + + if(dir) + { + if(dir->handle != -1) + { + result = _findclose(dir->handle); + } + + free(dir->name); + free(dir); + } + + if(result == -1) /* map all errors to EBADF */ + { + errno = EBADF; + } + + return result; +} + +struct dirent *readdir(DIR *dir) +{ + struct dirent *result = 0; + + if(dir && dir->handle != -1) + { + if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) + { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else + { + errno = EBADF; + } + + return result; +} + +void rewinddir(DIR *dir) +{ + if(dir && dir->handle != -1) + { + _findclose(dir->handle); + dir->handle = (handle_type) _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else + { + errno = EBADF; + } +} + +#ifdef __cplusplus +} +#endif + +/* + + Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ diff --git a/raylib/external/dr_mp3.h b/raylib/external/dr_mp3.h index 467d319..070f0c1 100644 --- a/raylib/external/dr_mp3.h +++ b/raylib/external/dr_mp3.h @@ -1,5 +1,5 @@ // MP3 audio decoder. Public domain. See "unlicense" statement at the end of this file. -// dr_mp3 - v0.2.5 - 2018-06-22 +// dr_mp3 - v0.4.0 - 2018-xx-xx // // David Reid - mackron@gmail.com // @@ -52,11 +52,6 @@ // // #define DR_MP3_NO_SIMD // Disable SIMD optimizations. -// -// -// LIMITATIONS -// =========== -// - Seeking is extremely inefficient. #ifndef dr_mp3_h #define dr_mp3_h @@ -92,35 +87,34 @@ typedef drmp3_uint32 drmp3_bool32; #define DRMP3_TRUE 1 #define DRMP3_FALSE 0 -#define DRMP3_MAX_SAMPLES_PER_FRAME (1152*2) +#define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 +#define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) // Low Level Push API // ================== typedef struct { - int frame_bytes; - int channels; - int hz; - int layer; - int bitrate_kbps; + int frame_bytes, channels, hz, layer, bitrate_kbps; } drmp3dec_frame_info; typedef struct { - float mdct_overlap[2][9*32]; - float qmf_state[15*2*32]; - int reserv; - int free_format_bytes; - unsigned char header[4]; - unsigned char reserv_buf[511]; + float mdct_overlap[2][9*32], qmf_state[15*2*32]; + int reserv, free_format_bytes; + unsigned char header[4], reserv_buf[511]; } drmp3dec; // Initializes a low level decoder. void drmp3dec_init(drmp3dec *dec); // Reads a frame from a low level decoder. -int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, short *pcm, drmp3dec_frame_info *info); +int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); + +// Helper for converting between f32 and s16. +void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); + + // Main API (Pull API) @@ -216,11 +210,12 @@ typedef struct drmp3_read_proc onRead; drmp3_seek_proc onSeek; void* pUserData; - drmp3_uint32 frameChannels; // The number of channels in the currently loaded MP3 frame. Internal use only. - drmp3_uint32 frameSampleRate; // The sample rate of the currently loaded MP3 frame. Internal use only. - drmp3_uint32 framesConsumed; - drmp3_uint32 framesRemaining; - drmp3_int16 frames[DRMP3_MAX_SAMPLES_PER_FRAME]; + drmp3_uint32 mp3FrameChannels; // The number of channels in the currently loaded MP3 frame. Internal use only. + drmp3_uint32 mp3FrameSampleRate; // The sample rate of the currently loaded MP3 frame. Internal use only. + drmp3_uint32 pcmFramesConsumedInMP3Frame; + drmp3_uint32 pcmFramesRemainingInMP3Frame; + drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; // <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. + drmp3_uint64 currentPCMFrame; // The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. drmp3_src src; size_t dataSize; size_t dataCapacity; @@ -270,12 +265,21 @@ void drmp3_uninit(drmp3* pMP3); // Reads PCM frames as interleaved 32-bit IEEE floating point PCM. // // Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. -drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); +drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); // Seeks to a specific frame. // // Note that this is _not_ an MP3 frame, but rather a PCM frame. -drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex); +drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); + +// Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet +// radio. Runs in linear time. Returns 0 on error. +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); + +// Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet +// radio. Runs in linear time. Returns 0 on error. +drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); + // Opens an decodes an entire MP3 stream as a single operation. @@ -283,10 +287,10 @@ drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex); // pConfig is both an input and output. On input it contains what you want. On output it contains what you got. // // Free the returned pointer with drmp3_free(). -float* drmp3_open_and_decode_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); -float* drmp3_open_and_decode_memory_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); #ifndef DR_MP3_NO_STDIO -float* drmp3_open_and_decode_file_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); #endif // Frees any memory that was allocated by a public drmp3 API. @@ -314,8 +318,12 @@ void drmp3_free(void* p); #define DR_MP3_NO_SIMD #endif +#define DRMP3_OFFSET_PTR(p, offset) ((void*)((drmp3_uint8*)(p) + (offset))) + #define DRMP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */ +#ifndef DRMP3_MAX_FRAME_SYNC_MATCHES #define DRMP3_MAX_FRAME_SYNC_MATCHES 10 +#endif #define DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES DRMP3_MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ @@ -361,7 +369,7 @@ void drmp3_free(void* p); #if defined(_MSC_VER) #include #endif -#include +#include #define DRMP3_HAVE_SSE 1 #define DRMP3_HAVE_SIMD 1 #define DRMP3_VSTORE _mm_storeu_ps @@ -462,44 +470,27 @@ static int drmp3_have_simd() typedef struct { const drmp3_uint8 *buf; - int pos; - int limit; + int pos, limit; } drmp3_bs; typedef struct { - drmp3_uint8 total_bands; - drmp3_uint8 stereo_bands; - drmp3_uint8 bitalloc[64]; - drmp3_uint8 scfcod[64]; float scf[3*64]; + drmp3_uint8 total_bands, stereo_bands, bitalloc[64], scfcod[64]; } drmp3_L12_scale_info; typedef struct { - drmp3_uint8 tab_offset; - drmp3_uint8 code_tab_width; - drmp3_uint8 band_count; + drmp3_uint8 tab_offset, code_tab_width, band_count; } drmp3_L12_subband_alloc; typedef struct { const drmp3_uint8 *sfbtab; - drmp3_uint16 part_23_length; - drmp3_uint16 big_values; - drmp3_uint16 scalefac_compress; - drmp3_uint8 global_gain; - drmp3_uint8 block_type; - drmp3_uint8 mixed_block_flag; - drmp3_uint8 n_long_sfb; - drmp3_uint8 n_short_sfb; - drmp3_uint8 table_select[3]; - drmp3_uint8 region_count[3]; - drmp3_uint8 subblock_gain[3]; - drmp3_uint8 preflag; - drmp3_uint8 scalefac_scale; - drmp3_uint8 count1_table; - drmp3_uint8 scfsi; + drmp3_uint16 part_23_length, big_values, scalefac_compress; + drmp3_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; + drmp3_uint8 table_select[3], region_count[3], subblock_gain[3]; + drmp3_uint8 preflag, scalefac_scale, count1_table, scfsi; } drmp3_L3_gr_info; typedef struct @@ -507,10 +498,8 @@ typedef struct drmp3_bs bs; drmp3_uint8 maindata[DRMP3_MAX_BITRESERVOIR_BYTES + DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES]; drmp3_L3_gr_info gr_info[4]; - float grbuf[2][576]; - float scf[40]; + float grbuf[2][576], scf[40], syn[18 + 15][2*32]; drmp3_uint8 ist_pos[2][39]; - float syn[18 + 15][2*32]; } drmp3dec_scratch; static void drmp3_bs_init(drmp3_bs *bs, const drmp3_uint8 *data, int bytes) @@ -988,17 +977,19 @@ static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *is } } +static const float g_drmp3_pow43[129 + 16] = { + 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, + 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f +}; + static float drmp3_L3_pow_43(int x) { - static const float g_pow43[129] = { - 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f - }; float frac; int sign, mult = 256; if (x < 129) { - return g_pow43[x]; + return g_drmp3_pow43[16 + x]; } if (x < 1024) @@ -1009,12 +1000,11 @@ static float drmp3_L3_pow_43(int x) sign = 2*x & 64; frac = (float)((x & 63) - sign) / ((x & ~63) + sign); - return g_pow43[(x + sign) >> 6]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; + return g_drmp3_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; } static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *gr_info, const float *scf, int layer3gr_limit) { - static const float g_pow43_signed[32] = { 0,0,1,-1,2.519842f,-2.519842f,4.326749f,-4.326749f,6.349604f,-6.349604f,8.549880f,-8.549880f,10.902724f,-10.902724f,13.390518f,-13.390518f,16.000000f,-16.000000f,18.720754f,-18.720754f,21.544347f,-21.544347f,24.463781f,-24.463781f,27.473142f,-27.473142f,30.567351f,-30.567351f,33.741992f,-33.741992f,36.993181f,-36.993181f }; static const drmp3_int16 tabs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 785,785,785,785,784,784,784,784,513,513,513,513,513,513,513,513,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256, -255,1313,1298,1282,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,290,288, @@ -1053,7 +1043,7 @@ static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *g { int tab_num = gr_info->table_select[ireg]; int sfb_cnt = gr_info->region_count[ireg++]; - const short *codebook = tabs + tabindex[tab_num]; + const drmp3_int16 *codebook = tabs + tabindex[tab_num]; int linbits = g_linbits[tab_num]; do { @@ -1083,7 +1073,7 @@ static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *g *dst = one*drmp3_L3_pow_43(lsb)*((int32_t)bs_cache < 0 ? -1: 1); } else { - *dst = g_pow43_signed[lsb*2 + (bs_cache >> 31)]*one; + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; } DRMP3_FLUSH_BITS(lsb ? 1 : 0); } @@ -1659,18 +1649,27 @@ static void drmp3d_DCT_II(float *grbuf, int n) #endif } -static short drmp3d_scale_pcm(float sample) -{ - if (sample > 32767.0) return (short) 32767; - if (sample < -32768.0) return (short)-32768; - int s = (int)(sample + .5f); - s -= (s < 0); /* away from zero, to be compliant */ - if (s > 32767) return (short) 32767; - if (s < -32768) return (short)-32768; - return (short)s; -} +#ifndef DR_MP3_FLOAT_OUTPUT +typedef drmp3_int16 drmp3d_sample_t; -static void drmp3d_synth_pair(short *pcm, int nch, const float *z) +static drmp3_int16 drmp3d_scale_pcm(float sample) +{ + if (sample >= 32766.5) return (drmp3_int16) 32767; + if (sample <= -32767.5) return (drmp3_int16)-32768; + drmp3_int16 s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + return (drmp3_int16)s; +} +#else +typedef float drmp3d_sample_t; + +static float drmp3d_scale_pcm(float sample) +{ + return sample*(1.f/32768.f); +} +#endif + +static void drmp3d_synth_pair(drmp3d_sample_t *pcm, int nch, const float *z) { float a; a = (z[14*64] - z[ 0]) * 29; @@ -1695,11 +1694,11 @@ static void drmp3d_synth_pair(short *pcm, int nch, const float *z) pcm[16*nch] = drmp3d_scale_pcm(a); } -static void drmp3d_synth(float *xl, short *dstl, int nch, float *lins) +static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) { int i; float *xr = xl + 576*(nch - 1); - short *dstr = dstl + (nch - 1); + drmp3d_sample_t *dstr = dstl + (nch - 1); static const float g_win[] = { -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992, @@ -1756,19 +1755,20 @@ static void drmp3d_synth(float *xl, short *dstl, int nch, float *lins) DRMP3_V0(0) DRMP3_V2(1) DRMP3_V1(2) DRMP3_V2(3) DRMP3_V1(4) DRMP3_V2(5) DRMP3_V1(6) DRMP3_V2(7) { +#ifndef DR_MP3_FLOAT_OUTPUT #if DRMP3_HAVE_SSE static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); - dstr[(15 - i)*nch] = (short)_mm_extract_epi16(pcm8, 1); - dstr[(17 + i)*nch] = (short)_mm_extract_epi16(pcm8, 5); - dstl[(15 - i)*nch] = (short)_mm_extract_epi16(pcm8, 0); - dstl[(17 + i)*nch] = (short)_mm_extract_epi16(pcm8, 4); - dstr[(47 - i)*nch] = (short)_mm_extract_epi16(pcm8, 3); - dstr[(49 + i)*nch] = (short)_mm_extract_epi16(pcm8, 7); - dstl[(47 - i)*nch] = (short)_mm_extract_epi16(pcm8, 2); - dstl[(49 + i)*nch] = (short)_mm_extract_epi16(pcm8, 6); + dstr[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + dstr[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + dstl[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + dstl[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + dstr[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + dstr[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); + dstl[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + dstl[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); #else int16x4_t pcma, pcmb; a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); @@ -1784,6 +1784,30 @@ static void drmp3d_synth(float *xl, short *dstl, int nch, float *lins) vst1_lane_s16(dstl + (47 - i)*nch, pcma, 2); vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); #endif +#else + static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + a = DRMP3_VMUL(a, g_scale); + b = DRMP3_VMUL(b, g_scale); +#if DRMP3_HAVE_SSE + _mm_store_ss(dstr + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstr + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstl + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstl + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstr + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstr + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstl + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2))); + _mm_store_ss(dstl + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2))); +#else + vst1q_lane_f32(dstr + (15 - i)*nch, a, 1); + vst1q_lane_f32(dstr + (17 + i)*nch, b, 1); + vst1q_lane_f32(dstl + (15 - i)*nch, a, 0); + vst1q_lane_f32(dstl + (17 + i)*nch, b, 0); + vst1q_lane_f32(dstr + (47 - i)*nch, a, 3); + vst1q_lane_f32(dstr + (49 + i)*nch, b, 3); + vst1q_lane_f32(dstl + (47 - i)*nch, a, 2); + vst1q_lane_f32(dstl + (49 + i)*nch, b, 2); +#endif +#endif /* DR_MP3_FLOAT_OUTPUT */ } } else #endif @@ -1821,7 +1845,7 @@ static void drmp3d_synth(float *xl, short *dstl, int nch, float *lins) #endif } -static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, short *pcm, float *lins) +static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, drmp3d_sample_t *pcm, float *lins) { int i; for (i = 0; i < nch; i++) @@ -1906,7 +1930,7 @@ void drmp3dec_init(drmp3dec *dec) dec->header[0] = 0; } -int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, short *pcm, drmp3dec_frame_info *info) +int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) { int i = 0, igr, frame_size = 0, success = 1; const drmp3_uint8 *hdr; @@ -1940,6 +1964,11 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); + if (!pcm) + { + return drmp3_hdr_frame_samples(hdr); + } + drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE); if (DRMP3_HDR_IS_CRC(hdr)) { @@ -1957,11 +1986,11 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); if (success) { - for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm += 576*info->channels) + for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) { memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); - drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, pcm, scratch.syn[0]); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); } } drmp3_L3_save_reservoir(dec, &scratch); @@ -1980,9 +2009,9 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes { i = 0; drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); - drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, pcm, scratch.syn[0]); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); - pcm += 384*info->channels; + pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); } if (bs_frame->pos > bs_frame->limit) { @@ -1995,6 +2024,64 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes return success*drmp3_hdr_frame_samples(dec->header); } +void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) +{ + if(num_samples > 0) + { + int i = 0; +#if DRMP3_HAVE_SIMD + int aligned_count = num_samples & ~7; + for(; i < aligned_count; i+=8) + { + static const drmp3_f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f }; + drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), g_scale); + drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), g_scale); +#if DRMP3_HAVE_SSE + static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); + out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(out+i , pcma, 0); + vst1_lane_s16(out+i+1, pcma, 1); + vst1_lane_s16(out+i+2, pcma, 2); + vst1_lane_s16(out+i+3, pcma, 3); + vst1_lane_s16(out+i+4, pcmb, 0); + vst1_lane_s16(out+i+5, pcmb, 1); + vst1_lane_s16(out+i+6, pcmb, 2); + vst1_lane_s16(out+i+7, pcmb, 3); +#endif + } +#endif + for(; i < num_samples; i++) + { + float sample = in[i] * 32768.0f; + if (sample >= 32766.5) + out[i] = (drmp3_int16) 32767; + else if (sample <= -32767.5) + out[i] = (drmp3_int16)-32768; + else + { + short s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + out[i] = s; + } + } + } +} @@ -2004,6 +2091,16 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes // /////////////////////////////////////////////////////////////////////////////// +#if defined(SIZE_MAX) + #define DRMP3_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRMP3_SIZE_MAX ((drmp3_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRMP3_SIZE_MAX 0xFFFFFFFF + #endif +#endif + // Options. #ifndef DR_MP3_DEFAULT_CHANNELS #define DR_MP3_DEFAULT_CHANNELS 2 @@ -2285,34 +2382,47 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou } +static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) +{ + return pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); +} -static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3) +static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) +{ + drmp3_assert(offset >= 0); + return pMP3->onSeek(pMP3->pUserData, offset, origin); +} + + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) { drmp3_assert(pMP3 != NULL); drmp3_assert(pMP3->onRead != NULL); if (pMP3->atEnd) { - return DRMP3_FALSE; + return 0; } - do - { + drmp3_uint32 pcmFramesRead = 0; + do { // minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) { if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE; drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); if (pNewData == NULL) { - return DRMP3_FALSE; // Out of memory. + return 0; // Out of memory. } pMP3->pData = pNewData; } - size_t bytesRead = pMP3->onRead(pMP3->pUserData, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { - pMP3->atEnd = DRMP3_TRUE; - return DRMP3_FALSE; // No data. + if (pMP3->dataSize == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // No data. + } } pMP3->dataSize += bytesRead; @@ -2320,23 +2430,23 @@ static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3) if (pMP3->dataSize > INT_MAX) { pMP3->atEnd = DRMP3_TRUE; - return DRMP3_FALSE; // File too big. + return 0; // File too big. } drmp3dec_frame_info info; - drmp3_uint32 samplesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pMP3->frames, &info); // <-- Safe size_t -> int conversion thanks to the check above. - if (samplesRead != 0) { + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); // <-- Safe size_t -> int conversion thanks to the check above. + if (pcmFramesRead != 0) { size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); for (size_t i = 0; i < leftoverDataSize; ++i) { pMP3->pData[i] = pMP3->pData[i + (size_t)info.frame_bytes]; } pMP3->dataSize = leftoverDataSize; - pMP3->framesConsumed = 0; - pMP3->framesRemaining = samplesRead; - pMP3->frameChannels = info.channels; - pMP3->frameSampleRate = info.hz; - drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->frameSampleRate); + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); break; } else { // Need more data. minimp3 recommends doing data submission in 16K chunks. @@ -2345,24 +2455,47 @@ static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3) pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE; drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); if (pNewData == NULL) { - return DRMP3_FALSE; // Out of memory. + return 0; // Out of memory. } pMP3->pData = pNewData; } // Fill in a chunk. - size_t bytesRead = pMP3->onRead(pMP3->pUserData, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { pMP3->atEnd = DRMP3_TRUE; - return DRMP3_FALSE; // Error reading more data. + return 0; // Error reading more data. } pMP3->dataSize += bytesRead; } } while (DRMP3_TRUE); - return DRMP3_TRUE; + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); +} + +static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + + drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFrameCount == 0) { + return 0; + } + + // We have essentially just skipped past the frame, so just set the remaining samples to 0. + pMP3->currentPCMFrame += pcmFrameCount; + pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; + pMP3->pcmFramesRemainingInMP3Frame = 0; + + return pcmFrameCount; } static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData) @@ -2372,38 +2505,64 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi drmp3_assert(pMP3->onRead != NULL); float* pFramesOutF = (float*)pFramesOut; - drmp3_uint32 totalFramesRead = 0; + drmp3_uint64 totalFramesRead = 0; while (frameCount > 0) { // Read from the in-memory buffer first. - while (pMP3->framesRemaining > 0 && frameCount > 0) { - if (pMP3->frameChannels == 1) { + while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) { + drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames; +#ifndef DR_MP3_FLOAT_OUTPUT + if (pMP3->mp3FrameChannels == 1) { if (pMP3->channels == 1) { // Mono -> Mono. - pFramesOutF[0] = pMP3->frames[pMP3->framesConsumed] / 32768.0f; + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; } else { // Mono -> Stereo. - pFramesOutF[0] = pMP3->frames[pMP3->framesConsumed] / 32768.0f; - pFramesOutF[1] = pMP3->frames[pMP3->framesConsumed] / 32768.0f; + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; } } else { if (pMP3->channels == 1) { // Stereo -> Mono float sample = 0; - sample += pMP3->frames[(pMP3->framesConsumed*pMP3->frameChannels)+0] / 32768.0f; - sample += pMP3->frames[(pMP3->framesConsumed*pMP3->frameChannels)+1] / 32768.0f; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; pFramesOutF[0] = sample * 0.5f; } else { // Stereo -> Stereo - pFramesOutF[0] = pMP3->frames[(pMP3->framesConsumed*pMP3->frameChannels)+0] / 32768.0f; - pFramesOutF[1] = pMP3->frames[(pMP3->framesConsumed*pMP3->frameChannels)+1] / 32768.0f; + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; } } +#else + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + // Mono -> Mono. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } else { + // Mono -> Stereo. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } + } else { + if (pMP3->channels == 1) { + // Stereo -> Mono + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + pFramesOutF[0] = sample * 0.5f; + } else { + // Stereo -> Stereo + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + } + } +#endif - pMP3->framesConsumed += 1; - pMP3->framesRemaining -= 1; - frameCount -= 1; + pMP3->pcmFramesConsumedInMP3Frame += 1; + pMP3->pcmFramesRemainingInMP3Frame -= 1; totalFramesRead += 1; + frameCount -= 1; pFramesOutF += pSRC->config.channels; } @@ -2411,11 +2570,11 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi break; } - drmp3_assert(pMP3->framesRemaining == 0); + drmp3_assert(pMP3->pcmFramesRemainingInMP3Frame == 0); // At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed // at this point which means we'll also need to update our sample rate conversion pipeline. - if (!drmp3_decode_next_frame(pMP3)) { + if (drmp3_decode_next_frame(pMP3) == 0) { break; } } @@ -2466,11 +2625,13 @@ drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek srcConfig.channels = pMP3->channels; srcConfig.algorithm = drmp3_src_algorithm_linear; if (!drmp3_src_init(&srcConfig, drmp3_read_src, pMP3, &pMP3->src)) { + drmp3_uninit(pMP3); return DRMP3_FALSE; } // Decode the first frame to confirm that it is indeed a valid MP3 stream. if (!drmp3_decode_next_frame(pMP3)) { + drmp3_uninit(pMP3); return DRMP3_FALSE; // Not a valid MP3 stream. } @@ -2589,7 +2750,9 @@ drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_conf void drmp3_uninit(drmp3* pMP3) { - if (pMP3 == NULL) return; + if (pMP3 == NULL) { + return; + } #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { @@ -2600,9 +2763,11 @@ void drmp3_uninit(drmp3* pMP3) drmp3_free(pMP3->pData); } -drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) { - if (pMP3 == NULL || pMP3->onRead == NULL) return 0; + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } drmp3_uint64 totalFramesRead = 0; @@ -2614,7 +2779,7 @@ drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBuff framesToReadRightNow = framesToRead; } - drmp3_uint64 framesJustRead = drmp3_read_f32(pMP3, framesToReadRightNow, temp); + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; } @@ -2624,40 +2789,191 @@ drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBuff } } else { totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE); + pMP3->currentPCMFrame += totalFramesRead; } return totalFramesRead; } -drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) { - if (pMP3 == NULL || pMP3->onSeek == NULL) return DRMP3_FALSE; + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onSeek != NULL); // Seek to the start of the stream to begin with. - if (!pMP3->onSeek(pMP3->pUserData, 0, drmp3_seek_origin_start)) { + if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { return DRMP3_FALSE; } // Clear any cached data. - pMP3->framesConsumed = 0; - pMP3->framesRemaining = 0; + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = 0; + pMP3->currentPCMFrame = 0; pMP3->dataSize = 0; pMP3->atEnd = DRMP3_FALSE; - // TODO: Optimize. - // - // This is inefficient. We simply read frames from the start of the stream. - drmp3_uint64 framesRead = drmp3_read_f32(pMP3, frameIndex, NULL); - if (framesRead != frameIndex) { + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + drmp3_assert(pMP3 != NULL); + + if (frameIndex == pMP3->currentPCMFrame) { + return DRMP3_TRUE; + } + + // If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of + // the stream and read from the beginning. + drmp3_uint64 framesToReadAndDiscard; + if (frameIndex >= pMP3->currentPCMFrame) { + // Moving foward. + framesToReadAndDiscard = frameIndex - pMP3->currentPCMFrame; + } else { + // Moving backward. Move to the start of the stream and then move forward. + framesToReadAndDiscard = frameIndex; + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + } + + // MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly + // depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that + // contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To + // resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder. + drmp3_uint64 maxFramesToReadAndDiscard = DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3; + + // First get rid of anything that's still sitting in the buffer. + if (framesToReadAndDiscard > maxFramesToReadAndDiscard && framesToReadAndDiscard > pMP3->pcmFramesRemainingInMP3Frame) { + framesToReadAndDiscard -= pMP3->pcmFramesRemainingInMP3Frame; + pMP3->currentPCMFrame += pMP3->pcmFramesRemainingInMP3Frame; + pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; + pMP3->pcmFramesRemainingInMP3Frame = 0; + } + + // Now get rid of leading whole frames. + while (framesToReadAndDiscard > maxFramesToReadAndDiscard) { + drmp3_uint32 pcmFramesSeeked = drmp3_seek_next_frame(pMP3); + if (pcmFramesSeeked == 0) { + break; + } + + framesToReadAndDiscard -= pcmFramesSeeked; + } + + // The last step is to read-and-discard any remaining PCM frames to make it sample-exact. + drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadAndDiscard, NULL); + if (framesRead != framesToReadAndDiscard) { return DRMP3_FALSE; } return DRMP3_TRUE; } +drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + if (pMP3 == NULL || pMP3->onSeek == NULL) { + return DRMP3_FALSE; + } + + // We currently only support brute force seeking. + return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); +} + +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return 0; + } + + // The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based + // on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. + + // The stream must support seeking for this to work. + if (pMP3->onSeek == NULL) { + return 0; + } + + // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. + drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + drmp3_uint64 totalPCMFrameCount = 0; + float totalPCMFrameCountFractionalPart = 0; // <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. + for (;;) { + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); // <-- Passing in NULL here will prevent decoding of the MP3 frame which should save time. + if (pcmFramesInCurrentMP3FrameIn == 0) { + break; + } + + float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + drmp3_assert(srcRatio > 0); + + float pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); + drmp3_uint32 pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; + totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; + totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; + } + + // Finally, we need to seek back to where we were. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return 0; + } + + return totalPCMFrameCount; +} + +drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return 0; + } + + // This works the same way as drmp3_get_pcm_frame_count() - move to the start, count MP3 frames, move back to the previous position. + + // The stream must support seeking for this to work. + if (pMP3->onSeek == NULL) { + return 0; + } + + // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. + drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + drmp3_uint64 totalMP3FrameCount = 0; + for (;;) { + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3FrameIn == 0) { + break; + } + + totalMP3FrameCount += 1; + } + + // Finally, we need to seek back to where we were. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return 0; + } + + return totalMP3FrameCount; +} -float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_assert(pMP3 != NULL); @@ -2668,7 +2984,7 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp float temp[4096]; for (;;) { drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; - drmp3_uint64 framesJustRead = drmp3_read_f32(pMP3, framesToReadRightNow, temp); + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; } @@ -2681,7 +2997,7 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp } drmp3_uint64 newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(float); - if (newFramesBufferSize > SIZE_MAX) { + if (newFramesBufferSize > DRMP3_SIZE_MAX) { break; } @@ -2714,35 +3030,35 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp return pFrames; } -float* drmp3_open_and_decode_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3 mp3; if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) { return NULL; } - return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount); + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -float* drmp3_open_and_decode_memory_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3 mp3; if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) { return NULL; } - return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount); + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } #ifndef DR_MP3_NO_STDIO -float* drmp3_open_and_decode_file_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3 mp3; if (!drmp3_init_file(&mp3, filePath, pConfig)) { return NULL; } - return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount); + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } #endif @@ -2769,7 +3085,49 @@ void drmp3_free(void* p) // REVISION HISTORY -// =============== +// ================ +// +// v0.4.0 - 2018-xx-xx +// - API CHANGE: Rename some APIs: +// - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32 +// - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame +// - drmp3_open_and_decode_f32 -> drmp3_open_and_read_f32 +// - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_f32 +// - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_f32 +// - Add drmp3_get_pcm_frame_count(). +// - Add drmp3_get_mp3_frame_count(). +// - Improve seeking performance. +// +// v0.3.2 - 2018-09-11 +// - Fix a couple of memory leaks. +// - Bring up to date with minimp3. +// +// v0.3.1 - 2018-08-25 +// - Fix C++ build. +// +// v0.3.0 - 2018-08-25 +// - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has +// been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or +// not the DR_MP3_FLOAT_OUTPUT option is set. +// +// v0.2.11 - 2018-08-08 +// - Fix a bug where the last part of a file is not read. +// +// v0.2.10 - 2018-08-07 +// - Improve 64-bit detection. +// +// v0.2.9 - 2018-08-05 +// - Fix C++ build on older versions of GCC. +// - Bring up to date with minimp3. +// +// v0.2.8 - 2018-08-02 +// - Fix compilation errors with older versions of GCC. +// +// v0.2.7 - 2018-07-13 +// - Bring up to date with minimp3. +// +// v0.2.6 - 2018-07-12 +// - Bring up to date with minimp3. // // v0.2.5 - 2018-06-22 // - Bring up to date with minimp3. diff --git a/raylib/external/glfw/include/GLFW/glfw3.h b/raylib/external/glfw/include/GLFW/glfw3.h index 00256d4..990fe3f 100644 --- a/raylib/external/glfw/include/GLFW/glfw3.h +++ b/raylib/external/glfw/include/GLFW/glfw3.h @@ -819,6 +819,12 @@ extern "C" { * Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib). */ #define GLFW_HOVERED 0x0002000B +/*! @brief Input focus on calling show window hint and attribute + * + * Input focus [window hint](@ref GLFW_FOCUS_ON_SHOW_hint) or + * [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib). + */ +#define GLFW_FOCUS_ON_SHOW 0x0002000C /*! @brief Framebuffer bit depth hint. * @@ -3085,6 +3091,11 @@ GLFWAPI void glfwMaximizeWindow(GLFWwindow* window); * hidden. If the window is already visible or is in full screen mode, this * function does nothing. * + * By default, windowed mode windows are focused when shown + * Set the [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_hint) window hint + * to change this behavior for all newly created windows, or change the + * behavior for an existing window with @ref glfwSetWindowAttrib. + * * @param[in] window The window to make visible. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref @@ -3132,6 +3143,10 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window); * initially created. Set the [GLFW_FOCUSED](@ref GLFW_FOCUSED_hint) to * disable this behavior. * + * Also by default, windowed mode windows are focused when shown + * with @ref glfwShowWindow. Set the + * [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_hint) to disable this behavior. + * * __Do not use this function__ to steal focus from other applications unless * you are certain that is what the user wants. Focus stealing can be * extremely disruptive. @@ -3306,8 +3321,9 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib); * * The supported attributes are [GLFW_DECORATED](@ref GLFW_DECORATED_attrib), * [GLFW_RESIZABLE](@ref GLFW_RESIZABLE_attrib), - * [GLFW_FLOATING](@ref GLFW_FLOATING_attrib) and - * [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib). + * [GLFW_FLOATING](@ref GLFW_FLOATING_attrib), + * [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and + * [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib). * * Some of these attributes are ignored for full screen windows. The new * value will take effect if the window is later made windowed. diff --git a/raylib/external/glfw/include/GLFW/glfw3native.h b/raylib/external/glfw/include/GLFW/glfw3native.h index 84bb339..d585496 100644 --- a/raylib/external/glfw/include/GLFW/glfw3native.h +++ b/raylib/external/glfw/include/GLFW/glfw3native.h @@ -101,7 +101,10 @@ extern "C" { #if defined(__OBJC__) #import #else - typedef void* id; + // RAY: Added protection in case OBJC types defined + #if !OBJC_TYPES_DEFINED + typedef void* id; + #endif #endif #elif defined(GLFW_EXPOSE_NATIVE_X11) #include diff --git a/raylib/external/glfw/src/input.c b/raylib/external/glfw/src/input.c index 4297545..b0bb3de 100644 --- a/raylib/external/glfw/src/input.c +++ b/raylib/external/glfw/src/input.c @@ -32,7 +32,6 @@ #include #include #include -#include // Internal key state used for sticky keys #define _GLFW_STICK 3 @@ -1086,7 +1085,9 @@ GLFWAPI int glfwUpdateGamepadMappings(const char* string) while (*c) { - if (isxdigit(*c)) + if ((*c >= '0' && *c <= '9') || + (*c >= 'a' && *c <= 'f') || + (*c >= 'A' && *c <= 'F')) { char line[1024]; diff --git a/raylib/external/glfw/src/internal.h b/raylib/external/glfw/src/internal.h index 92bbfcc..9fc626d 100644 --- a/raylib/external/glfw/src/internal.h +++ b/raylib/external/glfw/src/internal.h @@ -267,6 +267,7 @@ struct _GLFWwndconfig GLFWbool floating; GLFWbool maximized; GLFWbool centerCursor; + GLFWbool focusOnShow; struct { GLFWbool retina; char frameName[256]; @@ -372,6 +373,7 @@ struct _GLFWwindow GLFWbool decorated; GLFWbool autoIconify; GLFWbool floating; + GLFWbool focusOnShow; GLFWbool shouldClose; void* userPointer; GLFWvidmode videoMode; diff --git a/raylib/external/glfw/src/win32_platform.h b/raylib/external/glfw/src/win32_platform.h index 9a66921..5981514 100644 --- a/raylib/external/glfw/src/win32_platform.h +++ b/raylib/external/glfw/src/win32_platform.h @@ -242,7 +242,9 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)( #include "egl_context.h" #include "osmesa_context.h" -#define _GLFW_WNDCLASSNAME L"GLFW30" +#if !defined(_GLFW_WNDCLASSNAME) + #define _GLFW_WNDCLASSNAME L"GLFW30" +#endif #define _glfw_dlopen(name) LoadLibraryA(name) #define _glfw_dlclose(handle) FreeLibrary((HMODULE) handle) diff --git a/raylib/external/glfw/src/window.c b/raylib/external/glfw/src/window.c index 38a8982..4ed415c 100644 --- a/raylib/external/glfw/src/window.c +++ b/raylib/external/glfw/src/window.c @@ -201,6 +201,7 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, window->decorated = wndconfig.decorated; window->autoIconify = wndconfig.autoIconify; window->floating = wndconfig.floating; + window->focusOnShow = wndconfig.focusOnShow; window->cursorMode = GLFW_CURSOR_NORMAL; window->minwidth = GLFW_DONT_CARE; @@ -267,6 +268,7 @@ void glfwDefaultWindowHints(void) _glfw.hints.window.focused = GLFW_TRUE; _glfw.hints.window.autoIconify = GLFW_TRUE; _glfw.hints.window.centerCursor = GLFW_TRUE; + _glfw.hints.window.focusOnShow = GLFW_TRUE; // The default is 24 bits of color, 24 bits of depth and 8 bits of stencil, // double buffered @@ -370,6 +372,9 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_CENTER_CURSOR: _glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE; return; + case GLFW_FOCUS_ON_SHOW: + _glfw.hints.window.focusOnShow = value ? GLFW_TRUE : GLFW_FALSE; + return; case GLFW_CLIENT_API: _glfw.hints.context.client = value; return; @@ -755,7 +760,9 @@ GLFWAPI void glfwShowWindow(GLFWwindow* handle) return; _glfwPlatformShowWindow(window); - _glfwPlatformFocusWindow(window); + + if (window->focusOnShow) + _glfwPlatformFocusWindow(window); } GLFWAPI void glfwRequestWindowAttention(GLFWwindow* handle) @@ -810,6 +817,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) return _glfwPlatformWindowMaximized(window); case GLFW_HOVERED: return _glfwPlatformWindowHovered(window); + case GLFW_FOCUS_ON_SHOW: + return window->focusOnShow; case GLFW_TRANSPARENT_FRAMEBUFFER: return _glfwPlatformFramebufferTransparent(window); case GLFW_RESIZABLE: @@ -886,6 +895,8 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value) if (!window->monitor) _glfwPlatformSetWindowFloating(window, value); } + else if (attrib == GLFW_FOCUS_ON_SHOW) + window->focusOnShow = value; else _glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib); } diff --git a/raylib/external/glfw/src/x11_window.c b/raylib/external/glfw/src/x11_window.c index fc24f28..1c4e9c3 100644 --- a/raylib/external/glfw/src/x11_window.c +++ b/raylib/external/glfw/src/x11_window.c @@ -2672,8 +2672,9 @@ void _glfwPlatformPollEvents(void) #if defined(__linux__) _glfwDetectJoystickConnectionLinux(); #endif - int count = XPending(_glfw.x11.display); - while (count--) + XPending(_glfw.x11.display); + + while (XQLength(_glfw.x11.display)) { XEvent event; XNextEvent(_glfw.x11.display, &event); diff --git a/raylib/external/mini_al.h b/raylib/external/mini_al.h index 759662c..0372f3b 100644 --- a/raylib/external/mini_al.h +++ b/raylib/external/mini_al.h @@ -1,5 +1,5 @@ // Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file. -// mini_al - v0.8.8 - 2018-09-14 +// mini_al - v0.8.10 - 2018-10-21 // // David Reid - davidreidsoftware@gmail.com @@ -1114,6 +1114,11 @@ void mal_pcm_f32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dithe void mal_pcm_f32_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, mal_uint64 sampleCount, mal_dither_mode ditherMode); +// Deinterleaves an interleaved buffer. +void mal_deinterleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void* pInterleavedPCMFrames, void** ppDeinterleavedPCMFrames); + +// Interleaves a group of deinterleaved buffers. +void mal_interleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1629,6 +1634,7 @@ struct mal_context mal_proc pa_stream_get_sample_spec; mal_proc pa_stream_get_channel_map; mal_proc pa_stream_get_buffer_attr; + mal_proc pa_stream_set_buffer_attr; mal_proc pa_stream_get_device_name; mal_proc pa_stream_set_write_callback; mal_proc pa_stream_set_read_callback; @@ -4864,7 +4870,7 @@ mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_uint32* mal_assert(pCurrentPos != NULL); *pCurrentPos = 0; - mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate) / pDevice->channels; + mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate); *pCurrentPos = (mal_uint32)(currentFrameAbs % pDevice->bufferSizeInFrames); return MAL_TRUE; @@ -4917,11 +4923,11 @@ mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) while (!pDevice->null_device.breakFromMainLoop) { mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice); - if (framesAvailable > 0) { + if (framesAvailable >= (pDevice->bufferSizeInFrames/pDevice->periods)) { return framesAvailable; } - mal_sleep(16); + mal_sleep(pDevice->bufferSizeInMilliseconds/pDevice->periods); } // We'll get here if the loop was terminated. Just return whatever's available. @@ -11784,6 +11790,7 @@ typedef mal_pa_stream_state_t (* mal_pa_stream_get_state_proc) typedef const mal_pa_sample_spec* (* mal_pa_stream_get_sample_spec_proc) (mal_pa_stream* s); typedef const mal_pa_channel_map* (* mal_pa_stream_get_channel_map_proc) (mal_pa_stream* s); typedef const mal_pa_buffer_attr* (* mal_pa_stream_get_buffer_attr_proc) (mal_pa_stream* s); +typedef mal_pa_operation* (* mal_pa_stream_set_buffer_attr_proc) (mal_pa_stream* s, const mal_pa_buffer_attr* attr, mal_pa_stream_success_cb_t cb, void* userdata); typedef const char* (* mal_pa_stream_get_device_name_proc) (mal_pa_stream* s); typedef void (* mal_pa_stream_set_write_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); typedef void (* mal_pa_stream_set_read_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); @@ -12305,6 +12312,10 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); +#ifdef MAL_DEBUG_OUTPUT + printf("[PulseAudio] write_callback: sizeInBytes=%d\n", (int)sizeInBytes); +#endif + size_t bytesRemaining = sizeInBytes; while (bytesRemaining > 0) { size_t bytesToReadFromClient = bytesRemaining; @@ -12319,19 +12330,35 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, return; } +#ifdef MAL_DEBUG_OUTPUT + printf(" bytesToReadFromClient=%d", (int)bytesToReadFromClient); +#endif + if (pBuffer != NULL && bytesToReadFromClient > 0) { mal_uint32 framesToReadFromClient = (mal_uint32)bytesToReadFromClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); if (framesToReadFromClient > 0) { mal_device__read_frames_from_client(pDevice, framesToReadFromClient, pBuffer); +#ifdef MAL_DEBUG_OUTPUT + printf(", framesToReadFromClient=%d\n", (int)framesToReadFromClient); +#endif + error = ((mal_pa_stream_write_proc)pContext->pulse.pa_stream_write)((mal_pa_stream*)pDevice->pulse.pStream, pBuffer, bytesToReadFromClient, NULL, 0, MAL_PA_SEEK_RELATIVE); if (error < 0) { mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error)); return; } + } else { +#ifdef MAL_DEBUG_OUTPUT + printf(", framesToReadFromClient=0\n"); +#endif } bytesRemaining -= bytesToReadFromClient; + } else { +#ifdef MAL_DEBUG_OUTPUT + printf(", framesToReadFromClient=0\n"); +#endif } } } @@ -12460,6 +12487,7 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c mal_pa_sample_spec ss; mal_pa_channel_map cmap; mal_pa_buffer_attr attr; + mal_pa_stream_flags_t streamFlags; const mal_pa_sample_spec* pActualSS = NULL; const mal_pa_channel_map* pActualCMap = NULL; @@ -12533,53 +12561,6 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } - -#if 0 - mal_pa_sample_spec deviceSS; - mal_pa_channel_map deviceCMap; - if (type == mal_device_type_playback) { - deviceSS = sinkInfo.sample_spec; - deviceCMap = sinkInfo.channel_map; - } else { - deviceSS = sourceInfo.sample_spec; - deviceCMap = sourceInfo.channel_map; - } - - if (pDevice->usingDefaultFormat) { - ss.format = deviceSS.format; - } else { - ss.format = mal_format_to_pulse(pConfig->format); - } - if (ss.format == MAL_PA_SAMPLE_INVALID) { - ss.format = MAL_PA_SAMPLE_S16LE; - } - - if (pDevice->usingDefaultChannels) { - ss.channels = deviceSS.channels; - } else { - ss.channels = pConfig->channels; - } - - if (pDevice->usingDefaultSampleRate) { - ss.rate = deviceSS.rate; - } else { - ss.rate = pConfig->sampleRate; - } - - - if (pDevice->usingDefaultChannelMap) { - cmap = deviceCMap; - } else { - cmap.channels = pConfig->channels; - for (mal_uint32 iChannel = 0; iChannel < pConfig->channels; ++iChannel) { - cmap.map[iChannel] = mal_channel_position_to_pulse(pConfig->channelMap[iChannel]); - } - - if (((mal_pa_channel_map_valid_proc)pContext->pulse.pa_channel_map_valid)(&cmap) == 0 || ((mal_pa_channel_map_compatible_proc)pContext->pulse.pa_channel_map_compatible)(&cmap, &ss) == 0) { - ((mal_pa_channel_map_init_extend_proc)pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MAL_PA_CHANNEL_MAP_DEFAULT); // The channel map is invalid, so just fall back to the default. - } - } -#else if (type == mal_device_type_playback) { ss = sinkInfo.sample_spec; cmap = sinkInfo.channel_map; @@ -12587,7 +12568,7 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c ss = sourceInfo.sample_spec; cmap = sourceInfo.channel_map; } -#endif + // Buffer size. bufferSizeInFrames = pDevice->bufferSizeInFrames; @@ -12606,10 +12587,14 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c } attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels; - attr.tlength = attr.maxlength / pConfig->periods; + attr.tlength = attr.maxlength; attr.prebuf = (mal_uint32)-1; - attr.minreq = attr.tlength; - attr.fragsize = attr.tlength; + attr.minreq = attr.maxlength / pConfig->periods; + attr.fragsize = attr.maxlength / pConfig->periods; + +#ifdef MAL_DEBUG_OUTPUT + printf("[PulseAudio] attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; bufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, bufferSizeInFrames); +#endif char streamName[256]; if (pConfig->pulse.pStreamName != NULL) { @@ -12628,11 +12613,15 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c } - + streamFlags = MAL_PA_STREAM_START_CORKED; + if (dev != NULL) { + streamFlags |= MAL_PA_STREAM_DONT_MOVE | MAL_PA_STREAM_FIX_FORMAT | MAL_PA_STREAM_FIX_RATE | MAL_PA_STREAM_FIX_CHANNELS; + } + if (type == mal_device_type_playback) { - error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED, NULL, NULL); + error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, streamFlags, NULL, NULL); } else { - error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED); + error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, streamFlags); } if (error != MAL_PA_OK) { @@ -12652,6 +12641,21 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c // Internal format. pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStream); if (pActualSS != NULL) { + // If anything has changed between the requested and the actual sample spec, we need to update the buffer. + if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { + attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(pActualSS->format))*pActualSS->channels; + attr.tlength = attr.maxlength; + attr.prebuf = (mal_uint32)-1; + attr.minreq = attr.maxlength / pConfig->periods; + attr.fragsize = attr.maxlength / pConfig->periods; + + pOP = ((mal_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream, &attr, NULL, NULL); + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } + } + ss = *pActualSS; } @@ -12680,6 +12684,10 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->internalFormat)*pDevice->internalChannels); pDevice->periods = attr.maxlength / attr.tlength; +#ifdef MAL_DEBUG_OUTPUT + printf("[PulseAudio] actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; pDevice->bufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->bufferSizeInFrames); +#endif + // Grab the name of the device if we can. dev = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStream); @@ -12930,6 +12938,7 @@ mal_result mal_context_init__pulse(mal_context* pContext) pContext->pulse.pa_stream_get_sample_spec = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_sample_spec"); pContext->pulse.pa_stream_get_channel_map = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_channel_map"); pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_buffer_attr"); + pContext->pulse.pa_stream_set_buffer_attr = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_buffer_attr"); pContext->pulse.pa_stream_get_device_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_device_name"); pContext->pulse.pa_stream_set_write_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_write_callback"); pContext->pulse.pa_stream_set_read_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_read_callback"); @@ -12972,6 +12981,7 @@ mal_result mal_context_init__pulse(mal_context* pContext) mal_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; mal_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; mal_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; + mal_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr; mal_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; mal_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; mal_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; @@ -13013,6 +13023,7 @@ mal_result mal_context_init__pulse(mal_context* pContext) pContext->pulse.pa_stream_get_sample_spec = (mal_proc)_pa_stream_get_sample_spec; pContext->pulse.pa_stream_get_channel_map = (mal_proc)_pa_stream_get_channel_map; pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)_pa_stream_get_buffer_attr; + pContext->pulse.pa_stream_set_buffer_attr = (mal_proc)_pa_stream_set_buffer_attr; pContext->pulse.pa_stream_get_device_name = (mal_proc)_pa_stream_get_device_name; pContext->pulse.pa_stream_set_write_callback = (mal_proc)_pa_stream_set_write_callback; pContext->pulse.pa_stream_set_read_callback = (mal_proc)_pa_stream_set_read_callback; @@ -14900,27 +14911,62 @@ OSStatus mal_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* p #if defined(MAL_DEBUG_OUTPUT) printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); #endif - - // For now we can assume everything is interleaved. - for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { - mal_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); - if (frameCountForThisBuffer > 0) { - mal_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); - } - - #if defined(MAL_DEBUG_OUTPUT) - printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); - #endif - } else { - // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. We just - // output silence here. - mal_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - #if defined(MAL_DEBUG_OUTPUT) - printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); - #endif + // We need to check whether or not we are outputting interleaved or non-interleaved samples. The + // way we do this is slightly different for each type. + mal_stream_layout layout = mal_stream_layout_interleaved; + if (pBufferList->mBuffers[0].mNumberChannels != pDevice->internalChannels) { + layout = mal_stream_layout_deinterleaved; + } + + if (layout == mal_stream_layout_interleaved) { + // For now we can assume everything is interleaved. + for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { + mal_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (frameCountForThisBuffer > 0) { + mal_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); + } + + #if defined(MAL_DEBUG_OUTPUT) + printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. We just + // output silence here. + mal_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); + + #if defined(MAL_DEBUG_OUTPUT) + printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } + } + } else { + // This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This + // assumes each buffer is the same size. + mal_uint8 tempBuffer[4096]; + for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; iBuffer += pDevice->internalChannels) { + mal_uint32 frameCountPerBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + + mal_uint32 framesRemaining = frameCountPerBuffer; + while (framesRemaining > 0) { + mal_uint32 framesToRead = sizeof(tempBuffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (framesToRead > framesRemaining) { + framesToRead = framesRemaining; + } + + mal_device__read_frames_from_client(pDevice, framesToRead, tempBuffer); + + void* ppDeinterleavedBuffers[MAL_MAX_CHANNELS]; + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)mal_offset_ptr(pBufferList->mBuffers[iBuffer].mData, (frameCountPerBuffer - framesRemaining) * mal_get_bytes_per_sample(pDevice->internalFormat)); + } + + mal_deinterleave_pcm_frames(pDevice->internalFormat, pDevice->internalChannels, framesToRead, tempBuffer, ppDeinterleavedBuffers); + + framesRemaining -= framesToRead; + } } } @@ -14941,6 +14987,13 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA AudioBufferList* pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; mal_assert(pRenderedBufferList); + // We need to check whether or not we are outputting interleaved or non-interleaved samples. The + // way we do this is slightly different for each type. + mal_stream_layout layout = mal_stream_layout_interleaved; + if (pRenderedBufferList->mBuffers[0].mNumberChannels != pDevice->internalChannels) { + layout = mal_stream_layout_deinterleaved; + } + #if defined(MAL_DEBUG_OUTPUT) printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); #endif @@ -14953,16 +15006,58 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA return status; } - // For now we can assume everything is interleaved. - for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { - if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { - mal_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); - #if defined(MAL_DEBUG_OUTPUT) - printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); - #endif - } else { - // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. + if (layout == mal_stream_layout_interleaved) { + for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { + mal_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); + #if defined(MAL_DEBUG_OUTPUT) + printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. + + mal_uint8 silentBuffer[4096]; + mal_zero_memory(silentBuffer, sizeof(silentBuffer)); + + mal_uint32 framesRemaining = frameCount; + while (framesRemaining > 0) { + mal_uint32 framesToSend = sizeof(silentBuffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } + + mal_device__send_frames_to_client(pDevice, framesToSend, silentBuffer); + framesRemaining -= framesToSend; + } + + #if defined(MAL_DEBUG_OUTPUT) + printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pRenderBufferList->mBuffers[iBuffer].mNumberChannels, pRenderBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } + } + } else { + // This is the deinterleaved case. We need to interleave the audio data before sending it to the client. This + // assumes each buffer is the same size. + mal_uint8 tempBuffer[4096]; + for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; iBuffer += pDevice->internalChannels) { + mal_uint32 framesRemaining = frameCount; + while (framesRemaining > 0) { + mal_uint32 framesToSend = sizeof(tempBuffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } + + void* ppDeinterleavedBuffers[MAL_MAX_CHANNELS]; + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)mal_offset_ptr(pRenderedBufferList->mBuffers[iBuffer].mData, (frameCount - framesRemaining) * mal_get_bytes_per_sample(pDevice->internalFormat)); + } + + mal_interleave_pcm_frames(pDevice->internalFormat, pDevice->internalChannels, framesToSend, (const void**)ppDeinterleavedBuffers, tempBuffer); + mal_device__send_frames_to_client(pDevice, framesToSend, tempBuffer); + + framesRemaining -= framesToSend; + } } } @@ -14976,42 +15071,53 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro mal_device* pDevice = (mal_device*)pUserData; mal_assert(pDevice != NULL); - UInt32 isRunning; - UInt32 isRunningSize = sizeof(isRunning); - OSStatus status = ((mal_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); - if (status != noErr) { - return; // Don't really know what to do in this case... just ignore it, I suppose... - } - - if (!isRunning) { - // The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: - // - // 1) When the device is unplugged, this will be called _before_ the default device change notification. - // 2) When the device is changed via the default device change notification, this will be called _after_ the switch. - // - // For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. - if (pDevice->isDefaultDevice && mal_device__get_state(pDevice) != MAL_STATE_STOPPING && mal_device__get_state(pDevice) != MAL_STATE_STOPPED) { - // It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device - // via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the - // device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it - // hasn't!). - if (pDevice->coreaudio.isSwitchingDevice) { - return; - } - - // Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio - // will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most - // likely be successful in switching to the new device. - // - // TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. - return; - } - - // Getting here means we need to stop the device. + // There's been a report of a deadlock here when triggered by mal_device_uninit(). It looks like + // AudioUnitGetProprty (called below) and AudioComponentInstanceDispose (called in mal_device_uninit) + // can try waiting on the same lock. I'm going to try working around this by not calling any Core + // Audio APIs in the callback when the device has been stopped or uninitialized. + if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED || mal_device__get_state(pDevice) == MAL_STATE_STOPPING) { mal_stop_proc onStop = pDevice->onStop; if (onStop) { onStop(pDevice); } + } else { + UInt32 isRunning; + UInt32 isRunningSize = sizeof(isRunning); + OSStatus status = ((mal_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); + if (status != noErr) { + return; // Don't really know what to do in this case... just ignore it, I suppose... + } + + if (!isRunning) { + // The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: + // + // 1) When the device is unplugged, this will be called _before_ the default device change notification. + // 2) When the device is changed via the default device change notification, this will be called _after_ the switch. + // + // For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. + if (pDevice->isDefaultDevice && mal_device__get_state(pDevice) != MAL_STATE_STOPPING && mal_device__get_state(pDevice) != MAL_STATE_STOPPED) { + // It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device + // via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the + // device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it + // hasn't!). + if (pDevice->coreaudio.isSwitchingDevice) { + return; + } + + // Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio + // will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most + // likely be successful in switching to the new device. + // + // TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. + return; + } + + // Getting here means we need to stop the device. + mal_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + } } } @@ -15288,11 +15394,14 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device if (result != MAL_SUCCESS) { return result; } + + pData->bufferSizeInFramesOut = actualBufferSizeInFrames * pData->periodsOut; #else actualBufferSizeInFrames = 4096; + pData->bufferSizeInFramesOut = actualBufferSizeInFrames; #endif - pData->bufferSizeInFramesOut = actualBufferSizeInFrames * pData->periodsOut; + // During testing I discovered that the buffer size can be too big. You'll get an error like this: // @@ -15556,6 +15665,15 @@ mal_result mal_context_uninit__coreaudio(mal_context* pContext) mal_result mal_context_init__coreaudio(mal_context* pContext) { mal_assert(pContext != NULL); + +#if defined(MAL_APPLE_MOBILE) + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + mal_assert(pAudioSession != NULL); + + [pAudioSession setCategory: AVAudioSessionCategoryPlayAndRecord error:nil]; + } +#endif #if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE) pContext->coreaudio.hCoreFoundation = mal_dlopen("CoreFoundation.framework/CoreFoundation"); @@ -19725,7 +19843,7 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con // SDL wants the buffer size to be a power of 2. The SDL_AudioSpec property for this is only a Uint16, so we need // to explicitly clamp this because it will be easy to overflow. - mal_uint32 bufferSize = pConfig->bufferSizeInFrames; + mal_uint32 bufferSize = pDevice->bufferSizeInFrames; if (bufferSize > 32768) { bufferSize = 32768; } else { @@ -19760,7 +19878,7 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con pDevice->sdl.deviceID = ((MAL_PFN_SDL_OpenAudioDevice)pDevice->pContext->sdl.SDL_OpenAudioDevice)(pDeviceName, isCapture, &desiredSpec, &obtainedSpec, MAL_SDL_AUDIO_ALLOW_ANY_CHANGE); if (pDevice->sdl.deviceID == 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL2 device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } } else #endif @@ -19778,10 +19896,12 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con desiredSpec.format = MAL_AUDIO_S16; } - pDevice->sdl.deviceID = ((MAL_PFN_SDL_OpenAudio)pDevice->pContext->sdl.SDL_OpenAudio)(&desiredSpec, &obtainedSpec); - if (pDevice->sdl.deviceID != 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + int deviceID = ((MAL_PFN_SDL_OpenAudio)pDevice->pContext->sdl.SDL_OpenAudio)(&desiredSpec, &obtainedSpec); + if (deviceID < 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL1 device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } + + pDevice->sdl.deviceID = (mal_uint32)deviceID; } pDevice->internalFormat = mal_format_from_sdl(obtainedSpec.format); @@ -20429,6 +20549,14 @@ mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCoun mal_context_post_error(pContext, NULL, MAL_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. mal_context_get_device_info() is not thread safe.", MAL_FAILED_TO_CREATE_MUTEX); } +#ifdef MAL_DEBUG_OUTPUT + printf("[mini_al] Endian: %s\n", mal_is_little_endian() ? "LE" : "BE"); + printf("[mini_al] SSE2: %s\n", mal_has_sse2() ? "YES" : "NO"); + printf("[mini_al] AVX2: %s\n", mal_has_avx2() ? "YES" : "NO"); + printf("[mini_al] AVX512F: %s\n", mal_has_avx512f() ? "YES" : "NO"); + printf("[mini_al] NEON: %s\n", mal_has_neon() ? "YES" : "NO"); +#endif + pContext->backend = backend; return result; } @@ -20767,7 +20895,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi #ifdef MAL_DEBUG_OUTPUT - printf("[WASAPI] %s (%s)\n", pDevice->name, (pDevice->type == mal_device_type_playback) ? "Playback" : "Capture"); + printf("[%s] %s (%s)\n", mal_get_backend_name(pDevice->pContext->backend), pDevice->name, (pDevice->type == mal_device_type_playback) ? "Playback" : "Capture"); printf(" Format: %s -> %s\n", mal_get_format_name(pDevice->format), mal_get_format_name(pDevice->internalFormat)); printf(" Channels: %d -> %d\n", pDevice->channels, pDevice->internalChannels); printf(" Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->internalSampleRate); @@ -20949,7 +21077,7 @@ mal_result mal_device_stop(mal_device* pDevice) // Asynchronous backends need to be handled differently. if (mal_context_is_backend_asynchronous(pDevice->pContext)) { - pDevice->pContext->onDeviceStop(pDevice); + result = pDevice->pContext->onDeviceStop(pDevice); } else { // Synchronous backends. @@ -26138,6 +26266,92 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form } } +void mal_deinterleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void* pInterleavedPCMFrames, void** ppDeinterleavedPCMFrames) +{ + if (pInterleavedPCMFrames == NULL || ppDeinterleavedPCMFrames == NULL) { + return; // Invalid args. + } + + // For efficiency we do this per format. + switch (format) { + case mal_format_s16: + { + const mal_int16* pSrcS16 = (const mal_int16*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + mal_int16* pDstS16 = (mal_int16*)ppDeinterleavedPCMFrames[iChannel]; + pDstS16[iPCMFrame] = pSrcS16[iPCMFrame*channels+iChannel]; + } + } + } break; + + case mal_format_f32: + { + const float* pSrcF32 = (const float*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + float* pDstF32 = (float*)ppDeinterleavedPCMFrames[iChannel]; + pDstF32[iPCMFrame] = pSrcF32[iPCMFrame*channels+iChannel]; + } + } + } break; + + default: + { + mal_uint32 sampleSizeInBytes = mal_get_bytes_per_sample(format); + + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + void* pDst = mal_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); + const void* pSrc = mal_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); + memcpy(pDst, pSrc, sampleSizeInBytes); + } + } + } break; + } +} + +void mal_interleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames) +{ + switch (format) + { + case mal_format_s16: + { + mal_int16* pDstS16 = (mal_int16*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + const mal_int16* pSrcS16 = (const mal_int16*)ppDeinterleavedPCMFrames[iChannel]; + pDstS16[iPCMFrame*channels+iChannel] = pSrcS16[iPCMFrame]; + } + } + } break; + + case mal_format_f32: + { + float* pDstF32 = (float*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + const float* pSrcF32 = (const float*)ppDeinterleavedPCMFrames[iChannel]; + pDstF32[iPCMFrame*channels+iChannel] = pSrcF32[iPCMFrame]; + } + } + } break; + + default: + { + mal_uint32 sampleSizeInBytes = mal_get_bytes_per_sample(format); + + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + void* pDst = mal_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); + const void* pSrc = mal_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); + memcpy(pDst, pSrc, sampleSizeInBytes); + } + } + } break; + } +} + typedef struct @@ -28296,6 +28510,13 @@ mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount // REVISION HISTORY // ================ // +// v0.8.10 - 2018-10-21 +// - Core Audio: Fix a hang when uninitializing a device. +// - Fix a bug where an incorrect value is returned from mal_device_stop(). +// +// v0.8.9 - 2018-09-28 +// - Fix a bug with the SDL backend where device initialization fails. +// // v0.8.8 - 2018-09-14 // - Fix Linux build with the ALSA backend. // - Minor documentation fix. diff --git a/raylib/external/stb_vorbis.c b/raylib/external/stb_vorbis.c index c847242..4b72504 100644 --- a/raylib/external/stb_vorbis.c +++ b/raylib/external/stb_vorbis.c @@ -193,7 +193,7 @@ #undef __forceinline #endif #define __forceinline - //#define alloca __builtin_alloca + #define alloca __builtin_alloca #elif !defined(_MSC_VER) #if __GNUC__ #define __forceinline inline diff --git a/raylib/mini_al.c b/raylib/mini_al.c index 6e9821c..35cc002 100644 --- a/raylib/mini_al.c +++ b/raylib/mini_al.c @@ -1,4 +1,4 @@ // The implementation of mini_al needs to #include windows.h which means it needs to go into // it's own translation unit. Not doing this will cause conflicts with CloseWindow(), etc. #define MAL_IMPLEMENTATION -#include "mini_al.h" +#include "external/mini_al.h" diff --git a/raylib/raylib.h b/raylib/raylib.h index 6549ba1..ef47038 100644 --- a/raylib/raylib.h +++ b/raylib/raylib.h @@ -223,7 +223,7 @@ #define MOUSE_MIDDLE_BUTTON 2 // Touch points registered -#define MAX_TOUCH_POINTS 2 +#define MAX_TOUCH_POINTS 10 // Gamepad Number #define GAMEPAD_PLAYER1 0 @@ -311,7 +311,7 @@ // NOTE: MSC C++ compiler does not support compound literals (C99 feature) // Plain structures in C++ (without constructors) can be initialized from { } initializers. -#ifdef __cplusplus +#if defined(__cplusplus) #define CLITERAL #else #define CLITERAL (Color) @@ -511,7 +511,7 @@ typedef struct Mesh { float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) unsigned short *indices;// Vertex indices (in case vertex data comes indexed) - + // Animation vertex data float *baseVertices; // Vertex base position (required to apply bones transformations) float *baseNormals; // Vertex base normals (required to apply bones transformations) @@ -786,7 +786,7 @@ typedef enum { // Callbacks to be implemented by users typedef void (*TraceLogCallback)(int msgType, const char *text, va_list args); -#ifdef __cplusplus +#if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif @@ -868,9 +868,11 @@ RLAPI void TakeScreenshot(const char *fileName); // Takes a scr RLAPI int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) // Files management functions +RLAPI bool FileExists(const char *fileName); // Check if file exists RLAPI bool IsFileExtension(const char *fileName, const char *ext);// Check file extension RLAPI const char *GetExtension(const char *fileName); // Get pointer to extension for a filename string RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (memory should be freed) RLAPI const char *GetDirectoryPath(const char *fileName); // Get full path for a given fileName (uses static string) RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) RLAPI char **GetDirectoryFiles(const char *dirPath, int *count); // Get filenames in a directory path (memory should be freed) @@ -879,11 +881,14 @@ RLAPI bool ChangeDirectory(const char *dir); // Change work RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI char **GetDroppedFiles(int *count); // Get dropped files names (memory should be freed) RLAPI void ClearDroppedFiles(void); // Clear dropped files paths buffer (free memory) +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) // Persistent storage management RLAPI void StorageSaveValue(int position, int value); // Save integer value to storage file (to defined position) RLAPI int StorageLoadValue(int position); // Load integer value from storage file (from defined position) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) //------------------------------------------------------------------------------------ @@ -967,7 +972,7 @@ RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color colors[4]); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors @@ -979,6 +984,8 @@ RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Col RLAPI void DrawPolyEx(Vector2 *points, int numPoints, Color color); // Draw a closed polygon defined by points RLAPI void DrawPolyExLines(Vector2 *points, int numPoints, Color color); // Draw polygon lines +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Define default texture used to draw shapes + // Basic shapes collision detection functions RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles @@ -998,6 +1005,7 @@ RLAPI Image LoadImageEx(Color *pixels, int width, int height); RLAPI Image LoadImagePro(void *data, int width, int height, int format); // Load image from raw data with parameters RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data RLAPI void ExportImage(Image image, const char *fileName); // Export image data to file +RLAPI void ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) @@ -1019,15 +1027,17 @@ RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle -RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (bilinear filtering) +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color color); // Resize canvas and fill with color RLAPI void ImageMipmaps(Image *image); // Generate all mipmap levels for a provided image RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount); // Extract color palette from image to maximum size (memory should be freed) RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec); // Draw a source image within a destination image -RLAPI void ImageDrawRectangle(Image *dst, Vector2 position, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangle(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image RLAPI void ImageDrawText(Image *dst, Vector2 position, const char *text, int fontSize, Color color); // Draw text (default font) within an image (destination) RLAPI void ImageDrawTextEx(Image *dst, Vector2 position, Font font, const char *text, float fontSize, float spacing, Color color); // Draw text (custom sprite font) within an image (destination) RLAPI void ImageFlipVertical(Image *image); // Flip image vertically @@ -1073,21 +1083,25 @@ RLAPI Font GetFontDefault(void); RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) RLAPI Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load font from file with extended parameters RLAPI CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(CharInfo *chars, int fontSize, int charsCount, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) // Text drawing functions RLAPI void DrawFPS(int posX, int posY); // Shows current FPS RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char* text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters // Text misc. functions RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font -RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' -RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on font +// Text string edition functions +RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' +RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string +RLAPI char **SplitText(char *text, char delimiter, int *strCount); // Split text string into multiple strings (memory should be freed manually!) +RLAPI bool IsEqualText(const char *text1, const char *text2); // Check if two text string are equal + //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) //------------------------------------------------------------------------------------ @@ -1231,6 +1245,7 @@ RLAPI void UpdateSound(Sound sound, const void *data, int samplesCount);// Updat RLAPI void UnloadWave(Wave wave); // Unload wave data RLAPI void UnloadSound(Sound sound); // Unload sound RLAPI void ExportWave(Wave wave, const char *fileName); // Export wave data to file +RLAPI void ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h) // Wave/Sound management functions RLAPI void PlaySound(Sound sound); // Play a sound @@ -1273,7 +1288,7 @@ RLAPI void StopAudioStream(AudioStream stream); // Stop au RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) -#ifdef __cplusplus +#if defined(__cplusplus) } #endif diff --git a/raylib/raymath.h b/raylib/raymath.h index 3311653..42a45ad 100644 --- a/raylib/raymath.h +++ b/raylib/raymath.h @@ -46,38 +46,31 @@ //#define RAYMATH_HEADER_ONLY // NOTE: To compile functions as static inline, uncomment this line #ifndef RAYMATH_STANDALONE - #include "raylib.h" // Required for structs: Vector3, Matrix + #include "raylib.h" // Required for structs: Vector3, Matrix #endif -#ifdef __cplusplus - #define RMEXTERN extern "C" // Functions visible from other files (no name mangling of functions in C++) -#else - #define RMEXTERN // Functions visible from other files -#endif - -#if defined RAYMATH_IMPLEMENTATION && defined RAYMATH_HEADER_ONLY +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_HEADER_ONLY) #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_HEADER_ONLY is contradictory" #endif -#ifdef RAYMATH_IMPLEMENTATION +#if defined(RAYMATH_IMPLEMENTATION) #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) #define RMDEF __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll). #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #define RMDEF __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) #else #define RMDEF extern inline // Provide external definition #endif -#elif defined RAYMATH_HEADER_ONLY +#elif defined(RAYMATH_HEADER_ONLY) #define RMDEF static inline // Functions may be inlined, no external out-of-line definition #else - #ifdef __TINYC__ + #if defined(__TINYC__) #define RMDEF static inline // plain inline not supported by tinycc (See issue #435) #else #define RMDEF inline // Functions may be inlined or external definition used #endif #endif - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -300,7 +293,7 @@ RMDEF Vector3 Vector3Add(Vector3 v1, Vector3 v2) return result; } -// Substract two vectors +// Subtract two vectors RMDEF Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) { Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; @@ -720,8 +713,8 @@ RMDEF Matrix MatrixAdd(Matrix left, Matrix right) return result; } -// Substract two matrices (left - right) -RMDEF Matrix MatrixSubstract(Matrix left, Matrix right) +// Subtract two matrices (left - right) +RMDEF Matrix MatrixSubtract(Matrix left, Matrix right) { Matrix result = MatrixIdentity(); diff --git a/raylib/rlgl.h b/raylib/rlgl.h index 3f5c67a..7f2a171 100644 --- a/raylib/rlgl.h +++ b/raylib/rlgl.h @@ -199,7 +199,7 @@ typedef unsigned char byte; float *tangents; // vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3) unsigned short *indices;// vertex indices (in case vertex data comes indexed) - + // Animation vertex data float *baseVertices; // Vertex base position (required to apply bones transformations) float *baseNormals; // Vertex base normals (required to apply bones transformations) @@ -378,7 +378,7 @@ typedef unsigned char byte; } VrDevice; #endif -#ifdef __cplusplus +#if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif @@ -516,7 +516,7 @@ void TraceLog(int msgType, const char *text, ...); // Show trace log messag int GetPixelDataSize(int width, int height, int format);// Get pixel data size in bytes (image or texture) #endif -#ifdef __cplusplus +#if defined(__cplusplus) } #endif @@ -553,7 +553,7 @@ int GetPixelDataSize(int width, int height, int format);// Get pixel data size i #else // APIENTRY for OpenGL function pointer declarations is required #ifndef APIENTRY - #ifdef _WIN32 + #if defined(_WIN32) #define APIENTRY __stdcall #else #define APIENTRY @@ -729,7 +729,7 @@ typedef struct VrStereoConfig { //---------------------------------------------------------------------------------- #if !defined(GRAPHICS_API_OPENGL_11) && defined(SUPPORT_DISTORTION_SHADER) // Distortion shader embedded - static char distortionFShaderStr[] = + static char distortionFShaderStr[] = #if defined(GRAPHICS_API_OPENGL_21) "#version 120 \n" #elif defined(GRAPHICS_API_OPENGL_ES2) @@ -1217,9 +1217,9 @@ void rlEnd(void) // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlglDraw(), // we need to call rlPopMatrix() before to recover *currentMatrix (modelview) for the next forced draw call! // Also noted that if we had multiple matrix pushed, it will require "stackCounter" pops before launching the draw - + // TODO: Undoubtely, current rlPushMatrix/rlPopMatrix should be redesigned... or removed... it's not working properly - + rlPopMatrix(); rlglDraw(); } @@ -1506,9 +1506,17 @@ void rlDeleteTextures(unsigned int id) void rlDeleteRenderTextures(RenderTexture2D target) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (target.id > 0) glDeleteFramebuffers(1, &target.id); if (target.texture.id > 0) glDeleteTextures(1, &target.texture.id); - if (target.depth.id > 0) glDeleteTextures(1, &target.depth.id); + if (target.depth.id > 0) + { +#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteRenderbuffers(1, &target.depth.id); +#elif defined(GRAPHICS_API_OPENGL_33) + glDeleteTextures(1, &target.depth.id); +#endif + } + + if (target.id > 0) glDeleteFramebuffers(1, &target.id); TraceLog(LOG_INFO, "[FBO ID %i] Unloaded render texture data from VRAM (GPU)", target.id); #endif @@ -1620,7 +1628,7 @@ void rlglInit(int width, int height) // NOTE: We don't need to check again supported extensions but we do (GLAD already dealt with that) glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); -#ifdef _MSC_VER +#if defined(_MSC_VER) const char **extList = malloc(sizeof(const char *)*numExt); #else const char *extList[numExt]; @@ -1631,17 +1639,13 @@ void rlglInit(int width, int height) #elif defined(GRAPHICS_API_OPENGL_ES2) char *extensions = (char *)glGetString(GL_EXTENSIONS); // One big const string - // NOTE: We have to duplicate string because glGetString() returns a const value - // If not duplicated, it fails in some systems (Raspberry Pi) - // Equivalent to function: char *strdup(const char *str) - char *extensionsDup; - size_t len = strlen(extensions) + 1; - void *newstr = malloc(len); - if (newstr == NULL) extensionsDup = NULL; - extensionsDup = (char *)memcpy(newstr, extensions, len); + // NOTE: We have to duplicate string because glGetString() returns a const string + int len = strlen(extensions) + 1; + char *extensionsDup = (char *)malloc(len); + strcpy(extensionsDup, extensions); // NOTE: String could be splitted using strtok() function (string.h) - // NOTE: strtok() modifies the received string, it can not be const + // NOTE: strtok() modifies the passed string, it can not be const char *extList[512]; // Allocate 512 strings pointers (2 KB) @@ -2175,7 +2179,7 @@ void rlUnloadTexture(unsigned int id) // Load a texture to be used for rendering (fbo with color and depth attachments) RenderTexture2D rlLoadRenderTexture(int width, int height) { - RenderTexture2D target; + RenderTexture2D target = { 0 }; target.id = 0; @@ -2255,8 +2259,16 @@ RenderTexture2D rlLoadRenderTexture(int width, int height) default: break; } - glDeleteTextures(1, &target.texture.id); - glDeleteTextures(1, &target.depth.id); + if (target.texture.id > 0) glDeleteTextures(1, &target.texture.id); + if (target.depth.id > 0) + { +#if defined(USE_DEPTH_RENDERBUFFER) + glDeleteRenderbuffers(1, &target.depth.id); +#elif defined(USE_DEPTH_TEXTURE) + glDeleteTextures(1, &target.depth.id); +#endif + } + glDeleteFramebuffers(1, &target.id); } else TraceLog(LOG_INFO, "[FBO ID %i] Framebuffer object created successfully", target.id); @@ -2724,7 +2736,7 @@ void rlUnloadMesh(Mesh *mesh) if (mesh->tangents != NULL) free(mesh->tangents); if (mesh->texcoords2 != NULL) free(mesh->texcoords2); if (mesh->indices != NULL) free(mesh->indices); - + if (mesh->baseVertices != NULL) free(mesh->baseVertices); if (mesh->baseNormals != NULL) free(mesh->baseNormals); if (mesh->weightBias != NULL) free(mesh->weightBias); @@ -3807,7 +3819,7 @@ static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShad glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); -#ifdef _MSC_VER +#if defined(_MSC_VER) char *log = malloc(maxLength); #else char log[maxLength]; @@ -3816,7 +3828,7 @@ static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShad TraceLog(LOG_INFO, "%s", log); -#ifdef _MSC_VER +#if defined(_MSC_VER) free(log); #endif glDeleteProgram(program); diff --git a/raylib/shapes.c b/raylib/shapes.c index 90f9582..eb63d5c 100644 --- a/raylib/shapes.c +++ b/raylib/shapes.c @@ -9,7 +9,7 @@ * Allows drawing rectangles and text with a single draw call, very useful for GUI systems! * * #define SUPPORT_QUADS_DRAW_MODE -* Use QUADS instead of TRIANGLES for drawing when possible. +* Use QUADS instead of TRIANGLES for drawing when possible. * Some lines-based shapes could still use lines * * LICENSE: zlib/libpng @@ -54,12 +54,14 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -// ... +static Texture2D texShapes = { 0 }; +static Rectangle recTexShapes = { 0 }; //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- static float EaseCubicInOut(float t, float b, float c, float d); // Cubic easing +static Texture2D GetShapesTexture(void); // Get texture to draw shapes //---------------------------------------------------------------------------------- // Module Functions Definition @@ -114,27 +116,34 @@ void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) startPos = endPos; endPos = tempPos; } - + float dx = endPos.x - startPos.x; float dy = endPos.y - startPos.y; - + float d = sqrtf(dx*dx + dy*dy); float angle = asinf(dy/d); - - rlEnableTexture(GetTextureDefault().id); + + rlEnableTexture(GetShapesTexture().id); rlPushMatrix(); rlTranslatef((float)startPos.x, (float)startPos.y, 0); rlRotatef(RAD2DEG*angle, 0, 0, 1); - rlTranslatef(0, -thick/2.0f, 0); + rlTranslatef(0, (thick > 1.0f) ? -thick/2.0f : -1.0f, 0); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 0.0f, 1.0f); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(0.0f, 0.0f); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(0.0f, thick); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(d, thick); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(d, 0.0f); rlEnd(); rlPopMatrix(); @@ -153,12 +162,12 @@ void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) for (int i = 1; i <= LINE_DIVISIONS; i++) { // Cubic easing in-out - // NOTE: Easing is calculated only for y position value + // NOTE: Easing is calculated only for y position value current.y = EaseCubicInOut((float)i, startPos.y, endPos.y - startPos.y, (float)LINE_DIVISIONS); current.x = previous.x + (endPos.x - startPos.x)/ (float)LINE_DIVISIONS; - + DrawLineEx(previous, current, thick, color); - + previous = current; } } @@ -174,7 +183,7 @@ void DrawCircle(int centerX, int centerY, float radius, Color color) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) { if (rlCheckBufferLimit(RL_TRIANGLES, 3*36)) rlglDraw(); - + rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) { @@ -191,24 +200,31 @@ void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Co // Draw a color-filled circle (Vector version) // NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) void DrawCircleV(Vector2 center, float radius, Color color) -{ +{ #if defined(SUPPORT_QUADS_DRAW_MODE) if (rlCheckBufferLimit(RL_QUADS, 4*(36/2))) rlglDraw(); - - rlEnableTexture(GetTextureDefault().id); // Default white texture + + rlEnableTexture(GetShapesTexture().id); rlBegin(RL_QUADS); for (int i = 0; i < 360; i += 20) { rlColor4ub(color.r, color.g, color.b, color.a); - + + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(center.x, center.y); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*i)*radius, center.y + cosf(DEG2RAD*i)*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(i + 10))*radius, center.y + cosf(DEG2RAD*(i + 10))*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(i + 20))*radius, center.y + cosf(DEG2RAD*(i + 20))*radius); } rlEnd(); - + rlDisableTexture(); #else if (rlCheckBufferLimit(RL_TRIANGLES, 3*(36/2))) rlglDraw(); @@ -217,7 +233,7 @@ void DrawCircleV(Vector2 center, float radius, Color color) for (int i = 0; i < 360; i += 10) { rlColor4ub(color.r, color.g, color.b, color.a); - + rlVertex2f(center.x, center.y); rlVertex2f(center.x + sinf(DEG2RAD*i)*radius, center.y + cosf(DEG2RAD*i)*radius); rlVertex2f(center.x + sinf(DEG2RAD*(i + 10))*radius, center.y + cosf(DEG2RAD*(i + 10))*radius); @@ -230,7 +246,7 @@ void DrawCircleV(Vector2 center, float radius, Color color) void DrawCircleLines(int centerX, int centerY, float radius, Color color) { if (rlCheckBufferLimit(RL_LINES, 2*36)) rlglDraw(); - + rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -246,104 +262,55 @@ void DrawCircleLines(int centerX, int centerY, float radius, Color color) // Draw a color-filled rectangle void DrawRectangle(int posX, int posY, int width, int height, Color color) { - Vector2 position = { (float)posX, (float)posY }; - Vector2 size = { (float)width, (float)height }; - - DrawRectangleV(position, size, color); + DrawRectangleV((Vector2){ (float)posX, (float)posY }, (Vector2){ (float)width, (float)height }, color); } // Draw a color-filled rectangle (Vector version) // NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) void DrawRectangleV(Vector2 position, Vector2 size, Color color) { -#if defined(SUPPORT_QUADS_DRAW_MODE) - #if defined(SUPPORT_FONT_TEXTURE) - // Draw rectangle using font texture white character - rlEnableTexture(GetFontDefault().texture.id); + Color colors[4] = { color, color, color, color }; - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); - - // NOTE: Default raylib font character 95 is a white square - rlTexCoord2f((float)GetFontDefault().chars[95].rec.x/GetFontDefault().texture.width, - (float)GetFontDefault().chars[95].rec.y/GetFontDefault().texture.height); - rlVertex2f(position.x, position.y); - - rlTexCoord2f((float)GetFontDefault().chars[95].rec.x/GetFontDefault().texture.width, - (float)(GetFontDefault().chars[95].rec.y + GetFontDefault().chars[95].rec.height)/GetFontDefault().texture.height); - rlVertex2f(position.x, position.y + size.y); - - rlTexCoord2f((float)(GetFontDefault().chars[95].rec.x + GetFontDefault().chars[95].rec.width)/GetFontDefault().texture.width, - (float)(GetFontDefault().chars[95].rec.y + GetFontDefault().chars[95].rec.height)/GetFontDefault().texture.height); - rlVertex2f(position.x + size.x, position.y + size.y); - - rlTexCoord2f((float)(GetFontDefault().chars[95].rec.x + GetFontDefault().chars[95].rec.width)/GetFontDefault().texture.width, - (float)GetFontDefault().chars[95].rec.y/GetFontDefault().texture.height); - rlVertex2f(position.x + size.x, position.y); - rlEnd(); - - rlDisableTexture(); - #else - rlEnableTexture(GetTextureDefault().id); // Default white texture - - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); - - rlTexCoord2f(0.0f, 0.0f); - rlVertex2f(position.x, position.y); - - rlTexCoord2f(0.0f, 1.0f); - rlVertex2f(position.x, position.y + size.y); - - rlTexCoord2f(1.0f, 1.0f); - rlVertex2f(position.x + size.x, position.y + size.y); - - rlTexCoord2f(1.0f, 0.0f); - rlVertex2f(position.x + size.x, position.y); - rlEnd(); - - rlDisableTexture(); - #endif // SUPPORT_FONT_TEXTURE -#else - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - - rlVertex2i(position.x, position.y); - rlVertex2i(position.x, position.y + size.y); - rlVertex2i(position.x + size.x, position.y + size.y); - - rlVertex2i(position.x, position.y); - rlVertex2i(position.x + size.x, position.y + size.y); - rlVertex2i(position.x + size.x, position.y); - rlEnd(); -#endif // SUPPORT_QUADS_DRAW_MODE + DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, colors); } // Draw a color-filled rectangle void DrawRectangleRec(Rectangle rec, Color color) { - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, color); + Color colors[4] = { color, color, color, color }; + + DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, colors); } -void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) +// Draw a color-filled rectangle with pro parameters +void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color colors[4]) { - rlEnableTexture(GetTextureDefault().id); + rlEnableTexture(GetShapesTexture().id); rlPushMatrix(); - rlTranslatef(rec.x, rec.y, 0); + //rlTranslatef(rec.x, rec.y, 0); // Already considered on vertex position rlRotatef(rotation, 0, 0, 1); rlTranslatef(-origin.x, -origin.y, 0); rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + rlNormal3f(0.0f, 0.0f, 1.0f); - rlVertex2f(0.0f, 0.0f); - rlVertex2f(0.0f, rec.height); - rlVertex2f(rec.width, rec.height); - rlVertex2f(rec.width, 0.0f); + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(colors[0].r, colors[0].g, colors[0].b, colors[0].a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(rec.x, rec.y); + + rlColor4ub(colors[1].r, colors[1].g, colors[1].b, colors[1].a); + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlColor4ub(colors[2].r, colors[2].g, colors[2].b, colors[2].a); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlColor4ub(colors[3].r, colors[3].g, colors[3].b, colors[3].a); + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); rlPopMatrix(); @@ -368,67 +335,15 @@ void DrawRectangleGradientH(int posX, int posY, int width, int height, Color col // NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) { -#if defined(SUPPORT_FONT_TEXTURE) - // Draw rectangle using font texture white character - rlEnableTexture(GetFontDefault().texture.id); + Color colors[4] = { col1, col2, col3, col4 }; - rlBegin(RL_QUADS); - rlNormal3f(0.0f, 0.0f, 1.0f); - - // NOTE: Default raylib font character 95 is a white square - rlColor4ub(col1.r, col1.g, col1.b, col1.a); - rlTexCoord2f(GetFontDefault().chars[95].rec.x/GetFontDefault().texture.width, - GetFontDefault().chars[95].rec.y/GetFontDefault().texture.height); - rlVertex2f(rec.x, rec.y); - - rlColor4ub(col2.r, col2.g, col2.b, col2.a); - rlTexCoord2f(GetFontDefault().chars[95].rec.x/GetFontDefault().texture.width, - (GetFontDefault().chars[95].rec.y + GetFontDefault().chars[95].rec.height)/GetFontDefault().texture.height); - rlVertex2f(rec.x, rec.y + rec.height); - - rlColor4ub(col3.r, col3.g, col3.b, col3.a); - rlTexCoord2f((GetFontDefault().chars[95].rec.x + GetFontDefault().chars[95].rec.width)/GetFontDefault().texture.width, - (GetFontDefault().chars[95].rec.y + GetFontDefault().chars[95].rec.height)/GetFontDefault().texture.height); - rlVertex2f(rec.x + rec.width, rec.y + rec.height); - - rlColor4ub(col4.r, col4.g, col4.b, col4.a); - rlTexCoord2f((GetFontDefault().chars[95].rec.x + GetFontDefault().chars[95].rec.width)/GetFontDefault().texture.width, - GetFontDefault().chars[95].rec.y/GetFontDefault().texture.height); - rlVertex2f(rec.x + rec.width, rec.y); - rlEnd(); - - rlDisableTexture(); -#else - rlEnableTexture(GetTextureDefault().id); // Default white texture - - rlBegin(RL_QUADS); - rlNormal3f(0.0f, 0.0f, 1.0f); - - rlColor4ub(col1.r, col1.g, col1.b, col1.a); - rlTexCoord2f(0.0f, 0.0f); - rlVertex2f(rec.x, rec.y); - - rlColor4ub(col2.r, col2.g, col2.b, col2.a); - rlTexCoord2f(0.0f, 1.0f); - rlVertex2f(rec.x, rec.y + rec.height); - - rlColor4ub(col3.r, col3.g, col3.b, col3.a); - rlTexCoord2f(1.0f, 1.0f); - rlVertex2f(rec.x + rec.width, rec.y + rec.height); - - rlColor4ub(col4.r, col4.g, col4.b, col4.a); - rlTexCoord2f(1.0f, 0.0f); - rlVertex2f(rec.x + rec.width, rec.y); - rlEnd(); - - rlDisableTexture(); -#endif + DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, colors); } // Draw rectangle outline // NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) void DrawRectangleLines(int posX, int posY, int width, int height, Color color) -{ +{ #if defined(SUPPORT_QUADS_DRAW_MODE) DrawRectangle(posX, posY, width, 1, color); DrawRectangle(posX + width - 1, posY + 1, 1, height - 2, color); @@ -454,13 +369,13 @@ void DrawRectangleLines(int posX, int posY, int width, int height, Color color) // Draw rectangle outline with extended parameters void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) -{ +{ if (lineThick > rec.width || lineThick > rec.height) { if(rec.width > rec.height) lineThick = (int)rec.height/2; else if (rec.width < rec.height) lineThick = (int)rec.width/2; - } - + } + DrawRectangle( (int)rec.x, (int)rec.y, (int)rec.width, lineThick, color); DrawRectangle( (int)(rec.x - lineThick + rec.width), (int)(rec.y + lineThick), lineThick, (int)(rec.height - lineThick*2.0f), color); DrawRectangle( (int)rec.x, (int)(rec.y + rec.height - lineThick), (int)rec.width, lineThick, color); @@ -471,16 +386,24 @@ void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { #if defined(SUPPORT_QUADS_DRAW_MODE) - rlEnableTexture(GetTextureDefault().id); // Default white texture + rlEnableTexture(GetShapesTexture().id); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(v1.x, v1.y); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(v3.x, v3.y); rlEnd(); - + rlDisableTexture(); #else rlBegin(RL_TRIANGLES); @@ -512,24 +435,31 @@ void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color) { if (sides < 3) sides = 3; - + if (rlCheckBufferLimit(RL_QUADS, 4*(360/sides))) rlglDraw(); rlPushMatrix(); rlTranslatef(center.x, center.y, 0.0); rlRotatef(rotation, 0, 0, 1); - + #if defined(SUPPORT_QUADS_DRAW_MODE) - rlEnableTexture(GetTextureDefault().id); // Default white texture + rlEnableTexture(GetShapesTexture().id); rlBegin(RL_QUADS); for (int i = 0; i < 360; i += 360/sides) { rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(0, 0); + + rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height); rlVertex2f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius); + + rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, recTexShapes.y/texShapes.height); rlVertex2f(sinf(DEG2RAD*(i + 360/sides))*radius, cosf(DEG2RAD*(i + 360/sides))*radius); } rlEnd(); @@ -555,9 +485,9 @@ void DrawPolyEx(Vector2 *points, int pointsCount, Color color) if (pointsCount >= 3) { if (rlCheckBufferLimit(RL_QUADS, pointsCount)) rlglDraw(); - + #if defined(SUPPORT_QUADS_DRAW_MODE) - rlEnableTexture(GetTextureDefault().id); // Default white texture + rlEnableTexture(GetShapesTexture().id); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); @@ -605,6 +535,13 @@ void DrawPolyExLines(Vector2 *points, int pointsCount, Color color) } } +// Define default texture used to draw shapes +void SetShapesTexture(Texture2D texture, Rectangle source) +{ + texShapes = texture; + recTexShapes = source; +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Collision Detection functions //---------------------------------------------------------------------------------- @@ -647,7 +584,7 @@ bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) { bool collision = false; - + if ((rec1.x <= (rec2.x + rec2.width) && (rec1.x + rec1.width) >= rec2.x) && (rec1.y <= (rec2.y + rec2.height) && (rec1.y + rec1.height) >= rec2.y)) collision = true; @@ -675,7 +612,7 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) { int recCenterX = (int)(rec.x + rec.width/2.0f); int recCenterY = (int)(rec.y + rec.height/2.0f); - + float dx = (float)fabs(center.x - recCenterX); float dy = (float)fabs(center.y - recCenterY); @@ -685,7 +622,7 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) if (dx <= (rec.width/2.0f)) { return true; } if (dy <= (rec.height/2.0f)) { return true; } - float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) + + float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) + (dy - rec.height/2.0f)*(dy - rec.height/2.0f); return (cornerDistanceSq <= (radius*radius)); @@ -744,7 +681,7 @@ Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) { if (retRec.width >= rec1.width) retRec.width = rec1.width; } - + if (rec1.height > rec2.height) { if (retRec.height >= rec2.height) retRec.height = rec2.height; @@ -762,12 +699,29 @@ Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) // Module specific Functions Definition //---------------------------------------------------------------------------------- -// Cubic easing in-out +// Cubic easing in-out // NOTE: Required for DrawLineBezier() -static float EaseCubicInOut(float t, float b, float c, float d) -{ +static float EaseCubicInOut(float t, float b, float c, float d) +{ if ((t /= 0.5f*d) < 1) return 0.5f*c*t*t*t + b; t -= 2; return 0.5f*c*(t*t*t + 2.0f) + b; } + +// Get texture to draw shapes (RAII) +static Texture2D GetShapesTexture(void) +{ + if (texShapes.id <= 0) + { +#if defined(SUPPORT_FONT_TEXTURE) + texShapes = GetFontDefault().texture; // Use font texture white character + recTexShapes = GetFontDefault().chars[95].rec; +#else + texShapes = GetTextureDefault(); // Use default white texture + recTexShapes = { 0.0f, 0.0f, 1.0f, 1.0f }; +#endif + } + + return texShapes; +} diff --git a/raylib/shapes.go b/raylib/shapes.go index 20f7e6d..3b74e68 100644 --- a/raylib/shapes.go +++ b/raylib/shapes.go @@ -112,12 +112,12 @@ func DrawRectangleRec(rec Rectangle, color Color) { } // DrawRectanglePro - Draw a color-filled rectangle with pro parameters -func DrawRectanglePro(rec Rectangle, origin Vector2, rotation float32, color Color) { +func DrawRectanglePro(rec Rectangle, origin Vector2, rotation float32, colors []Color) { crec := rec.cptr() corigin := origin.cptr() crotation := (C.float)(rotation) - ccolor := color.cptr() - C.DrawRectanglePro(*crec, *corigin, crotation, *ccolor) + ccolor := (*C.Color)(unsafe.Pointer(&colors[0])) + C.DrawRectanglePro(*crec, *corigin, crotation, ccolor) } // DrawRectangleGradientV - Draw a vertical-gradient-filled rectangle @@ -290,3 +290,10 @@ func CheckCollisionPointTriangle(point, p1, p2, p3 Vector2) bool { v := bool(ret) return v } + +// SetShapesTexture - Define default texture used to draw shapes +func SetShapesTexture(texture Texture2D, source Rectangle) { + ctexture := texture.cptr() + csource := source.cptr() + C.SetShapesTexture(*ctexture, *csource) +} diff --git a/raylib/text.c b/raylib/text.c index 3dbb261..07a2596 100644 --- a/raylib/text.c +++ b/raylib/text.c @@ -190,10 +190,6 @@ extern void LoadDefaultFont(void) if (counter > 512) counter = 0; // Security check... } - //FILE *myimage = fopen("default_font.raw", "wb"); - //fwrite(image.pixels, 1, 128*128*4, myimage); - //fclose(myimage); - Image image = LoadImageEx(imagePixels, imWidth, imHeight); ImageFormat(&image, UNCOMPRESSED_GRAY_ALPHA); @@ -204,10 +200,10 @@ extern void LoadDefaultFont(void) // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount //------------------------------------------------------------------------------ - + // Allocate space for our characters info data // NOTE: This memory should be freed at end! --> CloseWindow() - defaultFont.chars = (CharInfo *)malloc(defaultFont.charsCount*sizeof(CharInfo)); + defaultFont.chars = (CharInfo *)malloc(defaultFont.charsCount*sizeof(CharInfo)); int currentLine = 0; int currentPosX = charsDivisor; @@ -242,7 +238,7 @@ extern void LoadDefaultFont(void) } defaultFont.baseSize = (int)defaultFont.chars[0].rec.height; - + TraceLog(LOG_INFO, "[TEX ID %i] Default font loaded successfully", defaultFont.texture.id); } @@ -262,7 +258,7 @@ Font GetFontDefault() #else Font font = { 0 }; return font; -#endif +#endif } // Load Font from file into GPU memory (VRAM) @@ -276,15 +272,7 @@ Font LoadFont(const char *fileName) Font font = { 0 }; #if defined(SUPPORT_FILEFORMAT_TTF) - if (IsFileExtension(fileName, ".ttf")) - { - font.baseSize = DEFAULT_TTF_FONTSIZE; - font.charsCount = DEFAULT_TTF_NUMCHARS; - font.chars = LoadFontData(fileName, font.baseSize, NULL, font.charsCount, FONT_DEFAULT); - Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 4, 0); - font.texture = LoadTextureFromImage(atlas); - UnloadImage(atlas); - } + if (IsFileExtension(fileName, ".ttf")) font = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, DEFAULT_TTF_NUMCHARS, NULL); else #endif #if defined(SUPPORT_FILEFORMAT_FNT) @@ -313,14 +301,19 @@ Font LoadFont(const char *fileName) Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars) { Font font = { 0 }; - + font.baseSize = fontSize; font.charsCount = (charsCount > 0) ? charsCount : 95; font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT); - Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 2, 0); - font.texture = LoadTextureFromImage(atlas); - UnloadImage(atlas); - + + if (font.chars != NULL) + { + Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 2, 0); + font.texture = LoadTextureFromImage(atlas); + UnloadImage(atlas); + } + else font = GetFontDefault(); + return font; } @@ -333,94 +326,100 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c #define SDF_CHAR_PADDING 4 #define SDF_ON_EDGE_VALUE 128 #define SDF_PIXEL_DIST_SCALE 64.0f - + #define BITMAP_ALPHA_THRESHOLD 80 - - // In case no chars count provided, default to 95 - charsCount = (charsCount > 0) ? charsCount : 95; - - CharInfo *chars = (CharInfo *)malloc(charsCount*sizeof(CharInfo)); - + + CharInfo *chars = NULL; + // Load font data (including pixel data) from TTF file // NOTE: Loaded information should be enough to generate font image atlas, // using any packaging method FILE *fontFile = fopen(fileName, "rb"); // Load font file - - fseek(fontFile, 0, SEEK_END); - long size = ftell(fontFile); // Get file size - fseek(fontFile, 0, SEEK_SET); // Reset file pointer - - unsigned char *fontBuffer = (unsigned char *)malloc(size); - - fread(fontBuffer, size, 1, fontFile); - fclose(fontFile); - - // Init font for data reading - stbtt_fontinfo fontInfo; - if (!stbtt_InitFont(&fontInfo, fontBuffer, 0)) TraceLog(LOG_WARNING, "Failed to init font!"); - // Calculate font scale factor - float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); + if (fontFile != NULL) + { + fseek(fontFile, 0, SEEK_END); + long size = ftell(fontFile); // Get file size + fseek(fontFile, 0, SEEK_SET); // Reset file pointer - // Calculate font basic metrics - // NOTE: ascent is equivalent to font baseline - int ascent, descent, lineGap; - stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); - - // Fill fontChars in case not provided externally - // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) - int genFontChars = false; - if (fontChars == NULL) genFontChars = true; - if (genFontChars) - { - fontChars = (int *)malloc(charsCount*sizeof(int)); - for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; - } - - // NOTE: Using simple packaging, one char after another - for (int i = 0; i < charsCount; i++) - { - int chw = 0, chh = 0; // Character width and height (on generation) - int ch = fontChars[i]; // Character value to get info for - chars[i].value = ch; - - // Render a unicode codepoint to a bitmap - // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap - // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be - // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide - - if (type != FONT_SDF) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); - else if (ch != 32) chars[i].data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); - - if (type == FONT_BITMAP) + unsigned char *fontBuffer = (unsigned char *)malloc(size); + + fread(fontBuffer, size, 1, fontFile); + fclose(fontFile); + + // Init font for data reading + stbtt_fontinfo fontInfo; + if (!stbtt_InitFont(&fontInfo, fontBuffer, 0)) TraceLog(LOG_WARNING, "Failed to init font!"); + + // Calculate font scale factor + float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + + // In case no chars count provided, default to 95 + charsCount = (charsCount > 0) ? charsCount : 95; + + // Fill fontChars in case not provided externally + // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) + int genFontChars = false; + if (fontChars == NULL) { - // Aliased bitmap (black & white) font generation, avoiding anti-aliasing - // NOTE: For optimum results, bitmap font should be generated at base pixel size - for (int p = 0; p < chw*chh; p++) - { - if (chars[i].data[p] < BITMAP_ALPHA_THRESHOLD) chars[i].data[p] = 0; - else chars[i].data[p] = 255; - } + fontChars = (int *)malloc(charsCount*sizeof(int)); + for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; + genFontChars = true; } - - chars[i].rec.width = (float)chw; - chars[i].rec.height = (float)chh; - chars[i].offsetY += (int)((float)ascent*scaleFactor); - - // Get bounding box for character (may be offset to account for chars that dip above or below the line) - int chX1, chY1, chX2, chY2; - stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); - - TraceLog(LOG_DEBUG, "Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); - TraceLog(LOG_DEBUG, "Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1); - stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); - chars[i].advanceX *= scaleFactor; + chars = (CharInfo *)malloc(charsCount*sizeof(CharInfo)); + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + int chw = 0, chh = 0; // Character width and height (on generation) + int ch = fontChars[i]; // Character value to get info for + chars[i].value = ch; + + // Render a unicode codepoint to a bitmap + // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap + // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be + // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide + + if (type != FONT_SDF) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else if (ch != 32) chars[i].data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + + if (type == FONT_BITMAP) + { + // Aliased bitmap (black & white) font generation, avoiding anti-aliasing + // NOTE: For optimum results, bitmap font should be generated at base pixel size + for (int p = 0; p < chw*chh; p++) + { + if (chars[i].data[p] < BITMAP_ALPHA_THRESHOLD) chars[i].data[p] = 0; + else chars[i].data[p] = 255; + } + } + + chars[i].rec.width = (float)chw; + chars[i].rec.height = (float)chh; + chars[i].offsetY += (int)((float)ascent*scaleFactor); + + // Get bounding box for character (may be offset to account for chars that dip above or below the line) + int chX1, chY1, chX2, chY2; + stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); + + TraceLog(LOG_DEBUG, "Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); + TraceLog(LOG_DEBUG, "Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1); + + stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); + chars[i].advanceX *= scaleFactor; + } + + free(fontBuffer); + if (genFontChars) free(fontChars); } - - free(fontBuffer); - if (genFontChars) free(fontChars); - + else TraceLog(LOG_WARNING, "[%s] TTF file could not be opened", fileName); + return chars; } @@ -429,25 +428,25 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod) { Image atlas = { 0 }; - + // In case no chars count provided we suppose default of 95 charsCount = (charsCount > 0) ? charsCount : 95; - + // Calculate image size based on required pixel area // NOTE 1: Image is forced to be squared and POT... very conservative! - // NOTE 2: SDF font characters already contain an internal padding, + // NOTE 2: SDF font characters already contain an internal padding, // so image size would result bigger than default font type float requiredArea = 0; for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].rec.width + 2*padding)*(chars[i].rec.height + 2*padding)); float guessSize = sqrtf(requiredArea)*1.25f; int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT - + atlas.width = imageSize; // Atlas bitmap width atlas.height = imageSize; // Atlas bitmap height atlas.data = (unsigned char *)calloc(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) atlas.format = UNCOMPRESSED_GRAYSCALE; atlas.mipmaps = 1; - + // DEBUG: We can see padding in the generated image setting a gray background... //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; @@ -455,9 +454,9 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi { int offsetX = padding; int offsetY = padding; - + // NOTE: Using simple packaging, one char after another - for (int i = 0; i < charsCount; i++) + for (int i = 0; i < charsCount; i++) { // Copy pixel data from fc.data to atlas for (int y = 0; y < (int)chars[i].rec.height; y++) @@ -467,22 +466,22 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; } } - + chars[i].rec.x = (float)offsetX; chars[i].rec.y = (float)offsetY; - + // Move atlas position X for next character drawing offsetX += ((int)chars[i].rec.width + 2*padding); - + if (offsetX >= (atlas.width - (int)chars[i].rec.width - padding)) { offsetX = padding; - - // NOTE: Be careful on offsetY for SDF fonts, by default SDF + + // NOTE: Be careful on offsetY for SDF fonts, by default SDF // use an internal padding of 4 pixels, it means char rectangle // height is bigger than fontSize, it could be up to (fontSize + 8) offsetY += (fontSize + 2*padding); - + if (offsetY > (atlas.height - fontSize - padding)) break; } } @@ -490,13 +489,13 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect) { TraceLog(LOG_DEBUG, "Using Skyline packing algorythm!"); - + stbrp_context *context = (stbrp_context *)malloc(sizeof(*context)); stbrp_node *nodes = (stbrp_node *)malloc(charsCount*sizeof(*nodes)); stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount); stbrp_rect *rects = (stbrp_rect *)malloc(charsCount*sizeof(stbrp_rect)); - + // Fill rectangles for packaging for (int i = 0; i < charsCount; i++) { @@ -507,12 +506,12 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi // Package rectangles into atlas stbrp_pack_rects(context, rects, charsCount); - + for (int i = 0; i < charsCount; i++) { chars[i].rec.x = rects[i].x + (float)padding; chars[i].rec.y = rects[i].y + (float)padding; - + if (rects[i].was_packed) { // Copy pixel data from fc.data to atlas @@ -527,12 +526,13 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi else TraceLog(LOG_WARNING, "Character could not be packed: %i", i); } + free(rects); free(nodes); free(context); } - + // TODO: Crop image if required for smaller size - + // Convert image data from GRAYSCALE to GRAY_ALPHA // WARNING: ImageAlphaMask(&atlas, atlas) does not work in this case, requires manual operation unsigned char *dataGrayAlpha = (unsigned char *)malloc(imageSize*imageSize*sizeof(unsigned char)*2); // Two channels @@ -546,7 +546,7 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi free(atlas.data); atlas.data = dataGrayAlpha; atlas.format = UNCOMPRESSED_GRAY_ALPHA; - + return atlas; } @@ -563,6 +563,28 @@ void UnloadFont(Font font) } } +// Shows current FPS on top-left corner +// NOTE: Uses default font +void DrawFPS(int posX, int posY) +{ + // NOTE: We are rendering fps every second for better viewing on high framerates + + static int fps = 0; + static int counter = 0; + static int refreshRate = 20; + + if (counter < refreshRate) counter++; + else + { + fps = GetFPS(); + refreshRate = fps; + counter = 0; + } + + // NOTE: We have rounding errors every frame, so it oscillates a lot + DrawText(FormatText("%2i FPS", fps), posX, posY, 20, LIME); +} + // Draw text (using default font) // NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used // NOTE: chars spacing is proportional to fontSize @@ -588,10 +610,10 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f int length = strlen(text); int textOffsetX = 0; // Offset between characters int textOffsetY = 0; // Required for line break! - float scaleFactor; + float scaleFactor = 0.0f; - unsigned char letter; // Current character - int index; // Index position in sprite font + unsigned char letter = 0; // Current character + int index = 0; // Index position in sprite font scaleFactor = fontSize/font.baseSize; @@ -623,7 +645,7 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f i++; } else index = GetGlyphIndex(font, (unsigned char)text[i]); - + if ((unsigned char)text[i] != ' ') { DrawTexturePro(font.texture, font.chars[index].rec, @@ -639,44 +661,6 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f } } -// Formatting of text with variables to 'embed' -const char *FormatText(const char *text, ...) -{ - static char buffer[MAX_FORMATTEXT_LENGTH]; - - va_list args; - va_start(args, text); - vsprintf(buffer, text, args); - va_end(args); - - return buffer; -} - -// Get a piece of a text string -const char *SubText(const char *text, int position, int length) -{ - static char buffer[MAX_SUBTEXT_LENGTH]; - int textLength = strlen(text); - - if (position >= textLength) - { - position = textLength - 1; - length = 0; - } - - if (length >= textLength) length = textLength; - - for (int c = 0 ; c < length ; c++) - { - *(buffer + c) = *(text + position); - text++; - } - - *(buffer + length) = '\0'; - - return buffer; -} - // Measure string width for default font int MeasureText(const char *text, int fontSize) { @@ -702,8 +686,8 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing int tempLen = 0; // Used to count longer text line num chars int lenCounter = 0; - float textWidth = 0; - float tempTextWidth = 0; // Used to count longer text line width + float textWidth = 0.0f; + float tempTextWidth = 0.0f; // Used to count longer text line width float textHeight = (float)font.baseSize; float scaleFactor = fontSize/(float)font.baseSize; @@ -761,26 +745,88 @@ int GetGlyphIndex(Font font, int character) #endif } -// Shows current FPS on top-left corner -// NOTE: Uses default font -void DrawFPS(int posX, int posY) +// Formatting of text with variables to 'embed' +const char *FormatText(const char *text, ...) { - // NOTE: We are rendering fps every second for better viewing on high framerates + static char buffer[MAX_FORMATTEXT_LENGTH]; - static int fps = 0; - static int counter = 0; - static int refreshRate = 20; + va_list args; + va_start(args, text); + vsprintf(buffer, text, args); + va_end(args); - if (counter < refreshRate) counter++; - else + return buffer; +} + +// Get a piece of a text string +const char *SubText(const char *text, int position, int length) +{ + static char buffer[MAX_SUBTEXT_LENGTH] = { 0 }; + int textLength = strlen(text); + + if (position >= textLength) { - fps = GetFPS(); - refreshRate = fps; - counter = 0; + position = textLength - 1; + length = 0; } - - // NOTE: We have rounding errors every frame, so it oscillates a lot - DrawText(FormatText("%2i FPS", fps), posX, posY, 20, LIME); + + if (length >= textLength) length = textLength; + + for (int c = 0 ; c < length ; c++) + { + *(buffer + c) = *(text + position); + text++; + } + + *(buffer + length) = '\0'; + + return buffer; +} + +// Split string into multiple strings +// NOTE: Files count is returned by parameters pointer +// NOTE: Allocated memory should be manually freed +char **SplitText(char *text, char delimiter, int *strCount) +{ + #define MAX_SUBSTRING_LENGTH 128 + + char **strings = NULL; + int len = strlen(text); + char *strDup = (char *)malloc(len + 1); + strcpy(strDup, text); + int counter = 1; + + // Count how many substrings we have on string + for (int i = 0; i < len; i++) if (text[i] == delimiter) counter++; + + // Memory allocation for substrings + strings = (char **)malloc(sizeof(char *)*counter); + for (int i = 0; i < counter; i++) strings[i] = (char *)malloc(sizeof(char)*MAX_SUBSTRING_LENGTH); + + char *substrPtr = NULL; + char delimiters[1] = { delimiter }; // Only caring for one delimiter + substrPtr = strtok(strDup, delimiters); + + for (int i = 0; (i < counter) && (substrPtr != NULL); i++) + { + strcpy(strings[i], substrPtr); + substrPtr = strtok(NULL, delimiters); + } + + *strCount = counter; + free(strDup); + + return strings; +} + +// Check if two text string are equal +bool IsEqualText(const char *text1, const char *text2) +{ + bool result = false; + + if (strcmp(text1, text2) == 0) result = true; + + return result; } //---------------------------------------------------------------------------------- @@ -815,7 +861,7 @@ static Font LoadImageFont(Image image, Color key, int firstChar) { if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; } - + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; } @@ -911,17 +957,18 @@ static Font LoadBMFont(const char *fileName) Font font = { 0 }; font.texture.id = 0; - char buffer[MAX_BUFFER_SIZE]; + char buffer[MAX_BUFFER_SIZE] = { 0 }; char *searchPoint = NULL; int fontSize = 0; - int texWidth, texHeight; + int texWidth = 0; + int texHeight = 0; char texFileName[129]; int charsCount = 0; - int base; // Useless data + int base = 0; // Useless data - FILE *fntFile; + FILE *fntFile = NULL; fntFile = fopen(fileName, "rt"); @@ -984,10 +1031,10 @@ static Font LoadBMFont(const char *fileName) UnloadImage(imCopy); } else font.texture = LoadTextureFromImage(imFont); - + UnloadImage(imFont); free(texPath); - + // Fill font characters info data font.baseSize = fontSize; diff --git a/raylib/textures.c b/raylib/textures.c index 3099bf6..fce1092 100644 --- a/raylib/textures.c +++ b/raylib/textures.c @@ -60,7 +60,7 @@ #include "raylib.h" // Declares module functions #include // Required for: malloc(), free() -#include // Required for: strcmp(), strrchr(), strncmp() +#include // Required for: strlen() #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2 // Required for: rlLoadTexture() rlDeleteTextures(), @@ -147,8 +147,8 @@ static Image LoadDDS(const char *fileName); // Load DDS file static Image LoadPKM(const char *fileName); // Load PKM file #endif #if defined(SUPPORT_FILEFORMAT_KTX) -static Image LoadKTX(const char *fileName); // Load KTX file -static void SaveKTX(Image image, const char *fileName); // Save image data as KTX file +static Image LoadKTX(const char *fileName); // Load KTX file +static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file #endif #if defined(SUPPORT_FILEFORMAT_PVR) static Image LoadPVR(const char *fileName); // Load PVR file @@ -323,7 +323,7 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int // NOTE: fread() returns num read elements instead of bytes, // to get bytes we need to read (1 byte size, elements) instead of (x byte size, 1 element) - size_t bytes = fread(image.data, 1, size, rawFile); + int bytes = fread(image.data, 1, size, rawFile); // Check if data has been read successfully if (bytes < size) @@ -424,7 +424,7 @@ Color *GetImageData(Image image) if ((image.format == UNCOMPRESSED_R32) || (image.format == UNCOMPRESSED_R32G32B32) || (image.format == UNCOMPRESSED_R32G32B32A32)) TraceLog(LOG_WARNING, "32bit pixel format converted to 8bit per channel"); - + for (int i = 0, k = 0; i < image.width*image.height; i++) { switch (image.format) @@ -500,7 +500,7 @@ Color *GetImageData(Image image) pixels[i].g = 0; pixels[i].b = 0; pixels[i].a = 255; - + } break; case UNCOMPRESSED_R32G32B32: { @@ -508,7 +508,7 @@ Color *GetImageData(Image image) pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); pixels[i].a = 255; - + k += 3; } case UNCOMPRESSED_R32G32B32A32: @@ -517,7 +517,7 @@ Color *GetImageData(Image image) pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); - + k += 4; } default: break; @@ -532,7 +532,7 @@ Color *GetImageData(Image image) Vector4 *GetImageDataNormalized(Image image) { Vector4 *pixels = (Vector4 *)malloc(image.width*image.height*sizeof(Vector4)); - + if (image.format >= COMPRESSED_DXT1_RGB) TraceLog(LOG_WARNING, "Pixel data retrieval not supported for compressed image formats"); else { @@ -611,7 +611,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].y = 0.0f; pixels[i].z = 0.0f; pixels[i].w = 1.0f; - + } break; case UNCOMPRESSED_R32G32B32: { @@ -619,7 +619,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].y = ((float *)image.data)[k + 1]; pixels[i].z = ((float *)image.data)[k + 2]; pixels[i].w = 1.0f; - + k += 3; } case UNCOMPRESSED_R32G32B32A32: @@ -628,14 +628,14 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].y = ((float *)image.data)[k + 1]; pixels[i].z = ((float *)image.data)[k + 2]; pixels[i].w = ((float *)image.data)[k + 3]; - + k += 4; } default: break; } } } - + return pixels; } @@ -720,31 +720,68 @@ void UpdateTexture(Texture2D texture, const void *pixels) void ExportImage(Image image, const char *fileName) { int success = 0; - + // NOTE: Getting Color array as RGBA unsigned char values unsigned char *imgData = (unsigned char *)GetImageData(image); - + if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4); else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData); else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData); else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100 - else if (IsFileExtension(fileName, ".ktx")) SaveKTX(image, fileName); - else if (IsFileExtension(fileName, ".raw")) + else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); + else if (IsFileExtension(fileName, ".raw")) { // Export raw pixel data (without header) // NOTE: It's up to the user to track image parameters FILE *rawFile = fopen(fileName, "wb"); - fwrite(image.data, GetPixelDataSize(image.width, image.height, image.format), 1, rawFile); + success = fwrite(image.data, GetPixelDataSize(image.width, image.height, image.format), 1, rawFile); fclose(rawFile); } - else if (IsFileExtension(fileName, ".h")) { } // TODO: Export pixel data as an array of bytes - + if (success != 0) TraceLog(LOG_INFO, "Image exported successfully: %s", fileName); else TraceLog(LOG_WARNING, "Image could not be exported."); - + free(imgData); } +// Export image as code file (.h) defining an array of bytes +void ExportImageAsCode(Image image, const char *fileName) +{ + #define BYTES_TEXT_PER_LINE 20 + + char varFileName[256] = { 0 }; + int dataSize = GetPixelDataSize(image.width, image.height, image.format); + + FILE *txtFile = fopen(fileName, "wt"); + + fprintf(txtFile, "\n//////////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } + + // Add image information + fprintf(txtFile, "// Image data information\n"); + fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width); + fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height); + fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + + 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 *)image.data)[i]); + fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + + fclose(txtFile); +} + // Copy an image to a new image Image ImageCopy(Image image) { @@ -950,7 +987,7 @@ void ImageFormat(Image *image, int newFormat) case UNCOMPRESSED_R32: { // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit - + image->data = (float *)malloc(image->width*image->height*sizeof(float)); for (int i = 0; i < image->width*image->height; i++) @@ -986,7 +1023,7 @@ void ImageFormat(Image *image, int newFormat) free(pixels); pixels = NULL; - + // In case original image had mipmaps, generate mipmaps for formated image // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost if (image->mipmaps > 1) @@ -1050,14 +1087,14 @@ void ImageAlphaMask(Image *image, Image alphaMask) void ImageAlphaClear(Image *image, Color color, float threshold) { Color *pixels = GetImageData(*image); - + for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; UnloadImage(*image); - + int prevFormat = image->format; *image = LoadImageEx(pixels, image->width, image->height); - + ImageFormat(image, prevFormat); } @@ -1065,13 +1102,13 @@ void ImageAlphaClear(Image *image, Color color, float threshold) void ImageAlphaCrop(Image *image, float threshold) { Rectangle crop = { 0 }; - + Color *pixels = GetImageData(*image); - + int minx = 0; int miny = 0; - for (int i = 0; i < image->width*image->height; i++) + for (int i = 0; i < image->width*image->height; i++) { if (pixels[i].a > (unsigned char)(threshold*255.0f)) { @@ -1090,18 +1127,18 @@ void ImageAlphaCrop(Image *image, float threshold) else if (crop.height < (float)miny) crop.height = (float)miny; } } - + crop.width -= (crop.x - 1); crop.height -= (crop.y - 1); - + TraceLog(LOG_INFO, "Crop rectangle: (%i, %i, %i, %i)", crop.x, crop.y, crop.width, crop.height); - + free(pixels); - + // NOTE: Added this weird check to avoid additional 1px crop to // image data that has already been cropped... - if ((crop.x != 1) && - (crop.y != 1) && + if ((crop.x != 1) && + (crop.y != 1) && (crop.width != image->width - 1) && (crop.height != image->height - 1)) ImageCrop(image, crop); } @@ -1111,8 +1148,8 @@ void ImageAlphaPremultiply(Image *image) { float alpha = 0.0f; Color *pixels = GetImageData(*image); - - for (int i = 0; i < image->width*image->height; i++) + + for (int i = 0; i < image->width*image->height; i++) { alpha = (float)pixels[i].a/255.0f; pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); @@ -1121,10 +1158,10 @@ void ImageAlphaPremultiply(Image *image) } UnloadImage(*image); - + int prevFormat = image->format; *image = LoadImageEx(pixels, image->width, image->height); - + ImageFormat(image, prevFormat); } @@ -1245,9 +1282,9 @@ void ImageResizeCanvas(Image *image, int newWidth,int newHeight, int offsetX, in Image imTemp = GenImageColor(newWidth, newHeight, color); Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; Rectangle dstRec = { (float)offsetX, (float)offsetY, (float)srcRec.width, (float)srcRec.height }; - + // TODO: Review different scaling situations - + if ((newWidth > image->width) && (newHeight > image->height)) { ImageDraw(&imTemp, *image, srcRec, dstRec); @@ -1277,7 +1314,7 @@ void ImageMipmaps(Image *image) { if (mipWidth != 1) mipWidth /= 2; if (mipHeight != 1) mipHeight /= 2; - + // Security check for NPOT textures if (mipWidth < 1) mipWidth = 1; if (mipHeight < 1) mipHeight = 1; @@ -1295,8 +1332,8 @@ void ImageMipmaps(Image *image) if (image->mipmaps < mipCount) { void *temp = realloc(image->data, mipSize); - - if (temp != NULL) + + if (temp != NULL) { image->data = temp; // Assign new pointer (new size) to store mipmaps data TraceLog(LOG_DEBUG, "Image data memory point reallocated: 0x%x", temp); @@ -1305,29 +1342,29 @@ void ImageMipmaps(Image *image) // Pointer to allocated memory point where store next mipmap level data unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format); - + mipWidth = image->width/2; mipHeight = image->height/2; mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); Image imCopy = ImageCopy(*image); - + for (int i = 1; i < mipCount; i++) { TraceLog(LOG_DEBUG, "Gen mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); - + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter memcpy(nextmip, imCopy.data, mipSize); nextmip += mipSize; image->mipmaps++; - + mipWidth /= 2; mipHeight /= 2; - + // Security check for NPOT textures if (mipWidth < 1) mipWidth = 1; if (mipHeight < 1) mipHeight = 1; - + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); } @@ -1445,6 +1482,57 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } } +// Extract color palette from image to maximum size +// NOTE: Memory allocated should be freed manually! +Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + Color *pixels = GetImageData(image); + Color *palette = (Color *)malloc(maxPaletteSize*sizeof(Color)); + + int palCount = 0; + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) + { + if (pixels[i].a > 0) + { + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) + { + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } + } + + // Store color if not on the palette + if (!colorInPalette) + { + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // We reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + printf("WARNING: Image palette is greater than %i colors!\n", maxPaletteSize); + } + } + } + } + + free(pixels); + + *extractCount = palCount; + + return palette; +} + // Draw an image (source) within an image (destination) // TODO: Feel this function could be simplified... void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) @@ -1507,7 +1595,7 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) UnloadImage(srcCopy); // Source copy not required any more... Vector4 fsrc, fdst, fout; // float based versions of pixel data - + // Blit pixels, copy source image into destination // TODO: Probably out-of-bounds blitting could be considered here instead of so much cropping... for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) @@ -1515,12 +1603,12 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) { // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) - + fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); - + if (fout.w <= 0.0f) { fout.x = 0.0f; @@ -1534,9 +1622,9 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; } - dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), - (unsigned char)(fout.y*255.0f), - (unsigned char)(fout.z*255.0f), + dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), + (unsigned char)(fout.y*255.0f), + (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; // TODO: Support other blending options @@ -1574,16 +1662,16 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co // TODO: ISSUE: Measured text size does not seem to be correct... issue on ImageDraw() Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); - + TraceLog(LOG_DEBUG, "Text Image size: %f, %f", imSize.x, imSize.y); // NOTE: glGetTexImage() not available in OpenGL ES - // TODO: This is horrible, retrieving font texture from GPU!!! + // TODO: This is horrible, retrieving font texture from GPU!!! // Define ImageFont struct? or include Image spritefont in Font struct? Image imFont = GetTextureData(font.texture); - + ImageFormat(&imFont, UNCOMPRESSED_R8G8B8A8); // Make sure image format could be properly colored! - + ImageColorTint(&imFont, tint); // Apply color tint to font // Create image to store text @@ -1614,10 +1702,10 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co else index = GetGlyphIndex(font, (unsigned char)text[i]); CharInfo letter = font.chars[index]; - + if ((unsigned char)text[i] != ' ') { - ImageDraw(&imText, imFont, letter.rec, (Rectangle){ (float)(posX + letter.offsetX), + ImageDraw(&imText, imFont, letter.rec, (Rectangle){ (float)(posX + letter.offsetX), (float)letter.offsetY, (float)letter.rec.width, (float)letter.rec.height }); } @@ -1643,17 +1731,22 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co } // Draw rectangle within an image -void ImageDrawRectangle(Image *dst, Vector2 position, Rectangle rec, Color color) +void ImageDrawRectangle(Image *dst, Rectangle rec, Color color) { Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); - - Rectangle dstRec = { position.x, position.y, (float)imRec.width, (float)imRec.height }; - - ImageDraw(dst, imRec, rec, dstRec); - + ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec); UnloadImage(imRec); } +// Draw rectangle lines within an image +void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) +{ + ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.y, rec.width, thick }, color); + ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.y + thick, thick, rec.height - thick*2 }, color); + ImageDrawRectangle(dst, (Rectangle){ rec.x + rec.width - thick, rec.y + thick, thick, rec.height - thick*2 }, color); + ImageDrawRectangle(dst, (Rectangle){ rec.x, rec.height - thick, rec.width, thick }, color); +} + // Draw text (default font) within an image (destination) void ImageDrawText(Image *dst, Vector2 position, const char *text, int fontSize, Color color) { @@ -1968,13 +2061,13 @@ void ImageColorReplace(Image *image, Color color, Color replace) Image GenImageColor(int width, int height, Color color) { Color *pixels = (Color *)calloc(width*height, sizeof(Color)); - + for (int i = 0; i < width*height; i++) pixels[i] = color; - + Image image = LoadImageEx(pixels, width, height); - + free(pixels); - + return image; } @@ -2032,17 +2125,17 @@ Image GenImageGradientRadial(int width, int height, float density, Color inner, float centerX = (float)width/2.0f; float centerY = (float)height/2.0f; - + for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float dist = hypotf((float)x - centerX, (float)y - centerY); float factor = (dist - radius*density)/(radius*(1.0f - density)); - + factor = (float)fmax(factor, 0.f); factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check - + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); @@ -2104,7 +2197,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float { float nx = (float)(x + offsetX)*scale/(float)width; float ny = (float)(y + offsetY)*scale/(float)height; - + // Typical values to start playing with: // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) // gain = 0.5 -- relative weighting applied to each successive octave @@ -2112,7 +2205,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float // NOTE: We need to translate the data from [-1..1] to [0..1] float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6, 0, 0, 0) + 1.0f)/2.0f; - + int intensity = (int)(p*255.0f); pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; } @@ -2145,7 +2238,7 @@ Image GenImageCellular(int width, int height, int tileSize) for (int y = 0; y < height; y++) { int tileY = y/tileSize; - + for (int x = 0; x < width; x++) { int tileX = x/tileSize; @@ -2175,7 +2268,7 @@ Image GenImageCellular(int width, int height, int tileSize) pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; } } - + free(seeds); Image image = LoadImageEx(pixels, width, height); @@ -2323,7 +2416,7 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V { float width = (float)texture.width; float height = (float)texture.height; - + if (sourceRec.width < 0) sourceRec.x -= sourceRec.width; if (sourceRec.height < 0) sourceRec.y -= sourceRec.height; @@ -2618,11 +2711,11 @@ static Image LoadDDS(const char *fileName) else { // Verify the type of file - char filecode[4]; + char ddsHeaderId[4]; - fread(filecode, 4, 1, ddsFile); + fread(ddsHeaderId, 4, 1, ddsFile); - if (strncmp(filecode, "DDS ", 4) != 0) + if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' ')) { TraceLog(LOG_WARNING, "[%s] DDS file does not seem to be a valid image", fileName); } @@ -2641,7 +2734,7 @@ static Image LoadDDS(const char *fileName) image.width = ddsHeader.width; image.height = ddsHeader.height; - + if (ddsHeader.mipmapCount == 0) image.mipmaps = 1; // Parameter not used else image.mipmaps = ddsHeader.mipmapCount; @@ -2729,7 +2822,7 @@ static Image LoadDDS(const char *fileName) TraceLog(LOG_DEBUG, "Pitch or linear size: %i", ddsHeader.pitchOrLinearSize); - image.data = (unsigned char*)malloc(size*sizeof(unsigned char)); + image.data = (unsigned char *)malloc(size*sizeof(unsigned char)); fread(image.data, size, 1, ddsFile); @@ -2802,7 +2895,7 @@ static Image LoadPKM(const char *fileName) // Get the image header fread(&pkmHeader, sizeof(PKMHeader), 1, pkmFile); - if (strncmp(pkmHeader.id, "PKM ", 4) != 0) + if ((pkmHeader.id[0] != 'P') || (pkmHeader.id[1] != 'K') || (pkmHeader.id[2] != 'M') || (pkmHeader.id[3] != ' ')) { TraceLog(LOG_WARNING, "[%s] PKM file does not seem to be a valid image", fileName); } @@ -2826,7 +2919,7 @@ static Image LoadPKM(const char *fileName) int size = image.width*image.height*bpp/8; // Total data size in bytes - image.data = (unsigned char*)malloc(size*sizeof(unsigned char)); + image.data = (unsigned char *)malloc(size*sizeof(unsigned char)); fread(image.data, size, 1, pkmFile); @@ -2858,9 +2951,9 @@ static Image LoadKTX(const char *fileName) // KTX file Header (64 bytes) // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ // v2.0 - http://github.khronos.org/KTX-Specification/ - + // TODO: Support KTX 2.2 specs! - + typedef struct { char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 @@ -2920,7 +3013,7 @@ static Image LoadKTX(const char *fileName) int dataSize; fread(&dataSize, sizeof(unsigned int), 1, ktxFile); - image.data = (unsigned char*)malloc(dataSize*sizeof(unsigned char)); + image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); fread(image.data, dataSize, 1, ktxFile); @@ -2937,12 +3030,14 @@ static Image LoadKTX(const char *fileName) // Save image data as KTX file // NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018) -static void SaveKTX(Image image, const char *fileName) +static int SaveKTX(Image image, const char *fileName) { + int success = 0; + // KTX file Header (64 bytes) // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ // v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation - + typedef struct { char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n" unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 @@ -2970,11 +3065,11 @@ static void SaveKTX(Image image, const char *fileName) else { KTXHeader ktxHeader; - + // KTX identifier (v2.2) //unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' }; //unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; - + // Get the image header strcpy(ktxHeader.id, "«KTX 11»\r\n\x1A\n"); // KTX 1.1 signature ktxHeader.endianness = 0; @@ -2990,28 +3085,28 @@ static void SaveKTX(Image image, const char *fileName) ktxHeader.faces = 1; ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats) ktxHeader.keyValueDataSize = 0; // No extra data after the header - + rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only - + // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC - + if (ktxHeader.glFormat == -1) TraceLog(LOG_WARNING, "Image format not supported for KTX export."); else { - fwrite(&ktxHeader, 1, sizeof(KTXHeader), ktxFile); - + success = fwrite(&ktxHeader, sizeof(KTXHeader), 1, ktxFile); + int width = image.width; int height = image.height; int dataOffset = 0; - + // Save all mipmaps data for (int i = 0; i < image.mipmaps; i++) { unsigned int dataSize = GetPixelDataSize(width, height, image.format); - fwrite(&dataSize, 1, sizeof(unsigned int), ktxFile); - fwrite((unsigned char *)image.data + dataOffset, 1, dataSize, ktxFile); - + success = fwrite(&dataSize, sizeof(unsigned int), 1, ktxFile); + success = fwrite((unsigned char *)image.data + dataOffset, dataSize, 1, ktxFile); + width /= 2; height /= 2; dataOffset += dataSize; @@ -3020,6 +3115,9 @@ static void SaveKTX(Image image, const char *fileName) fclose(ktxFile); // Close file pointer } + + // If all data has been written correctly to file, success = 1 + return success; } #endif @@ -3162,7 +3260,7 @@ static Image LoadPVR(const char *fileName) } int dataSize = image.width*image.height*bpp/8; // Total data size in bytes - image.data = (unsigned char*)malloc(dataSize*sizeof(unsigned char)); + image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); // Read data from file fread(image.data, dataSize, 1, pvrFile); @@ -3228,7 +3326,7 @@ static Image LoadASTC(const char *fileName) TraceLog(LOG_DEBUG, "ASTC image width: %i", image.width); TraceLog(LOG_DEBUG, "ASTC image height: %i", image.height); TraceLog(LOG_DEBUG, "ASTC image blocks: %ix%i", astcHeader.blockX, astcHeader.blockY); - + image.mipmaps = 1; // NOTE: ASTC format only contains one mipmap level // NOTE: Each block is always stored in 128bit so we can calculate the bpp diff --git a/raylib/textures.go b/raylib/textures.go index c91de4e..e2f2dfe 100644 --- a/raylib/textures.go +++ b/raylib/textures.go @@ -308,12 +308,20 @@ func ImageDraw(dst, src *Image, srcRec, dstRec Rectangle) { } // ImageDrawRectangle - Draw rectangle within an image -func ImageDrawRectangle(dst *Image, position Vector2, rec Rectangle, color Color) { +func ImageDrawRectangle(dst *Image, rec Rectangle, color Color) { cdst := dst.cptr() - cposition := position.cptr() crec := rec.cptr() ccolor := color.cptr() - C.ImageDrawRectangle(cdst, *cposition, *crec, *ccolor) + C.ImageDrawRectangle(cdst, *crec, *ccolor) +} + +// ImageDrawRectangleLines - Draw rectangle lines within an image +func ImageDrawRectangleLines(dst *Image, rec Rectangle, thick int, color Color) { + cdst := dst.cptr() + crec := rec.cptr() + cthick := (C.int)(thick) + ccolor := color.cptr() + C.ImageDrawRectangleLines(cdst, *crec, cthick, *ccolor) } // ImageDrawText - Draw text (default font) within an image (destination)