mirror of
https://github.com/raysan5/raylib.git
synced 2025-10-04 17:06:27 +00:00
Add support for pitch shifting.
This commit should bring the mini_al backend up to feature parity with the OpenAL backend.
This commit is contained in:
189
src/audio.c
189
src/audio.c
@@ -72,7 +72,9 @@
|
|||||||
#define SUPPORT_FILEFORMAT_MOD
|
#define SUPPORT_FILEFORMAT_MOD
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef USE_MINI_AL
|
||||||
#define USE_MINI_AL 1 // Set to 1 to use mini_al; 0 to use OpenAL.
|
#define USE_MINI_AL 1 // Set to 1 to use mini_al; 0 to use OpenAL.
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(AUDIO_STANDALONE)
|
#if defined(AUDIO_STANDALONE)
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
@@ -214,25 +216,24 @@ void TraceLog(int msgType, const char *text, ...); // Show trace lo
|
|||||||
typedef struct SoundData SoundData;
|
typedef struct SoundData SoundData;
|
||||||
struct SoundData
|
struct SoundData
|
||||||
{
|
{
|
||||||
mal_format format;
|
mal_dsp dsp; // Necessary for pitch shift. This is an optimized passthrough when the pitch == 1.
|
||||||
mal_uint32 channels;
|
|
||||||
mal_uint32 sampleRate;
|
|
||||||
mal_uint32 frameCount;
|
|
||||||
mal_uint32 frameCursorPos; // Keeps track of the next frame to read when mixing
|
|
||||||
float volume;
|
float volume;
|
||||||
float pitch;
|
float pitch;
|
||||||
bool playing;
|
bool playing;
|
||||||
bool paused;
|
bool paused;
|
||||||
bool looping;
|
bool looping;
|
||||||
|
unsigned int frameCursorPos; // Keeps track of the next frame to read when mixing
|
||||||
|
unsigned int bufferSizeInFrames;
|
||||||
SoundData* next;
|
SoundData* next;
|
||||||
SoundData* prev;
|
SoundData* prev;
|
||||||
mal_uint8 data[1]; // Raw audio data.
|
unsigned char data[1]; // Raw audio data.
|
||||||
};
|
};
|
||||||
|
|
||||||
// AudioStreamData
|
// AudioStreamData
|
||||||
typedef struct AudioStreamData AudioStreamData;
|
typedef struct AudioStreamData AudioStreamData;
|
||||||
struct AudioStreamData {
|
struct AudioStreamData
|
||||||
mal_dsp dsp; // AudioStream data needs to flow through a persistent conversion pipeline. Not doing this will result in glitches between buffer updates.
|
{
|
||||||
|
mal_dsp dsp; // AudioStream data needs to flow through a persistent conversion pipeline. Not doing this will result in glitches between buffer updates.
|
||||||
float volume;
|
float volume;
|
||||||
float pitch;
|
float pitch;
|
||||||
bool playing;
|
bool playing;
|
||||||
@@ -250,7 +251,7 @@ static mal_device device;
|
|||||||
static mal_bool32 isAudioInitialized = MAL_FALSE;
|
static mal_bool32 isAudioInitialized = MAL_FALSE;
|
||||||
static float masterVolume = 1;
|
static float masterVolume = 1;
|
||||||
static mal_mutex soundLock;
|
static mal_mutex soundLock;
|
||||||
static SoundData* firstSound; // Sounds are tracked in a linked list.
|
static SoundData* firstSound; // Sounds are tracked in a linked list.
|
||||||
static SoundData* lastSound;
|
static SoundData* lastSound;
|
||||||
static AudioStreamData* firstAudioStream;
|
static AudioStreamData* firstAudioStream;
|
||||||
static AudioStreamData* lastAudioStream;
|
static AudioStreamData* lastAudioStream;
|
||||||
@@ -286,6 +287,9 @@ static void RemoveSound(SoundData* internalSound)
|
|||||||
} else {
|
} else {
|
||||||
internalSound->next->prev = internalSound->prev;
|
internalSound->next->prev = internalSound->prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalSound->prev = NULL;
|
||||||
|
internalSound->next = NULL;
|
||||||
}
|
}
|
||||||
mal_mutex_unlock(&soundLock);
|
mal_mutex_unlock(&soundLock);
|
||||||
}
|
}
|
||||||
@@ -321,6 +325,9 @@ static void RemoveAudioStream(AudioStreamData* internalAudioStream)
|
|||||||
} else {
|
} else {
|
||||||
internalAudioStream->next->prev = internalAudioStream->prev;
|
internalAudioStream->next->prev = internalAudioStream->prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalAudioStream->prev = NULL;
|
||||||
|
internalAudioStream->next = NULL;
|
||||||
}
|
}
|
||||||
mal_mutex_unlock(&soundLock);
|
mal_mutex_unlock(&soundLock);
|
||||||
}
|
}
|
||||||
@@ -333,6 +340,21 @@ static void OnLog_MAL(mal_context* pContext, mal_device* pDevice, const char* me
|
|||||||
TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors.
|
TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation.
|
||||||
|
//
|
||||||
|
// framesOut is both an input and an output. It will be initially filled with zeros outside of this function.
|
||||||
|
static void MixFrames(float* framesOut, const float* framesIn, mal_uint32 frameCount, float localVolume)
|
||||||
|
{
|
||||||
|
for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) {
|
||||||
|
for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) {
|
||||||
|
float* frameOut = framesOut + (iFrame * device.channels);
|
||||||
|
float* frameIn = framesIn + (iFrame * device.channels);
|
||||||
|
|
||||||
|
frameOut[iChannel] += frameIn[iChannel] * masterVolume * localVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameCount, void* pFramesOut)
|
static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameCount, void* pFramesOut)
|
||||||
{
|
{
|
||||||
// This is where all of the mixing takes place.
|
// This is where all of the mixing takes place.
|
||||||
@@ -345,7 +367,7 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC
|
|||||||
// want to consider how you might want to avoid this.
|
// want to consider how you might want to avoid this.
|
||||||
mal_mutex_lock(&soundLock);
|
mal_mutex_lock(&soundLock);
|
||||||
{
|
{
|
||||||
float* pFramesOutF = (float*)pFramesOut; // <-- Just for convenience.
|
float* framesOutF = (float*)pFramesOut; // <-- Just for convenience.
|
||||||
|
|
||||||
// Sounds.
|
// Sounds.
|
||||||
for (SoundData* internalSound = firstSound; internalSound != NULL; internalSound = internalSound->next)
|
for (SoundData* internalSound = firstSound; internalSound != NULL; internalSound = internalSound->next)
|
||||||
@@ -365,33 +387,48 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep reading until the end of the buffer, or we've already read as much as is allowed.
|
// Just read as much data we can from the stream.
|
||||||
mal_uint32 framesToRead = (frameCount - framesRead);
|
mal_uint32 framesToRead = (frameCount - framesRead);
|
||||||
mal_uint32 framesRemaining = (internalSound->frameCount - internalSound->frameCursorPos);
|
while (framesToRead > 0) {
|
||||||
if (framesToRead > framesRemaining) {
|
float tempBuffer[1024]; // 512 frames for stereo.
|
||||||
framesToRead = framesRemaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is where the real mixing takes place. This can be optimized. This assumes the device and sound are of the same format.
|
mal_uint32 framesToReadRightNow = framesToRead;
|
||||||
//
|
if (framesToReadRightNow > sizeof(tempBuffer)/DEVICE_CHANNELS) {
|
||||||
// TODO: Implement pitching.
|
framesToReadRightNow = sizeof(tempBuffer)/DEVICE_CHANNELS;
|
||||||
for (mal_uint32 iFrame = 0; iFrame < framesToRead; ++iFrame) {
|
}
|
||||||
float* pFrameOut = pFramesOutF + ((framesRead+iFrame) * device.channels);
|
|
||||||
float* pFrameIn = ((float*)internalSound->data) + ((internalSound->frameCursorPos+iFrame) * device.channels);
|
|
||||||
|
|
||||||
for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) {
|
// If we're not looping, we need to make sure we flush the internal buffers of the DSP pipeline to ensure we get the
|
||||||
pFrameOut[iChannel] += pFrameIn[iChannel] * masterVolume * internalSound->volume;
|
// last few samples.
|
||||||
|
mal_bool32 flushDSP = !internalSound->looping;
|
||||||
|
|
||||||
|
mal_uint32 framesJustRead = mal_dsp_read_frames_ex(&internalSound->dsp, framesToReadRightNow, tempBuffer, flushDSP);
|
||||||
|
if (framesJustRead > 0) {
|
||||||
|
float* framesOut = framesOutF + (framesRead * device.channels);
|
||||||
|
float* framesIn = tempBuffer;
|
||||||
|
MixFrames(framesOut, framesIn, framesJustRead, internalSound->volume);
|
||||||
|
|
||||||
|
framesToRead -= framesJustRead;
|
||||||
|
framesRead += framesJustRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we weren't able to read all the frames we requested, break.
|
||||||
|
if (framesJustRead < framesToReadRightNow) {
|
||||||
|
if (!internalSound->looping) {
|
||||||
|
internalSound->playing = MAL_FALSE;
|
||||||
|
internalSound->frameCursorPos = 0;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Should never get here, but just for safety, move the cursor position back to the start and continue the loop.
|
||||||
|
internalSound->frameCursorPos = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
framesRead += framesToRead;
|
// If for some reason we weren't able to read every frame we'll need to break from the loop. Not doing this could
|
||||||
internalSound->frameCursorPos += framesToRead;
|
// theoretically put us into an infinite loop.
|
||||||
|
if (framesToRead > 0) {
|
||||||
// If we've reached the end of the sound's internal buffer we do one of two things: loop back to the start, or just stop.
|
break;
|
||||||
if (framesToRead == framesRemaining) {
|
|
||||||
if (!internalSound->looping) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,17 +464,9 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC
|
|||||||
|
|
||||||
mal_uint32 framesJustRead = mal_dsp_read_frames(&internalData->dsp, framesToReadRightNow, tempBuffer);
|
mal_uint32 framesJustRead = mal_dsp_read_frames(&internalData->dsp, framesToReadRightNow, tempBuffer);
|
||||||
if (framesJustRead > 0) {
|
if (framesJustRead > 0) {
|
||||||
// This is where the real mixing takes place. This can be optimized. This assumes the device and sound are of the same format.
|
float* framesOut = framesOutF + (framesRead * device.channels);
|
||||||
//
|
float* framesIn = tempBuffer;
|
||||||
// TODO: Implement pitching.
|
MixFrames(framesOut, framesIn, framesJustRead, internalData->volume);
|
||||||
for (mal_uint32 iFrame = 0; iFrame < framesToRead; ++iFrame) {
|
|
||||||
float* pFrameOut = pFramesOutF + ((framesRead+iFrame) * device.channels);
|
|
||||||
float* pFrameIn = tempBuffer + (iFrame * device.channels);
|
|
||||||
|
|
||||||
for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) {
|
|
||||||
pFrameOut[iChannel] += pFrameIn[iChannel] * masterVolume * internalData->volume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
framesToRead -= framesJustRead;
|
framesToRead -= framesJustRead;
|
||||||
framesRead += framesJustRead;
|
framesRead += framesJustRead;
|
||||||
@@ -667,6 +696,39 @@ Sound LoadSound(const char *fileName)
|
|||||||
return sound;
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if USE_MINI_AL
|
||||||
|
static mal_uint32 Sound_OnDSPRead(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData)
|
||||||
|
{
|
||||||
|
SoundData* internalData = (SoundData*)pUserData;
|
||||||
|
|
||||||
|
mal_uint32 frameSizeInBytes = mal_get_sample_size_in_bytes(internalData->dsp.config.formatIn)*internalData->dsp.config.channelsIn;
|
||||||
|
|
||||||
|
// Just keep reading as much as we can. Do not zero fill excess data in the output buffer.
|
||||||
|
mal_uint32 framesRead = 0;
|
||||||
|
while (framesRead < frameCount)
|
||||||
|
{
|
||||||
|
mal_uint32 framesRemaining = internalData->bufferSizeInFrames - internalData->frameCursorPos;
|
||||||
|
mal_uint32 framesToRead = (frameCount - framesRead);
|
||||||
|
if (framesToRead > framesRemaining) {
|
||||||
|
framesToRead = framesRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), internalData->data + (internalData->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes);
|
||||||
|
internalData->frameCursorPos += framesToRead;
|
||||||
|
framesRead += framesToRead;
|
||||||
|
|
||||||
|
// If we've reached the end of the buffer but we're not looping, return.
|
||||||
|
if (framesToRead == framesRemaining) {
|
||||||
|
if (!internalData->looping) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return framesRead;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Load sound from wave data
|
// Load sound from wave data
|
||||||
// NOTE: Wave data must be unallocated manually
|
// NOTE: Wave data must be unallocated manually
|
||||||
Sound LoadSoundFromWave(Wave wave)
|
Sound LoadSoundFromWave(Wave wave)
|
||||||
@@ -702,16 +764,28 @@ Sound LoadSoundFromWave(Wave wave)
|
|||||||
TraceLog(LOG_ERROR, "LoadSoundFromWave() : Format conversion failed.");
|
TraceLog(LOG_ERROR, "LoadSoundFromWave() : Format conversion failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
internalSound->format = DEVICE_FORMAT;
|
// We run audio data through a sample rate converter in order to support pitch shift. By default this will use an optimized passthrough
|
||||||
internalSound->channels = DEVICE_CHANNELS;
|
// algorithm, but when the application changes the pitch it will change to a less optimal linear SRC.
|
||||||
internalSound->sampleRate = DEVICE_SAMPLE_RATE;
|
mal_dsp_config dspConfig;
|
||||||
internalSound->frameCount = frameCount;
|
memset(&dspConfig, 0, sizeof(dspConfig));
|
||||||
internalSound->frameCursorPos = 0;
|
dspConfig.formatIn = DEVICE_FORMAT;
|
||||||
|
dspConfig.formatOut = DEVICE_FORMAT;
|
||||||
|
dspConfig.channelsIn = DEVICE_CHANNELS;
|
||||||
|
dspConfig.channelsOut = DEVICE_CHANNELS;
|
||||||
|
dspConfig.sampleRateIn = DEVICE_SAMPLE_RATE;
|
||||||
|
dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE;
|
||||||
|
mal_result resultMAL = mal_dsp_init(&dspConfig, Sound_OnDSPRead, internalSound, &internalSound->dsp);
|
||||||
|
if (resultMAL != MAL_SUCCESS) {
|
||||||
|
TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to create data conversion pipeline");
|
||||||
|
}
|
||||||
|
|
||||||
internalSound->volume = 1;
|
internalSound->volume = 1;
|
||||||
internalSound->pitch = 1;
|
internalSound->pitch = 1;
|
||||||
internalSound->playing = 0;
|
internalSound->playing = 0;
|
||||||
internalSound->paused = 0;
|
internalSound->paused = 0;
|
||||||
internalSound->looping = 0;
|
internalSound->looping = 0;
|
||||||
|
internalSound->bufferSizeInFrames = frameCount;
|
||||||
|
internalSound->frameCursorPos = 0;
|
||||||
AppendSound(internalSound);
|
AppendSound(internalSound);
|
||||||
|
|
||||||
sound.handle = (void*)internalSound;
|
sound.handle = (void*)internalSound;
|
||||||
@@ -816,9 +890,8 @@ void UpdateSound(Sound sound, const void *data, int samplesCount)
|
|||||||
internalSound->paused = false;
|
internalSound->paused = false;
|
||||||
internalSound->frameCursorPos = 0;
|
internalSound->frameCursorPos = 0;
|
||||||
|
|
||||||
// TODO: May want to lock/unlock this since this data buffer is read at mixing time. However, this puts a mutex in
|
// TODO: May want to lock/unlock this since this data buffer is read at mixing time.
|
||||||
// in the mixing code which makes it no longer real-time. This is likely not a critical issue for this project, though.
|
memcpy(internalSound->data, data, samplesCount*internalSound->dsp.config.channelsIn*mal_get_sample_size_in_bytes(internalSound->dsp.config.formatIn));
|
||||||
memcpy(internalSound->data, data, samplesCount*internalSound->channels*mal_get_sample_size_in_bytes(internalSound->format));
|
|
||||||
#else
|
#else
|
||||||
ALint sampleRate, sampleSize, channels;
|
ALint sampleRate, sampleSize, channels;
|
||||||
alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate);
|
alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate);
|
||||||
@@ -986,6 +1059,11 @@ void SetSoundPitch(Sound sound, float pitch)
|
|||||||
}
|
}
|
||||||
|
|
||||||
internalSound->pitch = pitch;
|
internalSound->pitch = pitch;
|
||||||
|
|
||||||
|
// Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches
|
||||||
|
// will make the sound faster; lower pitches make it slower.
|
||||||
|
mal_uint32 newOutputSampleRate = (mal_uint32)((((float)internalSound->dsp.config.sampleRateOut / (float)internalSound->dsp.config.sampleRateIn) / pitch) * internalSound->dsp.config.sampleRateIn);
|
||||||
|
mal_dsp_set_output_sample_rate(&internalSound->dsp, newOutputSampleRate);
|
||||||
#else
|
#else
|
||||||
alSourcef(sound.source, AL_PITCH, pitch);
|
alSourcef(sound.source, AL_PITCH, pitch);
|
||||||
#endif
|
#endif
|
||||||
@@ -2013,7 +2091,18 @@ void SetAudioStreamPitch(AudioStream stream, float pitch)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pitch == 0)
|
||||||
|
{
|
||||||
|
TraceLog(LOG_ERROR, "Attempting to set pitch to 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
internalData->pitch = pitch;
|
internalData->pitch = pitch;
|
||||||
|
|
||||||
|
// Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches
|
||||||
|
// will make the sound faster; lower pitches make it slower.
|
||||||
|
mal_uint32 newOutputSampleRate = (mal_uint32)((((float)internalData->dsp.config.sampleRateOut / (float)internalData->dsp.config.sampleRateIn) / pitch) * internalData->dsp.config.sampleRateIn);
|
||||||
|
mal_dsp_set_output_sample_rate(&internalData->dsp, newOutputSampleRate);
|
||||||
#else
|
#else
|
||||||
alSourcef(stream.source, AL_PITCH, pitch);
|
alSourcef(stream.source, AL_PITCH, pitch);
|
||||||
#endif
|
#endif
|
||||||
|
93
src/external/mini_al.h
vendored
93
src/external/mini_al.h
vendored
@@ -570,7 +570,6 @@ struct mal_src
|
|||||||
mal_src_config config;
|
mal_src_config config;
|
||||||
mal_src_read_proc onRead;
|
mal_src_read_proc onRead;
|
||||||
void* pUserData;
|
void* pUserData;
|
||||||
float ratio;
|
|
||||||
float bin[256];
|
float bin[256];
|
||||||
mal_src_cache cache; // <-- For simplifying and optimizing client -> memory reading.
|
mal_src_cache cache; // <-- For simplifying and optimizing client -> memory reading.
|
||||||
|
|
||||||
@@ -1353,6 +1352,12 @@ static inline mal_device_config mal_device_config_init_playback(mal_format forma
|
|||||||
// Initializes a sample rate conversion object.
|
// Initializes a sample rate conversion object.
|
||||||
mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC);
|
mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC);
|
||||||
|
|
||||||
|
// Dynamically adjusts the output sample rate.
|
||||||
|
//
|
||||||
|
// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this
|
||||||
|
// is not acceptable you will need to use your own algorithm.
|
||||||
|
mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOut);
|
||||||
|
|
||||||
// Reads a number of frames.
|
// Reads a number of frames.
|
||||||
//
|
//
|
||||||
// Returns the number of frames actually read.
|
// Returns the number of frames actually read.
|
||||||
@@ -1376,6 +1381,12 @@ mal_uint32 mal_src_read_frames_ex(mal_src* pSRC, mal_uint32 frameCount, void* pF
|
|||||||
// Initializes a DSP object.
|
// Initializes a DSP object.
|
||||||
mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP);
|
mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP);
|
||||||
|
|
||||||
|
// Dynamically adjusts the output sample rate.
|
||||||
|
//
|
||||||
|
// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this
|
||||||
|
// is not acceptable you will need to use your own algorithm.
|
||||||
|
mal_result mal_dsp_set_output_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut);
|
||||||
|
|
||||||
// Reads a number of frames and runs them through the DSP processor.
|
// Reads a number of frames and runs them through the DSP processor.
|
||||||
//
|
//
|
||||||
// This this _not_ flush the internal buffers which means you may end up with a few less frames than you may expect. Look at
|
// This this _not_ flush the internal buffers which means you may end up with a few less frames than you may expect. Look at
|
||||||
@@ -9313,21 +9324,27 @@ mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void*
|
|||||||
pSRC->onRead = onRead;
|
pSRC->onRead = onRead;
|
||||||
pSRC->pUserData = pUserData;
|
pSRC->pUserData = pUserData;
|
||||||
|
|
||||||
// If the in and out sample rates are the same, fall back to the passthrough algorithm.
|
|
||||||
if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) {
|
|
||||||
pSRC->config.algorithm = mal_src_algorithm_none;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pSRC->config.cacheSizeInFrames > MAL_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) {
|
if (pSRC->config.cacheSizeInFrames > MAL_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) {
|
||||||
pSRC->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES;
|
pSRC->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
pSRC->ratio = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut;
|
|
||||||
|
|
||||||
mal_src_cache_init(pSRC, &pSRC->cache);
|
mal_src_cache_init(pSRC, &pSRC->cache);
|
||||||
return MAL_SUCCESS;
|
return MAL_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOut)
|
||||||
|
{
|
||||||
|
if (pSRC == NULL) return MAL_INVALID_ARGS;
|
||||||
|
|
||||||
|
// Must have a sample rate of > 0.
|
||||||
|
if (sampleRateOut == 0) {
|
||||||
|
return MAL_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pSRC->config.sampleRateOut = sampleRateOut;
|
||||||
|
return MAL_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut)
|
mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut)
|
||||||
{
|
{
|
||||||
return mal_src_read_frames_ex(pSRC, frameCount, pFramesOut, MAL_FALSE);
|
return mal_src_read_frames_ex(pSRC, frameCount, pFramesOut, MAL_FALSE);
|
||||||
@@ -9337,6 +9354,13 @@ mal_uint32 mal_src_read_frames_ex(mal_src* pSRC, mal_uint32 frameCount, void* pF
|
|||||||
{
|
{
|
||||||
if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0;
|
if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0;
|
||||||
|
|
||||||
|
mal_src_algorithm algorithm = pSRC->config.algorithm;
|
||||||
|
|
||||||
|
// Always use passthrough if the sample rates are the same.
|
||||||
|
if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) {
|
||||||
|
algorithm = mal_src_algorithm_none;
|
||||||
|
}
|
||||||
|
|
||||||
// Could just use a function pointer instead of a switch for this...
|
// Could just use a function pointer instead of a switch for this...
|
||||||
switch (pSRC->config.algorithm)
|
switch (pSRC->config.algorithm)
|
||||||
{
|
{
|
||||||
@@ -9408,7 +9432,7 @@ mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void
|
|||||||
pSRC->linear.isNextFramesLoaded = MAL_TRUE;
|
pSRC->linear.isNextFramesLoaded = MAL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
float factor = pSRC->ratio;
|
float factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut;
|
||||||
|
|
||||||
mal_uint32 totalFramesRead = 0;
|
mal_uint32 totalFramesRead = 0;
|
||||||
while (frameCount > 0) {
|
while (frameCount > 0) {
|
||||||
@@ -9995,6 +10019,57 @@ mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void*
|
|||||||
return MAL_SUCCESS;
|
return MAL_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mal_result mal_dsp_set_output_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut)
|
||||||
|
{
|
||||||
|
if (pDSP == NULL) return MAL_INVALID_ARGS;
|
||||||
|
|
||||||
|
// Must have a sample rate of > 0.
|
||||||
|
if (sampleRateOut == 0) {
|
||||||
|
return MAL_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pDSP->config.sampleRateOut = sampleRateOut;
|
||||||
|
|
||||||
|
// If we already have an SRC pipeline initialized we do _not_ want to re-create it. Instead we adjust it. If we didn't previously
|
||||||
|
// have an SRC pipeline in place we'll need to initialize it.
|
||||||
|
if (pDSP->isSRCRequired) {
|
||||||
|
if (pDSP->config.sampleRateIn != pDSP->config.sampleRateOut) {
|
||||||
|
mal_src_set_output_sample_rate(&pDSP->src, sampleRateOut);
|
||||||
|
} else {
|
||||||
|
pDSP->isSRCRequired = MAL_FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We may need a new SRC pipeline.
|
||||||
|
if (pDSP->config.sampleRateIn != pDSP->config.sampleRateOut) {
|
||||||
|
pDSP->isSRCRequired = MAL_TRUE;
|
||||||
|
|
||||||
|
mal_src_config srcConfig;
|
||||||
|
srcConfig.sampleRateIn = pDSP->config.sampleRateIn;
|
||||||
|
srcConfig.sampleRateOut = pDSP->config.sampleRateOut;
|
||||||
|
srcConfig.formatIn = pDSP->config.formatIn;
|
||||||
|
srcConfig.formatOut = mal_format_f32;
|
||||||
|
srcConfig.channels = pDSP->config.channelsIn;
|
||||||
|
srcConfig.algorithm = mal_src_algorithm_linear;
|
||||||
|
srcConfig.cacheSizeInFrames = pDSP->config.cacheSizeInFrames;
|
||||||
|
mal_result result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pDSP->isSRCRequired = MAL_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update whether or not the pipeline is a passthrough.
|
||||||
|
if (pDSP->config.formatIn == pDSP->config.formatOut && pDSP->config.channelsIn == pDSP->config.channelsOut && pDSP->config.sampleRateIn == pDSP->config.sampleRateOut && !pDSP->isChannelMappingRequired) {
|
||||||
|
pDSP->isPassthrough = MAL_TRUE;
|
||||||
|
} else {
|
||||||
|
pDSP->isPassthrough = MAL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MAL_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut)
|
mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut)
|
||||||
{
|
{
|
||||||
return mal_dsp_read_frames_ex(pDSP, frameCount, pFramesOut, MAL_FALSE);
|
return mal_dsp_read_frames_ex(pDSP, frameCount, pFramesOut, MAL_FALSE);
|
||||||
|
Reference in New Issue
Block a user