WARNING: BREAKING: Use frameCount on audio

This is a big change for optimization and a more professional understanding of audio. Instead of dealing with samples, now we deal with frames, like miniaudio does, so, avoiding continuous conversions from samples to frames.
This commit is contained in:
Ray 2021-08-16 11:06:31 +02:00
parent e203fb58c6
commit 1b4c58b66f
3 changed files with 105 additions and 118 deletions

View File

@ -728,11 +728,11 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int
if (success) if (success)
{ {
wave.sampleCount = (unsigned int)wav.totalPCMFrameCount*wav.channels; wave.frameCount = (unsigned int)wav.totalPCMFrameCount;
wave.sampleRate = wav.sampleRate; wave.sampleRate = wav.sampleRate;
wave.sampleSize = 16; wave.sampleSize = 16;
wave.channels = wav.channels; wave.channels = wav.channels;
wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short));
// NOTE: We are forcing conversion to 16bit sample size on reading // NOTE: We are forcing conversion to 16bit sample size on reading
drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data); drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data);
@ -754,11 +754,11 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int
wave.sampleRate = info.sample_rate; wave.sampleRate = info.sample_rate;
wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short) wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short)
wave.channels = info.channels; wave.channels = info.channels;
wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData)*info.channels; // Independent by channel wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames!
wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short));
// NOTE: Get the number of samples to process (be careful! we ask for number of shorts!) // NOTE: Get the number of samples to process (be careful! we ask for number of shorts, not bytes!)
stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.sampleCount); stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels);
stb_vorbis_close(oggData); stb_vorbis_close(oggData);
} }
else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data");
@ -773,7 +773,7 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int
wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL);
wave.sampleSize = 16; wave.sampleSize = 16;
if (wave.data != NULL) wave.sampleCount = (unsigned int)totalFrameCount*wave.channels; if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount;
else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data");
} }
#endif #endif
@ -791,7 +791,7 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int
{ {
wave.channels = config.channels; wave.channels = config.channels;
wave.sampleRate = config.sampleRate; wave.sampleRate = config.sampleRate;
wave.sampleCount = (int)totalFrameCount*wave.channels; wave.frameCount = (int)totalFrameCount;
} }
else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data");
@ -835,7 +835,7 @@ Sound LoadSoundFromWave(Wave wave)
// First option has been selected, format conversion is done on the loading stage. // First option has been selected, format conversion is done on the loading stage.
// The downside is that it uses more memory if the original sound is u8 or s16. // The downside is that it uses more memory if the original sound is u8 or s16.
ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_uint32 frameCountIn = wave.sampleCount/wave.channels; ma_uint32 frameCountIn = wave.frameCount;
ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate);
if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion");
@ -850,7 +850,7 @@ Sound LoadSoundFromWave(Wave wave)
frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate);
if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion");
sound.sampleCount = frameCount*AUDIO_DEVICE_CHANNELS; sound.frameCount = frameCount;
sound.stream.sampleRate = AUDIO.System.device.sampleRate; sound.stream.sampleRate = AUDIO.System.device.sampleRate;
sound.stream.sampleSize = 32; sound.stream.sampleSize = 32;
sound.stream.channels = AUDIO_DEVICE_CHANNELS; sound.stream.channels = AUDIO_DEVICE_CHANNELS;
@ -908,7 +908,7 @@ bool ExportWave(Wave wave, const char *fileName)
void *fileData = NULL; void *fileData = NULL;
size_t fileDataSize = 0; size_t fileDataSize = 0;
success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL);
if (success) success = (int)drwav_write_pcm_frames(&wav, wave.sampleCount/wave.channels, wave.data); if (success) success = (int)drwav_write_pcm_frames(&wav, wave.frameCount, wave.data);
drwav_result result = drwav_uninit(&wav); drwav_result result = drwav_uninit(&wav);
if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize);
@ -920,7 +920,7 @@ bool ExportWave(Wave wave, const char *fileName)
{ {
// Export raw sample data (without header) // Export raw sample data (without header)
// NOTE: It's up to the user to track wave parameters // NOTE: It's up to the user to track wave parameters
success = SaveFileData(fileName, wave.data, wave.sampleCount*wave.sampleSize/8); success = SaveFileData(fileName, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8);
} }
if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName);
@ -938,7 +938,7 @@ bool ExportWaveAsCode(Wave wave, const char *fileName)
#define TEXT_BYTES_PER_LINE 20 #define TEXT_BYTES_PER_LINE 20
#endif #endif
int waveDataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; int waveDataSize = wave.frameCount*wave.channels*wave.sampleSize/8;
// NOTE: Text data buffer size is estimated considering wave data size in bytes // NOTE: Text data buffer size is estimated considering wave data size in bytes
// and requiring 6 char bytes for every byte: "0x00, " // and requiring 6 char bytes for every byte: "0x00, "
@ -966,7 +966,8 @@ bool ExportWaveAsCode(Wave wave, const char *fileName)
#endif #endif
bytesCount += sprintf(txtData + bytesCount, "// Wave data information\n"); bytesCount += sprintf(txtData + bytesCount, "// Wave data information\n");
bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_COUNT %u\n", varFileName, wave.sampleCount); bytesCount += sprintf(txtData + bytesCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount);
bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_COUNT %u\n", varFileName, wave.frameCount*wave.channels);
bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate);
bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize);
bytesCount += sprintf(txtData + bytesCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); bytesCount += sprintf(txtData + bytesCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels);
@ -1111,7 +1112,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels)
ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_uint32 frameCountIn = wave->sampleCount/wave->channels; ma_uint32 frameCountIn = wave->frameCount;
ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate);
if (frameCount == 0) if (frameCount == 0)
@ -1129,7 +1130,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels)
return; return;
} }
wave->sampleCount = frameCount*channels; wave->frameCount = frameCount;
wave->sampleSize = sampleSize; wave->sampleSize = sampleSize;
wave->sampleRate = sampleRate; wave->sampleRate = sampleRate;
wave->channels = channels; wave->channels = channels;
@ -1142,14 +1143,14 @@ Wave WaveCopy(Wave wave)
{ {
Wave newWave = { 0 }; Wave newWave = { 0 };
newWave.data = RL_MALLOC(wave.sampleCount*wave.sampleSize/8); newWave.data = RL_MALLOC(wave.frameCount*wave.channels*wave.sampleSize/8);
if (newWave.data != NULL) if (newWave.data != NULL)
{ {
// NOTE: Size must be provided in bytes // NOTE: Size must be provided in bytes
memcpy(newWave.data, wave.data, wave.sampleCount*wave.sampleSize/8); memcpy(newWave.data, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8);
newWave.sampleCount = wave.sampleCount; newWave.frameCount = wave.frameCount;
newWave.sampleRate = wave.sampleRate; newWave.sampleRate = wave.sampleRate;
newWave.sampleSize = wave.sampleSize; newWave.sampleSize = wave.sampleSize;
newWave.channels = wave.channels; newWave.channels = wave.channels;
@ -1163,7 +1164,7 @@ Wave WaveCopy(Wave wave)
void WaveCrop(Wave *wave, int initSample, int finalSample) void WaveCrop(Wave *wave, int initSample, int finalSample)
{ {
if ((initSample >= 0) && (initSample < finalSample) && if ((initSample >= 0) && (initSample < finalSample) &&
(finalSample > 0) && ((unsigned int)finalSample < wave->sampleCount)) (finalSample > 0) && ((unsigned int)finalSample < (wave->frameCount*wave->channels)))
{ {
int sampleCount = finalSample - initSample; int sampleCount = finalSample - initSample;
@ -1182,11 +1183,11 @@ void WaveCrop(Wave *wave, int initSample, int finalSample)
// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() // NOTE 2: Sample data allocated should be freed with UnloadWaveSamples()
float *LoadWaveSamples(Wave wave) float *LoadWaveSamples(Wave wave)
{ {
float *samples = (float *)RL_MALLOC(wave.sampleCount*sizeof(float)); float *samples = (float *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(float));
// NOTE: sampleCount is the total number of interlaced samples (including channels) // NOTE: sampleCount is the total number of interlaced samples (including channels)
for (unsigned int i = 0; i < wave.sampleCount; i++) for (unsigned int i = 0; i < wave.frameCount*wave.channels; i++)
{ {
if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f;
else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f; else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f;
@ -1228,7 +1229,7 @@ Music LoadMusicStream(const char *fileName)
if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream()
music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels);
music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount;
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1249,7 +1250,7 @@ Music LoadMusicStream(const char *fileName)
music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
// WARNING: It seems this function returns length in frames, not samples, so we multiply by channels // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels
music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1266,7 +1267,7 @@ Music LoadMusicStream(const char *fileName)
drflac *ctxFlac = (drflac *)music.ctxData; drflac *ctxFlac = (drflac *)music.ctxData;
music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels);
music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount;
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1284,7 +1285,7 @@ Music LoadMusicStream(const char *fileName)
if (result > 0) if (result > 0)
{ {
music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels);
music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3);
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1309,9 +1310,9 @@ Music LoadMusicStream(const char *fileName)
// NOTE: Only stereo is supported for XM // NOTE: Only stereo is supported for XM
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS);
music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo)
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
jar_xm_reset(ctxXm); // make sure we start at the beginning of the song jar_xm_reset(ctxXm); // make sure we start at the beginning of the song
musicLoaded = true; musicLoaded = true;
} }
} }
@ -1330,7 +1331,7 @@ Music LoadMusicStream(const char *fileName)
{ {
// NOTE: Only stereo is supported for MOD // NOTE: Only stereo is supported for MOD
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS);
music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo)
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1367,10 +1368,10 @@ Music LoadMusicStream(const char *fileName)
{ {
// Show some music stream info // Show some music stream info
TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", fileName); TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", fileName);
TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount);
TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate);
TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize);
TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi");
TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount);
} }
return music; return music;
@ -1402,7 +1403,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream()
music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels);
music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount;
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1419,7 +1420,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
drflac *ctxFlac = (drflac *)music.ctxData; drflac *ctxFlac = (drflac *)music.ctxData;
music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels);
music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount;
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1437,7 +1438,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
if (success) if (success)
{ {
music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels);
music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3);
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1459,7 +1460,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
// WARNING: It seems this function returns length in frames, not samples, so we multiply by channels // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels
music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1483,7 +1484,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
// NOTE: Only stereo is supported for XM // NOTE: Only stereo is supported for XM
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2); music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2);
music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo)
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
jar_xm_reset(ctxXm); // make sure we start at the beginning of the song jar_xm_reset(ctxXm); // make sure we start at the beginning of the song
@ -1521,7 +1522,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
// NOTE: Only stereo is supported for MOD // NOTE: Only stereo is supported for MOD
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2); music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2);
music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo)
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
@ -1561,10 +1562,10 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d
{ {
// Show some music stream info // Show some music stream info
TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully"); TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully");
TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount);
TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate);
TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize);
TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi");
TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount);
} }
return music; return music;
@ -1660,29 +1661,22 @@ void UpdateMusicStream(Music music)
{ {
if (music.stream.buffer == NULL) return; if (music.stream.buffer == NULL) return;
#if defined(SUPPORT_FILEFORMAT_XM)
if (music.ctxType == MUSIC_MODULE_XM) jar_xm_set_max_loop_count(music.ctxData, music.looping ? 0 : 1);
#endif
bool streamEnding = false; bool streamEnding = false;
unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2;
// NOTE: Using dynamic allocation because it could require more than 16KB // NOTE: Using dynamic allocation because it could require more than 16KB
void *pcm = RL_CALLOC(subBufferSizeInFrames*music.stream.channels*music.stream.sampleSize/8, 1); void *pcm = RL_CALLOC(subBufferSizeInFrames*music.stream.channels*music.stream.sampleSize/8, 1);
int samplesCount = 0; // Total size of data streamed in L+R samples for xm floats, individual L or R for ogg shorts int frameCountToStream = 0; // Total size of data in frames to be streamed
// TODO: Get the sampleLeft using framesProcessed... but first, get total frames processed correctly... // TODO: Get the framesLeft using framesProcessed... but first, get total frames processed correctly...
//ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels;
int sampleLeft = music.sampleCount - (music.stream.buffer->framesProcessed*music.stream.channels); int framesLeft = music.frameCount - music.stream.buffer->framesProcessed;
if (music.ctxType == MUSIC_MODULE_XM && music.looping) sampleLeft = subBufferSizeInFrames*4;
while (IsAudioStreamProcessed(music.stream)) while (IsAudioStreamProcessed(music.stream))
{ {
if ((sampleLeft/music.stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music.stream.channels; if (framesLeft >= subBufferSizeInFrames) frameCountToStream = subBufferSizeInFrames;
else samplesCount = sampleLeft; else frameCountToStream = framesLeft;
switch (music.ctxType) switch (music.ctxType)
{ {
@ -1690,8 +1684,8 @@ void UpdateMusicStream(Music music)
case MUSIC_AUDIO_WAV: case MUSIC_AUDIO_WAV:
{ {
// NOTE: Returns the number of samples to process (not required) // NOTE: Returns the number of samples to process (not required)
if (music.stream.sampleSize == 16) drwav_read_pcm_frames_s16((drwav *)music.ctxData, samplesCount/music.stream.channels, (short *)pcm); if (music.stream.sampleSize == 16) drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountToStream, (short *)pcm);
else if (music.stream.sampleSize == 32) drwav_read_pcm_frames_f32((drwav *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm); else if (music.stream.sampleSize == 32) drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountToStream, (float *)pcm);
} break; } break;
#endif #endif
@ -1699,7 +1693,7 @@ void UpdateMusicStream(Music music)
case MUSIC_AUDIO_OGG: case MUSIC_AUDIO_OGG:
{ {
// NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!)
stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)pcm, samplesCount); stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)pcm, frameCountToStream*music.stream.channels);
} break; } break;
#endif #endif
@ -1707,15 +1701,14 @@ void UpdateMusicStream(Music music)
case MUSIC_AUDIO_FLAC: case MUSIC_AUDIO_FLAC:
{ {
// NOTE: Returns the number of samples to process (not required) // NOTE: Returns the number of samples to process (not required)
drflac_read_pcm_frames_s16((drflac *)music.ctxData, samplesCount, (short *)pcm); drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountToStream*music.stream.channels, (short *)pcm);
} break; } break;
#endif #endif
#if defined(SUPPORT_FILEFORMAT_MP3) #if defined(SUPPORT_FILEFORMAT_MP3)
case MUSIC_AUDIO_MP3: case MUSIC_AUDIO_MP3:
{ {
// NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountToStream, (float *)pcm);
drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm);
} break; } break;
#endif #endif
@ -1723,9 +1716,9 @@ void UpdateMusicStream(Music music)
case MUSIC_MODULE_XM: case MUSIC_MODULE_XM:
{ {
// NOTE: Internally we consider 2 channels generation, so samplesCount/2 // NOTE: Internally we consider 2 channels generation, so samplesCount/2
if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)pcm, samplesCount/2); if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)pcm, frameCountToStream);
else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)pcm, samplesCount/2); else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)pcm, frameCountToStream);
else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)pcm, samplesCount/2); else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)pcm, frameCountToStream);
} break; } break;
#endif #endif
@ -1733,22 +1726,17 @@ void UpdateMusicStream(Music music)
case MUSIC_MODULE_MOD: case MUSIC_MODULE_MOD:
{ {
// NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2
jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)pcm, samplesCount/2, 0); jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)pcm, frameCountToStream, 0);
} break; } break;
#endif #endif
default: break; default: break;
} }
UpdateAudioStream(music.stream, pcm, samplesCount); UpdateAudioStream(music.stream, pcm, frameCountToStream);
framesLeft -= frameCountToStream;
if ((music.ctxType == MUSIC_MODULE_XM) || music.ctxType == MUSIC_MODULE_MOD) if (framesLeft <= 0)
{
if (samplesCount > 1) sampleLeft -= samplesCount/2;
else sampleLeft -= samplesCount;
}
else sampleLeft -= samplesCount;
if (sampleLeft <= 0)
{ {
streamEnding = true; streamEnding = true;
break; break;
@ -1795,7 +1783,7 @@ float GetMusicTimeLength(Music music)
{ {
float totalSeconds = 0.0f; float totalSeconds = 0.0f;
totalSeconds = (float)music.sampleCount/(music.stream.sampleRate*music.stream.channels); totalSeconds = (float)music.frameCount/music.stream.sampleRate;
return totalSeconds; return totalSeconds;
} }
@ -1803,22 +1791,24 @@ float GetMusicTimeLength(Music music)
// Get current music time played (in seconds) // Get current music time played (in seconds)
float GetMusicTimePlayed(Music music) float GetMusicTimePlayed(Music music)
{ {
#if defined(SUPPORT_FILEFORMAT_XM)
if (music.ctxType == MUSIC_MODULE_XM)
{
uint64_t samples = 0;
jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &samples);
samples = samples % (music.sampleCount);
return (float)(samples)/(music.stream.sampleRate*music.stream.channels);
}
#endif
float secondsPlayed = 0.0f; float secondsPlayed = 0.0f;
if (music.stream.buffer != NULL) if (music.stream.buffer != NULL)
{ {
//ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; #if defined(SUPPORT_FILEFORMAT_XM)
unsigned int samplesPlayed = music.stream.buffer->framesProcessed*music.stream.channels; if (music.ctxType == MUSIC_MODULE_XM)
secondsPlayed = (float)samplesPlayed/(music.stream.sampleRate*music.stream.channels); {
uint64_t framesPlayed = 0;
jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &framesPlayed);
secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
}
else
#endif
{
//ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels;
unsigned int framesPlayed = music.stream.buffer->framesProcessed;
secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
}
} }
return secondsPlayed; return secondsPlayed;
@ -1867,7 +1857,7 @@ void UnloadAudioStream(AudioStream stream)
// Update audio stream buffers with data // Update audio stream buffers with data
// NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue // NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue
// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioStreamProcessed() // NOTE 2: To unqueue a buffer it needs to be processed: IsAudioStreamProcessed()
void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) void UpdateAudioStream(AudioStream stream, const void *data, int frameCount)
{ {
if (stream.buffer != NULL) if (stream.buffer != NULL)
{ {
@ -1896,11 +1886,11 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount)
// Does this API expect a whole buffer to be updated in one go? // Does this API expect a whole buffer to be updated in one go?
// Assuming so, but if not will need to change this logic. // Assuming so, but if not will need to change this logic.
if (subBufferSizeInFrames >= (ma_uint32)samplesCount/stream.channels) if (subBufferSizeInFrames >= (ma_uint32)frameCount)
{ {
ma_uint32 framesToWrite = subBufferSizeInFrames; ma_uint32 framesToWrite = subBufferSizeInFrames;
if (framesToWrite > ((ma_uint32)samplesCount/stream.channels)) framesToWrite = (ma_uint32)samplesCount/stream.channels; if (framesToWrite > (ma_uint32)frameCount) framesToWrite = (ma_uint32)frameCount;
ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8);
memcpy(subBuffer, data, bytesToWrite); memcpy(subBuffer, data, bytesToWrite);

View File

@ -78,49 +78,42 @@
#endif #endif
#endif #endif
// Wave type, defines audio wave data // Wave, audio wave data
typedef struct Wave { typedef struct Wave {
unsigned int sampleCount; // Total number of samples unsigned int frameCount; // Total number of frames (considering channels)
unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleRate; // Frequency (samples per second)
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo) unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
void *data; // Buffer data pointer void *data; // Buffer data pointer
} Wave; } Wave;
typedef struct rAudioBuffer rAudioBuffer; typedef struct rAudioBuffer rAudioBuffer;
// Audio stream type // AudioStream, custom audio stream
// NOTE: Useful to create custom audio streams not bound to a specific file
typedef struct AudioStream { typedef struct AudioStream {
unsigned int sampleRate; // Frequency (samples per second) rAudioBuffer *buffer; // Pointer to internal data used by the audio system
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo)
rAudioBuffer *buffer; // Pointer to internal data used by the audio system unsigned int sampleRate; // Frequency (samples per second)
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
} AudioStream; } AudioStream;
// Sound source type // Sound
typedef struct Sound { typedef struct Sound {
unsigned int sampleCount; // Total number of samples AudioStream stream; // Audio stream
AudioStream stream; // Audio stream unsigned int frameCount; // Total number of frames (considering channels)
} Sound; } Sound;
// Music stream type (audio file streaming from memory) // Music, audio stream, anything longer than ~10 seconds should be streamed
// NOTE: Anything longer than ~10 seconds should be streamed
typedef struct Music { typedef struct Music {
int ctxType; // Type of music context (audio filetype) AudioStream stream; // Audio stream
void *ctxData; // Audio context data, depends on type unsigned int frameCount; // Total number of frames (considering channels)
bool looping; // Music looping enable
bool looping; // Music looping enable int ctxType; // Type of music context (audio filetype)
unsigned int sampleCount; // Total number of samples void *ctxData; // Audio context data, depends on type
AudioStream stream; // Audio stream
} Music; } Music;
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
#endif
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Global Variables Definition // Global Variables Definition
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@ -130,6 +123,10 @@ extern "C" { // Prevents name mangling of functions
// Module Functions Declaration // Module Functions Declaration
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
#endif
// Audio device management functions // Audio device management functions
void InitAudioDevice(void); // Initialize audio device and context void InitAudioDevice(void); // Initialize audio device and context
void CloseAudioDevice(void); // Close the audio device and context void CloseAudioDevice(void); // Close the audio device and context

View File

@ -423,10 +423,10 @@ typedef struct BoundingBox {
// Wave, audio wave data // Wave, audio wave data
typedef struct Wave { typedef struct Wave {
unsigned int sampleCount; // Total number of samples (considering channels!) unsigned int frameCount; // Total number of frames (considering channels)
unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleRate; // Frequency (samples per second)
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo) unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
void *data; // Buffer data pointer void *data; // Buffer data pointer
} Wave; } Wave;
@ -438,19 +438,19 @@ typedef struct AudioStream {
unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleRate; // Frequency (samples per second)
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo) unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
} AudioStream; } AudioStream;
// Sound // Sound
typedef struct Sound { typedef struct Sound {
AudioStream stream; // Audio stream AudioStream stream; // Audio stream
unsigned int sampleCount; // Total number of samples unsigned int frameCount; // Total number of frames (considering channels)
} Sound; } Sound;
// Music, audio stream, anything longer than ~10 seconds should be streamed // Music, audio stream, anything longer than ~10 seconds should be streamed
typedef struct Music { typedef struct Music {
AudioStream stream; // Audio stream AudioStream stream; // Audio stream
unsigned int sampleCount; // Total number of samples unsigned int frameCount; // Total number of frames (considering channels)
bool looping; // Music looping enable bool looping; // Music looping enable
int ctxType; // Type of music context (audio filetype) int ctxType; // Type of music context (audio filetype)
@ -1513,7 +1513,7 @@ RLAPI float GetMusicTimePlayed(Music music); // Get cur
// AudioStream management functions // AudioStream management functions
RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data)
RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory
RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int framesCount); // Update audio stream buffers with data
RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill
RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream
RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream